Cards for apps already installed from the catalog show a green
Installed badge (matched via the install record's registry name) that
opens the app, instead of a second Install button.
Two races around the post-install prompt:
- AppsView kept a stale selectedFolder after the open app was
uninstalled; on reinstall the 4s apps:list poll matched it again and
instantly swapped in the app frame, unmounting the catalog and the
enable-agents dialog mid-choice. Clear the selection when its app
disappears from the list.
- Bundled agents materialized on the NEXT apps:list poll, not at
install — so the dialog's bg-task:get/patch hit a task that didn't
exist yet if the user acted within ~4s. Install handlers now call
syncAppAgents synchronously.
The enable dialog now lists the user's available models (models:list),
preselecting the host-pinned default from materialization. Turning the
agents on patches the chosen model/provider onto the task along with
active — so installers can route a bundled agent to a specific model
at the moment they opt in, without digging into bg-tasks.
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).
Bundled agents install disabled (§8.3), but nothing told the installer
they exist — the app opened empty with the refresher buried in
bg-tasks. After an install that materialized agents, offer to turn
them on: 'Turn on & run now' activates each task and fires a first run
so the app opens with data; 'Not now' keeps them off.
The detail panel's Publish update button opened the same dialog as
first publish, which hardcoded apps:publish — so updating an already
published app always failed with name_taken from the registry check.
The dialog now takes a published flag: it runs apps:publishUpdate with
a version-bump picker (patch/minor/major), shows a simple progress
state (the update path emits no step events), and links the new
release on success.
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.
langium imports vscode-jsonrpc/lib/common/events.js without declaring the
dependency, which fails to resolve under pnpm's strict layout and broke the
renderer's production (vite) build during forge packaging.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>