deepclause-sdk 0.0.88 → 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 (318) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +418 -914
  3. package/dist/agent.d.ts +19 -2
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +221 -4
  6. package/dist/agent.js.map +1 -1
  7. package/dist/cli/config.d.ts +323 -176
  8. package/dist/cli/config.d.ts.map +1 -1
  9. package/dist/cli/config.js +146 -188
  10. package/dist/cli/config.js.map +1 -1
  11. package/dist/cli/index.d.ts +3 -2
  12. package/dist/cli/index.d.ts.map +1 -1
  13. package/dist/cli/index.js +61 -425
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/login.d.ts +29 -0
  16. package/dist/cli/login.d.ts.map +1 -0
  17. package/dist/cli/login.js +184 -0
  18. package/dist/cli/login.js.map +1 -0
  19. package/dist/cli/readline.d.ts +6 -0
  20. package/dist/cli/readline.d.ts.map +1 -0
  21. package/dist/cli/readline.js +15 -0
  22. package/dist/cli/readline.js.map +1 -0
  23. package/dist/cli/repl-bin.d.ts +9 -0
  24. package/dist/cli/repl-bin.d.ts.map +1 -0
  25. package/dist/cli/repl-bin.js +16 -0
  26. package/dist/cli/repl-bin.js.map +1 -0
  27. package/dist/cli/repl.d.ts +191 -0
  28. package/dist/cli/repl.d.ts.map +1 -0
  29. package/dist/cli/repl.js +1232 -0
  30. package/dist/cli/repl.js.map +1 -0
  31. package/dist/cli/run.d.ts +17 -18
  32. package/dist/cli/run.d.ts.map +1 -1
  33. package/dist/cli/run.js +196 -183
  34. package/dist/cli/run.js.map +1 -1
  35. package/dist/cli/set-backend.d.ts +53 -0
  36. package/dist/cli/set-backend.d.ts.map +1 -0
  37. package/dist/cli/set-backend.js +259 -0
  38. package/dist/cli/set-backend.js.map +1 -0
  39. package/dist/cli/visualize.d.ts +38 -0
  40. package/dist/cli/visualize.d.ts.map +1 -0
  41. package/dist/cli/visualize.js +125 -0
  42. package/dist/cli/visualize.js.map +1 -0
  43. package/dist/compiler.d.ts +5 -52
  44. package/dist/compiler.d.ts.map +1 -1
  45. package/dist/compiler.js +4 -768
  46. package/dist/compiler.js.map +1 -1
  47. package/dist/index.d.ts +29 -2
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +20 -2
  50. package/dist/index.js.map +1 -1
  51. package/dist/judge/index.d.ts +9 -0
  52. package/dist/judge/index.d.ts.map +1 -0
  53. package/dist/judge/index.js +6 -0
  54. package/dist/judge/index.js.map +1 -0
  55. package/dist/judge/jev.d.ts +21 -0
  56. package/dist/judge/jev.d.ts.map +1 -0
  57. package/dist/judge/jev.js +218 -0
  58. package/dist/judge/jev.js.map +1 -0
  59. package/dist/judge/llm.d.ts +23 -0
  60. package/dist/judge/llm.d.ts.map +1 -0
  61. package/dist/judge/llm.js +120 -0
  62. package/dist/judge/llm.js.map +1 -0
  63. package/dist/judge/marshal.d.ts +26 -0
  64. package/dist/judge/marshal.d.ts.map +1 -0
  65. package/dist/judge/marshal.js +266 -0
  66. package/dist/judge/marshal.js.map +1 -0
  67. package/dist/judge/mock.d.ts +28 -0
  68. package/dist/judge/mock.d.ts.map +1 -0
  69. package/dist/judge/mock.js +98 -0
  70. package/dist/judge/mock.js.map +1 -0
  71. package/dist/judge/types.d.ts +95 -0
  72. package/dist/judge/types.d.ts.map +1 -0
  73. package/dist/judge/types.js +23 -0
  74. package/dist/judge/types.js.map +1 -0
  75. package/dist/prolog/bridge.d.ts +5 -1
  76. package/dist/prolog/bridge.d.ts.map +1 -1
  77. package/dist/prolog/bridge.js +29 -1
  78. package/dist/prolog/bridge.js.map +1 -1
  79. package/dist/prolog-src/deepclause_analysis.pl +10 -2
  80. package/dist/prolog-src/deepclause_mi.pl +2522 -118
  81. package/dist/repl/agent.d.ts +72 -0
  82. package/dist/repl/agent.d.ts.map +1 -0
  83. package/dist/repl/agent.js +366 -0
  84. package/dist/repl/agent.js.map +1 -0
  85. package/dist/repl/auth.d.ts +45 -0
  86. package/dist/repl/auth.d.ts.map +1 -0
  87. package/dist/repl/auth.js +116 -0
  88. package/dist/repl/auth.js.map +1 -0
  89. package/dist/repl/backend.d.ts +290 -0
  90. package/dist/repl/backend.d.ts.map +1 -0
  91. package/dist/repl/backend.js +370 -0
  92. package/dist/repl/backend.js.map +1 -0
  93. package/dist/repl/compaction.d.ts +122 -0
  94. package/dist/repl/compaction.d.ts.map +1 -0
  95. package/dist/repl/compaction.js +318 -0
  96. package/dist/repl/compaction.js.map +1 -0
  97. package/dist/repl/export.d.ts +30 -0
  98. package/dist/repl/export.d.ts.map +1 -0
  99. package/dist/repl/export.js +23 -0
  100. package/dist/repl/export.js.map +1 -0
  101. package/dist/repl/frames.d.ts +21 -0
  102. package/dist/repl/frames.d.ts.map +1 -0
  103. package/dist/repl/frames.js +79 -0
  104. package/dist/repl/frames.js.map +1 -0
  105. package/dist/repl/harness-tools.d.ts +74 -0
  106. package/dist/repl/harness-tools.d.ts.map +1 -0
  107. package/dist/repl/harness-tools.js +121 -0
  108. package/dist/repl/harness-tools.js.map +1 -0
  109. package/dist/repl/highlight.d.ts +30 -0
  110. package/dist/repl/highlight.d.ts.map +1 -0
  111. package/dist/repl/highlight.js +285 -0
  112. package/dist/repl/highlight.js.map +1 -0
  113. package/dist/repl/history.d.ts +234 -0
  114. package/dist/repl/history.d.ts.map +1 -0
  115. package/dist/repl/history.js +661 -0
  116. package/dist/repl/history.js.map +1 -0
  117. package/dist/repl/index.d.ts +120 -0
  118. package/dist/repl/index.d.ts.map +1 -0
  119. package/dist/repl/index.js +107 -0
  120. package/dist/repl/index.js.map +1 -0
  121. package/dist/repl/judge.d.ts +51 -0
  122. package/dist/repl/judge.d.ts.map +1 -0
  123. package/dist/repl/judge.js +58 -0
  124. package/dist/repl/judge.js.map +1 -0
  125. package/dist/repl/markdown.d.ts +25 -0
  126. package/dist/repl/markdown.d.ts.map +1 -0
  127. package/dist/repl/markdown.js +103 -0
  128. package/dist/repl/markdown.js.map +1 -0
  129. package/dist/repl/picker.d.ts +34 -0
  130. package/dist/repl/picker.d.ts.map +1 -0
  131. package/dist/repl/picker.js +81 -0
  132. package/dist/repl/picker.js.map +1 -0
  133. package/dist/repl/project-context.d.ts +16 -0
  134. package/dist/repl/project-context.d.ts.map +1 -0
  135. package/dist/repl/project-context.js +23 -0
  136. package/dist/repl/project-context.js.map +1 -0
  137. package/dist/repl/render.d.ts +160 -0
  138. package/dist/repl/render.d.ts.map +1 -0
  139. package/dist/repl/render.js +715 -0
  140. package/dist/repl/render.js.map +1 -0
  141. package/dist/repl/session.d.ts +356 -0
  142. package/dist/repl/session.d.ts.map +1 -0
  143. package/dist/repl/session.js +1275 -0
  144. package/dist/repl/session.js.map +1 -0
  145. package/dist/repl/status.d.ts +100 -0
  146. package/dist/repl/status.d.ts.map +1 -0
  147. package/dist/repl/status.js +218 -0
  148. package/dist/repl/status.js.map +1 -0
  149. package/dist/repl/tools.d.ts +53 -0
  150. package/dist/repl/tools.d.ts.map +1 -0
  151. package/dist/repl/tools.js +307 -0
  152. package/dist/repl/tools.js.map +1 -0
  153. package/dist/repl/toolset.d.ts +32 -0
  154. package/dist/repl/toolset.d.ts.map +1 -0
  155. package/dist/repl/toolset.js +82 -0
  156. package/dist/repl/toolset.js.map +1 -0
  157. package/dist/repl/tui/editor.d.ts +17 -0
  158. package/dist/repl/tui/editor.d.ts.map +1 -0
  159. package/dist/repl/tui/editor.js +39 -0
  160. package/dist/repl/tui/editor.js.map +1 -0
  161. package/dist/repl/tui/status-bar.d.ts +26 -0
  162. package/dist/repl/tui/status-bar.d.ts.map +1 -0
  163. package/dist/repl/tui/status-bar.js +47 -0
  164. package/dist/repl/tui/status-bar.js.map +1 -0
  165. package/dist/repl/tui/theme.d.ts +30 -0
  166. package/dist/repl/tui/theme.d.ts.map +1 -0
  167. package/dist/repl/tui/theme.js +81 -0
  168. package/dist/repl/tui/theme.js.map +1 -0
  169. package/dist/repl/tui/transcript.d.ts +39 -0
  170. package/dist/repl/tui/transcript.d.ts.map +1 -0
  171. package/dist/repl/tui/transcript.js +123 -0
  172. package/dist/repl/tui/transcript.js.map +1 -0
  173. package/dist/runner.d.ts +145 -13
  174. package/dist/runner.d.ts.map +1 -1
  175. package/dist/runner.js +600 -282
  176. package/dist/runner.js.map +1 -1
  177. package/dist/sdk.d.ts.map +1 -1
  178. package/dist/sdk.js +56 -32
  179. package/dist/sdk.js.map +1 -1
  180. package/dist/system/assets/docs/AGENT_PROMPT.md +1373 -0
  181. package/dist/system/assets/docs/DML_AUTHORING.md +195 -0
  182. package/dist/system/assets/docs/DML_REFERENCE.md +393 -72
  183. package/dist/system/assets/docs/HARNESS_EXAMPLES.md +172 -0
  184. package/dist/system/assets/docs/HARNESS_PATTERNS.md +562 -0
  185. package/dist/system/assets/docs/META_PROMPT.md +68 -0
  186. package/dist/system/assets/docs/PROLOG_SNIPPETS.md +117 -0
  187. package/dist/system/assets/index.d.ts +9 -17
  188. package/dist/system/assets/index.d.ts.map +1 -1
  189. package/dist/system/assets/index.js +26 -85
  190. package/dist/system/assets/index.js.map +1 -1
  191. package/dist/system/runtime/agentvm-manager.d.ts +9 -2
  192. package/dist/system/runtime/agentvm-manager.d.ts.map +1 -1
  193. package/dist/system/runtime/agentvm-manager.js +70 -44
  194. package/dist/system/runtime/agentvm-manager.js.map +1 -1
  195. package/dist/system/runtime/media.d.ts +23 -0
  196. package/dist/system/runtime/media.d.ts.map +1 -0
  197. package/dist/system/runtime/media.js +55 -0
  198. package/dist/system/runtime/media.js.map +1 -0
  199. package/dist/system/runtime/shell-limits.d.ts +21 -0
  200. package/dist/system/runtime/shell-limits.d.ts.map +1 -0
  201. package/dist/system/runtime/shell-limits.js +39 -0
  202. package/dist/system/runtime/shell-limits.js.map +1 -0
  203. package/dist/system/runtime/shell-manager.d.ts +8 -3
  204. package/dist/system/runtime/shell-manager.d.ts.map +1 -1
  205. package/dist/system/runtime/shell-manager.js +56 -39
  206. package/dist/system/runtime/shell-manager.js.map +1 -1
  207. package/dist/system/runtime/shell-tool-events.js +5 -0
  208. package/dist/system/runtime/shell-tool-events.js.map +1 -1
  209. package/dist/system/runtime/token-usage.d.ts +2 -0
  210. package/dist/system/runtime/token-usage.d.ts.map +1 -1
  211. package/dist/system/runtime/token-usage.js +7 -0
  212. package/dist/system/runtime/token-usage.js.map +1 -1
  213. package/dist/tools.d.ts +3 -21
  214. package/dist/tools.d.ts.map +1 -1
  215. package/dist/tools.js +14 -168
  216. package/dist/tools.js.map +1 -1
  217. package/dist/types.d.ts +149 -118
  218. package/dist/types.d.ts.map +1 -1
  219. package/dist/validate.d.ts +19 -0
  220. package/dist/validate.d.ts.map +1 -0
  221. package/dist/validate.js +338 -0
  222. package/dist/validate.js.map +1 -0
  223. package/dist/visualize/index.d.ts +52 -0
  224. package/dist/visualize/index.d.ts.map +1 -0
  225. package/dist/visualize/index.js +189 -0
  226. package/dist/visualize/index.js.map +1 -0
  227. package/package.json +17 -15
  228. package/src/prolog-src/deepclause_analysis.pl +10 -2
  229. package/src/prolog-src/deepclause_mi.pl +2522 -118
  230. package/dist/cli/commands.d.ts +0 -42
  231. package/dist/cli/commands.d.ts.map +0 -1
  232. package/dist/cli/commands.js +0 -246
  233. package/dist/cli/commands.js.map +0 -1
  234. package/dist/cli/compile.d.ts +0 -99
  235. package/dist/cli/compile.d.ts.map +0 -1
  236. package/dist/cli/compile.js +0 -363
  237. package/dist/cli/compile.js.map +0 -1
  238. package/dist/cli/default-skills.d.ts +0 -10
  239. package/dist/cli/default-skills.d.ts.map +0 -1
  240. package/dist/cli/default-skills.js +0 -206
  241. package/dist/cli/default-skills.js.map +0 -1
  242. package/dist/cli/interactive.d.ts +0 -2
  243. package/dist/cli/interactive.d.ts.map +0 -1
  244. package/dist/cli/interactive.js +0 -20
  245. package/dist/cli/interactive.js.map +0 -1
  246. package/dist/cli/mcp.d.ts +0 -56
  247. package/dist/cli/mcp.d.ts.map +0 -1
  248. package/dist/cli/mcp.js +0 -138
  249. package/dist/cli/mcp.js.map +0 -1
  250. package/dist/cli/prompt.d.ts +0 -2
  251. package/dist/cli/prompt.d.ts.map +0 -1
  252. package/dist/cli/prompt.js +0 -2
  253. package/dist/cli/prompt.js.map +0 -1
  254. package/dist/cli/search.d.ts +0 -19
  255. package/dist/cli/search.d.ts.map +0 -1
  256. package/dist/cli/search.js +0 -189
  257. package/dist/cli/search.js.map +0 -1
  258. package/dist/cli/tool-args.d.ts +0 -2
  259. package/dist/cli/tool-args.d.ts.map +0 -1
  260. package/dist/cli/tool-args.js +0 -39
  261. package/dist/cli/tool-args.js.map +0 -1
  262. package/dist/cli/tools.d.ts +0 -36
  263. package/dist/cli/tools.d.ts.map +0 -1
  264. package/dist/cli/tools.js +0 -229
  265. package/dist/cli/tools.js.map +0 -1
  266. package/dist/cli/tui/index.d.ts +0 -22
  267. package/dist/cli/tui/index.d.ts.map +0 -1
  268. package/dist/cli/tui/index.js +0 -29
  269. package/dist/cli/tui/index.js.map +0 -1
  270. package/dist/cli/tui.d.ts +0 -167
  271. package/dist/cli/tui.d.ts.map +0 -1
  272. package/dist/cli/tui.js +0 -5228
  273. package/dist/cli/tui.js.map +0 -1
  274. package/dist/compiler_prompt.d.ts +0 -20
  275. package/dist/compiler_prompt.d.ts.map +0 -1
  276. package/dist/compiler_prompt.js +0 -870
  277. package/dist/compiler_prompt.js.map +0 -1
  278. package/dist/deepclause-cli.skill +0 -0
  279. package/dist/prolog-src/deepclause_mi.pl.bak +0 -570
  280. package/dist/system/assets/compactors/default-memory-compactor.dml +0 -6
  281. package/dist/system/assets/docs/CONDUCTOR_PROMPT.md +0 -105
  282. package/dist/system/assets/docs/DML_COMPILER_PROMPT.md +0 -700
  283. package/dist/system/assets/docs/TUI.md +0 -150
  284. package/dist/system/assets/recipes/deepclause-coding-workflow/SKILL.md +0 -100
  285. package/dist/system/assets/skills/conductor.dml +0 -76
  286. package/dist/system/assets/skills/deep-planner.dml +0 -412
  287. package/dist/system/assets/skills/plan.dml +0 -429
  288. package/dist/system/assets/skills/security-planner.dml +0 -337
  289. package/dist/system/assets/skills/skill-creator.dml +0 -187
  290. package/dist/system/runtime/catalog-recipes.d.ts +0 -24
  291. package/dist/system/runtime/catalog-recipes.d.ts.map +0 -1
  292. package/dist/system/runtime/catalog-recipes.js +0 -273
  293. package/dist/system/runtime/catalog-recipes.js.map +0 -1
  294. package/dist/system/runtime/catalog-skills.d.ts +0 -50
  295. package/dist/system/runtime/catalog-skills.d.ts.map +0 -1
  296. package/dist/system/runtime/catalog-skills.js +0 -152
  297. package/dist/system/runtime/catalog-skills.js.map +0 -1
  298. package/dist/system/runtime/conductor.d.ts +0 -105
  299. package/dist/system/runtime/conductor.d.ts.map +0 -1
  300. package/dist/system/runtime/conductor.js +0 -732
  301. package/dist/system/runtime/conductor.js.map +0 -1
  302. package/dist/system/runtime/console-capture.d.ts +0 -9
  303. package/dist/system/runtime/console-capture.d.ts.map +0 -1
  304. package/dist/system/runtime/console-capture.js +0 -67
  305. package/dist/system/runtime/console-capture.js.map +0 -1
  306. package/dist/system/runtime/dml-executor.d.ts +0 -52
  307. package/dist/system/runtime/dml-executor.d.ts.map +0 -1
  308. package/dist/system/runtime/dml-executor.js +0 -203
  309. package/dist/system/runtime/dml-executor.js.map +0 -1
  310. package/dist/system/runtime/runtime-tools.d.ts +0 -31
  311. package/dist/system/runtime/runtime-tools.d.ts.map +0 -1
  312. package/dist/system/runtime/runtime-tools.js +0 -312
  313. package/dist/system/runtime/runtime-tools.js.map +0 -1
  314. package/dist/system/runtime/skill-creator.d.ts +0 -38
  315. package/dist/system/runtime/skill-creator.d.ts.map +0 -1
  316. package/dist/system/runtime/skill-creator.js +0 -763
  317. package/dist/system/runtime/skill-creator.js.map +0 -1
  318. package/src/prolog-src/deepclause_mi.pl.bak +0 -570
package/dist/runner.js CHANGED
@@ -1,21 +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
- import { executeCompactor, getCompactionBindings, resolveCompactionOptions, } from './compaction.js';
6
+ import { judgeAnswersToValues, judgeCacheKey, normalizeJudgeState, parseJudgeQuestions, validateJudgeAnswers, valuesToPrologList } from './judge/marshal.js';
9
7
  import { buildToolCompletionEvent, buildToolFailureEvent, buildToolStartEvent, } from './system/runtime/shell-tool-events.js';
10
8
  import { checkToolPolicy } from './tools.js';
11
9
  import { generateLlmReply, sampleSingleToken } from './prolog/bridge.js';
12
- import { recordTokenUsage } from './system/runtime/token-usage.js';
13
10
  import { buildReasoningProviderOptions } from './system/config/model-database.js';
11
+ import { resolveMediaRefs } from './system/runtime/media.js';
14
12
  /**
15
13
  * Convert swipl-wasm value to plain JavaScript value
16
14
  * Handles PrologString, PrologAtom, PrologList, etc.
17
15
  */
18
- function toJsValue(value) {
16
+ export function toJsValue(value) {
19
17
  if (value === null || value === undefined) {
20
18
  return value;
21
19
  }
@@ -59,45 +57,45 @@ function toJsValue(value) {
59
57
  }
60
58
  return value;
61
59
  }
62
- function detectProvider(model) {
63
- const lower = model.toLowerCase();
64
- if (lower.includes('gpt') || lower.includes('o1') || lower.includes('o3')) {
65
- return 'openai';
66
- }
67
- if (lower.includes('claude')) {
68
- return 'anthropic';
69
- }
70
- if (lower.includes('gemini') || lower.includes('palm')) {
71
- 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 [];
72
71
  }
73
- return 'openrouter';
74
- }
75
- function createChildAbortController(parentSignal, timeoutMs) {
76
- const controller = new AbortController();
77
- const disposers = [];
78
- if (parentSignal) {
79
- if (parentSignal.aborted) {
80
- 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
+ }
81
90
  }
82
- else {
83
- const onAbort = () => controller.abort(parentSignal.reason);
84
- parentSignal.addEventListener('abort', onAbort, { once: true });
85
- 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
+ });
86
96
  }
87
97
  }
88
- let timeout;
89
- if (timeoutMs > 0) {
90
- timeout = setTimeout(() => controller.abort(new Error(`Compactor timed out after ${timeoutMs}ms`)), timeoutMs);
91
- disposers.push(() => clearTimeout(timeout));
92
- }
93
- return {
94
- signal: controller.signal,
95
- dispose: () => {
96
- for (const dispose of disposers) {
97
- dispose();
98
- }
99
- },
100
- };
98
+ return bindings;
101
99
  }
102
100
  export function decodeAgentOutputVars(rawOutputVars) {
103
101
  const normalized = toJsValue(rawOutputVars);
@@ -141,10 +139,18 @@ function prologTypeToJsonType(prologType) {
141
139
  export class DMLRunner {
142
140
  swipl;
143
141
  options;
144
- engine = null;
145
142
  sessionId = '';
146
143
  currentMemory = [];
147
144
  stepCounter = 0;
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;
148
154
  constructor(swipl, options) {
149
155
  this.swipl = swipl;
150
156
  this.options = options;
@@ -162,6 +168,7 @@ export class DMLRunner {
162
168
  // Generate unique session ID
163
169
  this.sessionId = `sess_${Date.now()}_${Math.random().toString(36).slice(2)}`;
164
170
  const memoryId = `mem_${this.sessionId}`; // Kept for API compatibility
171
+ this.judgeCache.clear();
165
172
  try {
166
173
  // Mount workspace if path provided
167
174
  if (options.workspacePath) {
@@ -188,91 +195,18 @@ export class DMLRunner {
188
195
  yield { type: 'error', content: parseResult.error };
189
196
  return;
190
197
  }
191
- // Create cooperative engine with args
198
+ // Create cooperative engine with args (registers the default query frame)
192
199
  const engineResult = this.createEngine(memoryId, args, params);
193
200
  if (engineResult.error) {
194
201
  yield { type: 'error', content: engineResult.error };
195
202
  return;
196
203
  }
197
- // Cooperative execution loop
198
- while (true) {
199
- // Check for abort
200
- if (options.signal?.aborted) {
201
- const reason = options.signal.reason;
202
- const abortMessage = reason instanceof Error
203
- ? reason.message
204
- : typeof reason === 'string' && reason.trim().length > 0
205
- ? reason
206
- : 'Execution aborted';
207
- yield { type: 'error', content: abortMessage };
208
- break;
209
- }
210
- // Step the engine
211
- const step = this.stepEngine();
212
- if (process.env.DEBUG_STEPS) {
213
- console.log('[MAIN_LOOP] step:', step.status, step.content?.substring(0, 50));
214
- }
215
- switch (step.status) {
216
- case 'output':
217
- if (step.content) {
218
- if (process.env.DEBUG_STEPS) {
219
- console.log('[MAIN_LOOP] yielding output:', step.content.substring(0, 50));
220
- }
221
- yield { type: 'output', content: step.content };
222
- }
223
- break;
224
- case 'log':
225
- if (step.content) {
226
- yield { type: 'log', content: step.content };
227
- }
228
- break;
229
- case 'answer':
230
- yield { type: 'answer', content: step.content };
231
- break;
232
- case 'request_agent_loop':
233
- // Run agent loop for task() predicate
234
- yield* this.handleAgentLoop(step.payload, memoryId, options);
235
- break;
236
- case 'request_exec':
237
- // Execute external tool
238
- yield* this.handleExec(step.payload, options);
239
- break;
240
- case 'request_sample_token':
241
- yield* this.handleSampleToken(step.payload, options);
242
- break;
243
- case 'request_llm':
244
- yield* this.handleLlm(step.payload, options);
245
- break;
246
- case 'wait_input':
247
- // Request user input
248
- yield { type: 'input_required', prompt: step.prompt };
249
- const input = await options.onInputRequired(step.prompt ?? '');
250
- this.provideInput(input);
251
- break;
252
- case 'finished':
253
- // Include trace data if trace mode was enabled
254
- if (this.options.trace && step.payload) {
255
- const traceData = toJsValue(step.payload.trace);
256
- yield { type: 'finished', trace: Array.isArray(traceData) ? traceData : undefined };
257
- }
258
- else {
259
- yield { type: 'finished' };
260
- }
261
- return;
262
- case 'error':
263
- if (this.options.trace && step.payload) {
264
- const traceData = toJsValue(step.payload.trace);
265
- yield { type: 'error', content: step.content, trace: Array.isArray(traceData) ? traceData : undefined };
266
- }
267
- else {
268
- yield { type: 'error', content: step.content };
269
- }
270
- return;
271
- default:
272
- yield { type: 'error', content: `Unknown step status: ${step.status}` };
273
- return;
274
- }
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;
275
208
  }
209
+ yield* this.stepLoop(frame, options);
276
210
  }
277
211
  catch (error) {
278
212
  const message = error instanceof Error ? error.message : String(error);
@@ -283,6 +217,164 @@ export class DMLRunner {
283
217
  this.cleanup();
284
218
  }
285
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
+ }
286
378
  /**
287
379
  * Parse DML code into Prolog clauses
288
380
  */
@@ -375,10 +467,15 @@ export class DMLRunner {
375
467
  createEngine(memoryId, args, params) {
376
468
  try {
377
469
  const initialMemory = this.serializeMemoryMessages(this.currentMemory);
378
- const result = this.query(`deepclause_mi:create_engine('${this.sessionId}', '${memoryId}', ${args}, ${params}, [${initialMemory}], Engine)`);
379
- if (result && result.Engine) {
380
- this.engine = result.Engine;
381
- 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 };
382
479
  }
383
480
  return { error: 'Failed to create engine' };
384
481
  }
@@ -388,11 +485,93 @@ export class DMLRunner {
388
485
  }
389
486
  }
390
487
  /**
391
- * 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.
392
498
  */
393
- 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) {
394
573
  try {
395
- 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)`);
396
575
  // Convert swipl-wasm values to plain JS
397
576
  const status = toJsValue(result?.Status);
398
577
  const content = toJsValue(result?.Content);
@@ -413,13 +592,15 @@ export class DMLRunner {
413
592
  * Handle agent loop request (task() predicate)
414
593
  */
415
594
  async *handleAgentLoop(payload, _memoryId, // No longer used - memory comes from payload
416
- options) {
595
+ options, _frame) {
417
596
  const rawPayload = payload;
418
597
  const taskDescription = String(toJsValue(rawPayload.taskDescription) ?? '');
419
598
  const stepId = `step_${this.stepCounter++}`;
420
599
  yield { type: 'task_activity', taskState: 'started', taskDescription, taskId: stepId };
421
600
  let stepSucceeded = null;
422
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.
423
604
  // Ensure outputVars is an array of either strings or TypedVar objects
424
605
  const outputVars = decodeAgentOutputVars(rawPayload.outputVars);
425
606
  // Parse userTools - now contains schema info as array of tool_info dicts
@@ -497,6 +678,9 @@ export class DMLRunner {
497
678
  try {
498
679
  emitToolEvent({ type: 'input_required', prompt });
499
680
  const response = await options.onInputRequired(prompt);
681
+ if (response === undefined || response === null) {
682
+ return { cancelled: true };
683
+ }
500
684
  return { user_response: response };
501
685
  }
502
686
  catch (err) {
@@ -509,15 +693,15 @@ export class DMLRunner {
509
693
  const emitToolCall = (toolName, args) => {
510
694
  emitToolEvent({ type: 'tool_call', toolName, toolArgs: args });
511
695
  };
512
- // Build available tools for agent - SIMPLIFIED: tools run in isolated engines
513
- const availableTools = this.buildAgentTools(userTools, options.toolPolicy,
514
- // executeToolIsolated callback - runs tool in separate engine (no state sharing)
515
- async (toolName, args) => {
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;
699
+ const availableTools = this.buildAgentTools(userTools, options.toolPolicy, async (toolName, args) => {
516
700
  return this.executeToolIsolated(toolName, args, augmentedTools, onToolOutput, {
517
701
  workspacePath: options.workspacePath,
518
702
  signal: options.signal,
519
- compaction: options.compaction,
520
703
  toolPolicy: options.toolPolicy,
704
+ onModelCall: options.onModelCall,
521
705
  }, [], emitToolCall, emitToolEvent);
522
706
  });
523
707
  // Run agent loop with streaming support
@@ -527,6 +711,7 @@ export class DMLRunner {
527
711
  memory: effectiveMemory,
528
712
  tools: availableTools,
529
713
  workspacePath: options.workspacePath,
714
+ resolveMedia: (refs) => this.readMediaRefs(options.workspacePath, refs),
530
715
  modelOptions: {
531
716
  model: this.options.model,
532
717
  provider: this.options.provider,
@@ -545,6 +730,13 @@ export class DMLRunner {
545
730
  streamResolve = null;
546
731
  }
547
732
  },
733
+ onReasoning: (chunk, done) => {
734
+ streamQueue.push({ type: 'reasoning', content: chunk, done });
735
+ if (streamResolve) {
736
+ streamResolve();
737
+ streamResolve = null;
738
+ }
739
+ },
548
740
  onToolCall: (toolName, args) => {
549
741
  streamQueue.push({ type: 'tool_call', toolName, toolArgs: args });
550
742
  if (streamResolve) {
@@ -559,23 +751,29 @@ export class DMLRunner {
559
751
  streamResolve = null;
560
752
  }
561
753
  },
562
- onBeforeModelCall: async (messages, lastInputTokens) => this.applyCompactionBindings({
563
- messages,
564
- lastInputTokens,
565
- scope: 'loop',
566
- trigger: 'before_model_call',
567
- options,
568
- executionContext: {
569
- workspacePath: options.workspacePath,
570
- signal: options.signal,
571
- compaction: options.compaction,
572
- },
573
- registeredTools: options.tools,
574
- toolPolicy: options.toolPolicy,
575
- emitEvent: emitRunnerEvent,
576
- }),
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
+ },
577
773
  onAskUser: options.onInputRequired,
774
+ onModelCall: options.onModelCall,
578
775
  signal: options.signal,
776
+ llmBackend: this.options.llmBackend,
579
777
  });
580
778
  // Yield streaming events as they arrive
581
779
  if (this.options.streaming) {
@@ -683,6 +881,7 @@ export class DMLRunner {
683
881
  providerOptions: this.options.providerOptions,
684
882
  },
685
883
  signal: options.signal,
884
+ llmBackend: this.options.llmBackend,
686
885
  });
687
886
  succeeded = true;
688
887
  this.postSampleTokenResult({ success: true, token });
@@ -721,6 +920,8 @@ export class DMLRunner {
721
920
  providerOptions: this.options.providerOptions,
722
921
  },
723
922
  signal: options.signal,
923
+ llmBackend: this.options.llmBackend,
924
+ resolveMedia: (refs) => this.readMediaRefs(options.workspacePath, refs),
724
925
  });
725
926
  succeeded = true;
726
927
  this.postLlmResult({ success: true, text });
@@ -737,20 +938,132 @@ export class DMLRunner {
737
938
  yield { type: 'task_activity', taskState: succeeded ? 'completed' : 'failed', taskId: stepId };
738
939
  }
739
940
  }
941
+ /**
942
+ * Handle a semantic judgment request (judge/2 and the one-off predicates).
943
+ */
944
+ async *handleJudgment(payload, options) {
945
+ const parsed = this.parseJudgmentPayload(payload);
946
+ const ids = parsed.questions.map((question) => question.id);
947
+ const backendName = parsed.requestedName === 'default'
948
+ ? options.judgeBackend ?? this.options.judgeSelection?.defaultName ?? 'llm'
949
+ : parsed.requestedName;
950
+ const stepId = `judge_${this.stepCounter++}`;
951
+ yield {
952
+ type: 'task_activity',
953
+ taskState: 'started',
954
+ taskDescription: `judge[${backendName}]: ${ids.join(', ')}`,
955
+ taskId: stepId,
956
+ judgeBackend: backendName,
957
+ judgeQuestionIds: ids,
958
+ judgeState: 'started',
959
+ };
960
+ const result = await this.completeJudgment(parsed, { signal: options.signal, judgeBackend: options.judgeBackend });
961
+ if (result.error) {
962
+ this.postJudgmentResult({ success: false, error: result.error });
963
+ yield { type: 'log', content: `judgment failed: ${result.error}` };
964
+ }
965
+ else {
966
+ this.postJudgmentResult({ success: true, values: result.values ?? [] });
967
+ if (result.usage) {
968
+ yield {
969
+ type: 'usage',
970
+ usage: {
971
+ inputTokens: result.usage.inputTokens,
972
+ outputTokens: result.usage.outputTokens,
973
+ totalTokens: result.usage.totalTokens ?? result.usage.inputTokens + result.usage.outputTokens,
974
+ },
975
+ usageSource: 'judge',
976
+ usageModel: result.usageModel,
977
+ };
978
+ }
979
+ }
980
+ yield {
981
+ type: 'task_activity',
982
+ taskState: result.error ? 'failed' : 'completed',
983
+ taskId: stepId,
984
+ judgeBackend: result.backendName,
985
+ judgeQuestionIds: result.ids,
986
+ judgeState: result.error ? 'failed' : 'completed',
987
+ };
988
+ }
989
+ parseJudgmentPayload(payload) {
990
+ const data = (payload ?? {});
991
+ return {
992
+ questions: parseJudgeQuestions(data.questions),
993
+ state: normalizeJudgeState(data.state),
994
+ requiredCapabilities: Array.isArray(data.requires) ? data.requires.map(String) : [],
995
+ requestedName: typeof data.backend === 'string' ? data.backend : 'default',
996
+ };
997
+ }
998
+ async completeJudgment(parsed, context) {
999
+ const selection = this.options.judgeSelection;
1000
+ const defaultName = context.judgeBackend ?? selection?.defaultName ?? 'llm';
1001
+ const backendName = parsed.requestedName === 'default' ? defaultName : parsed.requestedName;
1002
+ const ids = parsed.questions.map((question) => question.id);
1003
+ try {
1004
+ const backend = selection?.backends.get(backendName);
1005
+ if (!backend)
1006
+ throw new Error(`Unknown judgment backend: ${backendName}`);
1007
+ const missing = parsed.requiredCapabilities.filter((capability) => !backend.capabilities[capability]);
1008
+ if (missing.length > 0) {
1009
+ throw new Error(`Judgment backend '${backendName}' lacks required capabilities: ${missing.join(', ')}`);
1010
+ }
1011
+ const model = this.options.model;
1012
+ const key = judgeCacheKey(backend.id, model, parsed.state, parsed.questions);
1013
+ const cached = this.judgeCache.get(key);
1014
+ if (cached) {
1015
+ return { backendName, ids, values: judgeAnswersToValues(parsed.questions, cached) };
1016
+ }
1017
+ const response = await backend.complete({
1018
+ state: parsed.state,
1019
+ questions: parsed.questions,
1020
+ signal: context.signal,
1021
+ model,
1022
+ requiredCapabilities: parsed.requiredCapabilities,
1023
+ });
1024
+ const answers = validateJudgeAnswers(parsed.questions, response.answers);
1025
+ this.judgeCache.set(key, answers);
1026
+ return {
1027
+ backendName,
1028
+ ids,
1029
+ values: judgeAnswersToValues(parsed.questions, answers),
1030
+ usage: response.usage,
1031
+ usageModel: response.model ?? model,
1032
+ };
1033
+ }
1034
+ catch (error) {
1035
+ return {
1036
+ backendName,
1037
+ ids,
1038
+ error: error instanceof Error ? error.message : String(error),
1039
+ };
1040
+ }
1041
+ }
1042
+ postJudgmentResult(result) {
1043
+ if (result.success) {
1044
+ this.query(`deepclause_mi:post_judgment_result('${this.sessionId}', success, ${valuesToPrologList(result.values ?? [])})`);
1045
+ }
1046
+ else {
1047
+ this.query(`deepclause_mi:post_judgment_result('${this.sessionId}', failure, ${this.toPrologTerm(result.error ?? 'Unknown error')})`);
1048
+ }
1049
+ }
740
1050
  /**
741
1051
  * Handle exec request (exec() predicate)
742
1052
  */
743
1053
  async *handleExec(payload, options) {
744
1054
  const { toolName, args } = payload;
745
- // Check tool policy
1055
+ // Check tool policy (ask_user is the human channel and is exempt).
746
1056
  const policyCheck = checkToolPolicy(toolName, options.toolPolicy);
747
1057
  if (!policyCheck.allowed) {
748
1058
  yield { type: 'log', content: `Tool '${toolName}' blocked by policy: ${policyCheck.reason}` };
749
1059
  this.postExecResult({ success: false, error: policyCheck.reason });
750
1060
  return;
751
1061
  }
752
- // Get tool definition
753
- 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);
754
1067
  if (!tool) {
755
1068
  yield { type: 'log', content: `Tool '${toolName}' not found` };
756
1069
  this.postExecResult({ success: false, error: `Tool not found: ${toolName}` });
@@ -762,7 +1075,7 @@ export class DMLRunner {
762
1075
  argsObj = this.argsToObject(args, tool);
763
1076
  // Emit tool_call event before execution
764
1077
  yield buildToolStartEvent(toolName, argsObj);
765
- const result = await tool.execute(argsObj);
1078
+ const result = await tool.execute(argsObj, { signal: options.signal });
766
1079
  yield buildToolCompletionEvent(toolName, argsObj, result);
767
1080
  // Post result back to Prolog
768
1081
  this.postExecResult({ success: true, result });
@@ -934,16 +1247,14 @@ export class DMLRunner {
934
1247
  * Only includes tools defined in DML - registered TypeScript tools are NOT exposed to the LLM.
935
1248
  * DML tools can internally call exec/2 to use registered tools.
936
1249
  */
937
- buildAgentTools(userTools, policy, executeToolInline) {
1250
+ buildAgentTools(userTools, _policy, executeToolInline) {
938
1251
  const result = new Map();
939
1252
  // Add user-defined tools from DML file with schema info
940
1253
  // These are the ONLY tools the LLM can call during task()
1254
+ // Do not apply the host runtime-tool policy to these DML predicates. Their
1255
+ // nested exec/2 calls pass through handleExec/executeToolIsolated, where
1256
+ // the policy still restricts actual host capabilities.
941
1257
  for (const tool of userTools) {
942
- // Check policy for DML tools too
943
- const check = checkToolPolicy(tool.name, policy);
944
- if (!check.allowed) {
945
- continue;
946
- }
947
1258
  // Build JSON Schema properties from inputs
948
1259
  const properties = {};
949
1260
  const required = [];
@@ -1057,7 +1368,7 @@ export class DMLRunner {
1057
1368
  try {
1058
1369
  argsObj = this.argsToObject(execArgs, tool);
1059
1370
  onToolEvent?.(buildToolStartEvent(execToolName, argsObj));
1060
- const execResult = await tool.execute(argsObj);
1371
+ const execResult = await tool.execute(argsObj, { signal: runContext.signal });
1061
1372
  onToolEvent?.(buildToolCompletionEvent(execToolName, argsObj, execResult));
1062
1373
  const postGoal = `deepclause_mi:post_exec_result('${this.sessionId}', success, ${this.toPrologTerm(execResult)})`;
1063
1374
  this.query(postGoal);
@@ -1093,6 +1404,7 @@ export class DMLRunner {
1093
1404
  providerOptions: this.options.providerOptions,
1094
1405
  },
1095
1406
  signal: runContext.signal,
1407
+ llmBackend: this.options.llmBackend,
1096
1408
  });
1097
1409
  this.postSampleTokenResult({ success: true, token });
1098
1410
  if (usage) {
@@ -1126,6 +1438,8 @@ export class DMLRunner {
1126
1438
  providerOptions: this.options.providerOptions,
1127
1439
  },
1128
1440
  signal: runContext.signal,
1441
+ llmBackend: this.options.llmBackend,
1442
+ resolveMedia: (refs) => this.readMediaRefs(runContext.workspacePath, refs),
1129
1443
  });
1130
1444
  this.postLlmResult({ success: true, text });
1131
1445
  if (usage) {
@@ -1143,6 +1457,49 @@ export class DMLRunner {
1143
1457
  }
1144
1458
  break;
1145
1459
  }
1460
+ case 'request_judgment': {
1461
+ const parsed = this.parseJudgmentPayload(payload);
1462
+ const nestedStepId = `judge_${this.stepCounter++}`;
1463
+ const nestedIds = parsed.questions.map((question) => question.id);
1464
+ onToolEvent?.({
1465
+ type: 'task_activity',
1466
+ taskState: 'started',
1467
+ taskDescription: `judge[${parsed.requestedName}]: ${nestedIds.join(', ')}`,
1468
+ taskId: nestedStepId,
1469
+ judgeBackend: parsed.requestedName,
1470
+ judgeQuestionIds: nestedIds,
1471
+ judgeState: 'started',
1472
+ });
1473
+ const judgeResult = await this.completeJudgment(parsed, { signal: runContext.signal, judgeBackend: undefined });
1474
+ if (judgeResult.error) {
1475
+ this.postJudgmentResult({ success: false, error: judgeResult.error });
1476
+ }
1477
+ else {
1478
+ this.postJudgmentResult({ success: true, values: judgeResult.values ?? [] });
1479
+ if (judgeResult.usage) {
1480
+ onToolEvent?.({
1481
+ type: 'usage',
1482
+ usage: {
1483
+ inputTokens: judgeResult.usage.inputTokens,
1484
+ outputTokens: judgeResult.usage.outputTokens,
1485
+ totalTokens: judgeResult.usage.totalTokens ??
1486
+ judgeResult.usage.inputTokens + judgeResult.usage.outputTokens,
1487
+ },
1488
+ usageSource: 'judge',
1489
+ usageModel: judgeResult.usageModel,
1490
+ });
1491
+ }
1492
+ }
1493
+ onToolEvent?.({
1494
+ type: 'task_activity',
1495
+ taskState: judgeResult.error ? 'failed' : 'completed',
1496
+ taskId: nestedStepId,
1497
+ judgeBackend: judgeResult.backendName,
1498
+ judgeQuestionIds: judgeResult.ids,
1499
+ judgeState: judgeResult.error ? 'failed' : 'completed',
1500
+ });
1501
+ break;
1502
+ }
1146
1503
  case 'request_agent_loop': {
1147
1504
  // Tool is calling task() or prompt() - run nested agent loop
1148
1505
  const taskDescription = String(payload?.taskDescription ?? '');
@@ -1275,6 +1632,8 @@ export class DMLRunner {
1275
1632
  },
1276
1633
  streaming: this.options.streaming,
1277
1634
  debug: this.options.debug,
1635
+ llmBackend: this.options.llmBackend,
1636
+ resolveMedia: (refs) => this.readMediaRefs(runContext.workspacePath, refs),
1278
1637
  onOutput: (text) => {
1279
1638
  outputs.push(text);
1280
1639
  onOutput(text);
@@ -1282,26 +1641,26 @@ export class DMLRunner {
1282
1641
  onStream: (chunk, done) => {
1283
1642
  onToolEvent?.({ type: 'stream', content: chunk, done });
1284
1643
  },
1644
+ onReasoning: (chunk, done) => {
1645
+ onToolEvent?.({ type: 'reasoning', content: chunk, done });
1646
+ },
1285
1647
  onUsage: (usage) => {
1286
1648
  onToolEvent?.({ type: 'usage', usage });
1287
1649
  },
1650
+ onModelCallPhase: (phase) => {
1651
+ onToolEvent?.({ type: 'model_call', modelCallState: phase, modelId: this.options.model });
1652
+ },
1288
1653
  onToolCall: onToolCall ?? (() => { }), // Forward tool call events to parent
1289
- onBeforeModelCall: async (messages, lastInputTokens) => this.applyCompactionBindings({
1290
- messages,
1291
- lastInputTokens,
1292
- scope: 'loop',
1293
- trigger: 'before_model_call',
1294
- options: { compaction: runContext.compaction },
1295
- executionContext: runContext,
1296
- registeredTools,
1297
- toolPolicy: runContext.toolPolicy ?? null,
1298
- emitEvent: onToolEvent,
1299
- }),
1654
+ onBeforeModelCall: async (messages, lastInputTokens) => {
1655
+ const hostCompacted = await this.applyHostCompaction(messages, lastInputTokens, onToolEvent);
1656
+ return hostCompacted ?? messages;
1657
+ },
1300
1658
  onAskUser: async () => {
1301
1659
  // Nested agent loops don't support ask_user directly
1302
1660
  // If needed, the tool should use exec(ask_user, ...) instead
1303
1661
  return '';
1304
1662
  },
1663
+ onModelCall: runContext.onModelCall,
1305
1664
  signal: runContext.signal,
1306
1665
  });
1307
1666
  if (process.env.DEBUG_RUNNER) {
@@ -1363,116 +1722,17 @@ export class DMLRunner {
1363
1722
  }
1364
1723
  }
1365
1724
  }
1366
- async applyCompactionBindings(params) {
1367
- const resolved = resolveCompactionOptions(this.options.compaction, params.options.compaction);
1368
- const bindings = getCompactionBindings(resolved, params.scope, params.trigger);
1369
- if (bindings.length === 0) {
1370
- return params.messages;
1371
- }
1372
- let messages = params.messages;
1373
- for (const binding of bindings) {
1374
- const result = await executeCompactor({
1375
- binding,
1376
- messages,
1377
- knownInputTokens: params.lastInputTokens,
1378
- maxContextTokens: this.options.contextWindow,
1379
- emitEvent: params.emitEvent,
1380
- execute: (request) => this.runBoundCompactor({
1381
- request,
1382
- executionContext: params.executionContext,
1383
- registeredTools: params.registeredTools,
1384
- toolPolicy: params.toolPolicy,
1385
- }),
1386
- });
1387
- params.emitEvent?.(result.event);
1388
- if (result.usageByModel) {
1389
- for (const totals of Object.values(result.usageByModel)) {
1390
- params.emitEvent?.({ type: 'usage', usage: {
1391
- inputTokens: totals.inputTokens,
1392
- outputTokens: totals.outputTokens,
1393
- totalTokens: totals.totalTokens,
1394
- cacheReadTokens: totals.cacheReadTokens || undefined,
1395
- cacheWriteTokens: totals.cacheWriteTokens || undefined,
1396
- reasoningTokens: totals.reasoningTokens || undefined,
1397
- } });
1398
- }
1399
- }
1400
- messages = result.messages;
1401
- }
1402
- return messages;
1403
- }
1404
- async runBoundCompactor(params) {
1405
- const { binding, params: compactorParams } = params.request;
1406
- const code = await this.resolveCompactorSource(binding.compactor.source, binding.compactor.sourceType, params.executionContext.workspacePath);
1407
- const model = binding.compactor.model ?? this.options.model;
1408
- const provider = binding.compactor.provider
1409
- ?? (binding.compactor.model ? detectProvider(model) : this.options.provider);
1410
- const compactorRunner = new DMLRunner(this.swipl, {
1411
- ...this.options,
1412
- model,
1413
- provider,
1414
- streaming: false,
1415
- compaction: { enabled: false },
1416
- });
1417
- const { signal, dispose } = createChildAbortController(params.executionContext.signal, binding.compactor.timeoutMs);
1418
- try {
1419
- const tools = binding.compactor.inheritTools ? params.registeredTools : new Map();
1420
- let answer = '';
1421
- let errorContent = '';
1422
- const usageByModel = {};
1423
- for await (const event of compactorRunner.run(code, {
1424
- workspacePath: params.executionContext.workspacePath,
1425
- gasLimit: binding.compactor.gasLimit,
1426
- params: compactorParams,
1427
- initialMessages: params.request.messages,
1428
- tools,
1429
- toolPolicy: binding.compactor.toolPolicy ?? (binding.compactor.inheritTools ? params.toolPolicy : null) ?? null,
1430
- onInputRequired: async (prompt) => {
1431
- throw new Error(`Compactor requested unexpected input: ${prompt}`);
1432
- },
1433
- signal,
1434
- compaction: { enabled: false },
1435
- })) {
1436
- if (event.type === 'answer' && event.content) {
1437
- answer = event.content;
1438
- }
1439
- else if (event.type === 'error' && event.content) {
1440
- errorContent = event.content;
1441
- }
1442
- else if (event.type === 'usage' && event.usage) {
1443
- recordTokenUsage(usageByModel, this.options.model, event.usage);
1444
- }
1445
- }
1446
- return {
1447
- answer,
1448
- error: errorContent || undefined,
1449
- usageByModel: Object.keys(usageByModel).length > 0 ? usageByModel : undefined,
1450
- };
1451
- }
1452
- finally {
1453
- dispose();
1454
- await compactorRunner.dispose();
1455
- }
1456
- }
1457
- async resolveCompactorSource(source, sourceType, workspacePath) {
1458
- if (sourceType === 'inline') {
1459
- return source;
1725
+ async applyHostCompaction(messages, lastInputTokens, emit) {
1726
+ const host = this.options.compactMessages;
1727
+ if (!host) {
1728
+ return null;
1460
1729
  }
1461
- const basePath = workspacePath ?? process.cwd();
1462
- const candidatePath = path.isAbsolute(source) ? source : path.resolve(basePath, source);
1463
- try {
1464
- return await fs.readFile(candidatePath, 'utf8');
1465
- }
1466
- catch (error) {
1467
- if (sourceType === 'file') {
1468
- throw error;
1469
- }
1470
- return source;
1730
+ const result = await host(messages, lastInputTokens);
1731
+ if (result.event) {
1732
+ emit?.(result.event);
1471
1733
  }
1734
+ return result.messages;
1472
1735
  }
1473
- /**
1474
- * Extract memory from payload (now passed via state threading)
1475
- */
1476
1736
  extractMessagesFromValue(rawMessages) {
1477
1737
  const normalized = toJsValue(rawMessages);
1478
1738
  if (!Array.isArray(normalized)) {
@@ -1482,19 +1742,46 @@ export class DMLRunner {
1482
1742
  const msg = toJsValue(messageValue);
1483
1743
  const role = String(toJsValue(msg.role) ?? 'user');
1484
1744
  const content = String(toJsValue(msg.content) ?? '');
1745
+ const media = this.extractMediaRefs(msg.media);
1485
1746
  if (role !== 'system' && role !== 'user' && role !== 'assistant') {
1486
- return { role: 'user', content };
1747
+ return { role: 'user', content, media: media.length ? media : undefined };
1487
1748
  }
1488
- return { role: role, content };
1749
+ return { role: role, content, media: media.length ? media : undefined };
1489
1750
  });
1490
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
+ }
1491
1771
  extractMemoryFromPayload(rawPayload) {
1492
1772
  const rawMemory = toJsValue(rawPayload.memory);
1493
1773
  return this.extractMessagesFromValue(rawMemory);
1494
1774
  }
1495
1775
  serializeMemoryMessages(messages) {
1496
1776
  return messages
1497
- .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
+ })
1498
1785
  .join(', ');
1499
1786
  }
1500
1787
  /**
@@ -1562,6 +1849,30 @@ export class DMLRunner {
1562
1849
  const fullMemoryStr = this.serializeMemoryMessages(fullMemory ?? []);
1563
1850
  this.query(`deepclause_mi:post_tool_agent_result('${this.sessionId}', '${toolEngineId}', ${result.success}, vars{${varsStr}}, [${messagesStr}], [${fullMemoryStr}])`);
1564
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
+ }
1565
1876
  /**
1566
1877
  * Provide user input to waiting Prolog
1567
1878
  */
@@ -1569,6 +1880,10 @@ export class DMLRunner {
1569
1880
  const escaped = input.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
1570
1881
  this.query(`deepclause_mi:provide_input('${this.sessionId}', "${escaped}")`);
1571
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
+ }
1572
1887
  /**
1573
1888
  * Execute a Prolog query
1574
1889
  */
@@ -1587,14 +1902,17 @@ export class DMLRunner {
1587
1902
  * Cleanup resources
1588
1903
  */
1589
1904
  cleanup() {
1590
- if (this.engine) {
1905
+ if (this.frames.size > 0) {
1591
1906
  try {
1907
+ // destroy_engine/1 ends every frame registered for the session and
1908
+ // retracts the session's dynamic state.
1592
1909
  this.query(`deepclause_mi:destroy_engine('${this.sessionId}')`);
1593
1910
  }
1594
1911
  catch {
1595
1912
  // Ignore cleanup errors
1596
1913
  }
1597
- this.engine = null;
1914
+ this.frames.clear();
1915
+ this.defaultFrameId = null;
1598
1916
  }
1599
1917
  }
1600
1918
  /**