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.
Files changed (66) hide show
  1. package/README.md +10 -2
  2. package/dist/agent.d.ts +1 -0
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +252 -67
  5. package/dist/agent.js.map +1 -1
  6. package/dist/cli/commands.d.ts.map +1 -1
  7. package/dist/cli/commands.js +64 -7
  8. package/dist/cli/commands.js.map +1 -1
  9. package/dist/cli/config.d.ts +15 -12
  10. package/dist/cli/config.d.ts.map +1 -1
  11. package/dist/cli/config.js +38 -5
  12. package/dist/cli/config.js.map +1 -1
  13. package/dist/cli/default-skills.d.ts +1 -0
  14. package/dist/cli/default-skills.d.ts.map +1 -1
  15. package/dist/cli/default-skills.js +31 -0
  16. package/dist/cli/default-skills.js.map +1 -1
  17. package/dist/cli/tui.d.ts +16 -1
  18. package/dist/cli/tui.d.ts.map +1 -1
  19. package/dist/cli/tui.js +293 -21
  20. package/dist/cli/tui.js.map +1 -1
  21. package/dist/prolog/bridge.d.ts +15 -0
  22. package/dist/prolog/bridge.d.ts.map +1 -1
  23. package/dist/prolog/bridge.js +68 -0
  24. package/dist/prolog/bridge.js.map +1 -1
  25. package/dist/prolog-src/deepclause_mi.pl +135 -7
  26. package/dist/runner.d.ts +8 -0
  27. package/dist/runner.d.ts.map +1 -1
  28. package/dist/runner.js +79 -0
  29. package/dist/runner.js.map +1 -1
  30. package/dist/system/assets/docs/DML_COMPILER_PROMPT.md +7 -3
  31. package/dist/system/assets/docs/DML_REFERENCE.md +656 -0
  32. package/dist/system/assets/docs/TASK_PROMPT.md +33 -0
  33. package/dist/system/assets/docs/TUI.md +11 -0
  34. package/dist/system/assets/index.d.ts +12 -9
  35. package/dist/system/assets/index.d.ts.map +1 -1
  36. package/dist/system/assets/index.js +38 -9
  37. package/dist/system/assets/index.js.map +1 -1
  38. package/dist/system/assets/recipes/deepclause-coding-workflow/SKILL.md +56 -1
  39. package/dist/system/assets/skills/plan.dml +259 -0
  40. package/dist/system/runtime/catalog-skills.d.ts +1 -1
  41. package/dist/system/runtime/catalog-skills.d.ts.map +1 -1
  42. package/dist/system/runtime/catalog-skills.js +19 -5
  43. package/dist/system/runtime/catalog-skills.js.map +1 -1
  44. package/dist/system/runtime/conductor.d.ts +5 -1
  45. package/dist/system/runtime/conductor.d.ts.map +1 -1
  46. package/dist/system/runtime/conductor.js +22 -0
  47. package/dist/system/runtime/conductor.js.map +1 -1
  48. package/dist/system/runtime/dml-executor.d.ts +1 -1
  49. package/dist/system/runtime/dml-executor.d.ts.map +1 -1
  50. package/dist/system/runtime/dml-executor.js +2 -14
  51. package/dist/system/runtime/dml-executor.js.map +1 -1
  52. package/dist/system/runtime/dml-params.d.ts +2 -2
  53. package/dist/system/runtime/dml-params.d.ts.map +1 -1
  54. package/dist/system/runtime/dml-params.js +3 -0
  55. package/dist/system/runtime/dml-params.js.map +1 -1
  56. package/dist/system/runtime/runtime-tools.d.ts +1 -0
  57. package/dist/system/runtime/runtime-tools.d.ts.map +1 -1
  58. package/dist/system/runtime/runtime-tools.js +95 -0
  59. package/dist/system/runtime/runtime-tools.js.map +1 -1
  60. package/dist/system/runtime/shell-manager.js +1 -1
  61. package/dist/system/runtime/shell-manager.js.map +1 -1
  62. package/dist/system/runtime/skill-creator.d.ts.map +1 -1
  63. package/dist/system/runtime/skill-creator.js +52 -21
  64. package/dist/system/runtime/skill-creator.js.map +1 -1
  65. package/package.json +4 -3
  66. 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 and skill creator DML/prompt.
254
- - Changes to those `.deepclause/system/` files are picked up on the next conductor turn or skill-creator run; a dedicated TUI reload is not required.
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
@@ -12,6 +12,7 @@ export interface AgentLoopOptions {
12
12
  outputVars: (string | TypedVar)[];
13
13
  memory: MemoryMessage[];
14
14
  tools: Map<string, ToolDefinition>;
15
+ workspacePath?: string;
15
16
  modelOptions: {
16
17
  model: string;
17
18
  provider: string;
@@ -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;AAsC1E,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,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;AA4KD;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAsmBtF"}
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 validationError = validateTypedResultValue(typedVar, value);
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] = value;
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(taskDescription, normalizedOutputVars, tools);
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
- lastToolCallName = part.toolName;
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, exiting loop');
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
- if (lastToolCallName === null && !finished) {
711
+ const recoveryPrompt = buildRecoveryPrompt(normalizedOutputVars, repeatedStallCount, toolCallsThisIteration.length === 0);
712
+ if (recoveryPrompt && !finished) {
530
713
  messages.push({
531
714
  role: 'user',
532
- content: 'Please take action using the available tools, or call finish() when done.',
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, exiting loop');
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
- if ((!result.toolCalls || result.toolCalls.length === 0) && !finished) {
826
+ const recoveryPrompt = buildRecoveryPrompt(normalizedOutputVars, repeatedStallCount, toolCallsThisIteration.length === 0);
827
+ if (recoveryPrompt && !finished) {
623
828
  messages.push({
624
829
  role: 'user',
625
- content: 'Please take action using the available tools, or call finish() when done.',
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(taskDescription, outputVars, tools) {
926
+ async function buildSystemPrompt(options) {
716
927
  const toolDescriptions = [];
717
- // Normalize outputVars to TypedVar[]
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
- let prompt = `You are an AI agent executing a subtask within a larger workflow. Your job is to complete the following subtask:
746
-
747
- Subtask: ${taskDescription}
748
-
749
- Available tools:
750
-
751
- ${toolDescriptions.join('\n')}
752
-
753
- Workflow:
754
- 1. Analyze the task and gather any needed information using available tools.
755
- 2. Once the task is complete, call finish(true).
756
-
757
- If you determine the task cannot be completed, call finish(false) immediately.`;
758
- if (normalizedOutputVars.length > 0) {
759
- const varList = normalizedOutputVars.map(v => {
760
- const typeStr = v.type === 'array' && v.itemType ? `array<${v.itemType}>` : v.type;
761
- return `"${v.name}" (${typeStr})`;
762
- }).join(', ');
763
- prompt += `
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