Commit graph

395 commits

Author SHA1 Message Date
Arjun
5b2ecc6720 added stop button in video call 2026-07-04 13:24:32 +05:30
Arjun
af24596591 phase 1 improvements to reduce latency: smart endpointing, streaming tts, early clause speech, ack 2026-07-04 12:51:06 +05:30
Arjun
e9ee8fd975 popout and full screen work as expected 2026-07-04 12:06:32 +05:30
Arjun
d2501c4f4d video mode popout over the app 2026-07-04 00:25:55 +05:30
Arjun
1edc1b17f1 screen share pop out when I switch to a different app 2026-07-04 00:09:46 +05:30
Arjun
48a3ff6a36 added screen share 2026-07-03 23:47:16 +05:30
Arjun
6f901095ff initial commit of video mode 2026-07-03 22:29:10 +05:30
Arjun
930e377f34 feat(channels): model selection from WhatsApp/Telegram
New bridge commands: "model" lists the available models (same catalog as
the desktop picker), "model N" / "model <name>" sets a per-sender
override passed as agent.overrides.model on every turn, and
"model default" resets to the app default. In-memory scope: resets on
app restart, never sticks a session to a bad model.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 20:23:04 +05:30
Arjun
c80a09daba feat(channels): mobile bridge — chat with Rowboat from WhatsApp/Telegram
Adds a transport-agnostic messaging bridge in @x/core that drives the
session/turn runtime from your phone: WhatsApp links as a companion
device via Baileys QR pairing (self-chat is the command channel),
Telegram long-polls the user's own bot token. Commands: list, resume N,
new, status, stop; anything else runs a turn in the current session and
replies with the final assistant text. Ask-human questions are relayed
and answerable from the phone.

- packages/core/src/channels/: bridge (commands, settle watcher,
  ask-human relay), WhatsApp + Telegram transports, config repo, service
  (lifecycle, status fan-out, QR rendering, current-transport reply
  routing, dynamic baileys import)
- Settings → Mobile tab: enable toggles, QR pairing, bot token,
  sender allowlists
- Strict sender authorization: WhatsApp self-chat + allowlisted numbers
  (LID-aware), Telegram allowlisted chat IDs only
- Telegram offset persisted across restarts; generation-guarded
  WhatsApp socket lifecycle; vitest coverage for the bridge

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 20:17:31 +05:30
Arjun
fc9a76e1cb add whatsapp and telegram support 2026-07-03 19:25:06 +05:30
arkml
3ba94402d3
Merge pull request #650 from rowboatlabs/mail-enhancements
feat(email): drafts, search, read-state controls & Superhuman-style shortcuts
2026-07-03 01:12:40 +05:30
Arjun
080b8625c6 fix(email): draft lifecycle, keyboard nav, and composer layout fixes from review
- Remove the "Mark as read" toggle for Everything else end-to-end (UI, IPC,
  config, sync auto-read, markSectionRead) — it could bulk-mark backlog and
  archived mail read on the server during cache-recovery syncs
- Sending an edited draft now reuses the draft's stored In-Reply-To/References
  instead of self-referencing its own Message-ID (external clients thread
  correctly); standalone drafts send as fresh messages
- Keep draft autosaves out of the sync pipeline: skip DRAFT history messages
  (no more md/knowledge-event leaks, phantom "New email" notifications, or
  per-autosave LLM reclassification) and mirror the draft body onto the cached
  snapshot surgically instead of waking the sync loop
- Only recreate a Gmail draft when update fails with 404/410 — transient
  errors no longer pile up duplicates
- Emptying a draft and closing now deletes it from Gmail (matches Gmail)
- Forward keydown out of message iframes so j/k/e/r etc. keep working after
  clicking into an email body; restore expanded quotes across theme reloads
- Composer footer wraps on narrow panes: formatting toolbar + Discard move as
  one unit instead of overflowing into each other

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 01:08:55 +05:30
Arjun
b028e56e44 ship pre-recorded tour narration + new default voice
Bundle the 11 tour narration clips as renderer assets so the tour never
calls ElevenLabs (works offline and signed-out). useVoiceTTS gains
speakUrl() which plays a ready URL through the same queue/analyser path,
keeping lip-sync and cancellation intact. Clips are regenerated with
scripts/generate-tour-audio.mjs, which parses TOUR_STEPS from
product-tour.tsx and synthesizes via @x/core. Default TTS voice is now
s3TPKV1kjDlVtZbl4Ksh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:20:11 +05:30
Ramnique Singh
17f18a35a1 feat(migration): port legacy runs into the turn/session runtime at startup
One-time startup migration that scans ~/.rowboat/runs/*.jsonl and converts
each legacy run into the new event-sourced runtime, so existing chats and
agent history are visible and continuable after the runtime rewrite.

Mapping (a run is one whole conversation; turn boundaries are user messages):
- copilot_chat run  -> 1 session (sessionId = original runId) + one turn per
                       user message (turn ids <runId>-tNNN).
- every other run   -> a single standalone turn whose id IS the original runId,
                       so live-note (lastRunId) and background-task (runs.log)
                       history views resolve it via sessions:getTurn with no
                       renderer change.
- code_session runs are skipped (Code mode still uses the old runtime).

- convert.ts: pure convertRun(); synthesizes a reduceTurn/reduceSession-legal
  event log (exact request refs, permission replay, denials -> runtime isError
  results, reasoning parts preserved) and validates via the reducers before
  returning.
- migrate.ts: defensive IO runner. Successful runs are moved to runs-archive/
  (that move is the idempotency guard); failures are left in place (still
  served by the runs:fetch fallback) and retried next launch. Per-run
  try/catch never blocks boot. Writes config/runs-migration.json.
- main.ts: runs before sessions.initialize() and logs an [runs-migration]
  summary (N turns across M sessions, skipped/failed counts).
- Tests: 13 cases over 4 real (redacted) run fixtures — conversion, session
  chaining, deny->error, reasoning preservation, transcript fidelity,
  archive-on-success, skip, quarantine, idempotency, and read-back through the
  real FSTurnRepo/FSSessionRepo.

Dry-run on real data: 19 runs -> 3 sessions + 21 turns, 0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:56:04 +05:30
Arjun
7f3987094f fix browser issues 2026-07-02 19:39:21 +05:30
Prakhar Pandey
239080ae56 Merge remote-tracking branch 'upstream/dev' into feat/disk-skills
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-07-02 19:22:26 +05:30
Ramnique Singh
0e316d1273 refactor(x/core): inject headless agent dependencies 2026-07-02 15:18:30 +05:30
Ramnique Singh
627451cd94 docs(x): stage 7 resolved — document the code-mode runs carve-out
Decision: defer code-mode unification; no deletions. The legacy runs
runtime (runs/, AgentRuntime/streamAgent, runs:* IPC, App.tsx legacy
tab state) remains solely for code-mode sessions. AGENTS.md gets the
authoritative carve-out section: what stays and why, the no-new-callers
rule (headless work uses agents/headless.ts), the temporary fallbacks
that die with unification, and the scoped future project (rowboat-mode
code prompts as composition turns; direct-mode ACP streams as a
delegated turn kind). Turn-runtime design doc status updated to
implemented-and-live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
fa6943e905 feat(x): llm_usage analytics for turn model calls + voice-output parity
Analytics: the new runtime emitted usage into durable events but never
reported it to PostHog (only the permission classifier self-reported).
New IUsageReporter seam on the turn loop — invoked once per completed
model call, after the durable append, failure-isolated. The real bridge
emits the same llm_usage event as the old loop; useCase/subUseCase come
from the AsyncLocalStorage context the stage-6 headless runners already
establish via withUseCase, defaulting to copilot_chat for UI-driven
session turns (matching the old createRun default). Capturing at the
loop covers ALL turns — the session bus never sees headless ones.

Voice: the new renderer path rendered <voice> tags literally and never
fired TTS. The live overlay now extracts completed <voice> blocks
(robust to tags split across deltas) into chatState.voiceSegments;
App speaks new segments via the existing useVoiceTTS when voice output
is on, skipping segments streamed before a session became active. Tags
are stripped from both the streaming message and persisted assistant
messages, mirroring the legacy display-time strip.

Tests: usage report assertion in the runtime suite; four voice tests
(split-delta extraction, per-call scan reset, state stripping+segments,
persisted-message stripping).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
49e1e99d16 fix(x/renderer): load headless transcripts via the turn runtime
Background-task and live-note history views called the legacy runs:fetch
for ids that are now turn ids (ENOENT after stage 6). New shared loader
src/lib/agent-transcript.ts fetches sessions:getTurn first and falls
back to runs:fetch so pre-migration histories stay readable (fallback
dies with the runs runtime in stage 7); turn transcripts render through
the same buildTurnConversation used by the chat views. Unit-tested
(turnToTranscript mapping + failure surfacing).

The chat-log download used runs:downloadLog with what is now a session
id. Added sessions:downloadLog (concatenates the session's turn logs
into one JSONL via the save dialog); the sidebar tries it first and
falls back to runs:downloadLog for legacy background tabs.

Remaining renderer runs:* callers are code-mode only (use-code-chat,
runs:events feed) — the deliberate stage-7 carve-out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
c73531ec38 feat(x): stage 6 — migrate headless callers to the turn runtime
New src/agents/headless.ts wraps the turn runtime in the old headless
calling convention: startHeadlessAgent returns the turn id immediately
(callers record it in pointer files / bus events before completion) and
a done promise settling with { outcome, state, summary };
runHeadlessAgent awaits it. throwOnError reproduces the old
waitForRunCompletion({ throwOnError }) semantics via HeadlessRunError;
summary reproduces extractAgentResponse (last assistant text);
toolInputPaths replaces the run-bus tool-invocation subscriptions by
reading invoked calls from durable turn state. Model overrides pair the
caller's model id with the app-default provider. Unit-tested against an
injected fake runtime (7 tests).

Migrated all nine callers:
- background-tasks/runner: handle start wrapped in withUseCase so tools
  (notify-user) read the use case via AsyncLocalStorage
- knowledge/live-note/runner: same shape, gains withUseCase
- pre_built/runner, knowledge/agent_notes: run-and-wait
- knowledge/tag_notes, label_emails, build_graph: edited/created paths
  now come from turn state (toolInputPaths) instead of bus streaming
- knowledge/inline_tasks (both sites): summary text feeds the existing
  marker parsing unchanged
- agent-schedule/runner: fire-and-forget start with
  AbortSignal.timeout(TIMEOUT_MS); dropped the now-unused
  runsRepo/agentRuntime/idGenerator plumbing

Code-mode sessions remain on the runs infrastructure (deliberate
carve-out until stage 7 scoping); agents/utils.ts stays for
launch-code-task's extractAgentResponse. The notify-user useCase gate
already prefers ALS with a best-effort fetchRun fallback, so it works
unchanged for turn ids.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
32e02d588c feat(x): agent snapshot inheritance, session inspection, runtime docs
Third application of the reference mechanism: session turns whose
system prompt + tools are byte-identical to the context predecessor's
materialized snapshot persist { agentId, model, inheritedFrom } instead
of ~70KB per turn (measured: turn 2 of a session is now ~1.1KB total).
Inheritance is decided at createTurn by equality against the
materialized predecessor; the model stays concrete, and on
materialization the inherited record's own agentId/model win — a rule
the new test matrix caught as a real bug (the chain base's model was
overriding a mid-session model switch). Reducer invariants: inherited
snapshots must reference the context predecessor; tool identity arrives
via invocation events.

Test matrix per review: prompt-diff -> full snapshot, tools-diff ->
full snapshot, model-switch -> inherits with concrete model, multi-hop
chains materialize, standalone turns never inherit, unreadable
predecessor falls back to full, cyclic inheritance is corruption,
sessions denormalize the model from inherited snapshots.

The inspector now handles sessions too (auto-detected): overview with
per-turn status/size/input preview, --turns to cascade full turn
inspection. Documented in a new repo-root AGENTS.md (storage layout,
reference model, inspector usage, invariants) with a CLAUDE.md pointer.

Breaking for dev turn files: wipe ~/.rowboat/storage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
97169be93e feat(x): compact string request refs + turn inspector CLI
Two debuggability follow-ups from reviewing real turn files:

- Request refs are now compact strings — 'context' | 'input' |
  'assistant:<index>' | 'toolResult:<toolCallId>' — so a raw JSONL line
  reads naturally: {"messages": ["assistant:0", "toolResult:toolu_…"]}.
  Same exact-match reducer invariants, via parseRequestRef.

- npm run inspect-turn -- <turnId|path> [callIndex] [--full]: prints,
  per model call, the EXACT provider payload (resolved system prompt,
  tool list, wire-form messages with user-message context woven in, and
  the response/failure) — rebuilt by the same composer the loop sends
  through. This is the missing viewer for the derived-not-duplicated
  request design: the file stores facts once; the inspector shows what
  the model actually received.

Breaking for turn files written since the previous commit (dev only):
wipe ~/.rowboat/storage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
3822bf53da feat(x): reference-based model requests + wire-form composer
model_call_requested carried three duplications that dominated turn-file
size (measured on a real 647KB two-step turn): the system prompt (28KB)
and the 38-tool snapshot (40KB) repeated per call despite being byte-
identical to turn_created.agent.resolved, and messages re-inlining the
whole current-turn transcript (230KB re-stating a 207KB tool result).

ModelRequest is now a list of references into the turn's own events —
call 0: [{context}?, {input}]; call N: [{assistant: N-1}, ...that
batch's toolResults in source order]; re-issue after an interruption:
[]. Every referenced byte exists exactly once in the file; the measured
turn drops to ~285KB and each further model call costs ~200 bytes. The
reducer's ordering invariant got stricter: reference lists are matched
exactly against the transcript.

Debuggability is now byte-for-byte at the wire level: ResolvedModel
gains encodeMessages (the structural->wire conversion — user-message
context weaving, attachment rendering, tool-result enveloping, i.e.
convertFromMessages), and composeModelRequest rebuilds the exact
provider payload (resolved system prompt + wrapped tools + materialized
prefix + resolved refs, encoded). The loop transmits exactly the
composer's output, so the durable file plus the composer reproduce what
the model received — pinned by a property test asserting composed ==
sent for every call, and a 2KB size guard on request events. A bridge
test demonstrates the woven wire form (no raw userMessageContext).

Breaking for existing dev turn files (same schemaVersion, pre-release):
wipe ~/.rowboat/storage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
df607ea510 feat(x): sessions IPC + renderer data layer with testable stores (stage 5a)
The wire and state layer for the UI cutover; App.tsx integration follows
separately.

- shared/ipc.ts: eleven sessions:* invoke channels + the sessions:events
  push feed (SessionBusEvent via z.custom, like runs:events). The generic
  preload bridge needs no changes.
- main: sessions:* handlers as thin pass-throughs to the DI'd sessions
  service; startSessionsWatcher forwards the session bus to all windows;
  startup awaits the session-index scan before the renderer can list.
- renderer architecture per review guidance — all logic in framework-
  agnostic, dependency-injected modules; hooks are thin
  useSyncExternalStore subscriptions; components will consume pre-digested
  view models:
  - client.ts: narrow SessionsClient over window.ipc (fakeable).
  - feed.ts: one shared sessions:events consumer with fan-out (factory
    for tests).
  - turn-view.ts: pure derivations — live overlay (deltas accumulate,
    canonical events clear), TurnState -> ConversationItem[], and the
    session chat state (permission/ask-human maps re-manufactured in the
    runs-era shapes so existing components render unchanged;
    isProcessing/isThinking contract preserved).
  - store.ts: SessionChatStore (seed via getSession/getTurn, shared
    reduceTurn over live events, prior-turn freezing, unknown-turn
    reconciliation, stale-load guard, action routing) + SessionListStore.
  - hooks/useSessionChat + useSessions: thin wrappers, deps injectable.
- renderer test infra added from scratch (vitest + jsdom +
  testing-library); 29 tests across stores, pure views, feed, and hooks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
1d5782fa75 feat(x/core): real bridges + DI assembly for the turn runtime (stage 4)
Five bridges adapt existing app code to the runtime seams, each unit-
tested against injected fakes:

- RealAgentResolver: loadAgent + dynamic builders -> immutable
  ResolvedAgent. Model precedence override > agent > app default; tool
  descriptors from BuiltinTools (zod -> JSON schema) and MCP
  attachments (toolId schemes builtin:/mcp:server:tool); ask-human as
  the async requiresHuman tool. System prompt composed via
  composeSystemInstructions — extracted verbatim from streamAgent so
  old and new runtimes share one implementation — driven by the new
  opaque RequestedAgent.overrides.composition (session-sticky inputs
  preserve provider prefix caching; per-message context stays on
  UserMessage.userMessageContext as today).
- RealModelRegistry: models.json -> live AI SDK model; one streamText
  step (stopWhen: stepCountIs(1)) normalized into deltas, durable step
  events, and the completed assistant message.
- RealToolRegistry: execTool dispatch (builtins + MCP) with per-call
  abortRegistry bracketing and tool-output-stream -> durable progress.
- RealPermissionChecker: getToolPermissionMetadata rules (command
  allowlist, workspace file boundaries); session grants deferred.
- RealPermissionClassifier: classifyToolPermissions with conversation
  context now threaded through the classifier batch.

Interface refinements (doc updated): ToolExecutionContext gains
turnId/toolCallId; classifier takes a batch with turnId + messages;
TurnRuntime dep renamed bus -> lifecycleBus and SessionsImpl bus ->
sessionBus for strict-PROXY DI. Container registers the whole stack
(FS repos rooted at WorkDir/storage); the legacy agentRuntime
registration became lazy to survive the new import-order cycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
ccf38c6a0f feat(x/core): session layer with write-through index (stage 3)
SessionsImpl per session-design.md, constructor-injected and tested
entirely against a mocked ITurnRuntime:

- sendMessage: per-session lock, typed TurnNotSettledError while the
  latest turn is non-terminal (terminal statuses including failed/
  cancelled allow continuation), context as { previousTurnId } ref,
  turn-file-first write ordering (a failed session append leaves a
  benign orphan turn and no advance), denormalized agent/model on
  turn_appended, default title from the first message.
- Dedicated respondToAskHuman endpoint (async_tool_result wrapper);
  respondToPermission / deliverAsyncToolResult pass turn-runtime
  rejections through. stopTurn aborts a live advance or cancels an
  at-rest turn; resumeTurn re-enters idle turns (never at startup).
- In-memory index: startup scan (session files + each latest turn for
  status; corrupt files yield errored entries without aborting),
  write-through updates, deletion guard against late-settle
  resurrection. Bus events (turn-event / index-changed) defined in
  @x/shared as the renderer IPC contract.
- FSSessionRepo: date-partitioned append-only JSONL + list/delete;
  deleteSession removes the session file only.
- runHeadlessTurn: standalone turns (sessionId null, auto permission,
  no human) for background/knowledge/scheduled callers.

29 new tests covering the session-design §13 matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
9e1d9b81ce refactor(x/core): decompose turn advance into §18 phase methods
No behavior change — all 67 turn tests pass untouched. The ~620-line
advance method becomes an internal TurnAdvance class holding the
per-invocation context, with one named method per spec phase
(applyInput, closeInterrupted*, evaluatePermissions, classifyBatch,
denyUnresolvedWithoutHuman, executeAllowedTools, suspendIfPending,
completeIfFinished, failIfExhausted, runModelStep, cancel). The main
loop now reads as a direct transcription of the design doc's §18
algorithm; TurnRuntime itself is reduced to construction and
createTurn/getTurn/advanceTurn plumbing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
cda17c2d40 feat(x/core): turn runtime with event-sourced execution loop (stage 2)
The TurnRuntime per turn-runtime-design.md: constructor-injected,
container-ignorant, no per-turn state — every advanceTurn reconstructs
from the JSONL log, so normal execution and crash recovery share one
path.

- runtime.ts: createTurn/advanceTurn/getTurn; durable-barrier appends
  before side effects; sequential sync tools with progress; async tool
  exposure + durable suspension; permission pipeline (checker →
  optional classifier batch → human/deny fallback, fail-closed on
  checker errors); cancellation with synthetic results; recovery that
  re-issues interrupted model calls and continues after interrupted
  sync tools; model-call-limit exhaustion with machine-readable code.
- fs-repo.ts: date-partitioned append-only JSONL, strict line
  validation, path-traversal rejection, per-turn in-process locking.
- stream.ts: hot execution stream — buffers before the consumer
  attaches, outcome independent of consumption, close-drops-events.
- context-resolver.ts: chain-walking materialization of context
  references with cycle detection.
- Seam interfaces for stage-4 bridges: model/tool registries,
  permission checker/classifier, agent resolver, lifecycle bus, clock.
- shared reducer: track classification failures durably so failed
  classifications are never re-run.

67 new tests: all nine §26 end-to-end scenarios (mocked deps,
recovery boundaries seeded directly) plus repo/stream/resolver suites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
d821c419fe feat(x/shared): turn + session event schemas and pure reducers (stage 1)
Durable contracts for the new turn/session runtime, shared by core and
renderer:

- turns.ts: zod schemas for all 16 durable turn events, TurnContext
  (previousTurnId ref | inline messages), ModelRequest with contextRef
  and current-turn-only messages, ephemeral delta types, reduceTurn
  enforcing every spec invariant, and pure derivations
  (deriveTurnStatus, turnTranscript, outstanding work).
- sessions.ts: session_created/turn_appended/title_changed schemas,
  reduceSession, and the SessionIndexEntry projection.
- vitest wiring for packages/shared (config, build-excluded tests);
  92 tests covering happy paths, recovery-shaped histories, and one
  named test per corruption invariant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Ramnique Singh
1825202e65 docs(x): turn runtime + session layer design specs
Turn runtime spec amended after review: context references with a
context resolver, recovery that re-issues interrupted model calls and
continues after interrupted sync tools, turn_failed.code with
model-call-limit, default maxModelCalls 20.

New session layer spec: append-only session JSONL, turn-file-first
write ordering, in-memory write-through index with startup scan,
reject-while-busy sendMessage, dedicated ask-human endpoint, deferred
v2 shapes for queueing/steering/grants/compaction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:20:44 +05:30
Gagan
b548173593 feat(mini-apps): capable models for app bg-tasks
- list-models tool: returns allowed model IDs (gateway for signed-in, config
  for BYOK) + a safe defaultModel, so the copilot picks a valid capable model
  instead of guessing rejected IDs
- patch-background-task: clearModel option to reset a bad model/provider
  override (falls back to default); patchTask can now delete fields
- build-mini-app skill: use list-models + set a capable model on data tasks
2026-07-02 02:31:00 +05:30
Gagan
61f2bcff5a feat(mini-apps): New-app prefill + light/dark theme for apps
- New app card opens the chat sidebar and pre-fills (not sends) a
  'Build me a mini-app that ' prompt via the presetMessage path
- Push host theme to apps over the bridge: shim applies html.dark/.light +
  color-scheme and exposes getTheme/onTheme; host observes theme changes live
- build-mini-app skill: generated apps must style both light and dark
2026-07-02 02:17:33 +05:30
Gagan
c424976926 fix(mini-apps): reliability fixes + self-contained data loading
From two rounds of copilot build feedback:

- runtime: null-check hallucinated tool names (was crashing the whole run)
- add window.rowboat.fetch + builtin fetch-url: CORS-safe HTTP for UI and
  bg-task agents (no shell needed for plain HTTP)
- mini-app-set-data/install: reject stringified JSON; require object
- manifest dataContract (requiredKeys/nonEmptyArrayKeys) enforced on writes so
  a stray/legacy task can't corrupt an app's data or wipe series
- self-contained data: data.json is a served sibling (dist/), apps fetch it via
  relative URL; removed host-side data injection
- build-mini-app skill: CORS/fetch-url/no-shell/capable-model/dataContract gotchas
2026-07-02 01:06:05 +05:30
gagan
08a4077d43
Meeting prep: attendee notes + proactive 6h-ahead prep briefs (#636)
* feat(meetings): inline meeting prep under next meeting

Resolve a meeting's attendees against the knowledge base and render each
attendee's existing person.md inline beneath the next upcoming meeting in
the Meetings view. Deterministic (email-exact match, then unambiguous
name/alias), no LLM in the hot path.

- core: resolveMeetingPrep() + meeting-prep:resolve IPC
- renderer: notes-first rows with expandable person.md; unmatched attendees
  collapse into a 'no notes yet' row with a Create note action that hands
  off to the Copilot
- prep re-resolves when a People note changes

* feat(meetings): per-meeting prep toggle

Add a Prep toggle to every eligible meeting row, not just the next one.
The next meeting still auto-expands; other meetings resolve their attendee
notes lazily when their toggle is opened. All-day and solo events get no
toggle.

* feat(meetings): org matching, cached index, and field-bleed fix

#2 Matching: resolve the meeting's external companies from attendee email
domains (and matched people's Organization field), excluding the user's own
domain, and surface those org notes in a Companies section. Normalize display
names (strip '(via ...)' suffixes) before name/alias matching.

#3 Perf: meeting prep reads a cached knowledge index instead of rescanning the
whole knowledge dir on every resolve; invalidated whenever a file under
knowledge/ changes (wired to the workspace watcher).

Fix: extractField in the knowledge index consumed newlines after the label, so
an empty field (e.g. **Role:**) bled the next line's value. Restrict it to
spaces/tabs so empty fields resolve to undefined. Also strip the folder prefix
from link targets for display (Organizations/Rowboat Labs -> Rowboat Labs).

* feat(meetings): proactive prep notes generated 6h ahead

Generate a meeting prep note ~6h before each meeting and surface its brief
in the prep card.

- Generator (meeting_prep_brief.ts): assembles roster + 'last time' recap
  (extracts the prior instance's Action items) + agenda, ordered by meeting
  type (recurring -> recap first, one-off -> agenda first), then one model
  call for a 'what matters' brief. Writes knowledge/Meetings/prep/<slug>-<date>.md
  with eventId/recurringEventId frontmatter. Reuses the summarizeMeeting LLM
  path (configured model, useCase: meeting_prep).
- Scheduler (meeting_prep_scheduler.ts): calendar-aware tick (5m), generates
  prep within the 6h lead window, state file dedupes, self-heals on changes.
- Card: shows the brief (bulleted, compact) above a People list whose rows
  link out to each person's note instead of rendering it inline. Generated
  prep notes are kept out of the past-notes table.

* chore(meetings): poll prep scheduler every 15m instead of 5m
2026-07-02 00:35:35 +05:30
hrsvrn
063f6892a8 feat(email): on-demand attachment download, search clear, per-category read prefs
- Download search-result attachments on demand before opening (fixes Linux
  where xdg-open reports success on a missing file so the old open-then-
  download fallback never fired)
- Add a clear button to the search box that dismisses search and returns to
  the inbox
- Remove the read toggle from the Important section
- Replace the Everything else "Read" toggle with a "Mark as read" preference:
  turning it on marks all current and future "Everything else" mail read;
  turning it off only stops auto-reading future mail, leaving current threads
  untouched
2026-07-01 23:51:08 +05:30
PRAKHAR PANDEY
117a140e03
Merge branch 'dev' into feat/disk-skills 2026-07-01 22:38:59 +05:30
Prakhar Pandey
810034b13a feat: disk-backed agent skills with live-reload 2026-07-01 22:28:28 +05:30
hrsvrn
ab9bce6203 feat(email): drafts, search, bulk read/unread, configurable backfill
Gmail client enhancements in apps/x, bundling four independent features:

- Drafts: save/autosave, update, delete, and list Gmail drafts. The
  composer autosaves to a real Gmail draft (debounced ~1.5s) while
  typing, reuses the thread's existing draft so edits update in place,
  flushes a final save on close, and deletes the draft on discard. Adds
  isDraft/draftId to the shared thread types. New core helpers:
  saveThreadDraft, deleteThreadDraft, listDraftThreads,
  buildDraftSnapshot, buildRawMimeMessage.

- Search: searchThreads(query, {limit}) backed by an on-disk snapshot
  cache (read/writeSearchSnapshot). Extracts parseThreadSnapshot as the
  shared parse core reused by both the cache-building sync and search.

- Read state: markThreadRead now takes a `read` flag so it toggles
  read/unread; new markSectionRead marks a whole section
  (important/other) read/unread and returns the affected count.

- Backfill: the onboarding/recovery sync is now bounded by a
  configurable thread COUNT instead of a fixed 7-day window. New
  gmail_sync_config.ts (getMaxEmails/setMaxEmails) backed by
  ~/.rowboat/config/gmail_sync.json, default 500, clamped to 1-5000;
  seeded on first run.

New IPC channels: gmail:saveDraft, gmail:deleteDraft, gmail:getDrafts,
gmail:search, gmail:markSectionRead (plus a `read` field on
gmail:markThreadRead).
2026-07-01 16:20:33 +05:30
Gagan
6d7fd7b013 feat(mini-apps): open built app in middle pane from chat
After the copilot builds + verifies + populates an app, it opens it in the
middle pane under Mini Apps / <title>, with an 'Opened <app>' card in chat.

- app-navigation: new open-app action (reads manifest title)
- renderer: pending-nav opens the Mini Apps view with the app pre-selected;
  MiniAppsView accepts initialAppId/initialVersion
- chat: open-app app-action card labels
- build-mini-app skill: finalize step opens the app once data is populated
2026-07-01 02:12:05 +05:30
Gagan
4a4dcb1fa0 feat(mini-apps): copilot builder — build-mini-app skill + install/data tools
Lets the copilot build a Mini App into ~/.rowboat/apps from chat.

- build-mini-app skill: intent gate (confirm when ambiguous), verify Composio
  wiring by actually calling tools before building, writer branch (Code Mode
  via code-with-agents vs Copilot), agent-backed data pipeline, install
- mini-app-install tool: write an app folder (manifest + dist/index.html)
- mini-app-set-data tool: atomic deterministic data.json write (agent returns
  content; path/write handled in code)
- serve canonical bridge shim at app://miniapp/__bridge__.js
2026-07-01 01:25:45 +05:30
Gagan
924a93fa4a feat(mini-apps): serve apps from ~/.rowboat/apps via app://miniapp
Apps are now user data on disk, not bundled in the repo.

- MiniAppManifest schema + mini-apps:list/get-data/seed IPC channels
- main: mini-apps-handler (list/get-data + seed install primitive) and an
  app://miniapp/<id>/ protocol host serving ~/.rowboat/apps/<id>/dist
- renderer lists installed apps from disk and loads each via app://miniapp
  (real origin: remote images/fetch work); data sourced from data.json
- removed the bundled sample apps from source (they live in ~/.rowboat/apps)
2026-07-01 00:27:00 +05:30
Arjun
e6ff631191 fix codex unresponsive 2026-06-30 22:28:15 +05:30
Arjun
f2b5c6b1ab (1) add running transcript to voice input (2) capture till end of speech when mic is used (3) notify only on important emails 2026-06-30 18:43:46 +05:30
Gagan
30a46178d1 feat(mini-apps): real scoped Composio bridge + GitHub Dashboard
Phase 2 makes the Mini App bridge real instead of stubbed.

- New composio:execute-tool and composio:search-tools IPC channels
  (shared schema + main handlers reusing the agent's execute path)
- Bridge refactored to a scoped RPC: callAction/searchTools/isConnected/
  connect, enforced against each app's declared scope at the host
- Auth prompt: acting on a disconnected toolkit triggers Composio OAuth
- GitHub Dashboard sample: track repos, view their 20 most-recent open PRs
  with descriptions, plus the authenticated user's profile + contribution
  graph — all live via the bridge
- Twitter sample reverted to a local UI demo (X has no managed OAuth2)
2026-06-30 01:17:38 +05:30
Arjun
753e3448f0 Show previous chats related to the workspace in the workspace section. 2026-06-29 12:27:43 +05:30
hrsvrn
4a26bc9d80 feat(email): faster inbox load, Ctrl+Tab tab cycling, square preset buttons
- Inbox load: add an mtime-keyed in-memory cache to listInboxPage so it no
  longer re-reads and JSON.parses every cached thread (bodies included) on
  every call — only files whose mtime changed are re-parsed. Speeds up the
  Important→Everything-else handoff, live reloads during sync, and re-opening
  the inbox tab.
- Add Ctrl+Tab / Ctrl+Shift+Tab to cycle to the next/previous tab (wraps
  around), alongside the existing Cmd+Shift+] / [ shortcuts.
- Improve bar: make the tone preset buttons square (rounded-md), left-aligned,
  with a bit of vertical gap so wrapped rows aren't cramped.
2026-06-26 15:51:04 +05:30
Ramnique Singh
6373238ad8
Merge pull request #550 from aaronjmars/security/command-executor-ampersand-bypass
fix(security): close & (background) command-executor allowlist bypass
2026-06-25 23:06:53 +05:30
PRAKHAR PANDEY
1bfda94f48
fix: suppress notification spam on app re-open (#623)
* fix: suppress notification spam on app re-open

- Add background_task notification category (default ON) so users can
  toggle off background-task pings via Settings → Notifications
- Route notify-user builtin through notifyIfEnabled gate so the
  category toggle takes effect
- Add 60s startup grace period: background-task notifications fired
  within 60s of launch are suppressed, killing the reopen flood where
  all queued agents complete at once
- Suppress new_email notifications for emails older than 5 min so
  Gmail's startup backlog replay doesn't surface day-old mail

Fixes both issues reported by Ramnique.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: skip automatic chat_completion ping for background task agents

Background task runs were triggering "Response ready / Your agent
finished responding" on every completion. Skip the automatic
chat_completion notification when finalState.runUseCase ===
'background_task_agent' — background tasks notify explicitly via
notify-user when they have something worth surfacing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: correct notification deeplinks and skip internal agent pings

- runtime.ts: also skip chat_completion ping for knowledge_sync
  useCase (agent_notes_agent was opening notes view on click)
- sync_gmail.ts: new_email notification now links to specific
  email thread (rowboat://open?type=email&threadId=...)
- App.tsx: add email case to parseDeepLink, parse threadId param,
  wire threadId through applyViewState, update viewStatesEqual

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: distinguish background-agent notifications from auto knowledge-sync

Per team clarification, two background types are handled differently:
- knowledge_sync (auto knowledge-graph generation): never notifies;
  skips the generic chat_completion ping entirely.
- background_task_agent (user-configured agents): notifies via its
  own notify-user path, gated behind the toggleable "Background
  agents" category, deep-linking to the background-tasks page.

- runtime.ts: skip the generic chat_completion completion ping for
  both knowledge_sync and background_task_agent (the latter notifies
  via notify-user, so the generic ping would duplicate it).
- builtin-tools.ts: notify-user branches on getCurrentUseCase() —
  background agents route through notifyIfEnabled('background_task')
  with a bg-tasks deeplink default; chat agents notify directly.
- App.tsx: add the bg-tasks deeplink target (ViewState, parseDeepLink,
  applyViewState, currentViewState).
- settings-dialog.tsx: rename the category label to "Background agents".
- runner.ts: wrap the background-task run in withUseCase so tools see
  the correct use-case context.

* fix: route coding-session notifications through notifyIfEnabled

Code-mode status-tracker was calling notificationService.notify() directly,
bypassing the user's notification-category toggles. Routed both calls
through notifyIfEnabled() with correct categories:
- needs-you state → agent_permission
- idle after 30s  → chat_completion

Removed now-redundant container/INotificationService plumbing.

* fix: fall back to persisted run useCase when ALS context is missing in notify-user

AsyncLocalStorage does not propagate across the background-task agent's
async generator — getCurrentUseCase() returns undefined inside notify-user,
causing the background_task_agent branch to be skipped and the Background
agents toggle to be ignored.

Fix: load persisted useCase from run record via fetchRun(ctx.runId) when
ALS is falsy. Lazy dynamic import() avoids the known module-init cycle.
Background-agent notifications now correctly respect the toggle and only
fire when the app is in the background, with deep link to the bg-tasks page.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 01:36:06 +05:30
PRAKHAR PANDEY
812ed9222c
Merge pull request #633 from prakhar1605/fix/filter-chat-models
Filter non-chat models from BYOK model picker
2026-06-23 18:18:00 +05:30