diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 0af41f4af..a17e528d4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,7 +5,7 @@ ### Fixed - Fixed interactive submissions made while the TUI had no active input waiter: they now start a real prompt directly, with steer fallback if a background turn races in, instead of queueing behind a non-resumable idle transcript and appearing to do nothing. -- Fixed pressing Esc (or Alt+Up dequeue) while agent-authored messages were queued — advisor concern/blocker notes, hidden goal/plan/budget steers, IRC/extension asides — dumping their text into the user's editor. Editor restoration (`clearQueue()`), pending chips (`getQueuedMessages()`), and `popLastQueuedMessage()` now surface only genuinely user-authored queued messages (plain user turns and `attribution: "user"` custom messages like `/skill`). Advisor cards are left queued so the user-interrupt abort path still re-records them as visible advice; other agent-authored steers are dropped on clear so a user interrupt can't be silently undone by an auto-resume on leftover internal context. `queuedMessageCount` still reflects all actual queued work (advisor cards included) so `hasPendingMessages()`/RPC and the empty-submit abort gate stay accurate. +- Fixed pressing Esc (or Alt+Up dequeue) while agent-authored messages were queued — advisor concern/blocker notes, hidden goal/plan/budget steers, IRC/extension asides — dumping their text into the user's editor. Editor restoration (`clearQueue()`), pending chips (`getQueuedMessages()`), and `popLastQueuedMessage()` now surface only genuinely user-authored queued messages (plain user turns and `attribution: "user"` custom messages like `/skill`). Plain Alt+Up dequeue leaves all other queued messages in place for the continuing stream; only the Esc interrupt path keeps just advisor cards (so abort's preservation still re-records them as visible advice) and drops other internal steers, so a user interrupt can't be silently undone by an auto-resume on leftover internal context. `queuedMessageCount` still reflects all actual queued work (advisor cards included) so `hasPendingMessages()`/RPC and the empty-submit abort gate stay accurate. - Fixed `omp --continue`/`-c` sometimes resuming into a subagent transcript instead of the interactive session. Subagent (and HTML-export) `SessionManager.open()` calls run in the parent's terminal and were clobbering the per-TTY `--continue` breadcrumb with their own artifact-dir session file; these headless opens now suppress the breadcrumb. `continueRecent()` also recovers already-poisoned breadcrumbs by resolving any session file inside a parent's artifacts dir (`/.jsonl`) back up to the top-level session. ## [16.0.2] - 2026-06-16 diff --git a/packages/coding-agent/src/modes/controllers/input-controller.ts b/packages/coding-agent/src/modes/controllers/input-controller.ts index 5f7ffc171..1334a818e 100644 --- a/packages/coding-agent/src/modes/controllers/input-controller.ts +++ b/packages/coding-agent/src/modes/controllers/input-controller.ts @@ -998,7 +998,9 @@ export class InputController { restoreQueuedMessagesToEditor(options?: { abort?: boolean; currentText?: string }): number { this.ctx.locallySubmittedUserSignatures.clear(); - const { steering, followUp } = this.ctx.session.clearQueue(); + // On Esc (abort) drop non-user internal steers so the post-abort drain can't + // auto-resume; plain Alt+Up dequeue preserves them for the continuing stream. + const { steering, followUp } = this.ctx.session.clearQueue({ forInterrupt: options?.abort }); // Messages typed while compacting live in `compactionQueuedMessages`, not the // agent queue `clearQueue()` drains — but the pending bar shows the same // "Alt+Up to edit" hint for them (ui-helpers `updatePendingMessagesDisplay`). diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index 0390ed549..06ea74dd6 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -5885,21 +5885,24 @@ export class AgentSession { }); } - /** Clear queued messages and return them (text plus any attached images). */ - clearQueue(): { steering: RestoredQueuedMessage[]; followUp: RestoredQueuedMessage[] } { + /** Clear queued messages and return the user-restorable ones (text plus any attached images). + * Only user-authored messages (plain user turns, `attribution:"user"` custom like `/skill`) are + * returned for editor restore. Other queued messages stay in the agent-core queues so a continuing + * stream still delivers them — EXCEPT on `forInterrupt` (Esc+abort), where only advisor cards are + * kept (abort()'s #extractQueuedAdvisorCards preserves them as visible advice) and every other + * non-user steer (hidden goal/plan/budget, IRC/extension asides) is dropped, so abort()'s + * #drainStrandedQueuedMessages can't auto-resume the run the user just interrupted (the drain only + * fires while agent.hasQueuedMessages()). Plain Alt+Up dequeue preserves those non-user steers. */ + clearQueue(options?: { forInterrupt?: boolean }): { + steering: RestoredQueuedMessage[]; + followUp: RestoredQueuedMessage[]; + } { const steeringAll = this.agent.peekSteeringQueue(); const followUpAll = this.agent.peekFollowUpQueue(); const steering = steeringAll.filter(isUserQueuedMessage).map(toRestoredQueuedMessage); const followUp = followUpAll.filter(isUserQueuedMessage).map(toRestoredQueuedMessage); - // User-authored messages go back to the editor. Advisor concern/blocker cards - // are kept queued: a resumed stream still delivers them, and on Esc abort()'s - // #extractQueuedAdvisorCards extracts + preserves them as visible advice - // (with auto-resume suppressed). Every other non-user queued message — hidden - // goal/plan/budget steers, IRC/extension asides — is dropped, matching the - // original full clear: keeping them would both leak into the editor and let - // abort()'s #drainStrandedQueuedMessages silently auto-resume the run the user - // just interrupted (the drain only fires while agent.hasQueuedMessages()). - this.agent.replaceQueues(steeringAll.filter(isAdvisorCard), followUpAll.filter(isAdvisorCard)); + const keep: (m: AgentMessage) => boolean = options?.forInterrupt ? isAdvisorCard : m => !isUserQueuedMessage(m); + this.agent.replaceQueues(steeringAll.filter(keep), followUpAll.filter(keep)); return { steering, followUp }; } diff --git a/packages/coding-agent/test/input-controller-skill-queue.test.ts b/packages/coding-agent/test/input-controller-skill-queue.test.ts index 40719dc4d..6ea80b486 100644 --- a/packages/coding-agent/test/input-controller-skill-queue.test.ts +++ b/packages/coding-agent/test/input-controller-skill-queue.test.ts @@ -286,34 +286,42 @@ describe("AgentSession derived queued custom display", () => { expect(session.getQueuedMessages().steering).toEqual([]); expect(session.queuedMessageCount).toBe(0); - // A hidden agent-authored steer must not leak into the editor, and clearQueue - // must drop it so abort()'s stranded-message drain can't auto-resume the run - // the user just interrupted (the drain gate is agent.hasQueuedMessages()). + // Plain Alt+Up dequeue restores nothing AND preserves the hidden steer for the + // continuing stream — it isn't the user's draft. expect(session.clearQueue().steering).toEqual([]); + expect(session.agent.hasQueuedMessages()).toBe(true); + // Esc+abort drops it so abort()'s stranded-message drain can't auto-resume the + // run the user just interrupted (the drain gate is agent.hasQueuedMessages()). + expect(session.clearQueue({ forInterrupt: true }).steering).toEqual([]); expect(session.agent.hasQueuedMessages()).toBe(false); }); - it("never restores a visible agent-authored custom steer, and drops it on clearQueue", async () => { + it("never restores a visible agent-authored custom steer; preserves on dequeue, drops on interrupt", async () => { fixture = await createRealSession(); const { session } = fixture; - // An IRC aside / extension notice: visible, but agent-authored — editing it + // An IRC aside / extension/hook notice: visible, but agent-authored — editing it // makes no sense, so it must not ride the Esc/Alt+Up editor-restore path. - session.agent.steer({ - role: "custom", - customType: "irc", - content: "peer pinged you", - display: true, - attribution: "agent", - details: {}, - timestamp: Date.now(), - }); + const steer = () => + session.agent.steer({ + role: "custom", + customType: "irc", + content: "peer pinged you", + display: true, + attribution: "agent", + details: {}, + timestamp: Date.now(), + }); + steer(); expect(session.getQueuedMessages().steering).toEqual([]); // popLast leaves the agent steer untouched (not user-restorable)... expect(session.popLastQueuedMessage()).toBeUndefined(); expect(session.agent.peekSteeringQueue()).toHaveLength(1); - // ...and clearQueue restores nothing while dropping it (no auto-resume leftover). + // ...plain dequeue restores nothing but PRESERVES the extension steer (not lost)... expect(session.clearQueue().steering).toEqual([]); + expect(session.agent.peekSteeringQueue()).toHaveLength(1); + // ...and only Esc+abort drops it (no auto-resume leftover). + expect(session.clearQueue({ forInterrupt: true }).steering).toEqual([]); expect(session.agent.hasQueuedMessages()).toBe(false); }); diff --git a/packages/collab-web/index.html b/packages/collab-web/index.html index 367aa4daf..37fdefd1b 100644 --- a/packages/collab-web/index.html +++ b/packages/collab-web/index.html @@ -63,6 +63,9 @@ "isPartOf": { "@type": "WebSite", "name": "omp", "url": "https://omp.sh/" } } + + +