Rowboat code sessions (copilot orchestrating the coding agent) now run on the
new sessions/turn runtime instead of the retired LLM run path.
Core:
- TurnEvent gains code-run-event + code-run-permission-request variants;
ComposeContext + SendMessageOptions gain codeCwd + codePolicy.
- The agent loop threads codeCwd/codePolicy from the turn's composeContext into
ToolRunContext; RealToolRunner passes them to code_agent_run and stops
deferring the code-run events — it forwards them onto the turn's event stream.
- turnToChatState/applyOverlay accumulate per-tool code-run events + the pending
code permission and attach them to the code_agent_run tool call (live-only;
a completed turn collapses to the tool result).
Code-mode:
- CodeSessionService.create makes a rowboat session a real sessions row (id
shared) so sessions.sendMessage drives it; delete() also removes that row.
- sessions:sendMessage IPC handler pins the coding agent's agent/cwd/policy from
the code session meta (server-side source of truth), mirroring the old
createMessage override.
Rowboat renders in the main chat (App.tsx binds the tab's runId to the code
session id — preserved from dev); the code_agent_run tool card shows the agent's
plan/diffs/permission via the existing CodingRunBlock. Direct mode is unchanged
(its own runtime from step 1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebased new-runtime onto dev (which added code-mode on the old runs-based
runtime). Reconciliation: kept the generic event-log + bus (runs/repo.ts,
runs/bus.ts, a trimmed runs.ts = createRun + fetchRun only) decoupled from the
retired LLM runtime; restored the runsRepo DI registration and the
bus -> runs:events forwarder.
Step 1 of the code-mode migration — direct mode on its own dedicated runtime:
- new SQLite-backed CodeEventStore (migration 0008 + code_session_events) replaces
the runs JSONL log for code sessions
- dedicated codeEventBus (InMemoryBus) + codeSession:events feed +
codeSession:getEvents history channel, replacing the shared bus / runs:events /
runs:fetch for code
- CodeSessionService mints its own id (drops createRun) and writes to
codeEventStore / codeEventBus; status-tracker subscribes to codeEventBus
- renderer (use-code-chat) loads history via codeSession:getEvents and streams via
codeSession:events
Rowboat mode is temporarily disabled in the UI (its old copilot-LLM path is
retired); it moves onto the new sessions runtime in step 2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
shared/ipc.ts: add sessions:* channels (create / get / list / sendMessage /
getHistory / listTurns / getTurn / respondToPermission / setToolResult /
resumeTurn / stopTurn / delete) and the sessions:events feed; remove the runs:*
channels.
main:
- register the sessions handlers and forward the turn event bus to renderer
windows; getAgentRuntime() at startup
- stop in-flight headless runs via stopTurn (live-note / bg-task)
- drop the runs watcher, runs:* handlers, and the dev test-agent script
renderer:
- single global session-feed consumer; useSessionChat(sessionId) hook; pure
turn -> chat-state mappers (agent-turn-view, session-chat-state); shared
ChatConversation component
- chat (main view + sidebar) renders from the session feed; per-turn model /
permission mode; bg-task and live-note detail views load transcripts via
sessions:getTurn; chat delete via sessions:delete
- remove the dormant run-event path (handleRunEvent + runs:events) and its
orphaned state
- vitest + jsdom test setup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the old run-based agent runtime with an append-only, fact-log turn model.
agent-loop:
- turn = append-only fact logs; advance() reducer IS the resume function
- SystemComposer (loop-injected system prompt) + TurnObserver (live deltas +
state snapshots), per-turn ComposeContext (voice / search / code-mode) and
use-case attribution; per-step model usage; transcript prefix dedup at rest
sessions:
- ordered turn chains with copy-forward transcript; deleteSession support
agent-runtime bridges:
- RealToolRunner, RealPermissionGate (per-turn use-case), Copilot system /
user-message-context composers, TurnEventBus
- getAgentRuntime() process singleton; runHeadlessAgent() runs one-shot headless
work as a standalone turn (sessionId null) — no throwaway sessions, no growth
shared contracts (@x/shared): agent-turn.ts, sessions.ts (browser-safe)
headless consumers ported to standalone turns with use-case tagging: live-note,
background-task, knowledge pipelines (agent_notes / tag_notes / label_emails /
build_graph / inline_tasks), pre-built, agent-schedule.
retire the old runtime: delete runs/{runs,bus,repo}.ts and message-queue.ts; gut
agents/runtime.ts to loadAgent + convertFromMessages; trim agents/utils.ts; drop
the DI registrations for the old runtime / message queue / runs repo.
SQLite migrations: compose_context, model_usage, transcript dedup, use_case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three follow-ups from the runtime design review:
Transcript prefix dedup (storage-level, transparent):
- A session turn's input starts with the previous turn's closed transcript
(copy-forward); storing it again made session storage quadratic and every
fact append rewrote the whole transcript. Stores now keep only the suffix
past prefix_length and recompute the prefix on read from the immutable
previous turn (closedTranscript moved to agent-loop/types.ts; shared
split/join helpers in prefix-dedup.ts with loud tripwires).
- Opportunistic: input that doesn't extend the previous transcript (e.g.
future compaction summaries) falls back to whole-row storage. Nothing
above the stores changed; InMemoryTurnStore mirrors SQLite exactly.
- Migration 0005 adds prefix_length (default 0 = stored whole).
Per-model-call usage recording:
- New modelUsage fact log on the turn: one entry per model call, committed
in the same write as the assistant message it paid for. ModelAdapter
results now carry {message, usage}; VercelModelAdapter treats usage
reporting failures as null rather than failing a completed step.
- totalUsage() derives turn aggregates; null means never reported.
- Migration 0004 adds the model_usage column.
EventStream: replay removed, bus-style live delivery:
- Events go to consumers attached at push time and are dropped otherwise,
matching the runtime's IBus philosophy (facts persisted, deltas cosmetic,
renderer reconciles from snapshots). No buffering without consumers;
per-iterator queues bounded by consumer lag. Iterators attach
synchronously at creation so the loop's own for-await never misses events.
Also: dispatched-call closures get honest wording ("may have completed
externally") distinct from interrupted and never-ran; SqliteTurnStore.update
keeps the no-op write guard alongside the prefix_length read.
8 new tests (87 total).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A session is a grouping label (id, agentId, title) over an ordered chain
of self-contained turns; the loop itself never learns sessions exist.
- sendMessage builds each turn's input by copying the previous turn's
full transcript forward (copy-forward history) and only ever chains on
TERMINAL turns: anything running, waiting, or crashed-idle must be
resolved or stopped first. Terminal turns are immutable, so the copied
snapshot can never go stale or be re-activated.
- stopTurn now persists the stop as a terminal turn error (code
"stopped") instead of leaving the turn idle/resumable; resumeTurn,
setToolResult, and respondToPermission all reject terminal turns.
Unresolved tool calls on a stopped turn are closed out with synthetic
ToolMessages when history is copied forward — never re-executed.
- Turn rows gain sessionId/sessionSeq (opaque to the reducer) with a
UNIQUE(session_id, session_seq) index as a fork tripwire; createTurn
is now async and persists the row before returning so a claimed seq is
visible the moment the caller holds the handle.
- New sessions/ module: Session types, SessionStore (in-memory +
SQLite), SessionsImpl facade with a per-session KeyedMutex (extracted
from the loop's TurnMutex). getHistory returns the same closed-out
transcript the next turn will actually send.
- Migration 2026-06-12_0003_sessions: sessions table + additive turn
columns. Provider/model/permissionMode deliberately flow per
sendMessage call and land on the turn row, not the session.
- 21 new vitest tests (79 total): copy-forward, supersede-after-stop,
crashed-turn blocking, terminal-mutation guards, concurrent-send
serialization, SQLite round-trips and unique-seq enforcement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New @x/core module (src/agent-loop) — groundwork for replacing the JSONL
event runtime. Development-only; not wired into the app yet.
- Durable unit is a turn: append-only fact logs (messages, permission
requests/decisions, started/dispatched tools) + set-once error/completedAt;
status is derived from facts, never stored
- advance() reducer drives the loop purely from persisted state; crash
recovery = resumeTurn() re-entering the reducer
- Permission gating as data: batched requests, user/classifier decisions
with reasons, denials materialized as ToolMessages
- TurnHandle API per entry point: stream events or await the rest state
- Kysely migration (agent_loop_turns) + SqliteTurnStore with zod-parsed
reads; InMemoryTurnStore for tests
- VercelModelAdapter over the existing createProvider()/streamText
- 33 vitest tests: reducer, permissions, classifier, crash recovery,
abort, concurrency, SQLite round-trips
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Introduce a core-owned Kysely SQLite storage layer backed by
$WorkDir/db/rowboat.sqlite, with startup initialization, shutdown
cleanup, in-code migrations, and initial storage metadata schema.
Ignore database files in the workspace watcher, add focused
storage/watcher tests, and update Electron packaging to stage and rebuild
better-sqlite3 against Electron's native module ABI.
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.
* 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.
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>
* 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).
- 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
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.
* feat: add in-chat code mode toggle with claude/codex swap
* feat: show agent and add swap-and-retry on acpx permission card
* style: reorder permission card buttons (approve, deny, swap)
* feat: add tooltips to composer plus and web search buttons
* feat: add code mode settings tab with agent install/auth checks
* feat: show sign-in command when agent installed but signed out
* style: refine code-mode permission and command block UX
- Render permission block before the command block
- Collapse permission details after a response; click header to expand
- Drop status icons/badge; use minimal green / bold red blocks
- Auto-collapse the running command block once it completes
* feat: rotating progress labels for code-mode commands; darker tool borders
- Code-mode (acpx) command block shows status-aware labels: rotating
'Working on the task…' phrases (5s each, holding on the last) while
running, then 'Completed the task' / "Couldn't complete the task"
- Darken outer border on all tool blocks in light and dark modes
* fix: detect Claude Code sign-in via macOS Keychain
On macOS, Claude Code stores OAuth credentials in the login Keychain
(service 'Claude Code-credentials'), not in ~/.claude/.credentials.json.
Read the Keychain as a fallback so signed-in Mac users are detected.
* feat: persistent per-chat sessions for code-mode coding agents
- Use a named acpx session (rowboat-<runId>) per chat so follow-up
coding requests resume the same agent and keep context
- Create the session once at chat start (sessions new --name), then
prompt with -s <name>; reuse on follow-ups (no re-create)
- Drop the redundant in-chat 'reply yes' confirmation (the executeCommand
permission card is the confirmation)
- Code-mode output uses plain-text paths (overrides global filepath rule)
- On not-installed/auth errors, point user to Settings -> Code Mode
* fix: code-mode session creation uses idempotent ensure, run sequentially
- Use 'sessions ensure --name' instead of 'sessions new' so reopening a
chat resumes the existing session instead of erroring on a name clash
- Create the session and run the prompt as separate sequential calls so
the permission/command blocks render one at a time (not all at once)
* fix: reliable Claude Code session resume on Windows (avoid claude.cmd EINVAL)
Resuming a code-mode chat after restarting the app spawns a fresh ACP
agent. On Windows + Node >=20.12 the bridge spawning claude.cmd throws
EINVAL, so the session queue owner fails to start. Rowboat injects
CLAUDE_CODE_EXECUTABLE=claude.exe to dodge this, but the override didn't
reliably reach the spawn. Windows-only; no-op on macOS/Linux.
- executeCommand now accepts an env override and the non-abortable
fallback path passes it through (was silently dropped)
- resolveClaudeExeOnWindows also scans known npm/pnpm/volta global bin
dirs, not just PATH (Electron's runtime PATH can omit them)
- add --timeout 600 to acpx prompt commands so a genuine stall fails
cleanly instead of hanging on 'Running' forever
When the app launches after the access token has expired, the Gmail and
Calendar sync loops both try to refresh at the same instant. The backend
dedup returns 429 to whichever request arrives second, and we were
treating that 429 as a permanent failure — writing an error into
oauth.json that surfaces in the UI as "Needs reconnect", even though
the tokens are valid and the other refresh succeeded.
Two changes address this:
- GoogleClientFactory now serializes concurrent getClient() callers
end-to-end, so Gmail and Calendar share a single refresh round-trip
instead of racing the backend.
- A 429 (or 5xx) from the refresh API is now classified as transient:
we leave stored tokens and the in-memory cache alone and let the
next sync tick retry, rather than flagging the user for reconnect.
Refresh logs now include enough detail (time-since-expiry, new TTL,
cause chain on failure) to diagnose the next class of issue from a
single user's log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace workspace-scoped builtin file tools with general-purpose file-* tools that accept relative, absolute, and ~/ paths. Relative paths still resolve against the configured workdir.
File operations within the workdir are auto-approved. File operations outside the workdir now emit file permission metadata and require user approval, with support for once, session, and persistent grants.
Add a shared filesystem layer for text-focused read/write/edit/list/search operations, including binary-file safeguards for text reads. parseFile and LLMParse continue to read file buffers for document/image parsing.
Update copilot prompts, background/live-note agents, knowledge workflows, and renderer labels/UI to use the new file-* tool surface and permission details.
Add package-local Vitest setup for @x/core with colocated filesystem unit tests covering path resolution, canonical permission paths, binary detection, read/write/edit behavior, glob, and grep.
* added send, archive and delete
* fix scopes
* added replyall, cc, bcc etc
* - Added scope-aware Gmail status via gmail:getConnectionStatus, so the email empty state can
distinguish “not connected” from “connected but missing new Gmail scope.”
- Hardened Gmail send header construction against CR/LF header injection.
- Switched MIME parts from invalid UTF-8 7bit bodies to base64-encoded UTF-8 parts.
- Made forward send as a new message instead of attaching it to the original thread, and included
forwarded message content.
- Changed archive/delete UI behavior to remove the thread only after Gmail confirms success.
---------
Co-authored-by: Ramnique Singh <30795890+ramnique@users.noreply.github.com>
* fix: fall back to next port when OAuth callback server can't bind 8080
On Windows with Hyper-V/WSL2/Docker, port 8080 is often reserved by the
OS (EACCES) or already in use (EADDRINUSE), making sign-in completely
impossible. The app now scans 8080–8089 and binds the first available
port. For DCR providers, a stale registration locked to a blocked port
is detected and cleared so the client re-registers on the new port.
Static-client providers (Google BYOK) keep fixed-port behaviour with a
clear error message instead of a raw Node.js exception.
* fix: keep createAuthServer fixed-port by default, opt-in fallback
Address review feedback:
- Flip createAuthServer default to fixed-port; fallback is now opt-in via
{ fallback: true }. Composio (composio-handler.ts) keeps exact-port
semantics with no code change — only the Rowboat sign-in call site,
which builds its redirect URI from the actual bound port, opts in.
- Wrap post-bind setup (DCR, PKCE, auth URL) in try/catch and close the
server on any failure so the port is released for retries.
* fix: clear stale DCR registration when bound port differs from start port
Attach the current analytics use-case context to Rowboat gateway requests so backend billing generation rows can capture use_case, sub_use_case, and agent_name.
Wrap streamed agent calls and direct instrumented LLM call sites in explicit use-case context to keep metadata available when provider requests are created.
Add a read-only TipTap-backed RichMarkdownViewer and use it for Background Tasks output so rendered index.md files can display the same rich fenced blocks as notes, including email, calendar, chart, table, image, embed, transcript, and Mermaid blocks.
Keep the existing Source/Rendered toggle for raw markdown inspection, and hide editor-only delete controls in read-only output.
Move the rich block format examples out of the LiveNote-only prompt and into the shared knowledge note style guide. This gives both LiveNote and Background Task agents the same canonical renderer contract, including exact fenced-code schemas for rich Markdown blocks and the rule to avoid emitting task blocks as agent output.
Verified with:
- npm run build in apps/x/apps/renderer
- npm run build in apps/x/packages/core
* email view
* render html emails
* match unread and read status
* move to accordian
* faster loads
* iframe mounted across toggle and cached height
* prefetch on hover
* fix iframe caching
* split inbox
* email processing agent
* summary
* rich text
* email drafts
* add pagination, watcher and separation from gmail sync
* fix first load issue
* handle drafts
* send button opens the thread
* simplify renderer and fix flickering issue
* remove rended driven email path
* support attachments in incoming emails
* fix white background as well as dark mode
Adds Background Tasks — recurring background agents the user can set up to
either keep a digest current (daily email summary, top HN stories, weather
brief) or perform a recurring action (draft a reply, post to Slack, call an
API). Each task is a persistent set of instructions plus optional triggers
(schedule, time-of-day window, or matching incoming Gmail / calendar event).
The agent reads the verbs in the instructions on every run and picks the
right mode automatically.
User-facing surfaces:
- New "Background tasks" entry in the sidebar, with a table listing every
task, its schedule, last run, and an active toggle.
- A detail page per task with a max-width reader showing the task's
current output and a control sidebar for editing instructions, triggers,
and reviewing run history.
- "New task" can open in a free-form box where the user describes what they
want and Copilot sets it up end-to-end, or in a structured form for
manual setup.
- "Edit with Copilot" hand-off from the detail view, pre-seeded with the
task's context.
Under the hood:
- The event pipeline that previously powered live-notes is now a generic
consumer registry. Live-notes and background tasks both subscribe;
incoming events are routed to candidates from both concurrently.
- Schedule helpers and the agent-message trigger block are factored out of
live-notes into shared modules. Both features use the same building
blocks now.
- Copilot's proactive routing is reframed: anything recurring (cadence
words, watch / monitor verbs, action verbs, event-conditional asks) now
flows to background tasks. Live-notes load only on explicit mention.
- A small reliability fix for the run-creation fallback chain: an
empty-string model/provider passed by an LLM tool call now correctly
falls through to the default instead of being persisted as a real value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>