diff --git a/docs/tools/subagents.md b/docs/tools/subagents.md index a5c77fe3785..3d6ddec0e43 100644 --- a/docs/tools/subagents.md +++ b/docs/tools/subagents.md @@ -221,6 +221,7 @@ Per-agent override: `agents.list[].subagents.delegationMode`. If `thread: true` and `mode` is omitted, default becomes `session`. `mode: "session"` requires `thread: true`. If thread binding is unavailable for the requester channel, use `mode: "run"` instead. + With `visible: true`, omit `mode`; visible sessions are persistent and do not support `mode: "run"`. `"delete"` archives the session immediately after announce (still keeps the transcript via rename). @@ -251,7 +252,7 @@ their latest assistant turn back to the requester; external delivery stays with the parent/requester agent. -With `visible: true`, `model`, `cwd`, and a same-agent `context: "fork"` are supported. A sandboxed target restricts `cwd` to that agent's workspace. Thread binding, `mode`, thinking overrides, light bootstrap context, and attachment staging are unavailable on this path because visible sessions are persistent dashboard sessions created through `sessions.create`. Visible spawning is also rejected when inherited tool restrictions cannot be carried into the dashboard session. See [Managed worktrees](/concepts/managed-worktrees) for checkout naming, setup, cleanup, and restore behavior. +With `visible: true`, `model`, `cwd`, and a same-agent `context: "fork"` are supported. A sandboxed target restricts `cwd` to that agent's workspace. Thread binding, `mode`, thinking overrides, `lightContext`, `attachments`, and `attachAs` are unavailable on this path because visible sessions are persistent dashboard sessions created through `sessions.create`. Visible spawning is rejected when the requester was itself spawned with an inherited tool allowlist or denylist; that restriction is fixed at spawn time and has no config override. Session listing and addressing obey `tools.sessions.visibility`; the default `tree` scope covers the current session and its own spawn subtree. See [Managed worktrees](/concepts/managed-worktrees) for checkout naming, setup, cleanup, and restore behavior. ### Task names and targeting diff --git a/src/agents/tool-description-presets.ts b/src/agents/tool-description-presets.ts index 2126ceb0e95..b467e02743f 100644 --- a/src/agents/tool-description-presets.ts +++ b/src/agents/tool-description-presets.ts @@ -72,6 +72,8 @@ export function describeSessionsSpawnTool(options?: { options?.threadAvailable ? '`mode="run"` one-shot; `mode="session"` persistent/thread-bound only on supporting requester channel.' : '`mode="run"` one-shot background.', + '`visible=true`: persistent dashboard session; subagent only; omit `mode` (no `mode="run"`), `thread`, `thinking`, `lightContext`, `attachments`, `attachAs`; inherited tool allow/denylist blocks it at spawn with no config override.', + "Session listing/addressing obeys `tools.sessions.visibility` (`tree` default: current + own spawn subtree).", "Inherits parent workspace. Native task arrives as first `[Subagent Task]`.", 'Native transcript needed: `context="fork"`; else omit/isolated.', "Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers.", @@ -81,9 +83,9 @@ export function describeSessionsSpawnTool(options?: { return baseDescription.join(" "); } return [ - ...baseDescription.slice(0, 3), + ...baseDescription.slice(0, 5), '`runtime="acp"` ids: codex, claude, gemini, opencode, or configured ACP.', - ...baseDescription.slice(3), + ...baseDescription.slice(5), ].join(" "); } diff --git a/src/agents/tools/sessions-spawn-tool.test.ts b/src/agents/tools/sessions-spawn-tool.test.ts index 8020d131dbe..86270a764b8 100644 --- a/src/agents/tools/sessions-spawn-tool.test.ts +++ b/src/agents/tools/sessions-spawn-tool.test.ts @@ -382,12 +382,26 @@ describe("sessions_spawn tool", () => { it("advertises visible sessions with terse UI guidance", () => { const tool = createSessionsSpawnTool(); const schema = tool.parameters as { - properties?: { visible?: { description?: string }; worktree?: unknown }; + properties?: Record< + string, + { anyOf?: unknown; description?: string; enum?: string[] } | undefined + >; }; expect(schema.properties?.visible?.description).toBe( - "visible: user sees session in UI. Use when user asked or talks via web/app.", + "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.", ); + expect(tool.description).toContain("`visible=true`: persistent dashboard session"); + expect(tool.description).toContain('no `mode="run"`'); + expect(tool.description).toContain("inherited tool allow/denylist"); + expect(tool.description).toContain("`tools.sessions.visibility`"); + expect(schema.properties?.runtime?.description).toContain("visible=true"); + expect(schema.properties?.mode?.description).toContain("Omit with visible=true"); + expect(schema.properties?.lightContext?.description).toContain("unavailable with visible=true"); + expect(schema.properties?.attachments?.description).toContain("unavailable with visible=true"); + expect(schema.properties?.attachAs?.description).toContain("unavailable with visible=true"); + expect(schema.properties?.mode?.enum).toEqual(["run"]); + expect(schema.properties?.mode?.anyOf).toBeUndefined(); expect(schema.properties?.worktree).toBeDefined(); }); @@ -477,7 +491,7 @@ describe("sessions_spawn tool", () => { await expect( tool.execute("hidden-worktree", { task: "inspect", worktree: true }), - ).rejects.toThrow("worktree options require visible=true"); + ).rejects.toThrow("Parameters require visible=true: worktree"); expect(hoisted.spawnSubagentDirectMock).not.toHaveBeenCalled(); }); @@ -616,32 +630,32 @@ describe("sessions_spawn tool", () => { [ "thinking", { thinking: "high" }, - "thinking unavailable with visible=true: thinking overrides are not wired to the sessions.create path", + "Parameters unavailable with visible=true: thinking: thinking overrides are not wired to the sessions.create path", ], [ "thread", { thread: true }, - "thread unavailable with visible=true: visible sessions route to the dashboard, not a channel thread", + "Parameters unavailable with visible=true: thread: visible sessions route to the dashboard, not a channel thread", ], [ "mode", { mode: "session" }, - "mode unavailable with visible=true: visible sessions are persistent dashboard sessions", + "Parameters unavailable with visible=true: mode: visible sessions are persistent dashboard sessions", ], [ "lightContext", { lightContext: true }, - "lightContext unavailable with visible=true: bootstrap staging is not wired to the sessions.create path", + "Parameters unavailable with visible=true: lightContext: bootstrap staging is not wired to the sessions.create path", ], [ "attachments", { attachments: [{ name: "note.txt", content: "hello" }] }, - "attachments unavailable with visible=true: attachment staging is not wired to the sessions.create path", + "Parameters unavailable with visible=true: attachments: attachment staging is not wired to the sessions.create path", ], [ "attachAs", { attachAs: { mountPath: "inputs" } }, - "attachAs unavailable with visible=true: attachment staging is not wired to the sessions.create path", + "Parameters unavailable with visible=true: attachAs: attachment staging is not wired to the sessions.create path", ], ] as const)("rejects visible %s overrides with a reason", async (_name, override, message) => { const tool = createSessionsSpawnTool({ agentSessionKey: "agent:main:main" }); @@ -651,6 +665,26 @@ describe("sessions_spawn tool", () => { ).rejects.toThrow(message); }); + it("reports every unsupported visible parameter in one error", async () => { + const tool = createSessionsSpawnTool({ agentSessionKey: "agent:main:main" }); + + await expect( + tool.execute("visible-unsupported-many", { + task: "inspect", + runtime: "acp", + thinking: "high", + thread: true, + mode: "run", + lightContext: true, + attachments: [{ name: "note.txt", content: "hello" }], + attachAs: { mountPath: "inputs" }, + visible: true, + }), + ).rejects.toThrow( + 'Parameters unavailable with visible=true: runtime: supports runtime="subagent" only; thinking: thinking overrides are not wired to the sessions.create path; thread: visible sessions route to the dashboard, not a channel thread; mode: visible sessions are persistent dashboard sessions; lightContext: bootstrap staging is not wired to the sessions.create path; attachments: attachment staging is not wired to the sessions.create path; attachAs: attachment staging is not wired to the sessions.create path', + ); + }); + it("denies visible sessions when tool restrictions cannot carry forward", async () => { const callGateway = vi.fn(); const tool = createSessionsSpawnTool({ @@ -667,7 +701,8 @@ describe("sessions_spawn tool", () => { expect(result.details).toMatchObject({ status: "forbidden", - error: "Visible sessions unavailable with inherited tool restrictions.", + error: + "Visible sessions unavailable with inherited tool restrictions. This session was spawned with a tool allow/denylist; visible sessions require an unrestricted session.", }); expect(callGateway).not.toHaveBeenCalled(); }); diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index 72661ac1b11..765c5906f0b 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -143,29 +143,40 @@ function createSessionsSpawnToolSchema(params: { label: Type.Optional(Type.String()), runtime: optionalStringEnum( params.acpAvailable ? SESSIONS_SPAWN_RUNTIMES : (["subagent"] as const), + { description: 'Runtime; visible=true requires "subagent".' }, ), agentId: Type.Optional(Type.String()), model: Type.Optional(Type.String()), - thinking: Type.Optional(Type.String()), + thinking: Type.Optional( + Type.String({ description: "Thinking override; unavailable with visible=true." }), + ), cwd: Type.Optional(Type.String()), ...(params.threadAvailable ? { thread: Type.Optional( Type.Boolean({ - description: 'Bind new chat thread when supported; true defaults mode="session".', + description: + 'Bind new chat thread when supported; true defaults mode="session"; unavailable with visible=true.', }), ), } : {}), - mode: optionalStringEnum(spawnModes), - cleanup: optionalStringEnum(["delete", "keep"] as const), + mode: optionalStringEnum(spawnModes, { + description: params.threadAvailable + ? '"run" one-shot; "session" persistent/thread-bound. Omit with visible=true.' + : '"run" one-shot. Omit with visible=true; visible sessions are persistent.', + }), + cleanup: optionalStringEnum(["delete", "keep"] as const, { + description: "Hidden session cleanup; visible=true always keeps the session.", + }), sandbox: optionalStringEnum(SESSIONS_SPAWN_SANDBOX_MODES), context: optionalStringEnum(SUBAGENT_SPAWN_CONTEXT_MODES, { - description: "Native: omit/isolated clean; fork only needing requester transcript.", + description: + "Native: omit/isolated clean; fork only needing requester transcript; visible fork requires same agent.", }), lightContext: Type.Optional( Type.Boolean({ - description: "Light bootstrap; subagent only.", + description: "Light bootstrap; subagent only; unavailable with visible=true.", }), ), ...(params.swarmEnabled @@ -187,15 +198,18 @@ function createSessionsSpawnToolSchema(params: { encoding: Type.Optional(optionalStringEnum(["utf8", "base64"] as const)), mimeType: Type.Optional(Type.String()), }), - { maxItems: 50 }, + { maxItems: 50, description: "Inline snapshots; unavailable with visible=true." }, ), ), attachAs: Type.Optional( - Type.Object({ - // Where the spawned agent should look for attachments. - // Kept as a hint; implementation materializes into the child workspace. - mountPath: Type.Optional(Type.String()), - }), + Type.Object( + { + // Where the spawned agent should look for attachments. + // Kept as a hint; implementation materializes into the child workspace. + mountPath: Type.Optional(Type.String()), + }, + { description: "Attachment mount hint; unavailable with visible=true." }, + ), ), ...(params.acpAvailable ? { diff --git a/src/agents/tools/sessions-spawn-visible.ts b/src/agents/tools/sessions-spawn-visible.ts index bf606a08cc9..7c317c9e3a3 100644 --- a/src/agents/tools/sessions-spawn-visible.ts +++ b/src/agents/tools/sessions-spawn-visible.ts @@ -30,7 +30,8 @@ import { reserveVisibleChildSlot } from "./sessions-spawn-visible-admission.js"; export const VISIBLE_SESSIONS_SPAWN_SCHEMA = { visible: Type.Optional( Type.Boolean({ - description: "visible: user sees session in UI. Use when user asked or talks via web/app.", + description: + "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.", }), ), worktree: Type.Optional(Type.Boolean({ description: "Visible session worktree" })), @@ -94,18 +95,30 @@ export async function maybeSpawnVisibleSession(params: { const worktreeName = readStringParam(params.raw, "worktreeName"); const worktreeBaseRef = readStringParam(params.raw, "worktreeBaseRef"); if (params.raw.visible !== true) { - if (worktree || worktreeName || worktreeBaseRef) { - throw new ToolInputError("worktree options require visible=true"); + const visibleOnlyParams = [ + ["worktree", worktree], + ["worktreeName", worktreeName], + ["worktreeBaseRef", worktreeBaseRef], + ] as const; + const providedVisibleOnlyParams = visibleOnlyParams + .filter(([, value]) => value !== undefined && value !== false) + .map(([name]) => name); + if (providedVisibleOnlyParams.length > 0) { + throw new ToolInputError( + `Parameters require visible=true: ${providedVisibleOnlyParams.join(", ")}`, + ); } return undefined; } - if (params.runtime !== "subagent") { - throw new ToolInputError('visible=true supports runtime="subagent" only'); - } const modelOverride = normalizeToolModelOverride(readStringParam(params.raw, "model")); const requestedCwd = readStringParam(params.raw, "cwd"); const spawnedCwd = requestedCwd ? resolveUserPath(requestedCwd) : undefined; const unsupported = [ + [ + "runtime", + params.runtime === "subagent" ? undefined : params.runtime, + 'supports runtime="subagent" only', + ], [ "thinking", readStringParam(params.raw, "thinking"), @@ -133,10 +146,12 @@ export async function maybeSpawnVisibleSession(params: { "attachment staging is not wired to the sessions.create path", ], ] as const; - const unsupportedEntry = unsupported.find(([, value]) => value !== undefined); - if (unsupportedEntry) { + const unsupportedEntries = unsupported.filter(([, value]) => value !== undefined); + if (unsupportedEntries.length > 0) { throw new ToolInputError( - `${unsupportedEntry[0]} unavailable with visible=true: ${unsupportedEntry[2]}`, + `Parameters unavailable with visible=true: ${unsupportedEntries + .map(([name, , reason]) => `${name}: ${reason}`) + .join("; ")}`, ); } @@ -147,7 +162,8 @@ export async function maybeSpawnVisibleSession(params: { ) { return { status: "forbidden", - error: "Visible sessions unavailable with inherited tool restrictions.", + error: + "Visible sessions unavailable with inherited tool restrictions. This session was spawned with a tool allow/denylist; visible sessions require an unrestricted session.", }; } const ownership = resolveSubagentSpawnOwnership({ diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json index e2c491f54c4..cdcc42ce7d7 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json @@ -138,13 +138,14 @@ "type": "function" }, { - "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot; `mode=\"session\"` persistent/thread-bound only on supporting requester channel. Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work. Run result returns; session output stays thread.", + "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot; `mode=\"session\"` persistent/thread-bound only on supporting requester channel. `visible=true`: persistent dashboard session; subagent only; omit `mode` (no `mode=\"run\"`), `thread`, `thinking`, `lightContext`, `attachments`, `attachAs`; inherited tool allow/denylist blocks it at spawn with no config override. Session listing/addressing obeys `tools.sessions.visibility` (`tree` default: current + own spawn subtree). Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work. Run result returns; session output stays thread.", "inputSchema": { "properties": { "agentId": { "type": "string" }, "attachAs": { + "description": "Attachment mount hint; unavailable with visible=true.", "properties": { "mountPath": { "type": "string" @@ -153,6 +154,7 @@ "type": "object" }, "attachments": { + "description": "Inline snapshots; unavailable with visible=true.", "items": { "properties": { "content": { @@ -176,11 +178,12 @@ "type": "array" }, "cleanup": { + "description": "Hidden session cleanup; visible=true always keeps the session.", "enum": ["delete", "keep"], "type": "string" }, "context": { - "description": "Native: omit/isolated clean; fork only needing requester transcript.", + "description": "Native: omit/isolated clean; fork only needing requester transcript; visible fork requires same agent.", "enum": ["isolated", "fork"], "type": "string" }, @@ -191,10 +194,11 @@ "type": "string" }, "lightContext": { - "description": "Light bootstrap; subagent only.", + "description": "Light bootstrap; subagent only; unavailable with visible=true.", "type": "boolean" }, "mode": { + "description": "\"run\" one-shot; \"session\" persistent/thread-bound. Omit with visible=true.", "enum": ["run", "session"], "type": "string" }, @@ -202,6 +206,7 @@ "type": "string" }, "runtime": { + "description": "Runtime; visible=true requires \"subagent\".", "enum": ["subagent"], "type": "string" }, @@ -217,14 +222,15 @@ "type": "string" }, "thinking": { + "description": "Thinking override; unavailable with visible=true.", "type": "string" }, "thread": { - "description": "Bind new chat thread when supported; true defaults mode=\"session\".", + "description": "Bind new chat thread when supported; true defaults mode=\"session\"; unavailable with visible=true.", "type": "boolean" }, "visible": { - "description": "visible: user sees session in UI. Use when user asked or talks via web/app.", + "description": "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.", "type": "boolean" }, "worktree": { diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json index 7b1d28123c9..959b1e26b4b 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json @@ -138,13 +138,14 @@ "type": "function" }, { - "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background. Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work while run result returns.", + "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background. `visible=true`: persistent dashboard session; subagent only; omit `mode` (no `mode=\"run\"`), `thread`, `thinking`, `lightContext`, `attachments`, `attachAs`; inherited tool allow/denylist blocks it at spawn with no config override. Session listing/addressing obeys `tools.sessions.visibility` (`tree` default: current + own spawn subtree). Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work while run result returns.", "inputSchema": { "properties": { "agentId": { "type": "string" }, "attachAs": { + "description": "Attachment mount hint; unavailable with visible=true.", "properties": { "mountPath": { "type": "string" @@ -153,6 +154,7 @@ "type": "object" }, "attachments": { + "description": "Inline snapshots; unavailable with visible=true.", "items": { "properties": { "content": { @@ -176,11 +178,12 @@ "type": "array" }, "cleanup": { + "description": "Hidden session cleanup; visible=true always keeps the session.", "enum": ["delete", "keep"], "type": "string" }, "context": { - "description": "Native: omit/isolated clean; fork only needing requester transcript.", + "description": "Native: omit/isolated clean; fork only needing requester transcript; visible fork requires same agent.", "enum": ["isolated", "fork"], "type": "string" }, @@ -191,10 +194,11 @@ "type": "string" }, "lightContext": { - "description": "Light bootstrap; subagent only.", + "description": "Light bootstrap; subagent only; unavailable with visible=true.", "type": "boolean" }, "mode": { + "description": "\"run\" one-shot. Omit with visible=true; visible sessions are persistent.", "enum": ["run"], "type": "string" }, @@ -202,6 +206,7 @@ "type": "string" }, "runtime": { + "description": "Runtime; visible=true requires \"subagent\".", "enum": ["subagent"], "type": "string" }, @@ -217,10 +222,11 @@ "type": "string" }, "thinking": { + "description": "Thinking override; unavailable with visible=true.", "type": "string" }, "visible": { - "description": "visible: user sees session in UI. Use when user asked or talks via web/app.", + "description": "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.", "type": "boolean" }, "worktree": { diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json index 5e8295c804c..5659da9c46c 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json @@ -138,13 +138,14 @@ "type": "function" }, { - "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background. Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work while run result returns.", + "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background. `visible=true`: persistent dashboard session; subagent only; omit `mode` (no `mode=\"run\"`), `thread`, `thinking`, `lightContext`, `attachments`, `attachAs`; inherited tool allow/denylist blocks it at spawn with no config override. Session listing/addressing obeys `tools.sessions.visibility` (`tree` default: current + own spawn subtree). Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work while run result returns.", "inputSchema": { "properties": { "agentId": { "type": "string" }, "attachAs": { + "description": "Attachment mount hint; unavailable with visible=true.", "properties": { "mountPath": { "type": "string" @@ -153,6 +154,7 @@ "type": "object" }, "attachments": { + "description": "Inline snapshots; unavailable with visible=true.", "items": { "properties": { "content": { @@ -176,11 +178,12 @@ "type": "array" }, "cleanup": { + "description": "Hidden session cleanup; visible=true always keeps the session.", "enum": ["delete", "keep"], "type": "string" }, "context": { - "description": "Native: omit/isolated clean; fork only needing requester transcript.", + "description": "Native: omit/isolated clean; fork only needing requester transcript; visible fork requires same agent.", "enum": ["isolated", "fork"], "type": "string" }, @@ -191,10 +194,11 @@ "type": "string" }, "lightContext": { - "description": "Light bootstrap; subagent only.", + "description": "Light bootstrap; subagent only; unavailable with visible=true.", "type": "boolean" }, "mode": { + "description": "\"run\" one-shot. Omit with visible=true; visible sessions are persistent.", "enum": ["run"], "type": "string" }, @@ -202,6 +206,7 @@ "type": "string" }, "runtime": { + "description": "Runtime; visible=true requires \"subagent\".", "enum": ["subagent"], "type": "string" }, @@ -217,10 +222,11 @@ "type": "string" }, "thinking": { + "description": "Thinking override; unavailable with visible=true.", "type": "string" }, "visible": { - "description": "visible: user sees session in UI. Use when user asked or talks via web/app.", + "description": "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.", "type": "boolean" }, "worktree": { diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md index 946cc215dc3..52cf1b87681 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md @@ -216,8 +216,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 0 }, "dynamicToolsJson": { - "chars": 55806, - "roughTokens": 13952 + "chars": 56822, + "roughTokens": 14206 }, "openClawDeveloperInstructions": { "chars": 3559, @@ -228,8 +228,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 7021 }, "totalWithDynamicToolsJson": { - "chars": 83892, - "roughTokens": 20973 + "chars": 84908, + "roughTokens": 21227 }, "userInputText": { "chars": 1442, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md index 5cb8d4862ec..8d05c4fa11e 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md @@ -216,8 +216,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 0 }, "dynamicToolsJson": { - "chars": 55533, - "roughTokens": 13884 + "chars": 56514, + "roughTokens": 14129 }, "openClawDeveloperInstructions": { "chars": 2450, @@ -228,8 +228,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 6642 }, "totalWithDynamicToolsJson": { - "chars": 82101, - "roughTokens": 20526 + "chars": 83082, + "roughTokens": 20771 }, "userInputText": { "chars": 1033, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md index 588231b77e5..89e53071874 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md @@ -217,8 +217,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 0 }, "dynamicToolsJson": { - "chars": 56823, - "roughTokens": 14206 + "chars": 57804, + "roughTokens": 14451 }, "openClawDeveloperInstructions": { "chars": 2469, @@ -229,8 +229,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 6777 }, "totalWithDynamicToolsJson": { - "chars": 83930, - "roughTokens": 20983 + "chars": 84911, + "roughTokens": 21228 }, "userInputText": { "chars": 1271,