fix(tools): tolerate empty filters and unused goal arguments
This commit is contained in:
1 parent
8d6245bcc8
commit
080a2c60b3
83 files changed
+474
-262
No files matched your search
@@ -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
|
||||
@@ -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.
|
||||
@@ -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 的目标字段表达的冲突意图;它们不削弱授权,也不建立通用类型转换策略。
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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` 拒绝该尝试,面向用户的恢复路径拥有该转换。只有目标已经实现时才能标记完成,困难或不确定性本身不构成阻塞;阻塞报告必须说明具体条件。
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
@@ -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: <preview>`; `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: <preview>`; 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.
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ kind: "package-reference"
|
||||
| 工具 | 参数 | 行为 |
|
||||
|---|---|---|
|
||||
| `glob` | `pattern`、`path?` | 查找路径匹配 glob 模式的文件,包含隐藏与忽略文件但排除 VCS 元数据;不含 `/` 的模式匹配任意深度的基名,因此 `*` 匹配整棵树;完整结果保持按修改时间排序 |
|
||||
| `grep` | `pattern`、`path?`、`include?` | 用 ripgrep 正则搜索文件内容,并按文件分组返回 `Line N: <preview>` 匹配;`include` 是一个正向 glob 过滤器,逗号分隔列表与否定值会被前置拒绝 |
|
||||
| `grep` | `pattern`、`path?`、`include?` | 用 ripgrep 正则搜索文件内容,并按文件分组返回 `Line N: <preview>` 匹配;省略 `include` 或传入空字符串时不限制文件类型;否则 `include` 是一个正向 glob 过滤器,仅含空白的值、逗号分隔列表与否定值会被前置拒绝 |
|
||||
|
||||
常规预算不进入面向模型的 schema:需要周边上下文的模型用 `read` 读取匹配文件,需要后续结果的模型遵循返回的 spill locator 检索提示。
|
||||
|
||||
|
||||
@@ -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 } : {},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`,目标为空字符串、轮数上限为零时视为省略,且至少需要一个有效替换值。
|
||||
|
||||
### 配置
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
}))
|
||||
}
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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<string, JsonValue>;
|
||||
/** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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<string, JsonValue>;
|
||||
/** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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<string, JsonValue>;
|
||||
/** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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<string, JsonValue>;
|
||||
/** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */
|
||||
|
||||
@@ -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<string, JsonValue>;
|
||||
/** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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<string, JsonValue>;
|
||||
/** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */
|
||||
|
||||
@@ -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<string, JsonValue>;
|
||||
/** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"}}}
|
||||
@@ -0,0 +1,9 @@
|
||||
version: 1
|
||||
scenario: toolcall-unused-args
|
||||
profile: headless
|
||||
composition: default
|
||||
recording: authored
|
||||
header:
|
||||
class: default
|
||||
replay:
|
||||
override: true
|
||||
@@ -0,0 +1 @@
|
||||
marker in markdown
|
||||
@@ -0,0 +1 @@
|
||||
export const marker = 'typescript';
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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<string, JsonValue>;
|
||||
/** Fetch the content of a specific HTTP(S) URL and return it decoded to text. */
|
||||
|
||||
@@ -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": [
|
||||
|
||||
Reference in new issue
Block a user