TurnRepoContextResolver was still freely constructible, so a future
call site could silently lose elision. The factory now accepts an
injectable policy loader (also keeps tests off the machine's real
context.json), the raw class carries a do-not-construct note, and
factory-level tests pin both the decorator wiring and the default
policy behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real session data shows ~5k-char skill bodies are the most common
oversized historic result; the 10k default replayed them on every model
call for the life of the session. With the head preview in place the
model retains enough scent to re-load on demand, so the aggressive
default is the right trade. Still tunable via config/context.json.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bare placeholder tells the model only the tool name and size; the
first 400 characters tell it what the output actually was (a skill
guide reads very differently from a fetched page), which is what it
needs to judge whether re-running the tool is worth it. The preview is
capped at the threshold so small thresholds still shrink content, and
the transform stays a pure per-message function (byte-stable prefixes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every user message sent while a note is open carries a full snapshot of
that note in userMessageContext, so a long chat over one open note
resends N full copies on every model call. The elision decorator now
rewrites prior-turn user messages to keep the pane kind and path but
replace note content above a small floor with a placeholder pointing at
the still-readable file. The current message's snapshot is untouched,
so "summarize this" keeps working; the system prompt already tells the
model later middle-pane context overrides earlier. Config:
elideHistoricMiddlePaneContent, default on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Video-mode webcam and screen-share frames matter for the response they
were captured for; afterwards the assistant's own text carries the
takeaway and fresh frames arrive with every new call message, yet each
message's frames (~10k tokens) were resent on every subsequent model
call. The elision decorator now strips image parts from prior-turn user
messages, leaving a text part recording how many frames of each kind
were dropped. The current turn's just-captured frames are always sent
verbatim, and the policy generalizes the existing config
(elideHistoricImages, default on).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tool results from prior turns (skill loads, file reads, HTTP fetches)
dominate resent context and are rarely needed verbatim. A decorator over
the context resolver now replaces prior-turn tool results above a size
threshold with a short placeholder telling the model to re-run the tool
if it needs the output. The current turn's in-flight results are always
sent verbatim, the durable log is untouched, and elision is a pure
per-message function so resolved prefixes stay byte-stable for provider
prefix caching.
Policy lives in config/context.json (elideHistoricToolResults, default
on; elideHistoricToolResultsThresholdChars, default 10000). The inspect
CLI composes through the same decorated resolver so debug output still
matches transmitted bytes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bundled agents can't ship a model override (the author's providers are
not the installer's), so materialized tasks ran on the bg-task category
default — gemini flash lite — which reliably mangles large app-set-data
payloads (invalid JSON string → contract rejection → the installed app
never gets data; observed with pr-dashboard's refresh agent).
Materialization now resolves getDefaultModelAndProvider() on the host
and pins that on the new task — the installer's machine picks the
strong model, the package still pins nothing. Existing tasks keep
their user-set model (update path unchanged).
The skill treated mirroring the bg-task into agents/<slug>.yaml as
optional ('if the app should ship the agent'), so the copilot built
agent-backed apps (e.g. pr-dashboard) with the refresher as a loose
personal bg-task only. Publishing such an app ships a dead UI: data/
is user state and never packaged, and installers get no agent.
Bundling is now a required step with the failure mode spelled out.
GitHub rate-limits the device-code token endpoint: a poll arriving even
slightly under the flow interval returns slow_down and permanently raises
the required interval. The renderer polled on a fixed 5s timer (exactly
the limit), so one jittery request tripped slow_down and every poll after
it was 'too fast' — GitHub answered slow_down forever and the dialog sat
on 'Waiting for authorization' even after the user approved.
Core now tracks lastTokenPollAt and skips the request until the flow's
current interval (base +1s margin) has elapsed, so slow_down bumps take
effect and the flow recovers. The renderer timer is just a heartbeat (2s).
- start the apps server before createWindow instead of after the full
service-init chain: the Apps view was reachable ~10s before port 3210
was listening, so every app iframe opened to connection-refused
- retry EADDRINUSE binds (quick relaunch races left the server disabled
for the whole session with no retry)
- app frame: auto-retry the iframe instead of a dead manual Retry, and
surface when the apps server itself is down (new apps:serverStatus)
- log render-process-gone/child-process-gone reasons; renderer helper
crashes were previously invisible in packaged builds
Re-reading refs/heads/main immediately after the bootstrap commit can 409 on
a stale replica (GitHub eventual consistency) — the Contents API response
already carries the commit sha, so use it directly.
Resumed publishes skipped already-done steps without emitting progress, so
the dialog showed stale spinners for steps that finished in a prior attempt.
GitHub's Git Data API (blobs/trees/commits) returns 409 'Git Repository is
empty' on a repo with no commits — it cannot create the first commit. Ensure
main exists via a Contents API bootstrap commit (the generated README), then
chain the publish commit onto it.
Verified the wire calls are correct (both endpoints behave with this client
id from a plain node run), so instrument the runtime path instead of
guessing:
- log every poll outcome ([GitHubAuth] lines) at the token exchange, the
identity fetch, and the IPC handler
- identity failures now retry at most 3 polls then fail loudly with the HTTP
status — never silently degrade to 'pending' forever
- explicit User-Agent on all GitHub calls (defensive)
GitHub's OAuth endpoints take form-encoded params; the JSON token request
produced an unrecognized error that fell through to 'pending' forever, so the
dialog never advanced after the user authorized.
- form-encode device/code and access_token requests
- unknown token-endpoint errors now fail loudly instead of spinning;
incorrect_device_code maps to expired (restart offered)
- issued-token is remembered so a transient identity-fetch failure retries on
the next poll instead of burning the consumed device code
- dialog catches hard poll failures and surfaces them
- env: GITHUB_OAUTH_CLIENT_ID (device flow enabled on the Rowboat OAuth app;
overridable via ROWBOAT_GITHUB_CLIENT_ID)
- packager (§4.4): allowlist-only .rowboat-app ZIP (yazl), sorted entries,
symlink skip, sha256
- github-auth (§10): device-code start/poll, identity fetch, token stored
0600 with safeStorage encryption injected from main (core stays
electron-free); githubAuth:* IPC + external open of the verification page
- registry client (§9.2): unauthenticated tarball index with 5-min cache and
stale fallback, raw-record resolve, substring search, quota-free
latestManifest via release-asset redirect with name-mismatch guard
- registry repo contents (docs/apps-registry): record JSON schema +
validate-and-merge Action implementing §9.3 checks 1-7 with rejected:<code>
comments and per-name concurrency
- fix: host-api copilot-run adapted to dev's ModelSelection {provider,model}
(this is the same fix dev needs for Ramnique's packaging break)
- pnpm 11: blockExoticSubdeps=false (electron-forge has a git subdep)
The runs->turns migration broke codex live streaming in copilot chat: the
turns bridge's publish shim forwarded only tool-output-stream and silently
dropped code-run-event / code-run-permission-request (the latter would
deadlock a turn under policy 'ask'). An interim fix persisted every stream
event as durable tool_progress, but that wrote each text chunk to the turn
file — unsustainable.
Final architecture — live and durable paths split:
- Live (ephemeral, bypasses the turn runtime): code_agent_run broadcasts
each ACP event on a new CodeRunFeed (core DI singleton), forwarded by
main over a dedicated codeRun:events channel, buffered per toolCallId in
a module-level renderer store and rendered by CodingRunBlock. The buffer
survives session switches; nothing is persisted.
- Durable (one line per run): when the run settles (success, error, or
cancel), code_agent_run publishes a single code-run-events-batch with
the whole ordered timeline, consecutive same-role message chunks
coalesced (display-lossless — the timeline concatenates them anyway).
The turns bridge maps it to tool_progress {kind:'code-run-events'};
turn-view derives the replay timeline from it, so reloads keep history.
- Permissions stay durable per-ask (request + resolved marker): the
renderer overlay resets on session switch, so an ephemeral-only ask
would strand a blocked turn with no card to answer. Pending = requests
minus resolutions (handles concurrent asks), cleared on tool result.
The legacy code-section path (runs bus per-event) is untouched; its
per-event ctx.publish remains and is a no-op under the turns shim.
Also: repaired the two code_agent_run tests broken by the earlier cwd
existence check (they used a nonexistent /repo), and added coverage for
feed broadcast, batch coalescing, partial-batch-on-failure, bridge
durability routing, and pending-permission derivation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review feedback: drop the model-layer scheduler (per-call-site
interactive/classifier/background priorities, local-provider hostname
heuristic) in favor of app-level orchestration:
- deferBackgroundTasks flag in models.json, surfaced as a settings
toggle; auto-enabled once (UI logic) when the user connects Ollama
- ChatActivity counter marked by both chat runtimes (sessions layer and
legacy AgentRuntime.trigger)
- startWhenPossible/runWhenPossible wrappers around the headless agent
runner; all background invocations (knowledge pipeline, live notes,
background tasks, scheduled + prebuilt agents) go through them and
wait for chat-idle when the flag is set
createLanguageModel keeps only the Ollama context-window middleware;
the LM Studio capability probe now keys off the provider flavor instead
of hostname sniffing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
code_agent_run failed with a misleading "spawn <Electron> ENOENT" that
read as "Codex isn't installed". Two stacked causes:
1. cwd wasn't expanded/validated: a `~` or non-existent path was passed
straight to child_process.spawn, which reports ENOENT against the
command (Electron) rather than the missing directory. Now expandHome +
resolve + existence-check with a clear error.
2. Apple revoked the signing cert on the pinned @openai/codex@0.128.0, so
macOS Gatekeeper trashed the binary on launch and the ACP adapter died
mid-handshake. Bump the ACP stack off the revoked build:
codex-acp 0.0.44 -> 1.1.0, claude-agent-acp 0.39 -> 0.55, sdk 0.22 ->
1.1, regen engine-manifest (codex 0.142.5 / claude 0.3.198). Removed the
two now-obsolete patches (contextCompaction upstreamed; the codex
windowsHide patch targeted bin/codex.js, which we bypass via CODEX_PATH).
Bump fallout handled:
- client.ts setModel: sdk 1.x dropped unstable_setSessionModel; model is now
a config option -> setSessionConfigOption({configId:'model'}).
- engine-provisioner: codex 0.142 moved its binary (codex/ -> bin/) and rg
(path/ -> codex-path/); probe both layouts.
- pnpm override vscode-jsonrpc to 8.2.0: codex-acp 1.1 pulls jsonrpc 9.x
whose restrictive exports break langium's deep import in the renderer
(blank screen). codex-acp is the only 9.x consumer and works on 8.x
(handshake verified), so pin the workspace to 8.x.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Investigating 'apps blank while a bg task runs': measured serving latency
through a real scheduled bg-task run — 1-3ms on both loopbacks throughout,
so the server is not the bottleneck. Found and fixed the likely renderer-era
culprit plus made the failure observable:
- agent sync (apps:list, polled every 4s) rewrote each bundled agent's
task.yaml unconditionally — racing the bg runner's own patches mid-run and
spamming watcher events; now writes only when the definition changed
- AppFrame load watchdog: if the iframe hasn't loaded in 6s, show a visible
'taking too long' state with Retry instead of a silent blank pane
- main event-loop lag monitor: stalls >300ms are logged so future blank
reports can be tied to the offending work
- checkCapability choke point (D7): tools/llm/copilot access requires the
capability in the app's manifest; 403 capability_not_declared otherwise
- POST /_rowboat/tools/search|execute: Composio pass-through with the same
request shape as the builtin, typed error mapping
- POST /_rowboat/fetch: SSRF-guarded proxy (loopback/RFC1918/link-local/ULA/
*.localhost rejection re-checked per redirect hop, 5MB cap + truncated flag,
30s timeout, Host/Cookie stripped)
- POST /_rowboat/llm/generate: default-model plumbing, override validated
against the allowed set, token/concurrency caps, usage attributed
app_llm_generate/<folder>
- POST /_rowboat/copilot/run: headless run (bg-task tool profile, no shell),
app-attributed audit turn with origin context, 10-min timeout, 1-per-app cap
- registered M2 routes are POST-only (GETs are D17-exempt and must not reach them)
- bundled agents (§8): strict AppAgentDefinitionSchema, deterministic
app--<folder>--<name> slugs materialized disabled with sourceApp, manifest
removals deactivate, app delete removes owned tasks; sourceApp in summaries
- new use cases app_llm_generate / app_copilot_run in runs + analytics enums
macOS's resolver maps *.apps.localhost to ::1 only, so Electron's iframe
connects to [::1]:3210 while the server listened on 127.0.0.1 only —
connection refused, blank app, while external browsers succeeded via their
own IPv4 fallback. Listen on both loopback addresses (::1 best-effort for
IPv6-disabled machines).
- watch ~/.rowboat/apps: data.json changes push into open apps (shim
re-fetches, onData fires); installs/updates re-list the gallery live and
reload an open app once the write settles
- atomic installs (temp->rename) so an app opened mid-install never reads a
partial file
- serve app assets by direct disk read instead of net.fetch(file://) —
Chromium's network service could stall and blank every open app at once