diff --git a/.agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.i18n.yaml new file mode 100644 index 0000000000..be8679e067 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.md +2026-09-20-unambiguous-tool-arguments.md: b8092803f226fe08bcae640fae76c50efebabfd1 +2026-09-20-unambiguous-tool-arguments.zh.md: 3c355d8b66b024f790028a96df055e802197247e diff --git a/.agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.md b/.agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.md new file mode 100644 index 0000000000..b8092803f2 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.md @@ -0,0 +1,25 @@ +# Agent Note: Unambiguous tool argument compatibility + +Status: implemented + +English | [中文](2026-09-20-unambiguous-tool-arguments.zh.md) + +## Problem + +Models can populate optional fields that do not affect the requested operation. Rejecting these calls adds retries without protecting any state. A general OpenAI strict-schema adapter requires separate schema projection and argument restoration; these local compatibility rules do not implement that adapter. + +## Decision + +`grep` treats an exact empty `include` as omitted. Whitespace-only, negated, and comma-separated filters remain invalid. `update_goal` uses replacement fields only for `edit` and a blocker reason only for `blocked`; other actions ignore those known fields. Declared types, consumed values, authority, and exact revisions remain validated. Ignored fields never reach another goal operation or change unrelated state, and call summaries select only values used by the action. + +This partially supersedes the unused-field rejection in the [goal-tool decision](../feature/2026-07-19-model-facing-goal-tools.md). Its authority, lifecycle, scheduling, and persistence decisions remain active. The rules apply to every provider and stay inside the owning tool rather than weakening its underlying service. + +## Alternatives considered + +- **Complete strict-schema adaptation first** — deferred because it is a separate provider-wide change; these two cases have deterministic meanings and direct regression evidence. +- **Normalize all empty, null, or zero values** — rejected because such values can be meaningful input, including empty file contents and numeric limits. +- **Reject every populated inactive field** — rejected because the explicit action determines the goal operation, and unused fields cannot alter it. + +## Consequences + +The tools accept these calls without extra mutations. Unit tests cover ignored fields for every goal action, consumed-field and authority failures, stale revisions, filter validation, and real search results. A headless recorded-session scenario verifies both file extensions are searched and completion preserves the original objective and round cap. These rules give up detecting conflicting intent expressed only in inactive goal fields; they do not weaken authorization or establish a generic coercion policy. diff --git a/.agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.zh.md b/.agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.zh.md new file mode 100644 index 0000000000..3c355d8b66 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-20-unambiguous-tool-arguments.zh.md @@ -0,0 +1,25 @@ +# Agent Note: 无歧义的工具参数兼容 + +Status: implemented + +[English](2026-09-20-unambiguous-tool-arguments.md) | 中文 + +## 问题 + +模型可能填写不影响所请求操作的可选字段。拒绝这些调用只会增加重试,并不保护任何状态。通用 OpenAI 严格 schema 适配需要单独实现 schema 投影和参数还原;这些局部兼容规则并未实现该适配器。 + +## 决策 + +`grep` 将值恰好为空字符串的 `include` 视为省略。仅含空白、否定形式和逗号分隔的过滤器仍然无效。`update_goal` 仅在 `edit` 时使用替换字段,仅在 `blocked` 时使用阻塞原因;其他 action 忽略这些已知字段。声明的类型、实际使用的值、权限和精确修订号仍会校验。被忽略的字段不会传入另一种目标操作,也不会改变无关状态;调用摘要仅选择当前 action 使用的值。 + +本决策部分取代[目标工具决策](../feature/2026-07-19-model-facing-goal-tools.zh.md)中对无关字段的拒绝规则。其权限、生命周期、调度和持久化决策继续生效。这些规则适用于所有提供方,并保留在所属工具内,不削弱底层服务的校验。 + +## 考虑过的替代方案 + +- **先完成严格 schema 适配**——暂缓,因为这是单独的提供方级改造;这两种情况具有确定含义和直接的回归证据。 +- **统一规范化所有空值、null 或零值**——不予采纳,因为这些值可能是有意义的输入,包括空文件内容和数值限制。 +- **拒绝所有已填写的非当前 action 字段**——不予采纳,因为显式 action 决定目标操作,而未使用的字段无法改变该操作。 + +## 后果 + +工具接受这些调用,且不会产生额外变更。单元测试覆盖每种目标 action 对无关字段的忽略、有效字段与权限失败、陈旧修订号、过滤器校验以及真实搜索结果。headless 会话回放验证两种扩展名的文件都参与搜索,并且完成操作保留原始目标和轮数上限。这些规则放弃检测仅由非当前 action 的目标字段表达的冲突意图;它们不削弱授权,也不建立通用类型转换策略。 diff --git a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml index 3a30f6624d..1f375c2431 100644 --- a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md -2026-07-19-model-facing-goal-tools.md: 2c05c8aa0ee2caecb0264fa87984085b3df5d785 -2026-07-19-model-facing-goal-tools.zh.md: f16055e52e4a3d6a4db423699a36929f6f8c4cd2 +2026-07-19-model-facing-goal-tools.md: 0dbc3f6f5f5668005240a52300eae0eea01c9b8b +2026-07-19-model-facing-goal-tools.zh.md: a4328a026510e1be17ed7a37663f1aceac160003 diff --git a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md index 2c05c8aa0e..0dbc3f6f5f 100644 --- a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md +++ b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md @@ -16,7 +16,7 @@ The tool API also needs to preserve the separation between durable state and liv ### Tools and model contract -`get_goal()` returns the current goal or `null`. A non-null result contains the compare-and-set id and revision, objective, durable phase, admitted and maximum goal rounds, any blocker reason, plus the process-local activation observation. `create_goal(objective, max_goal_rounds?)` creates one long-running same-session objective. `update_goal(goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?)` supports `edit`, `pause`, `resume`, `complete`, and `blocked`; replacement fields are valid only for `edit`, while a non-empty `blocked_reason` is required only for `blocked` and persists under the stable `model-reported` code. A durable paused goal rejects `resume` with `GOAL_TOOL_RESUME_PAUSED`; the user-facing command or Web control owns that transition. The executor treats exact empty-string optional fields and a zero `max_goal_rounds` as strict-schema fillers: they count as omitted, an edit still requires at least one meaningful replacement, and all non-filler values retain the action restrictions. +`get_goal()` returns the current goal or `null`. A non-null result contains the compare-and-set id and revision, objective, durable phase, admitted and maximum goal rounds, any blocker reason, plus the process-local activation observation. `create_goal(objective, max_goal_rounds?)` creates one long-running same-session objective. `update_goal(goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?)` supports `edit`, `pause`, `resume`, `complete`, and `blocked`; replacement fields are used only for `edit`, while a non-empty `blocked_reason` is required only for `blocked` and persists under the stable `model-reported` code. A durable paused goal rejects `resume` with `GOAL_TOOL_RESUME_PAUSED`; the user-facing command or Web control owns that transition. For `edit`, exact empty-string objectives and a zero `max_goal_rounds` count as omitted, and at least one meaningful replacement is required. The rejection of populated fields unused by an action is superseded by [unambiguous tool argument compatibility](../bug-fix/2026-09-20-unambiguous-tool-arguments.md). The prompt tells the model that it may infer goal intent from a direct human request in any wording or language, but should not convert routine single-turn work into a goal. It must read the current goal before updating and copy the exact id and revision. On a restored or forked active-but-disarmed goal, a semantic human request to continue is grounds for `resume`. The prompt does not announce the durable paused boundary; execution rejects that attempt with `GOAL_TOOL_RESUME_PAUSED`, and the user-facing resume path owns the transition. Completion is reserved for an achieved objective, and difficulty or uncertainty alone is not a blocker; a block report must name the concrete condition. diff --git a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md index f16055e52e..a4328a0265 100644 --- a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md +++ b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md @@ -16,7 +16,7 @@ Status: implemented ### 工具与模型约定 -`get_goal()` 返回当前目标或 `null`。非空结果包含用于比较并交换的 id 与修订号、目标描述、持久阶段、已接纳和最大 Goal Round 数、可能存在的阻塞原因,以及进程本地激活态观察。`create_goal(objective, max_goal_rounds?)` 创建一个长时间运行的同会话目标。`update_goal(goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?)` 支持 `edit`、`pause`、`resume`、`complete` 和 `blocked`;替换字段仅对 `edit` 有效,非空的 `blocked_reason` 仅在 `blocked` 时必填,并以稳定代码 `model-reported` 持久化。持久 paused goal 会以 `GOAL_TOOL_RESUME_PAUSED` 拒绝 `resume`;面向用户的命令或 Web 控件拥有该转换。执行器把值恰好为空字符串的可选字段和值为 0 的 `max_goal_rounds` 视为严格 schema 占位值:这些值等同于省略;编辑时仍必须提供至少一个有实际意义的替换字段;所有非占位值仍受对应操作的限制。 +`get_goal()` 返回当前目标或 `null`。非空结果包含用于比较并交换的 id 与修订号、目标描述、持久阶段、已接纳和最大 Goal Round 数、可能存在的阻塞原因,以及进程本地激活态观察。`create_goal(objective, max_goal_rounds?)` 创建一个长时间运行的同会话目标。`update_goal(goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?)` 支持 `edit`、`pause`、`resume`、`complete` 和 `blocked`;替换字段仅在 `edit` 时使用,非空的 `blocked_reason` 仅在 `blocked` 时必填,并以稳定代码 `model-reported` 持久化。持久 paused goal 会以 `GOAL_TOOL_RESUME_PAUSED` 拒绝 `resume`;面向用户的命令或 Web 控件拥有该转换。对于 `edit`,值恰好为空字符串的目标描述和值为 0 的 `max_goal_rounds` 视为省略,且至少需要一个有实际意义的替换字段。对已填写但当前 action 不使用的字段的拒绝规则,已由[无歧义的工具参数兼容](../bug-fix/2026-09-20-unambiguous-tool-arguments.zh.md)取代。 提示词告诉模型:它可以从任何措辞或语言的直接人类请求中推断目标意图,但不应把常规单轮工作转换为目标。更新前必须读取当前目标,并复制准确的 id 和修订号。对于恢复或 fork 后处于活跃但未激活状态的目标,人类在语义上要求继续即可成为执行 `resume` 的依据。提示词不会静态声明持久 paused 的边界;执行时以 `GOAL_TOOL_RESUME_PAUSED` 拒绝该尝试,面向用户的恢复路径拥有该转换。只有目标已经实现时才能标记完成,困难或不确定性本身不构成阻塞;阻塞报告必须说明具体条件。 diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index 04a2230cee..156669a17c 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/tool-catalog.md -tool-catalog.md: 2f5746fc63e73b1caf2692a05ff942c13399e6bc -tool-catalog.zh.md: 6fb80b689ca569834e8f585c765547d23cec50df +tool-catalog.md: a5c3611da58b98da726323ee37f1a1d1e4cba6f0 +tool-catalog.zh.md: 0ce8e03b2c938ffb6e53b28ee028541ab81d9bf4 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 2f5746fc63..a5c3611da5 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -1342,15 +1342,15 @@ Update the exact current goal revision. edit, pause, and resume require a direct }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index 6fb80b689c..0ce8e03b2c 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -1348,15 +1348,15 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/packages/fs/tool-fs-search/README.i18n.yaml b/packages/fs/tool-fs-search/README.i18n.yaml index 3b9d2bb395..d98827d37c 100644 --- a/packages/fs/tool-fs-search/README.i18n.yaml +++ b/packages/fs/tool-fs-search/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/fs/tool-fs-search/README.md -README.md: 83b1d89b3276f3f6fabbe501017af2df8ec1868a -README.zh.md: fc0eeed77521c33262d057afd90937ce03280e2d +README.md: 650038d787a3a2272dd2615a8ad58f95466eae5f +README.zh.md: 115465604845e8249c1b0f1dd9647e82737af094 diff --git a/packages/fs/tool-fs-search/README.md b/packages/fs/tool-fs-search/README.md index 83b1d89b32..650038d787 100644 --- a/packages/fs/tool-fs-search/README.md +++ b/packages/fs/tool-fs-search/README.md @@ -46,7 +46,7 @@ A subprocess backend, then the tools; the spill backend is optional and makes ca | Tool | Arguments | Behavior | |---|---|---| | `glob` | `pattern`, `path?` | Finds files whose paths match a glob pattern, including hidden and ignored files but excluding VCS metadata; a pattern with no `/` matches basenames at any depth, so `*` matches the whole tree; complete results stay modification-time ordered | -| `grep` | `pattern`, `path?`, `include?` | Searches file contents with a ripgrep regex and returns matches grouped by file as `Line N: `; `include` is one positive glob filter, with comma-separated lists and negated values rejected up front | +| `grep` | `pattern`, `path?`, `include?` | Searches file contents with a ripgrep regex and returns matches grouped by file as `Line N: `; omitted or exact empty `include` applies no file filter; otherwise `include` is one positive glob, with whitespace-only values, comma-separated lists, and negated values rejected up front | Routine budgets stay out of the model-facing schema: a model that needs surrounding context reads the matched file with `read`, and one that needs later results follows the returned spill locator's retrieval hint. diff --git a/packages/fs/tool-fs-search/README.zh.md b/packages/fs/tool-fs-search/README.zh.md index fc0eeed775..1154656048 100644 --- a/packages/fs/tool-fs-search/README.zh.md +++ b/packages/fs/tool-fs-search/README.zh.md @@ -46,7 +46,7 @@ kind: "package-reference" | 工具 | 参数 | 行为 | |---|---|---| | `glob` | `pattern`、`path?` | 查找路径匹配 glob 模式的文件,包含隐藏与忽略文件但排除 VCS 元数据;不含 `/` 的模式匹配任意深度的基名,因此 `*` 匹配整棵树;完整结果保持按修改时间排序 | -| `grep` | `pattern`、`path?`、`include?` | 用 ripgrep 正则搜索文件内容,并按文件分组返回 `Line N: ` 匹配;`include` 是一个正向 glob 过滤器,逗号分隔列表与否定值会被前置拒绝 | +| `grep` | `pattern`、`path?`、`include?` | 用 ripgrep 正则搜索文件内容,并按文件分组返回 `Line N: ` 匹配;省略 `include` 或传入空字符串时不限制文件类型;否则 `include` 是一个正向 glob 过滤器,仅含空白的值、逗号分隔列表与否定值会被前置拒绝 | 常规预算不进入面向模型的 schema:需要周边上下文的模型用 `read` 读取匹配文件,需要后续结果的模型遵循返回的 spill locator 检索提示。 diff --git a/packages/fs/tool-fs-search/src/grep.ts b/packages/fs/tool-fs-search/src/grep.ts index eab158f951..0bb032ffb1 100644 --- a/packages/fs/tool-fs-search/src/grep.ts +++ b/packages/fs/tool-fs-search/src/grep.ts @@ -80,20 +80,22 @@ function validateInclude(include: string): void { /** * Validate value constraints the schema DSL can't express: a non-EMPTY * `pattern` (whitespace is a legitimate regex), a non-blank `path` when given, - * and a single positive `include` glob ({@link GrepInput}). Throws a plain - * `Error` (an ordinary tool argument error) otherwise. + * and a single positive `include` glob ({@link GrepInput}). An exact empty + * `include` is treated as omitted; whitespace-only values remain invalid. + * Throws a plain `Error` (an ordinary tool argument error) otherwise. * * @param args - the schema-validated `grep` arguments. - * @returns the accepted input, unchanged. + * @returns the accepted input with an empty `include` omitted. */ export function parseGrepArgs(args: { pattern: string; path?: string; include?: string }): GrepInput { if (args.pattern.length === 0) throw new Error('pattern must be a non-empty string') if (args.path !== undefined && args.path.trim().length === 0) throw new Error('path must be a non-empty string when given') - if (args.include !== undefined) validateInclude(args.include) + const include = args.include === '' ? undefined : args.include + if (include !== undefined) validateInclude(include) return { pattern: args.pattern, ...args.path !== undefined ? { path: args.path } : {}, - ...args.include !== undefined ? { include: args.include } : {}, + ...include !== undefined ? { include } : {}, } } diff --git a/packages/fs/tool-fs-search/tests/integration.spec.ts b/packages/fs/tool-fs-search/tests/integration.spec.ts index 77ed0c8c1a..2715f87f1d 100644 --- a/packages/fs/tool-fs-search/tests/integration.spec.ts +++ b/packages/fs/tool-fs-search/tests/integration.spec.ts @@ -68,6 +68,7 @@ describe('search tools over the real subprocess service + the packaged rg', () = }) afterEach(async () => { + await ctx.fiber.dispose() await rm(dir, { recursive: true, force: true }) }) @@ -106,8 +107,8 @@ describe('search tools over the real subprocess service + the packaged rg', () = }) describe('grep', () => { - it('greps a directory tree with grouped, line-numbered output', async () => { - const result = await call('grep', { pattern: 'alpha' }, agent()) + it.each([undefined, ''])('greps without a file filter when include is %j', async (include) => { + const result = await call('grep', { pattern: 'alpha', ...include !== undefined ? { include } : {} }, agent()) expect(result.isError).toBe(false) const output = text(result) expect(output).toContain('Found 3 matches') diff --git a/packages/fs/tool-fs-search/tests/tools.spec.ts b/packages/fs/tool-fs-search/tests/tools.spec.ts index fd64cf16d3..1c7769b049 100644 --- a/packages/fs/tool-fs-search/tests/tools.spec.ts +++ b/packages/fs/tool-fs-search/tests/tools.spec.ts @@ -1064,6 +1064,39 @@ describe('grep results', () => { expect(text(result)).toBe('Found 1 of 2 matches\n\na.ts\nLine 1: one\n\n(The complete result could not be saved; narrow pattern, path, or include to see more.)') }) + it('treats an empty include as omitted without passing a glob filter', async () => { + const { ctx, subprocess } = await setup() + subprocess.handler = () => runResult(matchLine('notes.md', 1, 'alpha')) + const omitted = await call(ctx, 'grep', { pattern: 'alpha', path: '.' }) + const empty = await call(ctx, 'grep', { pattern: 'alpha', path: '.', include: '' }) + expect(empty.isError).toBe(false) + expect(text(empty)).toBe('Found 1 match\n\nnotes.md\nLine 1: alpha') + expect(empty.content).toEqual(omitted.content) + expect(subprocess.spawns.map(spec => spec.argv)).toEqual([ + [rgPath, '--no-config', '--json', '--regexp=alpha', '--', '.'], + [rgPath, '--no-config', '--json', '--regexp=alpha', '--', '.'], + ]) + }) + + it.each([ + [{ include: '' }, 'pattern'], + [{ pattern: '', include: '' }, 'pattern must be a non-empty string'], + [{ pattern: 1, include: '' }, 'pattern'], + [{ pattern: 'x', path: '', include: '' }, 'path must be a non-empty string'], + [{ pattern: 'x', path: ' ', include: '' }, 'path must be a non-empty string'], + [{ pattern: 'x', path: 1, include: '' }, 'path'], + [{ pattern: 'x', include: null }, 'include'], + [{ pattern: 'x', include: false }, 'include'], + [{ pattern: 'x', include: 1 }, 'include'], + [{ pattern: 'x', include: [] }, 'include'], + ])('rejects malformed grep arguments %j before spawning', async (args, message) => { + const { ctx, subprocess } = await setup() + const result = await call(ctx, 'grep', args) + expect(result.isError).toBe(true) + expect(text(result)).toContain(message) + expect(subprocess.spawns).toHaveLength(0) + }) + it('validates arguments (empty pattern, blank path, bad include)', async () => { const { ctx } = await setup() expect(text(await call(ctx, 'grep', { pattern: '' }))).toContain('pattern must be a non-empty string') diff --git a/packages/goal/tool-goal/README.i18n.yaml b/packages/goal/tool-goal/README.i18n.yaml index abca3cb18d..2837ebac46 100644 --- a/packages/goal/tool-goal/README.i18n.yaml +++ b/packages/goal/tool-goal/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/goal/tool-goal/README.md -README.md: c40b9eb302ace23c2448114433eda364083a5293 -README.zh.md: b29848f33bf3692648b4810786cf28bb68cb202a +README.md: 0b413ca7840318b7b028a9e24cc6fd699ffd97f4 +README.zh.md: 498bd533165bd72c540e9881ee1404e5842149bb diff --git a/packages/goal/tool-goal/README.md b/packages/goal/tool-goal/README.md index c40b9eb302..0b413ca784 100644 --- a/packages/goal/tool-goal/README.md +++ b/packages/goal/tool-goal/README.md @@ -37,7 +37,7 @@ All three tools return the same compact JSON — `{ goal: null }` when no goal i | `create_goal(objective, max_goal_rounds?)` | Creates one goal from a direct top-level human turn | | `update_goal(goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?)` | `edit`, `pause`, `resume`, `complete`, or `blocked` on the exact goal revision | -Call `get_goal` before `update_goal` and copy the exact `goal_id` and `revision`; all calls are exclusive, so a model-ordered batch observes earlier mutations and their new revisions. Replacements belong only to `edit`; `blocked_reason` is required only for `blocked` and is persisted with the stable code `model-reported`. Strict-schema empty-string and zero fillers count as omitted, while meaningful values remain limited to their action. +Call `get_goal` before `update_goal` and copy the exact `goal_id` and `revision`; all calls are exclusive, so a model-ordered batch observes earlier mutations and their new revisions. `objective` and `max_goal_rounds` affect only `edit` and are ignored for all other actions. `blocked_reason` is required and non-empty for `blocked`, persisted with the stable code `model-reported`, and ignored for all other actions. Ignored fields cannot change the objective, round cap, or blocker reason. Declared types, authority, and exact-revision checks still apply. For `edit`, exact empty-string objectives and zero round caps count as omitted, and at least one meaningful replacement is required. ### Configure it diff --git a/packages/goal/tool-goal/README.zh.md b/packages/goal/tool-goal/README.zh.md index b29848f33b..498bd53316 100644 --- a/packages/goal/tool-goal/README.zh.md +++ b/packages/goal/tool-goal/README.zh.md @@ -37,7 +37,7 @@ kind: "package-reference" | `create_goal(objective, max_goal_rounds?)` | 根据人类直接发起的顶层轮次创建一个 goal | | `update_goal(goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?)` | 对精确 goal revision 执行 `edit`、`pause`、`resume`、`complete` 或 `blocked` | -在 `update_goal` 之前调用 `get_goal`,并复制精确的 `goal_id` 与 `revision`;所有调用都互斥,因此模型排序的批次能观察到更早变更及其新 revision。替换值只属于 `edit`;`blocked_reason` 只有在 `blocked` 时才必填,并以稳定代码 `model-reported` 持久化。严格 schema 下的空字符串和零填充值视为省略,而有意义的值仍限定到各自 action。 +在 `update_goal` 之前调用 `get_goal`,并复制精确的 `goal_id` 与 `revision`;所有调用都互斥,因此模型排序的批次能观察到更早变更及其新 revision。`objective` 和 `max_goal_rounds` 仅影响 `edit`,所有其他 action 均忽略它们。`blocked_reason` 在 `blocked` 时必填且非空,并以稳定代码 `model-reported` 持久化,所有其他 action 均忽略它。被忽略的字段不能改变目标、轮数上限或阻塞原因。声明的类型、权限和精确 revision 校验仍然生效。对于 `edit`,目标为空字符串、轮数上限为零时视为省略,且至少需要一个有效替换值。 ### 配置 diff --git a/packages/goal/tool-goal/src/index.ts b/packages/goal/tool-goal/src/index.ts index c706741c5a..5f0dbe58b8 100644 --- a/packages/goal/tool-goal/src/index.ts +++ b/packages/goal/tool-goal/src/index.ts @@ -252,37 +252,27 @@ export function apply(ctx: Context, config: Config): void { enum: UPDATE_ACTIONS, description: 'edit | pause | resume | complete | blocked', }, - objective: { type: 'string', description: 'Replacement objective; valid only with action edit.' }, - max_goal_rounds: { type: 'number', description: 'Replacement cap; valid only with action edit.' }, + objective: { type: 'string', description: 'Replacement objective for action edit; ignored otherwise.' }, + max_goal_rounds: { type: 'number', description: 'Replacement cap for action edit; ignored otherwise.' }, blocked_reason: { type: 'string', - description: 'Concrete blocking condition; required only with action blocked.', + description: 'Concrete blocking condition required for action blocked; ignored otherwise.', }, }, output: GOAL_OUTPUT, execute(args, exec) { const execution = goalToolExecution(ctx, exec) const ref = goalRef(args.goal_id, args.revision) - const replacements = { - ...hasText(args.objective) ? { objective: args.objective } : {}, - ...hasRoundCap(args.max_goal_rounds) ? { maxGoalRounds: args.max_goal_rounds } : {}, - } if (args.action === 'edit') { requireDirectHuman(ctx, execution) - if (hasText(args.blocked_reason)) { - throw new HarnessError('blocked_reason is valid only with action blocked', 'GOAL_TOOL_INVALID_UPDATE') - } - const goal = ctx.goals.edit(execution.agent, ref, replacements) + const goal = ctx.goals.edit(execution.agent, ref, { + ...hasText(args.objective) ? { objective: args.objective } : {}, + ...hasRoundCap(args.max_goal_rounds) ? { maxGoalRounds: args.max_goal_rounds } : {}, + }) return Promise.resolve(goalValue(goal)) } if (args.action === 'pause' || args.action === 'resume') { requireDirectHuman(ctx, execution) - if (hasText(args.objective) || hasRoundCap(args.max_goal_rounds) || hasText(args.blocked_reason)) { - throw new HarnessError( - 'objective and max_goal_rounds are valid only with action edit; blocked_reason is valid only with action blocked', - 'GOAL_TOOL_INVALID_UPDATE', - ) - } const current = ctx.goals.get(execution.agent) if (args.action === 'resume' && current?.id === ref.id && current.revision === ref.revision && current.phase === 'paused') { @@ -297,15 +287,6 @@ export function apply(ctx: Context, config: Config): void { return Promise.resolve(goalValue(goal)) } const authority = completionAuthority(ctx, execution) - if (hasText(args.objective) || hasRoundCap(args.max_goal_rounds)) { - throw new HarnessError( - 'objective and max_goal_rounds are valid only with action edit', - 'GOAL_TOOL_INVALID_UPDATE', - ) - } - if (args.action === 'complete' && hasText(args.blocked_reason)) { - throw new HarnessError('blocked_reason is valid only with action blocked', 'GOAL_TOOL_INVALID_UPDATE') - } if (args.action === 'blocked' && (args.blocked_reason === undefined || args.blocked_reason.trim().length === 0)) { throw new HarnessError('blocked_reason is required with action blocked', 'GOAL_TOOL_INVALID_UPDATE') @@ -341,11 +322,11 @@ export function apply(ctx: Context, config: Config): void { presentCall: args => present( `${args.action === 'blocked' ? 'Mark' : args.action.charAt(0).toUpperCase() + args.action.slice(1)} goal`, 'other', - hasText(args.blocked_reason) + args.action === 'blocked' && hasText(args.blocked_reason) ? args.blocked_reason - : hasText(args.objective) + : args.action === 'edit' && hasText(args.objective) ? args.objective - : hasRoundCap(args.max_goal_rounds) ? args.max_goal_rounds : args.goal_id, + : args.action === 'edit' && hasRoundCap(args.max_goal_rounds) ? args.max_goal_rounds : args.goal_id, ), })) } diff --git a/packages/goal/tool-goal/tests/tool-goal.spec.ts b/packages/goal/tool-goal/tests/tool-goal.spec.ts index ecc7822bce..392a8e9a5a 100644 --- a/packages/goal/tool-goal/tests/tool-goal.spec.ts +++ b/packages/goal/tool-goal/tests/tool-goal.spec.ts @@ -189,6 +189,19 @@ describe('goal tool registration and presentation', () => { goal_id: 'goal-1', revision: 2, action: 'resume', objective: '', max_goal_rounds: 0, blocked_reason: '', })).toEqual({ card: 'generic', title: 'Resume goal', kind: 'other', rawInput: 'goal-1' }) + for (const action of ['pause', 'resume', 'complete']) { + expect(ctx.tools.get('update_goal')?.presentCall?.({ + goal_id: 'goal-1', revision: 2, action, + objective: 'ignored objective', max_goal_rounds: 12, blocked_reason: 'ignored reason', + })).toEqual({ card: 'generic', title: `${action.charAt(0).toUpperCase() + action.slice(1)} goal`, kind: 'other', rawInput: 'goal-1' }) + } + expect(ctx.tools.get('update_goal')?.presentCall?.({ + goal_id: 'goal-1', revision: 2, action: 'edit', + objective: '', max_goal_rounds: 0, blocked_reason: 'ignored reason', + })).toEqual({ card: 'generic', title: 'Edit goal', kind: 'other', rawInput: 'goal-1' }) + expect(ctx.tools.get('update_goal')?.presentCall?.({ + goal_id: 'goal-1', revision: 2, action: 'blocked', blocked_reason: '', + })).toEqual({ card: 'generic', title: 'Mark goal', kind: 'other', rawInput: 'goal-1' }) expect(ctx.tools.get('update_goal')?.presentCall?.({ wrong: true })).toBeUndefined() }) @@ -470,20 +483,26 @@ describe('goal tool state transitions', () => { const invalidCreate = await execute(ctx, 'create_goal', { objective: ' ' }, root.agent) expect(invalidCreate.error?.info?.code).toBe('GOAL_INVALID_OBJECTIVE') const created = ctx.goals.create(root.agent, { objective: 'valid' }) - const replacement = await execute(ctx, 'update_goal', { - goal_id: created.id, - revision: created.revision, - action: 'pause', - objective: 'not valid for pause', + const invalidEdit = await execute(ctx, 'update_goal', { + goal_id: created.id, revision: created.revision, action: 'edit', objective: ' ', + blocked_reason: 'ignored', }, root.agent) - expect(replacement.error?.info?.code).toBe('GOAL_TOOL_INVALID_UPDATE') - const terminalUpdate = await execute(ctx, 'update_goal', { - goal_id: created.id, - revision: created.revision, - action: 'complete', - max_goal_rounds: 2, + expect(invalidEdit.error?.info?.code).toBe('GOAL_INVALID_OBJECTIVE') + const invalidCap = await execute(ctx, 'update_goal', { + goal_id: created.id, revision: created.revision, action: 'edit', max_goal_rounds: -1, + blocked_reason: 'ignored', }, root.agent) - expect(terminalUpdate.error?.info?.code).toBe('GOAL_TOOL_INVALID_UPDATE') + expect(invalidCap.error?.info?.code).toBe('GOAL_INVALID_MAX_ROUNDS') + const stale = await execute(ctx, 'update_goal', { + goal_id: created.id, revision: created.revision + 1, action: 'complete', + objective: 'ignored', max_goal_rounds: 12, blocked_reason: 'ignored', + }, root.agent) + expect(stale.error?.info?.code).toBe('GOAL_STALE_REVISION') + const wrongType = await execute(ctx, 'update_goal', { + goal_id: created.id, revision: created.revision, action: 'complete', max_goal_rounds: '12', + }, root.agent) + expect(wrongType.isError).toBe(true) + expect(ctx.goals.get(root.agent)).toMatchObject({ objective: 'valid', revision: created.revision, phase: 'active' }) const blockedWithoutReason = await execute(ctx, 'update_goal', { goal_id: created.id, revision: created.revision, action: 'blocked', }, root.agent) @@ -492,25 +511,48 @@ describe('goal tool state transitions', () => { goal_id: created.id, revision: created.revision, action: 'blocked', blocked_reason: ' ', }, root.agent) expect(blockedWithEmptyReason.error?.info?.code).toBe('GOAL_TOOL_INVALID_UPDATE') - const completeWithReason = await execute(ctx, 'update_goal', { - goal_id: created.id, revision: created.revision, action: 'complete', blocked_reason: 'Not a blocker.', - }, root.agent) - expect(completeWithReason.error?.info?.code).toBe('GOAL_TOOL_INVALID_UPDATE') - const editWithReason = await execute(ctx, 'update_goal', { - goal_id: created.id, - revision: created.revision, - action: 'edit', - objective: 'still valid', - blocked_reason: 'Not valid for edit.', - }, root.agent) - expect(editWithReason.error?.info?.code).toBe('GOAL_TOOL_INVALID_UPDATE') const malformedRef = await execute(ctx, 'update_goal', { goal_id: '', revision: 0, action: 'edit', objective: 'x', }, root.agent) expect(malformedRef.error?.info?.code).toBe('GOAL_TOOL_INVALID_UPDATE') }) - it('accepts only empty fillers in fields unused by the selected action', async () => { + it.each(['edit', 'pause', 'resume', 'complete', 'blocked'] as const)( + 'ignores populated fields unused by action %s without changing other goal values', + async (action) => { + const { ctx, root } = await harness() + try { + openTurn(root, { kind: 'user' }) + const goal = ctx.goals.create(root.agent, { objective: 'keep this objective', maxGoalRounds: 4 }) + if (action === 'resume') ctx.goals.disarm(root.agent) + const changesBefore = root.session.snapshotEvents().filter(event => event.type === 'goal/change').length + const result = await execute(ctx, 'update_goal', { + goal_id: goal.id, + revision: goal.revision, + action, + objective: 'replacement objective', + max_goal_rounds: 12, + blocked_reason: 'a concrete blocker', + }, root.agent) + const expected = { + objective: action === 'edit' ? 'replacement objective' : 'keep this objective', + maxGoalRounds: action === 'edit' ? 12 : 4, + revision: goal.revision + 1, + phase: action === 'pause' ? 'paused' : action === 'complete' || action === 'blocked' ? action : 'active', + } + expect(resultGoal(result)).toMatchObject(expected) + expect(ctx.goals.get(root.agent)).toMatchObject(expected) + expect(ctx.goals.get(root.agent)?.blockedReason).toEqual(action === 'blocked' + ? { code: 'model-reported', message: 'a concrete blocker' } + : undefined) + expect(root.session.snapshotEvents().filter(event => event.type === 'goal/change')).toHaveLength(changesBefore + 1) + } finally { + await ctx.fiber.dispose() + } + }, + ) + + it('accepts empty fillers in fields unused by the selected action', async () => { const { ctx, root } = await harness() openTurn(root, { kind: 'user' }) let goal = ctx.goals.create(root.agent, { objective: 'valid' }) @@ -595,8 +637,12 @@ describe('goal tool state transitions', () => { expect(edit.error?.info?.code).toBe('GOAL_TOOL_AUTHORITY_REQUIRED') const complete = await execute(ctx, 'update_goal', { goal_id: created.id, revision: created.revision, action: 'complete', + objective: 'ignored', max_goal_rounds: 12, blocked_reason: 'ignored', }, root.agent) - expect(resultGoal(complete)).toMatchObject({ phase: 'complete', revision: 2, roundsStarted: 1 }) + expect(resultGoal(complete)).toMatchObject({ + phase: 'complete', revision: 2, roundsStarted: 1, + objective: 'round-owned', maxGoalRounds: created.maxGoalRounds, + }) }) it('enforces the configured model self-block lower bound across admitted rounds', async () => { diff --git a/snapshots/sdk/bash-tool/tool-schemas.expected.json b/snapshots/sdk/bash-tool/tool-schemas.expected.json index 58b6f5a201..af58f05832 100644 --- a/snapshots/sdk/bash-tool/tool-schemas.expected.json +++ b/snapshots/sdk/bash-tool/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/sdk/subagent-activation-limit/tool-schemas.1.expected.json b/snapshots/sdk/subagent-activation-limit/tool-schemas.1.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/sdk/subagent-activation-limit/tool-schemas.1.expected.json +++ b/snapshots/sdk/subagent-activation-limit/tool-schemas.1.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/sdk/subagent-continuable-inheritance/tool-schemas.1.expected.json b/snapshots/sdk/subagent-continuable-inheritance/tool-schemas.1.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/sdk/subagent-continuable-inheritance/tool-schemas.1.expected.json +++ b/snapshots/sdk/subagent-continuable-inheritance/tool-schemas.1.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/sdk/subagent-continuable/tool-schemas.1.expected.json b/snapshots/sdk/subagent-continuable/tool-schemas.1.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/sdk/subagent-continuable/tool-schemas.1.expected.json +++ b/snapshots/sdk/subagent-continuable/tool-schemas.1.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json b/snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json index 55005a53e3..a17808e30c 100644 --- a/snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json +++ b/snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json @@ -503,15 +503,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json index c185eca7ec..bf856eedd4 100644 --- a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json +++ b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json @@ -453,15 +453,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json index e93f34ff32..2ff33ac78d 100644 --- a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json +++ b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json @@ -478,15 +478,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/sdk/subagent-list-agents/tool-schemas.1.expected.json b/snapshots/sdk/subagent-list-agents/tool-schemas.1.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/sdk/subagent-list-agents/tool-schemas.1.expected.json +++ b/snapshots/sdk/subagent-list-agents/tool-schemas.1.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/sdk/subagent-send-message/tool-schemas.1.expected.json b/snapshots/sdk/subagent-send-message/tool-schemas.1.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/sdk/subagent-send-message/tool-schemas.1.expected.json +++ b/snapshots/sdk/subagent-send-message/tool-schemas.1.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/sdk/text-turn/tool-schemas.expected.json b/snapshots/sdk/text-turn/tool-schemas.expected.json index 58b6f5a201..af58f05832 100644 --- a/snapshots/sdk/text-turn/tool-schemas.expected.json +++ b/snapshots/sdk/text-turn/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/agent-instructions/tool-schemas.expected.json b/snapshots/session/agent-instructions/tool-schemas.expected.json index a3a82e074d..86fd6cf68d 100644 --- a/snapshots/session/agent-instructions/tool-schemas.expected.json +++ b/snapshots/session/agent-instructions/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ @@ -1061,15 +1061,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/background-confinement-failure/tool-schemas.expected.json b/snapshots/session/background-confinement-failure/tool-schemas.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/session/background-confinement-failure/tool-schemas.expected.json +++ b/snapshots/session/background-confinement-failure/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/both-mode-turn/system-prompt.expected.md b/snapshots/session/both-mode-turn/system-prompt.expected.md index f15f09f8de..877f420ae9 100644 --- a/snapshots/session/both-mode-turn/system-prompt.expected.md +++ b/snapshots/session/both-mode-turn/system-prompt.expected.md @@ -198,11 +198,11 @@ interface ToolArgsMap { revision: number; /** edit | pause | resume | complete | blocked */ action: "edit" | "pause" | "resume" | "complete" | "blocked"; - /** Replacement objective; valid only with action edit. */ + /** Replacement objective for action edit; ignored otherwise. */ objective?: string; - /** Replacement cap; valid only with action edit. */ + /** Replacement cap for action edit; ignored otherwise. */ max_goal_rounds?: number; - /** Concrete blocking condition; required only with action blocked. */ + /** Concrete blocking condition required for action blocked; ignored otherwise. */ blocked_reason?: string; } & Record; /** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */ diff --git a/snapshots/session/both-mode-turn/tool-schemas.expected.json b/snapshots/session/both-mode-turn/tool-schemas.expected.json index 86ee3527e9..f6c26273bd 100644 --- a/snapshots/session/both-mode-turn/tool-schemas.expected.json +++ b/snapshots/session/both-mode-turn/tool-schemas.expected.json @@ -486,15 +486,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/browser-use-chrome-devtools-mcp/tool-schemas.expected.json b/snapshots/session/browser-use-chrome-devtools-mcp/tool-schemas.expected.json index 1cb484d359..d59e7ad3c7 100644 --- a/snapshots/session/browser-use-chrome-devtools-mcp/tool-schemas.expected.json +++ b/snapshots/session/browser-use-chrome-devtools-mcp/tool-schemas.expected.json @@ -556,15 +556,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/browser-use-playwright-mcp/tool-schemas.expected.json b/snapshots/session/browser-use-playwright-mcp/tool-schemas.expected.json index db6f9d1f94..61c4891f39 100644 --- a/snapshots/session/browser-use-playwright-mcp/tool-schemas.expected.json +++ b/snapshots/session/browser-use-playwright-mcp/tool-schemas.expected.json @@ -558,15 +558,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/browser-use-stagehand-native/tool-schemas.expected.json b/snapshots/session/browser-use-stagehand-native/tool-schemas.expected.json index 09640f90af..d5658e1a12 100644 --- a/snapshots/session/browser-use-stagehand-native/tool-schemas.expected.json +++ b/snapshots/session/browser-use-stagehand-native/tool-schemas.expected.json @@ -662,15 +662,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/compaction-recovery/tool-schemas.expected.json b/snapshots/session/compaction-recovery/tool-schemas.expected.json index a3a82e074d..86fd6cf68d 100644 --- a/snapshots/session/compaction-recovery/tool-schemas.expected.json +++ b/snapshots/session/compaction-recovery/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ @@ -1061,15 +1061,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/computer-use-cua-driver-mcp/tool-schemas.expected.json b/snapshots/session/computer-use-cua-driver-mcp/tool-schemas.expected.json index 024361e301..9d92e8f413 100644 --- a/snapshots/session/computer-use-cua-driver-mcp/tool-schemas.expected.json +++ b/snapshots/session/computer-use-cua-driver-mcp/tool-schemas.expected.json @@ -535,15 +535,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/computer-use-cua-driver-native/tool-schemas.expected.json b/snapshots/session/computer-use-cua-driver-native/tool-schemas.expected.json index ececc98096..9f1676eec1 100644 --- a/snapshots/session/computer-use-cua-driver-native/tool-schemas.expected.json +++ b/snapshots/session/computer-use-cua-driver-native/tool-schemas.expected.json @@ -495,15 +495,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/cordis-inspect-jsdoc/system-prompt.expected.md b/snapshots/session/cordis-inspect-jsdoc/system-prompt.expected.md index 158f24c1af..baa7f5819e 100644 --- a/snapshots/session/cordis-inspect-jsdoc/system-prompt.expected.md +++ b/snapshots/session/cordis-inspect-jsdoc/system-prompt.expected.md @@ -215,11 +215,11 @@ interface ToolArgsMap { revision: number; /** edit | pause | resume | complete | blocked */ action: "edit" | "pause" | "resume" | "complete" | "blocked"; - /** Replacement objective; valid only with action edit. */ + /** Replacement objective for action edit; ignored otherwise. */ objective?: string; - /** Replacement cap; valid only with action edit. */ + /** Replacement cap for action edit; ignored otherwise. */ max_goal_rounds?: number; - /** Concrete blocking condition; required only with action blocked. */ + /** Concrete blocking condition required for action blocked; ignored otherwise. */ blocked_reason?: string; } & Record; /** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */ diff --git a/snapshots/session/cordis-inspect-jsdoc/tool-schemas.expected.json b/snapshots/session/cordis-inspect-jsdoc/tool-schemas.expected.json index a48efa53bf..980b07c460 100644 --- a/snapshots/session/cordis-inspect-jsdoc/tool-schemas.expected.json +++ b/snapshots/session/cordis-inspect-jsdoc/tool-schemas.expected.json @@ -527,15 +527,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/deepseek-messages-system-prompt/tool-schemas.expected.json b/snapshots/session/deepseek-messages-system-prompt/tool-schemas.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/session/deepseek-messages-system-prompt/tool-schemas.expected.json +++ b/snapshots/session/deepseek-messages-system-prompt/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/deepseek-protocol-system-prompt/tool-schemas.expected.json b/snapshots/session/deepseek-protocol-system-prompt/tool-schemas.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/session/deepseek-protocol-system-prompt/tool-schemas.expected.json +++ b/snapshots/session/deepseek-protocol-system-prompt/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/foreground-confinement-timeout/tool-schemas.expected.json b/snapshots/session/foreground-confinement-timeout/tool-schemas.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/session/foreground-confinement-timeout/tool-schemas.expected.json +++ b/snapshots/session/foreground-confinement-timeout/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/headless.snapshot.ts b/snapshots/session/headless.snapshot.ts index 2389beae9f..95bdbacd71 100644 --- a/snapshots/session/headless.snapshot.ts +++ b/snapshots/session/headless.snapshot.ts @@ -1187,6 +1187,25 @@ describe('headless recorded-session snapshots', () => { expect(saved).toContain('id: demo') expect(saved).toContain('disabled: false') } + if (scenario.name === 'toolcall-unused-args') { + const events = parseSessionLog(actualLogs[0]!.content) + const results = events.flatMap(event => event.type === 'tool/result' ? [event.data.message] : []) + expect(results).toHaveLength(6) + expect(results.every(result => !result.isError)).toBe(true) + for (const extension of ['ts', 'md']) { + const grep = results.find(result => result.toolCallId === `call_grep_${extension}`) + const text = grep?.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('\n') + expect(text).toContain('Found 1 match') + expect(text).toContain(`seed/example.${extension}`) + expect(text).toContain('marker') + } + const changes = records(actualLogs[0]!.content) + .filter(event => event.type === 'goal/change').map(event => event.data as JsonObject) + expect(changes.map(change => change.operation)).toEqual(['create', 'complete']) + const original = changes[0]!.goal as JsonObject + expect(original).toMatchObject({ objective: 'Find marker in both seed files.', maxGoalRounds: 4 }) + expect(changes[1]!.goal).toEqual({ ...original, revision: 2, phase: 'complete' }) + } if (scenario.name === 'session-query-spill') { await verifySessionQuerySpill(actualLogs[0]!.content, spillRoot, locatorRoot) } diff --git a/snapshots/session/lsp-definition/tool-schemas.expected.json b/snapshots/session/lsp-definition/tool-schemas.expected.json index da96eef30a..3e143e3d29 100644 --- a/snapshots/session/lsp-definition/tool-schemas.expected.json +++ b/snapshots/session/lsp-definition/tool-schemas.expected.json @@ -486,15 +486,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/mcp-resources-ptc/system-prompt.expected.md b/snapshots/session/mcp-resources-ptc/system-prompt.expected.md index edb6c751b8..cb95c5d647 100644 --- a/snapshots/session/mcp-resources-ptc/system-prompt.expected.md +++ b/snapshots/session/mcp-resources-ptc/system-prompt.expected.md @@ -229,11 +229,11 @@ interface ToolArgsMap { revision: number; /** edit | pause | resume | complete | blocked */ action: "edit" | "pause" | "resume" | "complete" | "blocked"; - /** Replacement objective; valid only with action edit. */ + /** Replacement objective for action edit; ignored otherwise. */ objective?: string; - /** Replacement cap; valid only with action edit. */ + /** Replacement cap for action edit; ignored otherwise. */ max_goal_rounds?: number; - /** Concrete blocking condition; required only with action blocked. */ + /** Concrete blocking condition required for action blocked; ignored otherwise. */ blocked_reason?: string; } & Record; /** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */ diff --git a/snapshots/session/mcp-resources/tool-schemas.expected.json b/snapshots/session/mcp-resources/tool-schemas.expected.json index d6dea164c9..42f12ff7ce 100644 --- a/snapshots/session/mcp-resources/tool-schemas.expected.json +++ b/snapshots/session/mcp-resources/tool-schemas.expected.json @@ -510,15 +510,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/plugin-manager-mcp/tool-schemas.expected.json b/snapshots/session/plugin-manager-mcp/tool-schemas.expected.json index 07f01589c0..d7bfdde337 100644 --- a/snapshots/session/plugin-manager-mcp/tool-schemas.expected.json +++ b/snapshots/session/plugin-manager-mcp/tool-schemas.expected.json @@ -500,15 +500,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ @@ -1233,15 +1233,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/plugin-manager/tool-schemas.expected.json b/snapshots/session/plugin-manager/tool-schemas.expected.json index 2a18354c6f..f3e5ad3709 100644 --- a/snapshots/session/plugin-manager/tool-schemas.expected.json +++ b/snapshots/session/plugin-manager/tool-schemas.expected.json @@ -500,15 +500,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/product-subagent-both/tool-schemas.expected.json b/snapshots/session/product-subagent-both/tool-schemas.expected.json index a0ec94f072..6dceb42b45 100644 --- a/snapshots/session/product-subagent-both/tool-schemas.expected.json +++ b/snapshots/session/product-subagent-both/tool-schemas.expected.json @@ -549,15 +549,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/product-subagent-codex/tool-schemas.expected.json b/snapshots/session/product-subagent-codex/tool-schemas.expected.json index fa96127988..75d11a3c8d 100644 --- a/snapshots/session/product-subagent-codex/tool-schemas.expected.json +++ b/snapshots/session/product-subagent-codex/tool-schemas.expected.json @@ -499,15 +499,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/product-subagent-result-diagnostic/tool-schemas.expected.json b/snapshots/session/product-subagent-result-diagnostic/tool-schemas.expected.json index af722e03b0..8002ca0414 100644 --- a/snapshots/session/product-subagent-result-diagnostic/tool-schemas.expected.json +++ b/snapshots/session/product-subagent-result-diagnostic/tool-schemas.expected.json @@ -474,15 +474,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/provider-cwd/tool-schemas.expected.json b/snapshots/session/provider-cwd/tool-schemas.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/session/provider-cwd/tool-schemas.expected.json +++ b/snapshots/session/provider-cwd/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/ptc-node-read-only/system-prompt.expected.md b/snapshots/session/ptc-node-read-only/system-prompt.expected.md index ff41826d84..57d91d0bb8 100644 --- a/snapshots/session/ptc-node-read-only/system-prompt.expected.md +++ b/snapshots/session/ptc-node-read-only/system-prompt.expected.md @@ -204,11 +204,11 @@ interface ToolArgsMap { revision: number; /** edit | pause | resume | complete | blocked */ action: "edit" | "pause" | "resume" | "complete" | "blocked"; - /** Replacement objective; valid only with action edit. */ + /** Replacement objective for action edit; ignored otherwise. */ objective?: string; - /** Replacement cap; valid only with action edit. */ + /** Replacement cap for action edit; ignored otherwise. */ max_goal_rounds?: number; - /** Concrete blocking condition; required only with action blocked. */ + /** Concrete blocking condition required for action blocked; ignored otherwise. */ blocked_reason?: string; } & Record; /** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */ diff --git a/snapshots/session/ptc-node-workspace/system-prompt.expected.md b/snapshots/session/ptc-node-workspace/system-prompt.expected.md index ff41826d84..57d91d0bb8 100644 --- a/snapshots/session/ptc-node-workspace/system-prompt.expected.md +++ b/snapshots/session/ptc-node-workspace/system-prompt.expected.md @@ -204,11 +204,11 @@ interface ToolArgsMap { revision: number; /** edit | pause | resume | complete | blocked */ action: "edit" | "pause" | "resume" | "complete" | "blocked"; - /** Replacement objective; valid only with action edit. */ + /** Replacement objective for action edit; ignored otherwise. */ objective?: string; - /** Replacement cap; valid only with action edit. */ + /** Replacement cap for action edit; ignored otherwise. */ max_goal_rounds?: number; - /** Concrete blocking condition; required only with action blocked. */ + /** Concrete blocking condition required for action blocked; ignored otherwise. */ blocked_reason?: string; } & Record; /** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */ diff --git a/snapshots/session/ptc-python-turn/system-prompt.expected.md b/snapshots/session/ptc-python-turn/system-prompt.expected.md index b7dfe3bf47..99dcad198e 100644 --- a/snapshots/session/ptc-python-turn/system-prompt.expected.md +++ b/snapshots/session/ptc-python-turn/system-prompt.expected.md @@ -423,11 +423,11 @@ class UpdateGoalArgs(TypedDict): revision: float # edit | pause | resume | complete | blocked action: Literal["edit", "pause", "resume", "complete", "blocked"] - # Replacement objective; valid only with action edit. + # Replacement objective for action edit; ignored otherwise. objective: NotRequired[str] - # Replacement cap; valid only with action edit. + # Replacement cap for action edit; ignored otherwise. max_goal_rounds: NotRequired[float] - # Concrete blocking condition; required only with action blocked. + # Concrete blocking condition required for action blocked; ignored otherwise. blocked_reason: NotRequired[str] # Additional keys beyond those declared are allowed. diff --git a/snapshots/session/ptc-read-image/system-prompt.expected.md b/snapshots/session/ptc-read-image/system-prompt.expected.md index fd956219e6..81042ef1cd 100644 --- a/snapshots/session/ptc-read-image/system-prompt.expected.md +++ b/snapshots/session/ptc-read-image/system-prompt.expected.md @@ -200,11 +200,11 @@ interface ToolArgsMap { revision: number; /** edit | pause | resume | complete | blocked */ action: "edit" | "pause" | "resume" | "complete" | "blocked"; - /** Replacement objective; valid only with action edit. */ + /** Replacement objective for action edit; ignored otherwise. */ objective?: string; - /** Replacement cap; valid only with action edit. */ + /** Replacement cap for action edit; ignored otherwise. */ max_goal_rounds?: number; - /** Concrete blocking condition; required only with action blocked. */ + /** Concrete blocking condition required for action blocked; ignored otherwise. */ blocked_reason?: string; } & Record; /** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */ diff --git a/snapshots/session/ptc-turn/system-prompt.expected.md b/snapshots/session/ptc-turn/system-prompt.expected.md index ff41826d84..57d91d0bb8 100644 --- a/snapshots/session/ptc-turn/system-prompt.expected.md +++ b/snapshots/session/ptc-turn/system-prompt.expected.md @@ -204,11 +204,11 @@ interface ToolArgsMap { revision: number; /** edit | pause | resume | complete | blocked */ action: "edit" | "pause" | "resume" | "complete" | "blocked"; - /** Replacement objective; valid only with action edit. */ + /** Replacement objective for action edit; ignored otherwise. */ objective?: string; - /** Replacement cap; valid only with action edit. */ + /** Replacement cap for action edit; ignored otherwise. */ max_goal_rounds?: number; - /** Concrete blocking condition; required only with action blocked. */ + /** Concrete blocking condition required for action blocked; ignored otherwise. */ blocked_reason?: string; } & Record; /** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */ diff --git a/snapshots/session/pty-tools-sandbox-backend/tool-schemas.expected.json b/snapshots/session/pty-tools-sandbox-backend/tool-schemas.expected.json index 4471c7a371..ffc24844fe 100644 --- a/snapshots/session/pty-tools-sandbox-backend/tool-schemas.expected.json +++ b/snapshots/session/pty-tools-sandbox-backend/tool-schemas.expected.json @@ -578,15 +578,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/ralph-loop/tool-schemas.1.expected.json b/snapshots/session/ralph-loop/tool-schemas.1.expected.json index 2118a8e4b0..4c3b83d53e 100644 --- a/snapshots/session/ralph-loop/tool-schemas.1.expected.json +++ b/snapshots/session/ralph-loop/tool-schemas.1.expected.json @@ -512,15 +512,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/ralph-loop/tool-schemas.2.expected.json b/snapshots/session/ralph-loop/tool-schemas.2.expected.json index 2118a8e4b0..4c3b83d53e 100644 --- a/snapshots/session/ralph-loop/tool-schemas.2.expected.json +++ b/snapshots/session/ralph-loop/tool-schemas.2.expected.json @@ -512,15 +512,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/ralph-loop/tool-schemas.expected.json b/snapshots/session/ralph-loop/tool-schemas.expected.json index 57eaf5feaa..265132aa2c 100644 --- a/snapshots/session/ralph-loop/tool-schemas.expected.json +++ b/snapshots/session/ralph-loop/tool-schemas.expected.json @@ -469,15 +469,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/session-query-spill/tool-schemas.expected.json b/snapshots/session/session-query-spill/tool-schemas.expected.json index 5c635ee9a1..8d40a13bbe 100644 --- a/snapshots/session/session-query-spill/tool-schemas.expected.json +++ b/snapshots/session/session-query-spill/tool-schemas.expected.json @@ -653,15 +653,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/subagent-acp-diagnostic/tool-schemas.expected.json b/snapshots/session/subagent-acp-diagnostic/tool-schemas.expected.json index 2c08e64dc9..db507436cc 100644 --- a/snapshots/session/subagent-acp-diagnostic/tool-schemas.expected.json +++ b/snapshots/session/subagent-acp-diagnostic/tool-schemas.expected.json @@ -474,15 +474,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/subagent-child-question-rejection/tool-schemas.expected.json b/snapshots/session/subagent-child-question-rejection/tool-schemas.expected.json index 50b44889ea..376fd88904 100644 --- a/snapshots/session/subagent-child-question-rejection/tool-schemas.expected.json +++ b/snapshots/session/subagent-child-question-rejection/tool-schemas.expected.json @@ -512,15 +512,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/subagent-tool-filter/tool-schemas.1.expected.json b/snapshots/session/subagent-tool-filter/tool-schemas.1.expected.json index 1776b0e3ac..72c03b7d67 100644 --- a/snapshots/session/subagent-tool-filter/tool-schemas.1.expected.json +++ b/snapshots/session/subagent-tool-filter/tool-schemas.1.expected.json @@ -363,15 +363,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/system-prompt-in-history/tool-schemas.expected.json b/snapshots/session/system-prompt-in-history/tool-schemas.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/session/system-prompt-in-history/tool-schemas.expected.json +++ b/snapshots/session/system-prompt-in-history/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/team-targets/tool-schemas.expected.json b/snapshots/session/team-targets/tool-schemas.expected.json index 0a04365754..13b2a5d95d 100644 --- a/snapshots/session/team-targets/tool-schemas.expected.json +++ b/snapshots/session/team-targets/tool-schemas.expected.json @@ -575,15 +575,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/text-turn/tool-schemas.expected.json b/snapshots/session/text-turn/tool-schemas.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/session/text-turn/tool-schemas.expected.json +++ b/snapshots/session/text-turn/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/toolcall-unused-args/replay.override.json b/snapshots/session/toolcall-unused-args/replay.override.json new file mode 100644 index 0000000000..f3d93303ce --- /dev/null +++ b/snapshots/session/toolcall-unused-args/replay.override.json @@ -0,0 +1,17 @@ +{ + "patches": [ + { + "at": 3, + "entry": { + "kind": "chunks", + "chunks": [ + { "type": "block-start", "index": 0, "blockType": "tool-call" }, + { "type": "tool-call-delta", "index": 0, "id": "call_complete_goal", "name": "update_goal", "argumentsDelta": "{\"goal_id\":\"{{fromRequest:goal-[0-9a-f-]+}}\",\"revision\":1,\"action\":\"complete\",\"objective\":\"SHOULD NOT REPLACE\",\"max_goal_rounds\":12,\"blocked_reason\":\"ignored\"}" }, + { "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_complete_goal", "name": "update_goal", "arguments": "{\"goal_id\":\"{{fromRequest:goal-[0-9a-f-]+}}\",\"revision\":1,\"action\":\"complete\",\"objective\":\"SHOULD NOT REPLACE\",\"max_goal_rounds\":12,\"blocked_reason\":\"ignored\"}" } }, + { "type": "usage", "usage": { "inputTokens": 1, "outputTokens": 1 } }, + { "type": "finish", "reason": { "kind": "tool-calls" } } + ] + } + } + ] +} diff --git a/snapshots/session/toolcall-unused-args/session.v4.jsonl b/snapshots/session/toolcall-unused-args/session.v4.jsonl new file mode 100644 index 0000000000..bc133c5e15 --- /dev/null +++ b/snapshots/session/toolcall-unused-args/session.v4.jsonl @@ -0,0 +1,46 @@ +{"type":"session","version":4,"id":"{{session:1}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} +{"type":"permission/preset","data":{"preset":"danger-full-access"}} +{"type":"sandbox/mode","data":{"mode":"danger-full-access"}} +{"type":"approval/policy","data":{"policy":"never"}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Create a goal to find marker in both seed files with a cap of 4 rounds. Search seed/example.ts and seed/example.md separately with grep using an empty include filter. Read the goal, then complete it with objective SHOULD NOT REPLACE, max_goal_rounds 12, and blocked_reason ignored. Read the completed goal and reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} +{"type":"turn/start","data":{"turn":1}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} +{"type":"step/start","data":{"turn":1,"step":1}} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Create a goal to find marker in both seed files with a cap of 4 rounds. Search seed/example.ts and seed/example.md separately with grep using an empty include filter. Read the goal, then complete it with objective SHOULD NOT REPLACE, max_goal_rounds 12, and blocked_reason ignored. Read the completed goal and reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} +{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} +{"type":"session/title","data":{"title":"Create a goal to find","messageSeqs":[8],"source":{"kind":"fallback"}}} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_create_goal","name":"create_goal","arguments":"{\"objective\":\"Find marker in both seed files.\",\"max_goal_rounds\":4}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1789897413184,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1789897413184,"index":0,"dt":[],"id":"call_create_goal","name":"create_goal","args":["{\"objective\":\"Find marker in both seed files.\",\"max_goal_rounds\":4}"]},{"type":"chunk","time":1789897413184,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_create_goal","name":"create_goal","arguments":"{\"objective\":\"Find marker in both seed files.\",\"max_goal_rounds\":4}"}}},{"type":"chunk","time":1789897413184,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1789897413184,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_create_goal","name":"create_goal","arguments":"{\"objective\":\"Find marker in both seed files.\",\"max_goal_rounds\":4}"}} +{"type":"goal/change","data":{"kind":"goal/change","version":1,"operation":"create","goal":{"id":"{{id:1}}","revision":1,"objective":"Find marker in both seed files.","phase":"active","maxGoalRounds":4},"roundsStarted":0,"createdAt":1789897413190,"updatedAt":1789897413190}} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_create_goal"},"toolCallId":"call_create_goal","content":[{"type":"text","text":"{\"goal\":{\"id\":\"{{id:1}}\",\"revision\":1,\"objective\":\"Find marker in both seed files.\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":4},\"activation\":\"armed\"}"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":1}} +{"type":"step/start","data":{"turn":1,"step":2}} +{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_grep_ts","name":"grep","arguments":"{\"pattern\":\"marker\",\"path\":\"seed/example.ts\",\"include\":\"\"}"},{"type":"tool-call","id":"call_grep_md","name":"grep","arguments":"{\"pattern\":\"marker\",\"path\":\"seed/example.md\",\"include\":\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1789897413207,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1789897413207,"index":0,"dt":[],"id":"call_grep_ts","name":"grep","args":["{\"pattern\":\"marker\",\"path\":\"seed/example.ts\",\"include\":\"\"}"]},{"type":"chunk","time":1789897413207,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_grep_ts","name":"grep","arguments":"{\"pattern\":\"marker\",\"path\":\"seed/example.ts\",\"include\":\"\"}"}}},{"type":"chunk","time":1789897413207,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1789897413207,"index":1,"dt":[],"id":"call_grep_md","name":"grep","args":["{\"pattern\":\"marker\",\"path\":\"seed/example.md\",\"include\":\"\"}"]},{"type":"chunk","time":1789897413207,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_grep_md","name":"grep","arguments":"{\"pattern\":\"marker\",\"path\":\"seed/example.md\",\"include\":\"\"}"}}},{"type":"chunk","time":1789897413207,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1789897413207,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_grep_ts","name":"grep","arguments":"{\"pattern\":\"marker\",\"path\":\"seed/example.ts\",\"include\":\"\"}"}} +{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_grep_ts"},"toolCallId":"call_grep_ts","content":[{"type":"text","text":"Found 1 match\n\nseed/example.ts\nLine 1: export const marker = 'typescript';"}],"isError":false,"id":"{{message:7}}"},"meta":{"shape":"matches","files":[{"path":"seed/example.ts","matches":[{"lineNumber":1,"line":"export const marker = 'typescript';"}]}],"truncated":false,"total":1}},"sourceEventSeqs":[20],"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_grep_md","name":"grep","arguments":"{\"pattern\":\"marker\",\"path\":\"seed/example.md\",\"include\":\"\"}"}} +{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_grep_md"},"toolCallId":"call_grep_md","content":[{"type":"text","text":"Found 1 match\n\nseed/example.md\nLine 1: marker in markdown"}],"isError":false,"id":"{{message:8}}"},"meta":{"shape":"matches","files":[{"path":"seed/example.md","matches":[{"lineNumber":1,"line":"marker in markdown"}]}],"truncated":false,"total":1}},"sourceEventSeqs":[22],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":2}} +{"type":"step/start","data":{"turn":1,"step":3}} +{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_get_goal","name":"get_goal","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:9}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1789897413251,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1789897413251,"index":0,"dt":[],"id":"call_get_goal","name":"get_goal","args":["{}"]},{"type":"chunk","time":1789897413251,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_get_goal","name":"get_goal","arguments":"{}"}}},{"type":"chunk","time":1789897413251,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1789897413251,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":3,"callId":"call_get_goal","name":"get_goal","arguments":"{}"}} +{"type":"tool/result","data":{"turn":1,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"call_get_goal"},"toolCallId":"call_get_goal","content":[{"type":"text","text":"{\"goal\":{\"id\":\"{{id:1}}\",\"revision\":1,\"objective\":\"Find marker in both seed files.\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":4},\"activation\":\"armed\"}"}],"isError":false,"id":"{{message:10}}"}},"sourceEventSeqs":[27],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":3}} +{"type":"step/start","data":{"turn":1,"step":4}} +{"type":"assistant/message","data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_complete_goal","name":"update_goal","arguments":"{\"goal_id\":\"{{id:1}}\",\"revision\":1,\"action\":\"complete\",\"objective\":\"SHOULD NOT REPLACE\",\"max_goal_rounds\":12,\"blocked_reason\":\"ignored\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:11}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1789897413267,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1789897413267,"index":0,"dt":[],"id":"call_complete_goal","name":"update_goal","args":["{\"goal_id\":\"{{id:1}}\",\"revision\":1,\"action\":\"complete\",\"objective\":\"SHOULD NOT REPLACE\",\"max_goal_rounds\":12,\"blocked_reason\":\"ignored\"}"]},{"type":"chunk","time":1789897413267,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_complete_goal","name":"update_goal","arguments":"{\"goal_id\":\"{{id:1}}\",\"revision\":1,\"action\":\"complete\",\"objective\":\"SHOULD NOT REPLACE\",\"max_goal_rounds\":12,\"blocked_reason\":\"ignored\"}"}}},{"type":"chunk","time":1789897413267,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1789897413267,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":4,"callId":"call_complete_goal","name":"update_goal","arguments":"{\"goal_id\":\"{{id:1}}\",\"revision\":1,\"action\":\"complete\",\"objective\":\"SHOULD NOT REPLACE\",\"max_goal_rounds\":12,\"blocked_reason\":\"ignored\"}"}} +{"type":"goal/change","data":{"kind":"goal/change","version":1,"operation":"complete","goal":{"id":"{{id:1}}","revision":2,"objective":"Find marker in both seed files.","phase":"complete","maxGoalRounds":4},"roundsStarted":0,"createdAt":1789897413190,"updatedAt":1789897413272}} +{"type":"tool/result","data":{"turn":1,"step":4,"message":{"role":"tool","source":{"kind":"tool","callId":"call_complete_goal"},"toolCallId":"call_complete_goal","content":[{"type":"text","text":"{\"goal\":{\"id\":\"{{id:1}}\",\"revision\":2,\"objective\":\"Find marker in both seed files.\",\"phase\":\"complete\",\"roundsStarted\":0,\"maxGoalRounds\":4},\"activation\":\"disarmed\"}"}],"isError":false,"id":"{{message:12}}"}},"sourceEventSeqs":[32],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":4}} +{"type":"step/start","data":{"turn":1,"step":5}} +{"type":"assistant/message","data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_get_completed_goal","name":"get_goal","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:13}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1789897413285,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1789897413285,"index":0,"dt":[],"id":"call_get_completed_goal","name":"get_goal","args":["{}"]},{"type":"chunk","time":1789897413285,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_get_completed_goal","name":"get_goal","arguments":"{}"}}},{"type":"chunk","time":1789897413285,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1789897413285,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":5,"callId":"call_get_completed_goal","name":"get_goal","arguments":"{}"}} +{"type":"tool/result","data":{"turn":1,"step":5,"message":{"role":"tool","source":{"kind":"tool","callId":"call_get_completed_goal"},"toolCallId":"call_get_completed_goal","content":[{"type":"text","text":"{\"goal\":{\"id\":\"{{id:1}}\",\"revision\":2,\"objective\":\"Find marker in both seed files.\",\"phase\":\"complete\",\"roundsStarted\":0,\"maxGoalRounds\":4},\"activation\":\"disarmed\"}"}],"isError":false,"id":"{{message:14}}"}},"sourceEventSeqs":[38],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":5}} +{"type":"step/start","data":{"turn":1,"step":6}} +{"type":"assistant/message","data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:15}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1789897413301,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1789897413301,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":1789897413301,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":1789897413301,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1789897413301,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":6}} +{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/snapshots/session/toolcall-unused-args/snapshot.yml b/snapshots/session/toolcall-unused-args/snapshot.yml new file mode 100644 index 0000000000..aea8f078e7 --- /dev/null +++ b/snapshots/session/toolcall-unused-args/snapshot.yml @@ -0,0 +1,9 @@ +version: 1 +scenario: toolcall-unused-args +profile: headless +composition: default +recording: authored +header: + class: default +replay: + override: true diff --git a/snapshots/session/toolcall-unused-args/workspace/seed/example.md b/snapshots/session/toolcall-unused-args/workspace/seed/example.md new file mode 100644 index 0000000000..6c953dce5b --- /dev/null +++ b/snapshots/session/toolcall-unused-args/workspace/seed/example.md @@ -0,0 +1 @@ +marker in markdown diff --git a/snapshots/session/toolcall-unused-args/workspace/seed/example.ts b/snapshots/session/toolcall-unused-args/workspace/seed/example.ts new file mode 100644 index 0000000000..d57dd909d0 --- /dev/null +++ b/snapshots/session/toolcall-unused-args/workspace/seed/example.ts @@ -0,0 +1 @@ +export const marker = 'typescript'; diff --git a/snapshots/session/web-fetch/tool-schemas.expected.json b/snapshots/session/web-fetch/tool-schemas.expected.json index cccf295aa2..82fe18be28 100644 --- a/snapshots/session/web-fetch/tool-schemas.expected.json +++ b/snapshots/session/web-fetch/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/workflow-confinement/tool-schemas.expected.json b/snapshots/session/workflow-confinement/tool-schemas.expected.json index f1421e3b5d..e889e27e18 100644 --- a/snapshots/session/workflow-confinement/tool-schemas.expected.json +++ b/snapshots/session/workflow-confinement/tool-schemas.expected.json @@ -449,15 +449,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/session/workspace-dependencies/tool-schemas.expected.json b/snapshots/session/workspace-dependencies/tool-schemas.expected.json index 7697c3522f..0393281ae8 100644 --- a/snapshots/session/workspace-dependencies/tool-schemas.expected.json +++ b/snapshots/session/workspace-dependencies/tool-schemas.expected.json @@ -412,15 +412,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/web/cordis-tool-round/tool-schemas.expected.json b/snapshots/web/cordis-tool-round/tool-schemas.expected.json index ca7afe0add..84a1c18915 100644 --- a/snapshots/web/cordis-tool-round/tool-schemas.expected.json +++ b/snapshots/web/cordis-tool-round/tool-schemas.expected.json @@ -745,15 +745,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/web/fresh-round-trip/tool-schemas.expected.json b/snapshots/web/fresh-round-trip/tool-schemas.expected.json index 48bb6c3607..fcda43dc6e 100644 --- a/snapshots/web/fresh-round-trip/tool-schemas.expected.json +++ b/snapshots/web/fresh-round-trip/tool-schemas.expected.json @@ -548,15 +548,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [ diff --git a/snapshots/web/ptc-round/system-prompt.expected.md b/snapshots/web/ptc-round/system-prompt.expected.md index 8787372c8f..357da754b5 100644 --- a/snapshots/web/ptc-round/system-prompt.expected.md +++ b/snapshots/web/ptc-round/system-prompt.expected.md @@ -231,11 +231,11 @@ interface ToolArgsMap { revision: number; /** edit | pause | resume | complete | blocked */ action: "edit" | "pause" | "resume" | "complete" | "blocked"; - /** Replacement objective; valid only with action edit. */ + /** Replacement objective for action edit; ignored otherwise. */ objective?: string; - /** Replacement cap; valid only with action edit. */ + /** Replacement cap for action edit; ignored otherwise. */ max_goal_rounds?: number; - /** Concrete blocking condition; required only with action blocked. */ + /** Concrete blocking condition required for action blocked; ignored otherwise. */ blocked_reason?: string; } & Record; /** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */ diff --git a/snapshots/web/schedule-catalog/tool-schemas.expected.json b/snapshots/web/schedule-catalog/tool-schemas.expected.json index 4fc9dab8fd..ead1129537 100644 --- a/snapshots/web/schedule-catalog/tool-schemas.expected.json +++ b/snapshots/web/schedule-catalog/tool-schemas.expected.json @@ -592,15 +592,15 @@ }, "objective": { "type": "string", - "description": "Replacement objective; valid only with action edit." + "description": "Replacement objective for action edit; ignored otherwise." }, "max_goal_rounds": { "type": "number", - "description": "Replacement cap; valid only with action edit." + "description": "Replacement cap for action edit; ignored otherwise." }, "blocked_reason": { "type": "string", - "description": "Concrete blocking condition; required only with action blocked." + "description": "Concrete blocking condition required for action blocked; ignored otherwise." } }, "required": [