Commit graph

325 commits

Author SHA1 Message Date
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
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
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
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
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