Commit graph

310 commits

Author SHA1 Message Date
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
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
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
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
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
arkml
be81ffb27b
Drive (#583)
* add drive sync up and down

* add drive button

* google doc icon

* icon changes

* show error state with retry in google doc picker

* feat(google-docs): import and sync down as Markdown, record remote revision

* feat(google-docs): structure-preserving sync up with remote-conflict guard

* feat(google-docs): overwrite-confirm on sync conflict and last-synced indicator

* feat(google-docs): store linked docs as .docx, edit in docx editor, sync via Drive

* feat(google-docs): offer BYOK connect in picker so signed-in users can grant Drive/Docs scopes

* fix(google-docs): request full drive scope so .docx sync-up can write back

* fix(google-docs): search all drives in doc picker, log result count

* feat(google-docs): import native Docs AND uploaded .docx files from Drive

* chore(google-docs): drop dev-only test file

* feat(google-docs): use Google Picker + drive.file scope instead of full-drive listing

* fix(google-oauth): request offline access so BYOK tokens refresh

BYOK never requested access_type=offline/prompt=consent so no refresh token was issued and tokens died after ~1h; also stop handing back expired tokens and extend the connect timeout to 10m.

* feat(google-docs): pick docs via system-browser Google Picker

Runs the Picker in the user's real browser (it 403s inside Electron), sets appId so the drive.file grant attaches to the picked file, and downloads + opens the selected doc.

* feat(google-docs): managed OAuth-redirect Picker (no API key, no BYOK) (#620)

* feat(google-docs): managed OAuth-redirect Picker (no API key, no BYOK)

Adds the managed (rowboat-mode) Google Docs picker via Google's trigger_onepick
flow. The Rowboat backend runs a standalone drive.file OAuth with the company
client, renders the Picker inside the browser consent screen, and deep-links the
selection back; the desktop downloads the picked doc with the fresh drive.file
token the backend returns. No Picker API key, appId, or BYOK credentials on the
desktop.

- core: importGoogleDocWithToken downloads a picked doc with an explicit token;
  fetch/metadata helpers take an optional Drive client and share writeDocxAndLink.
  claimPickedFilesViaBackend claims the parked file ids + token from the api.
- main: google-picker-managed.ts opens the backend start URL and resolves on the
  rowboat://oauth/google/picker/done deep link; deeplink.ts routes that completion.
- ipc: google-docs:pickViaManaged.
- renderer: the picker dialog gates on Rowboat sign-in (the picker grants
  drive.file per-file, so no pre-existing connection or scope is required).

Backend contract: rowboatlabs/rowboatx-backend#7
(GET /oauth/google/picker/{start,callback}, POST /v1/google-oauth/claim-picked).

* chore(google-docs): remove the dead API-key/system-browser Picker

The managed picker replaced the only consumer (the picker dialog), so the
experimental API-key Picker is now unused. Removes:
- main: google-docs:openPicker handler (system-browser loopback Picker)
- shared: google-docs:openPicker + google-docs:getAccessToken IPC schemas
- core: getGoogleAccessToken (token plumbing for the client-side Picker)
- renderer: lib/google-picker.ts (Picker JS SDK loader)

Kept GoogleClientIdModal / google-credentials-store — still used by the
general BYOK Google connect in onboarding, connectors, and settings.

---------

Co-authored-by: Gagancreates <gaganp000999@gmail.com>
2026-06-23 03:13:58 +05:30
Arjun
de7d6b7a10 assistant notes below each note 2026-06-22 22:35:54 +05:30
Prakhar Pandey
1368d84bac fix(onboarding): filter non-chat models from provider list using models.dev 2026-06-22 12:49:48 +05:30
PRAKHAR PANDEY
51ca8778b5
Merge pull request #629 from prakhar1605/feat/byok-onboarding-simplify
Simplify BYOK onboarding to provider + key (tasks 4 & 6)
2026-06-22 11:59:55 +05:30
gagan
45188e7c1c
feat(code-mode): per-session model + effort, and keep output on nav (#632)
Two improvements to the Code section:

- Fix: leaving the Code section and returning no longer drops the open
  session's output. The selected session id is persisted to localStorage
  (mirroring the terminal-height pattern) and restored on remount, so the
  right-hand chat pane re-binds instead of falling back to the empty state.

- Feature: choose the coding agent's model and reasoning effort per session.
  Choices are discovered live from the engine (the same list `/model` shows)
  via a new `codeMode:listModelOptions` IPC, cached per agent — never
  hardcoded, so they track whatever the provider currently offers. Claude
  exposes model + effort as separate axes (with explicit Opus/Sonnet/Haiku
  alias rows surfaced for clarity); Codex folds effort into the model id and
  reports no separate effort. Selections persist on the CodeSession and are
  re-applied to the ACP session each turn (best-effort), editable from both
  the new-session dialog and the session header.
2026-06-21 21:08:49 +05:30
Arjun
a0429fc037 email reply all and first name signoff 2026-06-20 02:32:08 +05:30
Arjun
f848d235d8 fix file diffs 2026-06-20 00:02:29 +05:30
Ramnique Singh
f65f7e8fc8 feat(billing): consume plan variant catalog
Fetch the public billing catalog during account config load and use durable plan IDs from /v1/me for display, analytics, and upgrade/manage labels.

Renderer billing surfaces now resolve plan display data from the catalog and show Unknown when the backend returns an unmapped plan ID.
2026-06-19 16:52:22 +05:30
Arjun
aa8dfb74ad minor ui fixes and default model to claude 2026-06-19 16:24:29 +05:30
Arjun
dfd4075e0e filter out random contacts 2026-06-19 14:59:45 +05:30
Arjun
811ae22bb1 knowledge to brain 2026-06-19 11:59:56 +05:30
gagan
1f8ac2cf34
feat(bg-tasks): coding-from-meetings — auto-implement coding action items (#630)
* feat(bg-tasks): coding-from-meetings — auto-implement coding action items

A background-task flavor that watches for meeting notes, scans them for
actionable coding items, and autonomously implements them in isolated git
worktrees, summarizing results in the task's index.md.

- Emit `meeting.notes_ready` when Fireflies/Granola first write a meeting note
- Add optional `projectId` to BackgroundTask (pins a coding task to a repo)
- New `launch-code-task` builtin tool: per group of items, create a
  worktree-isolated, yolo, direct code session, wrap the prompt in an
  autonomous scaffold, run async, and finalize a per-session row in index.md
- Group code sessions under their meeting heading in index.md
- Summary from the code agent's `## Summary` section; file counts from
  `git diff` vs the worktree fork point (counts committed work, not just dirty)
- Guardrails: self-heal projectId across runs, cap launches per run, and bar
  the bg-task agent from managing/spawning tasks
- UI: "View available templates" -> Coding-from-meetings preset (repo picker,
  prefilled trigger + instructions)

See plan.md for the full design.

* let the copilot able to configure a coding background agent

* Delete plan.md

---------

Co-authored-by: Arjun <6592213+arkml@users.noreply.github.com>
2026-06-19 11:26:43 +05:30
Prakhar Pandey
36da053b8d Merge remote-tracking branch 'upstream/dev' into feat/byok-onboarding-simplify
# Conflicts:
#	apps/x/apps/main/src/ipc.ts
#	apps/x/packages/core/src/models/models.ts
#	apps/x/packages/shared/src/ipc.ts
2026-06-19 01:37:25 +05:30
Prakhar Pandey
92b95f659e feat(onboarding): simplify BYOK to provider + key, fetch models from key 2026-06-19 01:12:53 +05:30
gagan
2f926f8dc0
fix(copilot): make Copilot aware of native Slack and query selected channels (#627)
When Slack is connected natively (desktop/cURL auth, not Composio), the Copilot now routes Slack requests to the native slack skill instead of composio-integration, surfaces the user's selected channels in the system prompt, and steers catch-up queries to 'agent-slack message list --oldest' rather than unreads/search (which return empty under desktop-imported auth). Instruction cache is invalidated on slack:setConfig and knowledgeSources:upsert so changes take effect.
2026-06-18 11:53:56 -07:00
Harshvardhan Vatsa
c38ddef93f
feat: compose new email with contact autocomplete and AI drafting (#616)
* feat: compose new email with contact autocomplete and AI drafting

- Add a compose-new-email box to the email view with a recipient field
  that autocompletes from Gmail contacts (keyboard navigation, match
  highlighting, avatar chips)
- Build contact indices in core: gmail_sent_contacts syncs the SENT
  label via the Gmail API for full coverage of people you've emailed,
  with gmail_contacts as an instant local-snapshot fallback; both are
  pre-warmed at startup so the first keystroke is instant
- Add generateOneShot() one-shot text generation for the composer's
  "write with AI", resolving to the active default model/provider
- Add getAccountName() (parsed from a recent SENT message's From
  header, no extra OAuth scope) so AI drafts sign off with the real name
- New IPC channels: gmail:searchContacts, gmail:getAccountName,
  llm:generate, llm:getDefaultModel

* feat: attachments, undo/redo, and unified compose for new emails

- Merge ComposeNewBox into ComposeBox via a new 'new' mode, memoizing the
  component so inbox sync ticks no longer jank the open composer.
- Add file attachments: stage files in the renderer (25MB cap), pass raw
  base64 over IPC, and build a multipart/mixed MIME on send.
- Add undo/redo buttons to the compose toolbar.
- Single Write/Edit AI bar that generates a draft, then iteratively rewrites
  it; drop the hardcoded Gemini Flash model and use the default Copilot model.
- Suppress inbox reloads while the compose-new modal is open.
- Log llm:generate provider/model/output for debugging.

* fix: remove redundant Subject placeholder in composer

The subject row already has a 'Subject' gutter label, so the input's
placeholder repeated the word — an empty field read 'Subject' twice.
Drop the placeholder to match the To/Cc/Bcc fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:14:00 +05:30
gagan
3fe7f307ab
fix(code-mode): use System32 bsdtar to extract engines on Windows (#626)
Enabling Claude/Codex in Settings -> Code Mode failed on Windows with
"tar (child): Cannot connect to C: resolve failed / gzip: stdin:
unexpected end of file". PATH `tar` resolves to a GNU tar (Git/MSYS2)
that misreads the absolute archive path "C:\...\engine.tgz" as a remote
host:path spec.

Pin extraction to the bsdtar shipped in System32, which handles
drive-letter paths natively, with a relative-path fallback if it is
absent. macOS/Linux behavior is unchanged.
2026-06-18 02:03:21 +05:30
arkml
79162ebc69
feat: ship Slack as a knowledge source, hardened for production (#596)
* index slack and add to home page

* filter only useful slack messages in homr

* feat: bundle agent-slack CLI and route all calls through shared executor

Pins agent-slack@0.9.3, bundles it next to main.cjs (replaces the startup npm install -g), adds a structured-result executor with bundled/global/PATH resolution, a slack:cliStatus IPC probe, and a PATH shim so the Copilot skill keeps working.

* feat: surface Slack failures and add cross-OS auth fallbacks

Classify agent-slack errors (not_authed/rate_limited/network/bad_channel), persist per-source sync status with rate-limit backoff, and expose it via slack:knowledgeStatus. Fix the Settings Enable bounce-back with actionable copy, a browser-paste (parse-curl) fallback, and a Windows quit-Slack-and-import button; add home-feed empty/error states.

* feat: rank Slack home feed deterministically by recency

Drop the per-load LLM ranker (cost/latency/model dependency) in favor of a stronger deterministic filter + recency ordering. The filter now removes system messages, emoji/reaction-only posts, bare greetings/acks, and empty bodies, with a durable-signal escape hatch. Expand tests to one describe per noise class plus ordering/cap/volume coverage.

* fix: hide Slack knowledge Save button once saved

Only show the Save button when the channel list or enabled toggle differs from the last-persisted config, so it disappears after a successful save and reappears when a new channel is entered.

---------

Co-authored-by: Gagancreates <gaganp000999@gmail.com>
2026-06-17 12:52:27 -07:00
gagan
2ddec07712
Code mode: make packaged builds work via managed engine provisioning (#625)
* fix(code-mode): make packaged code mode work via on-demand engine provisioning

Packaged builds could never run code mode: the Claude/Codex ACP adapters are
spawned as separate `node <entry>` processes resolved at runtime, but esbuild
can't inline a dynamic spawn target and Forge strips the workspace node_modules,
so every release threw `Cannot find module '@agentclientprotocol/...'`. Dev
worked only because of the pnpm symlink.

Rather than bundle the ~400 MB of native engines (one claude + one codex binary
per OS), provision them on demand:

- forge.config.cjs: stage the two ACP adapters + their JS dependency closure into
  .package/acp/node_modules (npm-style nested layout, native engines skipped),
  exempt .package from the node_modules ignore rule, and only sign/notarize when
  APPLE_ID is set so unsigned local/CI builds can package.
- agents.ts: resolve the adapter from the staged location first (node_modules
  fallback in dev); provision the pinned engine and point the adapter at it via
  CLAUDE_CODE_EXECUTABLE / CODEX_PATH. No dependence on a user's global install.
- engine-provisioner.ts: ensureEngine() downloads the per-platform engine package
  from npm AT THE EXACT VERSION THE ADAPTER WAS BUILT AGAINST, verifies its sha512
  integrity, extracts atomically into ~/.rowboat/engines/<agent>/<version>/, and
  caches it. Version-pinning keeps the ACP handshake compatible.
- engine-manifest.ts + scripts/gen-engine-manifest.mjs: committed manifest of
  tarball URLs + integrity for all platforms, regenerated from the adapters'
  pinned versions on a bump.

Verified on macOS arm64: both engines provision and run, and both adapters
complete the ACP initialize handshake from the packaged .app against the
provisioned engines. Installer drops from ~790 MB to 390 MB.

* feat(code-mode): explicit per-agent Enable in Settings; no silent chat download

Code mode now requires the user to explicitly enable an agent before use, instead
of silently downloading a ~200 MB engine on the first chat message.

- Settings → Code Mode: each agent shows "Not enabled" + an Enable button that
  downloads its engine with a live progress indicator (download % → verify →
  install), then flips to "Engine ready". Driven by a new codeMode:provisionEngine
  IPC call + a codeMode:engineProgress push channel. The section now states the
  prerequisite explicitly: the agent must be installed (Enable) and logged in
  (claude login / codex login — code mode reuses that saved credential).
- Chat path no longer auto-downloads: getProvisionedEnginePath() returns the
  enabled engine or throws a clear "enable it in Settings → Code Mode" error, so
  there's never a surprise mid-conversation download. getAgentLaunchSpec is sync
  again.
- Agent status: `installed` now means "engine provisioned" (downloaded), driving
  the Enable/Ready state; the new-session dialog shows "Enable in Settings" and
  disables un-enabled agents. Dropped the dead PATH-probing for a global CLI.

Verified: empty cache -> status installed=false and the chat path throws the
enable-in-Settings error (no download); core, renderer, and main typecheck/build;
no new lint errors.

* fix(code-mode): show only percentage during engine download in Settings

* feat(code-mode): prune superseded engine versions after install

After a successful provision, remove any other version dirs (and their .meta) for
that agent so old ~200 MB engines don't accumulate across version bumps. Best-effort;
never fails a good install. Verified: a planted stale version dir + meta are both
removed after provisioning the current version.

* fix(code-mode): keep showing engine download % after reopening Settings

Provisioning state lived in the row component, which unmounts when the Settings
dialog closes — so reopening mid-download showed the Enable button again even though
the download was still running in the main process. Move provisioning state to a
module-level store with one persistent listener on codeMode:engineProgress, so a row
remounting (dialog reopened) reflects the live % and resolves to Ready on completion.

* fix(code-mode): flip Enable row straight to Ready after install (no Enable flash)

On successful provision the in-flight flag was cleared before the async status
refresh completed, so the row briefly (or until reopen) showed the Enable button
again. Await the status refresh before clearing the flag so it transitions directly
to Ready.

* fix(code-mode): optimistically show Ready right after Enable completes

Awaiting the status refresh wasn't enough — setStatus re-renders the parent
separately from the row, leaving a window where the in-flight flag was cleared but
the status prop was stale, so the row flashed/stuck on the Enable button until
reopen. Track just-enabled agents in a module-level set and treat them as installed
immediately; loadStatus still syncs the real status in the background.

* fix(code-mode): graft login-shell PATH + add startup deadline

#1 (the gh/git "command not found" in packaged builds): GUI/Finder launches inherit
launchd's stripped PATH (/usr/bin:/bin:...), so tools the engine spawns — gh, git,
rg, bash — fail even though they work from a terminal (e.g. Homebrew's
/opt/homebrew/bin/gh). Probe the user's login-shell PATH and graft it onto the
engine's env before spawn (shell-env.ts; no-op on Windows / probe failure).

#2: add a 60s startup deadline (initialize / session create+load) so a wedged engine
fails with a clear, stderr-enriched error instead of an infinite "(pending...)".
Overridable via ROWBOAT_ACP_STARTUP_TIMEOUT_MS. Manager now disposes the client on
startup failure so the spawned adapter doesn't leak.

Verified: getAgentLaunchSpec's env.PATH now includes /opt/homebrew/bin (where gh
lives); core builds; no new lint errors.

* chore(code-mode): comment out signing/notarization for local builds

Revert to the explicit comment-out approach for osxSign/osxNotarize: uncomment them
(with APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID) for a signed release build.

* chore(code-mode): keep signing/notarization active in committed config

The repo's forge.config ships with osxSign/osxNotarize enabled (release-ready).
Developers comment them out locally for unsigned test builds and don't commit that.

* chore: approve workspace build scripts so packaging runs non-interactively

The allowBuilds entries were left as "set this to true or false" placeholders, so
`pnpm install` / the pre-build deps check aborted with ERR_PNPM_IGNORED_BUILDS and
`npm run package` failed. Set them to true (and add node-pty, used by the code-mode
embedded terminal) so build scripts are approved and packaging works without a manual
`pnpm approve-builds`.
2026-06-17 21:53:15 +05:30
Gagan
8ce24ebb33 Revert "fix(code-mode): make packaged code mode work and drop ~460MB bundled engines (#614)"
This reverts commit 33c15cfbd9.
2026-06-16 23:29:46 -07:00
arkml
ed8f6f7246
Last modified (#624)
* order chats by recency
2026-06-17 10:21:24 +05:30
gagan
33c15cfbd9
fix(code-mode): make packaged code mode work and drop ~460MB bundled engines (#614)
* fix(code-mode): ship ACP adapters in packaged builds

Code mode spawns the Claude/Codex ACP adapters as separate `node <entry>`
processes resolved at runtime, so each must exist as a real file on disk.
esbuild can't inline them (dynamic require.resolve + spawn target), and Forge's
`ignore: /node_modules/` rule strips the workspace node_modules — so packaged
builds threw `Cannot find module '@agentclientprotocol/claude-agent-acp'` and
code mode was broken in every release. Dev worked only because the pnpm symlink
was present.

Stage the two adapters and their full production dependency closure into
.package/acp/node_modules during generateAssets, reconstructing an npm-style
nested layout: nest on version conflict (claude and codex keep their own
@agentclientprotocol/sdk; the @openai/codex launcher keeps its platform binary)
and skip platform-optional deps not installed for the build OS, so each OS ships
its own native binary. Exempt .package from the node_modules ignore rule, and
make the adapter resolver check the staged location first, falling back to
node_modules in dev.

Resolve dependency directories by walking node_modules directly rather than
require.resolve(`${pkg}/package.json`): the latter throws for packages whose
`exports` map doesn't expose package.json (e.g. @anthropic-ai/claude-agent-sdk),
which would silently drop them and their subtrees from the staged closure.

* fix(code-mode): drive agents from local install, drop bundled engines

Skip the platform-native engine packages (~230 MB each) when staging the ACP adapters and point each adapter at the user's local claude/codex via CLAUDE_CODE_EXECUTABLE / CODEX_PATH, erroring clearly when neither is installed. The codex resolver mirrors claude's login-shell probe so nvm/fnm installs resolve on macOS/Linux. Shrinks each installer ~460 MB.

* fix(code-mode): surface agent startup failures instead of hanging forever

- 60s deadline on adapter initialize / session create+load: an engine that
  launches but never completes the SDK handshake (e.g. an outdated local
  CLI) now fails with the adapter's stderr attached instead of leaving the
  turn (pending...) indefinitely; prompts themselves stay un-timed
- dispose the adapter client when startup fails so the spawned process
  does not leak
- set DEBUG_CLAUDE_AGENT_SDK=1 so the SDK logs the exact spawn command and
  claude's stderr to ~/.claude/debug/sdk-*.txt and startup errors point at
  that file (engine stderr is otherwise discarded entirely)
- graft the user's login-shell PATH onto the adapter env on macOS/Linux:
  GUI launches inherit launchd's stripped PATH, which breaks node-shebang
  claude launchers (nvm/npm installs) and the engines' own subprocess spawns

* ci(code-mode): cross-platform smoke matrix + one-shot diagnose script

- x-code-mode-smoke.yml: on apps/x PRs, package the app on mac/linux/windows
  and run acp-smoke.mjs, which asserts (1) adapters staged + native engines
  stripped, (2) each staged adapter boots from the packaged app via the
  packaged Electron binary and answers ACP initialize, (3) a fake engine that
  launches but never responds is converted into a clear startup-timeout error
  instead of hanging forever (the silent-hang class)
- diagnose-code-mode.sh: colleagues run one command and send one blob
  (engine versions/paths/types, login-shell vs GUI PATH, auth presence,
  stream-json probe, newest SDK debug log) — one round trip instead of five
- forge.config.cjs: only sign/notarize when APPLE_ID is set, so unsigned
  local mac builds and the CI smoke matrix can package deterministically
- client.ts: startup timeout overridable via ROWBOAT_ACP_STARTUP_TIMEOUT_MS
  (CI uses 10s; also an escape hatch for slow MCP-heavy setups)

Verified on Windows: all smoke checks pass, including the end-to-end
fake-hanging-engine timeout (fails in 10.0s with the stderr-enriched error).

* fix(ci): make smoke fake engines executable — codex-acp spawns CODEX_PATH directly on unix

A bare .js with no shebang/exec bit fails with EACCES and crashes the adapter
on mac/linux. Split into an exec-bit exit-0 fake for the handshake check and
the hanging fake for the timeout check.

* fix(code-mode): resolve claude/codex installed via version managers

commonInstallPaths only checked ~/.nvm/versions/node/<binary>, never
descending into nvm's versioned vX.Y.Z/bin subdirs, so an nvm-installed
codex/claude showed "not installed" in Settings and failed code-mode
chat with "CLI not found" on GUI launches (where the login-shell PATH
isn't inherited).

Enumerate each installed Node version's bin dir for nvm/fnm/asdf, and
add pnpm global (PNPM_HOME / platform default) and Claude Code's legacy
~/.claude/local install location. Shared by both the Settings status
check and the chat/tab binary resolvers, so it covers all code-mode
entry points.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(code-mode): remove code-mode smoke test workflow and script

Only needed during cross-platform verification of the packaged code-mode
fix; drop the CI workflow and its acp-smoke.mjs helper now that it's done.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:16:00 -07:00
Arjun
1632b16dfc add embedded terminal to code mode (node-pty + xterm)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 23:57:44 +05:30
Arjun
45100580c9 add code mode: coding-agent workspace with sessions, direct/Rowboat drive, diffs, and worktrees
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 23:57:44 +05:30
PRAKHAR PANDEY
0d21abcc7d
feat: add user-configurable notification settings (#601) 2026-06-11 19:43:26 +05:30
PRAKHAR PANDEY
faaefe936f
fix: resolve macOS mic permission on first click in voice mode (#613)
On macOS, the first getUserMedia({audio:true}) call hits TCC permission
status 'not-determined' — the OS prompt appears but the in-flight call
rejects, is silently swallowed, and the UI snaps back to idle. Second
click works because permission is already granted.

Fix: add voice:ensureMicAccess IPC channel (mirroring the existing
meeting:checkScreenPermission pattern) that calls
systemPreferences.askForMediaAccess('microphone') before getUserMedia,
so the same first click proceeds once the user grants access.

Also fixes a secondary bug: on the failure path, the code only called
setState('idle'), leaking the WebSocket that connectWs() had already
opened. Now calls stopAudioCapture() for proper cleanup.
2026-06-11 02:07:18 +05:30
gagan
9453e0d550
feat: render background-task HTML output and open its links externally (#615)
* feat: render background-task index.html output in a sandboxed iframe

The task output pane now prefers bg-tasks/<slug>/index.html when present and
non-empty, rendering it full-bleed via HtmlFileViewer (app://workspace
protocol) so CSS, layout, and scripts render faithfully. Falls back to the
markdown index.md note when there is no HTML artifact. The viewer remounts on
refreshKey so a re-run's updated HTML reloads. The Source/Rendered toggle works
for both formats.

The runner agent is instructed to choose index.md (default, notes) vs a
self-contained index.html (visual/styled output) per run, written via the
existing file-writeText tool. The Copilot background-task skill notes the HTML
option so visual asks are steered toward it.

* fix: open links from HTML report iframes in the system browser

Links inside the sandboxed iframe that renders a background-task/workspace
index.html did nothing on click, unlike the markdown viewer which opens links
in the browser.

Two causes: target="_blank" links were blocked by the sandbox before reaching
the window-open handler, and plain links fire will-frame-navigate (subframe),
which the app did not handle (will-navigate only covers the main frame).

- Add allow-popups to the HtmlFileViewer iframe sandbox so target="_blank"
  reaches setWindowOpenHandler, which routes to shell.openExternal.
- Handle will-frame-navigate in main, routing external subframe navigations to
  the system browser. Scoped to app://workspace frames so third-party note
  embeds (YouTube/Figma/Twitter) keep their internal navigation.
2026-06-11 01:45:10 +05:30
Harshvardhan Vatsa
c48ef5ac0c
add Gmail contacts autocomplete to compose box (#607)
Adds a gmail:searchContacts IPC channel backed by two indices: a
SENT-label API-backed index (gmail_sent_contacts) for full historical
coverage of people you've actually emailed, and a local-snapshot
fallback (gmail_contacts) used until the SENT sync finishes on first
launch. Both indices warm at startup so the first keystroke in the
recipient box is instant. Renderer wires the suggestions into the
to/cc/bcc fields in email-view with styled chips.

Co-authored-by: arkml <6592213+arkml@users.noreply.github.com>
2026-06-10 14:58:13 +05:30
gagan
372309eb18
feat: run code mode on an in-app ACP client with live approvals (#593)
* feat(code-mode): add ACP client engine (Layer 2 core)

Own the Agent Client Protocol client instead of shelling out to `acpx`, so code
mode can stream structured events (tool calls, diffs, plan) and surface live
permission requests. Headless acpx can't do live approvals (it only supports
--approve-all), which is why we drive the agent adapters ourselves.

- code-mode/acp/{agents,client,permission-broker,session-store,manager,types}.ts:
  headless engine driving the Claude/Codex ACP adapters; one warm session per chat
  with create-or-resume via session/load; approval policy (ask | auto-approve-reads
  | yolo) in the broker.
- claude-exec.ts: cross-platform claude resolver (Windows .cmd EINVAL fix + macOS/Linux
  GUI-PATH safety net) shared with the legacy acpx path in builtin-tools.ts.
- add @agentclientprotocol/sdk + claude/codex adapters to core.

* feat(code-mode): route code mode through code_agent_run tool + live approvals

Replace the acpx shell-out with a structured code_agent_run tool that drives the
ACP engine directly, streaming the agent's tool calls / diffs / plan into the chat
and surfacing permission requests inline.

- shared: code-mode.ts zod schemas; add code-run-event + code-run-permission-request
  RunEvent variants (stream to the renderer over the existing runs:events channel);
  codeRun:resolvePermission IPC channel.
- core: CodePermissionRegistry (promise-based mid-run approvals — the LLM tool-loop's
  pre-call gate can't model a mid-execution wait); register codeModeManager +
  codePermissionRegistry in awilix.
- core: code_agent_run builtin tool (streams via ctx.publish, asks via the registry,
  cancels on ctx.signal, returns the agent summary). CodeModeConfig.approvalPolicy
  (ask | auto-approve-reads | yolo; default ask). Exclude the tool from the headless
  background-task / live-note / inline-task agents so they can't block on an approval.
- main: codeRun:resolvePermission handler -> registry.resolve.
- rewrite the code-with-agents skill and the runtime "Code Mode (Active)" block to call
  code_agent_run instead of emitting npx acpx commands.

* feat(code-mode): render coding runs inline (live timeline + permission card)

Render a code_agent_run tool call as a live CodingRun block instead of generic
tool output: the agent's text, tool-call rows (kind icon + status + changed-file
names from diffs), a plan checklist, and resolved-permission lines — plus an
inline Allow / Always-allow / Deny card wired to codeRun:resolvePermission.

- chat-conversation.ts: ToolCall carries codeRunEvents + pendingCodePermission;
  code_agent_run is excluded from tool-grouping so it renders standalone.
- App.tsx: handle code-run-event / code-run-permission-request, clear the pending
  card on tool-result, handleCodePermissionResponse, render via CodingRunBlock.

* fix(code-mode): run the ACP adapter as Node under Electron + resolve it from main

Two runtime failures that only surfaced inside the packaged/bundled Electron app
(the headless harness used real node, so neither showed there):

- "ACP connection closed": the main process spawns the adapter via
  process.execPath, which inside Electron is the Electron binary, not node — so
  the child never ran as Node and its ACP stdio stream closed immediately. Set
  ELECTRON_RUN_AS_NODE=1 on the adapter env (a no-op under real node).
- "Cannot find module '@agentclientprotocol/claude-agent-acp'": the adapters were
  transitive (core) deps, unreachable from the esbuild-bundled main.cjs. Add them
  as direct deps of the main app so require.resolve finds them at runtime (and so
  they ship when packaged).

Also capture the adapter's stderr + exit code and enrich connection errors, so a
future failure reports the real cause instead of the opaque "ACP connection closed".

* chore(code-mode): remove dead acpx code paths and stale copy

Code mode now runs through the code_agent_run tool (owning the ACP client), so the
legacy acpx shell-out paths are dead. Remove them:

- core: envForCommand (acpx-only CLAUDE_CODE_EXECUTABLE injection) from
  executeCommand; getCodeModeCommandLabel (acpx run-status label).
- renderer: the acpx-detection "switch agent / auto-flip the code-mode chip" flow —
  App.tsx executeCommand detection, the permission-request onSwitchAgent button +
  badge, and the composer's code-mode-detected listener.
- copy: Settings -> Code Mode and the code-with-agents skill summary no longer
  mention acpx; tidy stale comments (claude-exec, command-executor).

No behavior change for code mode; the general executeCommand tool is unaffected.

* feat(code-mode): approval-policy selector in Settings

Surface the approval policy (Ask every time / Auto-approve reads / YOLO) in
Settings -> Code Mode, instead of being config-file only. The broker already
reads CodeModeConfig.approvalPolicy; this plumbs it through the
codeMode:getConfig / setConfig IPC + main handlers and adds the picker UI
(with a one-line explanation of each level). Defaults to "ask".

* fix(code-mode): harden ACP engine — turn-scoped connections, chip-authoritative agent, reliable stop

Three robustness fixes that co-modify manager.runPrompt and the code_agent_run
tool, so they land together:

- Lifecycle: scope each ACP adapter connection to the agent turn. Dispose it a
  short grace (60s) after the turn ends instead of holding it for the app's life;
  the next turn resumes via session/load (both agents support it). Wire
  disposeAll() on app quit (was dead code). Fixes the unbounded per-chat leak of
  booted agent processes.

- Agent selection: make the composer chip the source of truth. Thread codeMode
  into ToolContext; code_agent_run uses it instead of the model's guessed `agent`
  arg, which anchored on the thread's earlier agent and ignored a chip change.
  Prompts updated to match; the run is labelled by the agent that actually ran.

- Stop/abort: guarantee a stopped turn unwinds. On abort the manager sends ACP
  session/cancel, then force-kills the adapter after a 2s grace and resolves the
  turn as cancelled — a wedged adapter can no longer hang the run and lock the
  chat. code_agent_run returns a clean cancelled result.

* fix(code-mode): hide Codex's native console window on Windows

Codex's engine ships as a native console-subsystem binary (codex.exe). Launched
from our console-less Electron process tree, Windows allocated a fresh *visible*
console window for it; closing that window wedged the run in a pending state.
(Claude Code is a Node CLI, so it never triggers this.)

The window is created by @openai/codex's launcher (bin/codex.js), which spawns
codex.exe with no windowsHide. Patch it via pnpm to pass windowsHide: true
(CREATE_NO_WINDOW) so the console stays hidden — no window, nothing to close.

* refactor(code-mode): move ACP session files out of WorkDir/config

Per-run ACP session state is runtime state that accumulates one file per
chat run, not user/app config. Relocate it from WorkDir/config to a
dedicated WorkDir/code-mode/sessions/ so it can be listed, cleaned up, and
managed on its own without crowding config. Drop the now-redundant
codesession- filename prefix (the directory conveys it).
2026-06-05 14:45:08 +05:30
Ramnique Singh
d47cab6a0f Add run-level auto permission mode
- add LLM-based auto permission classifier for permission-gated tool calls
- store run-level permission mode and auto permission decision events
- auto-approve low-risk calls, and bubble auto-denied calls to manual approval
- show auto-denied reasons in chat and auto-approved labels below tool cards
- add BYOK setting for the auto-permission decision model
2026-06-03 07:58:04 +05:30
arkml
caea83aecf
clamp sync to 7 days even after long hiatus (#590) 2026-05-29 22:23:21 +05:30
Ramnique Singh
732401f72e Add app version to analytics events 2026-05-29 17:02:01 +05:30
arkml
5ae853e15c
fix thread boundary in email reply drafts (#588) 2026-05-29 10:58:57 +05:30
Ramnique Singh
129d91dc8d Persist per-message context for prompt caching
Move volatile current time and middle-pane data out of the system prompt and into a hidden userMessageContext stored on each user message. Reconstruct the LLM-facing message from this persisted context so older conversation turns remain stable across later requests while UI-facing content stays unchanged.

Keep finite branch instructions such as voice, search, code mode, agent notes, and workdir behavior in the system prompt so each conversation can still benefit from reusable prompt-cache prefixes.
2026-05-28 23:00:44 +05:30