@namzu/sdk 37.0.0 → 38.1.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 +86 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +1 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +2 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +16 -1
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/config/runtime.d.ts +16 -6
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +2 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/contracts/api.d.ts +1 -1
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +14 -9
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/plan/lifecycle.d.ts +1 -1
- package/dist/manager/plan/lifecycle.d.ts.map +1 -1
- package/dist/manager/plan/lifecycle.js +14 -12
- package/dist/manager/plan/lifecycle.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +2 -2
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +11 -5
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/capabilities.d.ts +2 -0
- package/dist/provider/capabilities.d.ts.map +1 -1
- package/dist/provider/capabilities.js +14 -0
- package/dist/provider/capabilities.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +7 -3
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/idle-timeout.d.ts.map +1 -1
- package/dist/provider/idle-timeout.js +5 -0
- package/dist/provider/idle-timeout.js.map +1 -1
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +5 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +1 -0
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -0
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -0
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +1 -0
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +1 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +1 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/run/token-budget.d.ts +10 -4
- package/dist/run/token-budget.d.ts.map +1 -1
- package/dist/run/token-budget.js +45 -14
- package/dist/run/token-budget.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +3 -3
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +24 -16
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +2 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +1 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +2 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +23 -2
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +11 -1
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/observation-context.d.ts +10 -0
- package/dist/runtime/query/observation-context.d.ts.map +1 -0
- package/dist/runtime/query/observation-context.js +84 -0
- package/dist/runtime/query/observation-context.js.map +1 -0
- package/dist/runtime/query/request-context.d.ts +30 -0
- package/dist/runtime/query/request-context.d.ts.map +1 -0
- package/dist/runtime/query/request-context.js +76 -0
- package/dist/runtime/query/request-context.js.map +1 -0
- package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
- package/dist/runtime/query/request-rich-content.js +6 -3
- package/dist/runtime/query/request-rich-content.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +3 -0
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +4 -1
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +1 -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/sandbox/file-walk-program.d.ts.map +1 -1
- package/dist/sandbox/file-walk-program.js +8 -0
- package/dist/sandbox/file-walk-program.js.map +1 -1
- package/dist/sandbox/file-walk.d.ts.map +1 -1
- package/dist/sandbox/file-walk.js +9 -0
- package/dist/sandbox/file-walk.js.map +1 -1
- package/dist/scheduler/completion-inbox.d.ts.map +1 -1
- package/dist/scheduler/completion-inbox.js +3 -0
- package/dist/scheduler/completion-inbox.js.map +1 -1
- package/dist/scheduler/local.d.ts.map +1 -1
- package/dist/scheduler/local.js +2 -0
- package/dist/scheduler/local.js.map +1 -1
- package/dist/store/run/token-budget-disk.js +3 -0
- package/dist/store/run/token-budget-disk.js.map +1 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -0
- package/dist/testing.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +12 -26
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/file-write-result.d.ts +4 -0
- package/dist/tools/builtins/file-write-result.d.ts.map +1 -0
- package/dist/tools/builtins/file-write-result.js +72 -0
- package/dist/tools/builtins/file-write-result.js.map +1 -0
- package/dist/tools/builtins/glob.d.ts.map +1 -1
- package/dist/tools/builtins/glob.js +8 -1
- package/dist/tools/builtins/glob.js.map +1 -1
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +10 -0
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/job.d.ts.map +1 -1
- package/dist/tools/builtins/job.js +6 -0
- package/dist/tools/builtins/job.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +10 -0
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +30 -44
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +3 -0
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts +4 -0
- package/dist/tools/file-read-tracker.d.ts.map +1 -0
- package/dist/tools/file-read-tracker.js +16 -0
- package/dist/tools/file-read-tracker.js.map +1 -0
- package/dist/tools/task/list.js +2 -2
- package/dist/tools/task/list.js.map +1 -1
- package/dist/types/agent/reactive.d.ts +4 -0
- package/dist/types/agent/reactive.d.ts.map +1 -1
- package/dist/types/agent/scheduler.d.ts +3 -0
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +3 -0
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/plugin/index.d.ts +7 -0
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/provider/chat.d.ts +4 -0
- package/dist/types/provider/chat.d.ts.map +1 -1
- package/dist/types/provider/config.d.ts +2 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/interface.d.ts +2 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/provider/stream.d.ts +6 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/run/config.d.ts +2 -0
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +13 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/sandbox/index.d.ts +37 -3
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +4 -0
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +1 -0
- package/src/bridge/a2a/mapper.ts +2 -0
- package/src/bridge/sse/mapper.ts +16 -1
- package/src/config/runtime.ts +2 -0
- package/src/contracts/api.ts +1 -0
- package/src/manager/agent/lifecycle.ts +16 -9
- package/src/manager/plan/lifecycle.ts +15 -13
- package/src/prompt/coding-agent-doctrine.ts +11 -5
- package/src/provider/capabilities.ts +21 -0
- package/src/provider/fallback.ts +14 -3
- package/src/provider/idle-timeout.ts +6 -0
- package/src/provider/retry.ts +6 -0
- package/src/public-runtime.ts +2 -0
- package/src/public-tools.ts +2 -0
- package/src/public-types.ts +6 -0
- package/src/run/reporter.ts +1 -0
- package/src/run/token-budget.ts +51 -14
- package/src/runtime/query/executor.ts +23 -15
- package/src/runtime/query/index.ts +3 -0
- package/src/runtime/query/iteration/index.ts +28 -2
- package/src/runtime/query/iteration/stream-turn.ts +14 -1
- package/src/runtime/query/observation-context.ts +86 -0
- package/src/runtime/query/request-context.ts +103 -0
- package/src/runtime/query/request-rich-content.ts +8 -3
- package/src/runtime/query/review-policy.ts +7 -1
- package/src/runtime/query/tooling.ts +2 -0
- package/src/sandbox/file-walk-program.ts +8 -0
- package/src/sandbox/file-walk.ts +9 -0
- package/src/scheduler/completion-inbox.ts +3 -0
- package/src/scheduler/local.ts +2 -0
- package/src/store/run/token-budget-disk.ts +3 -0
- package/src/testing.ts +2 -0
- package/src/tools/builtins/edit.ts +12 -27
- package/src/tools/builtins/file-write-result.ts +84 -0
- package/src/tools/builtins/glob.ts +7 -1
- package/src/tools/builtins/grep.ts +10 -0
- package/src/tools/builtins/job.ts +8 -0
- package/src/tools/builtins/read-file.ts +10 -0
- package/src/tools/builtins/write-file.ts +39 -44
- package/src/tools/coordinator/index.ts +3 -0
- package/src/tools/file-read-tracker.ts +16 -0
- package/src/tools/task/list.ts +2 -2
- package/src/types/agent/reactive.ts +2 -0
- package/src/types/agent/scheduler.ts +4 -0
- package/src/types/agent/task.ts +4 -0
- package/src/types/plugin/index.ts +10 -0
- package/src/types/provider/chat.ts +2 -0
- package/src/types/provider/config.ts +2 -0
- package/src/types/provider/interface.ts +3 -0
- package/src/types/provider/stream.ts +7 -0
- package/src/types/run/config.ts +2 -0
- package/src/types/run/events.ts +13 -0
- package/src/types/sandbox/index.ts +43 -4
- package/src/types/tool/presentation.ts +4 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import type { Message, MessageRole } from '../../types/message/index.js'
|
|
3
|
+
|
|
4
|
+
/** A content block present at the SDK's provider-input boundary, not a file freshness claim. */
|
|
5
|
+
export interface RequestContextPart {
|
|
6
|
+
readonly messageIndex: number
|
|
7
|
+
readonly partIndex: number
|
|
8
|
+
readonly role: MessageRole
|
|
9
|
+
readonly kind: 'text' | 'image' | 'document' | 'stored' | 'tool-call'
|
|
10
|
+
readonly digest: string
|
|
11
|
+
readonly toolCallId?: string
|
|
12
|
+
readonly isError?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RequestContextSnapshot {
|
|
16
|
+
readonly boundary: 'provider-input'
|
|
17
|
+
readonly parts: readonly RequestContextPart[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RequestContextChange {
|
|
21
|
+
/** Parts from the earlier snapshot no longer present, including replaced content. */
|
|
22
|
+
readonly removed: readonly RequestContextPart[]
|
|
23
|
+
/** Parts in the new snapshot not present before. */
|
|
24
|
+
readonly added: readonly RequestContextPart[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function canonicalJson(value: unknown): string {
|
|
28
|
+
return JSON.stringify(value, (_key, item: unknown) => {
|
|
29
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return item
|
|
30
|
+
return Object.fromEntries(Object.entries(item).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Inspect supplied messages after compaction and request projection. No content
|
|
36
|
+
* is retained in the snapshot. A digest identifies exact blocks, not semantic
|
|
37
|
+
* knowledge, token counts, file versions or provider-private reasoning replay.
|
|
38
|
+
*/
|
|
39
|
+
export function snapshotRequestContext(messages: readonly Message[]): RequestContextSnapshot {
|
|
40
|
+
const parts: RequestContextPart[] = []
|
|
41
|
+
for (const [messageIndex, message] of messages.entries()) {
|
|
42
|
+
let partIndex = 0
|
|
43
|
+
const add = (kind: RequestContextPart['kind'], payload: unknown, toolCallId?: string) => {
|
|
44
|
+
parts.push(
|
|
45
|
+
Object.freeze({
|
|
46
|
+
messageIndex,
|
|
47
|
+
partIndex: partIndex++,
|
|
48
|
+
role: message.role,
|
|
49
|
+
kind,
|
|
50
|
+
digest: createHash('sha256').update(canonicalJson(payload)).digest('hex'),
|
|
51
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
52
|
+
...(message.role === 'tool' ? { isError: message.isError === true } : {}),
|
|
53
|
+
}),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
const callId = message.role === 'tool' ? message.toolCallId : undefined
|
|
57
|
+
if (typeof message.content === 'string') add('text', message.content, callId)
|
|
58
|
+
else if (Array.isArray(message.content)) {
|
|
59
|
+
for (const block of message.content) {
|
|
60
|
+
if (block.type === 'text') add('text', block.text, callId)
|
|
61
|
+
else add(block.type, block, callId)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (message.role === 'user') {
|
|
65
|
+
for (const attachment of message.attachments ?? []) {
|
|
66
|
+
add(attachment.type ?? 'image', attachment)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (message.role === 'assistant') {
|
|
70
|
+
for (const call of message.toolCalls ?? []) {
|
|
71
|
+
add('tool-call', [call.function.name, call.function.arguments], call.id)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return Object.freeze({ boundary: 'provider-input', parts: Object.freeze(parts) })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const key = (part: RequestContextPart): string =>
|
|
79
|
+
JSON.stringify([part.role, part.kind, part.toolCallId, part.isError, part.digest])
|
|
80
|
+
|
|
81
|
+
/** Compare occurrences, not sets: removing one of two identical blocks is a removal. */
|
|
82
|
+
export function diffRequestContext(
|
|
83
|
+
previous: RequestContextSnapshot,
|
|
84
|
+
next: RequestContextSnapshot,
|
|
85
|
+
): RequestContextChange {
|
|
86
|
+
const unmatched = new Map<string, RequestContextPart[]>()
|
|
87
|
+
for (const part of previous.parts) {
|
|
88
|
+
const identity = key(part)
|
|
89
|
+
const occurrences = unmatched.get(identity) ?? []
|
|
90
|
+
occurrences.push(part)
|
|
91
|
+
unmatched.set(identity, occurrences)
|
|
92
|
+
}
|
|
93
|
+
const added: RequestContextPart[] = []
|
|
94
|
+
for (const part of next.parts) {
|
|
95
|
+
const occurrences = unmatched.get(key(part))
|
|
96
|
+
if (occurrences?.length) occurrences.pop()
|
|
97
|
+
else added.push(part)
|
|
98
|
+
}
|
|
99
|
+
const removed = [...unmatched.values()]
|
|
100
|
+
.flat()
|
|
101
|
+
.sort((a, b) => a.messageIndex - b.messageIndex || a.partIndex - b.partIndex)
|
|
102
|
+
return Object.freeze({ removed: Object.freeze(removed), added: Object.freeze(added) })
|
|
103
|
+
}
|
|
@@ -45,20 +45,25 @@ export interface RequestImageIdentity {
|
|
|
45
45
|
const userMarker = (kind: RichKind): string =>
|
|
46
46
|
`[${kind} omitted from this model request to keep the accumulated rich-content payload within its configured size limit; attach it again in a new message if it is still needed.]`
|
|
47
47
|
|
|
48
|
+
// Losing an observation does not undo the action that produced it. This
|
|
49
|
+
// projection knows the result bytes, not whether repeating its tool is safe.
|
|
50
|
+
const toolRecovery =
|
|
51
|
+
'The original result is retained in conversation history, but this content is not available in the current model request. Recover it from an available saved artifact or a read-only observation if needed. Do not repeat a state-changing action merely to recover its output.'
|
|
52
|
+
|
|
48
53
|
const toolMarker = (kind: RichKind): string =>
|
|
49
|
-
`[${kind} omitted from this model request to keep the accumulated rich-content payload within its configured size limit
|
|
54
|
+
`[${kind} omitted from this model request to keep the accumulated rich-content payload within its configured size limit. ${toolRecovery}]`
|
|
50
55
|
|
|
51
56
|
const rejectedUserImageMarker = (): string =>
|
|
52
57
|
'[image omitted from this model request because the provider rejected this image; attach a corrected image in a new message if it is still needed.]'
|
|
53
58
|
|
|
54
59
|
const rejectedToolImageMarker = (): string =>
|
|
55
|
-
|
|
60
|
+
`[image omitted from this model request because the provider rejected this image. ${toolRecovery} Correct the image source before presenting it again.]`
|
|
56
61
|
|
|
57
62
|
const invalidUserImageMarker = (): string =>
|
|
58
63
|
'[image omitted from this model request because its encoded bytes are not a complete supported raster matching the declared media type; attach a corrected image in a new message if it is still needed.]'
|
|
59
64
|
|
|
60
65
|
const invalidToolImageMarker = (): string =>
|
|
61
|
-
|
|
66
|
+
`[image omitted from this model request because the producing tool returned encoded bytes that are not a complete supported raster matching the declared media type. ${toolRecovery} Correct the image source before presenting it again.]`
|
|
62
67
|
|
|
63
68
|
function assertInlineAttachment(
|
|
64
69
|
attachment: MessageAttachment,
|
|
@@ -25,6 +25,7 @@ import type { ToolRegistry } from '../../registry/tool/execute.js'
|
|
|
25
25
|
import { isTrustedReadOnly } from '../../tools/trusted-read-only.js'
|
|
26
26
|
import type { HITLResumeDecision, ResumeHandler, ToolCallSummary } from '../../types/hitl/index.js'
|
|
27
27
|
import type { ApprovalPolicy } from '../../types/hitl/policy.js'
|
|
28
|
+
import type { RunId } from '../../types/ids/index.js'
|
|
28
29
|
import { PLAN_MODE_REFUSAL } from '../../types/permission/index.js'
|
|
29
30
|
|
|
30
31
|
export type ReviewMode =
|
|
@@ -141,6 +142,8 @@ export function batchNeedsReview(
|
|
|
141
142
|
|
|
142
143
|
/** The batch a person is asked about. */
|
|
143
144
|
export interface ToolReviewRequest {
|
|
145
|
+
/** Originating run, preserved by createReviewHandler for host attribution. */
|
|
146
|
+
readonly runId?: RunId
|
|
144
147
|
readonly toolCalls: readonly ToolCallSummary[]
|
|
145
148
|
}
|
|
146
149
|
|
|
@@ -204,7 +207,10 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
|
|
|
204
207
|
if (mode === 'auto' || !prompt || remembered.all) {
|
|
205
208
|
return { action: 'approve_tools' }
|
|
206
209
|
}
|
|
207
|
-
const answer = await prompt({
|
|
210
|
+
const answer = await prompt({
|
|
211
|
+
runId: request.runId,
|
|
212
|
+
toolCalls: request.toolCalls,
|
|
213
|
+
})
|
|
208
214
|
switch (answer.kind) {
|
|
209
215
|
case 'approve':
|
|
210
216
|
return { action: 'approve_tools' }
|
|
@@ -20,6 +20,7 @@ import { ToolExecutor } from './executor.js'
|
|
|
20
20
|
export type EmitEvent = (event: RunEvent) => Promise<void>
|
|
21
21
|
|
|
22
22
|
export interface ToolingBootstrapConfig {
|
|
23
|
+
fileReadTracker?: import('../../types/tool/index.js').FileReadTracker
|
|
23
24
|
tools: ToolRegistryContract
|
|
24
25
|
runId: RunId
|
|
25
26
|
workingDirectory: string
|
|
@@ -67,6 +68,7 @@ export class ToolingBootstrap {
|
|
|
67
68
|
return new ToolExecutor(
|
|
68
69
|
{
|
|
69
70
|
tools: config.tools,
|
|
71
|
+
...(config.fileReadTracker ? { fileReadTracker: config.fileReadTracker } : {}),
|
|
70
72
|
runId: config.runId,
|
|
71
73
|
workingDirectory: config.workingDirectory,
|
|
72
74
|
...(config.additionalDirectories?.length
|
|
@@ -51,6 +51,14 @@ async function run() {
|
|
|
51
51
|
try { canonicalRoot = await fs.realpath(plan.root); }
|
|
52
52
|
catch (error) { if (missing(error)) return; throw error; }
|
|
53
53
|
if (canonicalRoot !== path.resolve(plan.root)) throw new Error('File walk root follows a symbolic link; use its authorized real directory');
|
|
54
|
+
const rootInfo = await fs.lstat(plan.root);
|
|
55
|
+
if (rootInfo.isFile()) {
|
|
56
|
+
visit(++visited);
|
|
57
|
+
if (matchers.some(matcher => matcher.test(path.basename(plan.root)))) {
|
|
58
|
+
await write({type:'entry', path:plan.root, size:rootInfo.size});
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
54
62
|
let start = plan.root;
|
|
55
63
|
for (const part of plan.prefix) {
|
|
56
64
|
start = path.join(start, part);
|
package/src/sandbox/file-walk.ts
CHANGED
|
@@ -227,6 +227,15 @@ async function* executeWalk(
|
|
|
227
227
|
}
|
|
228
228
|
if (canonicalRoot !== path.resolve(plan.root))
|
|
229
229
|
throw new Error('File walk root follows a symbolic link; use its authorized real directory')
|
|
230
|
+
// A named regular file is a one-entry search, not an empty directory.
|
|
231
|
+
const rootInfo = await wait(fs.lstat(plan.root))
|
|
232
|
+
check()
|
|
233
|
+
if (rootInfo.isFile()) {
|
|
234
|
+
noteVisit()
|
|
235
|
+
if (matchers.some((matcher) => matcher.test(path.basename(plan.root))))
|
|
236
|
+
yield { path: plan.root, size: rootInfo.size }
|
|
237
|
+
return
|
|
238
|
+
}
|
|
230
239
|
let start = plan.root
|
|
231
240
|
// Never follow a static-prefix symlink to speed up a pattern: traversal
|
|
232
241
|
// and its optimized starting point must have the same link policy.
|
|
@@ -464,6 +464,9 @@ export function formatCompletionNotification(handles: readonly TaskHandle[]): st
|
|
|
464
464
|
`task_id: ${handle.taskId}`,
|
|
465
465
|
`agent: ${handle.agentId}`,
|
|
466
466
|
`state: ${handle.state}`,
|
|
467
|
+
...(handle.state === 'completed' && run?.stopReason && run.stopReason !== 'end_turn'
|
|
468
|
+
? ['outcome: incomplete — execution stopped; this does not establish task completion.']
|
|
469
|
+
: []),
|
|
467
470
|
...(handle.result?.stopReason ? [`stop_reason: ${handle.result.stopReason}`] : []),
|
|
468
471
|
...(durationMs !== undefined ? [`duration_ms: ${durationMs}`] : []),
|
|
469
472
|
'',
|
package/src/scheduler/local.ts
CHANGED
|
@@ -112,6 +112,8 @@ export class LocalTaskScheduler implements TaskScheduler {
|
|
|
112
112
|
{
|
|
113
113
|
agentId: options.agentId,
|
|
114
114
|
beforeStart: options.beforeStart,
|
|
115
|
+
...(options.planId ? { planId: options.planId } : {}),
|
|
116
|
+
...(options.planStepId ? { planStepId: options.planStepId } : {}),
|
|
115
117
|
input: {
|
|
116
118
|
messages: [createUserMessage(options.prompt)],
|
|
117
119
|
workingDirectory: options.workingDirectory,
|
|
@@ -227,6 +227,9 @@ function assertSameRoot(before: TokenBudgetSnapshot, after: TokenBudgetSnapshot)
|
|
|
227
227
|
!current ||
|
|
228
228
|
current.accountId !== previous.accountId ||
|
|
229
229
|
current.runId !== previous.runId ||
|
|
230
|
+
(previous.unresolved === true &&
|
|
231
|
+
current.unresolved !== true &&
|
|
232
|
+
(active.has(previous.id) || current.usage === undefined)) ||
|
|
230
233
|
(reconciled && current.usage === undefined) ||
|
|
231
234
|
!usageAtLeast(current.usage, previous.usage)
|
|
232
235
|
) {
|
package/src/testing.ts
CHANGED
|
@@ -362,12 +362,18 @@ export const EditTool = defineTool({
|
|
|
362
362
|
return withFileMutationLock(lockKey, async () => {
|
|
363
363
|
if (context.sandbox) {
|
|
364
364
|
const buffer = await context.sandbox.readFile(parsed.data.path)
|
|
365
|
-
const
|
|
365
|
+
const content = buffer.toString('utf-8')
|
|
366
|
+
const seen = context.fileReadTracker?.fingerprint?.(parsed.data.path)
|
|
367
|
+
if (seen !== undefined && seen !== fingerprintContent(content)) {
|
|
368
|
+
return { success: false as const, output: '', error: staleFileError(parsed.data.path) }
|
|
369
|
+
}
|
|
370
|
+
const result = applyEdit(content, normalized.operations)
|
|
366
371
|
if (!result.success) {
|
|
367
372
|
return { success: false as const, output: '', error: result.error }
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
await context.sandbox.writeFile(parsed.data.path, result.content)
|
|
376
|
+
context.fileReadTracker?.recordRead(parsed.data.path, result.content)
|
|
371
377
|
return {
|
|
372
378
|
success: true as const,
|
|
373
379
|
output: `Edited ${parsed.data.path}: ${result.replacements} replacement(s) [sandboxed]`,
|
|
@@ -378,33 +384,12 @@ export const EditTool = defineTool({
|
|
|
378
384
|
const hostPath = filePath as string
|
|
379
385
|
const content = await readFile(hostPath, 'utf-8')
|
|
380
386
|
|
|
381
|
-
const
|
|
382
|
-
if (
|
|
383
|
-
|
|
384
|
-
// situations produce that, and the difference is the whole
|
|
385
|
-
// value of the fingerprint:
|
|
386
|
-
//
|
|
387
|
-
// The file is as the agent left it, and the anchor is simply
|
|
388
|
-
// wrong — "not found, match the whitespace exactly" is the
|
|
389
|
-
// right thing to say.
|
|
390
|
-
//
|
|
391
|
-
// Or the file moved after the read — a person in an editor,
|
|
392
|
-
// another process, a second agent — and the same message is a
|
|
393
|
-
// lie that sends the agent to re-check text that was never the
|
|
394
|
-
// problem, so it retries the identical edit against the same
|
|
395
|
-
// moved file.
|
|
396
|
-
//
|
|
397
|
-
// Deliberately NOT a gate on the successful path. An anchor
|
|
398
|
-
// that still matches uniquely is well defined however much
|
|
399
|
-
// changed elsewhere in the file, and refusing there would
|
|
400
|
-
// reject safe edits every time anyone touched an unrelated
|
|
401
|
-
// line.
|
|
402
|
-
const seen = context.fileReadTracker?.fingerprint?.(hostPath)
|
|
403
|
-
if (seen !== undefined && seen !== fingerprintContent(content)) {
|
|
404
|
-
return { success: false as const, output: '', error: staleFileError(hostPath) }
|
|
405
|
-
}
|
|
406
|
-
return { success: false as const, output: '', error: result.error }
|
|
387
|
+
const seen = context.fileReadTracker?.fingerprint?.(hostPath)
|
|
388
|
+
if (seen !== undefined && seen !== fingerprintContent(content)) {
|
|
389
|
+
return { success: false as const, output: '', error: staleFileError(hostPath) }
|
|
407
390
|
}
|
|
391
|
+
const result = applyEdit(content, normalized.operations)
|
|
392
|
+
if (!result.success) return { success: false as const, output: '', error: result.error }
|
|
408
393
|
|
|
409
394
|
// Temp file, fsync, rename — a reader sees the old body or the new
|
|
410
395
|
// one, never a half-written one. A plain `writeFile` that fails
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import type { ToolResult } from '../../types/tool/index.js'
|
|
3
|
+
|
|
4
|
+
/** A final newline terminates a line; it does not add an empty line. */
|
|
5
|
+
function lines(text: string): string[] {
|
|
6
|
+
if (text === '') return []
|
|
7
|
+
const result = text.split('\n')
|
|
8
|
+
if (result.at(-1) === '') result.pop()
|
|
9
|
+
return result
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** null means the backend could not establish the previous state. */
|
|
13
|
+
export function fileWriteResult(
|
|
14
|
+
path: string,
|
|
15
|
+
before: string | undefined | null,
|
|
16
|
+
after: string,
|
|
17
|
+
sandboxed = false,
|
|
18
|
+
): ToolResult {
|
|
19
|
+
const operation =
|
|
20
|
+
before === null
|
|
21
|
+
? 'write'
|
|
22
|
+
: before === undefined
|
|
23
|
+
? 'create'
|
|
24
|
+
: before === after
|
|
25
|
+
? 'unchanged'
|
|
26
|
+
: 'replace'
|
|
27
|
+
const oldLines = lines(before ?? '')
|
|
28
|
+
const newLines = lines(after)
|
|
29
|
+
let start = 0
|
|
30
|
+
let end = 0
|
|
31
|
+
if (typeof before === 'string') {
|
|
32
|
+
while (
|
|
33
|
+
start < oldLines.length &&
|
|
34
|
+
start < newLines.length &&
|
|
35
|
+
oldLines[start] === newLines[start]
|
|
36
|
+
)
|
|
37
|
+
start++
|
|
38
|
+
while (
|
|
39
|
+
end < oldLines.length - start &&
|
|
40
|
+
end < newLines.length - start &&
|
|
41
|
+
oldLines[oldLines.length - end - 1] === newLines[newLines.length - end - 1]
|
|
42
|
+
)
|
|
43
|
+
end++
|
|
44
|
+
}
|
|
45
|
+
const removed = oldLines.slice(start, oldLines.length - end)
|
|
46
|
+
const added = newLines.slice(start, newLines.length - end)
|
|
47
|
+
const bytes = Buffer.byteLength(after, 'utf8')
|
|
48
|
+
const label =
|
|
49
|
+
operation === 'create'
|
|
50
|
+
? 'Created'
|
|
51
|
+
: operation === 'replace'
|
|
52
|
+
? 'Updated'
|
|
53
|
+
: operation === 'unchanged'
|
|
54
|
+
? 'Unchanged'
|
|
55
|
+
: 'Wrote'
|
|
56
|
+
const newlineChanged =
|
|
57
|
+
typeof before === 'string' && before.endsWith('\n') !== after.endsWith('\n')
|
|
58
|
+
const summary = `${label} ${path}${before === null ? '' : ` (+${added.length} -${removed.length})`}${newlineChanged ? ' · final newline changed' : ''}`
|
|
59
|
+
return {
|
|
60
|
+
success: true,
|
|
61
|
+
output: `${summary} · ${bytes} bytes`,
|
|
62
|
+
data: {
|
|
63
|
+
path,
|
|
64
|
+
size: after.length,
|
|
65
|
+
...(sandboxed ? { sandboxed: true } : {}),
|
|
66
|
+
fileChange: {
|
|
67
|
+
operation,
|
|
68
|
+
...(before === null
|
|
69
|
+
? {}
|
|
70
|
+
: {
|
|
71
|
+
preview: {
|
|
72
|
+
before: removed.length ? `${removed.join('\n')}\n` : '',
|
|
73
|
+
after: added.length ? `${added.join('\n')}\n` : '',
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
bytes,
|
|
77
|
+
sha256: createHash('sha256').update(after, 'utf8').digest('hex'),
|
|
78
|
+
...(before === null
|
|
79
|
+
? {}
|
|
80
|
+
: { added: added.length, removed: removed.length, newlineChanged }),
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -68,7 +68,13 @@ export const GlobTool = defineTool({
|
|
|
68
68
|
timeoutMs: 15_000,
|
|
69
69
|
|
|
70
70
|
presentCall(input) {
|
|
71
|
-
|
|
71
|
+
if (typeof input.pattern !== 'string') return undefined
|
|
72
|
+
return {
|
|
73
|
+
kind: 'generic',
|
|
74
|
+
label: `Find ${input.pattern} in ${input.path ?? '.'}`,
|
|
75
|
+
presentation: 'activity',
|
|
76
|
+
activity: 'exploration',
|
|
77
|
+
}
|
|
72
78
|
},
|
|
73
79
|
|
|
74
80
|
async execute(input, context) {
|
|
@@ -98,6 +98,16 @@ export const GrepTool = defineTool({
|
|
|
98
98
|
readOnly: true,
|
|
99
99
|
destructive: false,
|
|
100
100
|
concurrencySafe: true,
|
|
101
|
+
|
|
102
|
+
presentCall(input) {
|
|
103
|
+
if (typeof input.pattern !== 'string') return undefined
|
|
104
|
+
return {
|
|
105
|
+
kind: 'generic',
|
|
106
|
+
label: `Search ${input.pattern} in ${input.path ?? '.'}`,
|
|
107
|
+
presentation: 'activity',
|
|
108
|
+
activity: 'exploration',
|
|
109
|
+
}
|
|
110
|
+
},
|
|
101
111
|
timeoutMs: 15_000,
|
|
102
112
|
|
|
103
113
|
async execute(input, context) {
|
|
@@ -54,6 +54,14 @@ export const JobTool = defineTool({
|
|
|
54
54
|
destructive: (input: JobInput) => input.action === 'kill',
|
|
55
55
|
concurrencySafe: true,
|
|
56
56
|
|
|
57
|
+
presentCall(input) {
|
|
58
|
+
const label =
|
|
59
|
+
input.action === 'list'
|
|
60
|
+
? 'List background jobs'
|
|
61
|
+
: `${input.action === 'kill' ? 'Stop background job' : 'Read background output'} · ${input.id ?? '(missing id)'}`
|
|
62
|
+
return { kind: 'generic', presentation: 'activity', label }
|
|
63
|
+
},
|
|
64
|
+
|
|
57
65
|
async execute(input, context) {
|
|
58
66
|
if (!context.backgroundJobs) {
|
|
59
67
|
return {
|
|
@@ -32,6 +32,16 @@ export const ReadFileTool = defineTool({
|
|
|
32
32
|
destructive: false,
|
|
33
33
|
concurrencySafe: true,
|
|
34
34
|
|
|
35
|
+
presentCall(input) {
|
|
36
|
+
if (typeof input.path !== 'string') return undefined
|
|
37
|
+
return {
|
|
38
|
+
kind: 'generic',
|
|
39
|
+
label: `Read ${input.path}`,
|
|
40
|
+
presentation: 'activity',
|
|
41
|
+
activity: 'exploration',
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
35
45
|
async execute(input, context) {
|
|
36
46
|
// Sandbox-aware: route through sandbox.readFile() when available
|
|
37
47
|
if (context.sandbox) {
|
|
@@ -7,6 +7,7 @@ import { resolveWithinAnyReal, toolRoots } from '../paths.js'
|
|
|
7
7
|
import { atomicWriteFile } from './atomic-write-file.js'
|
|
8
8
|
import { fingerprintContent, staleFileError } from './content-fingerprint.js'
|
|
9
9
|
import { withFileMutationLock } from './file-mutation-lock.js'
|
|
10
|
+
import { fileWriteResult } from './file-write-result.js'
|
|
10
11
|
|
|
11
12
|
const inputSchema = z
|
|
12
13
|
.object({
|
|
@@ -81,40 +82,35 @@ export const WriteFileTool = defineTool({
|
|
|
81
82
|
destructive: true,
|
|
82
83
|
concurrencySafe: false,
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
* The whole file body, described by the tool.
|
|
86
|
-
*
|
|
87
|
-
* A `diff` with an EMPTY `before`, which is what a write actually is:
|
|
88
|
-
* whatever was there is gone and this is what replaces it. `edit`
|
|
89
|
-
* declines to do that for an insert — there, an empty `before` would
|
|
90
|
-
* claim the whole file was added and it was not. Here it is true.
|
|
91
|
-
*
|
|
92
|
-
* A host with nothing to contrast against renders the `after` plainly
|
|
93
|
-
* rather than as `+` lines, which is the row a reader wants: they are
|
|
94
|
-
* approving a file, not reviewing a patch.
|
|
95
|
-
*/
|
|
85
|
+
// Input alone cannot establish whether this replaces an existing file.
|
|
96
86
|
presentCall(input: WriteInput) {
|
|
97
|
-
|
|
98
|
-
if (typeof content !== 'string') return undefined
|
|
99
|
-
return {
|
|
100
|
-
kind: 'diff' as const,
|
|
101
|
-
...(input.path ? { path: input.path } : {}),
|
|
102
|
-
before: '',
|
|
103
|
-
after: content,
|
|
104
|
-
}
|
|
87
|
+
return { kind: 'generic' as const, label: input.path }
|
|
105
88
|
},
|
|
106
89
|
|
|
107
|
-
/**
|
|
108
|
-
* A label, deliberately — which is what suppresses the detail block.
|
|
109
|
-
*
|
|
110
|
-
* The content was already shown under the CALL, where the user could
|
|
111
|
-
* act on it. Repeating it under the result doubles the longest rows in
|
|
112
|
-
* the transcript to say nothing new. A host used to decide this by
|
|
113
|
-
* matching `name === 'write' || name === 'edit'`; it is the tool's to
|
|
114
|
-
* say, and now it says it.
|
|
115
|
-
*/
|
|
90
|
+
/** The receipt names the observed operation; failures retain their error output. */
|
|
116
91
|
presentResult(_input: WriteInput, result: ToolResult) {
|
|
117
|
-
|
|
92
|
+
const data = result.data as
|
|
93
|
+
| { fileChange?: { preview?: { before?: unknown; after?: unknown } } }
|
|
94
|
+
| undefined
|
|
95
|
+
const preview = data?.fileChange?.preview
|
|
96
|
+
if (
|
|
97
|
+
result.success &&
|
|
98
|
+
typeof preview?.before === 'string' &&
|
|
99
|
+
typeof preview.after === 'string'
|
|
100
|
+
) {
|
|
101
|
+
return {
|
|
102
|
+
kind: 'diff' as const,
|
|
103
|
+
path: _input.path,
|
|
104
|
+
label: result.output?.split('\n')[0],
|
|
105
|
+
before: preview.before,
|
|
106
|
+
after: preview.after,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
kind: 'generic' as const,
|
|
111
|
+
label: result.output?.split('\n')[0] ?? '',
|
|
112
|
+
...(result.success ? { presentation: 'activity' as const } : {}),
|
|
113
|
+
}
|
|
118
114
|
},
|
|
119
115
|
|
|
120
116
|
async execute(input: WriteInput, context) {
|
|
@@ -146,7 +142,7 @@ export const WriteFileTool = defineTool({
|
|
|
146
142
|
return withFileMutationLock(lockKey, async () => {
|
|
147
143
|
if (context.sandbox) {
|
|
148
144
|
const current = await readSandboxFileIfPresent(context, valid.path)
|
|
149
|
-
if (current !== undefined) {
|
|
145
|
+
if (current !== undefined && current !== null) {
|
|
150
146
|
const guard = enforceFreshOverwrite(context, valid.path, current.toString('utf-8'))
|
|
151
147
|
if (guard) return guard
|
|
152
148
|
}
|
|
@@ -155,11 +151,12 @@ export const WriteFileTool = defineTool({
|
|
|
155
151
|
// the boolean would leave an older content fingerprint in place and
|
|
156
152
|
// make the next same-run write refuse its own predecessor as drift.
|
|
157
153
|
context.fileReadTracker?.recordRead(valid.path, content)
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
154
|
+
return fileWriteResult(
|
|
155
|
+
valid.path,
|
|
156
|
+
current === null ? null : current?.toString('utf-8'),
|
|
157
|
+
content,
|
|
158
|
+
true,
|
|
159
|
+
)
|
|
163
160
|
}
|
|
164
161
|
|
|
165
162
|
const hostPath = filePath as string
|
|
@@ -176,11 +173,7 @@ export const WriteFileTool = defineTool({
|
|
|
176
173
|
await atomicWriteFile(hostPath, content)
|
|
177
174
|
context.fileReadTracker?.recordRead(hostPath, content)
|
|
178
175
|
|
|
179
|
-
return
|
|
180
|
-
success: true as const,
|
|
181
|
-
output: `File written successfully: ${hostPath} (${content.length} chars)`,
|
|
182
|
-
data: { path: hostPath, size: content.length },
|
|
183
|
-
}
|
|
176
|
+
return fileWriteResult(hostPath, current, content)
|
|
184
177
|
})
|
|
185
178
|
},
|
|
186
179
|
})
|
|
@@ -237,14 +230,16 @@ function isMissingFileError(error: unknown): boolean {
|
|
|
237
230
|
async function readSandboxFileIfPresent(
|
|
238
231
|
context: ToolContext,
|
|
239
232
|
path: string,
|
|
240
|
-
): Promise<Buffer | undefined> {
|
|
233
|
+
): Promise<Buffer | undefined | null> {
|
|
241
234
|
if (!context.sandbox) return undefined
|
|
242
235
|
try {
|
|
243
236
|
return await context.sandbox.readFile(path)
|
|
244
|
-
} catch {
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (isMissingFileError(error)) return undefined
|
|
245
239
|
// `Sandbox` currently has no typed missing-file result. Preserve the
|
|
246
240
|
// existing create behavior for all backends; when a read succeeds, the
|
|
247
241
|
// exact body above is still a real admission-time freshness check.
|
|
248
|
-
|
|
242
|
+
// An untyped read failure does not prove that the target was absent.
|
|
243
|
+
return null
|
|
249
244
|
}
|
|
250
245
|
}
|
|
@@ -566,6 +566,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
566
566
|
// could report `failed` or stay `executing` forever but never
|
|
567
567
|
// `completed`.
|
|
568
568
|
const planStepId = plan_step_id
|
|
569
|
+
const planId = planStepId ? getPlanManager?.()?.active?.id : undefined
|
|
569
570
|
const reportStep = (status: 'running' | 'completed' | 'failed', error?: string): void => {
|
|
570
571
|
if (!planStepId) return
|
|
571
572
|
getPlanManager?.()?.updateStepStatus(planStepId, status, error)
|
|
@@ -595,6 +596,8 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
595
596
|
prompt,
|
|
596
597
|
workingDirectory: cwd,
|
|
597
598
|
runtimeContext: opts.runtimeContext,
|
|
599
|
+
...(planStepId ? { planStepId } : {}),
|
|
600
|
+
...(planId ? { planId } : {}),
|
|
598
601
|
// Hang the child run off THIS tool's span, so the delegation
|
|
599
602
|
// shows up inside the turn that asked for it.
|
|
600
603
|
...(_context.parentSpan ? { parentSpan: _context.parentSpan } : {}),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FileReadTracker } from '../types/tool/index.js'
|
|
2
|
+
import { fingerprintContent } from './builtins/content-fingerprint.js'
|
|
3
|
+
|
|
4
|
+
/** Host-owned observation ledger. Share only across turns of the same conversation and filesystem. */
|
|
5
|
+
export function createFileReadTracker(): FileReadTracker {
|
|
6
|
+
const paths = new Set<string>()
|
|
7
|
+
const fingerprints = new Map<string, string>()
|
|
8
|
+
return {
|
|
9
|
+
recordRead(key, content) {
|
|
10
|
+
paths.add(key)
|
|
11
|
+
if (content !== undefined) fingerprints.set(key, fingerprintContent(content))
|
|
12
|
+
},
|
|
13
|
+
hasRead: (key) => paths.has(key),
|
|
14
|
+
fingerprint: (key) => fingerprints.get(key),
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/tools/task/list.ts
CHANGED
|
@@ -8,7 +8,7 @@ export function buildTaskListTool(taskStore: TaskStore, runId: RunId): ToolDefin
|
|
|
8
8
|
return defineTool({
|
|
9
9
|
name: 'task_list',
|
|
10
10
|
description:
|
|
11
|
-
'List
|
|
11
|
+
'List planning items for the current run, not delegated agent invocations. Use agent_task_list, when available, to inspect agent execution. Shows subject, status, owner, and unresolved blockers. Use this to review your plan and decide what to work on next.',
|
|
12
12
|
inputSchema: z.object({}),
|
|
13
13
|
category: 'custom',
|
|
14
14
|
permissions: [],
|
|
@@ -46,7 +46,7 @@ export function buildTaskListTool(taskStore: TaskStore, runId: RunId): ToolDefin
|
|
|
46
46
|
success: true,
|
|
47
47
|
output:
|
|
48
48
|
tasks.length === 0
|
|
49
|
-
? 'No tasks yet.'
|
|
49
|
+
? 'No planning tasks yet. This list does not report delegated agent status; use agent_task_list when available.'
|
|
50
50
|
: `${stats.total} tasks: ${stats.completed} completed, ${stats.in_progress} in progress, ${stats.pending} pending.`,
|
|
51
51
|
data: { tasks: summary, stats },
|
|
52
52
|
}
|
|
@@ -19,6 +19,8 @@ import type { WorkingMemoryProvider } from './working-memory.js'
|
|
|
19
19
|
|
|
20
20
|
export interface ReactiveAgentConfig extends BaseAgentConfig {
|
|
21
21
|
systemPrompt?: string
|
|
22
|
+
/** Provider-hosted search for this run; the selected driver must support it. */
|
|
23
|
+
webSearch?: { mode: 'live' | 'cached' }
|
|
22
24
|
|
|
23
25
|
persona?: AgentPersona
|
|
24
26
|
|