deepclause-sdk 0.0.68 → 0.0.70
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/README.md +10 -2
- package/dist/agent.d.ts +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +252 -67
- package/dist/agent.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +64 -7
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/config.d.ts +15 -12
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +38 -5
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/default-skills.d.ts +1 -0
- package/dist/cli/default-skills.d.ts.map +1 -1
- package/dist/cli/default-skills.js +31 -0
- package/dist/cli/default-skills.js.map +1 -1
- package/dist/cli/tui.d.ts +16 -1
- package/dist/cli/tui.d.ts.map +1 -1
- package/dist/cli/tui.js +293 -21
- package/dist/cli/tui.js.map +1 -1
- package/dist/prolog/bridge.d.ts +15 -0
- package/dist/prolog/bridge.d.ts.map +1 -1
- package/dist/prolog/bridge.js +68 -0
- package/dist/prolog/bridge.js.map +1 -1
- package/dist/prolog-src/deepclause_mi.pl +135 -7
- package/dist/runner.d.ts +8 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +79 -0
- package/dist/runner.js.map +1 -1
- package/dist/system/assets/docs/DML_COMPILER_PROMPT.md +7 -3
- package/dist/system/assets/docs/DML_REFERENCE.md +656 -0
- package/dist/system/assets/docs/TASK_PROMPT.md +33 -0
- package/dist/system/assets/docs/TUI.md +11 -0
- package/dist/system/assets/index.d.ts +12 -9
- package/dist/system/assets/index.d.ts.map +1 -1
- package/dist/system/assets/index.js +38 -9
- package/dist/system/assets/index.js.map +1 -1
- package/dist/system/assets/recipes/deepclause-coding-workflow/SKILL.md +56 -1
- package/dist/system/assets/skills/plan.dml +259 -0
- package/dist/system/runtime/catalog-skills.d.ts +1 -1
- package/dist/system/runtime/catalog-skills.d.ts.map +1 -1
- package/dist/system/runtime/catalog-skills.js +19 -5
- package/dist/system/runtime/catalog-skills.js.map +1 -1
- package/dist/system/runtime/conductor.d.ts +5 -1
- package/dist/system/runtime/conductor.d.ts.map +1 -1
- package/dist/system/runtime/conductor.js +22 -0
- package/dist/system/runtime/conductor.js.map +1 -1
- package/dist/system/runtime/dml-executor.d.ts +1 -1
- package/dist/system/runtime/dml-executor.d.ts.map +1 -1
- package/dist/system/runtime/dml-executor.js +2 -14
- package/dist/system/runtime/dml-executor.js.map +1 -1
- package/dist/system/runtime/dml-params.d.ts +2 -2
- package/dist/system/runtime/dml-params.d.ts.map +1 -1
- package/dist/system/runtime/dml-params.js +3 -0
- package/dist/system/runtime/dml-params.js.map +1 -1
- package/dist/system/runtime/runtime-tools.d.ts +1 -0
- package/dist/system/runtime/runtime-tools.d.ts.map +1 -1
- package/dist/system/runtime/runtime-tools.js +95 -0
- package/dist/system/runtime/runtime-tools.js.map +1 -1
- package/dist/system/runtime/shell-manager.js +1 -1
- package/dist/system/runtime/shell-manager.js.map +1 -1
- package/dist/system/runtime/skill-creator.d.ts.map +1 -1
- package/dist/system/runtime/skill-creator.js +52 -21
- package/dist/system/runtime/skill-creator.js.map +1 -1
- package/package.json +4 -3
- package/src/prolog-src/deepclause_mi.pl +135 -7
package/README.md
CHANGED
|
@@ -71,6 +71,7 @@ All commands except `init`, `help`, `--help`, `--version`, and `-V` expect a `.d
|
|
|
71
71
|
- `skill-creator.dml`
|
|
72
72
|
- `CONDUCTOR_PROMPT.md`
|
|
73
73
|
- `DML_COMPILER_PROMPT.md`
|
|
74
|
+
- `TASK_PROMPT.md`
|
|
74
75
|
|
|
75
76
|
The default recipe is created at `.deepclause/system/recipes/deepclause-coding-workflow/SKILL.md`. It is a real example, not a placeholder: it teaches the conductor how to approach local repository changes with small edits and focused validation. You can edit it, remove it, or add your own recipes next to it.
|
|
76
77
|
|
|
@@ -85,6 +86,9 @@ Running `deepclause` with no subcommand starts the fullscreen TUI.
|
|
|
85
86
|
- Execution log and context summary on the right
|
|
86
87
|
- Slash commands such as `/new`, `/sessions`, `/set-model <model> [--slot <slot>]`, `/compile <spec>`, `/skill-creator <spec>`, `/cancel`, and `/<skill> [args]`
|
|
87
88
|
- Direct shell commands with `!<command>`, streamed live in the execution pane and cancelled with `/cancel`
|
|
89
|
+
- Persistent shell commands with `!!<command>`, which also append the command and its output to the active session transcript so the conductor can see them on the next turn
|
|
90
|
+
|
|
91
|
+
Use `!<command>` when you just want an ephemeral shell escape in the execution pane. Use `!!<command>` when the shell result should become part of the conversation state for later conductor turns.
|
|
88
92
|
|
|
89
93
|
For non-interactive use, `deepclause -p "..."` runs a single headless conductor turn with a fresh session.
|
|
90
94
|
|
|
@@ -228,10 +232,13 @@ If you want to customize behavior for one workspace without modifying the packag
|
|
|
228
232
|
- `.deepclause/system/skill-creator.dml`
|
|
229
233
|
- `.deepclause/system/CONDUCTOR_PROMPT.md`
|
|
230
234
|
- `.deepclause/system/DML_COMPILER_PROMPT.md`
|
|
235
|
+
- `.deepclause/system/TASK_PROMPT.md`
|
|
231
236
|
- `.deepclause/system/recipes/<recipe-slug>/SKILL.md`
|
|
232
237
|
|
|
233
238
|
When present, the CLI runtime prefers those files over the packaged system DML and system prompt markdown. For recipes, workspace files override packaged recipes with the same slug.
|
|
234
239
|
|
|
240
|
+
`.deepclause/system/TASK_PROMPT.md` is the markdown template used for `task(...)` and `prompt(...)` harness calls. `deepclause init` seeds it into the workspace, and the runtime resolves that workspace copy before falling back to the packaged default.
|
|
241
|
+
|
|
235
242
|
#### 2. Source-level hacking in this repository
|
|
236
243
|
|
|
237
244
|
If you are developing DeepClause itself, these are the main files to edit:
|
|
@@ -240,6 +247,7 @@ If you are developing DeepClause itself, these are the main files to edit:
|
|
|
240
247
|
- `src/system/assets/skills/skill-creator.dml` - the skill creator's DML logic
|
|
241
248
|
- `src/system/assets/docs/CONDUCTOR_PROMPT.md` - the conductor system prompt template
|
|
242
249
|
- `src/system/assets/docs/DML_COMPILER_PROMPT.md` - the skill creator/compiler system prompt template
|
|
250
|
+
- `src/system/assets/docs/TASK_PROMPT.md` - the `task(...)` / `prompt(...)` harness template
|
|
243
251
|
- `src/system/assets/recipes/` - packaged default recipes copied into new workspaces
|
|
244
252
|
- `src/system/runtime/conductor.ts` - session loading, memory injection, tool registration, child-skill routing
|
|
245
253
|
- `src/system/runtime/skill-creator.ts` - compile-slot execution, skill-creator tool registration, validation/testing/deploy flow
|
|
@@ -250,8 +258,8 @@ Notes:
|
|
|
250
258
|
- The conductor uses the `gateway` model slot.
|
|
251
259
|
- Normal compiled skills use the `run` model slot.
|
|
252
260
|
- The skill creator uses the `compile` model slot.
|
|
253
|
-
- The TUI context pane shows the resolved source paths for the conductor DML/prompt
|
|
254
|
-
- Changes to those `.deepclause/system/` files are picked up on the next conductor turn
|
|
261
|
+
- The TUI context pane shows the resolved source paths for the conductor DML/prompt, skill creator DML/prompt, and task prompt template.
|
|
262
|
+
- Changes to those `.deepclause/system/` files are picked up on the next conductor turn, skill-creator run, or `task(...)` / `prompt(...)` execution; a dedicated TUI reload is not required.
|
|
255
263
|
|
|
256
264
|
After source-level changes, rebuild the package:
|
|
257
265
|
|
package/dist/agent.d.ts
CHANGED
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAwC1E,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,CAAC,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;IAClC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,OAAO,CAAC,EAAE,MAAM,CAAC;QAEjB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;KACvD,CAAC;IACF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACvE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,YAAY,EAAE,QAAQ,KAAK,IAAI,CAAC;IACzD,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC5E,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,mGAAmG;IACnG,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AAqWD;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAuqBtF"}
|
package/dist/agent.js
CHANGED
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
import { generateText, streamText, hasToolCall, tool as aiTool } from 'ai';
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import { createModelProvider } from './prolog/bridge.js';
|
|
12
|
+
import { readSystemPromptAsset } from './system/assets/index.js';
|
|
12
13
|
/** Maximum number of retries for LLM error finish reasons */
|
|
13
14
|
const MAX_ERROR_RETRIES = 3;
|
|
14
15
|
const DEFAULT_STREAM_RESPONSE_AWAIT_TIMEOUT_MS = 2_000;
|
|
16
|
+
const MAX_STALLED_ITERATIONS = 3;
|
|
15
17
|
/**
|
|
16
18
|
* Clean Prolog dict markers ($tag, $t) from tool results
|
|
17
19
|
* This makes the data more readable for the LLM
|
|
@@ -59,6 +61,110 @@ function normalizeMessagesForCompaction(messages) {
|
|
|
59
61
|
content: normalizeMessageContent(message.content),
|
|
60
62
|
}));
|
|
61
63
|
}
|
|
64
|
+
function normalizeLoopText(text) {
|
|
65
|
+
return text.trim().replace(/\s+/g, ' ').toLowerCase().slice(0, 400);
|
|
66
|
+
}
|
|
67
|
+
function sortJsonValue(value) {
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
return value.map(sortJsonValue);
|
|
70
|
+
}
|
|
71
|
+
if (!isPlainObject(value)) {
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
return Object.fromEntries(Object.entries(value)
|
|
75
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
76
|
+
.map(([key, entryValue]) => [key, sortJsonValue(entryValue)]));
|
|
77
|
+
}
|
|
78
|
+
function stableSerialize(value) {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.stringify(sortJsonValue(value));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return String(value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function buildIterationSignature(options) {
|
|
87
|
+
return stableSerialize({
|
|
88
|
+
finishReason: options.finishReason,
|
|
89
|
+
text: normalizeLoopText(options.text),
|
|
90
|
+
toolCalls: options.toolCalls.map((toolCall) => ({
|
|
91
|
+
toolName: toolCall.toolName,
|
|
92
|
+
input: toolCall.input,
|
|
93
|
+
})),
|
|
94
|
+
variables: options.variables,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function buildRecoveryPrompt(outputVars, repeatedStallCount, noToolCalls) {
|
|
98
|
+
const resultInstruction = outputVars.length > 0
|
|
99
|
+
? `If you can answer now, call set_result for ${outputVars.map((variable) => variable.name).join(', ')} and then finish(true).`
|
|
100
|
+
: 'If you can answer now, call finish(true).';
|
|
101
|
+
if (repeatedStallCount > 0) {
|
|
102
|
+
return [
|
|
103
|
+
'You are repeating the same response or action without making progress.',
|
|
104
|
+
'Do not repeat the previous step.',
|
|
105
|
+
resultInstruction,
|
|
106
|
+
'Otherwise call one different tool, or call finish(false) if the task is blocked.',
|
|
107
|
+
].join(' ');
|
|
108
|
+
}
|
|
109
|
+
if (noToolCalls) {
|
|
110
|
+
return [
|
|
111
|
+
'You did not call any tool and you did not finish the subtask.',
|
|
112
|
+
resultInstruction,
|
|
113
|
+
'If more information is required, call exactly one relevant tool.',
|
|
114
|
+
'If the task cannot be completed, call finish(false).',
|
|
115
|
+
].join(' ');
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function truncateSummaryText(text, maxLength = 240) {
|
|
120
|
+
if (text.length <= maxLength) {
|
|
121
|
+
return text;
|
|
122
|
+
}
|
|
123
|
+
return text.slice(0, Math.max(0, maxLength - 3)) + '...';
|
|
124
|
+
}
|
|
125
|
+
function renderSummaryValue(value, maxLength = 140) {
|
|
126
|
+
if (value === undefined) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const cleaned = cleanPrologMarkers(value);
|
|
130
|
+
const rendered = typeof cleaned === 'string' ? cleaned : stableSerialize(cleaned);
|
|
131
|
+
const normalized = rendered.trim().replace(/\s+/g, ' ');
|
|
132
|
+
if (!normalized) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return truncateSummaryText(normalized, maxLength);
|
|
136
|
+
}
|
|
137
|
+
function summarizeCompletedToolActivity(activity) {
|
|
138
|
+
const renderedInput = renderSummaryValue(activity.input, 100);
|
|
139
|
+
const renderedOutput = renderSummaryValue(activity.output, 140);
|
|
140
|
+
if (renderedInput && renderedOutput) {
|
|
141
|
+
return `${activity.toolName}(${renderedInput}) -> ${renderedOutput}`;
|
|
142
|
+
}
|
|
143
|
+
if (renderedOutput) {
|
|
144
|
+
return `${activity.toolName} -> ${renderedOutput}`;
|
|
145
|
+
}
|
|
146
|
+
if (renderedInput) {
|
|
147
|
+
return `${activity.toolName}(${renderedInput})`;
|
|
148
|
+
}
|
|
149
|
+
return activity.toolName;
|
|
150
|
+
}
|
|
151
|
+
function buildImplicitTaskSummary(assistantTextResponses, completedToolActivities) {
|
|
152
|
+
if (assistantTextResponses.length > 0) {
|
|
153
|
+
const latestAssistantText = assistantTextResponses[assistantTextResponses.length - 1]?.trim();
|
|
154
|
+
if (latestAssistantText) {
|
|
155
|
+
return latestAssistantText.startsWith('Task completed')
|
|
156
|
+
? latestAssistantText
|
|
157
|
+
: `Task completed. Summary: ${truncateSummaryText(latestAssistantText)}`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const renderedActivities = completedToolActivities
|
|
161
|
+
.map(summarizeCompletedToolActivity)
|
|
162
|
+
.filter((entry) => entry.length > 0);
|
|
163
|
+
if (renderedActivities.length > 0) {
|
|
164
|
+
return `Task completed. Summary: ${renderedActivities.slice(-3).join('; ')}`;
|
|
165
|
+
}
|
|
166
|
+
return 'Task completed successfully.';
|
|
167
|
+
}
|
|
62
168
|
function getStreamResponseAwaitTimeoutMs() {
|
|
63
169
|
const raw = process.env.DC_STREAM_RESPONSE_TIMEOUT_MS;
|
|
64
170
|
if (raw != null) {
|
|
@@ -128,6 +234,49 @@ function validateTypedResultValue(typedVar, value) {
|
|
|
128
234
|
return null;
|
|
129
235
|
}
|
|
130
236
|
}
|
|
237
|
+
function coerceTypedResultValue(typedVar, value) {
|
|
238
|
+
if (typeof value !== 'string') {
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
const trimmed = value.trim();
|
|
242
|
+
if (!trimmed) {
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
switch (typedVar.type) {
|
|
246
|
+
case 'array':
|
|
247
|
+
try {
|
|
248
|
+
const parsed = JSON.parse(trimmed);
|
|
249
|
+
return Array.isArray(parsed) ? parsed : value;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
case 'object':
|
|
255
|
+
try {
|
|
256
|
+
const parsed = JSON.parse(trimmed);
|
|
257
|
+
return isPlainObject(parsed) ? parsed : value;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
case 'number': {
|
|
263
|
+
const parsed = Number(trimmed);
|
|
264
|
+
return Number.isFinite(parsed) ? parsed : value;
|
|
265
|
+
}
|
|
266
|
+
case 'integer': {
|
|
267
|
+
const parsed = Number(trimmed);
|
|
268
|
+
return Number.isInteger(parsed) ? parsed : value;
|
|
269
|
+
}
|
|
270
|
+
case 'boolean':
|
|
271
|
+
if (trimmed === 'true')
|
|
272
|
+
return true;
|
|
273
|
+
if (trimmed === 'false')
|
|
274
|
+
return false;
|
|
275
|
+
return value;
|
|
276
|
+
default:
|
|
277
|
+
return value;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
131
280
|
/**
|
|
132
281
|
* Convert JSON Schema to Zod schema
|
|
133
282
|
* Handles basic JSON Schema types used in tool definitions
|
|
@@ -197,9 +346,13 @@ export async function runAgentLoop(options) {
|
|
|
197
346
|
debugLog('Output vars:', normalizedOutputVars.map(v => `${v.name}:${v.type}`));
|
|
198
347
|
const outputs = [];
|
|
199
348
|
const variables = {};
|
|
349
|
+
const completedToolActivities = [];
|
|
350
|
+
const pendingStreamingToolCalls = [];
|
|
200
351
|
let finished = false;
|
|
201
352
|
let success = false;
|
|
202
353
|
let errorRetryCount = 0;
|
|
354
|
+
let previousIterationSignature = null;
|
|
355
|
+
let repeatedStallCount = 0;
|
|
203
356
|
// Build the AI SDK tools using Zod schemas
|
|
204
357
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
205
358
|
const aiTools = {};
|
|
@@ -250,15 +403,16 @@ export async function runAgentLoop(options) {
|
|
|
250
403
|
execute: async ({ variable, value }) => {
|
|
251
404
|
const typedVar = normalizedOutputVars.find(v => v.name === variable);
|
|
252
405
|
if (typedVar) {
|
|
253
|
-
const
|
|
406
|
+
const coercedValue = coerceTypedResultValue(typedVar, value);
|
|
407
|
+
const validationError = validateTypedResultValue(typedVar, coercedValue);
|
|
254
408
|
if (validationError) {
|
|
255
409
|
return {
|
|
256
410
|
success: false,
|
|
257
411
|
error: `${validationError} for ${variable}`,
|
|
258
412
|
};
|
|
259
413
|
}
|
|
260
|
-
variables[variable] =
|
|
261
|
-
return { success: true, variable, value };
|
|
414
|
+
variables[variable] = coercedValue;
|
|
415
|
+
return { success: true, variable, value: coercedValue };
|
|
262
416
|
}
|
|
263
417
|
return { success: false, error: `Unknown variable: ${variable}` };
|
|
264
418
|
},
|
|
@@ -267,7 +421,7 @@ export async function runAgentLoop(options) {
|
|
|
267
421
|
// Reserved internal tool names that cannot be overwritten by user-defined tools
|
|
268
422
|
const reservedToolNames = ['finish', 'set_result'];
|
|
269
423
|
// Add user-defined tools - convert JSON Schema to Zod
|
|
270
|
-
for (const [name, tool] of tools) {
|
|
424
|
+
for (const [name, tool] of options.tools) {
|
|
271
425
|
if (reservedToolNames.includes(name)) {
|
|
272
426
|
continue;
|
|
273
427
|
}
|
|
@@ -289,7 +443,12 @@ export async function runAgentLoop(options) {
|
|
|
289
443
|
});
|
|
290
444
|
}
|
|
291
445
|
// Build the base system prompt with task instructions and tools
|
|
292
|
-
const baseSystemPrompt = buildSystemPrompt(
|
|
446
|
+
const baseSystemPrompt = await buildSystemPrompt({
|
|
447
|
+
taskDescription,
|
|
448
|
+
outputVars: normalizedOutputVars,
|
|
449
|
+
tools,
|
|
450
|
+
workspacePath: options.workspacePath,
|
|
451
|
+
});
|
|
293
452
|
// Extract system context from memory (user-defined system() calls)
|
|
294
453
|
const systemContext = memory
|
|
295
454
|
.filter(m => m.role === 'system' && typeof m.content === 'string')
|
|
@@ -386,6 +545,7 @@ export async function runAgentLoop(options) {
|
|
|
386
545
|
});
|
|
387
546
|
// Collect results from fullStream
|
|
388
547
|
let fullText = '';
|
|
548
|
+
const toolCallsThisIteration = [];
|
|
389
549
|
let ttftMs = null; // time to first TEXT token
|
|
390
550
|
let ttfeMs = null; // time to first event (any type)
|
|
391
551
|
let ttfrMs = null; // time to first reasoning token
|
|
@@ -397,7 +557,6 @@ export async function runAgentLoop(options) {
|
|
|
397
557
|
let toolInputChunks = 0;
|
|
398
558
|
let toolInputChars = 0;
|
|
399
559
|
let finishReason = 'other';
|
|
400
|
-
let lastToolCallName = null;
|
|
401
560
|
let stepUsage = null;
|
|
402
561
|
let lastEventMs = apiCallMs;
|
|
403
562
|
const eventCounts = {};
|
|
@@ -433,13 +592,25 @@ export async function runAgentLoop(options) {
|
|
|
433
592
|
case 'tool-call':
|
|
434
593
|
if (firstToolCallMs === null)
|
|
435
594
|
firstToolCallMs = nowMs - apiCallMs;
|
|
436
|
-
|
|
595
|
+
toolCallsThisIteration.push({ toolName: part.toolName, input: part.input });
|
|
596
|
+
pendingStreamingToolCalls.push({ toolName: part.toolName, input: part.input });
|
|
437
597
|
debugLog(`Tool call: ${part.toolName}`, JSON.stringify(part.input));
|
|
438
598
|
if (onToolCall) {
|
|
439
599
|
onToolCall(part.toolName, part.input);
|
|
440
600
|
}
|
|
441
601
|
break;
|
|
442
602
|
case 'tool-result':
|
|
603
|
+
{
|
|
604
|
+
const pendingIndex = pendingStreamingToolCalls.findIndex((pending) => pending.toolName === part.toolName);
|
|
605
|
+
const pendingCall = pendingIndex >= 0
|
|
606
|
+
? pendingStreamingToolCalls.splice(pendingIndex, 1)[0]
|
|
607
|
+
: undefined;
|
|
608
|
+
completedToolActivities.push({
|
|
609
|
+
toolName: part.toolName,
|
|
610
|
+
input: pendingCall?.input,
|
|
611
|
+
output: part.output,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
443
614
|
debugLog(`Tool result for ${part.toolName}:`, JSON.stringify(part.output).substring(0, 500));
|
|
444
615
|
break;
|
|
445
616
|
case 'error':
|
|
@@ -484,10 +655,8 @@ export async function runAgentLoop(options) {
|
|
|
484
655
|
}
|
|
485
656
|
}
|
|
486
657
|
debugLog(`Iteration ${iteration} timing: TTFE=${ttfeMs ?? '-'}ms TTFR=${ttfrMs ?? '-'}ms TTFT=${ttftMs ?? 'no-text'}ms stream=${streamDoneMs}ms total=${Date.now() - iterStartMs}ms${usageStr}`);
|
|
487
|
-
// Check if finish was called during tool execution
|
|
488
658
|
if (finished) {
|
|
489
|
-
debugLog('Finish tool was called
|
|
490
|
-
break;
|
|
659
|
+
debugLog('Finish tool was called; finalizing streamed iteration before exit');
|
|
491
660
|
}
|
|
492
661
|
debugLog(`Response text: ${fullText || '(empty)'}`);
|
|
493
662
|
debugLog(`Finish reason: ${finishReason}`);
|
|
@@ -524,12 +693,26 @@ export async function runAgentLoop(options) {
|
|
|
524
693
|
else if (responseResolution.timedOut && fullText) {
|
|
525
694
|
messages.push({ role: 'assistant', content: fullText });
|
|
526
695
|
}
|
|
696
|
+
const iterationSignature = buildIterationSignature({
|
|
697
|
+
finishReason,
|
|
698
|
+
text: fullText,
|
|
699
|
+
toolCalls: toolCallsThisIteration,
|
|
700
|
+
variables,
|
|
701
|
+
});
|
|
702
|
+
repeatedStallCount = iterationSignature === previousIterationSignature ? repeatedStallCount + 1 : 0;
|
|
703
|
+
previousIterationSignature = iterationSignature;
|
|
704
|
+
if (!finished && repeatedStallCount >= MAX_STALLED_ITERATIONS) {
|
|
705
|
+
success = false;
|
|
706
|
+
outputs.push('Agent loop detected repeated non-progressing responses and stopped early.');
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
527
709
|
// If no tool calls were made, nudge the model to act.
|
|
528
710
|
// This handles 'stop', 'other', and any unexpected finish reason.
|
|
529
|
-
|
|
711
|
+
const recoveryPrompt = buildRecoveryPrompt(normalizedOutputVars, repeatedStallCount, toolCallsThisIteration.length === 0);
|
|
712
|
+
if (recoveryPrompt && !finished) {
|
|
530
713
|
messages.push({
|
|
531
714
|
role: 'user',
|
|
532
|
-
content:
|
|
715
|
+
content: recoveryPrompt,
|
|
533
716
|
});
|
|
534
717
|
}
|
|
535
718
|
}
|
|
@@ -574,10 +757,8 @@ export async function runAgentLoop(options) {
|
|
|
574
757
|
}
|
|
575
758
|
}
|
|
576
759
|
debugLog(`Iteration ${iteration} timing: generateText=${Date.now() - apiCallMs}ms total=${Date.now() - iterStartMs}ms${genUsageStr}`);
|
|
577
|
-
// Check if finish was called during tool execution
|
|
578
760
|
if (finished) {
|
|
579
|
-
debugLog('Finish tool was called
|
|
580
|
-
break;
|
|
761
|
+
debugLog('Finish tool was called; finalizing non-streaming iteration before exit');
|
|
581
762
|
}
|
|
582
763
|
debugLog(`Response text: ${result.text || '(empty)'}`);
|
|
583
764
|
debugLog(`Tool calls: ${result.toolCalls?.length ?? 0}`);
|
|
@@ -609,7 +790,13 @@ export async function runAgentLoop(options) {
|
|
|
609
790
|
}
|
|
610
791
|
// Log tool results
|
|
611
792
|
if (result.toolResults) {
|
|
612
|
-
for (const tr of result.toolResults) {
|
|
793
|
+
for (const [index, tr] of result.toolResults.entries()) {
|
|
794
|
+
const matchingToolCall = result.toolCalls?.[index];
|
|
795
|
+
completedToolActivities.push({
|
|
796
|
+
toolName: tr.toolName,
|
|
797
|
+
input: matchingToolCall?.input,
|
|
798
|
+
output: tr.output,
|
|
799
|
+
});
|
|
613
800
|
debugLog(`Tool result for ${tr.toolName}:`, JSON.stringify(tr.output).substring(0, 500));
|
|
614
801
|
}
|
|
615
802
|
}
|
|
@@ -617,12 +804,30 @@ export async function runAgentLoop(options) {
|
|
|
617
804
|
// Use response.messages to update message history (AI SDK v6 pattern)
|
|
618
805
|
// This is the key change - let the SDK handle message formatting
|
|
619
806
|
messages = [...messages, ...getResponseMessages(result.response)];
|
|
807
|
+
const toolCallsThisIteration = (result.toolCalls ?? []).map((toolCall) => ({
|
|
808
|
+
toolName: toolCall.toolName,
|
|
809
|
+
input: toolCall.input,
|
|
810
|
+
}));
|
|
811
|
+
const iterationSignature = buildIterationSignature({
|
|
812
|
+
finishReason: result.finishReason,
|
|
813
|
+
text: result.text,
|
|
814
|
+
toolCalls: toolCallsThisIteration,
|
|
815
|
+
variables,
|
|
816
|
+
});
|
|
817
|
+
repeatedStallCount = iterationSignature === previousIterationSignature ? repeatedStallCount + 1 : 0;
|
|
818
|
+
previousIterationSignature = iterationSignature;
|
|
819
|
+
if (!finished && repeatedStallCount >= MAX_STALLED_ITERATIONS) {
|
|
820
|
+
success = false;
|
|
821
|
+
outputs.push('Agent loop detected repeated non-progressing responses and stopped early.');
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
620
824
|
// If no tool calls were made, nudge the model to act.
|
|
621
825
|
// This handles 'stop', 'other', and any unexpected finish reason.
|
|
622
|
-
|
|
826
|
+
const recoveryPrompt = buildRecoveryPrompt(normalizedOutputVars, repeatedStallCount, toolCallsThisIteration.length === 0);
|
|
827
|
+
if (recoveryPrompt && !finished) {
|
|
623
828
|
messages.push({
|
|
624
829
|
role: 'user',
|
|
625
|
-
content:
|
|
830
|
+
content: recoveryPrompt,
|
|
626
831
|
});
|
|
627
832
|
}
|
|
628
833
|
}
|
|
@@ -682,6 +887,12 @@ export async function runAgentLoop(options) {
|
|
|
682
887
|
content: `Task completed. Results: ${varSummary}`,
|
|
683
888
|
});
|
|
684
889
|
}
|
|
890
|
+
else if (success && normalizedOutputVars.length === 0) {
|
|
891
|
+
persistentMessages.push({
|
|
892
|
+
role: 'assistant',
|
|
893
|
+
content: buildImplicitTaskSummary(assistantTextResponses, completedToolActivities),
|
|
894
|
+
});
|
|
895
|
+
}
|
|
685
896
|
else if (assistantTextResponses.length > 0) {
|
|
686
897
|
persistentMessages.push({
|
|
687
898
|
role: 'assistant',
|
|
@@ -712,10 +923,9 @@ function getResponseMessages(response) {
|
|
|
712
923
|
/**
|
|
713
924
|
* Build the system prompt for the agent
|
|
714
925
|
*/
|
|
715
|
-
function buildSystemPrompt(
|
|
926
|
+
async function buildSystemPrompt(options) {
|
|
716
927
|
const toolDescriptions = [];
|
|
717
|
-
|
|
718
|
-
const normalizedOutputVars = outputVars.map(v => typeof v === 'string' ? { name: v, type: 'string' } : v);
|
|
928
|
+
const normalizedOutputVars = options.outputVars;
|
|
719
929
|
// Add finish tool description
|
|
720
930
|
toolDescriptions.push('- finish(success: boolean): Signal task completion. Call finish(true) when done successfully, or finish(false) if the task cannot be completed.');
|
|
721
931
|
// Add set_result tool if we have output variables
|
|
@@ -727,7 +937,7 @@ function buildSystemPrompt(taskDescription, outputVars, tools) {
|
|
|
727
937
|
toolDescriptions.push(`- set_result(variable: string, value: any): Store a result value. Variable must be one of: ${varList}`);
|
|
728
938
|
}
|
|
729
939
|
// Add user-defined tools
|
|
730
|
-
for (const [name, tool] of tools) {
|
|
940
|
+
for (const [name, tool] of options.tools) {
|
|
731
941
|
if (name === 'finish' || name === 'set_result')
|
|
732
942
|
continue;
|
|
733
943
|
// Build parameter signature from schema
|
|
@@ -742,50 +952,25 @@ function buildSystemPrompt(taskDescription, outputVars, tools) {
|
|
|
742
952
|
.join(', ');
|
|
743
953
|
toolDescriptions.push(`- ${name}(${paramList}): ${tool.description}`);
|
|
744
954
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
IMPORTANT: You MUST use set_result() to store values for: ${varList}
|
|
766
|
-
Call set_result for each variable before calling finish.
|
|
767
|
-
The tool will enforce strict type checking based on the variable type.`;
|
|
768
|
-
}
|
|
769
|
-
prompt += `
|
|
770
|
-
|
|
771
|
-
CRITICAL INSTRUCTIONS:
|
|
772
|
-
- You MUST use the structured tool-calling interface to invoke tools.
|
|
773
|
-
- NEVER write code syntax like print(), default_api.X(), or function_name() in your text response.
|
|
774
|
-
- NEVER describe tool calls in text - actually invoke them using the tool interface.
|
|
775
|
-
- Each tool call should be a separate structured function call, not embedded in text.
|
|
776
|
-
- When storing values, pass simple strings without code formatting or escaping unless the type is 'string'.
|
|
777
|
-
|
|
778
|
-
EXAMPLES OF CORRECT TOOL USAGE:
|
|
779
|
-
|
|
780
|
-
To complete a task successfully, invoke the finish tool with:
|
|
781
|
-
Tool: finish
|
|
782
|
-
Arguments: { "success": true }
|
|
783
|
-
|
|
784
|
-
WRONG (do NOT do this):
|
|
785
|
-
- print(finish(success=true))
|
|
786
|
-
- default_api.set_result(variable="X", value="Y")
|
|
787
|
-
- I will now call finish(true)
|
|
788
|
-
- \`\`\`finish(success=true)\`\`\``;
|
|
789
|
-
return prompt;
|
|
955
|
+
const resultSection = normalizedOutputVars.length > 0
|
|
956
|
+
? [
|
|
957
|
+
'You must set all named results before finishing successfully.',
|
|
958
|
+
...normalizedOutputVars.map((variable) => {
|
|
959
|
+
const typeStr = variable.type === 'array' && variable.itemType ? `array<${variable.itemType}>` : variable.type;
|
|
960
|
+
return `- ${variable.name}: ${typeStr}`;
|
|
961
|
+
}),
|
|
962
|
+
].join('\n')
|
|
963
|
+
: 'No named result variables are required. Call finish(true) as soon as the subtask is complete.';
|
|
964
|
+
const stallGuidance = [
|
|
965
|
+
'- Do not repeat the same explanation or identical tool call if the previous turn did not change the state.',
|
|
966
|
+
'- If you are stuck, either choose one different tool or call finish(false).',
|
|
967
|
+
'- Once enough information exists, stop planning, set results if needed, and finish.',
|
|
968
|
+
].join('\n');
|
|
969
|
+
const template = await readSystemPromptAsset('task', { workspacePath: options.workspacePath });
|
|
970
|
+
return template
|
|
971
|
+
.replace('{TASK_DESCRIPTION}', options.taskDescription)
|
|
972
|
+
.replace('{TOOL_DESCRIPTIONS}', toolDescriptions.join('\n'))
|
|
973
|
+
.replace('{RESULT_SECTION}', resultSection)
|
|
974
|
+
.replace('{STALL_GUIDANCE}', stallGuidance);
|
|
790
975
|
}
|
|
791
976
|
//# sourceMappingURL=agent.js.map
|