@bolt-foundry/gambit-core 0.8.1 → 0.8.5-rc.3

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 (262) hide show
  1. package/README.md +60 -34
  2. package/cards/context.card.md +5 -5
  3. package/cards/generate-test-input.card.md +12 -0
  4. package/{script/deps/jsr.io/@std/collections/1.1.4 → esm/deps/jsr.io/@std/collections/1.1.5}/deep_merge.d.ts +2 -2
  5. package/esm/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.d.ts.map +1 -1
  6. package/esm/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.js +29 -19
  7. package/esm/deps/jsr.io/@std/toml/1.0.11/_parser.js +1 -1
  8. package/esm/mod.d.ts +20 -10
  9. package/esm/mod.d.ts.map +1 -1
  10. package/esm/mod.js +11 -5
  11. package/esm/schemas/graders/contexts/conversation.d.ts +22 -0
  12. package/esm/schemas/graders/contexts/conversation.d.ts.map +1 -0
  13. package/esm/schemas/graders/contexts/conversation.js +17 -0
  14. package/esm/schemas/graders/contexts/conversation.zod.d.ts +3 -0
  15. package/esm/schemas/graders/contexts/conversation.zod.d.ts.map +1 -0
  16. package/esm/schemas/graders/contexts/conversation.zod.js +2 -0
  17. package/esm/schemas/graders/contexts/conversation_tools.d.ts +31 -0
  18. package/esm/schemas/graders/contexts/conversation_tools.d.ts.map +1 -0
  19. package/esm/schemas/graders/contexts/conversation_tools.js +25 -0
  20. package/esm/schemas/graders/contexts/conversation_tools.zod.d.ts +3 -0
  21. package/esm/schemas/graders/contexts/conversation_tools.zod.d.ts.map +1 -0
  22. package/esm/schemas/graders/contexts/conversation_tools.zod.js +2 -0
  23. package/esm/schemas/graders/contexts/tools.d.ts +4 -0
  24. package/esm/schemas/graders/contexts/tools.d.ts.map +1 -0
  25. package/esm/schemas/graders/contexts/tools.js +3 -0
  26. package/esm/schemas/graders/contexts/tools.zod.d.ts +3 -0
  27. package/esm/schemas/graders/contexts/tools.zod.d.ts.map +1 -0
  28. package/esm/schemas/graders/contexts/tools.zod.js +2 -0
  29. package/esm/schemas/graders/contexts/turn.d.ts +10 -0
  30. package/esm/schemas/graders/contexts/turn.d.ts.map +1 -0
  31. package/esm/schemas/graders/contexts/turn.js +8 -0
  32. package/esm/schemas/graders/contexts/turn.zod.d.ts +3 -0
  33. package/esm/schemas/graders/contexts/turn.zod.d.ts.map +1 -0
  34. package/esm/schemas/graders/contexts/turn.zod.js +2 -0
  35. package/esm/schemas/graders/contexts/turn_tools.d.ts +32 -0
  36. package/esm/schemas/graders/contexts/turn_tools.d.ts.map +1 -0
  37. package/esm/schemas/graders/contexts/turn_tools.js +28 -0
  38. package/esm/schemas/graders/contexts/turn_tools.zod.d.ts +3 -0
  39. package/esm/schemas/graders/contexts/turn_tools.zod.d.ts.map +1 -0
  40. package/esm/schemas/graders/contexts/turn_tools.zod.js +2 -0
  41. package/esm/schemas/graders/grader_output.d.ts +10 -0
  42. package/esm/schemas/graders/grader_output.d.ts.map +1 -0
  43. package/esm/schemas/graders/grader_output.js +8 -0
  44. package/esm/schemas/graders/grader_output.zod.d.ts +3 -0
  45. package/esm/schemas/graders/grader_output.zod.d.ts.map +1 -0
  46. package/esm/schemas/graders/grader_output.zod.js +2 -0
  47. package/esm/schemas/graders/respond.d.ts +12 -0
  48. package/esm/schemas/graders/respond.d.ts.map +1 -0
  49. package/esm/schemas/graders/respond.js +10 -0
  50. package/esm/schemas/graders/respond.zod.d.ts +3 -0
  51. package/esm/schemas/graders/respond.zod.d.ts.map +1 -0
  52. package/esm/schemas/graders/respond.zod.js +2 -0
  53. package/esm/schemas/scenarios/plain_chat_input_optional.d.ts +5 -0
  54. package/esm/schemas/scenarios/plain_chat_input_optional.d.ts.map +1 -0
  55. package/esm/schemas/scenarios/plain_chat_input_optional.js +5 -0
  56. package/esm/schemas/scenarios/plain_chat_input_optional.zod.d.ts +3 -0
  57. package/esm/schemas/scenarios/plain_chat_input_optional.zod.d.ts.map +1 -0
  58. package/esm/schemas/scenarios/plain_chat_input_optional.zod.js +2 -0
  59. package/esm/schemas/scenarios/plain_chat_output.d.ts +5 -0
  60. package/esm/schemas/scenarios/plain_chat_output.d.ts.map +1 -0
  61. package/esm/schemas/scenarios/plain_chat_output.js +4 -0
  62. package/esm/schemas/scenarios/plain_chat_output.zod.d.ts +3 -0
  63. package/esm/schemas/scenarios/plain_chat_output.zod.d.ts.map +1 -0
  64. package/esm/schemas/scenarios/plain_chat_output.zod.js +2 -0
  65. package/esm/src/builtins.d.ts +2 -0
  66. package/esm/src/builtins.d.ts.map +1 -1
  67. package/esm/src/builtins.js +45 -1
  68. package/esm/src/constants.d.ts +4 -0
  69. package/esm/src/constants.d.ts.map +1 -1
  70. package/esm/src/constants.js +5 -0
  71. package/esm/src/definitions.d.ts +5 -1
  72. package/esm/src/definitions.d.ts.map +1 -1
  73. package/esm/src/loader.d.ts.map +1 -1
  74. package/esm/src/loader.js +119 -13
  75. package/esm/src/markdown.d.ts.map +1 -1
  76. package/esm/src/markdown.js +222 -53
  77. package/esm/src/permissions.d.ts +143 -0
  78. package/esm/src/permissions.d.ts.map +1 -0
  79. package/esm/src/permissions.js +406 -0
  80. package/esm/src/render.d.ts.map +1 -1
  81. package/esm/src/render.js +22 -8
  82. package/esm/src/runtime.d.ts +28 -2
  83. package/esm/src/runtime.d.ts.map +1 -1
  84. package/esm/src/runtime.js +3051 -100
  85. package/esm/src/runtime_exec_host.d.ts +6 -0
  86. package/esm/src/runtime_exec_host.d.ts.map +1 -0
  87. package/esm/src/runtime_exec_host.js +17 -0
  88. package/esm/src/runtime_exec_host_contract.d.ts +23 -0
  89. package/esm/src/runtime_exec_host_contract.d.ts.map +1 -0
  90. package/esm/src/runtime_exec_host_contract.js +14 -0
  91. package/esm/src/runtime_exec_host_deno.d.ts +3 -0
  92. package/esm/src/runtime_exec_host_deno.d.ts.map +1 -0
  93. package/esm/src/runtime_exec_host_deno.js +35 -0
  94. package/esm/src/runtime_exec_host_unsupported.d.ts +3 -0
  95. package/esm/src/runtime_exec_host_unsupported.d.ts.map +1 -0
  96. package/esm/src/runtime_exec_host_unsupported.js +8 -0
  97. package/esm/src/runtime_worker_host.d.ts +6 -0
  98. package/esm/src/runtime_worker_host.d.ts.map +1 -0
  99. package/esm/src/runtime_worker_host.js +17 -0
  100. package/esm/src/runtime_worker_host_contract.d.ts +33 -0
  101. package/esm/src/runtime_worker_host_contract.d.ts.map +1 -0
  102. package/esm/src/runtime_worker_host_contract.js +14 -0
  103. package/esm/src/runtime_worker_host_deno.d.ts +3 -0
  104. package/esm/src/runtime_worker_host_deno.d.ts.map +1 -0
  105. package/esm/src/runtime_worker_host_deno.js +26 -0
  106. package/esm/src/runtime_worker_host_unsupported.d.ts +3 -0
  107. package/esm/src/runtime_worker_host_unsupported.d.ts.map +1 -0
  108. package/esm/src/runtime_worker_host_unsupported.js +8 -0
  109. package/esm/src/state.d.ts +4 -1
  110. package/esm/src/state.d.ts.map +1 -1
  111. package/esm/src/state.js +48 -2
  112. package/esm/src/types.d.ts +381 -1
  113. package/esm/src/types.d.ts.map +1 -1
  114. package/esm/src/types.js +102 -1
  115. package/package.json +73 -2
  116. package/schemas/graders/contexts/conversation.ts +32 -9
  117. package/schemas/graders/contexts/conversation.zod.ts +1 -0
  118. package/schemas/graders/contexts/conversation_tools.ts +63 -0
  119. package/schemas/graders/contexts/conversation_tools.zod.ts +1 -0
  120. package/schemas/graders/contexts/tools.ts +5 -0
  121. package/schemas/graders/contexts/tools.zod.ts +1 -0
  122. package/schemas/graders/contexts/turn.ts +8 -1
  123. package/schemas/graders/contexts/turn.zod.ts +1 -0
  124. package/schemas/graders/contexts/turn_tools.ts +63 -0
  125. package/schemas/graders/contexts/turn_tools.zod.ts +1 -0
  126. package/schemas/graders/grader_output.ts +15 -0
  127. package/schemas/graders/grader_output.zod.ts +1 -0
  128. package/schemas/graders/respond.ts +13 -3
  129. package/schemas/graders/respond.zod.ts +1 -0
  130. package/schemas/scenarios/plain_chat_input_optional.ts +6 -0
  131. package/schemas/scenarios/plain_chat_input_optional.zod.ts +1 -0
  132. package/schemas/scenarios/plain_chat_output.ts +5 -0
  133. package/schemas/scenarios/plain_chat_output.zod.ts +1 -0
  134. package/{esm/deps/jsr.io/@std/collections/1.1.4 → script/deps/jsr.io/@std/collections/1.1.5}/deep_merge.d.ts +2 -2
  135. package/script/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.d.ts.map +1 -1
  136. package/script/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.js +29 -19
  137. package/script/deps/jsr.io/@std/toml/1.0.11/_parser.js +1 -1
  138. package/script/mod.d.ts +20 -10
  139. package/script/mod.d.ts.map +1 -1
  140. package/script/mod.js +25 -9
  141. package/script/schemas/graders/contexts/conversation.d.ts +22 -0
  142. package/script/schemas/graders/contexts/conversation.d.ts.map +1 -0
  143. package/script/schemas/graders/contexts/conversation.js +20 -0
  144. package/script/schemas/graders/contexts/conversation.zod.d.ts +3 -0
  145. package/script/schemas/graders/contexts/conversation.zod.d.ts.map +1 -0
  146. package/script/schemas/graders/contexts/conversation.zod.js +9 -0
  147. package/script/schemas/graders/contexts/conversation_tools.d.ts +31 -0
  148. package/script/schemas/graders/contexts/conversation_tools.d.ts.map +1 -0
  149. package/script/schemas/graders/contexts/conversation_tools.js +28 -0
  150. package/script/schemas/graders/contexts/conversation_tools.zod.d.ts +3 -0
  151. package/script/schemas/graders/contexts/conversation_tools.zod.d.ts.map +1 -0
  152. package/script/schemas/graders/contexts/conversation_tools.zod.js +9 -0
  153. package/script/schemas/graders/contexts/tools.d.ts +4 -0
  154. package/script/schemas/graders/contexts/tools.d.ts.map +1 -0
  155. package/script/schemas/graders/contexts/tools.js +12 -0
  156. package/script/schemas/graders/contexts/tools.zod.d.ts +3 -0
  157. package/script/schemas/graders/contexts/tools.zod.d.ts.map +1 -0
  158. package/script/schemas/graders/contexts/tools.zod.js +9 -0
  159. package/script/schemas/graders/contexts/turn.d.ts +10 -0
  160. package/script/schemas/graders/contexts/turn.d.ts.map +1 -0
  161. package/script/schemas/graders/contexts/turn.js +10 -0
  162. package/script/schemas/graders/contexts/turn.zod.d.ts +3 -0
  163. package/script/schemas/graders/contexts/turn.zod.d.ts.map +1 -0
  164. package/script/schemas/graders/contexts/turn.zod.js +9 -0
  165. package/script/schemas/graders/contexts/turn_tools.d.ts +32 -0
  166. package/script/schemas/graders/contexts/turn_tools.d.ts.map +1 -0
  167. package/script/schemas/graders/contexts/turn_tools.js +31 -0
  168. package/script/schemas/graders/contexts/turn_tools.zod.d.ts +3 -0
  169. package/script/schemas/graders/contexts/turn_tools.zod.d.ts.map +1 -0
  170. package/script/schemas/graders/contexts/turn_tools.zod.js +9 -0
  171. package/script/schemas/graders/grader_output.d.ts +10 -0
  172. package/script/schemas/graders/grader_output.d.ts.map +1 -0
  173. package/script/schemas/graders/grader_output.js +10 -0
  174. package/script/schemas/graders/grader_output.zod.d.ts +3 -0
  175. package/script/schemas/graders/grader_output.zod.d.ts.map +1 -0
  176. package/script/schemas/graders/grader_output.zod.js +9 -0
  177. package/script/schemas/graders/respond.d.ts +12 -0
  178. package/script/schemas/graders/respond.d.ts.map +1 -0
  179. package/script/schemas/graders/respond.js +12 -0
  180. package/script/schemas/graders/respond.zod.d.ts +3 -0
  181. package/script/schemas/graders/respond.zod.d.ts.map +1 -0
  182. package/script/schemas/graders/respond.zod.js +9 -0
  183. package/script/schemas/scenarios/plain_chat_input_optional.d.ts +5 -0
  184. package/script/schemas/scenarios/plain_chat_input_optional.d.ts.map +1 -0
  185. package/script/schemas/scenarios/plain_chat_input_optional.js +7 -0
  186. package/script/schemas/scenarios/plain_chat_input_optional.zod.d.ts +3 -0
  187. package/script/schemas/scenarios/plain_chat_input_optional.zod.d.ts.map +1 -0
  188. package/script/schemas/scenarios/plain_chat_input_optional.zod.js +9 -0
  189. package/script/schemas/scenarios/plain_chat_output.d.ts +5 -0
  190. package/script/schemas/scenarios/plain_chat_output.d.ts.map +1 -0
  191. package/script/schemas/scenarios/plain_chat_output.js +6 -0
  192. package/script/schemas/scenarios/plain_chat_output.zod.d.ts +3 -0
  193. package/script/schemas/scenarios/plain_chat_output.zod.d.ts.map +1 -0
  194. package/script/schemas/scenarios/plain_chat_output.zod.js +9 -0
  195. package/script/src/builtins.d.ts +2 -0
  196. package/script/src/builtins.d.ts.map +1 -1
  197. package/script/src/builtins.js +47 -1
  198. package/script/src/constants.d.ts +4 -0
  199. package/script/src/constants.d.ts.map +1 -1
  200. package/script/src/constants.js +6 -1
  201. package/script/src/definitions.d.ts +5 -1
  202. package/script/src/definitions.d.ts.map +1 -1
  203. package/script/src/loader.d.ts.map +1 -1
  204. package/script/src/loader.js +118 -12
  205. package/script/src/markdown.d.ts.map +1 -1
  206. package/script/src/markdown.js +221 -52
  207. package/script/src/permissions.d.ts +143 -0
  208. package/script/src/permissions.d.ts.map +1 -0
  209. package/script/src/permissions.js +453 -0
  210. package/script/src/render.d.ts.map +1 -1
  211. package/script/src/render.js +22 -8
  212. package/script/src/runtime.d.ts +28 -2
  213. package/script/src/runtime.d.ts.map +1 -1
  214. package/script/src/runtime.js +3053 -99
  215. package/script/src/runtime_exec_host.d.ts +6 -0
  216. package/script/src/runtime_exec_host.d.ts.map +1 -0
  217. package/script/src/runtime_exec_host.js +56 -0
  218. package/script/src/runtime_exec_host_contract.d.ts +23 -0
  219. package/script/src/runtime_exec_host_contract.d.ts.map +1 -0
  220. package/script/src/runtime_exec_host_contract.js +18 -0
  221. package/script/src/runtime_exec_host_deno.d.ts +3 -0
  222. package/script/src/runtime_exec_host_deno.d.ts.map +1 -0
  223. package/script/src/runtime_exec_host_deno.js +71 -0
  224. package/script/src/runtime_exec_host_unsupported.d.ts +3 -0
  225. package/script/src/runtime_exec_host_unsupported.d.ts.map +1 -0
  226. package/script/src/runtime_exec_host_unsupported.js +11 -0
  227. package/script/src/runtime_worker_host.d.ts +6 -0
  228. package/script/src/runtime_worker_host.d.ts.map +1 -0
  229. package/script/src/runtime_worker_host.js +56 -0
  230. package/script/src/runtime_worker_host_contract.d.ts +33 -0
  231. package/script/src/runtime_worker_host_contract.d.ts.map +1 -0
  232. package/script/src/runtime_worker_host_contract.js +18 -0
  233. package/script/src/runtime_worker_host_deno.d.ts +3 -0
  234. package/script/src/runtime_worker_host_deno.d.ts.map +1 -0
  235. package/script/src/runtime_worker_host_deno.js +62 -0
  236. package/script/src/runtime_worker_host_unsupported.d.ts +3 -0
  237. package/script/src/runtime_worker_host_unsupported.d.ts.map +1 -0
  238. package/script/src/runtime_worker_host_unsupported.js +11 -0
  239. package/script/src/state.d.ts +4 -1
  240. package/script/src/state.d.ts.map +1 -1
  241. package/script/src/state.js +48 -2
  242. package/script/src/types.d.ts +381 -1
  243. package/script/src/types.d.ts.map +1 -1
  244. package/script/src/types.js +103 -0
  245. package/esm/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts +0 -6
  246. package/esm/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts.map +0 -1
  247. package/esm/deps/jsr.io/@std/collections/1.1.4/_utils.js +0 -18
  248. package/esm/src/openai_compat.d.ts +0 -63
  249. package/esm/src/openai_compat.d.ts.map +0 -1
  250. package/esm/src/openai_compat.js +0 -272
  251. package/esm/src/providers/openrouter.d.ts +0 -8
  252. package/esm/src/providers/openrouter.d.ts.map +0 -1
  253. package/esm/src/providers/openrouter.js +0 -168
  254. package/script/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts +0 -6
  255. package/script/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts.map +0 -1
  256. package/script/deps/jsr.io/@std/collections/1.1.4/_utils.js +0 -21
  257. package/script/src/openai_compat.d.ts +0 -63
  258. package/script/src/openai_compat.d.ts.map +0 -1
  259. package/script/src/openai_compat.js +0 -276
  260. package/script/src/providers/openrouter.d.ts +0 -8
  261. package/script/src/providers/openrouter.d.ts.map +0 -1
  262. package/script/src/providers/openrouter.js +0 -207
@@ -33,11 +33,17 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.RunCanceledError = void 0;
36
37
  exports.isGambitEndSignal = isGambitEndSignal;
38
+ exports.isRunCanceledError = isRunCanceledError;
37
39
  exports.runDeck = runDeck;
40
+ const dntShim = __importStar(require("../_dnt.shims.js"));
38
41
  const path = __importStar(require("../deps/jsr.io/@std/path/1.1.4/mod.js"));
39
42
  const constants_js_1 = require("./constants.js");
40
43
  const loader_js_1 = require("./loader.js");
44
+ const permissions_js_1 = require("./permissions.js");
45
+ const runtime_exec_host_js_1 = require("./runtime_exec_host.js");
46
+ const runtime_worker_host_js_1 = require("./runtime_worker_host.js");
41
47
  const schema_js_1 = require("./schema.js");
42
48
  function isGambitEndSignal(value) {
43
49
  return Boolean(value &&
@@ -50,6 +56,301 @@ function randomId(prefix) {
50
56
  // Keep IDs short enough for OpenAI/OpenRouter tool_call id limits (~40 chars).
51
57
  return `${prefix}-${suffix}`;
52
58
  }
59
+ const WORKER_SANDBOX_ENV = "GAMBIT_DECK_WORKER_SANDBOX";
60
+ const WORKER_TIMEOUT_MESSAGE = "Timeout exceeded";
61
+ const RUN_CANCELED_MESSAGE = "Run canceled";
62
+ const WORKER_SANDBOX_SIGNAL_UNSUPPORTED_MESSAGE = "workerSandbox is unsupported when `signal` is provided.";
63
+ const INSPECT_WORKER_TIMEOUT_MS = 1_500;
64
+ const INSPECT_WORKER_TIMEOUT_MESSAGE = "Deck inspection timed out";
65
+ const BUILTIN_TOOL_READ_FILE = "read_file";
66
+ const BUILTIN_TOOL_LIST_DIR = "list_dir";
67
+ const BUILTIN_TOOL_GREP_FILES = "grep_files";
68
+ const BUILTIN_TOOL_APPLY_PATCH = "apply_patch";
69
+ const BUILTIN_TOOL_EXEC = "exec";
70
+ const BUILTIN_TOOL_NAMES = new Set([
71
+ BUILTIN_TOOL_READ_FILE,
72
+ BUILTIN_TOOL_LIST_DIR,
73
+ BUILTIN_TOOL_GREP_FILES,
74
+ BUILTIN_TOOL_APPLY_PATCH,
75
+ BUILTIN_TOOL_EXEC,
76
+ ]);
77
+ const TRUSTED_SCHEMA_IMPORT_PREFIXES = [
78
+ "@bolt-foundry/gambit-core/schemas",
79
+ "gambit://schemas",
80
+ ];
81
+ class RunCanceledError extends Error {
82
+ constructor(message = RUN_CANCELED_MESSAGE) {
83
+ super(message);
84
+ Object.defineProperty(this, "code", {
85
+ enumerable: true,
86
+ configurable: true,
87
+ writable: true,
88
+ value: "run_canceled"
89
+ });
90
+ this.name = "RunCanceledError";
91
+ }
92
+ }
93
+ exports.RunCanceledError = RunCanceledError;
94
+ class WorkerSandboxSignalUnsupportedError extends Error {
95
+ constructor(message = WORKER_SANDBOX_SIGNAL_UNSUPPORTED_MESSAGE) {
96
+ super(message);
97
+ Object.defineProperty(this, "code", {
98
+ enumerable: true,
99
+ configurable: true,
100
+ writable: true,
101
+ value: "worker_sandbox_signal_unsupported"
102
+ });
103
+ this.name = "WorkerSandboxSignalUnsupportedError";
104
+ }
105
+ }
106
+ function isRunCanceledError(err) {
107
+ if (!err || typeof err !== "object")
108
+ return false;
109
+ const name = err.name;
110
+ const code = err.code;
111
+ if (name === "RunCanceledError" || code === "run_canceled")
112
+ return true;
113
+ if (name === "AbortError")
114
+ return true;
115
+ return false;
116
+ }
117
+ function shouldUseWorkerSandbox() {
118
+ let raw;
119
+ try {
120
+ raw = dntShim.Deno.env.get(WORKER_SANDBOX_ENV);
121
+ }
122
+ catch {
123
+ return false;
124
+ }
125
+ raw = raw?.trim().toLowerCase();
126
+ return raw === "1" || raw === "true" || raw === "yes";
127
+ }
128
+ function normalizedScopeToWire(scope) {
129
+ if (scope.all)
130
+ return true;
131
+ if (scope.values.size === 0)
132
+ return false;
133
+ return Array.from(scope.values).sort();
134
+ }
135
+ function normalizedRunToWire(scope) {
136
+ if (scope.all)
137
+ return true;
138
+ if (scope.paths.size === 0 && scope.commands.size === 0)
139
+ return false;
140
+ return {
141
+ paths: Array.from(scope.paths).sort(),
142
+ commands: Array.from(scope.commands).sort(),
143
+ };
144
+ }
145
+ function toWirePermissionSet(set) {
146
+ return {
147
+ baseDir: set.baseDir,
148
+ read: normalizedScopeToWire(set.read),
149
+ write: normalizedScopeToWire(set.write),
150
+ run: normalizedRunToWire(set.run),
151
+ net: normalizedScopeToWire(set.net),
152
+ env: normalizedScopeToWire(set.env),
153
+ };
154
+ }
155
+ function wireScopeToNormalized(scope) {
156
+ if (scope === true)
157
+ return { all: true, values: new Set() };
158
+ if (scope === false)
159
+ return { all: false, values: new Set() };
160
+ return { all: false, values: new Set(scope) };
161
+ }
162
+ function wireRunToNormalized(scope) {
163
+ if (scope === true) {
164
+ return {
165
+ all: true,
166
+ paths: new Set(),
167
+ commands: new Set(),
168
+ };
169
+ }
170
+ if (scope === false) {
171
+ return {
172
+ all: false,
173
+ paths: new Set(),
174
+ commands: new Set(),
175
+ };
176
+ }
177
+ return {
178
+ all: false,
179
+ paths: new Set(scope.paths),
180
+ commands: new Set(scope.commands),
181
+ };
182
+ }
183
+ function fromWirePermissionSet(set) {
184
+ return {
185
+ baseDir: set.baseDir,
186
+ read: wireScopeToNormalized(set.read),
187
+ write: wireScopeToNormalized(set.write),
188
+ run: wireRunToNormalized(set.run),
189
+ net: wireScopeToNormalized(set.net),
190
+ env: wireScopeToNormalized(set.env),
191
+ };
192
+ }
193
+ function normalizePermissionBaseDir(set, baseDir) {
194
+ return {
195
+ ...set,
196
+ baseDir,
197
+ read: { all: set.read.all, values: new Set(set.read.values) },
198
+ write: { all: set.write.all, values: new Set(set.write.values) },
199
+ run: {
200
+ all: set.run.all,
201
+ paths: new Set(set.run.paths),
202
+ commands: new Set(set.run.commands),
203
+ },
204
+ net: { all: set.net.all, values: new Set(set.net.values) },
205
+ env: { all: set.env.all, values: new Set(set.env.values) },
206
+ };
207
+ }
208
+ function deadlineForRun(guardrails, existing) {
209
+ const timeoutDeadline = performance.now() + guardrails.timeoutMs;
210
+ if (typeof existing === "number" && Number.isFinite(existing)) {
211
+ return Math.min(existing, timeoutDeadline);
212
+ }
213
+ return timeoutDeadline;
214
+ }
215
+ function ensureNotExpired(deadlineMs) {
216
+ if (performance.now() > deadlineMs) {
217
+ throw new Error(WORKER_TIMEOUT_MESSAGE);
218
+ }
219
+ }
220
+ function throwIfCanceled(signal) {
221
+ if (!signal?.aborted)
222
+ return;
223
+ const reason = signal.reason;
224
+ if (typeof reason === "string" && reason.trim().length > 0) {
225
+ throw new RunCanceledError(reason);
226
+ }
227
+ if (reason instanceof Error && reason.message.trim().length > 0) {
228
+ throw new RunCanceledError(reason.message);
229
+ }
230
+ throw new RunCanceledError();
231
+ }
232
+ function ensureRunActive(deadlineMs, signal) {
233
+ throwIfCanceled(signal);
234
+ ensureNotExpired(deadlineMs);
235
+ }
236
+ function isTrustedSchemaImportKey(key) {
237
+ const normalized = key.trim();
238
+ if (!normalized)
239
+ return false;
240
+ return TRUSTED_SCHEMA_IMPORT_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`));
241
+ }
242
+ function tryReadWorkspaceConfigPath(deckPath) {
243
+ const startDir = path.dirname(path.resolve(deckPath));
244
+ let current = startDir;
245
+ while (true) {
246
+ const denoJson = path.join(current, "deno.json");
247
+ const denoJsonc = path.join(current, "deno.jsonc");
248
+ try {
249
+ if (dntShim.Deno.statSync(denoJson).isFile)
250
+ return denoJson;
251
+ }
252
+ catch {
253
+ // continue search
254
+ }
255
+ try {
256
+ if (dntShim.Deno.statSync(denoJsonc).isFile)
257
+ return denoJsonc;
258
+ }
259
+ catch {
260
+ // continue search
261
+ }
262
+ const parent = path.dirname(current);
263
+ if (parent === current)
264
+ break;
265
+ current = parent;
266
+ }
267
+ return undefined;
268
+ }
269
+ function readWorkspaceImportMapKeys(configPath) {
270
+ const text = dntShim.Deno.readTextFileSync(configPath);
271
+ const parsed = parseWorkspaceConfig(text);
272
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) ||
273
+ !parsed.imports || typeof parsed.imports !== "object" ||
274
+ Array.isArray(parsed.imports)) {
275
+ return [];
276
+ }
277
+ return Object.keys(parsed.imports);
278
+ }
279
+ function parseWorkspaceConfig(text) {
280
+ try {
281
+ return JSON.parse(text);
282
+ }
283
+ catch {
284
+ const stripped = stripJsonComments(text);
285
+ return JSON.parse(stripped);
286
+ }
287
+ }
288
+ function stripJsonComments(text) {
289
+ let out = "";
290
+ let inString = false;
291
+ let escapeNext = false;
292
+ let inLineComment = false;
293
+ let inBlockComment = false;
294
+ for (let i = 0; i < text.length; i++) {
295
+ const ch = text[i];
296
+ const next = text[i + 1];
297
+ if (inLineComment) {
298
+ if (ch === "\n") {
299
+ inLineComment = false;
300
+ out += ch;
301
+ }
302
+ continue;
303
+ }
304
+ if (inBlockComment) {
305
+ if (ch === "*" && next === "/") {
306
+ inBlockComment = false;
307
+ i++;
308
+ }
309
+ continue;
310
+ }
311
+ if (inString) {
312
+ out += ch;
313
+ if (escapeNext) {
314
+ escapeNext = false;
315
+ }
316
+ else if (ch === "\\") {
317
+ escapeNext = true;
318
+ }
319
+ else if (ch === '"') {
320
+ inString = false;
321
+ }
322
+ continue;
323
+ }
324
+ if (ch === '"') {
325
+ inString = true;
326
+ out += ch;
327
+ continue;
328
+ }
329
+ if (ch === "/" && next === "/") {
330
+ inLineComment = true;
331
+ i++;
332
+ continue;
333
+ }
334
+ if (ch === "/" && next === "*") {
335
+ inBlockComment = true;
336
+ i++;
337
+ continue;
338
+ }
339
+ out += ch;
340
+ }
341
+ return out;
342
+ }
343
+ function enforceTrustedSchemaImportMapPolicy(deckPath) {
344
+ if (deckPath.startsWith("gambit://"))
345
+ return;
346
+ const configPath = tryReadWorkspaceConfigPath(deckPath);
347
+ if (!configPath)
348
+ return;
349
+ const violations = readWorkspaceImportMapKeys(configPath).filter((key) => isTrustedSchemaImportKey(key));
350
+ if (violations.length === 0)
351
+ return;
352
+ throw new Error(`[gambit] trust-boundary violation: workspace import map at ${configPath} remaps trusted schema namespace (${violations.join(", ")})`);
353
+ }
53
354
  async function runDeck(opts) {
54
355
  const guardrails = {
55
356
  ...constants_js_1.DEFAULT_GUARDRAILS,
@@ -62,34 +363,241 @@ async function runDeck(opts) {
62
363
  throw new Error(`Max depth ${guardrails.maxDepth} exceeded`);
63
364
  }
64
365
  const runId = opts.runId ?? opts.state?.runId ?? randomId("run");
65
- const deck = await (0, loader_js_1.loadDeck)(opts.path);
66
- const deckGuardrails = deck.guardrails ?? {};
67
- const effectiveGuardrails = {
68
- ...guardrails,
69
- ...deckGuardrails,
70
- };
366
+ enforceTrustedSchemaImportMapPolicy(opts.path);
367
+ const workerSandboxRequested = opts.workerSandbox ??
368
+ shouldUseWorkerSandbox();
369
+ if (workerSandboxRequested && !(0, runtime_worker_host_js_1.isWorkerSandboxHostSupported)()) {
370
+ throw new runtime_worker_host_js_1.WorkerSandboxUnsupportedHostError();
371
+ }
372
+ if (workerSandboxRequested && opts.signal) {
373
+ throw new WorkerSandboxSignalUnsupportedError();
374
+ }
375
+ const workerSandbox = workerSandboxRequested;
71
376
  const isRoot = Boolean(inferredRoot);
72
- ensureSchemaPresence(deck, isRoot);
73
- const resolvedInput = resolveInput({
74
- deck,
75
- input: opts.input,
76
- state: opts.state,
77
- isRoot,
78
- initialUserMessage: opts.initialUserMessage,
79
- });
80
- const validatedInput = validateInput(deck, resolvedInput, isRoot, opts.allowRootStringInput ?? false);
81
377
  const shouldEmitRun = opts.depth === undefined || opts.depth === 0;
82
- if (shouldEmitRun) {
83
- opts.trace?.({
84
- type: "run.start",
85
- runId,
86
- deckPath: deck.path,
87
- input: validatedInput,
88
- initialUserMessage: opts
89
- .initialUserMessage,
90
- });
91
- }
378
+ let canceled = false;
379
+ let cancelHandled = false;
380
+ const handleCancel = async () => {
381
+ if (cancelHandled)
382
+ return;
383
+ cancelHandled = true;
384
+ if (!opts.onCancel)
385
+ return;
386
+ try {
387
+ await opts.onCancel();
388
+ }
389
+ catch (err) {
390
+ logger.warn(`[gambit] runDeck onCancel callback failed: ${err instanceof Error ? err.message : String(err)}`);
391
+ }
392
+ };
92
393
  try {
394
+ throwIfCanceled(opts.signal);
395
+ if (workerSandbox) {
396
+ const preInspectRunDeadlineMs = deadlineForRun(guardrails, opts.runDeadlineMs);
397
+ ensureRunActive(preInspectRunDeadlineMs, opts.signal);
398
+ const inspectedDeck = await inspectDeckInWorker(opts.path, preInspectRunDeadlineMs);
399
+ const deckDir = path.dirname(inspectedDeck.deckPath);
400
+ const permissions = (0, permissions_js_1.resolveEffectivePermissions)({
401
+ baseDir: deckDir,
402
+ parent: opts.parentPermissions,
403
+ workspace: opts.workspacePermissions
404
+ ? {
405
+ baseDir: opts.workspacePermissionsBaseDir ?? deckDir,
406
+ permissions: opts.workspacePermissions,
407
+ }
408
+ : undefined,
409
+ declaration: inspectedDeck.permissions
410
+ ? { baseDir: deckDir, permissions: inspectedDeck.permissions }
411
+ : undefined,
412
+ reference: opts.referencePermissions
413
+ ? {
414
+ baseDir: opts.referencePermissionsBaseDir ?? deckDir,
415
+ permissions: opts.referencePermissions,
416
+ }
417
+ : undefined,
418
+ session: opts.sessionPermissions
419
+ ? {
420
+ baseDir: opts.sessionPermissionsBaseDir ?? dntShim.Deno.cwd(),
421
+ permissions: opts.sessionPermissions,
422
+ }
423
+ : undefined,
424
+ });
425
+ const effectiveGuardrails = {
426
+ ...guardrails,
427
+ ...(inspectedDeck.guardrails ?? {}),
428
+ };
429
+ const runDeadlineMs = deadlineForRun(effectiveGuardrails, opts.runDeadlineMs);
430
+ ensureRunActive(runDeadlineMs, opts.signal);
431
+ const resolvedInput = resolveInputWithoutDeck({
432
+ input: opts.input,
433
+ state: opts.state,
434
+ isRoot,
435
+ initialUserMessage: opts.initialUserMessage,
436
+ });
437
+ if (!inspectedDeck.hasModelParams) {
438
+ if (shouldEmitRun) {
439
+ opts.trace?.({
440
+ type: "run.start",
441
+ runId,
442
+ deckPath: inspectedDeck.deckPath,
443
+ input: resolvedInput,
444
+ initialUserMessage: opts
445
+ .initialUserMessage,
446
+ permissions: permissions.trace,
447
+ });
448
+ }
449
+ return await runComputeDeckInWorker({
450
+ deckPath: inspectedDeck.deckPath,
451
+ guardrails: effectiveGuardrails,
452
+ depth,
453
+ runId,
454
+ initialUserMessage: opts.initialUserMessage,
455
+ parentActionCallId: opts.parentActionCallId,
456
+ modelProvider: opts.modelProvider,
457
+ input: resolvedInput,
458
+ defaultModel: opts.defaultModel,
459
+ modelOverride: opts.modelOverride,
460
+ trace: opts.trace,
461
+ stream: opts.stream,
462
+ state: opts.state,
463
+ onStateUpdate: opts.onStateUpdate,
464
+ onStreamText: opts.onStreamText,
465
+ responsesMode: opts.responsesMode,
466
+ permissions: permissions.effective,
467
+ permissionsTrace: permissions.trace,
468
+ workspacePermissions: opts.workspacePermissions,
469
+ workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
470
+ sessionPermissions: opts.sessionPermissions,
471
+ sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
472
+ runDeadlineMs,
473
+ isRoot,
474
+ allowRootStringInput: opts.allowRootStringInput ?? false,
475
+ signal: opts.signal,
476
+ });
477
+ }
478
+ if (!opts.inOrchestrationWorker) {
479
+ return await runLlmDeckInWorker({
480
+ deckPath: inspectedDeck.deckPath,
481
+ guardrails: effectiveGuardrails,
482
+ depth,
483
+ runId,
484
+ parentActionCallId: opts.parentActionCallId,
485
+ modelProvider: opts.modelProvider,
486
+ input: resolvedInput,
487
+ inputProvided: opts.inputProvided ?? true,
488
+ initialUserMessage: opts.initialUserMessage,
489
+ defaultModel: opts.defaultModel,
490
+ modelOverride: opts.modelOverride,
491
+ trace: opts.trace,
492
+ stream: opts.stream,
493
+ state: opts.state,
494
+ onStateUpdate: opts.onStateUpdate,
495
+ onStreamText: opts.onStreamText,
496
+ responsesMode: opts.responsesMode,
497
+ permissions: permissions.effective,
498
+ permissionsTrace: permissions.trace,
499
+ workspacePermissions: opts.workspacePermissions,
500
+ workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
501
+ sessionPermissions: opts.sessionPermissions,
502
+ sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
503
+ runDeadlineMs,
504
+ workerSandbox,
505
+ allowRootStringInput: opts.allowRootStringInput,
506
+ isRoot,
507
+ signal: opts.signal,
508
+ });
509
+ }
510
+ }
511
+ const deck = await (0, loader_js_1.loadDeck)(opts.path);
512
+ const permissions = (0, permissions_js_1.resolveEffectivePermissions)({
513
+ baseDir: path.dirname(deck.path),
514
+ parent: opts.parentPermissions,
515
+ workspace: opts.workspacePermissions
516
+ ? {
517
+ baseDir: opts.workspacePermissionsBaseDir ?? path.dirname(deck.path),
518
+ permissions: opts.workspacePermissions,
519
+ }
520
+ : undefined,
521
+ declaration: deck.permissions
522
+ ? { baseDir: path.dirname(deck.path), permissions: deck.permissions }
523
+ : undefined,
524
+ reference: opts.referencePermissions
525
+ ? {
526
+ baseDir: opts.referencePermissionsBaseDir ?? path.dirname(deck.path),
527
+ permissions: opts.referencePermissions,
528
+ }
529
+ : undefined,
530
+ session: opts.sessionPermissions
531
+ ? {
532
+ baseDir: opts.sessionPermissionsBaseDir ?? dntShim.Deno.cwd(),
533
+ permissions: opts.sessionPermissions,
534
+ }
535
+ : undefined,
536
+ });
537
+ const deckGuardrails = deck.guardrails ?? {};
538
+ const effectiveGuardrails = {
539
+ ...guardrails,
540
+ ...deckGuardrails,
541
+ };
542
+ const runDeadlineMs = deadlineForRun(effectiveGuardrails, opts.runDeadlineMs);
543
+ ensureRunActive(runDeadlineMs, opts.signal);
544
+ ensureSchemaPresence(deck, isRoot);
545
+ const resolvedInput = resolveInput({
546
+ deck,
547
+ input: opts.input,
548
+ state: opts.state,
549
+ isRoot,
550
+ initialUserMessage: opts.initialUserMessage,
551
+ });
552
+ const validatedInput = validateInput(deck, resolvedInput, isRoot, opts.allowRootStringInput ?? false);
553
+ const useOrchestrationWorker = workerSandbox &&
554
+ !opts.inOrchestrationWorker &&
555
+ isRoot &&
556
+ !opts.onTool &&
557
+ Boolean(deck.modelParams?.model || deck.modelParams?.temperature !== undefined);
558
+ if (useOrchestrationWorker) {
559
+ return await runLlmDeckInWorker({
560
+ deckPath: deck.path,
561
+ guardrails: effectiveGuardrails,
562
+ depth,
563
+ runId,
564
+ parentActionCallId: opts.parentActionCallId,
565
+ modelProvider: opts.modelProvider,
566
+ input: validatedInput,
567
+ inputProvided: opts.inputProvided ?? true,
568
+ initialUserMessage: opts.initialUserMessage,
569
+ defaultModel: opts.defaultModel,
570
+ modelOverride: opts.modelOverride,
571
+ trace: opts.trace,
572
+ stream: opts.stream,
573
+ state: opts.state,
574
+ onStateUpdate: opts.onStateUpdate,
575
+ onStreamText: opts.onStreamText,
576
+ responsesMode: opts.responsesMode,
577
+ permissions: permissions.effective,
578
+ permissionsTrace: permissions.trace,
579
+ workspacePermissions: opts.workspacePermissions,
580
+ workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
581
+ sessionPermissions: opts.sessionPermissions,
582
+ sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
583
+ runDeadlineMs,
584
+ workerSandbox,
585
+ allowRootStringInput: opts.allowRootStringInput,
586
+ isRoot,
587
+ signal: opts.signal,
588
+ });
589
+ }
590
+ if (shouldEmitRun) {
591
+ opts.trace?.({
592
+ type: "run.start",
593
+ runId,
594
+ deckPath: deck.path,
595
+ input: validatedInput,
596
+ initialUserMessage: opts
597
+ .initialUserMessage,
598
+ permissions: permissions.trace,
599
+ });
600
+ }
93
601
  if (deck.modelParams?.model || deck.modelParams?.temperature !== undefined) {
94
602
  return await runLlmDeck({
95
603
  deck,
@@ -108,6 +616,17 @@ async function runDeck(opts) {
108
616
  state: opts.state,
109
617
  onStateUpdate: opts.onStateUpdate,
110
618
  onStreamText: opts.onStreamText,
619
+ responsesMode: opts.responsesMode,
620
+ permissions: permissions.effective,
621
+ permissionsTrace: permissions.trace,
622
+ workspacePermissions: opts.workspacePermissions,
623
+ workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
624
+ sessionPermissions: opts.sessionPermissions,
625
+ sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
626
+ runDeadlineMs,
627
+ workerSandbox,
628
+ onTool: opts.onTool,
629
+ signal: opts.signal,
111
630
  });
112
631
  }
113
632
  if (!deck.executor) {
@@ -118,6 +637,7 @@ async function runDeck(opts) {
118
637
  guardrails: effectiveGuardrails,
119
638
  depth,
120
639
  runId,
640
+ initialUserMessage: opts.initialUserMessage,
121
641
  parentActionCallId: opts.parentActionCallId,
122
642
  modelProvider: opts.modelProvider,
123
643
  input: validatedInput,
@@ -125,19 +645,42 @@ async function runDeck(opts) {
125
645
  modelOverride: opts.modelOverride,
126
646
  trace: opts.trace,
127
647
  stream: opts.stream,
648
+ state: opts.state,
649
+ onStateUpdate: opts.onStateUpdate,
128
650
  onStreamText: opts.onStreamText,
651
+ responsesMode: opts.responsesMode,
652
+ permissions: permissions.effective,
653
+ permissionsTrace: permissions.trace,
654
+ workspacePermissions: opts.workspacePermissions,
655
+ workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
656
+ sessionPermissions: opts.sessionPermissions,
657
+ sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
658
+ runDeadlineMs,
659
+ workerSandbox,
660
+ onTool: opts.onTool,
661
+ signal: opts.signal,
129
662
  });
130
663
  }
664
+ catch (err) {
665
+ if (isRunCanceledError(err)) {
666
+ canceled = true;
667
+ await handleCancel();
668
+ }
669
+ throw err;
670
+ }
131
671
  finally {
132
672
  if (shouldEmitRun) {
133
673
  opts.trace?.({ type: "run.end", runId });
134
674
  }
675
+ if (opts.signal?.aborted && !canceled) {
676
+ await handleCancel();
677
+ }
135
678
  }
136
679
  }
137
680
  function toProviderParams(params) {
138
681
  if (!params)
139
682
  return undefined;
140
- const { model: _model, temperature, top_p, frequency_penalty, presence_penalty, max_tokens, } = params;
683
+ const { model: _model, temperature, top_p, frequency_penalty, presence_penalty, max_tokens, verbosity, reasoning, } = params;
141
684
  const out = {};
142
685
  if (temperature !== undefined)
143
686
  out.temperature = temperature;
@@ -150,15 +693,51 @@ function toProviderParams(params) {
150
693
  out.presence_penalty = presence_penalty;
151
694
  if (max_tokens !== undefined)
152
695
  out.max_tokens = max_tokens;
696
+ if (verbosity !== undefined)
697
+ out.verbosity = verbosity;
698
+ if (reasoning !== undefined)
699
+ out.reasoning = reasoning;
153
700
  return Object.keys(out).length ? out : undefined;
154
701
  }
702
+ async function resolveModelChoice(args) {
703
+ const resolver = args.modelProvider.resolveModel;
704
+ if (resolver) {
705
+ return await resolver({
706
+ model: args.model,
707
+ params: args.params,
708
+ deckPath: args.deckPath,
709
+ });
710
+ }
711
+ if (Array.isArray(args.model)) {
712
+ const first = args.model.find((entry) => typeof entry === "string" && entry.trim().length > 0);
713
+ if (!first) {
714
+ throw new Error(`No model configured for deck ${args.deckPath}`);
715
+ }
716
+ return { model: first, params: args.params };
717
+ }
718
+ if (!args.model || !args.model.trim()) {
719
+ throw new Error(`No model configured for deck ${args.deckPath}`);
720
+ }
721
+ return { model: args.model, params: args.params };
722
+ }
723
+ function resolveContextSchema(deck) {
724
+ return deck.contextSchema ?? deck.inputSchema;
725
+ }
726
+ function resolveResponseSchema(deck) {
727
+ return deck.responseSchema ?? deck.outputSchema;
728
+ }
729
+ function isContextToolName(name) {
730
+ return name === constants_js_1.GAMBIT_TOOL_CONTEXT || name === constants_js_1.GAMBIT_TOOL_INIT;
731
+ }
155
732
  function ensureSchemaPresence(deck, isRoot) {
156
733
  if (!isRoot) {
157
- if (!deck.inputSchema || !deck.outputSchema) {
158
- throw new Error(`Deck ${deck.path} must declare inputSchema and outputSchema (non-root)`);
734
+ const contextSchema = resolveContextSchema(deck);
735
+ const responseSchema = resolveResponseSchema(deck);
736
+ if (!contextSchema || !responseSchema) {
737
+ throw new Error(`Deck ${deck.path} must declare contextSchema and responseSchema (non-root)`);
159
738
  }
160
- (0, schema_js_1.assertZodSchema)(deck.inputSchema, "inputSchema");
161
- (0, schema_js_1.assertZodSchema)(deck.outputSchema, "outputSchema");
739
+ (0, schema_js_1.assertZodSchema)(contextSchema, "contextSchema");
740
+ (0, schema_js_1.assertZodSchema)(responseSchema, "responseSchema");
162
741
  }
163
742
  }
164
743
  function resolveInput(args) {
@@ -166,11 +745,11 @@ function resolveInput(args) {
166
745
  return args.input;
167
746
  if (!args.isRoot)
168
747
  return args.input;
169
- const persisted = extractInitInput(args.state);
748
+ const persisted = extractContextInput(args.state);
170
749
  if (persisted !== undefined)
171
750
  return persisted;
172
751
  if (args.initialUserMessage !== undefined) {
173
- const schema = args.deck.inputSchema;
752
+ const schema = resolveContextSchema(args.deck);
174
753
  if (schema?.safeParse) {
175
754
  const candidates = [undefined, {}, ""];
176
755
  for (const candidate of candidates) {
@@ -188,12 +767,30 @@ function resolveInput(args) {
188
767
  }
189
768
  return args.input;
190
769
  }
191
- function extractInitInput(state) {
192
- if (!state?.messages)
770
+ function resolveInputWithoutDeck(args) {
771
+ if (args.input !== undefined)
772
+ return args.input;
773
+ if (!args.isRoot)
774
+ return args.input;
775
+ const persisted = extractContextInput(args.state);
776
+ if (persisted !== undefined)
777
+ return persisted;
778
+ if (args.initialUserMessage !== undefined) {
779
+ return "";
780
+ }
781
+ return args.input;
782
+ }
783
+ function extractContextInput(state) {
784
+ if (!state)
785
+ return undefined;
786
+ if (state.format === "responses" && Array.isArray(state.items)) {
787
+ return extractContextInputFromItems(state.items);
788
+ }
789
+ if (!state.messages)
193
790
  return undefined;
194
791
  for (let i = state.messages.length - 1; i >= 0; i--) {
195
792
  const msg = state.messages[i];
196
- if (msg.role === "tool" && msg.name === constants_js_1.GAMBIT_TOOL_INIT) {
793
+ if (msg.role === "tool" && isContextToolName(msg.name ?? "")) {
197
794
  const content = msg.content;
198
795
  if (typeof content !== "string")
199
796
  return undefined;
@@ -207,17 +804,244 @@ function extractInitInput(state) {
207
804
  }
208
805
  return undefined;
209
806
  }
807
+ function extractContextInputFromItems(items) {
808
+ const contextToolNames = new Set([constants_js_1.GAMBIT_TOOL_CONTEXT, constants_js_1.GAMBIT_TOOL_INIT]);
809
+ const callNameById = new Map();
810
+ for (const item of items) {
811
+ if (item.type === "function_call") {
812
+ callNameById.set(item.call_id, item.name);
813
+ }
814
+ }
815
+ for (let i = items.length - 1; i >= 0; i--) {
816
+ const item = items[i];
817
+ if (item.type !== "function_call_output")
818
+ continue;
819
+ const name = callNameById.get(item.call_id);
820
+ if (!name || !contextToolNames.has(name))
821
+ continue;
822
+ try {
823
+ return JSON.parse(item.output);
824
+ }
825
+ catch {
826
+ return item.output;
827
+ }
828
+ }
829
+ return undefined;
830
+ }
831
+ function messagesFromResponseItems(items) {
832
+ const messages = [];
833
+ const callNameById = new Map();
834
+ for (const item of items) {
835
+ if (item.type === "message") {
836
+ const text = item.content.map((part) => part.text).join("");
837
+ messages.push({
838
+ role: item.role,
839
+ content: text || null,
840
+ });
841
+ continue;
842
+ }
843
+ if (item.type === "function_call") {
844
+ callNameById.set(item.call_id, item.name);
845
+ messages.push({
846
+ role: "assistant",
847
+ content: null,
848
+ tool_calls: [{
849
+ id: item.call_id,
850
+ type: "function",
851
+ function: { name: item.name, arguments: item.arguments },
852
+ }],
853
+ });
854
+ continue;
855
+ }
856
+ if (item.type === "function_call_output") {
857
+ messages.push({
858
+ role: "tool",
859
+ name: callNameById.get(item.call_id),
860
+ tool_call_id: item.call_id,
861
+ content: item.output,
862
+ });
863
+ }
864
+ }
865
+ return messages;
866
+ }
867
+ function responseItemsFromMessages(messages) {
868
+ const items = [];
869
+ for (const message of messages) {
870
+ if (message.role === "tool") {
871
+ if (!message.tool_call_id || message.content === null)
872
+ continue;
873
+ items.push({
874
+ type: "function_call_output",
875
+ call_id: message.tool_call_id,
876
+ output: String(message.content),
877
+ });
878
+ continue;
879
+ }
880
+ const contentText = message.content ?? "";
881
+ if (typeof contentText === "string" && contentText.length > 0) {
882
+ items.push({
883
+ type: "message",
884
+ role: message.role,
885
+ content: [{
886
+ type: message.role === "assistant" ? "output_text" : "input_text",
887
+ text: contentText,
888
+ }],
889
+ });
890
+ }
891
+ if (message.role === "assistant" && message.tool_calls) {
892
+ for (const call of message.tool_calls) {
893
+ items.push({
894
+ type: "function_call",
895
+ call_id: call.id,
896
+ name: call.function.name,
897
+ arguments: call.function.arguments,
898
+ });
899
+ }
900
+ }
901
+ }
902
+ return items;
903
+ }
904
+ function safeJsonArgs(value) {
905
+ if (!value)
906
+ return {};
907
+ try {
908
+ const parsed = JSON.parse(value);
909
+ if (parsed && typeof parsed === "object") {
910
+ return parsed;
911
+ }
912
+ }
913
+ catch {
914
+ // ignore
915
+ }
916
+ return {};
917
+ }
918
+ function asToolKind(value, fallback) {
919
+ if (value === "action" || value === "external" || value === "mcp_bridge" ||
920
+ value === "internal") {
921
+ return value;
922
+ }
923
+ return fallback;
924
+ }
925
+ function projectStreamToolTraceEvents(input) {
926
+ if (!input.trace)
927
+ return;
928
+ const type = typeof input.streamEvent.type === "string"
929
+ ? input.streamEvent.type
930
+ : "";
931
+ if (type !== "tool.call" && type !== "tool.result")
932
+ return;
933
+ const actionCallId = typeof input.streamEvent.actionCallId === "string"
934
+ ? input.streamEvent.actionCallId
935
+ : "";
936
+ const name = typeof input.streamEvent.name === "string"
937
+ ? input.streamEvent.name
938
+ : input.toolNames.get(actionCallId) ?? "";
939
+ if (!actionCallId || !name)
940
+ return;
941
+ if (type === "tool.call") {
942
+ if (input.emittedCalls.has(actionCallId))
943
+ return;
944
+ input.emittedCalls.add(actionCallId);
945
+ input.toolNames.set(actionCallId, name);
946
+ const args = "args" in input.streamEvent
947
+ ? (input.streamEvent.args ?? {})
948
+ : {};
949
+ const toolKind = asToolKind(input.streamEvent.toolKind, "mcp_bridge");
950
+ input.trace({
951
+ type: "tool.call",
952
+ runId: input.runId,
953
+ actionCallId,
954
+ name,
955
+ args,
956
+ toolKind,
957
+ parentActionCallId: input.parentActionCallId,
958
+ });
959
+ return;
960
+ }
961
+ if (input.emittedResults.has(actionCallId))
962
+ return;
963
+ input.emittedResults.add(actionCallId);
964
+ const result = "result" in input.streamEvent
965
+ ? (input.streamEvent.result ?? null)
966
+ : null;
967
+ const toolKind = asToolKind(input.streamEvent.toolKind, "mcp_bridge");
968
+ input.trace({
969
+ type: "tool.result",
970
+ runId: input.runId,
971
+ actionCallId,
972
+ name,
973
+ result,
974
+ toolKind,
975
+ parentActionCallId: input.parentActionCallId,
976
+ });
977
+ }
978
+ function traceOpenResponsesStreamEvent(input) {
979
+ if (!input.trace)
980
+ return false;
981
+ const type = typeof input.streamEvent.type === "string"
982
+ ? input.streamEvent.type
983
+ : "";
984
+ if (!type.startsWith("response."))
985
+ return false;
986
+ const rawMeta = input.streamEvent._gambit;
987
+ const existingMeta = rawMeta && typeof rawMeta === "object" &&
988
+ !Array.isArray(rawMeta)
989
+ ? rawMeta
990
+ : {};
991
+ input.trace({
992
+ ...input.streamEvent,
993
+ type,
994
+ _gambit: {
995
+ ...existingMeta,
996
+ run_id: input.runId,
997
+ action_call_id: input.actionCallId,
998
+ parent_action_call_id: input.parentActionCallId,
999
+ deck_path: input.deckPath,
1000
+ model: input.model,
1001
+ },
1002
+ });
1003
+ return true;
1004
+ }
1005
+ function mapResponseOutput(output) {
1006
+ const toolCalls = [];
1007
+ const textParts = [];
1008
+ for (const item of output) {
1009
+ if (item.type === "function_call") {
1010
+ toolCalls.push({
1011
+ id: item.call_id,
1012
+ name: item.name,
1013
+ args: safeJsonArgs(item.arguments),
1014
+ });
1015
+ continue;
1016
+ }
1017
+ if (item.type === "message" && item.role === "assistant") {
1018
+ for (const part of item.content) {
1019
+ if (part.type === "output_text") {
1020
+ textParts.push(part.text);
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ return {
1026
+ message: {
1027
+ role: "assistant",
1028
+ content: textParts.length ? textParts.join("") : null,
1029
+ },
1030
+ toolCalls: toolCalls.length ? toolCalls : undefined,
1031
+ };
1032
+ }
210
1033
  function validateInput(deck, input, isRoot, allowRootStringInput) {
211
- if (deck.inputSchema) {
1034
+ const contextSchema = resolveContextSchema(deck);
1035
+ if (contextSchema) {
212
1036
  if (isRoot && typeof input === "string" && allowRootStringInput) {
213
1037
  try {
214
- return (0, schema_js_1.validateWithSchema)(deck.inputSchema, input);
1038
+ return (0, schema_js_1.validateWithSchema)(contextSchema, input);
215
1039
  }
216
1040
  catch {
217
1041
  return input;
218
1042
  }
219
1043
  }
220
- return (0, schema_js_1.validateWithSchema)(deck.inputSchema, input);
1044
+ return (0, schema_js_1.validateWithSchema)(contextSchema, input);
221
1045
  }
222
1046
  if (isRoot) {
223
1047
  if (input === undefined)
@@ -226,28 +1050,1209 @@ function validateInput(deck, input, isRoot, allowRootStringInput) {
226
1050
  return input;
227
1051
  return input;
228
1052
  }
229
- throw new Error(`Deck ${deck.path} requires inputSchema (non-root)`);
1053
+ throw new Error(`Deck ${deck.path} requires contextSchema (non-root)`);
230
1054
  }
231
1055
  function validateOutput(deck, output, isRoot) {
232
- if (deck.outputSchema) {
233
- return (0, schema_js_1.validateWithSchema)(deck.outputSchema, output);
1056
+ const responseSchema = resolveResponseSchema(deck);
1057
+ if (responseSchema) {
1058
+ return (0, schema_js_1.validateWithSchema)(responseSchema, output);
1059
+ }
1060
+ if (isRoot) {
1061
+ if (typeof output === "string")
1062
+ return output;
1063
+ return JSON.stringify(output);
1064
+ }
1065
+ throw new Error(`Deck ${deck.path} requires responseSchema (non-root)`);
1066
+ }
1067
+ async function runComputeDeck(ctx) {
1068
+ if (ctx.workerSandbox) {
1069
+ return await runComputeDeckInWorker({
1070
+ guardrails: ctx.guardrails,
1071
+ depth: ctx.depth,
1072
+ runId: ctx.runId,
1073
+ inputProvided: ctx.inputProvided,
1074
+ initialUserMessage: ctx.initialUserMessage,
1075
+ parentActionCallId: ctx.parentActionCallId,
1076
+ modelProvider: ctx.modelProvider,
1077
+ input: ctx.input,
1078
+ defaultModel: ctx.defaultModel,
1079
+ modelOverride: ctx.modelOverride,
1080
+ trace: ctx.trace,
1081
+ stream: ctx.stream,
1082
+ state: ctx.state,
1083
+ onStateUpdate: ctx.onStateUpdate,
1084
+ onStreamText: ctx.onStreamText,
1085
+ responsesMode: ctx.responsesMode,
1086
+ permissions: ctx.permissions,
1087
+ permissionsTrace: ctx.permissionsTrace,
1088
+ workspacePermissions: ctx.workspacePermissions,
1089
+ workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
1090
+ sessionPermissions: ctx.sessionPermissions,
1091
+ sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
1092
+ runDeadlineMs: ctx.runDeadlineMs,
1093
+ deckPath: ctx.deck.path,
1094
+ isRoot: ctx.depth === 0 && !ctx.parentActionCallId,
1095
+ allowRootStringInput: false,
1096
+ signal: ctx.signal,
1097
+ });
1098
+ }
1099
+ return await runComputeDeckInProcess(ctx);
1100
+ }
1101
+ function toDenoPermissionList(scope) {
1102
+ if (scope.all)
1103
+ return true;
1104
+ if (scope.values.size === 0)
1105
+ return false;
1106
+ return Array.from(scope.values).sort();
1107
+ }
1108
+ function toDenoRunPermission(scope) {
1109
+ if (scope.all)
1110
+ return true;
1111
+ const values = new Set([
1112
+ ...Array.from(scope.paths),
1113
+ ...Array.from(scope.commands),
1114
+ ]);
1115
+ if (values.size === 0)
1116
+ return false;
1117
+ return Array.from(values).sort();
1118
+ }
1119
+ const IMPORT_SOURCE_EXTENSIONS = new Set([
1120
+ ".ts",
1121
+ ".tsx",
1122
+ ".mts",
1123
+ ".cts",
1124
+ ".js",
1125
+ ".jsx",
1126
+ ".mjs",
1127
+ ".cjs",
1128
+ ]);
1129
+ const RESOLVABLE_MODULE_EXTENSIONS = [
1130
+ ".ts",
1131
+ ".tsx",
1132
+ ".mts",
1133
+ ".cts",
1134
+ ".js",
1135
+ ".jsx",
1136
+ ".mjs",
1137
+ ".cjs",
1138
+ ".json",
1139
+ ];
1140
+ function stripSpecifierSuffix(specifier) {
1141
+ let out = specifier;
1142
+ const q = out.indexOf("?");
1143
+ if (q >= 0)
1144
+ out = out.slice(0, q);
1145
+ const h = out.indexOf("#");
1146
+ if (h >= 0)
1147
+ out = out.slice(0, h);
1148
+ return out.trim();
1149
+ }
1150
+ function isIdentifierStart(ch) {
1151
+ return /[A-Za-z_$]/.test(ch);
1152
+ }
1153
+ function isIdentifierContinue(ch) {
1154
+ return /[A-Za-z0-9_$]/.test(ch);
1155
+ }
1156
+ function skipWhitespaceAndComments(source, start) {
1157
+ let i = start;
1158
+ while (i < source.length) {
1159
+ const ch = source[i];
1160
+ if (/\s/.test(ch)) {
1161
+ i++;
1162
+ continue;
1163
+ }
1164
+ if (ch === "/" && source[i + 1] === "/") {
1165
+ i += 2;
1166
+ while (i < source.length && source[i] !== "\n" && source[i] !== "\r") {
1167
+ i++;
1168
+ }
1169
+ continue;
1170
+ }
1171
+ if (ch === "/" && source[i + 1] === "*") {
1172
+ i += 2;
1173
+ while (i < source.length) {
1174
+ if (source[i] === "*" && source[i + 1] === "/") {
1175
+ i += 2;
1176
+ break;
1177
+ }
1178
+ i++;
1179
+ }
1180
+ continue;
1181
+ }
1182
+ break;
1183
+ }
1184
+ return i;
1185
+ }
1186
+ function readIdentifier(source, start) {
1187
+ if (start >= source.length)
1188
+ return undefined;
1189
+ if (!isIdentifierStart(source[start]))
1190
+ return undefined;
1191
+ let i = start + 1;
1192
+ while (i < source.length && isIdentifierContinue(source[i]))
1193
+ i++;
1194
+ return { value: source.slice(start, i), end: i };
1195
+ }
1196
+ function readStringLiteral(source, start) {
1197
+ const quote = source[start];
1198
+ if (quote !== "'" && quote !== '"')
1199
+ return undefined;
1200
+ let i = start + 1;
1201
+ let value = "";
1202
+ while (i < source.length) {
1203
+ const ch = source[i];
1204
+ if (ch === "\\") {
1205
+ if (i + 1 >= source.length)
1206
+ return undefined;
1207
+ value += source[i + 1];
1208
+ i += 2;
1209
+ continue;
1210
+ }
1211
+ if (ch === quote)
1212
+ return { value, end: i + 1 };
1213
+ if (ch === "\n" || ch === "\r")
1214
+ return undefined;
1215
+ value += ch;
1216
+ i++;
1217
+ }
1218
+ return undefined;
1219
+ }
1220
+ function skipTemplateExpression(source, start) {
1221
+ let i = start;
1222
+ let depth = 1;
1223
+ while (i < source.length && depth > 0) {
1224
+ i = skipWhitespaceAndComments(source, i);
1225
+ if (i >= source.length)
1226
+ break;
1227
+ const ch = source[i];
1228
+ if (ch === "'" || ch === '"') {
1229
+ const stringLiteral = readStringLiteral(source, i);
1230
+ i = stringLiteral ? stringLiteral.end : i + 1;
1231
+ continue;
1232
+ }
1233
+ if (ch === "`") {
1234
+ i = skipTemplateLiteral(source, i);
1235
+ continue;
1236
+ }
1237
+ if (ch === "{") {
1238
+ depth++;
1239
+ i++;
1240
+ continue;
1241
+ }
1242
+ if (ch === "}") {
1243
+ depth--;
1244
+ i++;
1245
+ continue;
1246
+ }
1247
+ i++;
1248
+ }
1249
+ return i;
1250
+ }
1251
+ function skipTemplateLiteral(source, start) {
1252
+ let i = start + 1;
1253
+ while (i < source.length) {
1254
+ const ch = source[i];
1255
+ if (ch === "\\") {
1256
+ i += 2;
1257
+ continue;
1258
+ }
1259
+ if (ch === "`")
1260
+ return i + 1;
1261
+ if (ch === "$" && source[i + 1] === "{") {
1262
+ i = skipTemplateExpression(source, i + 2);
1263
+ continue;
1264
+ }
1265
+ i++;
1266
+ }
1267
+ return i;
1268
+ }
1269
+ function readSpecifierAfterFrom(source, start) {
1270
+ const i = skipWhitespaceAndComments(source, start);
1271
+ const stringLiteral = readStringLiteral(source, i);
1272
+ if (!stringLiteral)
1273
+ return { end: i };
1274
+ return { specifier: stringLiteral.value, end: stringLiteral.end };
1275
+ }
1276
+ function readImportCallSpecifier(source, start) {
1277
+ let i = skipWhitespaceAndComments(source, start);
1278
+ if (source[i] !== "(")
1279
+ return { end: i };
1280
+ i = skipWhitespaceAndComments(source, i + 1);
1281
+ const stringLiteral = readStringLiteral(source, i);
1282
+ if (!stringLiteral)
1283
+ return { end: i };
1284
+ i = skipWhitespaceAndComments(source, stringLiteral.end);
1285
+ if (source[i] === ")")
1286
+ i++;
1287
+ return { specifier: stringLiteral.value, end: i };
1288
+ }
1289
+ function readImportOrExportStatementSpecifier(source, start, keyword) {
1290
+ let i = skipWhitespaceAndComments(source, start);
1291
+ if (keyword === "import") {
1292
+ if (source[i] === ".")
1293
+ return { end: i + 1 }; // import.meta
1294
+ const sideEffectImport = readStringLiteral(source, i);
1295
+ if (sideEffectImport) {
1296
+ return { specifier: sideEffectImport.value, end: sideEffectImport.end };
1297
+ }
1298
+ }
1299
+ let depth = 0;
1300
+ while (i < source.length) {
1301
+ i = skipWhitespaceAndComments(source, i);
1302
+ if (i >= source.length)
1303
+ break;
1304
+ const ch = source[i];
1305
+ if (ch === "'" || ch === '"') {
1306
+ const stringLiteral = readStringLiteral(source, i);
1307
+ i = stringLiteral ? stringLiteral.end : i + 1;
1308
+ continue;
1309
+ }
1310
+ if (ch === "`") {
1311
+ i = skipTemplateLiteral(source, i);
1312
+ continue;
1313
+ }
1314
+ if (ch === "(" || ch === "[" || ch === "{") {
1315
+ depth++;
1316
+ i++;
1317
+ continue;
1318
+ }
1319
+ if (ch === ")" || ch === "]" || ch === "}") {
1320
+ if (depth > 0)
1321
+ depth--;
1322
+ i++;
1323
+ continue;
1324
+ }
1325
+ if (depth === 0) {
1326
+ if (ch === ";")
1327
+ return { end: i + 1 };
1328
+ const identifier = readIdentifier(source, i);
1329
+ if (identifier?.value === "from") {
1330
+ return readSpecifierAfterFrom(source, identifier.end);
1331
+ }
1332
+ if (identifier) {
1333
+ i = identifier.end;
1334
+ continue;
1335
+ }
1336
+ }
1337
+ i++;
1338
+ }
1339
+ return { end: i };
1340
+ }
1341
+ function extractModuleSpecifiers(source) {
1342
+ const out = new Set();
1343
+ let i = 0;
1344
+ while (i < source.length) {
1345
+ i = skipWhitespaceAndComments(source, i);
1346
+ if (i >= source.length)
1347
+ break;
1348
+ const ch = source[i];
1349
+ if (ch === "'" || ch === '"') {
1350
+ const stringLiteral = readStringLiteral(source, i);
1351
+ i = stringLiteral ? stringLiteral.end : i + 1;
1352
+ continue;
1353
+ }
1354
+ if (ch === "`") {
1355
+ i = skipTemplateLiteral(source, i);
1356
+ continue;
1357
+ }
1358
+ const identifier = readIdentifier(source, i);
1359
+ if (!identifier) {
1360
+ i++;
1361
+ continue;
1362
+ }
1363
+ if (identifier.value === "import") {
1364
+ const afterImport = skipWhitespaceAndComments(source, identifier.end);
1365
+ if (source[afterImport] === "(") {
1366
+ const result = readImportCallSpecifier(source, afterImport);
1367
+ if (result.specifier)
1368
+ out.add(result.specifier);
1369
+ i = Math.max(result.end, afterImport + 1);
1370
+ continue;
1371
+ }
1372
+ const result = readImportOrExportStatementSpecifier(source, identifier.end, "import");
1373
+ if (result.specifier)
1374
+ out.add(result.specifier);
1375
+ i = Math.max(result.end, identifier.end);
1376
+ continue;
1377
+ }
1378
+ if (identifier.value === "export") {
1379
+ const result = readImportOrExportStatementSpecifier(source, identifier.end, "export");
1380
+ if (result.specifier)
1381
+ out.add(result.specifier);
1382
+ i = Math.max(result.end, identifier.end);
1383
+ continue;
1384
+ }
1385
+ i = identifier.end;
1386
+ }
1387
+ return out;
1388
+ }
1389
+ function resolveExistingModulePath(candidate) {
1390
+ const resolved = path.resolve(candidate);
1391
+ const candidates = new Set([resolved]);
1392
+ if (!path.extname(resolved)) {
1393
+ for (const ext of RESOLVABLE_MODULE_EXTENSIONS) {
1394
+ candidates.add(`${resolved}${ext}`);
1395
+ candidates.add(path.join(resolved, `index${ext}`));
1396
+ }
1397
+ }
1398
+ for (const filePath of candidates) {
1399
+ try {
1400
+ if (dntShim.Deno.statSync(filePath).isFile) {
1401
+ return path.resolve(filePath);
1402
+ }
1403
+ }
1404
+ catch {
1405
+ // ignore unresolved module candidates
1406
+ }
1407
+ }
1408
+ return undefined;
1409
+ }
1410
+ function resolveLocalImportPath(importerPath, specifier) {
1411
+ const cleaned = stripSpecifierSuffix(specifier);
1412
+ if (!cleaned)
1413
+ return undefined;
1414
+ if (cleaned.startsWith("file://")) {
1415
+ try {
1416
+ return resolveExistingModulePath(path.fromFileUrl(cleaned));
1417
+ }
1418
+ catch {
1419
+ return undefined;
1420
+ }
1421
+ }
1422
+ if (!(cleaned.startsWith("./") || cleaned.startsWith("../") ||
1423
+ path.isAbsolute(cleaned))) {
1424
+ return undefined;
1425
+ }
1426
+ const base = path.isAbsolute(cleaned)
1427
+ ? cleaned
1428
+ : path.resolve(path.dirname(importerPath), cleaned);
1429
+ return resolveExistingModulePath(base);
1430
+ }
1431
+ function collectLocalImportGraph(entryPath) {
1432
+ const visited = new Set();
1433
+ const queue = [path.resolve(entryPath)];
1434
+ while (queue.length > 0) {
1435
+ const current = queue.pop();
1436
+ if (visited.has(current))
1437
+ continue;
1438
+ visited.add(current);
1439
+ const ext = path.extname(current).toLowerCase();
1440
+ if (!IMPORT_SOURCE_EXTENSIONS.has(ext)) {
1441
+ continue;
1442
+ }
1443
+ let source;
1444
+ try {
1445
+ source = dntShim.Deno.readTextFileSync(current);
1446
+ }
1447
+ catch {
1448
+ continue;
1449
+ }
1450
+ const specifiers = extractModuleSpecifiers(source);
1451
+ for (const specifier of specifiers) {
1452
+ const resolved = resolveLocalImportPath(current, specifier);
1453
+ if (resolved && !visited.has(resolved)) {
1454
+ queue.push(resolved);
1455
+ }
1456
+ }
1457
+ }
1458
+ return visited;
1459
+ }
1460
+ const WORKER_ENTRY_PATHS = [
1461
+ "./runtime_worker.ts",
1462
+ "./runtime_orchestration_worker.ts",
1463
+ ].map((relative) => path.fromFileUrl(new URL(relative, globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url)));
1464
+ const BUILTIN_SCHEMAS_DIR = path.resolve(path.dirname(path.fromFileUrl(globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url)), "../schemas");
1465
+ const BUILTIN_SNIPPETS_DIR = path.resolve(path.dirname(path.fromFileUrl(globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url)), "../snippets");
1466
+ let builtinSchemaBootstrapCache;
1467
+ function builtinSchemaBootstrapReads() {
1468
+ if (builtinSchemaBootstrapCache)
1469
+ return builtinSchemaBootstrapCache;
1470
+ const schemaModules = [];
1471
+ const stack = [BUILTIN_SCHEMAS_DIR];
1472
+ while (stack.length > 0) {
1473
+ const current = stack.pop();
1474
+ let entries = [];
1475
+ try {
1476
+ entries = Array.from(dntShim.Deno.readDirSync(current));
1477
+ }
1478
+ catch {
1479
+ continue;
1480
+ }
1481
+ for (const entry of entries) {
1482
+ const target = path.join(current, entry.name);
1483
+ if (entry.isDirectory) {
1484
+ stack.push(target);
1485
+ continue;
1486
+ }
1487
+ if (!entry.isFile)
1488
+ continue;
1489
+ const ext = path.extname(entry.name).toLowerCase();
1490
+ if (ext !== ".ts")
1491
+ continue;
1492
+ schemaModules.push(target);
1493
+ }
1494
+ }
1495
+ builtinSchemaBootstrapCache = Array.from(new Set(schemaModules.flatMap((entry) => Array.from(collectLocalImportGraph(entry))))).sort();
1496
+ return builtinSchemaBootstrapCache;
1497
+ }
1498
+ let builtinSnippetBootstrapCache;
1499
+ function builtinSnippetBootstrapReads() {
1500
+ if (builtinSnippetBootstrapCache)
1501
+ return builtinSnippetBootstrapCache;
1502
+ const snippetFiles = [];
1503
+ const stack = [BUILTIN_SNIPPETS_DIR];
1504
+ while (stack.length > 0) {
1505
+ const current = stack.pop();
1506
+ let entries = [];
1507
+ try {
1508
+ entries = Array.from(dntShim.Deno.readDirSync(current));
1509
+ }
1510
+ catch {
1511
+ continue;
1512
+ }
1513
+ for (const entry of entries) {
1514
+ const target = path.join(current, entry.name);
1515
+ if (entry.isDirectory) {
1516
+ stack.push(target);
1517
+ continue;
1518
+ }
1519
+ if (!entry.isFile)
1520
+ continue;
1521
+ const ext = path.extname(entry.name).toLowerCase();
1522
+ if (ext !== ".md")
1523
+ continue;
1524
+ snippetFiles.push(target);
1525
+ }
1526
+ }
1527
+ builtinSnippetBootstrapCache = Array.from(new Set(snippetFiles)).sort();
1528
+ return builtinSnippetBootstrapCache;
1529
+ }
1530
+ function workerBootstrapReadAllowlist(deckPath) {
1531
+ return Array.from(new Set([
1532
+ ...Array.from(collectLocalImportGraph(deckPath)),
1533
+ ...WORKER_ENTRY_PATHS.flatMap((entry) => Array.from(collectLocalImportGraph(entry))),
1534
+ ...builtinSchemaBootstrapReads(),
1535
+ ...builtinSnippetBootstrapReads(),
1536
+ ])).sort();
1537
+ }
1538
+ let trustedWorkerBootstrapCache;
1539
+ function trustedWorkerBootstrapReads() {
1540
+ if (trustedWorkerBootstrapCache)
1541
+ return trustedWorkerBootstrapCache;
1542
+ const definitionsPath = path.fromFileUrl(new URL("./definitions.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url));
1543
+ const modPath = path.fromFileUrl(new URL("../mod.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url));
1544
+ trustedWorkerBootstrapCache = Array.from(new Set([
1545
+ ...WORKER_ENTRY_PATHS.flatMap((entry) => Array.from(collectLocalImportGraph(entry))),
1546
+ ...Array.from(collectLocalImportGraph(definitionsPath)),
1547
+ ...Array.from(collectLocalImportGraph(modPath)),
1548
+ ...builtinSchemaBootstrapReads(),
1549
+ ...builtinSnippetBootstrapReads(),
1550
+ ])).sort();
1551
+ return trustedWorkerBootstrapCache;
1552
+ }
1553
+ function pathMatchesPermissionRoot(root, target) {
1554
+ if (root === target)
1555
+ return true;
1556
+ const rel = path.relative(root, target);
1557
+ return rel.length > 0 && !rel.startsWith("..") && !path.isAbsolute(rel);
1558
+ }
1559
+ function constrainBootstrapReads(permissions, roots, trustedReads, reads) {
1560
+ const allowedRoots = [
1561
+ ...roots.map((entry) => path.resolve(entry)),
1562
+ ...Array.from(permissions.read.values).map((entry) => path.resolve(permissions.baseDir, entry)),
1563
+ ];
1564
+ if (permissions.read.all) {
1565
+ return Array.from(new Set(reads)).sort();
1566
+ }
1567
+ if (allowedRoots.length === 0)
1568
+ return [];
1569
+ return reads.filter((entry) => {
1570
+ const target = path.resolve(permissions.baseDir, entry);
1571
+ if (trustedReads.has(target))
1572
+ return true;
1573
+ return allowedRoots.some((root) => pathMatchesPermissionRoot(root, target));
1574
+ });
1575
+ }
1576
+ function buildWorkerPermissions(permissions, deckPath) {
1577
+ const workerDirs = WORKER_ENTRY_PATHS.map((entry) => path.dirname(entry));
1578
+ const bootstrapReads = constrainBootstrapReads(permissions, [path.dirname(deckPath), ...workerDirs], new Set(trustedWorkerBootstrapReads()), workerBootstrapReadAllowlist(deckPath));
1579
+ const mergedRead = permissions.read.all ? true : Array.from(new Set([
1580
+ ...Array.from(permissions.read.values),
1581
+ ...bootstrapReads,
1582
+ ])).sort();
1583
+ return {
1584
+ permissions: {
1585
+ read: mergedRead === true
1586
+ ? true
1587
+ : mergedRead.length > 0
1588
+ ? mergedRead
1589
+ : false,
1590
+ write: toDenoPermissionList(permissions.write),
1591
+ run: toDenoRunPermission(permissions.run),
1592
+ net: toDenoPermissionList(permissions.net),
1593
+ env: toDenoPermissionList(permissions.env),
1594
+ // Worker module graphs include JSR dependencies (e.g. @std/*). Allow
1595
+ // manifest resolution without widening deck runtime file/run permissions.
1596
+ import: ["jsr.io:443"],
1597
+ },
1598
+ };
1599
+ }
1600
+ function buildDeckInspectWorkerPermissions(deckPath) {
1601
+ const deckDir = path.dirname(deckPath);
1602
+ const workerDirs = WORKER_ENTRY_PATHS.map((entry) => path.dirname(entry));
1603
+ const inspectSeedPermissions = {
1604
+ baseDir: deckDir,
1605
+ read: { all: false, values: new Set() },
1606
+ write: { all: false, values: new Set() },
1607
+ run: { all: false, paths: new Set(), commands: new Set() },
1608
+ net: { all: false, values: new Set() },
1609
+ env: { all: false, values: new Set() },
1610
+ };
1611
+ const bootstrapReads = constrainBootstrapReads(inspectSeedPermissions, [path.dirname(deckPath), ...workerDirs], new Set(trustedWorkerBootstrapReads()), workerBootstrapReadAllowlist(deckPath));
1612
+ const inspectReads = Array.from(new Set([deckDir, ...bootstrapReads])).sort();
1613
+ return {
1614
+ permissions: {
1615
+ read: inspectReads.length > 0 ? inspectReads : false,
1616
+ write: false,
1617
+ run: false,
1618
+ net: false,
1619
+ env: false,
1620
+ },
1621
+ };
1622
+ }
1623
+ async function inspectDeckInWorker(deckPath, runDeadlineMs) {
1624
+ if (typeof runDeadlineMs === "number" && Number.isFinite(runDeadlineMs)) {
1625
+ ensureNotExpired(runDeadlineMs);
1626
+ }
1627
+ const bridgeSession = randomId("bridge");
1628
+ const worker = (0, runtime_worker_host_js_1.createWorkerSandboxBridge)(new URL("./runtime_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url).href, buildDeckInspectWorkerPermissions(deckPath));
1629
+ let settled = false;
1630
+ const clearAndTerminate = () => {
1631
+ try {
1632
+ worker.terminate();
1633
+ }
1634
+ catch {
1635
+ // ignore
1636
+ }
1637
+ };
1638
+ let timeoutId;
1639
+ const outcome = new Promise((resolve, reject) => {
1640
+ const finishResolve = (value) => {
1641
+ if (settled)
1642
+ return;
1643
+ settled = true;
1644
+ if (timeoutId !== undefined)
1645
+ clearTimeout(timeoutId);
1646
+ resolve(value);
1647
+ };
1648
+ const finishReject = (err) => {
1649
+ if (settled)
1650
+ return;
1651
+ settled = true;
1652
+ if (timeoutId !== undefined)
1653
+ clearTimeout(timeoutId);
1654
+ reject(err);
1655
+ };
1656
+ const deadlineConstrained = typeof runDeadlineMs === "number" &&
1657
+ Number.isFinite(runDeadlineMs);
1658
+ const timeoutMs = deadlineConstrained
1659
+ ? Math.max(0, Math.min(INSPECT_WORKER_TIMEOUT_MS, Math.floor(runDeadlineMs - performance.now())))
1660
+ : INSPECT_WORKER_TIMEOUT_MS;
1661
+ const timeoutMessage = deadlineConstrained &&
1662
+ timeoutMs < INSPECT_WORKER_TIMEOUT_MS
1663
+ ? WORKER_TIMEOUT_MESSAGE
1664
+ : INSPECT_WORKER_TIMEOUT_MESSAGE;
1665
+ timeoutId = setTimeout(() => {
1666
+ finishReject(new Error(timeoutMessage));
1667
+ clearAndTerminate();
1668
+ }, timeoutMs);
1669
+ worker.addEventListener("error", (event) => {
1670
+ event.preventDefault?.();
1671
+ finishReject(event.error ??
1672
+ new Error(typeof event.message === "string"
1673
+ ? event.message
1674
+ : "Worker execution failed"));
1675
+ });
1676
+ worker.addEventListener("messageerror", () => {
1677
+ finishReject(new Error("Worker bridge message serialization failed"));
1678
+ });
1679
+ worker.addEventListener("message", (event) => {
1680
+ const msg = event.data;
1681
+ const receivedSession = typeof msg.bridgeSession === "string"
1682
+ ? msg.bridgeSession
1683
+ : "";
1684
+ if (receivedSession !== bridgeSession) {
1685
+ if (typeof msg.type === "string") {
1686
+ logger.warn(`[gambit] rejected inspect-worker message with mismatched bridge session (type=${msg.type})`);
1687
+ }
1688
+ return;
1689
+ }
1690
+ const type = typeof msg.type === "string" ? msg.type : "";
1691
+ if (type === "deck.inspect.result") {
1692
+ finishResolve(msg.result);
1693
+ return;
1694
+ }
1695
+ if (type === "deck.inspect.error" || type === "run.error") {
1696
+ finishReject(normalizeWorkerError(msg.error));
1697
+ }
1698
+ });
1699
+ });
1700
+ try {
1701
+ worker.postMessage({ type: "deck.inspect", bridgeSession, deckPath });
1702
+ return await outcome;
1703
+ }
1704
+ finally {
1705
+ if (timeoutId !== undefined)
1706
+ clearTimeout(timeoutId);
1707
+ clearAndTerminate();
1708
+ }
1709
+ }
1710
+ function normalizeWorkerError(err) {
1711
+ if (!err || typeof err !== "object") {
1712
+ return new Error(String(err));
1713
+ }
1714
+ const rec = err;
1715
+ const message = typeof rec.message === "string" && rec.message.trim().length > 0
1716
+ ? rec.message
1717
+ : "Worker execution failed";
1718
+ const code = typeof rec.code === "string" ? rec.code : undefined;
1719
+ const name = typeof rec.name === "string" ? rec.name : undefined;
1720
+ const source = typeof rec.source === "string" ? rec.source : undefined;
1721
+ const out = new Error(source ? `[${source}] ${message}${code ? ` (${code})` : ""}` : message);
1722
+ if (name)
1723
+ out.name = name;
1724
+ return out;
1725
+ }
1726
+ async function runLlmDeckInWorker(ctx) {
1727
+ throwIfCanceled(ctx.signal);
1728
+ const bridgeSession = randomId("bridge");
1729
+ const completionNonce = randomId("done");
1730
+ const worker = (0, runtime_worker_host_js_1.createWorkerSandboxBridge)(new URL("./runtime_orchestration_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url).href, buildWorkerPermissions(ctx.permissions, ctx.deckPath));
1731
+ let settled = false;
1732
+ const clearAndTerminate = () => {
1733
+ try {
1734
+ worker.terminate();
1735
+ }
1736
+ catch {
1737
+ // ignore
1738
+ }
1739
+ };
1740
+ let timeoutId;
1741
+ const outcome = new Promise((resolve, reject) => {
1742
+ const finishResolve = (value) => {
1743
+ if (settled)
1744
+ return;
1745
+ settled = true;
1746
+ if (timeoutId !== undefined)
1747
+ clearTimeout(timeoutId);
1748
+ resolve(value);
1749
+ };
1750
+ const finishReject = (err) => {
1751
+ if (settled)
1752
+ return;
1753
+ settled = true;
1754
+ if (timeoutId !== undefined)
1755
+ clearTimeout(timeoutId);
1756
+ reject(err);
1757
+ };
1758
+ const remainingMs = Math.max(0, Math.floor(ctx.runDeadlineMs - performance.now()));
1759
+ timeoutId = setTimeout(() => {
1760
+ finishReject(new Error(WORKER_TIMEOUT_MESSAGE));
1761
+ clearAndTerminate();
1762
+ }, remainingMs);
1763
+ worker.addEventListener("error", (event) => {
1764
+ event.preventDefault?.();
1765
+ finishReject(event.error ??
1766
+ new Error(typeof event.message === "string"
1767
+ ? event.message
1768
+ : "Worker execution failed"));
1769
+ });
1770
+ worker.addEventListener("messageerror", () => {
1771
+ finishReject(new Error("Worker bridge message serialization failed"));
1772
+ });
1773
+ worker.addEventListener("message", (event) => {
1774
+ const msg = event.data;
1775
+ if (!msg || typeof msg !== "object")
1776
+ return;
1777
+ if (msg.bridgeSession !== bridgeSession) {
1778
+ logger.warn(`[gambit] rejected orchestration-worker message with mismatched bridge session (type=${msg.type})`);
1779
+ return;
1780
+ }
1781
+ if (msg.type === "trace.event") {
1782
+ ctx.trace?.(msg.event);
1783
+ return;
1784
+ }
1785
+ if (msg.type === "state.update") {
1786
+ ctx.onStateUpdate?.(msg.state);
1787
+ return;
1788
+ }
1789
+ if (msg.type === "stream.text") {
1790
+ ctx.onStreamText?.(msg.chunk);
1791
+ return;
1792
+ }
1793
+ if (msg.type === "model.chat.request") {
1794
+ (async () => {
1795
+ try {
1796
+ const result = await ctx.modelProvider.chat({
1797
+ ...msg.input,
1798
+ signal: ctx.signal,
1799
+ onStreamText: (chunk) => {
1800
+ worker.postMessage({
1801
+ type: "model.chat.stream",
1802
+ requestId: msg.requestId,
1803
+ chunk,
1804
+ });
1805
+ },
1806
+ });
1807
+ worker.postMessage({
1808
+ type: "model.chat.result",
1809
+ requestId: msg.requestId,
1810
+ result,
1811
+ });
1812
+ }
1813
+ catch (err) {
1814
+ worker.postMessage({
1815
+ type: "model.chat.error",
1816
+ requestId: msg.requestId,
1817
+ error: {
1818
+ source: "model",
1819
+ name: err instanceof Error ? err.name : undefined,
1820
+ message: err instanceof Error ? err.message : String(err),
1821
+ code: err?.code,
1822
+ },
1823
+ });
1824
+ }
1825
+ })();
1826
+ return;
1827
+ }
1828
+ if (msg.type === "model.responses.request") {
1829
+ (async () => {
1830
+ try {
1831
+ if (!ctx.modelProvider.responses) {
1832
+ throw new Error("Responses API unavailable for current model provider");
1833
+ }
1834
+ const result = await ctx.modelProvider.responses({
1835
+ ...msg.input,
1836
+ signal: ctx.signal,
1837
+ onStreamEvent: (streamEvent) => {
1838
+ worker.postMessage({
1839
+ type: "model.responses.event",
1840
+ requestId: msg.requestId,
1841
+ event: streamEvent,
1842
+ });
1843
+ },
1844
+ });
1845
+ worker.postMessage({
1846
+ type: "model.responses.result",
1847
+ requestId: msg.requestId,
1848
+ result,
1849
+ });
1850
+ }
1851
+ catch (err) {
1852
+ worker.postMessage({
1853
+ type: "model.responses.error",
1854
+ requestId: msg.requestId,
1855
+ error: {
1856
+ source: "model",
1857
+ name: err instanceof Error ? err.name : undefined,
1858
+ message: err instanceof Error ? err.message : String(err),
1859
+ code: err?.code,
1860
+ },
1861
+ });
1862
+ }
1863
+ })();
1864
+ return;
1865
+ }
1866
+ if (msg.type === "model.resolveModel.request") {
1867
+ (async () => {
1868
+ try {
1869
+ const result = ctx.modelProvider.resolveModel
1870
+ ? await ctx.modelProvider.resolveModel(msg.input)
1871
+ : {
1872
+ model: Array.isArray(msg.input.model)
1873
+ ? msg.input.model[0]
1874
+ : msg.input.model,
1875
+ params: msg.input.params,
1876
+ };
1877
+ worker.postMessage({
1878
+ type: "model.resolveModel.result",
1879
+ requestId: msg.requestId,
1880
+ result,
1881
+ });
1882
+ }
1883
+ catch (err) {
1884
+ worker.postMessage({
1885
+ type: "model.resolveModel.error",
1886
+ requestId: msg.requestId,
1887
+ error: {
1888
+ source: "model",
1889
+ name: err instanceof Error ? err.name : undefined,
1890
+ message: err instanceof Error ? err.message : String(err),
1891
+ code: err?.code,
1892
+ },
1893
+ });
1894
+ }
1895
+ })();
1896
+ return;
1897
+ }
1898
+ if (msg.type === "run.result") {
1899
+ if (msg.completionNonce !== completionNonce) {
1900
+ logger.warn(`[gambit] rejected orchestration-worker run.result with invalid completion nonce`);
1901
+ return;
1902
+ }
1903
+ finishResolve(msg.result);
1904
+ return;
1905
+ }
1906
+ if (msg.type === "run.error") {
1907
+ if (msg.completionNonce !== completionNonce) {
1908
+ logger.warn(`[gambit] rejected orchestration-worker run.error with invalid completion nonce`);
1909
+ return;
1910
+ }
1911
+ finishReject(normalizeWorkerError(msg.error));
1912
+ }
1913
+ });
1914
+ });
1915
+ try {
1916
+ worker.postMessage({
1917
+ type: "run.start",
1918
+ bridgeSession,
1919
+ completionNonce,
1920
+ options: {
1921
+ path: ctx.deckPath,
1922
+ input: ctx.input,
1923
+ inputProvided: ctx.inputProvided,
1924
+ initialUserMessage: ctx.initialUserMessage,
1925
+ isRoot: ctx.isRoot,
1926
+ guardrails: ctx.guardrails,
1927
+ depth: ctx.depth,
1928
+ parentActionCallId: ctx.parentActionCallId,
1929
+ runId: ctx.runId,
1930
+ defaultModel: ctx.defaultModel,
1931
+ modelOverride: ctx.modelOverride,
1932
+ stream: ctx.stream,
1933
+ state: ctx.state,
1934
+ responsesMode: ctx.responsesMode,
1935
+ allowRootStringInput: ctx.allowRootStringInput,
1936
+ runDeadlineMs: ctx.runDeadlineMs,
1937
+ },
1938
+ permissionCeiling: toWirePermissionSet(ctx.permissions),
1939
+ });
1940
+ ensureRunActive(ctx.runDeadlineMs, ctx.signal);
1941
+ return await outcome;
1942
+ }
1943
+ finally {
1944
+ if (timeoutId !== undefined)
1945
+ clearTimeout(timeoutId);
1946
+ clearAndTerminate();
1947
+ }
1948
+ }
1949
+ async function runComputeDeckInWorker(ctx) {
1950
+ throwIfCanceled(ctx.signal);
1951
+ const { runId } = ctx;
1952
+ const actionCallId = randomId("action");
1953
+ const bridgeSession = randomId("bridge");
1954
+ const completionNonce = randomId("done");
1955
+ const worker = (0, runtime_worker_host_js_1.createWorkerSandboxBridge)(new URL("./runtime_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url).href, buildWorkerPermissions(ctx.permissions, ctx.deckPath));
1956
+ let settled = false;
1957
+ const clearAndTerminate = () => {
1958
+ try {
1959
+ worker.terminate();
1960
+ }
1961
+ catch {
1962
+ // ignore
1963
+ }
1964
+ };
1965
+ let timeoutId;
1966
+ const activeSpawnRequests = new Set();
1967
+ let currentState = ctx.state;
1968
+ const outcome = new Promise((resolve, reject) => {
1969
+ const finishResolve = (value) => {
1970
+ if (settled)
1971
+ return;
1972
+ settled = true;
1973
+ if (timeoutId !== undefined)
1974
+ clearTimeout(timeoutId);
1975
+ resolve(value);
1976
+ };
1977
+ const finishReject = (err) => {
1978
+ if (settled)
1979
+ return;
1980
+ settled = true;
1981
+ if (timeoutId !== undefined)
1982
+ clearTimeout(timeoutId);
1983
+ reject(err);
1984
+ };
1985
+ const remainingMs = Math.max(0, Math.floor(ctx.runDeadlineMs - performance.now()));
1986
+ timeoutId = setTimeout(() => {
1987
+ finishReject(new Error(WORKER_TIMEOUT_MESSAGE));
1988
+ clearAndTerminate();
1989
+ }, remainingMs);
1990
+ worker.addEventListener("error", (event) => {
1991
+ event.preventDefault?.();
1992
+ finishReject(event.error ??
1993
+ new Error(typeof event.message === "string"
1994
+ ? event.message
1995
+ : "Worker execution failed"));
1996
+ });
1997
+ worker.addEventListener("messageerror", () => {
1998
+ finishReject(new Error("Worker bridge message serialization failed"));
1999
+ });
2000
+ worker.addEventListener("message", (event) => {
2001
+ const msg = event.data;
2002
+ const receivedBridgeSession = typeof msg.bridgeSession === "string"
2003
+ ? msg.bridgeSession
2004
+ : "";
2005
+ if (receivedBridgeSession !== bridgeSession) {
2006
+ const type = typeof msg.type === "string" ? msg.type : "unknown";
2007
+ logger.warn(`[gambit] rejected compute-worker message with mismatched bridge session (type=${type})`);
2008
+ return;
2009
+ }
2010
+ // Ignore any late worker messages once this run has already settled.
2011
+ if (settled)
2012
+ return;
2013
+ const type = typeof msg.type === "string" ? msg.type : "";
2014
+ if (type === "log.entry") {
2015
+ if (!ctx.trace)
2016
+ return;
2017
+ const entry = msg.entry;
2018
+ const raw = typeof entry === "string"
2019
+ ? { message: entry }
2020
+ : entry && typeof entry === "object"
2021
+ ? entry
2022
+ : { message: "" };
2023
+ const message = typeof raw.message === "string"
2024
+ ? raw.message
2025
+ : raw.message !== undefined
2026
+ ? String(raw.message)
2027
+ : "";
2028
+ const title = typeof raw.title === "string" ? raw.title : undefined;
2029
+ const body = raw.body ?? raw.message ?? message;
2030
+ ctx.trace({
2031
+ type: "log",
2032
+ runId,
2033
+ deckPath: ctx.deckPath,
2034
+ actionCallId,
2035
+ parentActionCallId: ctx.parentActionCallId,
2036
+ level: raw.level ?? "info",
2037
+ title: title ?? (message || undefined),
2038
+ message,
2039
+ body,
2040
+ meta: raw.meta,
2041
+ });
2042
+ return;
2043
+ }
2044
+ if (type === "spawn.request") {
2045
+ const req = msg;
2046
+ const requestId = req.requestId;
2047
+ if (!requestId)
2048
+ return;
2049
+ if (activeSpawnRequests.has(requestId)) {
2050
+ logger.warn(`[gambit] rejected duplicate compute-worker spawn.request (${requestId})`);
2051
+ return;
2052
+ }
2053
+ activeSpawnRequests.add(requestId);
2054
+ (async () => {
2055
+ try {
2056
+ const parentFromWorker = normalizePermissionBaseDir(fromWirePermissionSet(req.payload.parentPermissions), req.payload.parentPermissionsBaseDir);
2057
+ // Enforce monotonicity against the parent effective ceiling.
2058
+ const bridgedParent = (0, permissions_js_1.intersectPermissions)(ctx.permissions, parentFromWorker, req.payload.parentPermissionsBaseDir);
2059
+ const childResult = await runDeck({
2060
+ path: req.payload.path,
2061
+ input: req.payload.input,
2062
+ modelProvider: ctx.modelProvider,
2063
+ isRoot: false,
2064
+ guardrails: ctx.guardrails,
2065
+ depth: ctx.depth + 1,
2066
+ parentActionCallId: req.payload.parentActionCallId,
2067
+ runId,
2068
+ defaultModel: ctx.defaultModel,
2069
+ modelOverride: ctx.modelOverride,
2070
+ trace: ctx.trace,
2071
+ stream: ctx.stream,
2072
+ state: currentState,
2073
+ onStateUpdate: (state) => {
2074
+ currentState = state;
2075
+ ctx.onStateUpdate?.(state);
2076
+ },
2077
+ onStreamText: ctx.onStreamText,
2078
+ responsesMode: ctx.responsesMode,
2079
+ initialUserMessage: req.payload.initialUserMessage,
2080
+ inputProvided: true,
2081
+ parentPermissions: bridgedParent,
2082
+ workspacePermissions: req.payload.workspacePermissions,
2083
+ workspacePermissionsBaseDir: req.payload.workspacePermissionsBaseDir,
2084
+ sessionPermissions: req.payload.sessionPermissions,
2085
+ sessionPermissionsBaseDir: req.payload.sessionPermissionsBaseDir,
2086
+ runDeadlineMs: Math.min(ctx.runDeadlineMs, Number.isFinite(req.payload.runDeadlineMs)
2087
+ ? req.payload.runDeadlineMs
2088
+ : ctx.runDeadlineMs),
2089
+ workerSandbox: true,
2090
+ signal: ctx.signal,
2091
+ onTool: ctx.onTool,
2092
+ });
2093
+ worker.postMessage({
2094
+ type: "spawn.result",
2095
+ requestId,
2096
+ result: childResult,
2097
+ });
2098
+ }
2099
+ catch (err) {
2100
+ worker.postMessage({
2101
+ type: "spawn.error",
2102
+ requestId,
2103
+ error: {
2104
+ source: "child",
2105
+ name: err instanceof Error ? err.name : undefined,
2106
+ message: err instanceof Error ? err.message : String(err),
2107
+ code: err?.code,
2108
+ },
2109
+ });
2110
+ }
2111
+ finally {
2112
+ activeSpawnRequests.delete(requestId);
2113
+ }
2114
+ })();
2115
+ return;
2116
+ }
2117
+ if (type === "state.update") {
2118
+ const nextState = msg.state;
2119
+ if (!nextState || typeof nextState !== "object")
2120
+ return;
2121
+ currentState = nextState;
2122
+ ctx.onStateUpdate?.(nextState);
2123
+ return;
2124
+ }
2125
+ if (type === "run.result") {
2126
+ if (msg.completionNonce !==
2127
+ completionNonce) {
2128
+ logger.warn(`[gambit] rejected compute-worker run.result with invalid completion nonce`);
2129
+ return;
2130
+ }
2131
+ finishResolve(msg.result);
2132
+ return;
2133
+ }
2134
+ if (type === "run.error") {
2135
+ if (msg.completionNonce !==
2136
+ completionNonce) {
2137
+ logger.warn(`[gambit] rejected compute-worker run.error with invalid completion nonce`);
2138
+ return;
2139
+ }
2140
+ finishReject(normalizeWorkerError(msg.error));
2141
+ }
2142
+ });
2143
+ });
2144
+ try {
2145
+ worker.postMessage({
2146
+ type: "run.start",
2147
+ bridgeSession,
2148
+ completionNonce,
2149
+ runId,
2150
+ actionCallId,
2151
+ deckPath: ctx.deckPath,
2152
+ input: ctx.input,
2153
+ state: ctx.state,
2154
+ initialUserMessage: ctx.initialUserMessage,
2155
+ depth: ctx.depth,
2156
+ parentActionCallId: ctx.parentActionCallId,
2157
+ permissions: toWirePermissionSet(ctx.permissions),
2158
+ workspacePermissions: ctx.workspacePermissions,
2159
+ workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
2160
+ sessionPermissions: ctx.sessionPermissions,
2161
+ sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
2162
+ runDeadlineMs: ctx.runDeadlineMs,
2163
+ isRoot: ctx.isRoot,
2164
+ allowRootStringInput: ctx.allowRootStringInput,
2165
+ });
2166
+ const raw = await outcome;
2167
+ ensureRunActive(ctx.runDeadlineMs, ctx.signal);
2168
+ return raw;
234
2169
  }
235
- if (isRoot) {
236
- if (typeof output === "string")
237
- return output;
238
- return JSON.stringify(output);
2170
+ finally {
2171
+ if (timeoutId !== undefined)
2172
+ clearTimeout(timeoutId);
2173
+ clearAndTerminate();
239
2174
  }
240
- throw new Error(`Deck ${deck.path} requires outputSchema (non-root)`);
241
2175
  }
242
- async function runComputeDeck(ctx) {
2176
+ async function runComputeDeckInProcess(ctx) {
243
2177
  const { deck, runId } = ctx;
244
2178
  const actionCallId = randomId("action");
2179
+ let computeState = ctx.state
2180
+ ? {
2181
+ ...ctx.state,
2182
+ messages: Array.isArray(ctx.state.messages)
2183
+ ? ctx.state.messages.map(sanitizeMessage)
2184
+ : [],
2185
+ meta: ctx.state.meta ? { ...ctx.state.meta } : undefined,
2186
+ messageRefs: Array.isArray(ctx.state.messageRefs)
2187
+ ? [...ctx.state.messageRefs]
2188
+ : undefined,
2189
+ }
2190
+ : undefined;
2191
+ const ensureComputeState = () => {
2192
+ if (computeState)
2193
+ return computeState;
2194
+ computeState = {
2195
+ runId,
2196
+ messages: [],
2197
+ meta: {},
2198
+ messageRefs: [],
2199
+ };
2200
+ return computeState;
2201
+ };
2202
+ const publishComputeState = () => {
2203
+ if (!computeState)
2204
+ return;
2205
+ ctx.onStateUpdate?.({
2206
+ ...computeState,
2207
+ messages: computeState.messages.map(sanitizeMessage),
2208
+ meta: computeState.meta ? { ...computeState.meta } : undefined,
2209
+ messageRefs: Array.isArray(computeState.messageRefs)
2210
+ ? [...computeState.messageRefs]
2211
+ : undefined,
2212
+ });
2213
+ };
245
2214
  const execContext = {
246
2215
  runId,
247
2216
  actionCallId,
248
2217
  parentActionCallId: ctx.parentActionCallId,
249
2218
  depth: ctx.depth,
250
2219
  input: ctx.input,
2220
+ initialUserMessage: ctx.initialUserMessage,
2221
+ getSessionMeta: (key) => {
2222
+ if (!key)
2223
+ return undefined;
2224
+ return computeState?.meta?.[key];
2225
+ },
2226
+ setSessionMeta: (key, value) => {
2227
+ if (!key)
2228
+ return;
2229
+ const state = ensureComputeState();
2230
+ const nextMeta = { ...(state.meta ?? {}) };
2231
+ if (value === undefined) {
2232
+ delete nextMeta[key];
2233
+ }
2234
+ else {
2235
+ nextMeta[key] = value;
2236
+ }
2237
+ state.meta = nextMeta;
2238
+ publishComputeState();
2239
+ },
2240
+ appendMessage: (message) => {
2241
+ const role = message.role;
2242
+ const content = String(message.content ?? "");
2243
+ if ((role !== "user" && role !== "assistant") || !content.trim()) {
2244
+ return;
2245
+ }
2246
+ const state = ensureComputeState();
2247
+ const sanitized = sanitizeMessage({ role, content: content.trim() });
2248
+ state.messages = [...(state.messages ?? []), sanitized];
2249
+ const refs = Array.isArray(state.messageRefs)
2250
+ ? [...state.messageRefs]
2251
+ : [];
2252
+ refs.push({ id: randomId("msg"), role: sanitized.role });
2253
+ state.messageRefs = refs;
2254
+ publishComputeState();
2255
+ },
251
2256
  label: deck.label,
252
2257
  log: (entry) => {
253
2258
  if (!ctx.trace)
@@ -278,9 +2283,13 @@ async function runComputeDeck(ctx) {
278
2283
  });
279
2284
  },
280
2285
  spawnAndWait: async (opts) => {
2286
+ ensureRunActive(ctx.runDeadlineMs, ctx.signal);
281
2287
  const childPath = path.isAbsolute(opts.path)
282
2288
  ? opts.path
283
2289
  : path.resolve(path.dirname(deck.path), opts.path);
2290
+ const childInitialUserMessage = Object.hasOwn(opts, "initialUserMessage")
2291
+ ? opts.initialUserMessage
2292
+ : ctx.initialUserMessage;
284
2293
  return await runDeck({
285
2294
  path: childPath,
286
2295
  input: opts.input,
@@ -294,11 +2303,33 @@ async function runComputeDeck(ctx) {
294
2303
  modelOverride: ctx.modelOverride,
295
2304
  trace: ctx.trace,
296
2305
  stream: ctx.stream,
297
- state: ctx.state,
298
- onStateUpdate: ctx.onStateUpdate,
2306
+ state: computeState,
2307
+ onStateUpdate: (state) => {
2308
+ computeState = {
2309
+ ...state,
2310
+ messages: Array.isArray(state.messages)
2311
+ ? state.messages.map(sanitizeMessage)
2312
+ : [],
2313
+ meta: state.meta ? { ...state.meta } : undefined,
2314
+ messageRefs: Array.isArray(state.messageRefs)
2315
+ ? [...state.messageRefs]
2316
+ : undefined,
2317
+ };
2318
+ ctx.onStateUpdate?.(state);
2319
+ },
299
2320
  onStreamText: ctx.onStreamText,
300
- initialUserMessage: undefined,
2321
+ responsesMode: ctx.responsesMode,
2322
+ initialUserMessage: childInitialUserMessage,
301
2323
  inputProvided: true,
2324
+ parentPermissions: ctx.permissions,
2325
+ workspacePermissions: ctx.workspacePermissions,
2326
+ workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
2327
+ sessionPermissions: ctx.sessionPermissions,
2328
+ sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
2329
+ runDeadlineMs: ctx.runDeadlineMs,
2330
+ workerSandbox: ctx.workerSandbox,
2331
+ signal: ctx.signal,
2332
+ onTool: ctx.onTool,
302
2333
  });
303
2334
  },
304
2335
  fail: (opts) => {
@@ -306,7 +2337,9 @@ async function runComputeDeck(ctx) {
306
2337
  },
307
2338
  return: (payload) => Promise.resolve(payload),
308
2339
  };
2340
+ ensureRunActive(ctx.runDeadlineMs, ctx.signal);
309
2341
  const raw = await deck.executor(execContext);
2342
+ ensureRunActive(ctx.runDeadlineMs, ctx.signal);
310
2343
  return validateOutput(deck, raw, ctx.depth === 0);
311
2344
  }
312
2345
  async function runLlmDeck(ctx) {
@@ -314,13 +2347,17 @@ async function runLlmDeck(ctx) {
314
2347
  const actionCallId = randomId("action");
315
2348
  const start = performance.now();
316
2349
  const respondEnabled = Boolean(deck.respond);
2350
+ const useResponses = Boolean(ctx.responsesMode) ||
2351
+ ctx.state?.format === "responses";
317
2352
  const systemPrompt = buildSystemPrompt(deck);
318
2353
  const refToolCallId = randomId("call");
319
- const messages = ctx.state?.messages
2354
+ const messages = ctx.state?.messages?.length
320
2355
  ? ctx.state.messages.map(sanitizeMessage)
321
- : [];
2356
+ : ctx.state?.items?.length
2357
+ ? messagesFromResponseItems(ctx.state.items).map(sanitizeMessage)
2358
+ : [];
322
2359
  const resumed = messages.length > 0;
323
- const sendInit = Boolean(inputProvided) && input !== undefined && !resumed;
2360
+ const sendContext = Boolean(inputProvided) && input !== undefined && !resumed;
324
2361
  const idleController = createIdleController({
325
2362
  cfg: deck.handlers?.onIdle,
326
2363
  deck,
@@ -335,11 +2372,21 @@ async function runLlmDeck(ctx) {
335
2372
  stream: ctx.stream,
336
2373
  onStreamText: ctx.onStreamText,
337
2374
  pushMessages: (msgs) => messages.push(...msgs.map(sanitizeMessage)),
2375
+ responsesMode: ctx.responsesMode,
2376
+ permissions: ctx.permissions,
2377
+ workspacePermissions: ctx.workspacePermissions,
2378
+ workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
2379
+ sessionPermissions: ctx.sessionPermissions,
2380
+ sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
2381
+ runDeadlineMs: ctx.runDeadlineMs,
2382
+ workerSandbox: ctx.workerSandbox,
2383
+ signal: ctx.signal,
2384
+ onTool: ctx.onTool,
338
2385
  });
339
2386
  let streamingBuffer = "";
340
2387
  let streamingCommitted = false;
341
2388
  const wrappedOnStreamText = (chunk) => {
342
- if (!chunk)
2389
+ if (!chunk || ctx.signal?.aborted)
343
2390
  return;
344
2391
  idleController.touch();
345
2392
  streamingBuffer += chunk;
@@ -347,13 +2394,14 @@ async function runLlmDeck(ctx) {
347
2394
  };
348
2395
  if (!resumed) {
349
2396
  messages.push(sanitizeMessage({ role: "system", content: systemPrompt }));
350
- if (sendInit) {
2397
+ if (sendContext) {
351
2398
  ctx.trace?.({
352
2399
  type: "tool.call",
353
2400
  runId,
354
2401
  actionCallId: refToolCallId,
355
- name: constants_js_1.GAMBIT_TOOL_INIT,
2402
+ name: constants_js_1.GAMBIT_TOOL_CONTEXT,
356
2403
  args: {},
2404
+ toolKind: "internal",
357
2405
  parentActionCallId: actionCallId,
358
2406
  });
359
2407
  messages.push(sanitizeMessage({
@@ -363,13 +2411,13 @@ async function runLlmDeck(ctx) {
363
2411
  id: refToolCallId,
364
2412
  type: "function",
365
2413
  function: {
366
- name: constants_js_1.GAMBIT_TOOL_INIT,
2414
+ name: constants_js_1.GAMBIT_TOOL_CONTEXT,
367
2415
  arguments: "{}",
368
2416
  },
369
2417
  }],
370
2418
  }), sanitizeMessage({
371
2419
  role: "tool",
372
- name: constants_js_1.GAMBIT_TOOL_INIT,
2420
+ name: constants_js_1.GAMBIT_TOOL_CONTEXT,
373
2421
  tool_call_id: refToolCallId,
374
2422
  content: JSON.stringify(input),
375
2423
  }));
@@ -377,8 +2425,9 @@ async function runLlmDeck(ctx) {
377
2425
  type: "tool.result",
378
2426
  runId,
379
2427
  actionCallId: refToolCallId,
380
- name: constants_js_1.GAMBIT_TOOL_INIT,
2428
+ name: constants_js_1.GAMBIT_TOOL_CONTEXT,
381
2429
  result: input,
2430
+ toolKind: "internal",
382
2431
  parentActionCallId: actionCallId,
383
2432
  });
384
2433
  }
@@ -399,29 +2448,36 @@ async function runLlmDeck(ctx) {
399
2448
  });
400
2449
  }
401
2450
  idleController.touch();
402
- const tools = await buildToolDefs(deck);
2451
+ const tools = await buildToolDefs(deck, ctx.permissions);
403
2452
  ctx.trace?.({
404
2453
  type: "deck.start",
405
2454
  runId,
406
2455
  deckPath: deck.path,
407
2456
  actionCallId,
408
2457
  parentActionCallId: ctx.parentActionCallId,
2458
+ permissions: ctx.permissionsTrace,
409
2459
  });
410
2460
  let passes = 0;
411
2461
  try {
412
2462
  while (passes < guardrails.maxPasses) {
413
2463
  passes++;
414
- if (performance.now() - start > guardrails.timeoutMs) {
415
- throw new Error("Timeout exceeded");
416
- }
2464
+ ensureRunActive(ctx.runDeadlineMs, ctx.signal);
417
2465
  streamingBuffer = "";
418
2466
  streamingCommitted = false;
419
- const model = ctx.modelOverride ??
2467
+ const modelCandidate = ctx.modelOverride ??
420
2468
  deck.modelParams?.model ??
421
2469
  ctx.defaultModel ??
422
2470
  (() => {
423
2471
  throw new Error(`No model configured for deck ${deck.path} and no --model provided`);
424
2472
  })();
2473
+ const resolved = await resolveModelChoice({
2474
+ model: modelCandidate,
2475
+ params: toProviderParams(deck.modelParams),
2476
+ modelProvider,
2477
+ deckPath: deck.path,
2478
+ });
2479
+ const model = resolved.model;
2480
+ const providerParams = resolved.params;
425
2481
  const stateMessages = ctx.state?.messages?.length;
426
2482
  ctx.trace?.({
427
2483
  type: "model.call",
@@ -435,21 +2491,134 @@ async function runLlmDeck(ctx) {
435
2491
  messages: messages.map(sanitizeMessage),
436
2492
  tools,
437
2493
  stateMessages,
438
- parentActionCallId: ctx.parentActionCallId,
439
- });
440
- const result = await modelProvider.chat({
441
- model,
442
- messages,
443
- tools,
444
- stream: ctx.stream,
445
- state: ctx.state,
446
- params: toProviderParams(deck.modelParams),
447
- onStreamText: (ctx.onStreamText || deck.handlers?.onIdle)
448
- ? wrappedOnStreamText
2494
+ mode: useResponses ? "responses" : "chat",
2495
+ responseItems: useResponses
2496
+ ? responseItemsFromMessages(messages)
449
2497
  : undefined,
2498
+ parentActionCallId: ctx.parentActionCallId,
450
2499
  });
2500
+ let responseOutputItems;
2501
+ const responses = modelProvider.responses;
2502
+ const projectedToolCalls = new Set();
2503
+ const projectedToolResults = new Set();
2504
+ const projectedToolNames = new Map();
2505
+ const result = (useResponses && responses)
2506
+ ? await (async () => {
2507
+ const responseItems = responseItemsFromMessages(messages);
2508
+ let sawDelta = false;
2509
+ const response = await responses({
2510
+ request: {
2511
+ model,
2512
+ input: responseItems,
2513
+ tools: tools,
2514
+ stream: ctx.stream,
2515
+ params: providerParams,
2516
+ },
2517
+ state: ctx.state,
2518
+ deckPath: deck.path,
2519
+ signal: ctx.signal,
2520
+ onStreamEvent: (ctx.trace || ctx.onStreamText || deck.handlers?.onIdle)
2521
+ ? (event) => {
2522
+ if (ctx.trace) {
2523
+ const streamEvent = event;
2524
+ const handledAsResponse = traceOpenResponsesStreamEvent({
2525
+ streamEvent,
2526
+ runId,
2527
+ actionCallId,
2528
+ deckPath: deck.path,
2529
+ model,
2530
+ parentActionCallId: ctx.parentActionCallId,
2531
+ trace: ctx.trace,
2532
+ });
2533
+ if (!handledAsResponse) {
2534
+ ctx.trace({
2535
+ type: "model.stream.event",
2536
+ runId,
2537
+ actionCallId,
2538
+ deckPath: deck.path,
2539
+ model,
2540
+ event: streamEvent,
2541
+ parentActionCallId: ctx.parentActionCallId,
2542
+ });
2543
+ }
2544
+ projectStreamToolTraceEvents({
2545
+ streamEvent,
2546
+ runId,
2547
+ parentActionCallId: actionCallId,
2548
+ trace: ctx.trace,
2549
+ emittedCalls: projectedToolCalls,
2550
+ emittedResults: projectedToolResults,
2551
+ toolNames: projectedToolNames,
2552
+ });
2553
+ }
2554
+ if (event.type === "response.output_text.delta") {
2555
+ sawDelta = true;
2556
+ wrappedOnStreamText(event.delta);
2557
+ }
2558
+ else if (event.type === "response.output_text.done" && !sawDelta) {
2559
+ wrappedOnStreamText(event.text);
2560
+ }
2561
+ }
2562
+ : undefined,
2563
+ });
2564
+ responseOutputItems = response.output ?? [];
2565
+ const mapped = mapResponseOutput(responseOutputItems);
2566
+ return {
2567
+ message: mapped.message,
2568
+ finishReason: mapped.toolCalls?.length ? "tool_calls" : "stop",
2569
+ toolCalls: mapped.toolCalls,
2570
+ usage: response.usage,
2571
+ updatedState: response.updatedState,
2572
+ };
2573
+ })()
2574
+ : await modelProvider.chat({
2575
+ model,
2576
+ messages,
2577
+ tools,
2578
+ stream: ctx.stream,
2579
+ state: ctx.state,
2580
+ deckPath: deck.path,
2581
+ signal: ctx.signal,
2582
+ params: providerParams,
2583
+ onStreamText: (ctx.onStreamText || deck.handlers?.onIdle)
2584
+ ? wrappedOnStreamText
2585
+ : undefined,
2586
+ onStreamEvent: ctx.trace
2587
+ ? (event) => {
2588
+ const handledAsResponse = traceOpenResponsesStreamEvent({
2589
+ streamEvent: event,
2590
+ runId,
2591
+ actionCallId,
2592
+ deckPath: deck.path,
2593
+ model,
2594
+ parentActionCallId: ctx.parentActionCallId,
2595
+ trace: ctx.trace,
2596
+ });
2597
+ if (!handledAsResponse) {
2598
+ ctx.trace?.({
2599
+ type: "model.stream.event",
2600
+ runId,
2601
+ actionCallId,
2602
+ deckPath: deck.path,
2603
+ model,
2604
+ event,
2605
+ parentActionCallId: ctx.parentActionCallId,
2606
+ });
2607
+ }
2608
+ projectStreamToolTraceEvents({
2609
+ streamEvent: event,
2610
+ runId,
2611
+ parentActionCallId: actionCallId,
2612
+ trace: ctx.trace,
2613
+ emittedCalls: projectedToolCalls,
2614
+ emittedResults: projectedToolResults,
2615
+ toolNames: projectedToolNames,
2616
+ });
2617
+ }
2618
+ : undefined,
2619
+ });
451
2620
  idleController.touch();
452
- const message = result.message;
2621
+ let message = result.message;
453
2622
  ctx.trace?.({
454
2623
  type: "model.result",
455
2624
  runId,
@@ -460,6 +2629,9 @@ async function runLlmDeck(ctx) {
460
2629
  message: sanitizeMessage(message),
461
2630
  toolCalls: result.toolCalls,
462
2631
  stateMessages: result.updatedState?.messages?.length,
2632
+ usage: result.usage,
2633
+ mode: useResponses ? "responses" : "chat",
2634
+ responseItems: responseOutputItems,
463
2635
  parentActionCallId: ctx.parentActionCallId,
464
2636
  });
465
2637
  const computeState = (updated) => {
@@ -468,17 +2640,31 @@ async function runLlmDeck(ctx) {
468
2640
  const mergedMessages = base.messages && base.messages.length > 0
469
2641
  ? base.messages.map(sanitizeMessage)
470
2642
  : messages.map(sanitizeMessage);
2643
+ const responseItems = useResponses
2644
+ ? responseItemsFromMessages(mergedMessages)
2645
+ : updated?.items ?? ctx.state?.items;
471
2646
  const priorRefs = updated?.messageRefs ?? ctx.state?.messageRefs ?? [];
472
2647
  const messageRefs = mergedMessages.map((m, idx) => priorRefs[idx] ?? { id: randomId("msg"), role: m.role });
473
2648
  const feedback = updated?.feedback ?? ctx.state?.feedback;
474
2649
  const traces = updated?.traces ?? ctx.state?.traces;
2650
+ const meta = updated?.meta ?? ctx.state?.meta;
2651
+ const notes = updated?.notes ?? ctx.state?.notes;
2652
+ const conversationScore = updated?.conversationScore ??
2653
+ ctx.state?.conversationScore;
475
2654
  return {
476
2655
  ...base,
477
2656
  runId,
478
2657
  messages: mergedMessages,
2658
+ format: useResponses
2659
+ ? "responses"
2660
+ : updated?.format ?? ctx.state?.format,
2661
+ items: responseItems,
479
2662
  messageRefs,
480
2663
  feedback,
481
2664
  traces,
2665
+ meta,
2666
+ notes,
2667
+ conversationScore,
482
2668
  };
483
2669
  };
484
2670
  if (result.toolCalls && result.toolCalls.length > 0) {
@@ -486,8 +2672,10 @@ async function runLlmDeck(ctx) {
486
2672
  let respondValue;
487
2673
  let endSignal;
488
2674
  const appendedMessages = [];
489
- if (!streamingCommitted && streamingBuffer) {
490
- messages.push(sanitizeMessage({ role: "assistant", content: streamingBuffer }));
2675
+ const toolCallText = streamingBuffer ||
2676
+ (typeof message.content === "string" ? message.content : "");
2677
+ if (!streamingCommitted && toolCallText) {
2678
+ messages.push(sanitizeMessage({ role: "assistant", content: toolCallText }));
491
2679
  streamingCommitted = true;
492
2680
  }
493
2681
  for (const call of result.toolCalls) {
@@ -525,6 +2713,7 @@ async function runLlmDeck(ctx) {
525
2713
  actionCallId: call.id,
526
2714
  name: call.name,
527
2715
  args: call.args,
2716
+ toolKind: "internal",
528
2717
  parentActionCallId: actionCallId,
529
2718
  });
530
2719
  const toolContent = JSON.stringify(call.args ?? {});
@@ -554,6 +2743,7 @@ async function runLlmDeck(ctx) {
554
2743
  actionCallId: call.id,
555
2744
  name: call.name,
556
2745
  result: respondEnvelope,
2746
+ toolKind: "internal",
557
2747
  parentActionCallId: actionCallId,
558
2748
  });
559
2749
  continue;
@@ -580,6 +2770,7 @@ async function runLlmDeck(ctx) {
580
2770
  actionCallId: call.id,
581
2771
  name: call.name,
582
2772
  args: call.args,
2773
+ toolKind: "internal",
583
2774
  parentActionCallId: actionCallId,
584
2775
  });
585
2776
  const toolContent = JSON.stringify(call.args ?? {});
@@ -619,10 +2810,23 @@ async function runLlmDeck(ctx) {
619
2810
  actionCallId: call.id,
620
2811
  name: call.name,
621
2812
  result: signal,
2813
+ toolKind: "internal",
622
2814
  parentActionCallId: actionCallId,
623
2815
  });
624
2816
  continue;
625
2817
  }
2818
+ const actionRef = deck.actionDecks.find((a) => a.name === call.name);
2819
+ const toolKind = actionRef ? "action" : "external";
2820
+ const actionPermissions = (0, permissions_js_1.resolveEffectivePermissions)({
2821
+ baseDir: path.dirname(deck.path),
2822
+ parent: ctx.permissions,
2823
+ reference: actionRef?.permissions
2824
+ ? {
2825
+ baseDir: path.dirname(deck.path),
2826
+ permissions: actionRef.permissions,
2827
+ }
2828
+ : undefined,
2829
+ });
626
2830
  ctx.trace?.({
627
2831
  type: "action.start",
628
2832
  runId,
@@ -630,6 +2834,7 @@ async function runLlmDeck(ctx) {
630
2834
  name: call.name,
631
2835
  path: call.name,
632
2836
  parentActionCallId: actionCallId,
2837
+ permissions: actionPermissions.trace,
633
2838
  });
634
2839
  ctx.trace?.({
635
2840
  type: "tool.call",
@@ -637,6 +2842,7 @@ async function runLlmDeck(ctx) {
637
2842
  actionCallId: call.id,
638
2843
  name: call.name,
639
2844
  args: call.args,
2845
+ toolKind,
640
2846
  parentActionCallId: actionCallId,
641
2847
  });
642
2848
  const toolResult = await handleToolCall(call, {
@@ -655,6 +2861,16 @@ async function runLlmDeck(ctx) {
655
2861
  runStartedAt: start,
656
2862
  inputProvided: true,
657
2863
  idle: idleController,
2864
+ responsesMode: ctx.responsesMode,
2865
+ permissions: ctx.permissions,
2866
+ workspacePermissions: ctx.workspacePermissions,
2867
+ workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
2868
+ sessionPermissions: ctx.sessionPermissions,
2869
+ sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
2870
+ runDeadlineMs: ctx.runDeadlineMs,
2871
+ workerSandbox: ctx.workerSandbox,
2872
+ signal: ctx.signal,
2873
+ onTool: ctx.onTool,
658
2874
  });
659
2875
  ctx.trace?.({
660
2876
  type: "tool.result",
@@ -662,6 +2878,7 @@ async function runLlmDeck(ctx) {
662
2878
  actionCallId: call.id,
663
2879
  name: call.name,
664
2880
  result: toolResult.toolContent,
2881
+ toolKind,
665
2882
  parentActionCallId: actionCallId,
666
2883
  });
667
2884
  appendedMessages.push({
@@ -699,6 +2916,7 @@ async function runLlmDeck(ctx) {
699
2916
  idleController.touch();
700
2917
  }
701
2918
  if (ctx.onStateUpdate) {
2919
+ ensureRunActive(ctx.runDeadlineMs, ctx.signal);
702
2920
  const state = computeState(result.updatedState);
703
2921
  ctx.onStateUpdate(state);
704
2922
  }
@@ -724,6 +2942,12 @@ async function runLlmDeck(ctx) {
724
2942
  }
725
2943
  continue;
726
2944
  }
2945
+ if (!respondEnabled &&
2946
+ result.finishReason === "stop" &&
2947
+ (message.content === null || message.content === undefined) &&
2948
+ (!result.toolCalls || result.toolCalls.length === 0)) {
2949
+ message = { ...message, content: "" };
2950
+ }
727
2951
  if (result.finishReason === "tool_calls") {
728
2952
  throw new Error("Model requested tool_calls but provided none");
729
2953
  }
@@ -733,6 +2957,7 @@ async function runLlmDeck(ctx) {
733
2957
  }
734
2958
  if (message.content !== null && message.content !== undefined) {
735
2959
  messages.push(sanitizeMessage(message));
2960
+ ensureRunActive(ctx.runDeadlineMs, ctx.signal);
736
2961
  if (ctx.onStateUpdate) {
737
2962
  const state = computeState(result.updatedState);
738
2963
  ctx.onStateUpdate(state);
@@ -775,23 +3000,11 @@ async function runLlmDeck(ctx) {
775
3000
  throw new Error("Model did not complete within guardrails");
776
3001
  }
777
3002
  async function handleToolCall(call, ctx) {
778
- const action = ctx.parentDeck.actionDecks.find((a) => a.name === call.name);
3003
+ ensureRunActive(ctx.runDeadlineMs, ctx.signal);
779
3004
  const source = {
780
3005
  deckPath: ctx.parentDeck.path,
781
- actionName: action?.name ?? call.name,
3006
+ actionName: call.name,
782
3007
  };
783
- if (!action) {
784
- return {
785
- toolContent: JSON.stringify({
786
- runId: ctx.runId,
787
- actionCallId: call.id,
788
- parentActionCallId: ctx.parentActionCallId,
789
- source,
790
- status: 404,
791
- message: "unknown action",
792
- }),
793
- };
794
- }
795
3008
  const baseComplete = (payload) => JSON.stringify({
796
3009
  runId: ctx.runId,
797
3010
  actionCallId: call.id,
@@ -805,6 +3018,480 @@ async function handleToolCall(call, ctx) {
805
3018
  });
806
3019
  const extraMessages = [];
807
3020
  const started = performance.now();
3021
+ const runBuiltinTool = async () => {
3022
+ if (!isBuiltinTool(call.name))
3023
+ return null;
3024
+ const deny = (message) => ({
3025
+ toolContent: baseComplete({
3026
+ status: 403,
3027
+ code: "permission_denied",
3028
+ message,
3029
+ }),
3030
+ });
3031
+ if (call.name === BUILTIN_TOOL_READ_FILE) {
3032
+ let targetPath;
3033
+ try {
3034
+ targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
3035
+ }
3036
+ catch (err) {
3037
+ return {
3038
+ toolContent: baseComplete({
3039
+ status: 400,
3040
+ code: "invalid_input",
3041
+ message: err instanceof Error ? err.message : String(err),
3042
+ }),
3043
+ };
3044
+ }
3045
+ if (!(0, permissions_js_1.canReadPath)(ctx.permissions, targetPath)) {
3046
+ return deny(`read_file denied for ${targetPath}`);
3047
+ }
3048
+ const text = await dntShim.Deno.readTextFile(targetPath);
3049
+ const lines = text.split(/\r?\n/);
3050
+ const { startLine, endLine } = parseLineRange(call.args);
3051
+ const sliced = lines.slice(startLine - 1, endLine).join("\n");
3052
+ return {
3053
+ toolContent: baseComplete({
3054
+ status: 200,
3055
+ payload: {
3056
+ path: targetPath,
3057
+ start_line: startLine,
3058
+ end_line: endLine,
3059
+ total_lines: lines.length,
3060
+ content: sliced,
3061
+ },
3062
+ }),
3063
+ };
3064
+ }
3065
+ if (call.name === BUILTIN_TOOL_LIST_DIR) {
3066
+ let targetPath;
3067
+ try {
3068
+ targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
3069
+ }
3070
+ catch (err) {
3071
+ return {
3072
+ toolContent: baseComplete({
3073
+ status: 400,
3074
+ code: "invalid_input",
3075
+ message: err instanceof Error ? err.message : String(err),
3076
+ }),
3077
+ };
3078
+ }
3079
+ if (!(0, permissions_js_1.canReadPath)(ctx.permissions, targetPath)) {
3080
+ return deny(`list_dir denied for ${targetPath}`);
3081
+ }
3082
+ const recursive = Boolean(call.args.recursive);
3083
+ const maxEntries = parseToolLimit(call.args.max_entries, 200, 2000);
3084
+ const out = [];
3085
+ const pending = [targetPath];
3086
+ while (pending.length > 0 && out.length < maxEntries) {
3087
+ const current = pending.pop();
3088
+ for await (const entry of dntShim.Deno.readDir(current)) {
3089
+ if (out.length >= maxEntries)
3090
+ break;
3091
+ const entryPath = path.join(current, entry.name);
3092
+ if (!(0, permissions_js_1.canReadPath)(ctx.permissions, entryPath))
3093
+ continue;
3094
+ const type = entry.isDirectory
3095
+ ? "dir"
3096
+ : entry.isSymlink
3097
+ ? "symlink"
3098
+ : "file";
3099
+ out.push({ path: entryPath, type });
3100
+ if (recursive && entry.isDirectory) {
3101
+ pending.push(entryPath);
3102
+ }
3103
+ }
3104
+ }
3105
+ return {
3106
+ toolContent: baseComplete({
3107
+ status: 200,
3108
+ payload: {
3109
+ path: targetPath,
3110
+ recursive,
3111
+ entries: out,
3112
+ truncated: out.length >= maxEntries,
3113
+ },
3114
+ }),
3115
+ };
3116
+ }
3117
+ if (call.name === BUILTIN_TOOL_GREP_FILES) {
3118
+ let targetPath;
3119
+ try {
3120
+ targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
3121
+ }
3122
+ catch (err) {
3123
+ return {
3124
+ toolContent: baseComplete({
3125
+ status: 400,
3126
+ code: "invalid_input",
3127
+ message: err instanceof Error ? err.message : String(err),
3128
+ }),
3129
+ };
3130
+ }
3131
+ if (!(0, permissions_js_1.canReadPath)(ctx.permissions, targetPath)) {
3132
+ return deny(`grep_files denied for ${targetPath}`);
3133
+ }
3134
+ const query = typeof call.args.query === "string" ? call.args.query : "";
3135
+ if (!query) {
3136
+ return {
3137
+ toolContent: baseComplete({
3138
+ status: 400,
3139
+ code: "invalid_input",
3140
+ message: "query is required",
3141
+ }),
3142
+ };
3143
+ }
3144
+ let re;
3145
+ try {
3146
+ re = new RegExp(query, "g");
3147
+ }
3148
+ catch (err) {
3149
+ return {
3150
+ toolContent: baseComplete({
3151
+ status: 400,
3152
+ code: "invalid_regex",
3153
+ message: err instanceof Error ? err.message : String(err),
3154
+ }),
3155
+ };
3156
+ }
3157
+ const maxMatches = parseToolLimit(call.args.max_matches, 200, 2000);
3158
+ const matches = [];
3159
+ const pending = [targetPath];
3160
+ while (pending.length > 0 && matches.length < maxMatches) {
3161
+ const current = pending.pop();
3162
+ const stat = await dntShim.Deno.stat(current);
3163
+ if (stat.isDirectory) {
3164
+ for await (const entry of dntShim.Deno.readDir(current)) {
3165
+ const entryPath = path.join(current, entry.name);
3166
+ if (!(0, permissions_js_1.canReadPath)(ctx.permissions, entryPath))
3167
+ continue;
3168
+ if (entry.isDirectory) {
3169
+ pending.push(entryPath);
3170
+ continue;
3171
+ }
3172
+ if (!entry.isFile)
3173
+ continue;
3174
+ const text = await dntShim.Deno.readTextFile(entryPath).catch(() => null);
3175
+ if (text === null)
3176
+ continue;
3177
+ const lines = text.split(/\r?\n/);
3178
+ for (let i = 0; i < lines.length; i++) {
3179
+ re.lastIndex = 0;
3180
+ if (!re.test(lines[i]))
3181
+ continue;
3182
+ matches.push({ path: entryPath, line: i + 1, text: lines[i] });
3183
+ if (matches.length >= maxMatches)
3184
+ break;
3185
+ }
3186
+ if (matches.length >= maxMatches)
3187
+ break;
3188
+ }
3189
+ continue;
3190
+ }
3191
+ if (!stat.isFile)
3192
+ continue;
3193
+ const text = await dntShim.Deno.readTextFile(current).catch(() => null);
3194
+ if (text === null)
3195
+ continue;
3196
+ const lines = text.split(/\r?\n/);
3197
+ for (let i = 0; i < lines.length; i++) {
3198
+ re.lastIndex = 0;
3199
+ if (!re.test(lines[i]))
3200
+ continue;
3201
+ matches.push({ path: current, line: i + 1, text: lines[i] });
3202
+ if (matches.length >= maxMatches)
3203
+ break;
3204
+ }
3205
+ }
3206
+ return {
3207
+ toolContent: baseComplete({
3208
+ status: 200,
3209
+ payload: {
3210
+ path: targetPath,
3211
+ query,
3212
+ matches,
3213
+ truncated: matches.length >= maxMatches,
3214
+ },
3215
+ }),
3216
+ };
3217
+ }
3218
+ if (call.name === BUILTIN_TOOL_APPLY_PATCH) {
3219
+ let targetPath;
3220
+ try {
3221
+ targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
3222
+ }
3223
+ catch (err) {
3224
+ return {
3225
+ toolContent: baseComplete({
3226
+ status: 400,
3227
+ code: "invalid_input",
3228
+ message: err instanceof Error ? err.message : String(err),
3229
+ }),
3230
+ };
3231
+ }
3232
+ if (!(0, permissions_js_1.canWritePath)(ctx.permissions, targetPath)) {
3233
+ return deny(`apply_patch denied for ${targetPath}`);
3234
+ }
3235
+ const rawEdits = Array.isArray(call.args.edits) ? call.args.edits : [];
3236
+ const edits = rawEdits.flatMap((entry) => {
3237
+ if (!entry || typeof entry !== "object")
3238
+ return [];
3239
+ const rec = entry;
3240
+ if (typeof rec.old_text !== "string" || typeof rec.new_text !== "string") {
3241
+ return [];
3242
+ }
3243
+ return [{
3244
+ oldText: rec.old_text,
3245
+ newText: rec.new_text,
3246
+ replaceAll: Boolean(rec.replace_all),
3247
+ }];
3248
+ });
3249
+ if (edits.length === 0) {
3250
+ return {
3251
+ toolContent: baseComplete({
3252
+ status: 400,
3253
+ code: "invalid_input",
3254
+ message: "edits must include at least one old_text/new_text pair",
3255
+ }),
3256
+ };
3257
+ }
3258
+ const createIfMissing = Boolean(call.args.create_if_missing);
3259
+ let existing = "";
3260
+ let created = false;
3261
+ try {
3262
+ if (!(0, permissions_js_1.canReadPath)(ctx.permissions, targetPath)) {
3263
+ return deny(`apply_patch read denied for ${targetPath}`);
3264
+ }
3265
+ existing = await dntShim.Deno.readTextFile(targetPath);
3266
+ }
3267
+ catch (err) {
3268
+ if (err instanceof dntShim.Deno.errors.NotFound) {
3269
+ if (!createIfMissing) {
3270
+ return {
3271
+ toolContent: baseComplete({
3272
+ status: 404,
3273
+ code: "not_found",
3274
+ message: `file not found: ${targetPath}`,
3275
+ }),
3276
+ };
3277
+ }
3278
+ created = true;
3279
+ existing = "";
3280
+ }
3281
+ else {
3282
+ throw err;
3283
+ }
3284
+ }
3285
+ const patched = applySimplePatch(existing, edits);
3286
+ if (!created && patched.applied === 0) {
3287
+ return {
3288
+ toolContent: baseComplete({
3289
+ status: 409,
3290
+ code: "no_changes",
3291
+ message: `No edit targets were found in ${targetPath}`,
3292
+ }),
3293
+ };
3294
+ }
3295
+ if (created) {
3296
+ const parentDir = path.dirname(targetPath);
3297
+ if (parentDir && parentDir !== "." && parentDir !== targetPath) {
3298
+ await dntShim.Deno.mkdir(parentDir, { recursive: true });
3299
+ }
3300
+ }
3301
+ try {
3302
+ await dntShim.Deno.writeTextFile(targetPath, patched.next);
3303
+ }
3304
+ catch (err) {
3305
+ if (err instanceof dntShim.Deno.errors.NotFound) {
3306
+ return {
3307
+ toolContent: baseComplete({
3308
+ status: 404,
3309
+ code: "not_found",
3310
+ message: `path not found: ${targetPath}`,
3311
+ }),
3312
+ };
3313
+ }
3314
+ return {
3315
+ toolContent: baseComplete({
3316
+ status: 500,
3317
+ code: "write_failed",
3318
+ message: err instanceof Error ? err.message : String(err),
3319
+ }),
3320
+ };
3321
+ }
3322
+ return {
3323
+ toolContent: baseComplete({
3324
+ status: 200,
3325
+ payload: {
3326
+ path: targetPath,
3327
+ applied: patched.applied,
3328
+ created,
3329
+ },
3330
+ }),
3331
+ };
3332
+ }
3333
+ if (call.name === BUILTIN_TOOL_EXEC) {
3334
+ const command = typeof call.args.command === "string"
3335
+ ? call.args.command
3336
+ : "";
3337
+ if (!command) {
3338
+ return {
3339
+ toolContent: baseComplete({
3340
+ status: 400,
3341
+ code: "invalid_input",
3342
+ message: "command is required",
3343
+ }),
3344
+ };
3345
+ }
3346
+ if (!(0, permissions_js_1.canRunCommand)(ctx.permissions, command) &&
3347
+ !(0, permissions_js_1.canRunPath)(ctx.permissions, command)) {
3348
+ return deny(`exec denied for command ${command}`);
3349
+ }
3350
+ const args = toStringArray(call.args.args);
3351
+ const cwd = typeof call.args.cwd === "string"
3352
+ ? path.resolve(ctx.permissions.baseDir, call.args.cwd)
3353
+ : ctx.permissions.baseDir;
3354
+ const timeoutMs = parseToolLimit(call.args.timeout_ms, 5000, 30000);
3355
+ const remainingMs = Math.max(1, Math.min(timeoutMs, Math.floor(ctx.runDeadlineMs - performance.now())));
3356
+ const controller = new AbortController();
3357
+ const onAbort = () => controller.abort();
3358
+ if (ctx.signal?.aborted) {
3359
+ controller.abort();
3360
+ }
3361
+ else if (ctx.signal) {
3362
+ ctx.signal.addEventListener("abort", onAbort, { once: true });
3363
+ }
3364
+ const timeoutId = setTimeout(() => controller.abort(), remainingMs);
3365
+ try {
3366
+ const output = await (0, runtime_exec_host_js_1.executeBuiltinCommand)({
3367
+ command,
3368
+ args,
3369
+ cwd,
3370
+ signal: controller.signal,
3371
+ });
3372
+ const stdout = new TextDecoder().decode(output.stdout).slice(0, 65536);
3373
+ const stderr = new TextDecoder().decode(output.stderr).slice(0, 65536);
3374
+ return {
3375
+ toolContent: baseComplete({
3376
+ status: 200,
3377
+ payload: {
3378
+ command,
3379
+ args,
3380
+ cwd,
3381
+ code: output.code,
3382
+ success: output.success,
3383
+ stdout,
3384
+ stderr,
3385
+ },
3386
+ }),
3387
+ };
3388
+ }
3389
+ catch (err) {
3390
+ if (err instanceof runtime_exec_host_js_1.ExecToolUnsupportedHostError) {
3391
+ return {
3392
+ toolContent: baseComplete({
3393
+ status: 501,
3394
+ code: err.code,
3395
+ message: err.message,
3396
+ }),
3397
+ };
3398
+ }
3399
+ return {
3400
+ toolContent: baseComplete({
3401
+ status: 500,
3402
+ code: "exec_failed",
3403
+ message: err instanceof Error ? err.message : String(err),
3404
+ }),
3405
+ };
3406
+ }
3407
+ finally {
3408
+ clearTimeout(timeoutId);
3409
+ if (ctx.signal) {
3410
+ ctx.signal.removeEventListener("abort", onAbort);
3411
+ }
3412
+ }
3413
+ }
3414
+ return null;
3415
+ };
3416
+ const builtinResult = await runBuiltinTool();
3417
+ if (builtinResult) {
3418
+ return builtinResult;
3419
+ }
3420
+ const action = ctx.parentDeck.actionDecks.find((a) => a.name === call.name);
3421
+ if (!action) {
3422
+ const externalTool = ctx.parentDeck.tools.find((tool) => tool.name === call.name);
3423
+ if (!externalTool) {
3424
+ return {
3425
+ toolContent: JSON.stringify({
3426
+ runId: ctx.runId,
3427
+ actionCallId: call.id,
3428
+ parentActionCallId: ctx.parentActionCallId,
3429
+ source,
3430
+ status: 404,
3431
+ message: "unknown action",
3432
+ }),
3433
+ };
3434
+ }
3435
+ let externalInput = call.args;
3436
+ if (externalTool.inputSchema) {
3437
+ try {
3438
+ externalInput = (0, schema_js_1.validateWithSchema)(externalTool.inputSchema, call.args);
3439
+ }
3440
+ catch (err) {
3441
+ return {
3442
+ toolContent: baseComplete({
3443
+ status: 400,
3444
+ code: "invalid_input",
3445
+ message: err instanceof Error ? err.message : String(err),
3446
+ }),
3447
+ };
3448
+ }
3449
+ }
3450
+ if (!ctx.onTool) {
3451
+ return {
3452
+ toolContent: baseComplete({
3453
+ status: 500,
3454
+ code: "missing_on_tool",
3455
+ message: `External tool ${call.name} requires runtime onTool handler`,
3456
+ }),
3457
+ };
3458
+ }
3459
+ try {
3460
+ const result = await ctx.onTool({
3461
+ name: call.name,
3462
+ args: externalInput,
3463
+ runId: ctx.runId,
3464
+ actionCallId: call.id,
3465
+ parentActionCallId: ctx.parentActionCallId,
3466
+ deckPath: ctx.parentDeck.path,
3467
+ });
3468
+ return { toolContent: baseComplete(normalizeChildResult(result)) };
3469
+ }
3470
+ catch (err) {
3471
+ return {
3472
+ toolContent: baseComplete({
3473
+ status: 500,
3474
+ code: "tool_handler_error",
3475
+ message: err instanceof Error ? err.message : String(err),
3476
+ }),
3477
+ };
3478
+ }
3479
+ }
3480
+ let actionInput = call.args;
3481
+ if (action.contextSchema) {
3482
+ try {
3483
+ actionInput = (0, schema_js_1.validateWithSchema)(action.contextSchema, call.args);
3484
+ }
3485
+ catch (err) {
3486
+ return {
3487
+ toolContent: baseComplete({
3488
+ status: 400,
3489
+ code: "invalid_input",
3490
+ message: err instanceof Error ? err.message : String(err),
3491
+ }),
3492
+ };
3493
+ }
3494
+ }
808
3495
  const busyCfg = ctx.parentDeck.handlers?.onBusy ??
809
3496
  ctx.parentDeck.handlers?.onInterval;
810
3497
  const busyDelay = busyCfg?.delayMs ?? constants_js_1.DEFAULT_STATUS_DELAY_MS;
@@ -818,7 +3505,7 @@ async function handleToolCall(call, ctx) {
818
3505
  try {
819
3506
  const result = await runDeck({
820
3507
  path: action.path,
821
- input: call.args,
3508
+ input: actionInput,
822
3509
  modelProvider: ctx.modelProvider,
823
3510
  isRoot: false,
824
3511
  guardrails: ctx.guardrails,
@@ -830,7 +3517,19 @@ async function handleToolCall(call, ctx) {
830
3517
  trace: ctx.trace,
831
3518
  stream: ctx.stream,
832
3519
  onStreamText: ctx.onStreamText,
3520
+ responsesMode: ctx.responsesMode,
833
3521
  initialUserMessage: undefined,
3522
+ parentPermissions: ctx.permissions,
3523
+ referencePermissions: action.permissions,
3524
+ referencePermissionsBaseDir: path.dirname(ctx.parentDeck.path),
3525
+ workspacePermissions: ctx.workspacePermissions,
3526
+ workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
3527
+ sessionPermissions: ctx.sessionPermissions,
3528
+ sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
3529
+ runDeadlineMs: ctx.runDeadlineMs,
3530
+ workerSandbox: ctx.workerSandbox,
3531
+ signal: ctx.signal,
3532
+ onTool: ctx.onTool,
834
3533
  });
835
3534
  return { ok: true, result };
836
3535
  }
@@ -862,7 +3561,17 @@ async function handleToolCall(call, ctx) {
862
3561
  trace: ctx.trace,
863
3562
  stream: ctx.stream,
864
3563
  onStreamText: ctx.onStreamText,
3564
+ responsesMode: ctx.responsesMode,
865
3565
  initialUserMessage: undefined,
3566
+ permissions: ctx.permissions,
3567
+ workspacePermissions: ctx.workspacePermissions,
3568
+ workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
3569
+ sessionPermissions: ctx.sessionPermissions,
3570
+ sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
3571
+ runDeadlineMs: ctx.runDeadlineMs,
3572
+ workerSandbox: ctx.workerSandbox,
3573
+ signal: ctx.signal,
3574
+ onTool: ctx.onTool,
866
3575
  });
867
3576
  if (envelope.length) {
868
3577
  extraMessages.push(...envelope.map(sanitizeMessage));
@@ -920,6 +3629,9 @@ async function handleToolCall(call, ctx) {
920
3629
  throw childResult.error;
921
3630
  }
922
3631
  const normalized = normalizeChildResult(childResult.result);
3632
+ if (action.responseSchema) {
3633
+ normalized.payload = (0, schema_js_1.validateWithSchema)(action.responseSchema, normalized.payload);
3634
+ }
923
3635
  const toolContent = baseComplete(normalized);
924
3636
  if (busyCfg?.path) {
925
3637
  const elapsedFromAction = performance.now() - started;
@@ -941,7 +3653,17 @@ async function handleToolCall(call, ctx) {
941
3653
  trace: ctx.trace,
942
3654
  stream: ctx.stream,
943
3655
  onStreamText: ctx.onStreamText,
3656
+ responsesMode: ctx.responsesMode,
944
3657
  initialUserMessage: undefined,
3658
+ permissions: ctx.permissions,
3659
+ workspacePermissions: ctx.workspacePermissions,
3660
+ workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
3661
+ sessionPermissions: ctx.sessionPermissions,
3662
+ sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
3663
+ runDeadlineMs: ctx.runDeadlineMs,
3664
+ workerSandbox: ctx.workerSandbox,
3665
+ signal: ctx.signal,
3666
+ onTool: ctx.onTool,
945
3667
  });
946
3668
  if (envelope.length) {
947
3669
  extraMessages.push(...envelope.map(sanitizeMessage));
@@ -991,6 +3713,7 @@ function normalizeChildResult(result) {
991
3713
  }
992
3714
  async function runBusyHandler(args) {
993
3715
  try {
3716
+ ensureRunActive(args.runDeadlineMs, args.signal);
994
3717
  const input = {
995
3718
  kind: "busy",
996
3719
  label: args.action.label ?? args.parentDeck.label,
@@ -1015,8 +3738,18 @@ async function runBusyHandler(args) {
1015
3738
  trace: args.trace,
1016
3739
  stream: args.stream,
1017
3740
  onStreamText: args.onStreamText,
3741
+ responsesMode: args.responsesMode,
1018
3742
  initialUserMessage: args.initialUserMessage,
1019
3743
  inputProvided: true,
3744
+ parentPermissions: args.permissions,
3745
+ workspacePermissions: args.workspacePermissions,
3746
+ workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
3747
+ sessionPermissions: args.sessionPermissions,
3748
+ sessionPermissionsBaseDir: args.sessionPermissionsBaseDir,
3749
+ runDeadlineMs: args.runDeadlineMs,
3750
+ workerSandbox: args.workerSandbox,
3751
+ signal: args.signal,
3752
+ onTool: args.onTool,
1020
3753
  });
1021
3754
  const elapsedMs = Math.floor(args.elapsedMs);
1022
3755
  let message;
@@ -1033,7 +3766,7 @@ async function runBusyHandler(args) {
1033
3766
  }
1034
3767
  if (!message)
1035
3768
  return [];
1036
- if (args.onStreamText) {
3769
+ if (args.onStreamText && !args.signal?.aborted) {
1037
3770
  args.onStreamText(`${message}\n`);
1038
3771
  }
1039
3772
  else {
@@ -1093,6 +3826,16 @@ function createIdleController(args) {
1093
3826
  trace: args.trace,
1094
3827
  stream: args.stream,
1095
3828
  onStreamText: args.onStreamText,
3829
+ responsesMode: args.responsesMode,
3830
+ permissions: args.permissions,
3831
+ workspacePermissions: args.workspacePermissions,
3832
+ workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
3833
+ sessionPermissions: args.sessionPermissions,
3834
+ sessionPermissionsBaseDir: args.sessionPermissionsBaseDir,
3835
+ runDeadlineMs: args.runDeadlineMs,
3836
+ workerSandbox: args.workerSandbox,
3837
+ signal: args.signal,
3838
+ onTool: args.onTool,
1096
3839
  });
1097
3840
  if (envelope.length)
1098
3841
  args.pushMessages(envelope.map(sanitizeMessage));
@@ -1132,6 +3875,7 @@ function createIdleController(args) {
1132
3875
  }
1133
3876
  async function runIdleHandler(args) {
1134
3877
  try {
3878
+ ensureRunActive(args.runDeadlineMs, args.signal);
1135
3879
  const input = {
1136
3880
  kind: "idle",
1137
3881
  label: args.deck.label,
@@ -1155,8 +3899,18 @@ async function runIdleHandler(args) {
1155
3899
  trace: args.trace,
1156
3900
  stream: args.stream,
1157
3901
  onStreamText: args.onStreamText,
3902
+ responsesMode: args.responsesMode,
1158
3903
  initialUserMessage: undefined,
1159
3904
  inputProvided: true,
3905
+ parentPermissions: args.permissions,
3906
+ workspacePermissions: args.workspacePermissions,
3907
+ workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
3908
+ sessionPermissions: args.sessionPermissions,
3909
+ sessionPermissionsBaseDir: args.sessionPermissionsBaseDir,
3910
+ runDeadlineMs: args.runDeadlineMs,
3911
+ workerSandbox: args.workerSandbox,
3912
+ signal: args.signal,
3913
+ onTool: args.onTool,
1160
3914
  });
1161
3915
  const elapsedMs = Math.floor(args.elapsedMs);
1162
3916
  let message;
@@ -1173,7 +3927,7 @@ async function runIdleHandler(args) {
1173
3927
  }
1174
3928
  if (!message)
1175
3929
  return [];
1176
- if (args.onStreamText) {
3930
+ if (args.onStreamText && !args.signal?.aborted) {
1177
3931
  args.onStreamText(`${message}\n`);
1178
3932
  }
1179
3933
  else {
@@ -1192,6 +3946,7 @@ async function maybeHandleError(args) {
1192
3946
  const handlerPath = args.ctx.parentDeck.handlers?.onError?.path;
1193
3947
  if (!handlerPath)
1194
3948
  return undefined;
3949
+ ensureRunActive(args.ctx.runDeadlineMs, args.ctx.signal);
1195
3950
  const message = args.err instanceof Error
1196
3951
  ? args.err.message
1197
3952
  : String(args.err);
@@ -1220,8 +3975,18 @@ async function maybeHandleError(args) {
1220
3975
  trace: args.ctx.trace,
1221
3976
  stream: args.ctx.stream,
1222
3977
  onStreamText: args.ctx.onStreamText,
3978
+ responsesMode: args.ctx.responsesMode,
1223
3979
  initialUserMessage: undefined,
1224
3980
  inputProvided: true,
3981
+ parentPermissions: args.ctx.permissions,
3982
+ workspacePermissions: args.ctx.workspacePermissions,
3983
+ workspacePermissionsBaseDir: args.ctx.workspacePermissionsBaseDir,
3984
+ sessionPermissions: args.ctx.sessionPermissions,
3985
+ sessionPermissionsBaseDir: args.ctx.sessionPermissionsBaseDir,
3986
+ runDeadlineMs: args.ctx.runDeadlineMs,
3987
+ workerSandbox: args.ctx.workerSandbox,
3988
+ signal: args.ctx.signal,
3989
+ onTool: args.ctx.onTool,
1225
3990
  });
1226
3991
  const parsed = typeof handlerOutput === "object" && handlerOutput !== null
1227
3992
  ? handlerOutput
@@ -1345,8 +4110,173 @@ function sanitizeMessage(msg) {
1345
4110
  : undefined;
1346
4111
  return { ...msg, tool_calls: toolCalls };
1347
4112
  }
1348
- async function buildToolDefs(deck) {
4113
+ function resolveToolPath(baseDir, rawPath) {
4114
+ if (typeof rawPath !== "string" || rawPath.trim().length === 0) {
4115
+ throw new Error("path is required");
4116
+ }
4117
+ return path.resolve(baseDir, rawPath);
4118
+ }
4119
+ function parseLineRange(args) {
4120
+ const startLine = Number.isInteger(args.start_line)
4121
+ ? Math.max(1, Number(args.start_line))
4122
+ : 1;
4123
+ const endLine = Number.isInteger(args.end_line)
4124
+ ? Math.max(startLine, Number(args.end_line))
4125
+ : startLine + 399;
4126
+ return { startLine, endLine };
4127
+ }
4128
+ function parseToolLimit(value, fallback, max) {
4129
+ if (!Number.isInteger(value))
4130
+ return fallback;
4131
+ return Math.min(max, Math.max(1, Number(value)));
4132
+ }
4133
+ function toStringArray(value) {
4134
+ if (!Array.isArray(value))
4135
+ return [];
4136
+ return value.filter((entry) => typeof entry === "string");
4137
+ }
4138
+ function hasAnyScope(scope) {
4139
+ return scope.all || scope.values.size > 0;
4140
+ }
4141
+ function hasAnyRunScope(scope) {
4142
+ return scope.all || scope.paths.size > 0 || scope.commands.size > 0;
4143
+ }
4144
+ function isBuiltinTool(name) {
4145
+ return BUILTIN_TOOL_NAMES.has(name);
4146
+ }
4147
+ function applySimplePatch(content, edits) {
4148
+ let next = content;
4149
+ let applied = 0;
4150
+ for (const edit of edits) {
4151
+ const oldText = edit.oldText ?? "";
4152
+ const newText = edit.newText ?? "";
4153
+ if (!oldText)
4154
+ continue;
4155
+ if (edit.replaceAll) {
4156
+ if (!next.includes(oldText))
4157
+ continue;
4158
+ next = next.split(oldText).join(newText);
4159
+ applied++;
4160
+ continue;
4161
+ }
4162
+ const idx = next.indexOf(oldText);
4163
+ if (idx === -1)
4164
+ continue;
4165
+ next = `${next.slice(0, idx)}${newText}${next.slice(idx + oldText.length)}`;
4166
+ applied++;
4167
+ }
4168
+ return { next, applied };
4169
+ }
4170
+ async function buildToolDefs(deck, permissions) {
1349
4171
  const defs = [];
4172
+ const addBuiltinTools = () => {
4173
+ if (hasAnyScope(permissions.read)) {
4174
+ defs.push({
4175
+ type: "function",
4176
+ function: {
4177
+ name: BUILTIN_TOOL_READ_FILE,
4178
+ description: "Read a UTF-8 text file.",
4179
+ parameters: {
4180
+ type: "object",
4181
+ properties: {
4182
+ path: { type: "string" },
4183
+ start_line: { type: "number" },
4184
+ end_line: { type: "number" },
4185
+ },
4186
+ required: ["path"],
4187
+ additionalProperties: false,
4188
+ },
4189
+ },
4190
+ }, {
4191
+ type: "function",
4192
+ function: {
4193
+ name: BUILTIN_TOOL_LIST_DIR,
4194
+ description: "List directory entries.",
4195
+ parameters: {
4196
+ type: "object",
4197
+ properties: {
4198
+ path: { type: "string" },
4199
+ recursive: { type: "boolean" },
4200
+ max_entries: { type: "number" },
4201
+ },
4202
+ required: ["path"],
4203
+ additionalProperties: false,
4204
+ },
4205
+ },
4206
+ }, {
4207
+ type: "function",
4208
+ function: {
4209
+ name: BUILTIN_TOOL_GREP_FILES,
4210
+ description: "Search text files using a regular expression.",
4211
+ parameters: {
4212
+ type: "object",
4213
+ properties: {
4214
+ path: { type: "string" },
4215
+ query: { type: "string" },
4216
+ max_matches: { type: "number" },
4217
+ },
4218
+ required: ["path", "query"],
4219
+ additionalProperties: false,
4220
+ },
4221
+ },
4222
+ });
4223
+ }
4224
+ if (hasAnyScope(permissions.write)) {
4225
+ defs.push({
4226
+ type: "function",
4227
+ function: {
4228
+ name: BUILTIN_TOOL_APPLY_PATCH,
4229
+ description: "Apply text replacements to a file using old/new edit pairs.",
4230
+ parameters: {
4231
+ type: "object",
4232
+ properties: {
4233
+ path: { type: "string" },
4234
+ create_if_missing: { type: "boolean" },
4235
+ edits: {
4236
+ type: "array",
4237
+ items: {
4238
+ type: "object",
4239
+ properties: {
4240
+ old_text: { type: "string" },
4241
+ new_text: { type: "string" },
4242
+ replace_all: { type: "boolean" },
4243
+ },
4244
+ required: ["old_text", "new_text"],
4245
+ additionalProperties: false,
4246
+ },
4247
+ },
4248
+ },
4249
+ required: ["path", "edits"],
4250
+ additionalProperties: false,
4251
+ },
4252
+ },
4253
+ });
4254
+ }
4255
+ if (hasAnyRunScope(permissions.run)) {
4256
+ defs.push({
4257
+ type: "function",
4258
+ function: {
4259
+ name: BUILTIN_TOOL_EXEC,
4260
+ description: "Run an allowed command with optional args.",
4261
+ parameters: {
4262
+ type: "object",
4263
+ properties: {
4264
+ command: { type: "string" },
4265
+ args: {
4266
+ type: "array",
4267
+ items: { type: "string" },
4268
+ },
4269
+ cwd: { type: "string" },
4270
+ timeout_ms: { type: "number" },
4271
+ },
4272
+ required: ["command"],
4273
+ additionalProperties: false,
4274
+ },
4275
+ },
4276
+ });
4277
+ }
4278
+ };
4279
+ addBuiltinTools();
1350
4280
  if (deck.allowEnd) {
1351
4281
  defs.push({
1352
4282
  type: "function",
@@ -1388,9 +4318,15 @@ async function buildToolDefs(deck) {
1388
4318
  });
1389
4319
  }
1390
4320
  for (const action of deck.actionDecks) {
1391
- const child = await (0, loader_js_1.loadDeck)(action.path, deck.path);
1392
- ensureSchemaPresence(child, false);
1393
- const schema = child.inputSchema;
4321
+ if (isBuiltinTool(action.name)) {
4322
+ throw new Error(`Action name ${action.name} conflicts with a built-in tool name`);
4323
+ }
4324
+ let schema = action.contextSchema;
4325
+ if (!schema) {
4326
+ const child = await (0, loader_js_1.loadDeck)(action.path, deck.path);
4327
+ ensureSchemaPresence(child, false);
4328
+ schema = resolveContextSchema(child);
4329
+ }
1394
4330
  const params = (0, schema_js_1.toJsonSchema)(schema);
1395
4331
  defs.push({
1396
4332
  type: "function",
@@ -1401,5 +4337,23 @@ async function buildToolDefs(deck) {
1401
4337
  },
1402
4338
  });
1403
4339
  }
4340
+ const actionNames = new Set(deck.actionDecks.map((action) => action.name));
4341
+ for (const external of deck.tools) {
4342
+ if (actionNames.has(external.name))
4343
+ continue;
4344
+ if (isBuiltinTool(external.name)) {
4345
+ throw new Error(`External tool name ${external.name} conflicts with a built-in tool name`);
4346
+ }
4347
+ defs.push({
4348
+ type: "function",
4349
+ function: {
4350
+ name: external.name,
4351
+ description: external.description,
4352
+ parameters: external.inputSchema
4353
+ ? (0, schema_js_1.toJsonSchema)(external.inputSchema)
4354
+ : { type: "object", additionalProperties: true },
4355
+ },
4356
+ });
4357
+ }
1404
4358
  return defs;
1405
4359
  }