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
- toolbar (local apps): 'Publish' button; turns into a green 'Published'
badge-check once published (click opens the detail panel)
- detail Source section: clickable repo + release links for published apps
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)
The mobile-channels feature added baileys, whose install-time scripts
require build approval. A placeholder value ("set this to true or
false") was left in the allowBuilds block of pnpm-workspace.yaml. pnpm
11 treats an unresolved build decision as a hard error, causing
pnpm install to exit non-zero and abort Electron Forge's generateAssets
hook during npm run package.
Set baileys: true to match the onlyBuiltDependencies entry.
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>
A mute button on both call surfaces (full-screen call and floating
popout) that pauses everything going to the assistant while keeping the
call alive — for stepping away to talk to someone in the room without
ending and restarting the call.
- Mic audio stops reaching Deepgram (user mute OR'd into the existing
thinking/speaking setPaused; KeepAlives keep the socket warm so
unmute is instant)
- Camera/screen frames stop being sampled (new setCapturePaused in
useVideoMode); collectFrames() returns nothing while muted, so typed
messages during a mute carry no frames either
- Devices stay acquired for instant resume; mute resets at call
start/end; assistant output is unaffected (Stop handles that)
- Honest UI: "Muted" status chip replaces the green "Listening" pulse,
muted badge on the user tile, pill's share badge flips to "Sharing
paused"; new toggle-mic popout action + micMuted in popout state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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