Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 5 additions & 2 deletions docs/orchestration-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <task>` or `ultraloop: <task>` in the TUI, or pass
it as a launch prompt, for example:
Expand Down Expand Up @@ -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.
Expand Down
32 changes: 29 additions & 3 deletions packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -151,12 +170,13 @@ 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);
ctx.ui.setStatus("ultracode", sessionMode ? "Ultracode on" : undefined);
});

// BeforeAgentStartEventResult.message is appended after the real user input
Expand Down Expand Up @@ -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.",
Expand All @@ -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: <task> for one-turn opt-in.", "info");
return;
Expand Down
42 changes: 33 additions & 9 deletions packages/coding-agent/test/workflow-ultraloop-opt-in.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => {},
Expand Down Expand Up @@ -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",
},
Expand Down Expand Up @@ -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", () => {
Expand All @@ -414,18 +422,34 @@ 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(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");
});

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);
Expand Down Expand Up @@ -533,11 +557,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);
Expand All @@ -546,8 +570,8 @@ 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(startOrdinaryTurn(h)?.message?.customType).not.toBe("ultraloop-opt-in");
expect(h.statuses.get("ultracode")).toBe("Ultracode on");
expect(startOrdinaryTurn(h)?.message?.content).toContain("Ultraloop is on for the session");
});

test("ordinary turns do not inject capability tutorials or grant consent", () => {
Expand Down
Loading