From 5b82eb849b2b378bfa5b9f6a3c637083ed2f56f2 Mon Sep 17 00:00:00 2001 From: liuedcson <332840681+liuedcson@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:21:49 +0800 Subject: [PATCH 1/2] feat: persist Ultracode mode per session Record the latest Ultracode on/off choice in the current session journal and restore it when that session is reopened. Keep new sessions opted out and avoid global or project settings. --- CHANGELOG.md | 1 + docs/orchestration-lifecycle.md | 7 +++- .../src/features/workflow/ultraloop-opt-in.ts | 30 ++++++++++++++- .../test/workflow-ultraloop-opt-in.test.ts | 38 +++++++++++++++---- 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7917b1ea..308bedb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- Persist Ultracode on/off state in the current session journal so reopening that session restores its mode without changing global or project settings. - Stop appending Ultracode capability tutorials on every turn. Announce session-mode transitions after user input, reuse unchanged state, and restore necessary reminders when context is compacted or replaced. - Recognize `/exit` as an alias for `/quit`, including while the agent is running or compacting. - Recognize `search_files`, `find_files`, and `list_directory` when selecting default system-prompt file-exploration guidance. diff --git a/docs/orchestration-lifecycle.md b/docs/orchestration-lifecycle.md index beb36715..5491a3ac 100644 --- a/docs/orchestration-lifecycle.md +++ b/docs/orchestration-lifecycle.md @@ -24,7 +24,10 @@ capability is registered, use these commands in the interactive editor: `help`, and share one state in the workflow extension. Bare commands, `help`, and `status` leave that state unchanged. Enabling session mode projects `Ultracode on` into the status line; disabling it or crossing a session boundary -clears the projection. The UI reads this state from the extension. +clears the projection. The latest on/off choice is stored in the current session +journal, so reopening that session restores it; a new session without saved mode +state starts off. This state is not written to global or project settings. The UI +reads it from the extension. For one turn, send `ultracode: ` or `ultraloop: ` in the TUI, or pass it as a launch prompt, for example: @@ -65,7 +68,7 @@ A product run may contain multiple low-level attempts because of retries or context compaction. The opt-in and prompt budget survive those attempts and clear at `agent_settled`. The next submitted prompt replaces the prompt-specific state. Session mode survives settlement until `/ultracode off` (or -`/ultraloop off`) or a session boundary, when it resets to off. +`/ultraloop off`) or a new session with no saved mode state. The runtime checks cancellation before entering the VM and after it returns. A tool call whose signal was already aborted does not start a child agent. diff --git a/packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts b/packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts index c76f3811..b378c3b7 100644 --- a/packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts +++ b/packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts @@ -22,8 +22,15 @@ */ import type { ExtensionAPI, ExtensionCommandContext, ExtensionFactory } from "../../core/extensions/types.ts"; +import type { SessionEntry } from "../../core/session-manager.ts"; import { isWorkflowRegistrationEnabled } from "./registration-gate.ts"; +const ULTRALOOP_SESSION_MODE_ENTRY = "ultraloop-session-mode"; + +interface UltraloopSessionModeData { + enabled: boolean; +} + // "ultracode" is Claude Code's name for the same opt-in; users switching // between the two products type it interchangeably, so both spellings grant // the per-turn signal. @@ -104,6 +111,18 @@ function lastSessionMode(records: readonly ReminderRecord[]): boolean | undefine return undefined; } +function persistedSessionMode(entries: readonly SessionEntry[]): boolean | undefined { + for (let index = entries.length - 1; index >= 0; index--) { + const entry = entries[index]!; + if (entry.type !== "custom" || entry.customType !== ULTRALOOP_SESSION_MODE_ENTRY) continue; + const data = entry.data; + if (typeof data === "object" && data !== null && "enabled" in data && typeof data.enabled === "boolean") { + return data.enabled; + } + } + return undefined; +} + function sessionModeReminder(sessionMode: boolean, previous: boolean | undefined) { if (sessionMode ? previous === true : previous !== true) return undefined; const content = sessionMode ? buildUltraloopSessionReminder() : buildUltraloopSessionOffReminder(); @@ -151,9 +170,10 @@ export function createUltraloopOptInExtension(options: UltraloopOptInExtensionOp if (options.turnState) options.turnState.budgetTotal = value; }; - // Session boundary resets standing mode; each new session starts opted-out. + // Session boundaries restore only the latest mode entry from this session; + // sessions without a saved mode start opted-out. pi.on("session_start", (_event, ctx) => { - sessionMode = false; + sessionMode = persistedSessionMode(ctx.sessionManager.getEntries()) ?? false; optedInThisTurn = false; setTurnBudget(undefined); ctx.ui.setStatus("ultracode", undefined); @@ -228,6 +248,9 @@ export function createUltraloopOptInExtension(options: UltraloopOptInExtensionOp const token = args.trim().toLowerCase(); if (token === "on") { sessionMode = true; + pi.appendEntry(ULTRALOOP_SESSION_MODE_ENTRY, { + enabled: true, + } satisfies UltraloopSessionModeData); ctx.ui.setStatus("ultracode", "Ultracode on"); ctx.ui.notify( "Ultracode is on for the session. Multi-agent workflows are authorized until /ultracode off or the session ends.", @@ -237,6 +260,9 @@ export function createUltraloopOptInExtension(options: UltraloopOptInExtensionOp } if (token === "off") { sessionMode = false; + pi.appendEntry(ULTRALOOP_SESSION_MODE_ENTRY, { + enabled: false, + } satisfies UltraloopSessionModeData); ctx.ui.setStatus("ultracode", undefined); ctx.ui.notify("Ultracode session mode is off. Use ultracode: for one-turn opt-in.", "info"); return; diff --git a/packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts b/packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts index 4bb7bbb3..df0ff24c 100644 --- a/packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts +++ b/packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts @@ -46,6 +46,7 @@ function harness(): Recorded { }, appendEntry: (customType: string, data?: unknown) => { entries.push({ customType, data }); + contextEntries.push({ type: "custom", customType, data }); }, getActiveTools: () => [...activeTools], sendMessage: () => {}, @@ -77,6 +78,10 @@ function makeCtx( }, sessionManager: { getEntries: () => contextEntries, + appendCustomEntry: (customType: string, data?: unknown) => { + contextEntries.push({ type: "custom", customType, data }); + return "test-entry"; + }, buildContextEntries: () => contextEntries, getSessionId: () => "test", }, @@ -387,7 +392,10 @@ describe("createUltraloopOptInExtension", () => { const revoked = context({ messages: restored.messages } as never) as { messages: unknown[] }; expect(revoked.messages.at(-1)).toMatchObject({ details: { source: "session", sessionMode: false } }); expect(context({ messages: revoked.messages } as never)).toBeUndefined(); - expect(h.contextEntries).toEqual([]); + expect(h.contextEntries).toEqual([ + { type: "custom", customType: "ultraloop-session-mode", data: { enabled: true } }, + { type: "custom", customType: "ultraloop-session-mode", data: { enabled: false } }, + ]); }); test("/ultraloop status reports the current mode without changing it", () => { @@ -414,18 +422,32 @@ describe("createUltraloopOptInExtension", () => { toolCallId: "call-session", input: {}, } as never); - expect(h.entries).toEqual([]); + expect(h.entries).toEqual([{ customType: "ultraloop-session-mode", data: { enabled: true } }]); }); - test("session_start resets session-standing mode", () => { + test("session_start restores the latest mode from the current session journal", async () => { const h = harness(); install(h); - void h.commands.get("ultraloop")?.handler("on", makeCtx(h.notifications)); - // Simulate new session boundary. + await h.commands.get("ultraloop")?.handler("on", makeCtx(h.notifications, h.statuses, h.contextEntries)); + h.handlers.get("session_start")?.({ type: "session_start" } as never); + expect(startOrdinaryTurn(h)?.message?.content).toContain("Ultraloop is on for the session"); + + await h.commands.get("ultraloop")?.handler("off", makeCtx(h.notifications, h.statuses, h.contextEntries)); h.handlers.get("session_start")?.({ type: "session_start" } as never); expect(startOrdinaryTurn(h)?.message?.customType).not.toBe("ultraloop-opt-in"); }); + test("does not persist mode in settings or leak it to a new session", async () => { + const h = harness(); + install(h); + await h.commands.get("ultracode")?.handler("on", makeCtx(h.notifications, h.statuses, h.contextEntries)); + expect(h.entries).toEqual([{ customType: "ultraloop-session-mode", data: { enabled: true } }]); + const fresh = harness(); + install(fresh); + fresh.handlers.get("session_start")?.({ type: "session_start" } as never); + expect(startOrdinaryTurn(fresh)?.message?.customType).not.toBe("ultraloop-opt-in"); + }); + test("unknown /ultraloop arg surfaces a usage warning", () => { const h = harness(); install(h); @@ -533,11 +555,11 @@ describe("Ultracode discovery and command aliases", () => { expect(startOrdinaryTurn(h)?.message?.customType).not.toBe("ultraloop-opt-in"); }); - test("projects enabled mode and clears it on disable and session reset", async () => { + test("projects enabled mode and restores it on session reset", async () => { const h = harness(); install(h); const command = h.commands.get("ultraloop")!; - const ctx = makeCtx(h.notifications, h.statuses); + const ctx = makeCtx(h.notifications, h.statuses, h.contextEntries); await command.handler("on", ctx); expect(h.statuses.get("ultracode")).toBe("Ultracode on"); await command.handler("help", ctx); @@ -547,7 +569,7 @@ describe("Ultracode discovery and command aliases", () => { await command.handler("on", ctx); h.handlers.get("session_start")?.({ type: "session_start" } as never, ctx); expect(h.statuses.has("ultracode")).toBe(false); - expect(startOrdinaryTurn(h)?.message?.customType).not.toBe("ultraloop-opt-in"); + expect(startOrdinaryTurn(h)?.message?.content).toContain("Ultraloop is on for the session"); }); test("ordinary turns do not inject capability tutorials or grant consent", () => { From d691b4f8fe6e18bae7e420a76b7059e3d2b68881 Mon Sep 17 00:00:00 2001 From: liuedcson <332840681+liuedcson@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:53:37 +0800 Subject: [PATCH 2/2] fix: restore Ultracode status after session reopen --- .../coding-agent/src/features/workflow/ultraloop-opt-in.ts | 2 +- packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts b/packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts index b378c3b7..f7262a2b 100644 --- a/packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts +++ b/packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts @@ -176,7 +176,7 @@ export function createUltraloopOptInExtension(options: UltraloopOptInExtensionOp sessionMode = persistedSessionMode(ctx.sessionManager.getEntries()) ?? false; optedInThisTurn = false; setTurnBudget(undefined); - ctx.ui.setStatus("ultracode", undefined); + ctx.ui.setStatus("ultracode", sessionMode ? "Ultracode on" : undefined); }); // BeforeAgentStartEventResult.message is appended after the real user input diff --git a/packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts b/packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts index df0ff24c..2b0374c9 100644 --- a/packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts +++ b/packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts @@ -430,10 +430,12 @@ describe("createUltraloopOptInExtension", () => { install(h); await h.commands.get("ultraloop")?.handler("on", makeCtx(h.notifications, h.statuses, h.contextEntries)); h.handlers.get("session_start")?.({ type: "session_start" } as never); + expect(h.statuses.get("ultracode")).toBe("Ultracode on"); expect(startOrdinaryTurn(h)?.message?.content).toContain("Ultraloop is on for the session"); await h.commands.get("ultraloop")?.handler("off", makeCtx(h.notifications, h.statuses, h.contextEntries)); h.handlers.get("session_start")?.({ type: "session_start" } as never); + expect(h.statuses.has("ultracode")).toBe(false); expect(startOrdinaryTurn(h)?.message?.customType).not.toBe("ultraloop-opt-in"); }); @@ -568,7 +570,7 @@ describe("Ultracode discovery and command aliases", () => { expect(h.statuses.has("ultracode")).toBe(false); await command.handler("on", ctx); h.handlers.get("session_start")?.({ type: "session_start" } as never, ctx); - expect(h.statuses.has("ultracode")).toBe(false); + expect(h.statuses.get("ultracode")).toBe("Ultracode on"); expect(startOrdinaryTurn(h)?.message?.content).toContain("Ultraloop is on for the session"); });