Commit graph

366 commits

Author SHA1 Message Date
Gagan
d7af51c10b feat(apps): M3 groundwork — packager, GitHub device-flow auth, registry client
- env: GITHUB_OAUTH_CLIENT_ID (device flow enabled on the Rowboat OAuth app;
  overridable via ROWBOAT_GITHUB_CLIENT_ID)
- packager (§4.4): allowlist-only .rowboat-app ZIP (yazl), sorted entries,
  symlink skip, sha256
- github-auth (§10): device-code start/poll, identity fetch, token stored
  0600 with safeStorage encryption injected from main (core stays
  electron-free); githubAuth:* IPC + external open of the verification page
- registry client (§9.2): unauthenticated tarball index with 5-min cache and
  stale fallback, raw-record resolve, substring search, quota-free
  latestManifest via release-asset redirect with name-mismatch guard
- registry repo contents (docs/apps-registry): record JSON schema +
  validate-and-merge Action implementing §9.3 checks 1-7 with rejected:<code>
  comments and per-name concurrency
- fix: host-api copilot-run adapted to dev's ModelSelection {provider,model}
  (this is the same fix dev needs for Ramnique's packaging break)
- pnpm 11: blockExoticSubdeps=false (electron-forge has a git subdep)
2026-07-06 14:49:52 +05:30
gagan
b7d1019538
Merge pull request #665 from rowboatlabs/feature/apps-v1
Rowboat Apps V1 — M1 + M2 (spec implementation)
2026-07-06 14:18:11 +05:30
Ramnique Singh
1050614295
Merge pull request #674 from rowboatlabs/code-mode-fixes
Code mode fixes
2026-07-06 14:06:41 +05:30
Ramnique Singh
3bbed55735 feat(x): ephemeral CodeRunFeed for codex streaming + settle-time durable batch
The runs->turns migration broke codex live streaming in copilot chat: the
turns bridge's publish shim forwarded only tool-output-stream and silently
dropped code-run-event / code-run-permission-request (the latter would
deadlock a turn under policy 'ask'). An interim fix persisted every stream
event as durable tool_progress, but that wrote each text chunk to the turn
file — unsustainable.

Final architecture — live and durable paths split:

- Live (ephemeral, bypasses the turn runtime): code_agent_run broadcasts
  each ACP event on a new CodeRunFeed (core DI singleton), forwarded by
  main over a dedicated codeRun:events channel, buffered per toolCallId in
  a module-level renderer store and rendered by CodingRunBlock. The buffer
  survives session switches; nothing is persisted.

- Durable (one line per run): when the run settles (success, error, or
  cancel), code_agent_run publishes a single code-run-events-batch with
  the whole ordered timeline, consecutive same-role message chunks
  coalesced (display-lossless — the timeline concatenates them anyway).
  The turns bridge maps it to tool_progress {kind:'code-run-events'};
  turn-view derives the replay timeline from it, so reloads keep history.

- Permissions stay durable per-ask (request + resolved marker): the
  renderer overlay resets on session switch, so an ephemeral-only ask
  would strand a blocked turn with no card to answer. Pending = requests
  minus resolutions (handles concurrent asks), cleared on tool result.

The legacy code-section path (runs bus per-event) is untouched; its
per-event ctx.publish remains and is a no-op under the turns shim.

Also: repaired the two code_agent_run tests broken by the earlier cwd
existence check (they used a nonexistent /repo), and added coverage for
feed broadcast, batch coalescing, partial-batch-on-failure, bridge
durability routing, and pending-permission derivation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:04:33 +05:30
Arjun
fd49334c33 move local-model concurrency to a turn-level defer lock
Review feedback: drop the model-layer scheduler (per-call-site
interactive/classifier/background priorities, local-provider hostname
heuristic) in favor of app-level orchestration:

- deferBackgroundTasks flag in models.json, surfaced as a settings
  toggle; auto-enabled once (UI logic) when the user connects Ollama
- ChatActivity counter marked by both chat runtimes (sessions layer and
  legacy AgentRuntime.trigger)
- startWhenPossible/runWhenPossible wrappers around the headless agent
  runner; all background invocations (knowledge pipeline, live notes,
  background tasks, scheduled + prebuilt agents) go through them and
  wait for chat-idle when the flag is set

createLanguageModel keeps only the Ollama context-window middleware;
the LM Studio capability probe now keys off the provider flavor instead
of hostname sniffing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:38:39 +05:30
Arjun
c1fd4e6221 allow byok even when logged in 2026-07-06 13:32:13 +05:30
Arjun
0b55dc3300 fix ollama model effort level 2026-07-06 13:31:13 +05:30
Arjun
799d7584b8 make local models work well 2026-07-06 13:31:13 +05:30
Ramnique Singh
7c06af04a7 fix(x): unblock codex code-mode (bad cwd + revoked engine cert)
code_agent_run failed with a misleading "spawn <Electron> ENOENT" that
read as "Codex isn't installed". Two stacked causes:

1. cwd wasn't expanded/validated: a `~` or non-existent path was passed
   straight to child_process.spawn, which reports ENOENT against the
   command (Electron) rather than the missing directory. Now expandHome +
   resolve + existence-check with a clear error.

2. Apple revoked the signing cert on the pinned @openai/codex@0.128.0, so
   macOS Gatekeeper trashed the binary on launch and the ACP adapter died
   mid-handshake. Bump the ACP stack off the revoked build:
   codex-acp 0.0.44 -> 1.1.0, claude-agent-acp 0.39 -> 0.55, sdk 0.22 ->
   1.1, regen engine-manifest (codex 0.142.5 / claude 0.3.198). Removed the
   two now-obsolete patches (contextCompaction upstreamed; the codex
   windowsHide patch targeted bin/codex.js, which we bypass via CODEX_PATH).

Bump fallout handled:
- client.ts setModel: sdk 1.x dropped unstable_setSessionModel; model is now
  a config option -> setSessionConfigOption({configId:'model'}).
- engine-provisioner: codex 0.142 moved its binary (codex/ -> bin/) and rg
  (path/ -> codex-path/); probe both layouts.
- pnpm override vscode-jsonrpc to 8.2.0: codex-acp 1.1 pulls jsonrpc 9.x
  whose restrictive exports break langium's deep import in the renderer
  (blank screen). codex-acp is the only 9.x consumer and works on 8.x
  (handshake verified), so pin the workspace to 8.x.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:57:02 +05:30
Ramnique Singh
4a2fe30153 fix(x): surface coding tool errors 2026-07-06 10:56:23 +05:30
Arjun
adb8b16a3c Add in-call mic mute: pauses all input (mic audio + frame capture)
A mute button on both call surfaces (full-screen call and floating
popout) that pauses everything going to the assistant while keeping the
call alive — for stepping away to talk to someone in the room without
ending and restarting the call.

- Mic audio stops reaching Deepgram (user mute OR'd into the existing
  thinking/speaking setPaused; KeepAlives keep the socket warm so
  unmute is instant)
- Camera/screen frames stop being sampled (new setCapturePaused in
  useVideoMode); collectFrames() returns nothing while muted, so typed
  messages during a mute carry no frames either
- Devices stay acquired for instant resume; mute resets at call
  start/end; assistant output is unaffected (Stop handles that)
- Honest UI: "Muted" status chip replaces the green "Listening" pulse,
  muted badge on the user tile, pill's share badge flips to "Sharing
  paused"; new toggle-mic popout action + micMuted in popout state

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 00:28:34 +05:30
Arjun
b493baf689 use native gmail in copilot 2026-07-05 22:44:06 +05:30
arkml
6b2120d376
Merge pull request #666 from rowboatlabs/graph2
Improvements to graph and email labeling
2026-07-05 11:38:31 +05:30
Arjun
0d14220239 labeling improvements 2026-07-05 11:09:18 +05:30
Arjun
c88a45ee7e fix issue with sent emails showing up 2026-07-05 03:14:45 +05:30
Arjun
9d31d25046 learns email importance from preference actions 2026-07-05 02:36:01 +05:30
Arjun
258f157b39 improvements to graph building 2026-07-05 02:10:46 +05:30
Gagan
dc28b0b868 merge dev into feature/apps-v1
Union resolutions — dev's changes kept, apps-v1 additions appended:
- use_case.ts: dev's meeting_prep + our app_llm_generate/app_copilot_run
- ipc.ts: dev's HttpAuthRequestSchema import + our AppSummarySchema
- shared/index.ts: dev's channels export + our rowboatApp export
- builtin-tools app-navigation: dev's read-view/open-item actions + our open-app
- chat-conversation: dev's read-view/open-item labels + our open-app labels
- sidebar: dev's nav-workspaces tour id + our Apps entry
- App.tsx: dev's open-item handler + our open-app handler
2026-07-05 01:08:35 +05:30
Gagan
7c9fe7214d fix(apps): stop task.yaml rewrite races + blank-load watchdog + loop-lag monitor
Investigating 'apps blank while a bg task runs': measured serving latency
through a real scheduled bg-task run — 1-3ms on both loopbacks throughout,
so the server is not the bottleneck. Found and fixed the likely renderer-era
culprit plus made the failure observable:

- agent sync (apps:list, polled every 4s) rewrote each bundled agent's
  task.yaml unconditionally — racing the bg runner's own patches mid-run and
  spamming watcher events; now writes only when the definition changed
- AppFrame load watchdog: if the iframe hasn't loaded in 6s, show a visible
  'taking too long' state with Retry instead of a silent blank pane
- main event-loop lag monitor: stalls >300ms are logged so future blank
  reports can be tied to the offending work
2026-07-05 00:45:30 +05:30
Gagan
810fd2da1a feat(apps): detail panel, skill trigger, analytics, real apps:get
- app detail panel (toolbar Info toggle): manifest info, capabilities,
  provenance/publish state, bundled agents with enable toggles + run-now,
  rollback affordance, README
- copilot instructions: apps-skill trigger line (build/make/create an app →
  load the apps skill first)
- analytics: app_created / app_deleted (main), app_opened (renderer)
- apps:get returns real readme + rollbackAvailable (.previous/ check)
2026-07-05 00:17:55 +05:30
Gagan
3e34cbe1af feat(apps): M2 host integration — tools, fetch proxy, LLM, copilot, bundled agents
- checkCapability choke point (D7): tools/llm/copilot access requires the
  capability in the app's manifest; 403 capability_not_declared otherwise
- POST /_rowboat/tools/search|execute: Composio pass-through with the same
  request shape as the builtin, typed error mapping
- POST /_rowboat/fetch: SSRF-guarded proxy (loopback/RFC1918/link-local/ULA/
  *.localhost rejection re-checked per redirect hop, 5MB cap + truncated flag,
  30s timeout, Host/Cookie stripped)
- POST /_rowboat/llm/generate: default-model plumbing, override validated
  against the allowed set, token/concurrency caps, usage attributed
  app_llm_generate/<folder>
- POST /_rowboat/copilot/run: headless run (bg-task tool profile, no shell),
  app-attributed audit turn with origin context, 10-min timeout, 1-per-app cap
- registered M2 routes are POST-only (GETs are D17-exempt and must not reach them)
- bundled agents (§8): strict AppAgentDefinitionSchema, deterministic
  app--<folder>--<name> slugs materialized disabled with sourceApp, manifest
  removals deactivate, app delete removes owned tasks; sourceApp in summaries
- new use cases app_llm_generate / app_copilot_run in runs + analytics enums
2026-07-04 18:16:43 +05:30
Gagan
c81739f9f7 fix(apps): bind IPv6 loopback too — blank apps in-app
macOS's resolver maps *.apps.localhost to ::1 only, so Electron's iframe
connects to [::1]:3210 while the server listened on 127.0.0.1 only —
connection refused, blank app, while external browsers succeeded via their
own IPv4 fallback. Listen on both loopback addresses (::1 best-effort for
IPv6-disabled machines).
2026-07-04 17:30:55 +05:30
Gagan
7f15f67273 feat(apps): Rowboat Apps M1 — per-app origins, Host API core, apps skill
Implements M1 of the Apps V1 spec, replacing the mini-apps prototype:

- rowboat-app.json manifest + AppSummary/install/publish/registry schemas;
  sourceApp on background tasks
- apps server on 127.0.0.1:3210 (absorbs local-sites): Host-header routing to
  <slug>.apps.localhost origins, 421 rebinding guard, static from dist/ with
  entry/SPA fallback + realpath confinement, control host /health, chokidar
  watcher + per-app SSE with dist/data areas, embed-opt-in autosize bootstrap
  and cancelable rowboat:data-change event
- Host API: D17 anti-CSRF (X-Rowboat-App + strict Origin on non-GET),
  GET /_rowboat/app (manifest + theme), /_rowboat/events, data API under
  data/ with atomic writes and dataContracts enforcement
- indexer: list (ok/invalid surfaced) / create scaffold / delete
- IPC: apps:list/get/create/delete/setTheme; renderer reports theme live
- renderer: Apps home (My apps grid + catalog placeholder, server banner,
  invalid/kind badges) and full-height app view on the app origin
- apps copilot skill (replaces build-mini-app); app-set-data builtin replaces
  mini-app-install/set-data; deleted the app://miniapp protocol, postMessage
  bridge, mini-apps handler/channels, and local-sites
2026-07-04 17:12:54 +05:30
Gagan
2c038fe518 feat(mini-apps): live data refresh + install reliability
- watch ~/.rowboat/apps: data.json changes push into open apps (shim
  re-fetches, onData fires); installs/updates re-list the gallery live and
  reload an open app once the write settles
- atomic installs (temp->rename) so an app opened mid-install never reads a
  partial file
- serve app assets by direct disk read instead of net.fetch(file://) —
  Chromium's network service could stall and blank every open app at once
2026-07-04 15:59:13 +05:30
arkml
51c289ba36
Merge pull request #663 from rowboatlabs/video1
Video mode with screenshare and app navigation
2026-07-04 14:27:18 +05:30
Arjun
82abfe9a04 add better app navigation on call 2026-07-04 14:20:13 +05:30
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
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