Sync upstream/main to d1b705a0c (8 commits) - #43
Conversation
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
…etpaseo#4736) * fix(agents): restore archived Codex workspaces without losing focus History selection must survive without cached agent records, and reading an archived Codex thread must not acquire an interactive writer. Keep workspace restore explicit, serialize native lifecycle transitions, and leave transcript lifetime with its owner. * test(app): express Codex restore specs as user journeys * test(app): finish restore fixture cleanup after teardown errors * test(app): align restore coverage and await fixture readiness Workspace restore leaves agents archived until explicit Unarchive. Keep that distinction covered for multiple agents. Compile the recovery fixture before its interaction test starts, and wait for sidebar agent hydration before asserting tab presence.
…o#4742) * fix(app): keep incomplete Markdown formatted while streaming Treat unfinished formatting at the growing assistant-message tail as provisional so delimiters and link destinations do not flash while tokens arrive. Completed messages retain ordinary Markdown parsing. * test(app): express streaming Markdown checks as user actions
…etpaseo#4180) * fix(server): probe Cursor thinking options per model Cursor ACP only reports thought_level for the current model, so the catalog was copying Haiku's Off/On onto Grok 4.6 until the next probe happened to land on Grok. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(server): drop inherited thinking options when an ACP catalog probe fails A rejected model switch was leaving the probe session's thinking list on the failed model, so Grok could still advertise Haiku's Off/On. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(server): probe per-model thinking when session/new has no thought_level Composer/Auto as the Cursor ACP default omits thought_level, so the catalog helper skipped every model switch and Grok kept an empty thinking list. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(server): discover model options without changing preferences Use the provider model catalog extension instead of probing model switches that persist user preferences. Keep provider-specific discovery in each shim and validate extension responses at the boundary. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
…etpaseo#4737) * fix(subscriptions): preserve older-host workflows and explicit timeline recovery Keep established app and SDK workflows available on older daemons without emulating independent directory filters. Restore live timeline membership without choosing the consumer's history recovery policy. Preserve daemon filesystem paths separately from legacy workspace IDs. * fix(client): keep legacy workspace replies scoped to the requested page
…paseo#4756) Keep plan proposals at their original timeline position through approval, rejection, and cancellation. Make plan cards collapsible while retaining readable history after follow-up prompts and daemon restart.
Decode clipboard image data URLs locally because native fetch rejects them. Add native-runtime and byte-integrity regression coverage, and clarify empty-clipboard guidance.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e3f7689f88
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| read(entries: AgentEntry[], reset: boolean): Workspace[] { | ||
| if (reset) this.agents.clear(); |
There was a problem hiding this comment.
Avoid resetting live workspace state during one-shot reads
When an old daemon has an active workspace observation, any later first-page fetchWorkspaces() call also reaches this shared LegacyWorkspaces instance with reset = true, clearing agents accumulated for the live observation. A subsequent removal for an agent omitted from that one-shot page then produces no workspace_update, so the subscriber can retain a stale workspace indefinitely. Keep one-shot/page state separate from the aggregate backing the active legacy subscription, or only reset that aggregate when its subscribing query is replaced.
AGENTS.md reference: AGENTS.md:L139-L140
Useful? React with 👍 / 👎.
| await this.drainSessionEvents(agentId); | ||
| // Retain ownership until shutdown succeeds. A failed close may still own a | ||
| // native writer, so publishing a resumable closed snapshot would orphan it. | ||
| await agent.session.close(); |
There was a problem hiding this comment.
Roll back archive state when provider shutdown fails
When archiveAgent() reaches this call, markRecordArchived() has already persisted archivedAt and fired the archive lifecycle callback. If the provider's close() rejects, the new early throw retains the live runtime and reports archive failure while leaving the durable record archived; after a daemon restart the agent therefore disappears into History despite the failed operation, and retrying can fire the archive callback again. Either close before committing the archive transition or restore the prior record/callback state on this failure path.
Useful? React with 👍 / 👎.
| case "session.events.set_subscription.request": | ||
| if (!this.info.features?.explicitEventSubscriptions) return null; | ||
| return { | ||
| ...query, | ||
| events: this.members(query.type, "events").filter(isLegacyEvent), | ||
| }; |
There was a problem hiding this comment.
Aggregate notification demand across legacy event observers
On a daemon that supports explicit events but not owned subscriptions, every new event observation replaces the connection-wide server slot. This request merges the surviving event names but retains only the newest observer's notifications value, so a later ordinary observation—such as directory or provider updates—can set notifications false even while the session's { notifications: true } attention feed remains active; release has the same problem because it rebuilds membership from the released query. Aggregate notification demand across all surviving event interests (true if any requests it) along with the event names.
AGENTS.md reference: AGENTS.md:L139-L140
Useful? React with 👍 / 👎.
What was merged
upstream/main@d1b705a0c— 8 commits past merge-basef22a37e6:d1b705a0cfix(app): restore Android clipboard image pasting (fix(app): restore Android clipboard image pasting getpaseo/paseo#4758)0d7760e68fix(agents): keep rejected plans collapsed in conversation order (Keep rejected plans collapsed in conversation order getpaseo/paseo#4756)03a54a171Keep older daemons usable and let clients choose timeline recovery (Keep older daemons usable and let clients choose timeline recovery getpaseo/paseo#4737)d0e1edca7fix(server): show the right thinking options for each Cursor model (fix(server): show the right thinking options for each Cursor model getpaseo/paseo#4180)2d7733cd6fix(app): keep incomplete Markdown formatted while streaming (fix(app): keep incomplete Markdown formatted while streaming getpaseo/paseo#4742)a7ce602c8fix(agents): restore archived Codex workspaces without losing focus (fix(agents): restore archived Codex workspaces without losing focus getpaseo/paseo#4736)fa93c4290docs(changelog): update generated release notes (Keep the macOS-only clarification in generated changelogs getpaseo/paseo#4728)192c51783docs(changelog): clarify macOS version requirement (Clarify the macOS-only desktop requirement in the changelog getpaseo/paseo#4726)99 files changed. The
v0.8.0tag was already merged onmain, so this sync takes upstream tip rather than a release tag.Conflict-resolution decisions
Seven conflicts; resolved by ownership (upstream for infrastructure/correctness, fork for deliberate product differences):
directory-sync/internal/agent-store.ts— upstream moved transcript cleanup (timeline cursor, stream head/tail, authoritative history) fromremoveFromDirectory()intoremove(). Took upstream's split; the fork's directory cleanup (details, queued messages, init state, permissions) stays inremoveFromDirectory().host-runtime.ts/host-runtime.test.ts— upstream deleted therequireAppHostcapability gate entirely as part of Keep older daemons usable and let clients choose timeline recovery getpaseo/paseo#4737 (old daemons now connect in legacy mode instead of being rejected). Took upstream's removal, including the fork's Forkeo error text on that gate.workspace-layout-store.ts— the substantive merge. Upstream removedpendingAgentIdsByWorkspace/resolvePendingAgentand made pinned tabs unconditional; the fork keepscontinuationPendingIdsByWorkspace(handoff-successor tab protection). Resolution: dropped the obsolete upstream pending-ids mechanism, kept the fork's continuation-pending set, passed it as pinned ids into upstream's newreconcileWorkspaceTabscontract, and re-addedknownAgentIdsas an optional field onWorkspaceTabSnapshot/WorkspaceAgentVisibilityso continuation protection can still tell an archived/known successor from a deleted one. Optional keeps upstream's literal test fixtures type-valid.agent-manager.ts— combined fork'sregisterOnce/account-aware resume with upstream'srunLifecycleMutationlane; kept fork's lease release incloseAgentRuntime; upstream's earlysession.close()ownership-retention adopted. See "Post-merge fix" below for the race this initially introduced.codex-app-server-agent.ts— upstream's resume/session changes merged with the fork's account-aware Codex provider.Post-merge fix (race found by tests, not by conflicts)
agent-manager.test.ts→ "a stored-only archive closes a shared resume before a later protected load returns" failed deterministically after the merge. Root cause: the fork'sawait this.cancelContinuation(agentId)sat beforerunLifecycleMutation(...)inarchiveSnapshot/archiveAgent. That one-microtask gap deferredlifecycleMutationTails.setpast the point where a concurrentensureUnarchivedAgentLoaded→waitForAgentClosereads the tail — so the protected load waited only on the in-flight resume, re-read storage before the archive'sarchivedAtwrite landed, and resolved instead of rejecting.Fix: moved
cancelContinuationinside the lane mutation inarchiveAgent,archiveSnapshot, andcancelAgentRun(the latter uses the foreground lane; same hazard).cancelExistingnever re-enters either lane (verified: it touches the continuation store,agentStorage.get,getAgentonly), so this is deadlock-free and restores upstream's synchronous lane entry while keeping cancel-before-archive ordering. 196/196 inagent-manager.test.tsnow pass.Fork behaviors kept / deliberately diverged
continuationPendingIdsByWorkspace,cancelContinuationin archive/cancel paths,registerOnce, account-awareresumeWithAccount, handoff labels/successor checks inagent-loading.ts) — verified viaagent-handoff/runtime.test.tsand the continuation service tests.accounts,accountLeases,accountClients, account-awarerequireClient).respondToPermission/AndWait— present upstream too; intact.projectGroupscapability-gate wording kept perdocs/fork.md(deliberate locale-churn avoidance).Decisions
requireAppHostgate, so a host that predates owned subscriptions connects in legacy mode instead of failing with "Update the host to use this version of Forkeo." That gate was never a fork behavior: upstream added it in Keep plugin subscriptions independent and connections quiet getpaseo/paseo#4596 on 2026-09-11, the rename PR rebranded the string, and upstream deleted it a day later. No fork release tag (v1.3.1 or earlier) contains it. Re-adding it would block a new phone build from reaching a Mac still on v1.3.1 and would conflict on every sync ofhost-runtime.ts. Per-feature "Update the host to…" strings still nudge users when they hit a gated feature. The separateprojectGroupsgate wording is unaffected.Validation
npm run build:server— clean (protocol/relay/highlight/plugin/server/cli declarations rebuilt before typecheck).npm run typecheck— clean across all workspaces.npm run lint— 0 warnings, 0 errors (4347 files).npm run format— clean.npx vitest runon 12 touched files (agent-directory-sync, workspace-subagents-integration, assistant-markdown-parser, local-file-attachment-store, workspace-recovery model, provider-account tests, agent-handoff runtime, others) — 382 passed.agent-manager.test.ts(196 passed, incl. the previously failing archive/resume race test), plusagent-loading.test.ts,agent-continuation/service.test.ts,agent-continuation/daemon.test.ts,workspace-archive-service.test.ts,snapshot-mutation-ownership.test.ts,lifecycle-command.test.ts,handoff-agent.test.ts— 88 passed.