deepclause-sdk 0.0.89 → 0.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.
Files changed (292) hide show
  1. package/README.md +417 -948
  2. package/dist/agent.d.ts +18 -2
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +74 -21
  5. package/dist/agent.js.map +1 -1
  6. package/dist/cli/config.d.ts +323 -176
  7. package/dist/cli/config.d.ts.map +1 -1
  8. package/dist/cli/config.js +146 -188
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/index.d.ts +3 -2
  11. package/dist/cli/index.d.ts.map +1 -1
  12. package/dist/cli/index.js +61 -425
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/login.d.ts +29 -0
  15. package/dist/cli/login.d.ts.map +1 -0
  16. package/dist/cli/login.js +184 -0
  17. package/dist/cli/login.js.map +1 -0
  18. package/dist/cli/readline.d.ts +6 -0
  19. package/dist/cli/readline.d.ts.map +1 -0
  20. package/dist/cli/readline.js +15 -0
  21. package/dist/cli/readline.js.map +1 -0
  22. package/dist/cli/repl-bin.d.ts +9 -0
  23. package/dist/cli/repl-bin.d.ts.map +1 -0
  24. package/dist/cli/repl-bin.js +16 -0
  25. package/dist/cli/repl-bin.js.map +1 -0
  26. package/dist/cli/repl.d.ts +191 -0
  27. package/dist/cli/repl.d.ts.map +1 -0
  28. package/dist/cli/repl.js +1232 -0
  29. package/dist/cli/repl.js.map +1 -0
  30. package/dist/cli/run.d.ts +17 -18
  31. package/dist/cli/run.d.ts.map +1 -1
  32. package/dist/cli/run.js +196 -183
  33. package/dist/cli/run.js.map +1 -1
  34. package/dist/cli/set-backend.d.ts +53 -0
  35. package/dist/cli/set-backend.d.ts.map +1 -0
  36. package/dist/cli/set-backend.js +259 -0
  37. package/dist/cli/set-backend.js.map +1 -0
  38. package/dist/cli/visualize.d.ts +38 -0
  39. package/dist/cli/visualize.d.ts.map +1 -0
  40. package/dist/cli/visualize.js +125 -0
  41. package/dist/cli/visualize.js.map +1 -0
  42. package/dist/compiler.d.ts +5 -52
  43. package/dist/compiler.d.ts.map +1 -1
  44. package/dist/compiler.js +4 -768
  45. package/dist/compiler.js.map +1 -1
  46. package/dist/index.d.ts +27 -2
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +18 -2
  49. package/dist/index.js.map +1 -1
  50. package/dist/prolog/bridge.d.ts +3 -1
  51. package/dist/prolog/bridge.d.ts.map +1 -1
  52. package/dist/prolog/bridge.js +8 -1
  53. package/dist/prolog/bridge.js.map +1 -1
  54. package/dist/prolog-src/deepclause_analysis.pl +10 -2
  55. package/dist/prolog-src/deepclause_mi.pl +2245 -41
  56. package/dist/repl/agent.d.ts +72 -0
  57. package/dist/repl/agent.d.ts.map +1 -0
  58. package/dist/repl/agent.js +366 -0
  59. package/dist/repl/agent.js.map +1 -0
  60. package/dist/repl/auth.d.ts +45 -0
  61. package/dist/repl/auth.d.ts.map +1 -0
  62. package/dist/repl/auth.js +116 -0
  63. package/dist/repl/auth.js.map +1 -0
  64. package/dist/repl/backend.d.ts +290 -0
  65. package/dist/repl/backend.d.ts.map +1 -0
  66. package/dist/repl/backend.js +370 -0
  67. package/dist/repl/backend.js.map +1 -0
  68. package/dist/repl/compaction.d.ts +122 -0
  69. package/dist/repl/compaction.d.ts.map +1 -0
  70. package/dist/repl/compaction.js +318 -0
  71. package/dist/repl/compaction.js.map +1 -0
  72. package/dist/repl/export.d.ts +30 -0
  73. package/dist/repl/export.d.ts.map +1 -0
  74. package/dist/repl/export.js +23 -0
  75. package/dist/repl/export.js.map +1 -0
  76. package/dist/repl/frames.d.ts +21 -0
  77. package/dist/repl/frames.d.ts.map +1 -0
  78. package/dist/repl/frames.js +79 -0
  79. package/dist/repl/frames.js.map +1 -0
  80. package/dist/repl/harness-tools.d.ts +74 -0
  81. package/dist/repl/harness-tools.d.ts.map +1 -0
  82. package/dist/repl/harness-tools.js +121 -0
  83. package/dist/repl/harness-tools.js.map +1 -0
  84. package/dist/repl/highlight.d.ts +30 -0
  85. package/dist/repl/highlight.d.ts.map +1 -0
  86. package/dist/repl/highlight.js +285 -0
  87. package/dist/repl/highlight.js.map +1 -0
  88. package/dist/repl/history.d.ts +234 -0
  89. package/dist/repl/history.d.ts.map +1 -0
  90. package/dist/repl/history.js +661 -0
  91. package/dist/repl/history.js.map +1 -0
  92. package/dist/repl/index.d.ts +120 -0
  93. package/dist/repl/index.d.ts.map +1 -0
  94. package/dist/repl/index.js +107 -0
  95. package/dist/repl/index.js.map +1 -0
  96. package/dist/repl/judge.d.ts +51 -0
  97. package/dist/repl/judge.d.ts.map +1 -0
  98. package/dist/repl/judge.js +58 -0
  99. package/dist/repl/judge.js.map +1 -0
  100. package/dist/repl/markdown.d.ts +25 -0
  101. package/dist/repl/markdown.d.ts.map +1 -0
  102. package/dist/repl/markdown.js +103 -0
  103. package/dist/repl/markdown.js.map +1 -0
  104. package/dist/repl/picker.d.ts +34 -0
  105. package/dist/repl/picker.d.ts.map +1 -0
  106. package/dist/repl/picker.js +81 -0
  107. package/dist/repl/picker.js.map +1 -0
  108. package/dist/repl/project-context.d.ts +16 -0
  109. package/dist/repl/project-context.d.ts.map +1 -0
  110. package/dist/repl/project-context.js +23 -0
  111. package/dist/repl/project-context.js.map +1 -0
  112. package/dist/repl/render.d.ts +160 -0
  113. package/dist/repl/render.d.ts.map +1 -0
  114. package/dist/repl/render.js +715 -0
  115. package/dist/repl/render.js.map +1 -0
  116. package/dist/repl/session.d.ts +356 -0
  117. package/dist/repl/session.d.ts.map +1 -0
  118. package/dist/repl/session.js +1275 -0
  119. package/dist/repl/session.js.map +1 -0
  120. package/dist/repl/status.d.ts +100 -0
  121. package/dist/repl/status.d.ts.map +1 -0
  122. package/dist/repl/status.js +218 -0
  123. package/dist/repl/status.js.map +1 -0
  124. package/dist/repl/tools.d.ts +53 -0
  125. package/dist/repl/tools.d.ts.map +1 -0
  126. package/dist/repl/tools.js +307 -0
  127. package/dist/repl/tools.js.map +1 -0
  128. package/dist/repl/toolset.d.ts +32 -0
  129. package/dist/repl/toolset.d.ts.map +1 -0
  130. package/dist/repl/toolset.js +82 -0
  131. package/dist/repl/toolset.js.map +1 -0
  132. package/dist/repl/tui/editor.d.ts +17 -0
  133. package/dist/repl/tui/editor.d.ts.map +1 -0
  134. package/dist/repl/tui/editor.js +39 -0
  135. package/dist/repl/tui/editor.js.map +1 -0
  136. package/dist/repl/tui/status-bar.d.ts +26 -0
  137. package/dist/repl/tui/status-bar.d.ts.map +1 -0
  138. package/dist/repl/tui/status-bar.js +47 -0
  139. package/dist/repl/tui/status-bar.js.map +1 -0
  140. package/dist/repl/tui/theme.d.ts +30 -0
  141. package/dist/repl/tui/theme.d.ts.map +1 -0
  142. package/dist/repl/tui/theme.js +81 -0
  143. package/dist/repl/tui/theme.js.map +1 -0
  144. package/dist/repl/tui/transcript.d.ts +39 -0
  145. package/dist/repl/tui/transcript.d.ts.map +1 -0
  146. package/dist/repl/tui/transcript.js +123 -0
  147. package/dist/repl/tui/transcript.js.map +1 -0
  148. package/dist/runner.d.ts +132 -13
  149. package/dist/runner.d.ts.map +1 -1
  150. package/dist/runner.js +434 -277
  151. package/dist/runner.js.map +1 -1
  152. package/dist/sdk.d.ts.map +1 -1
  153. package/dist/sdk.js +0 -32
  154. package/dist/sdk.js.map +1 -1
  155. package/dist/system/assets/docs/AGENT_PROMPT.md +1373 -0
  156. package/dist/system/assets/docs/DML_AUTHORING.md +195 -0
  157. package/dist/system/assets/docs/DML_REFERENCE.md +312 -72
  158. package/dist/system/assets/docs/HARNESS_EXAMPLES.md +172 -0
  159. package/dist/system/assets/docs/HARNESS_PATTERNS.md +562 -0
  160. package/dist/system/assets/docs/META_PROMPT.md +68 -0
  161. package/dist/system/assets/docs/PROLOG_SNIPPETS.md +117 -0
  162. package/dist/system/assets/index.d.ts +9 -17
  163. package/dist/system/assets/index.d.ts.map +1 -1
  164. package/dist/system/assets/index.js +26 -85
  165. package/dist/system/assets/index.js.map +1 -1
  166. package/dist/system/runtime/agentvm-manager.d.ts +9 -2
  167. package/dist/system/runtime/agentvm-manager.d.ts.map +1 -1
  168. package/dist/system/runtime/agentvm-manager.js +70 -44
  169. package/dist/system/runtime/agentvm-manager.js.map +1 -1
  170. package/dist/system/runtime/media.d.ts +23 -0
  171. package/dist/system/runtime/media.d.ts.map +1 -0
  172. package/dist/system/runtime/media.js +55 -0
  173. package/dist/system/runtime/media.js.map +1 -0
  174. package/dist/system/runtime/shell-limits.d.ts +21 -0
  175. package/dist/system/runtime/shell-limits.d.ts.map +1 -0
  176. package/dist/system/runtime/shell-limits.js +39 -0
  177. package/dist/system/runtime/shell-limits.js.map +1 -0
  178. package/dist/system/runtime/shell-manager.d.ts +8 -3
  179. package/dist/system/runtime/shell-manager.d.ts.map +1 -1
  180. package/dist/system/runtime/shell-manager.js +56 -39
  181. package/dist/system/runtime/shell-manager.js.map +1 -1
  182. package/dist/system/runtime/shell-tool-events.js +5 -0
  183. package/dist/system/runtime/shell-tool-events.js.map +1 -1
  184. package/dist/system/runtime/token-usage.d.ts +2 -0
  185. package/dist/system/runtime/token-usage.d.ts.map +1 -1
  186. package/dist/system/runtime/token-usage.js +7 -0
  187. package/dist/system/runtime/token-usage.js.map +1 -1
  188. package/dist/tools.d.ts +3 -21
  189. package/dist/tools.d.ts.map +1 -1
  190. package/dist/tools.js +14 -168
  191. package/dist/tools.js.map +1 -1
  192. package/dist/types.d.ts +80 -118
  193. package/dist/types.d.ts.map +1 -1
  194. package/dist/validate.d.ts +19 -0
  195. package/dist/validate.d.ts.map +1 -0
  196. package/dist/validate.js +338 -0
  197. package/dist/validate.js.map +1 -0
  198. package/dist/visualize/index.d.ts +52 -0
  199. package/dist/visualize/index.d.ts.map +1 -0
  200. package/dist/visualize/index.js +189 -0
  201. package/dist/visualize/index.js.map +1 -0
  202. package/package.json +17 -15
  203. package/src/prolog-src/deepclause_analysis.pl +10 -2
  204. package/src/prolog-src/deepclause_mi.pl +2245 -41
  205. package/dist/cli/commands.d.ts +0 -42
  206. package/dist/cli/commands.d.ts.map +0 -1
  207. package/dist/cli/commands.js +0 -246
  208. package/dist/cli/commands.js.map +0 -1
  209. package/dist/cli/compile.d.ts +0 -99
  210. package/dist/cli/compile.d.ts.map +0 -1
  211. package/dist/cli/compile.js +0 -363
  212. package/dist/cli/compile.js.map +0 -1
  213. package/dist/cli/default-skills.d.ts +0 -10
  214. package/dist/cli/default-skills.d.ts.map +0 -1
  215. package/dist/cli/default-skills.js +0 -206
  216. package/dist/cli/default-skills.js.map +0 -1
  217. package/dist/cli/interactive.d.ts +0 -2
  218. package/dist/cli/interactive.d.ts.map +0 -1
  219. package/dist/cli/interactive.js +0 -20
  220. package/dist/cli/interactive.js.map +0 -1
  221. package/dist/cli/mcp.d.ts +0 -56
  222. package/dist/cli/mcp.d.ts.map +0 -1
  223. package/dist/cli/mcp.js +0 -138
  224. package/dist/cli/mcp.js.map +0 -1
  225. package/dist/cli/prompt.d.ts +0 -2
  226. package/dist/cli/prompt.d.ts.map +0 -1
  227. package/dist/cli/prompt.js +0 -2
  228. package/dist/cli/prompt.js.map +0 -1
  229. package/dist/cli/search.d.ts +0 -19
  230. package/dist/cli/search.d.ts.map +0 -1
  231. package/dist/cli/search.js +0 -189
  232. package/dist/cli/search.js.map +0 -1
  233. package/dist/cli/tool-args.d.ts +0 -2
  234. package/dist/cli/tool-args.d.ts.map +0 -1
  235. package/dist/cli/tool-args.js +0 -39
  236. package/dist/cli/tool-args.js.map +0 -1
  237. package/dist/cli/tools.d.ts +0 -36
  238. package/dist/cli/tools.d.ts.map +0 -1
  239. package/dist/cli/tools.js +0 -229
  240. package/dist/cli/tools.js.map +0 -1
  241. package/dist/cli/tui/index.d.ts +0 -22
  242. package/dist/cli/tui/index.d.ts.map +0 -1
  243. package/dist/cli/tui/index.js +0 -29
  244. package/dist/cli/tui/index.js.map +0 -1
  245. package/dist/cli/tui.d.ts +0 -167
  246. package/dist/cli/tui.d.ts.map +0 -1
  247. package/dist/cli/tui.js +0 -5228
  248. package/dist/cli/tui.js.map +0 -1
  249. package/dist/compiler_prompt.d.ts +0 -20
  250. package/dist/compiler_prompt.d.ts.map +0 -1
  251. package/dist/compiler_prompt.js +0 -870
  252. package/dist/compiler_prompt.js.map +0 -1
  253. package/dist/deepclause-cli.skill +0 -0
  254. package/dist/prolog-src/deepclause_mi.pl.bak +0 -570
  255. package/dist/system/assets/docs/CONDUCTOR_PROMPT.md +0 -105
  256. package/dist/system/assets/docs/DML_COMPILER_PROMPT.md +0 -700
  257. package/dist/system/assets/docs/TUI.md +0 -150
  258. package/dist/system/assets/recipes/deepclause-coding-workflow/SKILL.md +0 -100
  259. package/dist/system/assets/skills/conductor.dml +0 -76
  260. package/dist/system/assets/skills/deep-planner.dml +0 -412
  261. package/dist/system/assets/skills/plan.dml +0 -429
  262. package/dist/system/assets/skills/security-planner.dml +0 -337
  263. package/dist/system/assets/skills/skill-creator.dml +0 -187
  264. package/dist/system/runtime/catalog-recipes.d.ts +0 -24
  265. package/dist/system/runtime/catalog-recipes.d.ts.map +0 -1
  266. package/dist/system/runtime/catalog-recipes.js +0 -273
  267. package/dist/system/runtime/catalog-recipes.js.map +0 -1
  268. package/dist/system/runtime/catalog-skills.d.ts +0 -50
  269. package/dist/system/runtime/catalog-skills.d.ts.map +0 -1
  270. package/dist/system/runtime/catalog-skills.js +0 -152
  271. package/dist/system/runtime/catalog-skills.js.map +0 -1
  272. package/dist/system/runtime/conductor.d.ts +0 -105
  273. package/dist/system/runtime/conductor.d.ts.map +0 -1
  274. package/dist/system/runtime/conductor.js +0 -732
  275. package/dist/system/runtime/conductor.js.map +0 -1
  276. package/dist/system/runtime/console-capture.d.ts +0 -9
  277. package/dist/system/runtime/console-capture.d.ts.map +0 -1
  278. package/dist/system/runtime/console-capture.js +0 -67
  279. package/dist/system/runtime/console-capture.js.map +0 -1
  280. package/dist/system/runtime/dml-executor.d.ts +0 -52
  281. package/dist/system/runtime/dml-executor.d.ts.map +0 -1
  282. package/dist/system/runtime/dml-executor.js +0 -203
  283. package/dist/system/runtime/dml-executor.js.map +0 -1
  284. package/dist/system/runtime/runtime-tools.d.ts +0 -31
  285. package/dist/system/runtime/runtime-tools.d.ts.map +0 -1
  286. package/dist/system/runtime/runtime-tools.js +0 -312
  287. package/dist/system/runtime/runtime-tools.js.map +0 -1
  288. package/dist/system/runtime/skill-creator.d.ts +0 -38
  289. package/dist/system/runtime/skill-creator.d.ts.map +0 -1
  290. package/dist/system/runtime/skill-creator.js +0 -763
  291. package/dist/system/runtime/skill-creator.js.map +0 -1
  292. package/src/prolog-src/deepclause_mi.pl.bak +0 -570
package/dist/runner.js CHANGED
@@ -1,22 +1,19 @@
1
1
  /**
2
2
  * DML Runner - Executes DML code using SWI-Prolog WASM
3
3
  */
4
- import * as fs from 'fs/promises';
5
- import * as path from 'path';
6
4
  import { mountWorkspace } from './prolog/loader.js';
7
5
  import { runAgentLoop } from './agent.js';
8
6
  import { judgeAnswersToValues, judgeCacheKey, normalizeJudgeState, parseJudgeQuestions, validateJudgeAnswers, valuesToPrologList } from './judge/marshal.js';
9
- import { executeCompactor, getCompactionBindings, resolveCompactionOptions, } from './compaction.js';
10
7
  import { buildToolCompletionEvent, buildToolFailureEvent, buildToolStartEvent, } from './system/runtime/shell-tool-events.js';
11
8
  import { checkToolPolicy } from './tools.js';
12
9
  import { generateLlmReply, sampleSingleToken } from './prolog/bridge.js';
13
- import { recordTokenUsage } from './system/runtime/token-usage.js';
14
10
  import { buildReasoningProviderOptions } from './system/config/model-database.js';
11
+ import { resolveMediaRefs } from './system/runtime/media.js';
15
12
  /**
16
13
  * Convert swipl-wasm value to plain JavaScript value
17
14
  * Handles PrologString, PrologAtom, PrologList, etc.
18
15
  */
19
- function toJsValue(value) {
16
+ export function toJsValue(value) {
20
17
  if (value === null || value === undefined) {
21
18
  return value;
22
19
  }
@@ -60,45 +57,45 @@ function toJsValue(value) {
60
57
  }
61
58
  return value;
62
59
  }
63
- function detectProvider(model) {
64
- const lower = model.toLowerCase();
65
- if (lower.includes('gpt') || lower.includes('o1') || lower.includes('o3')) {
66
- return 'openai';
67
- }
68
- if (lower.includes('claude')) {
69
- return 'anthropic';
70
- }
71
- if (lower.includes('gemini') || lower.includes('palm')) {
72
- return 'google';
60
+ /**
61
+ * Decode the `bindings` payload of a goal-mode `solution` event.
62
+ *
63
+ * `collect_bindings/2` yields a list of `Name-Text` pairs. swipl-wasm encodes
64
+ * that compound as `{ '$t': 't', '-': [[Name, Text]] }`; a dict/array form is
65
+ * also accepted for forward compatibility.
66
+ */
67
+ export function decodeSolutionBindings(raw) {
68
+ const normalized = toJsValue(raw);
69
+ if (!Array.isArray(normalized)) {
70
+ return [];
73
71
  }
74
- return 'openrouter';
75
- }
76
- function createChildAbortController(parentSignal, timeoutMs) {
77
- const controller = new AbortController();
78
- const disposers = [];
79
- if (parentSignal) {
80
- if (parentSignal.aborted) {
81
- controller.abort(parentSignal.reason);
72
+ const bindings = [];
73
+ for (const item of normalized) {
74
+ const decoded = toJsValue(item);
75
+ if (decoded && typeof decoded === 'object') {
76
+ const compound = decoded;
77
+ const args = compound['-'];
78
+ if (Array.isArray(args) && Array.isArray(args[0]) && args[0].length >= 2) {
79
+ const [name, text] = args[0];
80
+ bindings.push({ name: String(toJsValue(name) ?? ''), value: String(toJsValue(text) ?? '') });
81
+ continue;
82
+ }
83
+ if ('name' in compound || 'value' in compound) {
84
+ bindings.push({
85
+ name: String(toJsValue(compound.name) ?? ''),
86
+ value: String(toJsValue(compound.value) ?? ''),
87
+ });
88
+ continue;
89
+ }
82
90
  }
83
- else {
84
- const onAbort = () => controller.abort(parentSignal.reason);
85
- parentSignal.addEventListener('abort', onAbort, { once: true });
86
- disposers.push(() => parentSignal.removeEventListener('abort', onAbort));
91
+ if (Array.isArray(decoded) && decoded.length >= 2) {
92
+ bindings.push({
93
+ name: String(toJsValue(decoded[0]) ?? ''),
94
+ value: String(toJsValue(decoded[1]) ?? ''),
95
+ });
87
96
  }
88
97
  }
89
- let timeout;
90
- if (timeoutMs > 0) {
91
- timeout = setTimeout(() => controller.abort(new Error(`Compactor timed out after ${timeoutMs}ms`)), timeoutMs);
92
- disposers.push(() => clearTimeout(timeout));
93
- }
94
- return {
95
- signal: controller.signal,
96
- dispose: () => {
97
- for (const dispose of disposers) {
98
- dispose();
99
- }
100
- },
101
- };
98
+ return bindings;
102
99
  }
103
100
  export function decodeAgentOutputVars(rawOutputVars) {
104
101
  const normalized = toJsValue(rawOutputVars);
@@ -142,11 +139,18 @@ function prologTypeToJsonType(prologType) {
142
139
  export class DMLRunner {
143
140
  swipl;
144
141
  options;
145
- engine = null;
146
142
  sessionId = '';
147
143
  currentMemory = [];
148
144
  stepCounter = 0;
149
145
  judgeCache = new Map();
146
+ /**
147
+ * Runner-owned frame table: FrameId -> { frame, engine }.
148
+ * The Prolog session owns the authoritative engine handles
149
+ * (session_frame_engine/2); this map mirrors them for the driver.
150
+ */
151
+ frames = new Map();
152
+ /** FrameId of the session's default query frame (created by createEngine). */
153
+ defaultFrameId = null;
150
154
  constructor(swipl, options) {
151
155
  this.swipl = swipl;
152
156
  this.options = options;
@@ -191,94 +195,18 @@ export class DMLRunner {
191
195
  yield { type: 'error', content: parseResult.error };
192
196
  return;
193
197
  }
194
- // Create cooperative engine with args
198
+ // Create cooperative engine with args (registers the default query frame)
195
199
  const engineResult = this.createEngine(memoryId, args, params);
196
200
  if (engineResult.error) {
197
201
  yield { type: 'error', content: engineResult.error };
198
202
  return;
199
203
  }
200
- // Cooperative execution loop
201
- while (true) {
202
- // Check for abort
203
- if (options.signal?.aborted) {
204
- const reason = options.signal.reason;
205
- const abortMessage = reason instanceof Error
206
- ? reason.message
207
- : typeof reason === 'string' && reason.trim().length > 0
208
- ? reason
209
- : 'Execution aborted';
210
- yield { type: 'error', content: abortMessage };
211
- break;
212
- }
213
- // Step the engine
214
- const step = this.stepEngine();
215
- if (process.env.DEBUG_STEPS) {
216
- console.log('[MAIN_LOOP] step:', step.status, step.content?.substring(0, 50));
217
- }
218
- switch (step.status) {
219
- case 'output':
220
- if (step.content) {
221
- if (process.env.DEBUG_STEPS) {
222
- console.log('[MAIN_LOOP] yielding output:', step.content.substring(0, 50));
223
- }
224
- yield { type: 'output', content: step.content };
225
- }
226
- break;
227
- case 'log':
228
- if (step.content) {
229
- yield { type: 'log', content: step.content };
230
- }
231
- break;
232
- case 'answer':
233
- yield { type: 'answer', content: step.content };
234
- break;
235
- case 'request_agent_loop':
236
- // Run agent loop for task() predicate
237
- yield* this.handleAgentLoop(step.payload, memoryId, options);
238
- break;
239
- case 'request_exec':
240
- // Execute external tool
241
- yield* this.handleExec(step.payload, options);
242
- break;
243
- case 'request_sample_token':
244
- yield* this.handleSampleToken(step.payload, options);
245
- break;
246
- case 'request_llm':
247
- yield* this.handleLlm(step.payload, options);
248
- break;
249
- case 'request_judgment':
250
- yield* this.handleJudgment(step.payload, options);
251
- break;
252
- case 'wait_input':
253
- // Request user input
254
- yield { type: 'input_required', prompt: step.prompt };
255
- const input = await options.onInputRequired(step.prompt ?? '');
256
- this.provideInput(input);
257
- break;
258
- case 'finished':
259
- // Include trace data if trace mode was enabled
260
- if (this.options.trace && step.payload) {
261
- const traceData = toJsValue(step.payload.trace);
262
- yield { type: 'finished', trace: Array.isArray(traceData) ? traceData : undefined };
263
- }
264
- else {
265
- yield { type: 'finished' };
266
- }
267
- return;
268
- case 'error':
269
- if (this.options.trace && step.payload) {
270
- const traceData = toJsValue(step.payload.trace);
271
- yield { type: 'error', content: step.content, trace: Array.isArray(traceData) ? traceData : undefined };
272
- }
273
- else {
274
- yield { type: 'error', content: step.content };
275
- }
276
- return;
277
- default:
278
- yield { type: 'error', content: `Unknown step status: ${step.status}` };
279
- return;
280
- }
204
+ const frame = this.defaultFrameId ? this.frames.get(this.defaultFrameId)?.frame : undefined;
205
+ if (!frame) {
206
+ yield { type: 'error', content: 'Failed to register default query frame' };
207
+ return;
281
208
  }
209
+ yield* this.stepLoop(frame, options);
282
210
  }
283
211
  catch (error) {
284
212
  const message = error instanceof Error ? error.message : String(error);
@@ -289,6 +217,164 @@ export class DMLRunner {
289
217
  this.cleanup();
290
218
  }
291
219
  }
220
+ /**
221
+ * Drive a single frame until it finishes or errors, yielding its events.
222
+ *
223
+ * This is the extracted, reentrant form of the original `while(true)` loop.
224
+ * `run()` calls it for the default query frame; `runFrame` calls it for
225
+ * nested frames. Because frames are read from the Prolog frame table via
226
+ * `step_frame/4`, stepping a different frame never disturbs a suspended
227
+ * parent's engine.
228
+ */
229
+ async *stepLoop(frame, options) {
230
+ for await (const event of this.stepLoopInner(frame, options)) {
231
+ // Preserve frame metadata already attached by a nested frame; otherwise
232
+ // stamp this frame so the renderer can attribute output correctly.
233
+ yield {
234
+ ...event,
235
+ frameId: event.frameId ?? frame.id,
236
+ depth: event.depth ?? frame.depth,
237
+ frameKind: event.frameKind ?? frame.kind,
238
+ };
239
+ }
240
+ }
241
+ async *stepLoopInner(frame, options) {
242
+ // Cooperative execution loop
243
+ while (true) {
244
+ // Check for abort
245
+ if (options.signal?.aborted) {
246
+ const reason = options.signal.reason;
247
+ const abortMessage = reason instanceof Error
248
+ ? reason.message
249
+ : typeof reason === 'string' && reason.trim().length > 0
250
+ ? reason
251
+ : 'Execution aborted';
252
+ yield { type: 'error', content: abortMessage };
253
+ break;
254
+ }
255
+ // Step the frame
256
+ const step = this.stepFrame(frame.id);
257
+ if (process.env.DEBUG_STEPS) {
258
+ console.log('[MAIN_LOOP] step:', step.status, step.content?.substring(0, 50));
259
+ }
260
+ switch (step.status) {
261
+ case 'output':
262
+ if (step.content) {
263
+ if (process.env.DEBUG_STEPS) {
264
+ console.log('[MAIN_LOOP] yielding output:', step.content.substring(0, 50));
265
+ }
266
+ yield { type: 'output', content: step.content };
267
+ }
268
+ break;
269
+ case 'log':
270
+ if (step.content) {
271
+ yield { type: 'log', content: step.content };
272
+ }
273
+ break;
274
+ case 'answer':
275
+ yield { type: 'answer', content: step.content };
276
+ break;
277
+ case 'solution': {
278
+ const payload = (step.payload ?? {});
279
+ yield {
280
+ type: 'solution',
281
+ solution: {
282
+ bindings: decodeSolutionBindings(payload.bindings),
283
+ deterministic: String(toJsValue(payload.deterministic) ?? 'false') === 'true',
284
+ },
285
+ };
286
+ break;
287
+ }
288
+ case 'request_agent_loop':
289
+ // Run agent loop for task() predicate. The frame kind decides the
290
+ // tool set: `meta` frames get the host meta tools and hide the
291
+ // harness's own DML tools (proposal §9.2). Budget exhaustion (and any
292
+ // other agent-loop error) is surfaced as an error event.
293
+ try {
294
+ yield* this.handleAgentLoop(step.payload, '', options, frame);
295
+ }
296
+ catch (error) {
297
+ yield { type: 'error', content: error instanceof Error ? error.message : String(error) };
298
+ return;
299
+ }
300
+ break;
301
+ case 'meta_result': {
302
+ const payload = (step.payload ?? {});
303
+ yield {
304
+ type: 'meta_result',
305
+ metaResult: {
306
+ code: String(toJsValue(payload.code) ?? ''),
307
+ answer: String(toJsValue(payload.answer) ?? ''),
308
+ },
309
+ };
310
+ break;
311
+ }
312
+ case 'request_exec':
313
+ // Execute external tool
314
+ yield* this.handleExec(step.payload, options);
315
+ break;
316
+ case 'request_sample_token':
317
+ yield* this.handleSampleToken(step.payload, options);
318
+ break;
319
+ case 'request_llm':
320
+ yield* this.handleLlm(step.payload, options);
321
+ break;
322
+ case 'request_judgment':
323
+ yield* this.handleJudgment(step.payload, options);
324
+ break;
325
+ case 'wait_input': {
326
+ // Request user input
327
+ yield { type: 'input_required', prompt: step.prompt };
328
+ const input = await options.onInputRequired(step.prompt ?? '');
329
+ if (input === undefined || input === null) {
330
+ this.cancelInput();
331
+ }
332
+ else {
333
+ this.provideInput(input);
334
+ }
335
+ break;
336
+ }
337
+ case 'finished':
338
+ // Include trace data if trace mode was enabled
339
+ if (this.options.trace && step.payload) {
340
+ const traceData = toJsValue(step.payload.trace);
341
+ yield { type: 'finished', trace: Array.isArray(traceData) ? traceData : undefined };
342
+ }
343
+ else {
344
+ yield { type: 'finished' };
345
+ }
346
+ return;
347
+ case 'error':
348
+ if (this.options.trace && step.payload) {
349
+ const traceData = toJsValue(step.payload.trace);
350
+ yield { type: 'error', content: step.content, trace: Array.isArray(traceData) ? traceData : undefined };
351
+ }
352
+ else {
353
+ yield { type: 'error', content: step.content };
354
+ }
355
+ return;
356
+ default:
357
+ yield { type: 'error', content: `Unknown step status: ${step.status}` };
358
+ return;
359
+ }
360
+ }
361
+ }
362
+ /**
363
+ * Run a specific frame to completion, yielding its events.
364
+ *
365
+ * This is the reentrant entry point used by nested execution (the REPL's
366
+ * `execute` meta tool): the caller builds a child frame with
367
+ * `createNestedFrame`, runs it with this method while the parent frame is
368
+ * suspended, then posts the result back to the parent.
369
+ */
370
+ async *runFrame(frameId, options) {
371
+ const frame = this.frames.get(frameId)?.frame;
372
+ if (!frame) {
373
+ yield { type: 'error', content: `Unknown frame: ${frameId}` };
374
+ return;
375
+ }
376
+ yield* this.stepLoop(frame, options);
377
+ }
292
378
  /**
293
379
  * Parse DML code into Prolog clauses
294
380
  */
@@ -381,10 +467,15 @@ export class DMLRunner {
381
467
  createEngine(memoryId, args, params) {
382
468
  try {
383
469
  const initialMemory = this.serializeMemoryMessages(this.currentMemory);
384
- const result = this.query(`deepclause_mi:create_engine('${this.sessionId}', '${memoryId}', ${args}, ${params}, [${initialMemory}], Engine)`);
385
- if (result && result.Engine) {
386
- this.engine = result.Engine;
387
- return {};
470
+ // create_engine/6 registers the default `query` frame; default_frame/2
471
+ // returns its FrameId so the runner can address it explicitly.
472
+ const result = this.query(`deepclause_mi:create_engine('${this.sessionId}', '${memoryId}', ${args}, ${params}, [${initialMemory}], Engine), deepclause_mi:default_frame('${this.sessionId}', FrameId)`);
473
+ if (result && result.Engine && result.FrameId) {
474
+ const frameId = String(toJsValue(result.FrameId));
475
+ const frame = { id: frameId, kind: 'query', depth: 0 };
476
+ this.frames.set(frameId, { frame, engine: result.Engine });
477
+ this.defaultFrameId = frameId;
478
+ return { frameId };
388
479
  }
389
480
  return { error: 'Failed to create engine' };
390
481
  }
@@ -394,11 +485,93 @@ export class DMLRunner {
394
485
  }
395
486
  }
396
487
  /**
397
- * Step the cooperative engine
488
+ * Create a nested frame that runs a Prolog goal in this session.
489
+ *
490
+ * `goalTerm` is inserted verbatim into create_frame/6, so callers MUST pass
491
+ * a term built with the runner's `toPrologTerm` escaping or parsed from DML
492
+ * via `read_term` — never raw user text (foot gun #18). `stateTerm` is an
493
+ * optional Prolog `state{...}` term; the default is an empty state with no
494
+ * gas limit.
495
+ *
496
+ * Returns the registered frame, or null on failure. The caller is
497
+ * responsible for running (`runFrame`) and ending (`endFrame`) it.
398
498
  */
399
- stepEngine() {
499
+ createNestedFrame(params) {
500
+ const depth = params.depth ?? 1;
501
+ const stateTerm = params.stateTerm
502
+ ?? 'state{memory: [], params: params{}, context_stack: [], depth: 0}';
503
+ const result = this.query(`deepclause_mi:create_frame('${this.sessionId}', ${params.kind}, ${depth}, ${params.goalTerm}, ${stateTerm}, FrameId)`);
504
+ if (!result || !result.FrameId) {
505
+ return null;
506
+ }
507
+ const id = String(toJsValue(result.FrameId));
508
+ const frame = { id, kind: params.kind, depth };
509
+ this.frames.set(id, { frame, engine: null });
510
+ return frame;
511
+ }
512
+ /** End a frame, destroying its engine in the Prolog session. */
513
+ endFrame(frameId) {
514
+ this.query(`deepclause_mi:end_frame('${frameId}')`);
515
+ this.frames.delete(frameId);
516
+ if (this.defaultFrameId === frameId) {
517
+ this.defaultFrameId = null;
518
+ }
519
+ }
520
+ /**
521
+ * Bind this runner to an existing session id. Used by the REPL's
522
+ * DMLSession, whose session outlives a single `run()`.
523
+ */
524
+ bindSession(sessionId) {
525
+ this.sessionId = sessionId;
526
+ }
527
+ /**
528
+ * Adopt the session's default frame (for example one created by the REPL's
529
+ * `query_start/5`) into the runner's frame table so `runFrame` can drive it.
530
+ */
531
+ registerDefaultFrame(kind, depth = 0) {
532
+ const result = this.query(`deepclause_mi:default_frame('${this.sessionId}', FrameId)`);
533
+ if (!result || !result.FrameId) {
534
+ return null;
535
+ }
536
+ const id = String(toJsValue(result.FrameId));
537
+ const frame = { id, kind, depth };
538
+ this.frames.set(id, { frame, engine: null });
539
+ this.defaultFrameId = id;
540
+ return frame;
541
+ }
542
+ /**
543
+ * Adopt a Prolog-created frame into the runner's frame table. Used by the
544
+ * REPL for `execute` child frames (`repl_execute_frame/4`).
545
+ */
546
+ registerFrame(frameId, kind, depth = 0) {
547
+ const frame = { id: frameId, kind, depth };
548
+ this.frames.set(frameId, { frame, engine: null });
549
+ return frame;
550
+ }
551
+ /**
552
+ * Drop all runner-side frame bookkeeping without touching Prolog. Used after
553
+ * the REPL clears a session (the Prolog side already ended every frame).
554
+ */
555
+ clearFrames() {
556
+ this.frames.clear();
557
+ this.defaultFrameId = null;
558
+ }
559
+ /**
560
+ * Step a specific frame exactly once and return its raw step result.
561
+ * Used by the reentrant driver and nested-execution tests.
562
+ */
563
+ stepFrameOnce(frameId) {
564
+ if (!this.frames.has(frameId)) {
565
+ return null;
566
+ }
567
+ return this.stepFrame(frameId);
568
+ }
569
+ /**
570
+ * Step the cooperative engine for a frame
571
+ */
572
+ stepFrame(frameId) {
400
573
  try {
401
- const result = this.query(`deepclause_mi:step_engine('${this.sessionId}', Status, Content, Payload)`);
574
+ const result = this.query(`deepclause_mi:step_frame('${frameId}', Status, Content, Payload)`);
402
575
  // Convert swipl-wasm values to plain JS
403
576
  const status = toJsValue(result?.Status);
404
577
  const content = toJsValue(result?.Content);
@@ -419,13 +592,15 @@ export class DMLRunner {
419
592
  * Handle agent loop request (task() predicate)
420
593
  */
421
594
  async *handleAgentLoop(payload, _memoryId, // No longer used - memory comes from payload
422
- options) {
595
+ options, _frame) {
423
596
  const rawPayload = payload;
424
597
  const taskDescription = String(toJsValue(rawPayload.taskDescription) ?? '');
425
598
  const stepId = `step_${this.stepCounter++}`;
426
599
  yield { type: 'task_activity', taskState: 'started', taskDescription, taskId: stepId };
427
600
  let stepSucceeded = null;
428
601
  try {
602
+ // The per-model-call budget is enforced inside the agent loop (before each
603
+ // model call) so it also covers multi-iteration turns.
429
604
  // Ensure outputVars is an array of either strings or TypedVar objects
430
605
  const outputVars = decodeAgentOutputVars(rawPayload.outputVars);
431
606
  // Parse userTools - now contains schema info as array of tool_info dicts
@@ -503,6 +678,9 @@ export class DMLRunner {
503
678
  try {
504
679
  emitToolEvent({ type: 'input_required', prompt });
505
680
  const response = await options.onInputRequired(prompt);
681
+ if (response === undefined || response === null) {
682
+ return { cancelled: true };
683
+ }
506
684
  return { user_response: response };
507
685
  }
508
686
  catch (err) {
@@ -515,14 +693,15 @@ export class DMLRunner {
515
693
  const emitToolCall = (toolName, args) => {
516
694
  emitToolEvent({ type: 'tool_call', toolName, toolArgs: args });
517
695
  };
518
- // Build available tools for agent - SIMPLIFIED: tools run in isolated engines
519
- // The callback runs DML tools in isolated engines with no shared state.
696
+ // Build available tools for the agent: the harness's DML tool/2 predicates
697
+ // plus registered TS tools (reachable via exec/2).
698
+ const selfReportingTools = undefined;
520
699
  const availableTools = this.buildAgentTools(userTools, options.toolPolicy, async (toolName, args) => {
521
700
  return this.executeToolIsolated(toolName, args, augmentedTools, onToolOutput, {
522
701
  workspacePath: options.workspacePath,
523
702
  signal: options.signal,
524
- compaction: options.compaction,
525
703
  toolPolicy: options.toolPolicy,
704
+ onModelCall: options.onModelCall,
526
705
  }, [], emitToolCall, emitToolEvent);
527
706
  });
528
707
  // Run agent loop with streaming support
@@ -532,6 +711,7 @@ export class DMLRunner {
532
711
  memory: effectiveMemory,
533
712
  tools: availableTools,
534
713
  workspacePath: options.workspacePath,
714
+ resolveMedia: (refs) => this.readMediaRefs(options.workspacePath, refs),
535
715
  modelOptions: {
536
716
  model: this.options.model,
537
717
  provider: this.options.provider,
@@ -550,6 +730,13 @@ export class DMLRunner {
550
730
  streamResolve = null;
551
731
  }
552
732
  },
733
+ onReasoning: (chunk, done) => {
734
+ streamQueue.push({ type: 'reasoning', content: chunk, done });
735
+ if (streamResolve) {
736
+ streamResolve();
737
+ streamResolve = null;
738
+ }
739
+ },
553
740
  onToolCall: (toolName, args) => {
554
741
  streamQueue.push({ type: 'tool_call', toolName, toolArgs: args });
555
742
  if (streamResolve) {
@@ -564,22 +751,27 @@ export class DMLRunner {
564
751
  streamResolve = null;
565
752
  }
566
753
  },
567
- onBeforeModelCall: async (messages, lastInputTokens) => this.applyCompactionBindings({
568
- messages,
569
- lastInputTokens,
570
- scope: 'loop',
571
- trigger: 'before_model_call',
572
- options,
573
- executionContext: {
574
- workspacePath: options.workspacePath,
575
- signal: options.signal,
576
- compaction: options.compaction,
577
- },
578
- registeredTools: options.tools,
579
- toolPolicy: options.toolPolicy,
580
- emitEvent: emitRunnerEvent,
581
- }),
754
+ onModelCallPhase: (phase) => {
755
+ streamQueue.push({ type: 'model_call', modelCallState: phase, modelId: this.options.model });
756
+ if (streamResolve) {
757
+ streamResolve();
758
+ streamResolve = null;
759
+ }
760
+ },
761
+ selfReportingTools,
762
+ onToolResult: (toolName, toolArgs, toolResult) => {
763
+ streamQueue.push(buildToolCompletionEvent(toolName, toolArgs, toolResult));
764
+ if (streamResolve) {
765
+ streamResolve();
766
+ streamResolve = null;
767
+ }
768
+ },
769
+ onBeforeModelCall: async (messages, lastInputTokens) => {
770
+ const hostCompacted = await this.applyHostCompaction(messages, lastInputTokens, emitRunnerEvent);
771
+ return hostCompacted ?? messages;
772
+ },
582
773
  onAskUser: options.onInputRequired,
774
+ onModelCall: options.onModelCall,
583
775
  signal: options.signal,
584
776
  llmBackend: this.options.llmBackend,
585
777
  });
@@ -729,6 +921,7 @@ export class DMLRunner {
729
921
  },
730
922
  signal: options.signal,
731
923
  llmBackend: this.options.llmBackend,
924
+ resolveMedia: (refs) => this.readMediaRefs(options.workspacePath, refs),
732
925
  });
733
926
  succeeded = true;
734
927
  this.postLlmResult({ success: true, text });
@@ -859,15 +1052,18 @@ export class DMLRunner {
859
1052
  */
860
1053
  async *handleExec(payload, options) {
861
1054
  const { toolName, args } = payload;
862
- // Check tool policy
1055
+ // Check tool policy (ask_user is the human channel and is exempt).
863
1056
  const policyCheck = checkToolPolicy(toolName, options.toolPolicy);
864
1057
  if (!policyCheck.allowed) {
865
1058
  yield { type: 'log', content: `Tool '${toolName}' blocked by policy: ${policyCheck.reason}` };
866
1059
  this.postExecResult({ success: false, error: policyCheck.reason });
867
1060
  return;
868
1061
  }
869
- // Get tool definition
870
- const tool = options.tools.get(toolName);
1062
+ // Get tool definition. `ask_user` is SDK-internal so exec(ask_user(...))
1063
+ // works in DML even when no capability tools are registered.
1064
+ const tool = toolName === 'ask_user'
1065
+ ? this.internalAskUserTool(options)
1066
+ : options.tools.get(toolName);
871
1067
  if (!tool) {
872
1068
  yield { type: 'log', content: `Tool '${toolName}' not found` };
873
1069
  this.postExecResult({ success: false, error: `Tool not found: ${toolName}` });
@@ -879,7 +1075,7 @@ export class DMLRunner {
879
1075
  argsObj = this.argsToObject(args, tool);
880
1076
  // Emit tool_call event before execution
881
1077
  yield buildToolStartEvent(toolName, argsObj);
882
- const result = await tool.execute(argsObj);
1078
+ const result = await tool.execute(argsObj, { signal: options.signal });
883
1079
  yield buildToolCompletionEvent(toolName, argsObj, result);
884
1080
  // Post result back to Prolog
885
1081
  this.postExecResult({ success: true, result });
@@ -1172,7 +1368,7 @@ export class DMLRunner {
1172
1368
  try {
1173
1369
  argsObj = this.argsToObject(execArgs, tool);
1174
1370
  onToolEvent?.(buildToolStartEvent(execToolName, argsObj));
1175
- const execResult = await tool.execute(argsObj);
1371
+ const execResult = await tool.execute(argsObj, { signal: runContext.signal });
1176
1372
  onToolEvent?.(buildToolCompletionEvent(execToolName, argsObj, execResult));
1177
1373
  const postGoal = `deepclause_mi:post_exec_result('${this.sessionId}', success, ${this.toPrologTerm(execResult)})`;
1178
1374
  this.query(postGoal);
@@ -1243,6 +1439,7 @@ export class DMLRunner {
1243
1439
  },
1244
1440
  signal: runContext.signal,
1245
1441
  llmBackend: this.options.llmBackend,
1442
+ resolveMedia: (refs) => this.readMediaRefs(runContext.workspacePath, refs),
1246
1443
  });
1247
1444
  this.postLlmResult({ success: true, text });
1248
1445
  if (usage) {
@@ -1436,6 +1633,7 @@ export class DMLRunner {
1436
1633
  streaming: this.options.streaming,
1437
1634
  debug: this.options.debug,
1438
1635
  llmBackend: this.options.llmBackend,
1636
+ resolveMedia: (refs) => this.readMediaRefs(runContext.workspacePath, refs),
1439
1637
  onOutput: (text) => {
1440
1638
  outputs.push(text);
1441
1639
  onOutput(text);
@@ -1443,26 +1641,26 @@ export class DMLRunner {
1443
1641
  onStream: (chunk, done) => {
1444
1642
  onToolEvent?.({ type: 'stream', content: chunk, done });
1445
1643
  },
1644
+ onReasoning: (chunk, done) => {
1645
+ onToolEvent?.({ type: 'reasoning', content: chunk, done });
1646
+ },
1446
1647
  onUsage: (usage) => {
1447
1648
  onToolEvent?.({ type: 'usage', usage });
1448
1649
  },
1650
+ onModelCallPhase: (phase) => {
1651
+ onToolEvent?.({ type: 'model_call', modelCallState: phase, modelId: this.options.model });
1652
+ },
1449
1653
  onToolCall: onToolCall ?? (() => { }), // Forward tool call events to parent
1450
- onBeforeModelCall: async (messages, lastInputTokens) => this.applyCompactionBindings({
1451
- messages,
1452
- lastInputTokens,
1453
- scope: 'loop',
1454
- trigger: 'before_model_call',
1455
- options: { compaction: runContext.compaction },
1456
- executionContext: runContext,
1457
- registeredTools,
1458
- toolPolicy: runContext.toolPolicy ?? null,
1459
- emitEvent: onToolEvent,
1460
- }),
1654
+ onBeforeModelCall: async (messages, lastInputTokens) => {
1655
+ const hostCompacted = await this.applyHostCompaction(messages, lastInputTokens, onToolEvent);
1656
+ return hostCompacted ?? messages;
1657
+ },
1461
1658
  onAskUser: async () => {
1462
1659
  // Nested agent loops don't support ask_user directly
1463
1660
  // If needed, the tool should use exec(ask_user, ...) instead
1464
1661
  return '';
1465
1662
  },
1663
+ onModelCall: runContext.onModelCall,
1466
1664
  signal: runContext.signal,
1467
1665
  });
1468
1666
  if (process.env.DEBUG_RUNNER) {
@@ -1524,116 +1722,17 @@ export class DMLRunner {
1524
1722
  }
1525
1723
  }
1526
1724
  }
1527
- async applyCompactionBindings(params) {
1528
- const resolved = resolveCompactionOptions(this.options.compaction, params.options.compaction);
1529
- const bindings = getCompactionBindings(resolved, params.scope, params.trigger);
1530
- if (bindings.length === 0) {
1531
- return params.messages;
1532
- }
1533
- let messages = params.messages;
1534
- for (const binding of bindings) {
1535
- const result = await executeCompactor({
1536
- binding,
1537
- messages,
1538
- knownInputTokens: params.lastInputTokens,
1539
- maxContextTokens: this.options.contextWindow,
1540
- emitEvent: params.emitEvent,
1541
- execute: (request) => this.runBoundCompactor({
1542
- request,
1543
- executionContext: params.executionContext,
1544
- registeredTools: params.registeredTools,
1545
- toolPolicy: params.toolPolicy,
1546
- }),
1547
- });
1548
- params.emitEvent?.(result.event);
1549
- if (result.usageByModel) {
1550
- for (const totals of Object.values(result.usageByModel)) {
1551
- params.emitEvent?.({ type: 'usage', usage: {
1552
- inputTokens: totals.inputTokens,
1553
- outputTokens: totals.outputTokens,
1554
- totalTokens: totals.totalTokens,
1555
- cacheReadTokens: totals.cacheReadTokens || undefined,
1556
- cacheWriteTokens: totals.cacheWriteTokens || undefined,
1557
- reasoningTokens: totals.reasoningTokens || undefined,
1558
- } });
1559
- }
1560
- }
1561
- messages = result.messages;
1562
- }
1563
- return messages;
1564
- }
1565
- async runBoundCompactor(params) {
1566
- const { binding, params: compactorParams } = params.request;
1567
- const code = await this.resolveCompactorSource(binding.compactor.source, binding.compactor.sourceType, params.executionContext.workspacePath);
1568
- const model = binding.compactor.model ?? this.options.model;
1569
- const provider = binding.compactor.provider
1570
- ?? (binding.compactor.model ? detectProvider(model) : this.options.provider);
1571
- const compactorRunner = new DMLRunner(this.swipl, {
1572
- ...this.options,
1573
- model,
1574
- provider,
1575
- streaming: false,
1576
- compaction: { enabled: false },
1577
- });
1578
- const { signal, dispose } = createChildAbortController(params.executionContext.signal, binding.compactor.timeoutMs);
1579
- try {
1580
- const tools = binding.compactor.inheritTools ? params.registeredTools : new Map();
1581
- let answer = '';
1582
- let errorContent = '';
1583
- const usageByModel = {};
1584
- for await (const event of compactorRunner.run(code, {
1585
- workspacePath: params.executionContext.workspacePath,
1586
- gasLimit: binding.compactor.gasLimit,
1587
- params: compactorParams,
1588
- initialMessages: params.request.messages,
1589
- tools,
1590
- toolPolicy: binding.compactor.toolPolicy ?? (binding.compactor.inheritTools ? params.toolPolicy : null) ?? null,
1591
- onInputRequired: async (prompt) => {
1592
- throw new Error(`Compactor requested unexpected input: ${prompt}`);
1593
- },
1594
- signal,
1595
- compaction: { enabled: false },
1596
- })) {
1597
- if (event.type === 'answer' && event.content) {
1598
- answer = event.content;
1599
- }
1600
- else if (event.type === 'error' && event.content) {
1601
- errorContent = event.content;
1602
- }
1603
- else if (event.type === 'usage' && event.usage) {
1604
- recordTokenUsage(usageByModel, this.options.model, event.usage);
1605
- }
1606
- }
1607
- return {
1608
- answer,
1609
- error: errorContent || undefined,
1610
- usageByModel: Object.keys(usageByModel).length > 0 ? usageByModel : undefined,
1611
- };
1612
- }
1613
- finally {
1614
- dispose();
1615
- await compactorRunner.dispose();
1616
- }
1617
- }
1618
- async resolveCompactorSource(source, sourceType, workspacePath) {
1619
- if (sourceType === 'inline') {
1620
- return source;
1621
- }
1622
- const basePath = workspacePath ?? process.cwd();
1623
- const candidatePath = path.isAbsolute(source) ? source : path.resolve(basePath, source);
1624
- try {
1625
- return await fs.readFile(candidatePath, 'utf8');
1725
+ async applyHostCompaction(messages, lastInputTokens, emit) {
1726
+ const host = this.options.compactMessages;
1727
+ if (!host) {
1728
+ return null;
1626
1729
  }
1627
- catch (error) {
1628
- if (sourceType === 'file') {
1629
- throw error;
1630
- }
1631
- return source;
1730
+ const result = await host(messages, lastInputTokens);
1731
+ if (result.event) {
1732
+ emit?.(result.event);
1632
1733
  }
1734
+ return result.messages;
1633
1735
  }
1634
- /**
1635
- * Extract memory from payload (now passed via state threading)
1636
- */
1637
1736
  extractMessagesFromValue(rawMessages) {
1638
1737
  const normalized = toJsValue(rawMessages);
1639
1738
  if (!Array.isArray(normalized)) {
@@ -1643,19 +1742,46 @@ export class DMLRunner {
1643
1742
  const msg = toJsValue(messageValue);
1644
1743
  const role = String(toJsValue(msg.role) ?? 'user');
1645
1744
  const content = String(toJsValue(msg.content) ?? '');
1745
+ const media = this.extractMediaRefs(msg.media);
1646
1746
  if (role !== 'system' && role !== 'user' && role !== 'assistant') {
1647
- return { role: 'user', content };
1747
+ return { role: 'user', content, media: media.length ? media : undefined };
1648
1748
  }
1649
- return { role: role, content };
1749
+ return { role: role, content, media: media.length ? media : undefined };
1650
1750
  });
1651
1751
  }
1752
+ /** Decode `media{path:..., mime:...}` dicts into reference-only refs. */
1753
+ extractMediaRefs(raw) {
1754
+ const value = toJsValue(raw);
1755
+ if (!Array.isArray(value))
1756
+ return [];
1757
+ const refs = [];
1758
+ for (const entry of value) {
1759
+ const obj = toJsValue(entry);
1760
+ const mediaPath = String(obj?.path ?? '');
1761
+ const mimeType = String(obj?.mime ?? obj?.mimeType ?? '');
1762
+ if (mediaPath && mimeType)
1763
+ refs.push({ path: mediaPath, mimeType });
1764
+ }
1765
+ return refs;
1766
+ }
1767
+ /** Resolve a message's reference-only media to base64 bytes (workspace files). */
1768
+ async readMediaRefs(workspacePath, refs) {
1769
+ return resolveMediaRefs(workspacePath ?? process.cwd(), refs);
1770
+ }
1652
1771
  extractMemoryFromPayload(rawPayload) {
1653
1772
  const rawMemory = toJsValue(rawPayload.memory);
1654
1773
  return this.extractMessagesFromValue(rawMemory);
1655
1774
  }
1656
1775
  serializeMemoryMessages(messages) {
1657
1776
  return messages
1658
- .map((message) => `message{role: ${message.role}, content: ${this.toPrologTerm(message.content)}}`)
1777
+ .map((message) => {
1778
+ const media = message.media && message.media.length > 0
1779
+ ? `, media: [${message.media
1780
+ .map((ref) => `media{path: ${this.toPrologTerm(ref.path)}, mime: ${this.toPrologTerm(ref.mimeType)}}`)
1781
+ .join(', ')}]`
1782
+ : '';
1783
+ return `message{role: ${message.role}, content: ${this.toPrologTerm(message.content)}${media}}`;
1784
+ })
1659
1785
  .join(', ');
1660
1786
  }
1661
1787
  /**
@@ -1723,6 +1849,30 @@ export class DMLRunner {
1723
1849
  const fullMemoryStr = this.serializeMemoryMessages(fullMemory ?? []);
1724
1850
  this.query(`deepclause_mi:post_tool_agent_result('${this.sessionId}', '${toolEngineId}', ${result.success}, vars{${varsStr}}, [${messagesStr}], [${fullMemoryStr}])`);
1725
1851
  }
1852
+ /** SDK-internal ask_user tool used by exec(ask_user(...)) in DML. */
1853
+ internalAskUserTool(options) {
1854
+ return {
1855
+ description: 'Ask the user for input or clarification',
1856
+ parameters: {
1857
+ type: 'object',
1858
+ properties: {
1859
+ prompt: { type: 'string', description: 'The question or prompt to show the user' },
1860
+ },
1861
+ required: ['prompt'],
1862
+ },
1863
+ execute: async (args) => {
1864
+ const prompt = String(args.prompt ?? args.arg1 ?? '');
1865
+ if (!prompt) {
1866
+ return { error: 'No prompt provided to ask_user' };
1867
+ }
1868
+ const response = await options.onInputRequired(prompt);
1869
+ if (response === undefined || response === null) {
1870
+ return { cancelled: true };
1871
+ }
1872
+ return { user_response: response };
1873
+ },
1874
+ };
1875
+ }
1726
1876
  /**
1727
1877
  * Provide user input to waiting Prolog
1728
1878
  */
@@ -1730,6 +1880,10 @@ export class DMLRunner {
1730
1880
  const escaped = input.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
1731
1881
  this.query(`deepclause_mi:provide_input('${this.sessionId}', "${escaped}")`);
1732
1882
  }
1883
+ /** Signal a waiting input/2 that no human is available. */
1884
+ cancelInput() {
1885
+ this.query(`deepclause_mi:provide_input_cancelled('${this.sessionId}')`);
1886
+ }
1733
1887
  /**
1734
1888
  * Execute a Prolog query
1735
1889
  */
@@ -1748,14 +1902,17 @@ export class DMLRunner {
1748
1902
  * Cleanup resources
1749
1903
  */
1750
1904
  cleanup() {
1751
- if (this.engine) {
1905
+ if (this.frames.size > 0) {
1752
1906
  try {
1907
+ // destroy_engine/1 ends every frame registered for the session and
1908
+ // retracts the session's dynamic state.
1753
1909
  this.query(`deepclause_mi:destroy_engine('${this.sessionId}')`);
1754
1910
  }
1755
1911
  catch {
1756
1912
  // Ignore cleanup errors
1757
1913
  }
1758
- this.engine = null;
1914
+ this.frames.clear();
1915
+ this.defaultFrameId = null;
1759
1916
  }
1760
1917
  }
1761
1918
  /**