deepclause-sdk 0.1.1 → 0.1.2
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/dist/agent.d.ts +12 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +116 -9
- package/dist/agent.js.map +1 -1
- package/dist/cli/config.d.ts +24 -24
- package/dist/cli/repl.d.ts +2 -0
- package/dist/cli/repl.d.ts.map +1 -1
- package/dist/cli/repl.js +26 -0
- package/dist/cli/repl.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +10 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/prolog/loader.d.ts +32 -0
- package/dist/prolog/loader.d.ts.map +1 -1
- package/dist/prolog/loader.js +87 -0
- package/dist/prolog/loader.js.map +1 -1
- package/dist/prolog-src/deepclause_mi.pl +1028 -118
- package/dist/repl/agent.d.ts +15 -1
- package/dist/repl/agent.d.ts.map +1 -1
- package/dist/repl/agent.js +44 -5
- package/dist/repl/agent.js.map +1 -1
- package/dist/repl/harness-tools.d.ts +24 -2
- package/dist/repl/harness-tools.d.ts.map +1 -1
- package/dist/repl/harness-tools.js +14 -5
- package/dist/repl/harness-tools.js.map +1 -1
- package/dist/repl/index.d.ts +8 -0
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +1 -0
- package/dist/repl/index.js.map +1 -1
- package/dist/repl/render.d.ts +8 -1
- package/dist/repl/render.d.ts.map +1 -1
- package/dist/repl/render.js +81 -16
- package/dist/repl/render.js.map +1 -1
- package/dist/repl/session.d.ts +61 -4
- package/dist/repl/session.d.ts.map +1 -1
- package/dist/repl/session.js +174 -9
- package/dist/repl/session.js.map +1 -1
- package/dist/repl/tool-arguments.d.ts +36 -0
- package/dist/repl/tool-arguments.d.ts.map +1 -0
- package/dist/repl/tool-arguments.js +123 -0
- package/dist/repl/tool-arguments.js.map +1 -0
- package/dist/repl/toolset.d.ts +28 -1
- package/dist/repl/toolset.d.ts.map +1 -1
- package/dist/repl/toolset.js +52 -4
- package/dist/repl/toolset.js.map +1 -1
- package/dist/runner.d.ts +31 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +192 -17
- package/dist/runner.js.map +1 -1
- package/dist/system/assets/docs/AGENT_PROMPT.md +925 -43
- package/dist/system/assets/docs/DML_AUTHORING.md +96 -17
- package/dist/system/assets/docs/DML_REFERENCE.md +620 -39
- package/dist/system/assets/docs/PROLOG_SNIPPETS.md +45 -1
- package/dist/system/assets/docs/TASK_PROMPT.md +2 -0
- package/dist/system/environment.d.ts +36 -0
- package/dist/system/environment.d.ts.map +1 -0
- package/dist/system/environment.js +81 -0
- package/dist/system/environment.js.map +1 -0
- package/dist/system/runtime/shell-manager.d.ts +40 -2
- package/dist/system/runtime/shell-manager.d.ts.map +1 -1
- package/dist/system/runtime/shell-manager.js +82 -16
- package/dist/system/runtime/shell-manager.js.map +1 -1
- package/dist/system/runtime/shell-tool-events.js +25 -1
- package/dist/system/runtime/shell-tool-events.js.map +1 -1
- package/dist/types.d.ts +20 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/prolog-src/deepclause_mi.pl +1028 -118
- package/dist/compaction.d.ts +0 -98
- package/dist/compaction.d.ts.map +0 -1
- package/dist/compaction.js +0 -471
- package/dist/compaction.js.map +0 -1
- package/dist/repl/tools.d.ts +0 -53
- package/dist/repl/tools.d.ts.map +0 -1
- package/dist/repl/tools.js +0 -307
- package/dist/repl/tools.js.map +0 -1
- package/dist/system/assets/compactors/default-loop-compactor.dml +0 -43
- package/dist/system/assets/compactors/default-session-compactor.dml +0 -30
- package/dist/system/assets/docs/META_PROMPT.md +0 -68
package/dist/runner.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { mountWorkspace } from './prolog/loader.js';
|
|
5
5
|
import { runAgentLoop } from './agent.js';
|
|
6
|
+
import { takePrologMessages } from './prolog/loader.js';
|
|
7
|
+
import { validateToolArguments } from './repl/tool-arguments.js';
|
|
6
8
|
import { judgeAnswersToValues, judgeCacheKey, normalizeJudgeState, parseJudgeQuestions, validateJudgeAnswers, valuesToPrologList } from './judge/marshal.js';
|
|
7
9
|
import { buildToolCompletionEvent, buildToolFailureEvent, buildToolStartEvent, } from './system/runtime/shell-tool-events.js';
|
|
8
10
|
import { checkToolPolicy } from './tools.js';
|
|
@@ -111,11 +113,66 @@ export function decodeAgentOutputVars(rawOutputVars) {
|
|
|
111
113
|
&& 'type' in value
|
|
112
114
|
&& typeof value.name === 'string'
|
|
113
115
|
&& typeof value.type === 'string') {
|
|
114
|
-
|
|
116
|
+
const record = value;
|
|
117
|
+
const typed = value;
|
|
118
|
+
if (Array.isArray(record.fields)) {
|
|
119
|
+
typed.fields = record.fields
|
|
120
|
+
.map((raw) => {
|
|
121
|
+
const field = toJsValue(raw);
|
|
122
|
+
if (!field || typeof field !== 'object')
|
|
123
|
+
return undefined;
|
|
124
|
+
const f = field;
|
|
125
|
+
const name = f.name === undefined ? undefined : String(toJsValue(f.name));
|
|
126
|
+
if (!name)
|
|
127
|
+
return undefined;
|
|
128
|
+
const required = Number(toJsValue(f.required)) === 1;
|
|
129
|
+
const rawDefault = toJsValue(f.default);
|
|
130
|
+
const hasDefault = rawDefault !== undefined && rawDefault !== 'no_default';
|
|
131
|
+
return { name, type: String(toJsValue(f.type) ?? 'string'), required, ...(hasDefault ? { default: rawDefault } : {}) };
|
|
132
|
+
})
|
|
133
|
+
.filter((field) => field !== undefined);
|
|
134
|
+
}
|
|
135
|
+
return typed;
|
|
115
136
|
}
|
|
116
137
|
return String(value);
|
|
117
138
|
});
|
|
118
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* User tool info from Prolog
|
|
142
|
+
*/
|
|
143
|
+
/**
|
|
144
|
+
* The error a tool call gets when the tool is already running further up the
|
|
145
|
+
* call chain. Shared by the runtime guard and the withheld-tool stub.
|
|
146
|
+
*/
|
|
147
|
+
export function unavailableToolError(toolName) {
|
|
148
|
+
return `Tool '${toolName}' is not available in this task: it is already running further up the `
|
|
149
|
+
+ 'call chain, and calling it again would recurse. Do not call it again; work with the text '
|
|
150
|
+
+ 'you were given, or say what is missing.';
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Parse the `toolScope` a `task/N` carries from `with_tools/2` / `without_tools/2`.
|
|
154
|
+
*
|
|
155
|
+
* Shape: `{$t:'t', whitelist:[[Names]]}` / `blacklist:[[Names]]`, or the atom
|
|
156
|
+
* `none`. The extra list nesting comes from the term's argument list.
|
|
157
|
+
*/
|
|
158
|
+
export function parseToolScope(raw) {
|
|
159
|
+
const value = toJsValue(raw);
|
|
160
|
+
if (!value || typeof value !== 'object' || !('$t' in value)) {
|
|
161
|
+
return { type: 'none', list: [] };
|
|
162
|
+
}
|
|
163
|
+
const compound = value;
|
|
164
|
+
const kind = 'whitelist' in compound ? 'whitelist' : 'blacklist' in compound ? 'blacklist' : null;
|
|
165
|
+
if (!kind)
|
|
166
|
+
return { type: 'none', list: [] };
|
|
167
|
+
const args = toJsValue(compound[kind]);
|
|
168
|
+
if (!Array.isArray(args) || args.length === 0)
|
|
169
|
+
return { type: kind, list: [] };
|
|
170
|
+
const inner = toJsValue(args[0]);
|
|
171
|
+
if (!Array.isArray(inner) || inner.length === 0)
|
|
172
|
+
return { type: kind, list: [] };
|
|
173
|
+
const names = toJsValue(inner[0]);
|
|
174
|
+
return { type: kind, list: Array.isArray(names) ? names.map((n) => String(n)) : [] };
|
|
175
|
+
}
|
|
119
176
|
/**
|
|
120
177
|
* Map Prolog types to JSON Schema types
|
|
121
178
|
*/
|
|
@@ -145,6 +202,16 @@ export class DMLRunner {
|
|
|
145
202
|
currentMemory = [];
|
|
146
203
|
stepCounter = 0;
|
|
147
204
|
judgeCache = new Map();
|
|
205
|
+
/**
|
|
206
|
+
* DML tools currently executing, innermost last.
|
|
207
|
+
*
|
|
208
|
+
* A tool must not be reachable by any agent it spawns, or `foo(A) :- task(...)`
|
|
209
|
+
* recurses: the agent inside `task/N` calls `foo`, which spawns another agent,
|
|
210
|
+
* and so on. The tool list is built *before* the tool runs (so a static list
|
|
211
|
+
* cannot know), which is why the check is applied when the model calls the
|
|
212
|
+
* tool rather than when the list is built.
|
|
213
|
+
*/
|
|
214
|
+
toolCallStack = [];
|
|
148
215
|
/**
|
|
149
216
|
* Runner-owned frame table: FrameId -> { frame, engine }.
|
|
150
217
|
* The Prolog session owns the authoritative engine handles
|
|
@@ -203,6 +270,9 @@ export class DMLRunner {
|
|
|
203
270
|
yield { type: 'error', content: engineResult.error };
|
|
204
271
|
return;
|
|
205
272
|
}
|
|
273
|
+
// Now that the session module exists, install the workspace predicates so
|
|
274
|
+
// an SDK run resolves `read_file/2` and friends exactly like the REPL.
|
|
275
|
+
this.query(`deepclause_mi:repl_inject_workspace_predicates('${this.sessionId}')`);
|
|
206
276
|
const frame = this.defaultFrameId ? this.frames.get(this.defaultFrameId)?.frame : undefined;
|
|
207
277
|
if (!frame) {
|
|
208
278
|
yield { type: 'error', content: 'Failed to register default query frame' };
|
|
@@ -256,6 +326,15 @@ export class DMLRunner {
|
|
|
256
326
|
}
|
|
257
327
|
// Step the frame
|
|
258
328
|
const step = this.stepFrame(frame.id);
|
|
329
|
+
// Prolog's own output (warnings from the term reader, `format(user_error,
|
|
330
|
+
// ...)`, a stray `write/1`) is captured by the loader rather than printed,
|
|
331
|
+
// and surfaced here so it renders in order and through the normal path.
|
|
332
|
+
// This is what keeps a warning from scribbling over an interactive frame.
|
|
333
|
+
for (const message of takePrologMessages()) {
|
|
334
|
+
yield message.channel === 'stdout'
|
|
335
|
+
? { type: 'output', content: message.text, channel: message.channel }
|
|
336
|
+
: { type: 'log', content: message.text, level: message.level, channel: message.channel };
|
|
337
|
+
}
|
|
259
338
|
if (process.env.DEBUG_STEPS) {
|
|
260
339
|
console.log('[MAIN_LOOP] step:', step.status, step.content?.substring(0, 50));
|
|
261
340
|
}
|
|
@@ -753,14 +832,32 @@ export class DMLRunner {
|
|
|
753
832
|
// Build available tools for the agent: the harness's DML tool/2 predicates
|
|
754
833
|
// plus registered TS tools (reachable via exec/2).
|
|
755
834
|
const selfReportingTools = undefined;
|
|
756
|
-
|
|
835
|
+
// Tools already executing further up the call chain are NOT offered to this
|
|
836
|
+
// agent. This is the `task/N` inside a tool: the tool that spawned this agent
|
|
837
|
+
// is on the stack, and offering it would invite the model to call the thing
|
|
838
|
+
// that is already running. A withheld tool is still declared (with the reason
|
|
839
|
+
// in its description) so a stray call gets an explanation, but it cannot run.
|
|
840
|
+
const withheld = [...this.toolCallStack];
|
|
841
|
+
// `with_tools/2` / `without_tools/2` on this `task/N` narrow the list further.
|
|
842
|
+
// This path used to ignore the scope, so scoping a `task/N` inside a predicate
|
|
843
|
+
// silently did nothing.
|
|
844
|
+
const scope = parseToolScope(rawPayload.toolScope);
|
|
845
|
+
const scopedUserTools = scope.type === 'whitelist'
|
|
846
|
+
? userTools.filter((t) => scope.list.includes(t.name))
|
|
847
|
+
: scope.type === 'blacklist'
|
|
848
|
+
? userTools.filter((t) => !scope.list.includes(t.name))
|
|
849
|
+
: userTools;
|
|
850
|
+
const availableTools = this.buildAgentTools(scopedUserTools, options.toolPolicy, async (toolName, args) => {
|
|
851
|
+
if (this.toolCallStack.includes(toolName)) {
|
|
852
|
+
return { result: { error: unavailableToolError(toolName) }, outputs: [] };
|
|
853
|
+
}
|
|
757
854
|
return this.executeToolIsolated(toolName, args, augmentedTools, onToolOutput, {
|
|
758
855
|
workspacePath: options.workspacePath,
|
|
759
856
|
signal: options.signal,
|
|
760
857
|
toolPolicy: options.toolPolicy,
|
|
761
858
|
onModelCall: options.onModelCall,
|
|
762
|
-
},
|
|
763
|
-
});
|
|
859
|
+
}, withheld, emitToolCall, emitToolEvent);
|
|
860
|
+
}, withheld);
|
|
764
861
|
// Run agent loop with streaming support
|
|
765
862
|
const resultPromise = runAgentLoop({
|
|
766
863
|
taskDescription,
|
|
@@ -768,6 +865,7 @@ export class DMLRunner {
|
|
|
768
865
|
memory: effectiveMemory,
|
|
769
866
|
tools: availableTools,
|
|
770
867
|
workspacePath: options.workspacePath,
|
|
868
|
+
environment: this.options.environment,
|
|
771
869
|
resolveMedia: (refs) => this.readMediaRefs(options.workspacePath, refs),
|
|
772
870
|
modelOptions: {
|
|
773
871
|
model: scopedModel?.id ?? this.options.model,
|
|
@@ -1127,7 +1225,11 @@ export class DMLRunner {
|
|
|
1127
1225
|
? this.internalAskUserTool(options)
|
|
1128
1226
|
: options.tools.get(toolName);
|
|
1129
1227
|
if (!tool) {
|
|
1130
|
-
|
|
1228
|
+
// Emit start+failure so the renderer shows the reason. A lone `log` event
|
|
1229
|
+
// is not rendered in goal mode, so an unknown tool used to fail with no
|
|
1230
|
+
// explanation at all — just `false.`
|
|
1231
|
+
yield buildToolStartEvent(toolName, {});
|
|
1232
|
+
yield buildToolFailureEvent(toolName, {}, new Error(`Tool not found: ${toolName}`));
|
|
1131
1233
|
this.postExecResult({ success: false, error: `Tool not found: ${toolName}` });
|
|
1132
1234
|
return;
|
|
1133
1235
|
}
|
|
@@ -1135,6 +1237,15 @@ export class DMLRunner {
|
|
|
1135
1237
|
try {
|
|
1136
1238
|
// Execute tool
|
|
1137
1239
|
argsObj = this.argsToObject(args, tool);
|
|
1240
|
+
// A call that names a parameter the tool does not have cannot mean what it
|
|
1241
|
+
// says, and dropping the key silently is how `stdin` looked like it worked.
|
|
1242
|
+
const validation = validateToolArguments(toolName, tool.parameters, argsObj);
|
|
1243
|
+
if (!validation.ok) {
|
|
1244
|
+
yield buildToolStartEvent(toolName, argsObj);
|
|
1245
|
+
yield buildToolFailureEvent(toolName, argsObj, new Error(validation.error));
|
|
1246
|
+
this.postExecResult({ success: false, error: validation.error });
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1138
1249
|
// Emit tool_call event before execution
|
|
1139
1250
|
yield buildToolStartEvent(toolName, argsObj);
|
|
1140
1251
|
const result = await tool.execute(argsObj, { signal: options.signal });
|
|
@@ -1309,14 +1420,26 @@ export class DMLRunner {
|
|
|
1309
1420
|
* Only includes tools defined in DML - registered TypeScript tools are NOT exposed to the LLM.
|
|
1310
1421
|
* DML tools can internally call exec/2 to use registered tools.
|
|
1311
1422
|
*/
|
|
1312
|
-
buildAgentTools(userTools, _policy, executeToolInline) {
|
|
1423
|
+
buildAgentTools(userTools, _policy, executeToolInline, excludedTools = []) {
|
|
1313
1424
|
const result = new Map();
|
|
1425
|
+
if (process.env.DC_TRACE_TOOLS) {
|
|
1426
|
+
// Append to a file: inside the TUI, stderr is not visible.
|
|
1427
|
+
try {
|
|
1428
|
+
const { appendFileSync } = require('fs');
|
|
1429
|
+
appendFileSync(process.env.DC_TRACE_TOOLS, `executing=${JSON.stringify(this.toolCallStack)} withheld=${JSON.stringify(excludedTools)} offered=${JSON.stringify(userTools.map((t) => t.name))}\n`);
|
|
1430
|
+
}
|
|
1431
|
+
catch { /* diagnostics must not break a run */ }
|
|
1432
|
+
}
|
|
1314
1433
|
// Add user-defined tools from DML file with schema info
|
|
1315
1434
|
// These are the ONLY tools the LLM can call during task()
|
|
1316
1435
|
// Do not apply the host runtime-tool policy to these DML predicates. Their
|
|
1317
1436
|
// nested exec/2 calls pass through handleExec/executeToolIsolated, where
|
|
1318
1437
|
// the policy still restricts actual host capabilities.
|
|
1319
1438
|
for (const tool of userTools) {
|
|
1439
|
+
// A withheld tool keeps its place in the list (so a call gets the reason),
|
|
1440
|
+
// but carries no schema and a refusing implementation.
|
|
1441
|
+
if (excludedTools.includes(tool.name))
|
|
1442
|
+
continue;
|
|
1320
1443
|
// Build JSON Schema properties from inputs
|
|
1321
1444
|
const properties = {};
|
|
1322
1445
|
const required = [];
|
|
@@ -1339,12 +1462,33 @@ export class DMLRunner {
|
|
|
1339
1462
|
required: required.length > 0 ? required : undefined,
|
|
1340
1463
|
},
|
|
1341
1464
|
execute: async (args) => {
|
|
1465
|
+
if (process.env.DC_TRACE_TOOLS) {
|
|
1466
|
+
// eslint-disable-next-line no-console
|
|
1467
|
+
console.error('[toolCall] %s executing=[%s] -> %s', toolName, this.toolCallStack.join(','), this.toolCallStack.includes(toolName) ? 'REFUSED' : 'ran');
|
|
1468
|
+
}
|
|
1469
|
+
// The list is built before the tool runs, so a tool already on the call
|
|
1470
|
+
// stack is refused here rather than being withheld from the list.
|
|
1471
|
+
if (this.toolCallStack.includes(toolName)) {
|
|
1472
|
+
return { error: unavailableToolError(toolName) };
|
|
1473
|
+
}
|
|
1342
1474
|
// Execute tool inline via main Prolog engine (shares state with task)
|
|
1343
1475
|
const { result: toolResult } = await executeToolInline(toolName, args);
|
|
1344
1476
|
return toolResult;
|
|
1345
1477
|
},
|
|
1346
1478
|
});
|
|
1347
1479
|
}
|
|
1480
|
+
// Withheld tools are still declared, but their implementation refuses with
|
|
1481
|
+
// the reason. Dropping them entirely makes a call look like a missing tool,
|
|
1482
|
+
// which reads as a broken harness and invites a retry loop.
|
|
1483
|
+
for (const tool of userTools) {
|
|
1484
|
+
if (!excludedTools.includes(tool.name))
|
|
1485
|
+
continue;
|
|
1486
|
+
result.set(tool.name, {
|
|
1487
|
+
description: `${tool.description || `Tool: ${tool.name}`} (unavailable in this task)`,
|
|
1488
|
+
parameters: { type: 'object', properties: {} },
|
|
1489
|
+
execute: async () => ({ error: unavailableToolError(tool.name) }),
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1348
1492
|
// NOTE: Registered TypeScript tools are NOT added here.
|
|
1349
1493
|
// They are only available via exec/2 in DML code.
|
|
1350
1494
|
// DML tools can call exec/2 internally to use registered tools.
|
|
@@ -1366,6 +1510,20 @@ export class DMLRunner {
|
|
|
1366
1510
|
* @param onToolCall - Callback to emit tool call events from nested tasks
|
|
1367
1511
|
*/
|
|
1368
1512
|
async executeToolIsolated(toolName, args, registeredTools, onOutput, runContext, excludedTools = [], onToolCall, onToolEvent) {
|
|
1513
|
+
if (this.toolCallStack.includes(toolName)) {
|
|
1514
|
+
return { result: { error: unavailableToolError(toolName) }, outputs: [] };
|
|
1515
|
+
}
|
|
1516
|
+
this.toolCallStack.push(toolName);
|
|
1517
|
+
try {
|
|
1518
|
+
return await this.executeToolIsolatedInner(toolName, args, registeredTools, onOutput, runContext, excludedTools, onToolCall, onToolEvent);
|
|
1519
|
+
}
|
|
1520
|
+
finally {
|
|
1521
|
+
const index = this.toolCallStack.lastIndexOf(toolName);
|
|
1522
|
+
if (index !== -1)
|
|
1523
|
+
this.toolCallStack.splice(index, 1);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
async executeToolIsolatedInner(toolName, args, registeredTools, onOutput, runContext, excludedTools = [], onToolCall, onToolEvent) {
|
|
1369
1527
|
const outputs = [];
|
|
1370
1528
|
if (process.env.DEBUG_RUNNER) {
|
|
1371
1529
|
console.log('[RUNNER] executeToolIsolated called for:', toolName);
|
|
@@ -1422,6 +1580,8 @@ export class DMLRunner {
|
|
|
1422
1580
|
const tool = registeredTools.get(execToolName);
|
|
1423
1581
|
if (!tool) {
|
|
1424
1582
|
// Tool not found - post failure and continue
|
|
1583
|
+
onToolEvent?.(buildToolStartEvent(execToolName, {}));
|
|
1584
|
+
onToolEvent?.(buildToolFailureEvent(execToolName, {}, new Error(`Tool not found: ${execToolName}`)));
|
|
1425
1585
|
const postGoal = `deepclause_mi:post_exec_result('${this.sessionId}', failure, "Tool not found: ${execToolName}")`;
|
|
1426
1586
|
this.query(postGoal);
|
|
1427
1587
|
}
|
|
@@ -1429,6 +1589,13 @@ export class DMLRunner {
|
|
|
1429
1589
|
let argsObj = null;
|
|
1430
1590
|
try {
|
|
1431
1591
|
argsObj = this.argsToObject(execArgs, tool);
|
|
1592
|
+
const validation = validateToolArguments(execToolName, tool.parameters, argsObj);
|
|
1593
|
+
if (!validation.ok) {
|
|
1594
|
+
onToolEvent?.(buildToolStartEvent(execToolName, argsObj));
|
|
1595
|
+
onToolEvent?.(buildToolFailureEvent(execToolName, argsObj, new Error(validation.error)));
|
|
1596
|
+
this.query(`deepclause_mi:post_exec_result('${this.sessionId}', failure, ${JSON.stringify(validation.error)})`);
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1432
1599
|
onToolEvent?.(buildToolStartEvent(execToolName, argsObj));
|
|
1433
1600
|
const execResult = await tool.execute(argsObj, { signal: runContext.signal });
|
|
1434
1601
|
onToolEvent?.(buildToolCompletionEvent(execToolName, argsObj, execResult));
|
|
@@ -1623,19 +1790,24 @@ export class DMLRunner {
|
|
|
1623
1790
|
}
|
|
1624
1791
|
}
|
|
1625
1792
|
}
|
|
1626
|
-
//
|
|
1627
|
-
|
|
1628
|
-
//
|
|
1629
|
-
//
|
|
1630
|
-
|
|
1631
|
-
|
|
1793
|
+
// Withhold every tool that is executing at this point, not just this one
|
|
1794
|
+
// and whatever was passed in: a tool reached by a longer chain (tool →
|
|
1795
|
+
// tool → task/N) must not be offered to the agent at the end of it
|
|
1796
|
+
// either. `this.toolCallStack` is the authoritative stack.
|
|
1797
|
+
const nestedExcludedTools = [
|
|
1798
|
+
...new Set([...excludedTools, ...this.toolCallStack, toolName]),
|
|
1799
|
+
];
|
|
1800
|
+
// Manual scope (with_tools/without_tools). Call-stack exclusion is
|
|
1801
|
+
// applied inside buildAgentTools, which needs the withheld names so
|
|
1802
|
+
// it can declare them and refuse them with the reason.
|
|
1803
|
+
let scopedTools = userTools;
|
|
1632
1804
|
if (toolScopeType === 'whitelist') {
|
|
1633
1805
|
// Only include tools in the whitelist
|
|
1634
|
-
|
|
1806
|
+
scopedTools = scopedTools.filter(t => toolScopeList.includes(t.name));
|
|
1635
1807
|
}
|
|
1636
1808
|
else if (toolScopeType === 'blacklist') {
|
|
1637
1809
|
// Exclude tools in the blacklist
|
|
1638
|
-
|
|
1810
|
+
scopedTools = scopedTools.filter(t => !toolScopeList.includes(t.name));
|
|
1639
1811
|
}
|
|
1640
1812
|
// Extract memory
|
|
1641
1813
|
const memory = this.extractMemoryFromPayload(payload);
|
|
@@ -1663,7 +1835,7 @@ export class DMLRunner {
|
|
|
1663
1835
|
console.log('[RUNNER] Tool agent outputVars:', outputVars);
|
|
1664
1836
|
console.log('[RUNNER] Call stack excluded tools:', nestedExcludedTools);
|
|
1665
1837
|
console.log('[RUNNER] Tool scope:', toolScopeType, toolScopeList);
|
|
1666
|
-
console.log('[RUNNER] Available tools for nested agent:',
|
|
1838
|
+
console.log('[RUNNER] Available tools for nested agent:', scopedTools.map(t => t.name));
|
|
1667
1839
|
console.log('[RUNNER] Memory passed to nested agent:', memory.length, 'messages');
|
|
1668
1840
|
if (memory.length > 0) {
|
|
1669
1841
|
console.log('[RUNNER] Memory contents:', JSON.stringify(memory, null, 2));
|
|
@@ -1673,8 +1845,8 @@ export class DMLRunner {
|
|
|
1673
1845
|
const nestedToolCallback = async (nestedToolName, nestedArgs) => {
|
|
1674
1846
|
return this.executeToolIsolated(nestedToolName, nestedArgs, registeredTools, onOutput, runContext, nestedExcludedTools, onToolCall, onToolEvent);
|
|
1675
1847
|
};
|
|
1676
|
-
const availableTools = this.buildAgentTools(
|
|
1677
|
-
nestedToolCallback);
|
|
1848
|
+
const availableTools = this.buildAgentTools(scopedTools, null, // No policy override for nested
|
|
1849
|
+
nestedToolCallback, nestedExcludedTools);
|
|
1678
1850
|
const nestedStepId = `step_${this.stepCounter++}`;
|
|
1679
1851
|
onToolEvent?.({ type: 'task_activity', taskState: 'started', taskDescription, taskId: nestedStepId });
|
|
1680
1852
|
try {
|
|
@@ -1684,6 +1856,9 @@ export class DMLRunner {
|
|
|
1684
1856
|
outputVars,
|
|
1685
1857
|
memory: nestedEffectiveMemory,
|
|
1686
1858
|
tools: availableTools,
|
|
1859
|
+
// Withheld to break a call cycle; declared to the model so a
|
|
1860
|
+
// call can be answered with the reason rather than "not found".
|
|
1861
|
+
excludedTools: nestedExcludedTools,
|
|
1687
1862
|
modelOptions: {
|
|
1688
1863
|
model: this.options.model,
|
|
1689
1864
|
provider: this.options.provider,
|