@namzu/sdk 62.6.1 → 62.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +5 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/examples/SupervisorAgent.js +1 -0
- package/dist/agents/examples/SupervisorAgent.js.map +1 -1
- package/dist/agents/explore.d.ts.map +1 -1
- package/dist/agents/explore.js +2 -1
- package/dist/agents/explore.js.map +1 -1
- package/dist/compaction/index.d.ts +1 -1
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js +1 -1
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/model-summary.js +2 -2
- package/dist/compaction/model-summary.js.map +1 -1
- package/dist/compaction/summary.d.ts +16 -1
- package/dist/compaction/summary.d.ts.map +1 -1
- package/dist/compaction/summary.js +25 -2
- package/dist/compaction/summary.js.map +1 -1
- package/dist/config/runtime.d.ts +8 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +15 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/execution/workflow/runtime.d.ts.map +1 -1
- package/dist/execution/workflow/runtime.js +3 -0
- package/dist/execution/workflow/runtime.js.map +1 -1
- package/dist/execution/workflow/types.d.ts +8 -0
- package/dist/execution/workflow/types.d.ts.map +1 -1
- package/dist/manager/agent/child-session.d.ts +12 -0
- package/dist/manager/agent/child-session.d.ts.map +1 -1
- package/dist/manager/agent/child-session.js +29 -0
- package/dist/manager/agent/child-session.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +8 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts +13 -2
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +57 -3
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +3 -3
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +5 -3
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +5 -1
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/retry.d.ts +25 -0
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +28 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +3 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -3
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +4 -2
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +5 -2
- package/dist/public-tools.js.map +1 -1
- package/dist/runtime/query/auto-review/classifier.d.ts +7 -1
- package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/classifier.js +8 -4
- package/dist/runtime/query/auto-review/classifier.js.map +1 -1
- package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/prompt.js +2 -0
- package/dist/runtime/query/auto-review/prompt.js.map +1 -1
- package/dist/runtime/query/auto-review/script-evidence.d.ts +93 -0
- package/dist/runtime/query/auto-review/script-evidence.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/script-evidence.js +566 -0
- package/dist/runtime/query/auto-review/script-evidence.js.map +1 -0
- package/dist/runtime/query/checkpoint.d.ts +13 -0
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +37 -0
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +38 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +130 -11
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/finalize-turn.d.ts +7 -1
- package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
- package/dist/runtime/query/finalize-turn.js +2 -0
- package/dist/runtime/query/finalize-turn.js.map +1 -1
- package/dist/runtime/query/index.d.ts +8 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +7 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +32 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +158 -10
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +12 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +48 -1
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +5 -0
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +3 -3
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/prelude-lease.d.ts.map +1 -1
- package/dist/runtime/query/prelude-lease.js +22 -0
- package/dist/runtime/query/prelude-lease.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +13 -4
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +29 -0
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +2 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +1 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/runtime/query/turn/completion-audit.d.ts +27 -1
- package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -1
- package/dist/runtime/query/turn/completion-audit.js +71 -6
- package/dist/runtime/query/turn/completion-audit.js.map +1 -1
- package/dist/runtime/query/turn/outcome-ledger.d.ts +113 -0
- package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -0
- package/dist/runtime/query/turn/outcome-ledger.js +578 -0
- package/dist/runtime/query/turn/outcome-ledger.js.map +1 -0
- package/dist/sandbox/glob-segment.d.ts +8 -1
- package/dist/sandbox/glob-segment.d.ts.map +1 -1
- package/dist/sandbox/glob-segment.js +28 -9
- package/dist/sandbox/glob-segment.js.map +1 -1
- package/dist/store/session-log/index.d.ts +1 -0
- package/dist/store/session-log/index.d.ts.map +1 -1
- package/dist/store/session-log/index.js +1 -0
- package/dist/store/session-log/index.js.map +1 -1
- package/dist/store/session-log/turn-outcome.d.ts +37 -0
- package/dist/store/session-log/turn-outcome.d.ts.map +1 -0
- package/dist/store/session-log/turn-outcome.js +77 -0
- package/dist/store/session-log/turn-outcome.js.map +1 -0
- package/dist/tools/builtins/bash.js +1 -1
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/edit-apply.d.ts.map +1 -1
- package/dist/tools/builtins/edit-apply.js +24 -3
- package/dist/tools/builtins/edit-apply.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts +31 -17
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +92 -27
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/glob.js +1 -1
- package/dist/tools/builtins/glob.js.map +1 -1
- package/dist/tools/builtins/grep.js +1 -1
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/notebook-guard.d.ts +19 -0
- package/dist/tools/builtins/notebook-guard.d.ts.map +1 -0
- package/dist/tools/builtins/notebook-guard.js +75 -0
- package/dist/tools/builtins/notebook-guard.js.map +1 -0
- package/dist/tools/builtins/read-render.d.ts.map +1 -1
- package/dist/tools/builtins/read-render.js +8 -2
- package/dist/tools/builtins/read-render.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +14 -1
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +25 -6
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +6 -1
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/coordinator/outcome-record.d.ts +22 -0
- package/dist/tools/coordinator/outcome-record.d.ts.map +1 -0
- package/dist/tools/coordinator/outcome-record.js +91 -0
- package/dist/tools/coordinator/outcome-record.js.map +1 -0
- package/dist/tools/defineTool.d.ts +2 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +1 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts +23 -2
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +37 -2
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/tools/untrusted-envelope.d.ts +6 -0
- package/dist/tools/untrusted-envelope.d.ts.map +1 -1
- package/dist/tools/untrusted-envelope.js +22 -1
- package/dist/tools/untrusted-envelope.js.map +1 -1
- package/dist/toolsets/manager.d.ts.map +1 -1
- package/dist/toolsets/manager.js +3 -4
- package/dist/toolsets/manager.js.map +1 -1
- package/dist/toolsets/validation-issues.d.ts +27 -0
- package/dist/toolsets/validation-issues.d.ts.map +1 -0
- package/dist/toolsets/validation-issues.js +83 -0
- package/dist/toolsets/validation-issues.js.map +1 -0
- package/dist/turn/memory-recall.d.ts +13 -0
- package/dist/turn/memory-recall.d.ts.map +1 -1
- package/dist/turn/memory-recall.js +18 -0
- package/dist/turn/memory-recall.js.map +1 -1
- package/dist/types/agent/base.d.ts +19 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/query.d.ts +2 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/common/index.d.ts +17 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +8 -1
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/hitl/index.d.ts +22 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/session/config.d.ts +20 -0
- package/dist/types/session/config.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +3 -3
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/index.d.ts +1 -0
- package/dist/types/session/index.d.ts.map +1 -1
- package/dist/types/session/index.js +1 -0
- package/dist/types/session/index.js.map +1 -1
- package/dist/types/session/outcome.d.ts +95 -0
- package/dist/types/session/outcome.d.ts.map +1 -0
- package/dist/types/session/outcome.js +45 -0
- package/dist/types/session/outcome.js.map +1 -0
- package/dist/types/session/prepare-step.d.ts +2 -0
- package/dist/types/session/prepare-step.d.ts.map +1 -1
- package/dist/types/session/records.d.ts +167 -2
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +42 -0
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/session/turn.d.ts +18 -0
- package/dist/types/session/turn.d.ts.map +1 -1
- package/dist/types/session/turn.js.map +1 -1
- package/dist/types/tool/index.d.ts +30 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +5 -0
- package/src/agents/examples/SupervisorAgent.ts +1 -0
- package/src/agents/explore.ts +2 -1
- package/src/compaction/index.ts +1 -0
- package/src/compaction/model-summary.ts +2 -2
- package/src/compaction/summary.ts +30 -2
- package/src/config/runtime.ts +15 -0
- package/src/execution/workflow/runtime.ts +4 -0
- package/src/execution/workflow/types.ts +9 -0
- package/src/manager/agent/child-session.ts +36 -0
- package/src/manager/agent/lifecycle.ts +9 -0
- package/src/manager/session/turn-recorder.ts +62 -3
- package/src/pricing/rates.source.json +1 -1
- package/src/prompt/coding-agent-doctrine.ts +5 -3
- package/src/provider/fallback.ts +6 -1
- package/src/provider/retry.ts +51 -0
- package/src/public-runtime.ts +10 -1
- package/src/public-tools.ts +6 -1
- package/src/runtime/query/auto-review/classifier.ts +21 -5
- package/src/runtime/query/auto-review/prompt.ts +2 -0
- package/src/runtime/query/auto-review/script-evidence.ts +618 -0
- package/src/runtime/query/checkpoint.ts +53 -1
- package/src/runtime/query/executor.ts +159 -19
- package/src/runtime/query/finalize-turn.ts +8 -1
- package/src/runtime/query/index.ts +19 -0
- package/src/runtime/query/iteration/index.ts +167 -14
- package/src/runtime/query/iteration/phases/compaction.ts +53 -3
- package/src/runtime/query/iteration/phases/context.ts +6 -0
- package/src/runtime/query/iteration/stream-turn.ts +3 -3
- package/src/runtime/query/prelude-lease.ts +19 -0
- package/src/runtime/query/prepare-turn.ts +13 -4
- package/src/runtime/query/review-policy.ts +33 -0
- package/src/runtime/query/tooling.ts +3 -0
- package/src/runtime/query/turn/completion-audit.ts +76 -5
- package/src/runtime/query/turn/outcome-ledger.ts +633 -0
- package/src/sandbox/glob-segment.ts +48 -24
- package/src/store/session-log/index.ts +7 -0
- package/src/store/session-log/turn-outcome.ts +84 -0
- package/src/tools/builtins/bash.ts +1 -1
- package/src/tools/builtins/edit-apply.ts +26 -4
- package/src/tools/builtins/edit.ts +106 -41
- package/src/tools/builtins/glob.ts +1 -1
- package/src/tools/builtins/grep.ts +1 -1
- package/src/tools/builtins/notebook-guard.ts +76 -0
- package/src/tools/builtins/read-render.ts +12 -2
- package/src/tools/builtins/write-file.ts +12 -1
- package/src/tools/coordinator/agent.ts +31 -7
- package/src/tools/coordinator/index.ts +6 -1
- package/src/tools/coordinator/outcome-record.ts +93 -0
- package/src/tools/defineTool.ts +3 -0
- package/src/tools/file-read-tracker.ts +51 -2
- package/src/tools/untrusted-envelope.ts +23 -1
- package/src/toolsets/manager.ts +6 -10
- package/src/toolsets/validation-issues.ts +97 -0
- package/src/turn/memory-recall.ts +28 -0
- package/src/types/agent/base.ts +20 -0
- package/src/types/agent/query.ts +2 -0
- package/src/types/common/index.ts +25 -1
- package/src/types/hitl/index.ts +27 -0
- package/src/types/session/config.ts +20 -0
- package/src/types/session/events.ts +3 -3
- package/src/types/session/index.ts +1 -0
- package/src/types/session/outcome.ts +139 -0
- package/src/types/session/prepare-step.ts +2 -0
- package/src/types/session/records.ts +67 -1
- package/src/types/session/turn.ts +19 -0
- package/src/types/tool/index.ts +30 -0
|
@@ -11,6 +11,7 @@ import { fingerprintContent, staleFileError } from './content-fingerprint.js'
|
|
|
11
11
|
import { withFileMutationLock } from './file-mutation-lock.js'
|
|
12
12
|
import { fileWriteResult } from './file-write-result.js'
|
|
13
13
|
import { jsonStringEscapes } from './json-string-hint.js'
|
|
14
|
+
import { notebookRefusal } from './notebook-guard.js'
|
|
14
15
|
import { staleBeforeReview } from './stale-before-review.js'
|
|
15
16
|
|
|
16
17
|
const inputSchema = z
|
|
@@ -74,7 +75,7 @@ const modelInputSchema: Record<string, unknown> = {
|
|
|
74
75
|
export const WriteFileTool = defineTool({
|
|
75
76
|
name: 'write',
|
|
76
77
|
description:
|
|
77
|
-
'Writes a file to the local filesystem. Overwrites the existing file at the path if there is one.\n\n- If the file already exists, you must use the `read` tool on it first in this conversation, or this call will fail.\n- To modify an existing file, use `edit`: it only sends the diff and preserves the rest of the file byte-for-byte.\n- Use `write` to create a new file or to perform a deliberate full rewrite of a file you have already read.\n- Self-budget content/newStr under 12000 characters before emitting the tool call. For long content, write a smaller opening section, then use `edit` with insertLine: "end" to extend the file section by section. Chaining several `write` calls to one path loses content, because each overwrites the previous.',
|
|
78
|
+
'Writes a file to the local filesystem. Overwrites the existing file at the path if there is one.\n\n- If the file already exists, you must use the `read` tool on it first in this conversation, or this call will fail.\n- To modify an existing file, use `edit`: it only sends the diff and preserves the rest of the file byte-for-byte.\n- Use `write` to create a new file or to perform a deliberate full rewrite of a file you have already read.\n- Self-budget content/newStr under 12000 characters before emitting the tool call. For long content, write a smaller opening section, then use `edit` with insertLine: "end" to extend the file section by section. Chaining several `write` calls to one path loses content, because each overwrites the previous.\n- A `.ipynb` body must stay a valid notebook (JSON with cells, nbformat and metadata), or the write is refused.',
|
|
78
79
|
inputSchema,
|
|
79
80
|
modelInputSchema,
|
|
80
81
|
enforceModelInput: true,
|
|
@@ -154,6 +155,12 @@ export const WriteFileTool = defineTool({
|
|
|
154
155
|
}
|
|
155
156
|
const valid = parsed.data
|
|
156
157
|
const content = valid.content ?? valid.newStr ?? ''
|
|
158
|
+
// The sandbox has its own root, so its path is checked as typed. The
|
|
159
|
+
// host path is checked below, once containment has resolved symlinks.
|
|
160
|
+
if (context.sandbox) {
|
|
161
|
+
const notebook = notebookRefusal(valid.path, content)
|
|
162
|
+
if (notebook) return { success: false as const, output: '', error: notebook }
|
|
163
|
+
}
|
|
157
164
|
// Host-side containment, on the host branch only. The sandbox has its
|
|
158
165
|
// own root and its own resolver; canonicalizing a sandbox-relative
|
|
159
166
|
// path against the HOST filesystem asks a question about the wrong
|
|
@@ -161,6 +168,10 @@ export const WriteFileTool = defineTool({
|
|
|
161
168
|
const filePath = context.sandbox
|
|
162
169
|
? undefined
|
|
163
170
|
: await resolveWithinAnyReal(toolRoots(context), valid.path)
|
|
171
|
+
if (filePath !== undefined) {
|
|
172
|
+
const notebook = notebookRefusal(filePath, content)
|
|
173
|
+
if (notebook) return { success: false as const, output: '', error: notebook }
|
|
174
|
+
}
|
|
164
175
|
// The exists-check and the write are a check-then-act pair. Unlocked,
|
|
165
176
|
// two writers both see "absent", both skip the read-before-overwrite
|
|
166
177
|
// guard, and the second silently discards the first.
|
|
@@ -5,7 +5,8 @@ import type { CreateTaskOptions, TaskHandle, TaskScheduler } from '../../types/a
|
|
|
5
5
|
import type { ToolDefinition } from '../../types/tool/index.js'
|
|
6
6
|
import { jsonStringEscapes } from '../builtins/json-string-hint.js'
|
|
7
7
|
import { defineTool } from '../defineTool.js'
|
|
8
|
-
import { wrapUntrusted } from '../untrusted-envelope.js'
|
|
8
|
+
import { KERNEL_RECORD_PREFIX, wrapUntrusted } from '../untrusted-envelope.js'
|
|
9
|
+
import { describeOutcomeRecord } from './outcome-record.js'
|
|
9
10
|
import { failureLabel, taskSucceeded } from './outcome.js'
|
|
10
11
|
|
|
11
12
|
import { DELEGATION_TIMEOUT_MS } from './index.js'
|
|
@@ -169,13 +170,16 @@ export function buildAgentTool(opts: AgentToolOptions): ToolDefinition {
|
|
|
169
170
|
// of every delegation. Merged into ONE `configOverrides`
|
|
170
171
|
// rather than spread twice — the second spread would replace
|
|
171
172
|
// the first and drop the environment.
|
|
172
|
-
...(Object.keys(context.env ?? {}).length > 0 ||
|
|
173
|
+
...(Object.keys(context.env ?? {}).length > 0 ||
|
|
174
|
+
context.toolResultGuardrails ||
|
|
175
|
+
context.fileReadTracker
|
|
173
176
|
? {
|
|
174
177
|
configOverrides: {
|
|
175
178
|
...(Object.keys(context.env ?? {}).length > 0 ? { env: context.env } : {}),
|
|
176
179
|
...(context.toolResultGuardrails
|
|
177
180
|
? { toolResultGuardrails: context.toolResultGuardrails }
|
|
178
181
|
: {}),
|
|
182
|
+
...(context.fileReadTracker ? { fileReadTracker: context.fileReadTracker } : {}),
|
|
179
183
|
},
|
|
180
184
|
}
|
|
181
185
|
: {}),
|
|
@@ -222,19 +226,28 @@ export function buildAgentTool(opts: AgentToolOptions): ToolDefinition {
|
|
|
222
226
|
? JSON.stringify(completed.result.result)
|
|
223
227
|
: ''
|
|
224
228
|
|
|
229
|
+
// The kernel's own record of the child's turn, never the child's words.
|
|
230
|
+
// Written outside the marked block below, and attributed to the record.
|
|
231
|
+
const outcome = completed.result?.outcome
|
|
232
|
+
const recordLine = outcome ? describeOutcomeRecord(outcome) : undefined
|
|
233
|
+
|
|
225
234
|
if (!succeeded) {
|
|
226
|
-
|
|
227
|
-
|
|
235
|
+
// The child's words are not marked on this path, so a line of them
|
|
236
|
+
// that opens like the kernel's own would read as the kernel's.
|
|
237
|
+
const detail = neutraliseKernelLines(
|
|
238
|
+
completed.result?.lastError ?? resultText ?? '(subagent provided no failure detail)',
|
|
239
|
+
)
|
|
228
240
|
return {
|
|
229
241
|
success: false,
|
|
230
242
|
output: '',
|
|
231
|
-
error: `Subagent ${agentId} ${failureLabel(completed)}: ${detail}`,
|
|
243
|
+
error: `Subagent ${agentId} ${failureLabel(completed)}: ${detail}${recordLine ? `\n${recordLine}` : ''}`,
|
|
232
244
|
data: {
|
|
233
245
|
task_id: handle.taskId,
|
|
234
246
|
subagent_type: agentId,
|
|
235
247
|
state: completed.state,
|
|
236
248
|
status: completed.result?.status,
|
|
237
249
|
lastError: completed.result?.lastError,
|
|
250
|
+
...(outcome ? { outcome } : {}),
|
|
238
251
|
},
|
|
239
252
|
}
|
|
240
253
|
}
|
|
@@ -247,20 +260,21 @@ export function buildAgentTool(opts: AgentToolOptions): ToolDefinition {
|
|
|
247
260
|
// the result programmatically.
|
|
248
261
|
return {
|
|
249
262
|
success: true,
|
|
250
|
-
output: wrapUntrusted(
|
|
263
|
+
output: `${wrapUntrusted(
|
|
251
264
|
{
|
|
252
265
|
kind: 'agent-result',
|
|
253
266
|
attributes: { agent: agentId, task: handle.taskId },
|
|
254
267
|
provenance: `This is the output of the delegated subagent "${agentId}", not this agent's own work.`,
|
|
255
268
|
},
|
|
256
269
|
resultText || '(subagent returned no text)',
|
|
257
|
-
)
|
|
270
|
+
)}${recordLine ? `\n\n${recordLine}` : ''}`,
|
|
258
271
|
data: {
|
|
259
272
|
task_id: handle.taskId,
|
|
260
273
|
subagent_type: agentId,
|
|
261
274
|
result: resultText,
|
|
262
275
|
state: completed.state,
|
|
263
276
|
status: completed.result?.status,
|
|
277
|
+
...(outcome ? { outcome } : {}),
|
|
264
278
|
},
|
|
265
279
|
}
|
|
266
280
|
},
|
|
@@ -339,3 +353,13 @@ async function runBlockingAgentTask(
|
|
|
339
353
|
signal.removeEventListener('abort', onAbort)
|
|
340
354
|
}
|
|
341
355
|
}
|
|
356
|
+
|
|
357
|
+
/** `text` with any line that opens with the kernel record prefix quoted, so it cannot pass for the kernel's line. */
|
|
358
|
+
function neutraliseKernelLines(text: string): string {
|
|
359
|
+
return text
|
|
360
|
+
.split('\n')
|
|
361
|
+
.map((line) =>
|
|
362
|
+
line.trimStart().startsWith(KERNEL_RECORD_PREFIX) ? `> ${line.trimStart()}` : line,
|
|
363
|
+
)
|
|
364
|
+
.join('\n')
|
|
365
|
+
}
|
|
@@ -679,7 +679,9 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
679
679
|
// turned them off had that decision revert behind every
|
|
680
680
|
// delegation. One merged `configOverrides`, because a second
|
|
681
681
|
// spread of the key would replace this one.
|
|
682
|
-
...(Object.keys(_context.env ?? {}).length > 0 ||
|
|
682
|
+
...(Object.keys(_context.env ?? {}).length > 0 ||
|
|
683
|
+
_context.toolResultGuardrails ||
|
|
684
|
+
_context.fileReadTracker
|
|
683
685
|
? {
|
|
684
686
|
configOverrides: {
|
|
685
687
|
...(_context.env && Object.keys(_context.env).length > 0
|
|
@@ -688,6 +690,9 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
688
690
|
...(_context.toolResultGuardrails
|
|
689
691
|
? { toolResultGuardrails: _context.toolResultGuardrails }
|
|
690
692
|
: {}),
|
|
693
|
+
...(_context.fileReadTracker
|
|
694
|
+
? { fileReadTracker: _context.fileReadTracker }
|
|
695
|
+
: {}),
|
|
691
696
|
},
|
|
692
697
|
}
|
|
693
698
|
: {}),
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { TurnOutcome, TurnOutcomeCheck } from '../../types/session/outcome.js'
|
|
2
|
+
import { KERNEL_RECORD_PREFIX } from '../untrusted-envelope.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The kernel's own sentence about what a delegated run did, for the parent
|
|
6
|
+
* that delegated it.
|
|
7
|
+
*
|
|
8
|
+
* It states the record, not the child's account of itself, and it sits outside
|
|
9
|
+
* the marked block that holds the child's words: the parent reads it as a fact
|
|
10
|
+
* of the kernel's, the way it reads the task id. What it can say is exactly
|
|
11
|
+
* what the record can say. `checked` means a recognised check passed and the
|
|
12
|
+
* workspace did not change after it; it does not mean the check covers the
|
|
13
|
+
* change.
|
|
14
|
+
*
|
|
15
|
+
* A run that changed nothing and failed no check gets no line.
|
|
16
|
+
*
|
|
17
|
+
* The command is the child's own text, so it is shown as plain data: one line
|
|
18
|
+
* of printable ASCII with no quotes or backticks, cut short. A command that
|
|
19
|
+
* does not survive that is named as "a command" instead.
|
|
20
|
+
*/
|
|
21
|
+
export function describeOutcomeRecord(outcome: TurnOutcome): string | undefined {
|
|
22
|
+
const body = describe(outcome)
|
|
23
|
+
return body === undefined ? undefined : `${KERNEL_RECORD_PREFIX}${body}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SHOWN_COMMAND_CHARS = 60
|
|
27
|
+
|
|
28
|
+
/** A command as data inside backticks, or `undefined` when it cannot be shown without being rewritten. */
|
|
29
|
+
export function showCommand(command: string): string | undefined {
|
|
30
|
+
const trimmed = command.trim()
|
|
31
|
+
if (trimmed === '' || trimmed.length > SHOWN_COMMAND_CHARS) return undefined
|
|
32
|
+
// Printable ASCII, and none of the characters that open a quotation or a
|
|
33
|
+
// substitution in whatever reads this next.
|
|
34
|
+
if (!/^[\x20-\x7e]+$/.test(trimmed) || /[`'"$\\#]/.test(trimmed)) return undefined
|
|
35
|
+
return `\`${trimmed}\``
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function named(check: TurnOutcomeCheck): string {
|
|
39
|
+
return showCommand(check.command) ?? 'a command'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function changedPhrase(outcome: TurnOutcome): string | undefined {
|
|
43
|
+
const { total, outsideKnownTools } = outcome.changes
|
|
44
|
+
if (total > 0) return total === 1 ? '1 file changed' : `${total} files changed`
|
|
45
|
+
if (outsideKnownTools) return 'files changed, by something other than the edit tools'
|
|
46
|
+
return undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function describe(outcome: TurnOutcome): string | undefined {
|
|
50
|
+
const changed = changedPhrase(outcome)
|
|
51
|
+
const failed = outcome.checks.filter((check) => check.result === 'failed')
|
|
52
|
+
const lead = changed === undefined ? '' : `${changed}; `
|
|
53
|
+
|
|
54
|
+
switch (outcome.verdict) {
|
|
55
|
+
case 'unchanged':
|
|
56
|
+
// Nothing to say, and a line on every delegation that only read would
|
|
57
|
+
// be noise the parent learns to skip. The record is still in `data`.
|
|
58
|
+
return undefined
|
|
59
|
+
case 'unknown':
|
|
60
|
+
return 'a command ran that may have changed files, and nothing here could tell whether it did.'
|
|
61
|
+
case 'unchecked':
|
|
62
|
+
return `${lead}nothing was run to check ${total(outcome) === 1 ? 'it' : 'them'}.`
|
|
63
|
+
case 'failing': {
|
|
64
|
+
const first = failed[0]
|
|
65
|
+
if (first === undefined) return undefined
|
|
66
|
+
const more = failed.length > 1 ? ` (and ${failed.length - 1} more)` : ''
|
|
67
|
+
return `${lead}${named(first)} failed${more}.`
|
|
68
|
+
}
|
|
69
|
+
case 'checked': {
|
|
70
|
+
const passed = [...outcome.checks].reverse().find((c) => c.result === 'passed' && c.fresh)
|
|
71
|
+
return `${lead}${passed ? named(passed) : 'a check'} passed and nothing changed after it.`
|
|
72
|
+
}
|
|
73
|
+
case 'stale': {
|
|
74
|
+
const passed = [...outcome.checks].reverse().find((c) => c.result === 'passed')
|
|
75
|
+
return `${lead}${passed ? named(passed) : 'a check'} passed, then files changed again.`
|
|
76
|
+
}
|
|
77
|
+
case 'inconclusive': {
|
|
78
|
+
const masked = outcome.checks.find((c) => c.result === 'masked')
|
|
79
|
+
if (masked) {
|
|
80
|
+
const how = /(?<!\|)\|(?!\|)/.test(masked.command) ? 'the pipe' : 'the command line'
|
|
81
|
+
return `${lead}${named(masked)} ran, but ${how} hides whether it passed.`
|
|
82
|
+
}
|
|
83
|
+
const unsettled = outcome.checks.find((c) => c.result === 'unsettled')
|
|
84
|
+
if (unsettled) return `${lead}${named(unsettled)} ran, but its result was not seen.`
|
|
85
|
+
const passed = outcome.checks.find((c) => c.result === 'passed')
|
|
86
|
+
return `${lead}${passed ? named(passed) : 'a check'} passed, but it cannot be told whether files changed after it.`
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function total(outcome: TurnOutcome): number {
|
|
92
|
+
return outcome.changes.total
|
|
93
|
+
}
|
package/src/tools/defineTool.ts
CHANGED
|
@@ -41,6 +41,8 @@ export interface DefineToolOptions<S extends z.ZodType> {
|
|
|
41
41
|
executionBarrier?: boolean
|
|
42
42
|
/** The tool bounds its own output; see {@link ToolDefinition.selfLimitingOutput}. */
|
|
43
43
|
selfLimitingOutput?: boolean
|
|
44
|
+
/** A tighter per-result cap; see {@link ToolDefinition.maxOutputChars}. */
|
|
45
|
+
maxOutputChars?: number
|
|
44
46
|
tier?: string
|
|
45
47
|
/**
|
|
46
48
|
* How this tool's call and result should be shown; see
|
|
@@ -163,6 +165,7 @@ export function defineTool<S extends z.ZodType>(
|
|
|
163
165
|
...(options.selfLimitingOutput !== undefined
|
|
164
166
|
? { selfLimitingOutput: options.selfLimitingOutput }
|
|
165
167
|
: {}),
|
|
168
|
+
...(options.maxOutputChars !== undefined ? { maxOutputChars: options.maxOutputChars } : {}),
|
|
166
169
|
category: options.category,
|
|
167
170
|
permissions: options.permissions,
|
|
168
171
|
isReadOnly:
|
|
@@ -4,9 +4,48 @@ import { fingerprintContent } from './builtins/content-fingerprint.js'
|
|
|
4
4
|
/** A body larger than this is not kept; the file's change is still seen by fingerprint. */
|
|
5
5
|
const MAX_BODY_CHARS = 512 * 1024
|
|
6
6
|
const MAX_BODIES = 128
|
|
7
|
+
const MAX_AUTHORED = 1024
|
|
7
8
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* What a family of agents (a parent and every descendant) wrote through the
|
|
11
|
+
* write and edit tools: path to the fingerprint of the content last put there,
|
|
12
|
+
* and when the ledger began. Shared by the whole family, because the approval
|
|
13
|
+
* classifier asks "did one of us write this script?". It carries no read
|
|
14
|
+
* state, so sharing it can never stand in for one agent's read of a file.
|
|
15
|
+
*/
|
|
16
|
+
export interface AuthoredLedger {
|
|
17
|
+
readonly startedAt: number
|
|
18
|
+
record(key: string, content: string): void
|
|
19
|
+
fingerprint(key: string): string | undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createAuthoredLedger(): AuthoredLedger {
|
|
23
|
+
const authored = new Map<string, string>()
|
|
24
|
+
return {
|
|
25
|
+
startedAt: Date.now(),
|
|
26
|
+
record(key, content) {
|
|
27
|
+
authored.delete(key)
|
|
28
|
+
authored.set(key, fingerprintContent(content))
|
|
29
|
+
while (authored.size > MAX_AUTHORED) {
|
|
30
|
+
const oldest = authored.keys().next().value
|
|
31
|
+
if (oldest === undefined) break
|
|
32
|
+
authored.delete(oldest)
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
fingerprint: (key) => authored.get(key),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Host-owned observation ledger. Share only across turns of the same
|
|
41
|
+
* conversation and filesystem, never between agents: what a ledger holds is
|
|
42
|
+
* what ITS reader has read, and the stale-write refusal compares the disk to
|
|
43
|
+
* that. A child gets `tracker.forChild()`: its own reader record, the family's
|
|
44
|
+
* authored ledger.
|
|
45
|
+
*/
|
|
46
|
+
export function createFileReadTracker(options?: {
|
|
47
|
+
readonly authored?: AuthoredLedger
|
|
48
|
+
}): FileReadTracker {
|
|
10
49
|
const paths = new Set<string>()
|
|
11
50
|
const fingerprints = new Map<string, string>()
|
|
12
51
|
const writes = new Map<string, string>()
|
|
@@ -36,6 +75,11 @@ export function createFileReadTracker(): FileReadTracker {
|
|
|
36
75
|
// than the cap keeps no body (its change is still detectable by
|
|
37
76
|
// fingerprint), and the oldest bodies go first.
|
|
38
77
|
const bodies = new Map<string, string>()
|
|
78
|
+
// What the agent family wrote, by fingerprint. Not a body: a classifier
|
|
79
|
+
// reads the file as it is, and uses this only to say whether it is still
|
|
80
|
+
// what an agent wrote. Shared with children; everything else here is not.
|
|
81
|
+
const authored = options?.authored ?? createAuthoredLedger()
|
|
82
|
+
const keepAuthored = (key: string, content: string): void => authored.record(key, content)
|
|
39
83
|
function keepBody(key: string, content: string | undefined): void {
|
|
40
84
|
bodies.delete(key)
|
|
41
85
|
if (content === undefined || content.length > MAX_BODY_CHARS) return
|
|
@@ -63,6 +107,7 @@ export function createFileReadTracker(): FileReadTracker {
|
|
|
63
107
|
// A write supplies the whole body; any other observation without a
|
|
64
108
|
// witness may be a window of the file, which proves nothing about the rest.
|
|
65
109
|
keepBody(key, fullWriteCallId ? content : undefined)
|
|
110
|
+
if (content !== undefined && fullWriteCallId) keepAuthored(key, content)
|
|
66
111
|
if (next !== undefined && fullWriteCallId) {
|
|
67
112
|
writes.set(key, fullWriteCallId)
|
|
68
113
|
// A full body arrived whole in one call, so the chain is empty
|
|
@@ -76,6 +121,9 @@ export function createFileReadTracker(): FileReadTracker {
|
|
|
76
121
|
const MAX_WINDOWS_PER_FILE = 6
|
|
77
122
|
const MAX_WINDOWED_FILES = 512
|
|
78
123
|
return {
|
|
124
|
+
startedAt: authored.startedAt,
|
|
125
|
+
authoredFingerprint: (key) => authored.fingerprint(key),
|
|
126
|
+
forChild: () => createFileReadTracker({ authored }),
|
|
79
127
|
recordReadWindow(key, window, entry) {
|
|
80
128
|
let forFile = windows.get(key)
|
|
81
129
|
if (forFile) windows.delete(key)
|
|
@@ -127,6 +175,7 @@ export function createFileReadTracker(): FileReadTracker {
|
|
|
127
175
|
reads.delete(key)
|
|
128
176
|
fingerprints.set(key, fingerprintContent(content))
|
|
129
177
|
keepBody(key, content)
|
|
178
|
+
keepAuthored(key, content)
|
|
130
179
|
if (!chained) {
|
|
131
180
|
writes.delete(key)
|
|
132
181
|
chains.delete(key)
|
|
@@ -205,6 +205,28 @@ export function wrapUntrusted(
|
|
|
205
205
|
].join('\n')
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/**
|
|
209
|
+
* The opening words of the one line the kernel writes beside a delegated
|
|
210
|
+
* agent's answer, outside its block, to say what the run did according to its
|
|
211
|
+
* own record (`describeOutcomeRecord`).
|
|
212
|
+
*/
|
|
213
|
+
export const KERNEL_RECORD_PREFIX = 'Record of this run: '
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* `text` without a kernel record line that follows its last block.
|
|
217
|
+
*
|
|
218
|
+
* A reader of the block must still find it when the kernel has said one more
|
|
219
|
+
* thing after it. The line is the kernel's: everything a child produced is
|
|
220
|
+
* inside the block, where this module has already made its delimiter
|
|
221
|
+
* impossible to forge, so a line out here asks the reader to trust nothing
|
|
222
|
+
* that it did not already trust.
|
|
223
|
+
*/
|
|
224
|
+
function withoutKernelRecord(trimmed: string): string {
|
|
225
|
+
const at = trimmed.lastIndexOf(`\n\n${KERNEL_RECORD_PREFIX}`)
|
|
226
|
+
if (at < 0 || trimmed.indexOf('\n', at + 2) >= 0) return trimmed
|
|
227
|
+
return trimmed.slice(0, at)
|
|
228
|
+
}
|
|
229
|
+
|
|
208
230
|
/**
|
|
209
231
|
* The body of a single wrapped block, when `text` is one.
|
|
210
232
|
*
|
|
@@ -246,7 +268,7 @@ export function wrapUntrusted(
|
|
|
246
268
|
* it, which is the one failure this function must not have.
|
|
247
269
|
*/
|
|
248
270
|
export function untrustedEnvelopeBody(text: string): string | undefined {
|
|
249
|
-
const trimmed = text.trim()
|
|
271
|
+
const trimmed = withoutKernelRecord(text.trim())
|
|
250
272
|
const opening = OPENING_TAG_PATTERN.exec(trimmed)
|
|
251
273
|
if (!opening) return undefined
|
|
252
274
|
const nonce = opening[1]
|
package/src/toolsets/manager.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { type Logger, resolveLogger } from '../utils/logger.js'
|
|
|
35
35
|
import { ToolsetConflictError } from './combine.js'
|
|
36
36
|
import type { ToolSourceRef, Toolset, ToolsetAvailability } from './types.js'
|
|
37
37
|
import { toToolSourceRef, toolsetIsReady } from './types.js'
|
|
38
|
+
import { type ValidationIssueLike, describeValidationIssues } from './validation-issues.js'
|
|
38
39
|
|
|
39
40
|
export type { ToolExecutionResult }
|
|
40
41
|
|
|
@@ -984,16 +985,9 @@ Executable tool names, descriptions, and JSON input schemas are attached through
|
|
|
984
985
|
private validationFailure(
|
|
985
986
|
tool: ToolDefinition,
|
|
986
987
|
rawInput: unknown,
|
|
987
|
-
error: {
|
|
988
|
-
readonly issues: readonly {
|
|
989
|
-
readonly path: readonly PropertyKey[]
|
|
990
|
-
readonly message: string
|
|
991
|
-
}[]
|
|
992
|
-
},
|
|
988
|
+
error: { readonly issues: readonly ValidationIssueLike[] },
|
|
993
989
|
): ToolExecutionResult {
|
|
994
|
-
const errorMessage = error.issues
|
|
995
|
-
.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
|
|
996
|
-
.join('; ')
|
|
990
|
+
const errorMessage = describeValidationIssues(error.issues)
|
|
997
991
|
const isEmptyInput =
|
|
998
992
|
rawInput === null ||
|
|
999
993
|
rawInput === undefined ||
|
|
@@ -1006,7 +1000,9 @@ Executable tool names, descriptions, and JSON input schemas are attached through
|
|
|
1006
1000
|
: ''
|
|
1007
1001
|
const enrichedMessage = isEmptyInput
|
|
1008
1002
|
? `Tool "${tool.name}" was called with no arguments. ${requiredHint}${recoveryHint} Retry the call with the required parameters populated.`
|
|
1009
|
-
: `Validation failed for "${tool.name}": ${errorMessage}
|
|
1003
|
+
: `Validation failed for "${tool.name}": ${errorMessage}${
|
|
1004
|
+
errorMessage.includes('\n') ? '\n' : '. '
|
|
1005
|
+
}${requiredHint}${recoveryHint}`
|
|
1010
1006
|
this.log.error('Tool input validation failed', {
|
|
1011
1007
|
'namzu.tool.name': tool.name,
|
|
1012
1008
|
'namzu.registry.errors': errorMessage,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-input validation issues as sentences a model can act on.
|
|
3
|
+
*
|
|
4
|
+
* A zod message is written for a developer reading a stack trace:
|
|
5
|
+
* `edits: Too small: expected array to have >=1 items` names neither what to
|
|
6
|
+
* send nor why. A small model that reads it tends to repeat the same call. Each
|
|
7
|
+
* kind of issue a flat tool schema commonly produces is rewritten here to name
|
|
8
|
+
* the field and the fix; anything not recognised keeps its own message, so
|
|
9
|
+
* nothing is lost by this being a short list.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface ValidationIssueLike {
|
|
13
|
+
readonly path: readonly PropertyKey[]
|
|
14
|
+
readonly message: string
|
|
15
|
+
readonly code?: string
|
|
16
|
+
readonly origin?: string
|
|
17
|
+
readonly minimum?: number | bigint
|
|
18
|
+
readonly maximum?: number | bigint
|
|
19
|
+
readonly expected?: string
|
|
20
|
+
readonly keys?: readonly string[]
|
|
21
|
+
readonly values?: readonly unknown[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `edits.0.old_string` as `edits[0].old_string`, the way a model wrote it. */
|
|
25
|
+
export function renderIssuePath(path: readonly PropertyKey[]): string {
|
|
26
|
+
let out = ''
|
|
27
|
+
for (const part of path) {
|
|
28
|
+
if (typeof part === 'number') out += `[${part}]`
|
|
29
|
+
else out += out === '' ? String(part) : `.${String(part)}`
|
|
30
|
+
}
|
|
31
|
+
return out
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function plural(n: number | bigint, noun: string): string {
|
|
35
|
+
return `${n} ${noun}${n === 1 || n === 1n ? '' : 's'}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function received(message: string): string | undefined {
|
|
39
|
+
return /received (\w+)$/.exec(message)?.[1]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** One issue as one sentence, without a trailing period. */
|
|
43
|
+
export function describeValidationIssue(issue: ValidationIssueLike): string {
|
|
44
|
+
const field = renderIssuePath(issue.path)
|
|
45
|
+
const name = field === '' ? 'The input' : `"${field}"`
|
|
46
|
+
const min = issue.minimum
|
|
47
|
+
switch (issue.code) {
|
|
48
|
+
case 'too_small':
|
|
49
|
+
if (issue.origin === 'string' && min !== undefined && Number(min) <= 1) {
|
|
50
|
+
return `${name} must not be empty; send the text itself`
|
|
51
|
+
}
|
|
52
|
+
if (issue.origin === 'string' && min !== undefined) {
|
|
53
|
+
return `${name} is too short; it needs at least ${plural(min, 'character')}`
|
|
54
|
+
}
|
|
55
|
+
if (issue.origin === 'array' && min !== undefined) {
|
|
56
|
+
return `${name} is a list with too few entries; it needs at least ${plural(min, 'entry').replace('entrys', 'entries')}, or leave the field out`
|
|
57
|
+
}
|
|
58
|
+
if (min !== undefined) return `${name} is too small; the smallest accepted is ${min}`
|
|
59
|
+
break
|
|
60
|
+
case 'too_big':
|
|
61
|
+
if (issue.maximum !== undefined) {
|
|
62
|
+
return `${name} is too large; the largest accepted is ${issue.maximum}`
|
|
63
|
+
}
|
|
64
|
+
break
|
|
65
|
+
case 'invalid_type': {
|
|
66
|
+
const got = received(issue.message)
|
|
67
|
+
if (issue.expected !== undefined && got === 'undefined') {
|
|
68
|
+
return `${name} is missing; send it as ${withArticle(issue.expected)}`
|
|
69
|
+
}
|
|
70
|
+
if (issue.expected !== undefined && got !== undefined) {
|
|
71
|
+
return `${name} must be ${withArticle(issue.expected)}, not ${got === 'null' ? 'null' : `a ${got}`}`
|
|
72
|
+
}
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
case 'unrecognized_keys':
|
|
76
|
+
if (issue.keys !== undefined && issue.keys.length > 0) {
|
|
77
|
+
const list = issue.keys.map((key) => `"${key}"`).join(', ')
|
|
78
|
+
return `${field === '' ? '' : `${name} has `}${issue.keys.length === 1 ? 'a field' : 'fields'} this tool does not take: ${list}. Remove ${issue.keys.length === 1 ? 'it' : 'them'}`
|
|
79
|
+
}
|
|
80
|
+
break
|
|
81
|
+
case 'invalid_value':
|
|
82
|
+
if (issue.values !== undefined && issue.values.length > 0) {
|
|
83
|
+
return `${name} must be one of ${issue.values.map((v) => JSON.stringify(v)).join(', ')}`
|
|
84
|
+
}
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
const message = issue.message.replace(/\.+$/, '')
|
|
88
|
+
return field === '' ? message : `${field}: ${message}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function withArticle(type: string): string {
|
|
92
|
+
return /^[aeiou]/.test(type) ? `an ${type}` : `a ${type}`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function describeValidationIssues(issues: readonly ValidationIssueLike[]): string {
|
|
96
|
+
return issues.map(describeValidationIssue).join('; ')
|
|
97
|
+
}
|
|
@@ -28,6 +28,19 @@ export interface MemoryRecallOptions {
|
|
|
28
28
|
* aged memory names before relying on it. Default one day.
|
|
29
29
|
*/
|
|
30
30
|
readonly ageNoticeAfterMs?: number
|
|
31
|
+
/**
|
|
32
|
+
* Asked once for each record about to be shown (at most `maxMemories` a
|
|
33
|
+
* request), with its id and metadata, never its body. A returned string is
|
|
34
|
+
* added to the record's line as `caution` and to the event's entry, so the
|
|
35
|
+
* model reads that the record may no longer hold; the record is still
|
|
36
|
+
* shown. A host uses it to check what a record names (a file, say) against
|
|
37
|
+
* the project as it is now. It runs inside the recall deadline, so it must
|
|
38
|
+
* be cheap; a throw, or a deadline passing, means no caution.
|
|
39
|
+
*/
|
|
40
|
+
readonly caution?: (record: {
|
|
41
|
+
readonly id: string
|
|
42
|
+
readonly metadata?: Readonly<Record<string, unknown>>
|
|
43
|
+
}) => string | undefined | Promise<string | undefined>
|
|
31
44
|
/** Clock for ages; defaults to `Date.now`. */
|
|
32
45
|
readonly now?: () => number
|
|
33
46
|
/**
|
|
@@ -593,6 +606,19 @@ export function createMemoryRecallStep(options: MemoryRecallOptions): PrepareSte
|
|
|
593
606
|
)
|
|
594
607
|
)
|
|
595
608
|
continue
|
|
609
|
+
let caution: string | undefined
|
|
610
|
+
if (options.caution) {
|
|
611
|
+
try {
|
|
612
|
+
caution = await options.caution({
|
|
613
|
+
id: entry.id,
|
|
614
|
+
...(full.metadata ? { metadata: full.metadata } : {}),
|
|
615
|
+
})
|
|
616
|
+
} catch {
|
|
617
|
+
// A check that failed says nothing about the record.
|
|
618
|
+
}
|
|
619
|
+
if (expired) break
|
|
620
|
+
if (caution !== undefined) caution = clipped(caution, 200)
|
|
621
|
+
}
|
|
596
622
|
const aged = now() - entry.updatedAt > ageNoticeAfterMs
|
|
597
623
|
// The first aged record pays for the notice it brings with it, so
|
|
598
624
|
// a block of fresh records spends nothing on it.
|
|
@@ -614,6 +640,7 @@ export function createMemoryRecallStep(options: MemoryRecallOptions): PrepareSte
|
|
|
614
640
|
),
|
|
615
641
|
updatedAt: entry.updatedAt,
|
|
616
642
|
...(aged ? { age: describeMemoryAge(entry.updatedAt, now()) } : {}),
|
|
643
|
+
...(caution ? { caution } : {}),
|
|
617
644
|
sourceTurn:
|
|
618
645
|
typeof full.metadata?.turnId === 'string'
|
|
619
646
|
? clipped(full.metadata.turnId, 80)
|
|
@@ -632,6 +659,7 @@ export function createMemoryRecallStep(options: MemoryRecallOptions): PrepareSte
|
|
|
632
659
|
title: entry.title,
|
|
633
660
|
origin: memoryOrigin(full.metadata),
|
|
634
661
|
updatedAt: entry.updatedAt,
|
|
662
|
+
...(caution ? { caution } : {}),
|
|
635
663
|
// Without a selector the words chose it and there is nothing to add;
|
|
636
664
|
// with one, say so when it did not.
|
|
637
665
|
...(reasons.has(entry.id) || options.select
|
package/src/types/agent/base.ts
CHANGED
|
@@ -160,6 +160,20 @@ export interface BaseAgentConfig {
|
|
|
160
160
|
*/
|
|
161
161
|
toolResultGuardrails?: readonly import('../guardrail/index.js').ToolResultGuardrailSpec[]
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* This agent's own file observation ledger.
|
|
165
|
+
*
|
|
166
|
+
* A delegated child is a fresh turn with its own executor. It starts with
|
|
167
|
+
* its own read record (`FileReadTracker.forChild`), so a write by its
|
|
168
|
+
* parent or a sibling still makes what it read stale, but it shares the
|
|
169
|
+
* delegating turn's record of what the agents wrote: a script its parent
|
|
170
|
+
* wrote, run by the child, is the session's own, so the auto-mode
|
|
171
|
+
* classifier reads it. Stamped by `AgentManager` from
|
|
172
|
+
* `configOverrides.fileReadTracker`, which the delegation tools fill from
|
|
173
|
+
* the turn's own `ToolContext`.
|
|
174
|
+
*/
|
|
175
|
+
fileReadTracker?: import('../tool/index.js').FileReadTracker
|
|
176
|
+
|
|
163
177
|
/**
|
|
164
178
|
* Tools this turn may NOT use, subtracted from whatever it would
|
|
165
179
|
* otherwise have.
|
|
@@ -232,6 +246,10 @@ export interface BaseAgentConfig {
|
|
|
232
246
|
reasoning?: import('../session/config.js').TurnConfig['reasoning']
|
|
233
247
|
/** Ask once for a check of edits before a turn ends; see {@link import('../session/config.js').TurnConfig.completionAudit}. */
|
|
234
248
|
completionAudit?: boolean
|
|
249
|
+
/** Consecutive end-of-turn hook refusals honoured; see {@link import('../session/config.js').TurnConfig.maxHookRefusals}. */
|
|
250
|
+
maxHookRefusals?: number
|
|
251
|
+
/** The outcome record's hooks; see {@link import('../session/config.js').TurnConfig.outcome}. */
|
|
252
|
+
outcome?: import('../session/config.js').TurnConfig['outcome']
|
|
235
253
|
|
|
236
254
|
/**
|
|
237
255
|
* Deduplicate a retried invocation instead of running it twice.
|
|
@@ -411,6 +429,8 @@ export interface BaseAgentResult {
|
|
|
411
429
|
durationMs: number
|
|
412
430
|
messages: Message[]
|
|
413
431
|
result?: string
|
|
432
|
+
/** What the turn changed and what checked it; see {@link import('../session/turn.js').Turn.outcome}. */
|
|
433
|
+
outcome?: import('../session/outcome.js').TurnOutcome
|
|
414
434
|
/**
|
|
415
435
|
* The schema-validated answer, when the turn was configured to produce one.
|
|
416
436
|
*
|
package/src/types/agent/query.ts
CHANGED
|
@@ -67,6 +67,8 @@ export interface QueryAgentConfig extends BaseAgentConfig {
|
|
|
67
67
|
sandboxTeardownTimeoutMs?: number
|
|
68
68
|
/** See {@link QueryParams.outsideRootAccess}. Default `'refuse'`. */
|
|
69
69
|
outsideRootAccess?: 'refuse' | 'review'
|
|
70
|
+
/** See {@link QueryParams.outsideRootRefusal}. */
|
|
71
|
+
outsideRootRefusal?: string
|
|
70
72
|
/** See {@link QueryParams.sandboxEscape}. Default `'refuse'`. */
|
|
71
73
|
sandboxEscape?: 'refuse' | 'review'
|
|
72
74
|
|