@namzu/sdk 1.0.0 → 1.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 +167 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +5 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts +22 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +47 -14
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/__tests__/supervisor-ledger.test.d.ts +2 -0
- package/dist/agents/__tests__/supervisor-ledger.test.d.ts.map +1 -0
- package/dist/agents/__tests__/supervisor-ledger.test.js +131 -0
- package/dist/agents/__tests__/supervisor-ledger.test.js.map +1 -0
- package/dist/config/runtime.d.ts +24 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +9 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/connector/builtins/webhook.test.js +17 -0
- package/dist/connector/builtins/webhook.test.js.map +1 -1
- package/dist/connector/index.d.ts +1 -0
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -0
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +4 -0
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +1 -0
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +1 -0
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/streamable-http.d.ts +22 -0
- package/dist/connector/mcp/streamable-http.d.ts.map +1 -0
- package/dist/connector/mcp/streamable-http.js +128 -0
- package/dist/connector/mcp/streamable-http.js.map +1 -0
- package/dist/connector/mcp/streamable-http.test.d.ts +2 -0
- package/dist/connector/mcp/streamable-http.test.d.ts.map +1 -0
- package/dist/connector/mcp/streamable-http.test.js +132 -0
- package/dist/connector/mcp/streamable-http.test.js.map +1 -0
- package/dist/persona/assembler.d.ts.map +1 -1
- package/dist/persona/assembler.js +18 -9
- package/dist/persona/assembler.js.map +1 -1
- package/dist/provider/collect.d.ts.map +1 -1
- package/dist/provider/collect.js +4 -1
- package/dist/provider/collect.js.map +1 -1
- package/dist/public-runtime.d.ts +1 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/execute.d.ts +27 -0
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +124 -21
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/tool/execute.test.d.ts +9 -6
- package/dist/registry/tool/execute.test.d.ts.map +1 -1
- package/dist/registry/tool/execute.test.js +72 -10
- package/dist/registry/tool/execute.test.js.map +1 -1
- package/dist/registry/toolset/catalog.test.js +62 -0
- package/dist/registry/toolset/catalog.test.js.map +1 -1
- package/dist/runtime/query/__tests__/deferred-tools.test.js +43 -5
- package/dist/runtime/query/__tests__/deferred-tools.test.js.map +1 -1
- package/dist/runtime/query/__tests__/long-document-flow.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.js +154 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.js.map +1 -0
- package/dist/runtime/query/__tests__/prompt.test.js +15 -11
- package/dist/runtime/query/__tests__/prompt.test.js.map +1 -1
- package/dist/runtime/query/index.d.ts +9 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +23 -2
- 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 +99 -6
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/checkpoint.js +2 -2
- package/dist/runtime/query/iteration/phases/checkpoint.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +8 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +84 -3
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +12 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +53 -0
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js +73 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js +120 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/tool-review.d.ts +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +7 -2
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts +19 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js +177 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory.d.ts +31 -0
- package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory.js +81 -0
- package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -0
- package/dist/session/workspace/__tests__/shared-run.test.js +1 -1
- package/dist/session/workspace/__tests__/shared-run.test.js.map +1 -1
- package/dist/session/workspace/shared-run.js +1 -0
- package/dist/session/workspace/shared-run.js.map +1 -1
- package/dist/skills/loader.test.js +8 -8
- package/dist/skills/loader.test.js.map +1 -1
- package/dist/streaming/coalesce.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/payload-budget.test.js +48 -0
- package/dist/tools/builtins/__tests__/payload-budget.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/read-file.test.js +9 -0
- package/dist/tools/builtins/__tests__/read-file.test.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +50 -2
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/search-tools.d.ts.map +1 -1
- package/dist/tools/builtins/search-tools.js +32 -7
- package/dist/tools/builtins/search-tools.js.map +1 -1
- package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.js +114 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.js.map +1 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js +309 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js.map +1 -0
- package/dist/tools/coordinator/index.d.ts +8 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +195 -15
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/types/agent/index.d.ts +1 -0
- package/dist/types/agent/index.d.ts.map +1 -1
- package/dist/types/agent/index.js +1 -0
- package/dist/types/agent/index.js.map +1 -1
- package/dist/types/agent/reactive.d.ts +15 -0
- package/dist/types/agent/reactive.d.ts.map +1 -1
- package/dist/types/agent/supervisor.d.ts +18 -0
- package/dist/types/agent/supervisor.d.ts.map +1 -1
- package/dist/types/agent/working-memory.d.ts +29 -0
- package/dist/types/agent/working-memory.d.ts.map +1 -0
- package/dist/types/agent/working-memory.js +2 -0
- package/dist/types/agent/working-memory.js.map +1 -0
- package/dist/types/common/index.d.ts +10 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +18 -0
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/common/usage-merge.test.d.ts +2 -0
- package/dist/types/common/usage-merge.test.d.ts.map +1 -0
- package/dist/types/common/usage-merge.test.js +48 -0
- package/dist/types/common/usage-merge.test.js.map +1 -0
- package/dist/types/connector/mcp.d.ts +9 -2
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +42 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js +11 -0
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/provider/chat.d.ts +9 -0
- package/dist/types/provider/chat.d.ts.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +25 -4
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +5 -0
- package/src/agents/SupervisorAgent.ts +56 -16
- package/src/agents/__tests__/supervisor-ledger.test.ts +185 -0
- package/src/config/runtime.ts +9 -0
- package/src/connector/builtins/webhook.test.ts +21 -0
- package/src/connector/index.ts +1 -0
- package/src/connector/mcp/client.ts +4 -0
- package/src/connector/mcp/index.ts +1 -0
- package/src/connector/mcp/streamable-http.test.ts +169 -0
- package/src/connector/mcp/streamable-http.ts +152 -0
- package/src/persona/assembler.ts +18 -6
- package/src/provider/collect.ts +4 -1
- package/src/public-runtime.ts +1 -0
- package/src/registry/tool/execute.test.ts +85 -10
- package/src/registry/tool/execute.ts +124 -21
- package/src/registry/toolset/catalog.test.ts +68 -0
- package/src/runtime/query/__tests__/deferred-tools.test.ts +52 -5
- package/src/runtime/query/__tests__/long-document-flow.test.ts +180 -0
- package/src/runtime/query/__tests__/prompt.test.ts +15 -11
- package/src/runtime/query/index.ts +34 -2
- package/src/runtime/query/iteration/index.ts +98 -6
- package/src/runtime/query/iteration/phases/checkpoint.ts +7 -2
- package/src/runtime/query/iteration/phases/compaction.ts +89 -3
- package/src/runtime/query/iteration/phases/context.ts +58 -0
- package/src/runtime/query/iteration/phases/hitl-abort-race.test.ts +86 -0
- package/src/runtime/query/iteration/phases/hitl-answer-question.test.ts +139 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +7 -3
- package/src/runtime/query/iteration/phases/working-memory-compaction.test.ts +215 -0
- package/src/runtime/query/iteration/phases/working-memory.ts +86 -0
- package/src/session/workspace/__tests__/shared-run.test.ts +1 -1
- package/src/session/workspace/shared-run.ts +1 -0
- package/src/skills/loader.test.ts +8 -8
- package/src/streaming/coalesce.test.ts +3 -3
- package/src/tools/builtins/__tests__/payload-budget.test.ts +63 -0
- package/src/tools/builtins/__tests__/read-file.test.ts +12 -0
- package/src/tools/builtins/read-file.ts +50 -2
- package/src/tools/builtins/search-tools.ts +38 -7
- package/src/tools/coordinator/__tests__/approve-plan.test.ts +139 -0
- package/src/tools/coordinator/__tests__/ask-user-question.test.ts +370 -0
- package/src/tools/coordinator/index.ts +227 -17
- package/src/types/agent/index.ts +1 -0
- package/src/types/agent/reactive.ts +17 -0
- package/src/types/agent/supervisor.ts +20 -0
- package/src/types/agent/working-memory.ts +29 -0
- package/src/types/common/index.ts +19 -0
- package/src/types/common/usage-merge.test.ts +53 -0
- package/src/types/connector/mcp.ts +14 -2
- package/src/types/hitl/index.ts +52 -1
- package/src/types/provider/chat.ts +10 -0
- package/src/version.ts +26 -4
|
@@ -19,7 +19,35 @@ export type { ToolExecutionResult }
|
|
|
19
19
|
// Tokens too generic to identify a tool by name — ignored when matching a
|
|
20
20
|
// batched `search_tools` query so they can't activate the whole catalog
|
|
21
21
|
// (every bridged tool name shares the `clawtool` prefix, for instance).
|
|
22
|
-
|
|
22
|
+
// Generic CRUD verbs are stopped too: a query like "list deals" must rank
|
|
23
|
+
// by "deals", not token-match every `list_*` tool in the deferred catalog.
|
|
24
|
+
const SEARCH_STOP_TOKENS = new Set([
|
|
25
|
+
'clawtool',
|
|
26
|
+
'tool',
|
|
27
|
+
'tools',
|
|
28
|
+
'mcp',
|
|
29
|
+
'the',
|
|
30
|
+
'and',
|
|
31
|
+
'for',
|
|
32
|
+
'use',
|
|
33
|
+
'list',
|
|
34
|
+
'read',
|
|
35
|
+
'create',
|
|
36
|
+
'update',
|
|
37
|
+
'get',
|
|
38
|
+
'find',
|
|
39
|
+
'delete',
|
|
40
|
+
'search',
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
// Weighted-scoring weights mirroring ToolCatalog.searchTools (the richer,
|
|
44
|
+
// otherwise-unused catalog scorer): exact name 12, name substring 8,
|
|
45
|
+
// description 5 — extended here with argument-name indexing (3), following
|
|
46
|
+
// Anthropic's tool-search practice of searching argument names too.
|
|
47
|
+
const SEARCH_WEIGHT_NAME_EXACT = 12
|
|
48
|
+
const SEARCH_WEIGHT_NAME_PARTIAL = 8
|
|
49
|
+
const SEARCH_WEIGHT_DESCRIPTION = 5
|
|
50
|
+
const SEARCH_WEIGHT_ARGUMENT = 3
|
|
23
51
|
|
|
24
52
|
export class ToolRegistry extends ManagedRegistry<ToolDefinition> {
|
|
25
53
|
private availability: Map<string, ToolAvailability> = new Map()
|
|
@@ -118,28 +146,58 @@ export class ToolRegistry extends ManagedRegistry<ToolDefinition> {
|
|
|
118
146
|
return this.availability.get(name) ?? 'active'
|
|
119
147
|
}
|
|
120
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Ranked lexical search over DEFERRED tools, score-descending (ties broken
|
|
151
|
+
* by name) so callers can cap activation at a top-k. Each meaningful query
|
|
152
|
+
* term (≥3 chars, not a stop token) is scored against the tool name
|
|
153
|
+
* (exact/substring), description, and argument names; only tools with a
|
|
154
|
+
* positive score are returned. Description matching is safe here precisely
|
|
155
|
+
* because the result is RANKED — the `search_tools` builtin activates only
|
|
156
|
+
* the top slice, so a shared word can no longer drag in the whole catalog.
|
|
157
|
+
*
|
|
158
|
+
* PARKED (phase 5 of the tool-loading plan): an embedding-backed semantic
|
|
159
|
+
* upgrade was evaluated and deliberately NOT built — at ≤~35 deferred
|
|
160
|
+
* in-house tools with distinct names, weighted lexical scoring sits inside
|
|
161
|
+
* the literature's safe zone, and a weak retriever underperforms no
|
|
162
|
+
* retriever at all. Revisit only when (a) the deferred catalog grows past
|
|
163
|
+
* ~75-100 tools (realistic driver: connector-MCP growth), or (b) telemetry
|
|
164
|
+
* shows a search_tools miss-rate above ~10%. Sticky activation is also
|
|
165
|
+
* deliberate: activating inserts the schema into the tools array at its
|
|
166
|
+
* registry position (a one-time prompt-cache prefix bust); re-defer/TTL
|
|
167
|
+
* would churn that prefix repeatedly and is rejected.
|
|
168
|
+
*/
|
|
121
169
|
searchDeferred(query: string): ToolDefinition[] {
|
|
122
170
|
const q = query.toLowerCase().trim()
|
|
123
171
|
if (q.length === 0) return []
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
172
|
+
const terms = q.split(/\s+/).filter((tok) => tok.length >= 3 && !SEARCH_STOP_TOKENS.has(tok))
|
|
173
|
+
if (terms.length === 0) return []
|
|
174
|
+
|
|
175
|
+
const scored: Array<{ tool: ToolDefinition; score: number }> = []
|
|
176
|
+
for (const tool of this.getByAvailability(['deferred'])) {
|
|
177
|
+
const name = tool.name.toLowerCase()
|
|
178
|
+
const description = tool.description.toLowerCase()
|
|
179
|
+
const argumentNames = listArgumentNames(tool)
|
|
180
|
+
let score = 0
|
|
181
|
+
for (const term of terms) {
|
|
182
|
+
if (name === term) {
|
|
183
|
+
score += SEARCH_WEIGHT_NAME_EXACT
|
|
184
|
+
} else if (name.includes(term)) {
|
|
185
|
+
score += SEARCH_WEIGHT_NAME_PARTIAL
|
|
186
|
+
}
|
|
187
|
+
if (description.includes(term)) {
|
|
188
|
+
score += SEARCH_WEIGHT_DESCRIPTION
|
|
189
|
+
}
|
|
190
|
+
if (argumentNames.some((arg) => arg.includes(term))) {
|
|
191
|
+
score += SEARCH_WEIGHT_ARGUMENT
|
|
192
|
+
}
|
|
139
193
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
194
|
+
if (score > 0) {
|
|
195
|
+
scored.push({ tool, score })
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
scored.sort((a, b) => b.score - a.score || a.tool.name.localeCompare(b.tool.name))
|
|
200
|
+
return scored.map((entry) => entry.tool)
|
|
143
201
|
}
|
|
144
202
|
|
|
145
203
|
assignTiers(mapping: Record<string, string>): void {
|
|
@@ -176,12 +234,24 @@ Executable tool names, descriptions, and JSON input schemas are attached through
|
|
|
176
234
|
</tool_runtime_contract>`
|
|
177
235
|
|
|
178
236
|
if (active.length > 0) {
|
|
179
|
-
|
|
237
|
+
// Name-only: every active tool's full description + JSON schema
|
|
238
|
+
// already rides the runtime tools parameter on each request —
|
|
239
|
+
// repeating descriptions here double-bills the same tokens.
|
|
240
|
+
const entries = active.map((t) => `- ${t.name}`).join('\n')
|
|
180
241
|
parts.push(`<available_tools>\n${entries}\n</available_tools>`)
|
|
181
242
|
}
|
|
182
243
|
|
|
183
244
|
if (deferred.length > 0) {
|
|
184
|
-
|
|
245
|
+
// Name + one-line hint: deferred schemas stay off the wire, so the
|
|
246
|
+
// hint is the model's only signal of what a deferred tool does. A
|
|
247
|
+
// bare name list caused a real discovery failure in production
|
|
248
|
+
// (the agent never found read_document behind search_tools).
|
|
249
|
+
const entries = deferred
|
|
250
|
+
.map((t) => {
|
|
251
|
+
const hint = toolDiscoveryHint(t.description)
|
|
252
|
+
return hint.length > 0 ? `- ${t.name}: ${hint}` : `- ${t.name}`
|
|
253
|
+
})
|
|
254
|
+
.join('\n')
|
|
185
255
|
const deferredIntro =
|
|
186
256
|
this.has('search_tools') && this.getAvailability('search_tools') === 'active'
|
|
187
257
|
? 'Use search_tools to load these before use:'
|
|
@@ -372,6 +442,39 @@ Executable tool names, descriptions, and JSON input schemas are attached through
|
|
|
372
442
|
}
|
|
373
443
|
}
|
|
374
444
|
|
|
445
|
+
/**
|
|
446
|
+
* One-line discoverability hint for a deferred tool: the first sentence of
|
|
447
|
+
* its description, capped at ~100 chars. Used for the `<deferred_tools>`
|
|
448
|
+
* prompt listing and for `search_tools` near-miss suggestions, where the
|
|
449
|
+
* full description would re-import the token weight deferral avoids.
|
|
450
|
+
*/
|
|
451
|
+
export function toolDiscoveryHint(description: string, maxLength = 100): string {
|
|
452
|
+
const normalized = description.trim().replace(/\s+/g, ' ')
|
|
453
|
+
if (normalized.length === 0) return ''
|
|
454
|
+
const sentenceMatch = normalized.match(/^.*?[.!?](?=\s|$)/)
|
|
455
|
+
const sentence = sentenceMatch ? sentenceMatch[0] : normalized
|
|
456
|
+
if (sentence.length <= maxLength) return sentence
|
|
457
|
+
return `${sentence.slice(0, maxLength - 1).trimEnd()}…`
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Lower-cased argument (property) names of a tool's input schema, used by
|
|
462
|
+
* `searchDeferred` ranking. Walks the JSON-Schema rendering (already a
|
|
463
|
+
* registration dependency) instead of Zod internals; opaque schemas simply
|
|
464
|
+
* contribute no argument matches.
|
|
465
|
+
*/
|
|
466
|
+
function listArgumentNames(tool: ToolDefinition): string[] {
|
|
467
|
+
try {
|
|
468
|
+
const json = zodToJsonSchema(tool.inputSchema, {
|
|
469
|
+
target: 'jsonSchema7',
|
|
470
|
+
$refStrategy: 'none',
|
|
471
|
+
}) as { properties?: Record<string, unknown> }
|
|
472
|
+
return Object.keys(json.properties ?? {}).map((key) => key.toLowerCase())
|
|
473
|
+
} catch {
|
|
474
|
+
return []
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
375
478
|
/**
|
|
376
479
|
* Build a one-sentence "Required: <field>: <type>, <field>: <type>"
|
|
377
480
|
* hint from a Zod schema, used to enrich tool-input validation
|
|
@@ -81,6 +81,74 @@ describe('ToolCatalog', () => {
|
|
|
81
81
|
expect(catalog.toLLMTools().map((t) => t.function.name)).toEqual(['bash'])
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
+
it('scores and reports the matched facets: partial name, source text, toolset text', () => {
|
|
85
|
+
const catalog = new ToolCatalog()
|
|
86
|
+
catalog.registerSource({
|
|
87
|
+
id: 'host',
|
|
88
|
+
kind: 'host_tool',
|
|
89
|
+
name: 'GitHub',
|
|
90
|
+
description: 'repository automation',
|
|
91
|
+
})
|
|
92
|
+
catalog.registerToolset({
|
|
93
|
+
id: 'issues',
|
|
94
|
+
sourceId: 'host',
|
|
95
|
+
name: 'Issue toolset',
|
|
96
|
+
defaultPolicy: { enabled: true, loading: 'eager' },
|
|
97
|
+
})
|
|
98
|
+
catalog.registerTool({
|
|
99
|
+
name: 'create_issue',
|
|
100
|
+
description: 'open a ticket',
|
|
101
|
+
sourceId: 'host',
|
|
102
|
+
toolsetId: 'issues',
|
|
103
|
+
policy: { enabled: true, loading: 'eager' },
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// 'issue' hits the tool NAME (create_issue, partial) AND the TOOLSET name.
|
|
107
|
+
const byIssue = catalog.searchTools('issue')
|
|
108
|
+
expect(byIssue).toHaveLength(1)
|
|
109
|
+
expect([...(byIssue[0]?.matched ?? [])].sort()).toEqual(['name', 'toolset'])
|
|
110
|
+
|
|
111
|
+
// 'github' hits only the SOURCE text.
|
|
112
|
+
const bySource = catalog.searchTools('github')
|
|
113
|
+
expect(bySource[0]?.matched).toEqual(['source'])
|
|
114
|
+
|
|
115
|
+
// An exact name match outscores a partial one.
|
|
116
|
+
const exact = catalog.searchTools('create_issue')
|
|
117
|
+
expect(exact[0]?.matched).toContain('name')
|
|
118
|
+
expect(exact[0]?.score ?? 0).toBeGreaterThan(byIssue[0]?.score ?? 0)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('adds the preferred boost so a preferred tool outranks an equal match', () => {
|
|
122
|
+
const catalog = new ToolCatalog()
|
|
123
|
+
catalog.registerSource({ id: 'host', kind: 'host_tool', name: 'Host' })
|
|
124
|
+
catalog.registerToolset({
|
|
125
|
+
id: 'ts',
|
|
126
|
+
sourceId: 'host',
|
|
127
|
+
name: 'Tools',
|
|
128
|
+
defaultPolicy: { enabled: true, loading: 'eager' },
|
|
129
|
+
})
|
|
130
|
+
catalog.registerTool({
|
|
131
|
+
name: 'search_plain',
|
|
132
|
+
description: 'search things',
|
|
133
|
+
sourceId: 'host',
|
|
134
|
+
toolsetId: 'ts',
|
|
135
|
+
policy: { enabled: true, loading: 'eager' },
|
|
136
|
+
})
|
|
137
|
+
catalog.registerTool({
|
|
138
|
+
name: 'search_pref',
|
|
139
|
+
description: 'search things',
|
|
140
|
+
sourceId: 'host',
|
|
141
|
+
toolsetId: 'ts',
|
|
142
|
+
policy: { enabled: true, loading: 'eager', preferred: true },
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const results = catalog.searchTools('search')
|
|
146
|
+
expect(results.map((r) => r.tool.name)).toEqual(['search_pref', 'search_plain'])
|
|
147
|
+
const pref = results.find((r) => r.tool.name === 'search_pref')?.score ?? 0
|
|
148
|
+
const plain = results.find((r) => r.tool.name === 'search_plain')?.score ?? 0
|
|
149
|
+
expect(pref - plain).toBe(1)
|
|
150
|
+
})
|
|
151
|
+
|
|
84
152
|
it('preserves registry availability as catalog loading policy', () => {
|
|
85
153
|
const registry = new ToolRegistry()
|
|
86
154
|
registry.register(makeTool('read_file'))
|
|
@@ -159,10 +159,12 @@ describe('query deferred tool discovery', () => {
|
|
|
159
159
|
it('does not let search_tools reveal or activate deferred tools outside allowedTools', async () => {
|
|
160
160
|
const tools = new ToolRegistry()
|
|
161
161
|
registerDeferredDocumentTool(tools)
|
|
162
|
-
registerDeferredDocumentTool(tools, '
|
|
162
|
+
registerDeferredDocumentTool(tools, 'dangerous_purge_document')
|
|
163
163
|
|
|
164
|
+
// 'dangerous' matches only the out-of-allowlist tool ('delete'-style
|
|
165
|
+
// CRUD verbs are stop tokens and never match anything by themselves).
|
|
164
166
|
const result = await SearchToolsTool.execute(
|
|
165
|
-
{ query: '
|
|
167
|
+
{ query: 'dangerous' },
|
|
166
168
|
{
|
|
167
169
|
runId: 'run_deferred_allowed_tools' as RunId,
|
|
168
170
|
workingDirectory: '/tmp',
|
|
@@ -175,9 +177,54 @@ describe('query deferred tool discovery', () => {
|
|
|
175
177
|
)
|
|
176
178
|
|
|
177
179
|
expect(result.success).toBe(true)
|
|
178
|
-
expect(result.output).toContain('No deferred tools matching "
|
|
179
|
-
expect(result.output).not.toContain('
|
|
180
|
+
expect(result.output).toContain('No deferred tools matching "dangerous"')
|
|
181
|
+
expect(result.output).not.toContain('dangerous_purge_document')
|
|
180
182
|
expect(tools.getAvailability('generate_document')).toBe('deferred')
|
|
181
|
-
expect(tools.getAvailability('
|
|
183
|
+
expect(tools.getAvailability('dangerous_purge_document')).toBe('deferred')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('activates only the top-5 ranked matches and reports near-misses without activating', async () => {
|
|
187
|
+
const tools = new ToolRegistry()
|
|
188
|
+
// Eight deferred tools that all match "invoice" equally by name; the
|
|
189
|
+
// alphabetical tie-break makes the top-5 cut deterministic.
|
|
190
|
+
const names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'].map((s) => `invoice_${s}`)
|
|
191
|
+
for (const name of names) {
|
|
192
|
+
tools.register(
|
|
193
|
+
{
|
|
194
|
+
name,
|
|
195
|
+
description: `Billing helper ${name.slice(-1)}.`,
|
|
196
|
+
inputSchema: z.object({ id: z.string() }),
|
|
197
|
+
execute: async () => ({ success: true, output: 'ok' }),
|
|
198
|
+
},
|
|
199
|
+
'deferred',
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = await SearchToolsTool.execute(
|
|
204
|
+
{ query: 'invoice' },
|
|
205
|
+
{
|
|
206
|
+
runId: 'run_deferred_top_k' as RunId,
|
|
207
|
+
workingDirectory: '/tmp',
|
|
208
|
+
abortSignal: new AbortController().signal,
|
|
209
|
+
env: {},
|
|
210
|
+
log: () => undefined,
|
|
211
|
+
toolRegistry: tools,
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
expect(result.success).toBe(true)
|
|
216
|
+
expect(result.output).toContain('Activated 5 tool(s)')
|
|
217
|
+
expect(result.output).toContain('NOT loaded')
|
|
218
|
+
expect(result.data).toMatchObject({
|
|
219
|
+
activated: ['invoice_a', 'invoice_b', 'invoice_c', 'invoice_d', 'invoice_e'],
|
|
220
|
+
count: 5,
|
|
221
|
+
nearMisses: ['invoice_f', 'invoice_g', 'invoice_h'],
|
|
222
|
+
})
|
|
223
|
+
for (const name of ['invoice_a', 'invoice_b', 'invoice_c', 'invoice_d', 'invoice_e']) {
|
|
224
|
+
expect(tools.getAvailability(name)).toBe('active')
|
|
225
|
+
}
|
|
226
|
+
for (const name of ['invoice_f', 'invoice_g', 'invoice_h']) {
|
|
227
|
+
expect(tools.getAvailability(name)).toBe('deferred')
|
|
228
|
+
}
|
|
182
229
|
})
|
|
183
230
|
})
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { readFile, rm } from 'node:fs/promises'
|
|
2
|
+
import { mkdtemp } from 'node:fs/promises'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { ToolRegistry } from '../../../registry/tool/execute.js'
|
|
8
|
+
import { EditTool, WriteFileTool } from '../../../tools/builtins/index.js'
|
|
9
|
+
import type { SessionId, TenantId } from '../../../types/ids/index.js'
|
|
10
|
+
import { createUserMessage } from '../../../types/message/index.js'
|
|
11
|
+
import type { LLMProvider, StreamChunk } from '../../../types/provider/index.js'
|
|
12
|
+
import type { RunEvent } from '../../../types/run/index.js'
|
|
13
|
+
import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
|
|
14
|
+
import { drainQuery } from '../index.js'
|
|
15
|
+
|
|
16
|
+
const ZERO_USAGE = {
|
|
17
|
+
promptTokens: 0,
|
|
18
|
+
completionTokens: 0,
|
|
19
|
+
totalTokens: 0,
|
|
20
|
+
cachedTokens: 0,
|
|
21
|
+
cacheWriteTokens: 0,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ToolStep {
|
|
25
|
+
readonly name: string
|
|
26
|
+
readonly input: Record<string, unknown>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class ScriptedLongDocumentProvider implements LLMProvider {
|
|
30
|
+
readonly id = 'scripted-long-document'
|
|
31
|
+
readonly name = 'Scripted Long Document Provider'
|
|
32
|
+
calls = 0
|
|
33
|
+
|
|
34
|
+
constructor(private readonly steps: readonly ToolStep[]) {}
|
|
35
|
+
|
|
36
|
+
async *chatStream(): AsyncIterable<StreamChunk> {
|
|
37
|
+
const step = this.steps[this.calls]
|
|
38
|
+
this.calls += 1
|
|
39
|
+
|
|
40
|
+
if (!step) {
|
|
41
|
+
yield {
|
|
42
|
+
id: 'msg_done',
|
|
43
|
+
delta: { content: 'Long document created and verified.' },
|
|
44
|
+
finishReason: 'stop',
|
|
45
|
+
usage: ZERO_USAGE,
|
|
46
|
+
}
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
yield {
|
|
51
|
+
id: `msg_${this.calls}`,
|
|
52
|
+
delta: {
|
|
53
|
+
toolCalls: [
|
|
54
|
+
{
|
|
55
|
+
index: 0,
|
|
56
|
+
id: `toolu_${this.calls}`,
|
|
57
|
+
type: 'function',
|
|
58
|
+
function: { name: step.name },
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
yield {
|
|
64
|
+
id: `msg_${this.calls}`,
|
|
65
|
+
delta: {
|
|
66
|
+
toolCalls: [
|
|
67
|
+
{
|
|
68
|
+
index: 0,
|
|
69
|
+
id: `toolu_${this.calls}`,
|
|
70
|
+
function: { arguments: JSON.stringify(step.input) },
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
yield {
|
|
76
|
+
id: `msg_${this.calls}`,
|
|
77
|
+
delta: {},
|
|
78
|
+
finishReason: 'tool_calls',
|
|
79
|
+
usage: ZERO_USAGE,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe('query long-document tool flow', () => {
|
|
85
|
+
const workdirs: string[] = []
|
|
86
|
+
|
|
87
|
+
afterEach(async () => {
|
|
88
|
+
await Promise.all(workdirs.map((dir) => rm(dir, { recursive: true, force: true })))
|
|
89
|
+
workdirs.length = 0
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('executes the canonical write skeleton plus bounded edit chunks flow end to end', async () => {
|
|
93
|
+
const workingDirectory = await mkdtemp(join(tmpdir(), 'namzu-query-long-doc-'))
|
|
94
|
+
workdirs.push(workingDirectory)
|
|
95
|
+
|
|
96
|
+
const chunks = Array.from({ length: 4 }, (_, sectionIndex) => {
|
|
97
|
+
const lines = Array.from({ length: 40 }, (_, lineIndex) => {
|
|
98
|
+
return `Section ${sectionIndex + 1}.${lineIndex + 1}: this content is intentionally long enough to require repeated bounded edit calls.`
|
|
99
|
+
})
|
|
100
|
+
const chunk = [`## Section ${sectionIndex + 1}`, ...lines, ''].join('\n')
|
|
101
|
+
expect(chunk.length).toBeLessThan(12_000)
|
|
102
|
+
return chunk
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const provider = new ScriptedLongDocumentProvider([
|
|
106
|
+
{
|
|
107
|
+
name: 'write',
|
|
108
|
+
input: {
|
|
109
|
+
path: 'outputs/long-document-flow.md',
|
|
110
|
+
content: '# Long document flow\n\n{{BODY}}\n',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'edit',
|
|
115
|
+
input: {
|
|
116
|
+
path: 'outputs/long-document-flow.md',
|
|
117
|
+
oldStr: '{{BODY}}',
|
|
118
|
+
newStr: chunks[0],
|
|
119
|
+
replace_all: false,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
...chunks.slice(1).map((chunk) => ({
|
|
123
|
+
name: 'edit',
|
|
124
|
+
input: {
|
|
125
|
+
path: 'outputs/long-document-flow.md',
|
|
126
|
+
insertLine: 'end',
|
|
127
|
+
newStr: chunk,
|
|
128
|
+
replace_all: false,
|
|
129
|
+
},
|
|
130
|
+
})),
|
|
131
|
+
])
|
|
132
|
+
const tools = new ToolRegistry()
|
|
133
|
+
tools.register(WriteFileTool)
|
|
134
|
+
tools.register(EditTool)
|
|
135
|
+
const events: RunEvent[] = []
|
|
136
|
+
|
|
137
|
+
const run = await drainQuery(
|
|
138
|
+
{
|
|
139
|
+
provider,
|
|
140
|
+
tools,
|
|
141
|
+
runConfig: {
|
|
142
|
+
model: 'mock-model',
|
|
143
|
+
timeoutMs: 5_000,
|
|
144
|
+
tokenBudget: 100_000,
|
|
145
|
+
maxIterations: 8,
|
|
146
|
+
maxResponseTokens: 256,
|
|
147
|
+
},
|
|
148
|
+
agentId: 'agent_long_doc',
|
|
149
|
+
agentName: 'Long Document Agent',
|
|
150
|
+
messages: [createUserMessage('create a long document')],
|
|
151
|
+
workingDirectory,
|
|
152
|
+
sessionId: 'ses_long_document_flow' as SessionId,
|
|
153
|
+
threadId: 'thd_long_document_flow' as ThreadId,
|
|
154
|
+
projectId: 'prj_long_document_flow' as ProjectId,
|
|
155
|
+
tenantId: 'tnt_long_document_flow' as TenantId,
|
|
156
|
+
},
|
|
157
|
+
(event) => {
|
|
158
|
+
events.push(event)
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const final = await readFile(join(workingDirectory, 'outputs/long-document-flow.md'), 'utf-8')
|
|
163
|
+
const executingTools = events
|
|
164
|
+
.filter(
|
|
165
|
+
(event): event is Extract<RunEvent, { type: 'tool_executing' }> =>
|
|
166
|
+
event.type === 'tool_executing',
|
|
167
|
+
)
|
|
168
|
+
.map((event) => event.toolName)
|
|
169
|
+
|
|
170
|
+
expect(run.status).toBe('completed')
|
|
171
|
+
expect(run.result).toBe('Long document created and verified.')
|
|
172
|
+
expect(provider.calls).toBe(6)
|
|
173
|
+
expect(executingTools).toEqual(['write', 'edit', 'edit', 'edit', 'edit'])
|
|
174
|
+
expect(final).not.toContain('{{BODY}}')
|
|
175
|
+
expect(final.split('\n').length).toBeGreaterThan(160)
|
|
176
|
+
expect(final).toContain('## Section 1')
|
|
177
|
+
expect(final).toContain('## Section 4')
|
|
178
|
+
expect(final).toContain('Section 4.40')
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -43,18 +43,21 @@ describe('PromptBuilder runtime context', () => {
|
|
|
43
43
|
skills: [
|
|
44
44
|
{
|
|
45
45
|
metadata: {
|
|
46
|
-
name: '
|
|
47
|
-
description: 'Draft and edit
|
|
46
|
+
name: 'delivery-briefing',
|
|
47
|
+
description: 'Draft and edit delivery briefings from grounded inputs.',
|
|
48
48
|
},
|
|
49
|
-
dirPath: '/repo/.agents/skills/
|
|
49
|
+
dirPath: '/repo/.agents/skills/delivery-briefing',
|
|
50
50
|
},
|
|
51
51
|
],
|
|
52
52
|
}).build('full', '/tmp/work')
|
|
53
53
|
|
|
54
54
|
expect(prompt).toContain('You are a project assistant.')
|
|
55
55
|
expect(prompt).toContain('## Available Skills')
|
|
56
|
-
expect(prompt).toContain('
|
|
57
|
-
expect(prompt).toContain('
|
|
56
|
+
expect(prompt).toContain('<available_skills>')
|
|
57
|
+
expect(prompt).toContain('delivery-briefing')
|
|
58
|
+
expect(prompt).toContain('Draft and edit delivery briefings')
|
|
59
|
+
expect(prompt).toContain('<location>/repo/.agents/skills/delivery-briefing/SKILL.md</location>')
|
|
60
|
+
expect(prompt).toContain('The following block is a manifest')
|
|
58
61
|
expect(prompt).not.toContain('## Loaded Skills')
|
|
59
62
|
})
|
|
60
63
|
|
|
@@ -65,22 +68,23 @@ describe('PromptBuilder runtime context', () => {
|
|
|
65
68
|
skills: [
|
|
66
69
|
{
|
|
67
70
|
metadata: {
|
|
68
|
-
name: '
|
|
69
|
-
description: 'Create
|
|
71
|
+
name: 'structured-file-authoring',
|
|
72
|
+
description: 'Create structured files with bounded edit chunks.',
|
|
70
73
|
license: 'MIT',
|
|
71
74
|
compatibility: 'Requires file tools',
|
|
72
75
|
allowedTools: 'read write edit',
|
|
73
76
|
},
|
|
74
77
|
body: 'Use skeleton-first writes and bounded edit chunks.',
|
|
75
|
-
dirPath: '/repo/.agents/skills/
|
|
78
|
+
dirPath: '/repo/.agents/skills/structured-file-authoring',
|
|
76
79
|
},
|
|
77
80
|
],
|
|
78
81
|
}).build('full', '/tmp/work')
|
|
79
82
|
|
|
80
83
|
expect(prompt).toContain('## Available Skills')
|
|
81
|
-
expect(prompt).toContain('license
|
|
82
|
-
expect(prompt).toContain('compatibility
|
|
83
|
-
expect(prompt).toContain('
|
|
84
|
+
expect(prompt).toContain('<license>MIT</license>')
|
|
85
|
+
expect(prompt).toContain('<compatibility>Requires file tools</compatibility>')
|
|
86
|
+
expect(prompt).toContain('<allowed_tools>read write edit</allowed_tools>')
|
|
87
|
+
expect(prompt).toContain('read the SKILL.md at its <location> before writing code')
|
|
84
88
|
expect(prompt).toContain('## Loaded Skills')
|
|
85
89
|
expect(prompt).toContain('Use skeleton-first writes')
|
|
86
90
|
})
|
|
@@ -16,6 +16,7 @@ import { buildTaskTools } from '../../tools/task/index.js'
|
|
|
16
16
|
import type { AdvisoryConfig } from '../../types/advisory/index.js'
|
|
17
17
|
import type { AgentRuntimeContext, RuntimeToolOverrides } from '../../types/agent/base.js'
|
|
18
18
|
import type { AgentContextLevel } from '../../types/agent/factory.js'
|
|
19
|
+
import type { WorkingMemoryProvider } from '../../types/agent/working-memory.js'
|
|
19
20
|
import {
|
|
20
21
|
type CheckpointId,
|
|
21
22
|
type ResumeHandler,
|
|
@@ -42,6 +43,8 @@ import { RunContextFactory } from './context.js'
|
|
|
42
43
|
import { EventTranslator } from './events.js'
|
|
43
44
|
import { GuardCoordinator } from './guard.js'
|
|
44
45
|
import { IterationOrchestrator } from './iteration/index.js'
|
|
46
|
+
import { isCompactionMessage } from './iteration/phases/compaction.js'
|
|
47
|
+
import { isWorkingMemoryMessage } from './iteration/phases/working-memory.js'
|
|
45
48
|
import { applyLifecycleHookResults } from './plugin-hooks.js'
|
|
46
49
|
import { PromptBuilder } from './prompt.js'
|
|
47
50
|
import type { PromptSegments } from './prompt.js'
|
|
@@ -127,6 +130,15 @@ export interface QueryParams {
|
|
|
127
130
|
|
|
128
131
|
compactionConfig?: CompactionConfig
|
|
129
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Optional neutral working-memory seam. When set, the iteration loop
|
|
135
|
+
* re-renders the provider's string into a single pinned leading system
|
|
136
|
+
* message every turn (the primacy-edge, compaction-preserved slot).
|
|
137
|
+
* Absent ⇒ `refreshWorkingMemory` early-returns and the run path is
|
|
138
|
+
* byte-identical.
|
|
139
|
+
*/
|
|
140
|
+
workingMemoryProvider?: WorkingMemoryProvider
|
|
141
|
+
|
|
130
142
|
agentBus?: import('../../bus/index.js').AgentBus
|
|
131
143
|
|
|
132
144
|
verificationGate?: VerificationGateConfig
|
|
@@ -187,7 +199,15 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
187
199
|
})
|
|
188
200
|
|
|
189
201
|
if (decision.action === 'approve_plan') {
|
|
190
|
-
|
|
202
|
+
// Optional approve-with-edits channel: the host may attach
|
|
203
|
+
// feedback to an approval. `PlanApprovalResponse.feedback`
|
|
204
|
+
// already exists on the type; threading it through lets the
|
|
205
|
+
// coordinator's approve_plan tool surface the user's edits in
|
|
206
|
+
// the same tool_result that unblocks the park. Bare approvals
|
|
207
|
+
// stay byte-identical (`{ approved: true }`).
|
|
208
|
+
return decision.feedback
|
|
209
|
+
? { approved: true, feedback: decision.feedback }
|
|
210
|
+
: { approved: true }
|
|
191
211
|
}
|
|
192
212
|
if (decision.action === 'reject_plan') {
|
|
193
213
|
return { approved: false, feedback: decision.feedback }
|
|
@@ -322,6 +342,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
322
342
|
advisoryCtx,
|
|
323
343
|
compactionConfig: params.compactionConfig,
|
|
324
344
|
workingStateManager,
|
|
345
|
+
workingMemoryProvider: params.workingMemoryProvider,
|
|
325
346
|
agentBus: params.agentBus,
|
|
326
347
|
verificationGate: verificationGate,
|
|
327
348
|
pluginManager: params.pluginManager,
|
|
@@ -408,7 +429,18 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
408
429
|
|
|
409
430
|
pushSystemMessages()
|
|
410
431
|
for (const msg of checkpoint.messages) {
|
|
411
|
-
if (msg.role === 'system')
|
|
432
|
+
if (msg.role === 'system') {
|
|
433
|
+
// Re-push the FRESH static/dynamic floor (done above) but PRESERVE
|
|
434
|
+
// the two system messages that carry irreplaceable run state: the
|
|
435
|
+
// `[COMPACTED CONTEXT]` summary is the only surviving record of the
|
|
436
|
+
// older history a compaction pass deleted, and the working-memory
|
|
437
|
+
// slot pins the produced-artifact ledger. Dropping every system
|
|
438
|
+
// message on restore silently lost both on resume.
|
|
439
|
+
if (isCompactionMessage(msg.content) || isWorkingMemoryMessage(msg.content)) {
|
|
440
|
+
ctx.runMgr.pushMessage(msg)
|
|
441
|
+
}
|
|
442
|
+
continue
|
|
443
|
+
}
|
|
412
444
|
ctx.runMgr.pushMessage(msg)
|
|
413
445
|
}
|
|
414
446
|
} else if (params.continuationMode) {
|