la-machina-engine 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -38,23 +38,23 @@ var init_cjs_shims = __esm({
38
38
  });
39
39
 
40
40
  // src/orchestrator/types.ts
41
- var import_zod23, PlanStepSchema, PlanSchema;
41
+ var import_zod25, PlanStepSchema, PlanSchema;
42
42
  var init_types = __esm({
43
43
  "src/orchestrator/types.ts"() {
44
44
  "use strict";
45
45
  init_cjs_shims();
46
- import_zod23 = require("zod");
47
- PlanStepSchema = import_zod23.z.object({
48
- id: import_zod23.z.string().min(1),
49
- description: import_zod23.z.string().min(1),
50
- action: import_zod23.z.enum(["research", "implement", "verify", "review", "custom"]),
51
- files: import_zod23.z.array(import_zod23.z.string()).optional(),
52
- spec: import_zod23.z.string().optional(),
53
- dependsOn: import_zod23.z.array(import_zod23.z.string()).optional()
46
+ import_zod25 = require("zod");
47
+ PlanStepSchema = import_zod25.z.object({
48
+ id: import_zod25.z.string().min(1),
49
+ description: import_zod25.z.string().min(1),
50
+ action: import_zod25.z.enum(["research", "implement", "verify", "review", "custom"]),
51
+ files: import_zod25.z.array(import_zod25.z.string()).optional(),
52
+ spec: import_zod25.z.string().optional(),
53
+ dependsOn: import_zod25.z.array(import_zod25.z.string()).optional()
54
54
  });
55
- PlanSchema = import_zod23.z.object({
56
- summary: import_zod23.z.string().min(1),
57
- steps: import_zod23.z.array(PlanStepSchema).min(1)
55
+ PlanSchema = import_zod25.z.object({
56
+ summary: import_zod25.z.string().min(1),
57
+ steps: import_zod25.z.array(PlanStepSchema).min(1)
58
58
  });
59
59
  }
60
60
  });
@@ -785,6 +785,8 @@ __export(index_exports, {
785
785
  buildSystemPrompt: () => buildSystemPrompt,
786
786
  buildWorkerAgent: () => buildWorkerAgent,
787
787
  canSpawnProcesses: () => canSpawnProcesses,
788
+ capabilityStub: () => capabilityStub,
789
+ createApiCallTool: () => createApiCallTool,
788
790
  createLogger: () => createLogger,
789
791
  createModelAdapter: () => createModelAdapter,
790
792
  createSendMessageTool: () => createSendMessageTool,
@@ -816,7 +818,8 @@ __export(index_exports, {
816
818
  synthesizeSpec: () => synthesizeSpec,
817
819
  toResponse: () => toResponse,
818
820
  tryParseJSON: () => tryParseJSON,
819
- validateOutput: () => validateOutput
821
+ validateOutput: () => validateOutput,
822
+ withCapabilityCheck: () => withCapabilityCheck
820
823
  });
821
824
  module.exports = __toCommonJS(index_exports);
822
825
  init_cjs_shims();
@@ -1110,6 +1113,42 @@ var LoggingConfigResolved = import_zod.z.object({
1110
1113
  level: LogLevelEnum,
1111
1114
  sink: LogSinkSchema
1112
1115
  }).strict();
1116
+ var ApiHttpMethodEnum = import_zod.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]);
1117
+ var ApiAuthSchema = import_zod.z.discriminatedUnion("type", [
1118
+ import_zod.z.object({ type: import_zod.z.literal("none") }).strict(),
1119
+ import_zod.z.object({ type: import_zod.z.literal("bearer"), tokenRef: import_zod.z.string().min(1) }).strict(),
1120
+ import_zod.z.object({
1121
+ type: import_zod.z.literal("header"),
1122
+ name: import_zod.z.string().min(1),
1123
+ valueRef: import_zod.z.string().min(1)
1124
+ }).strict(),
1125
+ import_zod.z.object({
1126
+ type: import_zod.z.literal("basic"),
1127
+ userRef: import_zod.z.string().min(1),
1128
+ passRef: import_zod.z.string().min(1)
1129
+ }).strict(),
1130
+ import_zod.z.object({ type: import_zod.z.literal("custom"), id: import_zod.z.string().min(1) }).strict()
1131
+ ]);
1132
+ var ApiServiceSchema = import_zod.z.object({
1133
+ name: import_zod.z.string().min(1),
1134
+ description: import_zod.z.string().optional(),
1135
+ baseUrl: import_zod.z.string().url(),
1136
+ auth: ApiAuthSchema.optional(),
1137
+ // Allow both strings and RegExp values via z.union — RegExp is
1138
+ // serialized as its source when cloned, so we accept both.
1139
+ allowedPaths: import_zod.z.array(import_zod.z.union([import_zod.z.string(), import_zod.z.instanceof(RegExp)])).optional(),
1140
+ allowedMethods: import_zod.z.array(ApiHttpMethodEnum).optional(),
1141
+ defaultHeaders: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
1142
+ maxBodyBytes: import_zod.z.number().int().positive().optional()
1143
+ }).strict();
1144
+ var ApiConfigResolved = import_zod.z.object({
1145
+ services: import_zod.z.array(ApiServiceSchema),
1146
+ maxResponseBytes: import_zod.z.number().int().positive().optional()
1147
+ }).strict();
1148
+ var RunnerConfigResolved = import_zod.z.object({
1149
+ url: import_zod.z.string().url(),
1150
+ secret: import_zod.z.string().min(1, "runner.secret cannot be empty")
1151
+ }).strict();
1113
1152
  var ResolvedConfigSchema = import_zod.z.object({
1114
1153
  model: ModelConfigResolved,
1115
1154
  storage: StorageConfigResolved,
@@ -1125,7 +1164,9 @@ var ResolvedConfigSchema = import_zod.z.object({
1125
1164
  permissions: PermissionsConfigResolved,
1126
1165
  compaction: CompactionConfigResolved,
1127
1166
  coordinator: CoordinatorConfigResolved,
1128
- orchestrator: OrchestratorConfigResolved
1167
+ orchestrator: OrchestratorConfigResolved,
1168
+ runner: RunnerConfigResolved.optional(),
1169
+ api: ApiConfigResolved.optional()
1129
1170
  }).strict();
1130
1171
  var R2ConfigUser = R2ConfigResolved.partial();
1131
1172
  var ModelConfigUser = ModelConfigResolved.partial();
@@ -1166,6 +1207,8 @@ var McpConfigUser = import_zod.z.object({
1166
1207
  shutdownTimeoutMs: import_zod.z.number().int().positive().optional()
1167
1208
  }).strict();
1168
1209
  var PermissionsConfigUser = PermissionsConfigResolved.partial();
1210
+ var RunnerConfigUser = RunnerConfigResolved;
1211
+ var ApiConfigUser = ApiConfigResolved.partial();
1169
1212
  var CompactionConfigUser = CompactionConfigResolved.partial();
1170
1213
  var CoordinatorConfigUser = CoordinatorConfigResolved.partial();
1171
1214
  var OrchestratorConfigUser = OrchestratorConfigResolved.deepPartial();
@@ -1184,7 +1227,9 @@ var UserConfigSchema = import_zod.z.object({
1184
1227
  permissions: PermissionsConfigUser.optional(),
1185
1228
  compaction: CompactionConfigUser.optional(),
1186
1229
  coordinator: CoordinatorConfigUser.optional(),
1187
- orchestrator: OrchestratorConfigUser.optional()
1230
+ orchestrator: OrchestratorConfigUser.optional(),
1231
+ runner: RunnerConfigUser.optional(),
1232
+ api: ApiConfigUser.optional()
1188
1233
  }).strict();
1189
1234
 
1190
1235
  // src/config/merge.ts
@@ -1229,10 +1274,36 @@ function deepMerge(base, override) {
1229
1274
  }
1230
1275
  return result;
1231
1276
  }
1277
+ var API_RUNTIME_KEYS = ["env", "resolveAuth", "onRequest", "onResponse"];
1278
+ function splitApiRuntime(user) {
1279
+ const api = user.api;
1280
+ if (api === void 0) return { stripped: user, runtime: {} };
1281
+ const runtime = {};
1282
+ const schemaSafe = {};
1283
+ for (const [k, v] of Object.entries(api)) {
1284
+ if (API_RUNTIME_KEYS.includes(k)) {
1285
+ runtime[k] = v;
1286
+ } else {
1287
+ schemaSafe[k] = v;
1288
+ }
1289
+ }
1290
+ const clone = { ...user, api: schemaSafe };
1291
+ return { stripped: clone, runtime };
1292
+ }
1232
1293
  function mergeConfig(user) {
1233
- const validatedUser = UserConfigSchema.parse(user);
1294
+ const { stripped, runtime } = splitApiRuntime(user);
1295
+ const validatedUser = UserConfigSchema.parse(stripped);
1234
1296
  const merged = deepMerge(DEFAULTS, validatedUser);
1235
- return ResolvedConfigSchema.parse(merged);
1297
+ const resolved = ResolvedConfigSchema.parse(merged);
1298
+ if (resolved.api !== void 0 && Object.keys(runtime).length > 0) {
1299
+ const mutableResolved = resolved;
1300
+ mutableResolved.api = {
1301
+ ...resolved.api,
1302
+ ...runtime
1303
+ };
1304
+ } else if (Object.keys(runtime).length > 0) {
1305
+ }
1306
+ return resolved;
1236
1307
  }
1237
1308
 
1238
1309
  // src/engine/engine.ts
@@ -1676,8 +1747,8 @@ function toAISdkMessages(messages) {
1676
1747
  textParts.push({ type: "text", text: block.text });
1677
1748
  }
1678
1749
  }
1679
- if (textParts.length > 0) out.push({ role: "user", content: textParts });
1680
1750
  if (toolResults.length > 0) out.push({ role: "tool", content: toolResults });
1751
+ if (textParts.length > 0) out.push({ role: "user", content: textParts });
1681
1752
  } else if (role === "assistant") {
1682
1753
  if (typeof content === "string") {
1683
1754
  out.push({ role: "assistant", content });
@@ -1875,6 +1946,68 @@ function hasProcessLifecycle() {
1875
1946
  return typeof process !== "undefined" && typeof process.on === "function" && typeof process.removeListener === "function" && detectRuntime() === "node";
1876
1947
  }
1877
1948
 
1949
+ // src/tools/capabilityStub.ts
1950
+ init_cjs_shims();
1951
+ var import_zod2 = require("zod");
1952
+
1953
+ // src/tools/contract.ts
1954
+ init_cjs_shims();
1955
+ function defineTool(tool) {
1956
+ return tool;
1957
+ }
1958
+ var ToolRegistry = class {
1959
+ tools = /* @__PURE__ */ new Map();
1960
+ register(tool) {
1961
+ if (typeof tool.name !== "string" || tool.name.length === 0) {
1962
+ throw new Error("ToolRegistry: tool.name must be a non-empty string");
1963
+ }
1964
+ if (this.tools.has(tool.name)) {
1965
+ throw new Error(`ToolRegistry: "${tool.name}" is already registered`);
1966
+ }
1967
+ this.tools.set(tool.name, tool);
1968
+ }
1969
+ registerAll(tools) {
1970
+ for (const tool of tools) this.register(tool);
1971
+ }
1972
+ unregister(name) {
1973
+ this.tools.delete(name);
1974
+ }
1975
+ get(name) {
1976
+ return this.tools.get(name);
1977
+ }
1978
+ has(name) {
1979
+ return this.tools.has(name);
1980
+ }
1981
+ list() {
1982
+ return Array.from(this.tools.values());
1983
+ }
1984
+ count() {
1985
+ return this.tools.size;
1986
+ }
1987
+ };
1988
+
1989
+ // src/tools/capabilityStub.ts
1990
+ var anyInput = import_zod2.z.unknown();
1991
+ function capabilityStub(original) {
1992
+ return defineTool({
1993
+ name: original.name,
1994
+ description: original.description,
1995
+ inputSchema: anyInput,
1996
+ isCapabilityStub: true,
1997
+ execute: async () => ({
1998
+ isError: true,
1999
+ content: `Tool "${original.name}" requires Node runtime and cannot execute in this environment. To use this tool, run async via engine.start() with config.runner configured so the run can hand off to a Node runner. See README \xA7"Runner contract" for setup.`,
2000
+ metadata: { capabilityMissing: original.name }
2001
+ })
2002
+ });
2003
+ }
2004
+ function withCapabilityCheck(tool, spawnAvailable) {
2005
+ if (tool.requiresNode === true && !spawnAvailable) {
2006
+ return capabilityStub(tool);
2007
+ }
2008
+ return tool;
2009
+ }
2010
+
1878
2011
  // src/subagent/registry.ts
1879
2012
  init_cjs_shims();
1880
2013
 
@@ -2057,7 +2190,7 @@ var SubagentRegistry = class _SubagentRegistry {
2057
2190
 
2058
2191
  // src/tools/agent.ts
2059
2192
  init_cjs_shims();
2060
- var import_zod5 = require("zod");
2193
+ var import_zod6 = require("zod");
2061
2194
 
2062
2195
  // src/subagent/runner.ts
2063
2196
  init_cjs_shims();
@@ -2068,48 +2201,49 @@ var import_zod_to_json_schema = require("zod-to-json-schema");
2068
2201
 
2069
2202
  // src/transcript/snapshot.ts
2070
2203
  init_cjs_shims();
2071
- var import_zod2 = require("zod");
2204
+ var import_zod3 = require("zod");
2072
2205
  var SNAPSHOT_FILENAME = "snapshot.json";
2073
- var TokenUsageSchema = import_zod2.z.object({
2074
- input: import_zod2.z.number().int().nonnegative(),
2075
- output: import_zod2.z.number().int().nonnegative(),
2076
- cacheCreationInput: import_zod2.z.number().int().nonnegative().optional(),
2077
- cacheReadInput: import_zod2.z.number().int().nonnegative().optional()
2206
+ var TokenUsageSchema = import_zod3.z.object({
2207
+ input: import_zod3.z.number().int().nonnegative(),
2208
+ output: import_zod3.z.number().int().nonnegative(),
2209
+ cacheCreationInput: import_zod3.z.number().int().nonnegative().optional(),
2210
+ cacheReadInput: import_zod3.z.number().int().nonnegative().optional()
2078
2211
  }).strict();
2079
- var PendingToolCallSchema = import_zod2.z.object({
2080
- toolName: import_zod2.z.string().min(1),
2081
- toolUseId: import_zod2.z.string().min(1),
2082
- input: import_zod2.z.unknown(),
2083
- calledAt: import_zod2.z.string().datetime({ offset: true })
2212
+ var PendingToolCallSchema = import_zod3.z.object({
2213
+ toolName: import_zod3.z.string().min(1),
2214
+ toolUseId: import_zod3.z.string().min(1),
2215
+ input: import_zod3.z.unknown(),
2216
+ calledAt: import_zod3.z.string().datetime({ offset: true })
2084
2217
  }).strict();
2085
- var PendingSubagentSchema = import_zod2.z.lazy(
2086
- () => import_zod2.z.object({
2087
- subagentType: import_zod2.z.string().min(1),
2088
- parentToolUseId: import_zod2.z.string().min(1),
2218
+ var PendingSubagentSchema = import_zod3.z.lazy(
2219
+ () => import_zod3.z.object({
2220
+ subagentType: import_zod3.z.string().min(1),
2221
+ parentToolUseId: import_zod3.z.string().min(1),
2089
2222
  childSnapshot: RunSnapshotSchema
2090
2223
  }).strict()
2091
2224
  );
2092
- var RunSnapshotSchema = import_zod2.z.lazy(
2093
- () => import_zod2.z.object({
2094
- version: import_zod2.z.literal(1),
2095
- status: import_zod2.z.literal("paused"),
2096
- runId: import_zod2.z.string().min(1),
2097
- nodeId: import_zod2.z.string().min(1),
2098
- pausedAt: import_zod2.z.string().datetime({ offset: true }),
2099
- pauseReason: import_zod2.z.enum([
2225
+ var RunSnapshotSchema = import_zod3.z.lazy(
2226
+ () => import_zod3.z.object({
2227
+ version: import_zod3.z.literal(1),
2228
+ status: import_zod3.z.literal("paused"),
2229
+ runId: import_zod3.z.string().min(1),
2230
+ nodeId: import_zod3.z.string().min(1),
2231
+ pausedAt: import_zod3.z.string().datetime({ offset: true }),
2232
+ pauseReason: import_zod3.z.enum([
2100
2233
  "gate_required",
2101
2234
  "subagent_gate_required",
2235
+ "handoff_to_runner",
2102
2236
  "max_turns",
2103
2237
  "explicit",
2104
2238
  "timeout"
2105
2239
  ]),
2106
- messageCount: import_zod2.z.number().int().nonnegative(),
2107
- lastShardIndex: import_zod2.z.number().int().nonnegative(),
2108
- lastMessageUuid: import_zod2.z.string().uuid(),
2240
+ messageCount: import_zod3.z.number().int().nonnegative(),
2241
+ lastShardIndex: import_zod3.z.number().int().nonnegative(),
2242
+ lastMessageUuid: import_zod3.z.string().uuid(),
2109
2243
  pendingToolCall: PendingToolCallSchema.optional(),
2110
2244
  pendingSubagent: PendingSubagentSchema.optional(),
2111
2245
  tokensUsedSoFar: TokenUsageSchema,
2112
- turnsUsed: import_zod2.z.number().int().nonnegative()
2246
+ turnsUsed: import_zod3.z.number().int().nonnegative()
2113
2247
  }).strict()
2114
2248
  );
2115
2249
  function snapshotPath(logPath) {
@@ -2837,6 +2971,27 @@ async function agentLoop(options) {
2837
2971
  }
2838
2972
  }
2839
2973
  }
2974
+ if (options.handoffToRunner === true) {
2975
+ for (const call of toolCallsToDispatch) {
2976
+ const tool = options.registry?.get(call.name);
2977
+ if (tool?.isCapabilityStub === true) {
2978
+ const paused = await pauseHere({
2979
+ ctx,
2980
+ transcript,
2981
+ reason: "handoff_to_runner",
2982
+ pendingToolCall: {
2983
+ toolName: call.name,
2984
+ toolUseId: call.id,
2985
+ input: call.input,
2986
+ calledAt: (/* @__PURE__ */ new Date()).toISOString()
2987
+ },
2988
+ storage: options.storage,
2989
+ subagentRegistry: options.subagentRegistry
2990
+ });
2991
+ return paused;
2992
+ }
2993
+ }
2994
+ }
2840
2995
  const firstTool = toolCallsToDispatch[0]?.name;
2841
2996
  await fireProgress("tool_dispatch", firstTool);
2842
2997
  const streamExec = new StreamingToolExecutor(executor);
@@ -2847,6 +3002,10 @@ async function agentLoop(options) {
2847
3002
  }
2848
3003
  try {
2849
3004
  for await (const { id, result } of streamExec.results()) {
3005
+ const missing = result.metadata?.capabilityMissing;
3006
+ if (typeof missing === "string") {
3007
+ ctx.recordCapabilityMissing(missing);
3008
+ }
2850
3009
  await ctx.addToolResult(id, truncateToolResult(result.content), result.isError === true);
2851
3010
  }
2852
3011
  } catch (err) {
@@ -3105,6 +3264,12 @@ var RunContext = class {
3105
3264
  turnCount = 0;
3106
3265
  tokensUsed = { input: 0, output: 0 };
3107
3266
  lastUuid = null;
3267
+ /**
3268
+ * Plan 019 — names of tools whose capability-stub returned an
3269
+ * `isError` result during this run. Aggregated and surfaced on the
3270
+ * response's `meta.capabilitiesMissing` (deduped, insertion-ordered).
3271
+ */
3272
+ capabilitiesMissing = /* @__PURE__ */ new Set();
3108
3273
  constructor(options) {
3109
3274
  this.runId = options.runId;
3110
3275
  this.nodeId = options.nodeId;
@@ -3145,6 +3310,42 @@ var RunContext = class {
3145
3310
  this.episodes.logTurn(this.turnCount, "assistant", summary);
3146
3311
  }
3147
3312
  }
3313
+ /**
3314
+ * Inject a tool_result + a follow-up text block as a SINGLE user
3315
+ * message. Used by the resume() synthetic-release path when a
3316
+ * paused run is resumed without an explicit `gateAnswer`: we need
3317
+ * to both satisfy the tool_use↔tool_result pairing AND give the
3318
+ * model a retry instruction, in the same user turn.
3319
+ *
3320
+ * Splitting this into `addToolResult(...) + addUserMessage(...)`
3321
+ * produces two consecutive user messages, which the AI SDK's
3322
+ * openai-compatible adapter rejects with
3323
+ * `MissingToolResultsError`. Combining them into one user message
3324
+ * is the portable shape that works on every provider we support.
3325
+ *
3326
+ * Writes a single `user` transcript entry carrying the mixed
3327
+ * content, so `rebuildMessagesFromEntries` reconstructs the same
3328
+ * single-message shape on resume.
3329
+ */
3330
+ async addMixedUserMessage(blocks) {
3331
+ this.messages.push({ role: "user", content: blocks });
3332
+ await this.writeEntry({
3333
+ type: "user",
3334
+ uuid: this.nextUuid(),
3335
+ parentUuid: this.lastUuid,
3336
+ ts: this.now(),
3337
+ message: { role: "user", content: blocks }
3338
+ });
3339
+ const summary = blocks.map((b) => {
3340
+ const x = b;
3341
+ if (x.type === "text") return x.text ?? "";
3342
+ if (x.type === "tool_result") {
3343
+ return typeof x.content === "string" ? x.content : JSON.stringify(x.content);
3344
+ }
3345
+ return "";
3346
+ }).filter((s) => s.length > 0).join("\n");
3347
+ this.episodes?.logTurn(this.turnCount, "user", summary);
3348
+ }
3148
3349
  /**
3149
3350
  * Append a tool result to the conversation. The Anthropic Messages
3150
3351
  * API requires tool_result blocks to be wrapped in a user message,
@@ -3190,6 +3391,14 @@ var RunContext = class {
3190
3391
  getTokensUsed() {
3191
3392
  return this.tokensUsed;
3192
3393
  }
3394
+ /** Plan 019 — record that a capability-stubbed tool fired during this run. */
3395
+ recordCapabilityMissing(toolName) {
3396
+ this.capabilitiesMissing.add(toolName);
3397
+ }
3398
+ /** Plan 019 — capability-stubbed tool names observed this run (deduped). */
3399
+ getCapabilitiesMissing() {
3400
+ return [...this.capabilitiesMissing];
3401
+ }
3193
3402
  shouldContinue() {
3194
3403
  return this.turnCount < this.maxTurns;
3195
3404
  }
@@ -3363,9 +3572,9 @@ init_cjs_shims();
3363
3572
 
3364
3573
  // src/transcript/entries.ts
3365
3574
  init_cjs_shims();
3366
- var import_zod3 = require("zod");
3367
- var UuidSchema = import_zod3.z.string().uuid();
3368
- var IsoTsSchema = import_zod3.z.string().datetime({ offset: true });
3575
+ var import_zod4 = require("zod");
3576
+ var UuidSchema = import_zod4.z.string().uuid();
3577
+ var IsoTsSchema = import_zod4.z.string().datetime({ offset: true });
3369
3578
  var TimelineBase = {
3370
3579
  uuid: UuidSchema,
3371
3580
  parentUuid: UuidSchema.nullable(),
@@ -3375,55 +3584,55 @@ var SessionBase = {
3375
3584
  uuid: UuidSchema,
3376
3585
  ts: IsoTsSchema
3377
3586
  };
3378
- var MessageSchema = import_zod3.z.object({
3379
- role: import_zod3.z.enum(["user", "assistant"]),
3380
- content: import_zod3.z.array(import_zod3.z.unknown())
3587
+ var MessageSchema = import_zod4.z.object({
3588
+ role: import_zod4.z.enum(["user", "assistant"]),
3589
+ content: import_zod4.z.array(import_zod4.z.unknown())
3381
3590
  }).strict();
3382
- var UserEntrySchema = import_zod3.z.object({
3383
- type: import_zod3.z.literal("user"),
3591
+ var UserEntrySchema = import_zod4.z.object({
3592
+ type: import_zod4.z.literal("user"),
3384
3593
  ...TimelineBase,
3385
3594
  message: MessageSchema
3386
3595
  }).strict();
3387
- var AssistantEntrySchema = import_zod3.z.object({
3388
- type: import_zod3.z.literal("assistant"),
3596
+ var AssistantEntrySchema = import_zod4.z.object({
3597
+ type: import_zod4.z.literal("assistant"),
3389
3598
  ...TimelineBase,
3390
3599
  message: MessageSchema
3391
3600
  }).strict();
3392
- var ToolResultEntrySchema = import_zod3.z.object({
3393
- type: import_zod3.z.literal("tool_result"),
3601
+ var ToolResultEntrySchema = import_zod4.z.object({
3602
+ type: import_zod4.z.literal("tool_result"),
3394
3603
  ...TimelineBase,
3395
- toolUseId: import_zod3.z.string().min(1),
3396
- content: import_zod3.z.unknown(),
3397
- isError: import_zod3.z.boolean().optional()
3604
+ toolUseId: import_zod4.z.string().min(1),
3605
+ content: import_zod4.z.unknown(),
3606
+ isError: import_zod4.z.boolean().optional()
3398
3607
  }).strict();
3399
- var SubagentSpawnEntrySchema = import_zod3.z.object({
3400
- type: import_zod3.z.literal("subagent_spawn"),
3608
+ var SubagentSpawnEntrySchema = import_zod4.z.object({
3609
+ type: import_zod4.z.literal("subagent_spawn"),
3401
3610
  ...TimelineBase,
3402
- agentId: import_zod3.z.string().min(1),
3403
- agentType: import_zod3.z.string().min(1)
3611
+ agentId: import_zod4.z.string().min(1),
3612
+ agentType: import_zod4.z.string().min(1)
3404
3613
  }).strict();
3405
- var SubagentDoneEntrySchema = import_zod3.z.object({
3406
- type: import_zod3.z.literal("subagent_done"),
3614
+ var SubagentDoneEntrySchema = import_zod4.z.object({
3615
+ type: import_zod4.z.literal("subagent_done"),
3407
3616
  ...TimelineBase,
3408
- agentId: import_zod3.z.string().min(1),
3409
- output: import_zod3.z.string()
3617
+ agentId: import_zod4.z.string().min(1),
3618
+ output: import_zod4.z.string()
3410
3619
  }).strict();
3411
- var MetaEntrySchema = import_zod3.z.object({
3412
- type: import_zod3.z.literal("meta"),
3620
+ var MetaEntrySchema = import_zod4.z.object({
3621
+ type: import_zod4.z.literal("meta"),
3413
3622
  ...SessionBase,
3414
- key: import_zod3.z.string().min(1),
3415
- value: import_zod3.z.unknown()
3623
+ key: import_zod4.z.string().min(1),
3624
+ value: import_zod4.z.unknown()
3416
3625
  }).strict();
3417
- var ErrorEntrySchema = import_zod3.z.object({
3418
- type: import_zod3.z.literal("error"),
3626
+ var ErrorEntrySchema = import_zod4.z.object({
3627
+ type: import_zod4.z.literal("error"),
3419
3628
  ...SessionBase,
3420
- error: import_zod3.z.object({
3421
- code: import_zod3.z.string().min(1),
3422
- message: import_zod3.z.string(),
3423
- stack: import_zod3.z.string().optional()
3629
+ error: import_zod4.z.object({
3630
+ code: import_zod4.z.string().min(1),
3631
+ message: import_zod4.z.string(),
3632
+ stack: import_zod4.z.string().optional()
3424
3633
  }).strict()
3425
3634
  }).strict();
3426
- var EntrySchema = import_zod3.z.discriminatedUnion("type", [
3635
+ var EntrySchema = import_zod4.z.discriminatedUnion("type", [
3427
3636
  UserEntrySchema,
3428
3637
  AssistantEntrySchema,
3429
3638
  ToolResultEntrySchema,
@@ -3455,16 +3664,16 @@ function parseEntryLine(line) {
3455
3664
 
3456
3665
  // src/transcript/meta.ts
3457
3666
  init_cjs_shims();
3458
- var import_zod4 = require("zod");
3459
- var TranscriptMetaSchema = import_zod4.z.object({
3460
- version: import_zod4.z.literal(1),
3461
- status: import_zod4.z.enum(["pending", "running", "paused", "done", "failed"]),
3462
- startedAt: import_zod4.z.string().datetime({ offset: true }),
3463
- updatedAt: import_zod4.z.string().datetime({ offset: true }),
3464
- turnCount: import_zod4.z.number().int().nonnegative(),
3465
- messageCount: import_zod4.z.number().int().nonnegative(),
3466
- lastShardIndex: import_zod4.z.number().int().nonnegative().nullable(),
3467
- shardCount: import_zod4.z.number().int().nonnegative()
3667
+ var import_zod5 = require("zod");
3668
+ var TranscriptMetaSchema = import_zod5.z.object({
3669
+ version: import_zod5.z.literal(1),
3670
+ status: import_zod5.z.enum(["pending", "running", "paused", "done", "failed"]),
3671
+ startedAt: import_zod5.z.string().datetime({ offset: true }),
3672
+ updatedAt: import_zod5.z.string().datetime({ offset: true }),
3673
+ turnCount: import_zod5.z.number().int().nonnegative(),
3674
+ messageCount: import_zod5.z.number().int().nonnegative(),
3675
+ lastShardIndex: import_zod5.z.number().int().nonnegative().nullable(),
3676
+ shardCount: import_zod5.z.number().int().nonnegative()
3468
3677
  }).strict();
3469
3678
  var META_FILENAME = "meta.json";
3470
3679
  function metaPath(logPath) {
@@ -3812,47 +4021,11 @@ Output format:
3812
4021
  Directive: ${directive}`;
3813
4022
  }
3814
4023
 
3815
- // src/tools/contract.ts
3816
- init_cjs_shims();
3817
- function defineTool(tool) {
3818
- return tool;
3819
- }
3820
- var ToolRegistry = class {
3821
- tools = /* @__PURE__ */ new Map();
3822
- register(tool) {
3823
- if (typeof tool.name !== "string" || tool.name.length === 0) {
3824
- throw new Error("ToolRegistry: tool.name must be a non-empty string");
3825
- }
3826
- if (this.tools.has(tool.name)) {
3827
- throw new Error(`ToolRegistry: "${tool.name}" is already registered`);
3828
- }
3829
- this.tools.set(tool.name, tool);
3830
- }
3831
- registerAll(tools) {
3832
- for (const tool of tools) this.register(tool);
3833
- }
3834
- unregister(name) {
3835
- this.tools.delete(name);
3836
- }
3837
- get(name) {
3838
- return this.tools.get(name);
3839
- }
3840
- has(name) {
3841
- return this.tools.has(name);
3842
- }
3843
- list() {
3844
- return Array.from(this.tools.values());
3845
- }
3846
- count() {
3847
- return this.tools.size;
3848
- }
3849
- };
3850
-
3851
4024
  // src/tools/agent.ts
3852
- var inputSchema = import_zod5.z.object({
3853
- description: import_zod5.z.string().min(1),
3854
- subagent_type: import_zod5.z.string().optional(),
3855
- run_in_background: import_zod5.z.boolean().optional()
4025
+ var inputSchema = import_zod6.z.object({
4026
+ description: import_zod6.z.string().min(1),
4027
+ subagent_type: import_zod6.z.string().optional(),
4028
+ run_in_background: import_zod6.z.boolean().optional()
3856
4029
  });
3857
4030
  function createAgentTool(options) {
3858
4031
  if (options.agents.length === 0) {
@@ -4057,7 +4230,7 @@ function handlePausedResult(result, agentId, subagentType) {
4057
4230
 
4058
4231
  // src/tools/bash.ts
4059
4232
  init_cjs_shims();
4060
- var import_zod6 = require("zod");
4233
+ var import_zod7 = require("zod");
4061
4234
  var _spawn = null;
4062
4235
  async function getSpawn() {
4063
4236
  if (_spawn === null) {
@@ -4069,15 +4242,16 @@ async function getSpawn() {
4069
4242
  var MAX_OUTPUT_BYTES = 512 * 1024;
4070
4243
  var SIGKILL_GRACE_MS = 500;
4071
4244
  var BLOCKED_DEVICE_PATHS = /\b(\/dev\/zero|\/dev\/random|\/dev\/urandom|\/proc\/kcore|\/dev\/sda|\/dev\/mem)\b/;
4072
- var inputSchema2 = import_zod6.z.object({
4073
- command: import_zod6.z.string().min(1),
4074
- cwd: import_zod6.z.string().min(1).optional()
4245
+ var inputSchema2 = import_zod7.z.object({
4246
+ command: import_zod7.z.string().min(1),
4247
+ cwd: import_zod7.z.string().min(1).optional()
4075
4248
  });
4076
4249
  function createBashTool() {
4077
4250
  return defineTool({
4078
4251
  name: "Bash",
4079
4252
  description: "Execute a shell command via /bin/sh -c. Returns combined stdout+stderr and the exit code.",
4080
4253
  inputSchema: inputSchema2,
4254
+ requiresNode: true,
4081
4255
  execute: async ({ command, cwd }, ctx) => {
4082
4256
  if (BLOCKED_DEVICE_PATHS.test(command)) {
4083
4257
  return {
@@ -4157,10 +4331,10 @@ function createBashTool() {
4157
4331
 
4158
4332
  // src/tools/sendMessage.ts
4159
4333
  init_cjs_shims();
4160
- var import_zod7 = require("zod");
4161
- var inputSchema3 = import_zod7.z.object({
4162
- to: import_zod7.z.string().min(1).describe("Agent name (subagent_type) or agentId to send the message to."),
4163
- message: import_zod7.z.string().min(1).describe("Message content to deliver to the target agent.")
4334
+ var import_zod8 = require("zod");
4335
+ var inputSchema3 = import_zod8.z.object({
4336
+ to: import_zod8.z.string().min(1).describe("Agent name (subagent_type) or agentId to send the message to."),
4337
+ message: import_zod8.z.string().min(1).describe("Message content to deliver to the target agent.")
4164
4338
  });
4165
4339
  function createSendMessageTool(options) {
4166
4340
  return defineTool({
@@ -4211,12 +4385,12 @@ function createSendMessageTool(options) {
4211
4385
 
4212
4386
  // src/tools/fileEdit.ts
4213
4387
  init_cjs_shims();
4214
- var import_zod8 = require("zod");
4215
- var inputSchema4 = import_zod8.z.object({
4216
- path: import_zod8.z.string().min(1),
4217
- old_string: import_zod8.z.string().min(1),
4218
- new_string: import_zod8.z.string(),
4219
- replace_all: import_zod8.z.boolean().optional()
4388
+ var import_zod9 = require("zod");
4389
+ var inputSchema4 = import_zod9.z.object({
4390
+ path: import_zod9.z.string().min(1),
4391
+ old_string: import_zod9.z.string().min(1),
4392
+ new_string: import_zod9.z.string(),
4393
+ replace_all: import_zod9.z.boolean().optional()
4220
4394
  });
4221
4395
  function createFileEditTool(storage) {
4222
4396
  return defineTool({
@@ -4300,13 +4474,13 @@ function normalizeQuotes(s) {
4300
4474
 
4301
4475
  // src/tools/fileRead.ts
4302
4476
  init_cjs_shims();
4303
- var import_zod9 = require("zod");
4304
- var inputSchema5 = import_zod9.z.object({
4305
- path: import_zod9.z.string().min(1),
4306
- offset: import_zod9.z.number().int().positive().optional(),
4307
- limit: import_zod9.z.number().int().positive().optional(),
4477
+ var import_zod10 = require("zod");
4478
+ var inputSchema5 = import_zod10.z.object({
4479
+ path: import_zod10.z.string().min(1),
4480
+ offset: import_zod10.z.number().int().positive().optional(),
4481
+ limit: import_zod10.z.number().int().positive().optional(),
4308
4482
  /** PDF page range, e.g. "1-5", "3", "10-20". Max 20 pages per request. */
4309
- pages: import_zod9.z.string().optional()
4483
+ pages: import_zod10.z.string().optional()
4310
4484
  });
4311
4485
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"]);
4312
4486
  var PDF_EXTENSION = ".pdf";
@@ -4474,7 +4648,7 @@ async function readImage(storage, filePath, ext) {
4474
4648
  ".svg": "image/svg+xml"
4475
4649
  };
4476
4650
  const mimeType = mimeMap[ext] ?? "application/octet-stream";
4477
- const base64 = buffer.toString("base64");
4651
+ const base642 = buffer.toString("base64");
4478
4652
  return {
4479
4653
  content: `[Image: ${filePath} (${mimeType}, ${(buffer.length / 1024).toFixed(1)}KB)]
4480
4654
 
@@ -4483,17 +4657,17 @@ Base64 data is included in the metadata for visual analysis.`,
4483
4657
  format: "image",
4484
4658
  mimeType,
4485
4659
  bytes: buffer.length,
4486
- base64
4660
+ base64: base642
4487
4661
  }
4488
4662
  };
4489
4663
  }
4490
4664
 
4491
4665
  // src/tools/fileWrite.ts
4492
4666
  init_cjs_shims();
4493
- var import_zod10 = require("zod");
4494
- var inputSchema6 = import_zod10.z.object({
4495
- path: import_zod10.z.string().min(1),
4496
- content: import_zod10.z.string()
4667
+ var import_zod11 = require("zod");
4668
+ var inputSchema6 = import_zod11.z.object({
4669
+ path: import_zod11.z.string().min(1),
4670
+ content: import_zod11.z.string()
4497
4671
  });
4498
4672
  function createFileWriteTool(storageOrOptions) {
4499
4673
  const opts = "readFile" in storageOrOptions ? { storage: storageOrOptions } : storageOrOptions;
@@ -4532,7 +4706,7 @@ function createFileWriteTool(storageOrOptions) {
4532
4706
  // src/tools/glob.ts
4533
4707
  init_cjs_shims();
4534
4708
  var import_picomatch = __toESM(require("picomatch"), 1);
4535
- var import_zod11 = require("zod");
4709
+ var import_zod12 = require("zod");
4536
4710
 
4537
4711
  // src/tools/walkAdapter.ts
4538
4712
  init_cjs_shims();
@@ -4569,9 +4743,9 @@ async function* walkAdapter(adapter, startPath, options = {}) {
4569
4743
  }
4570
4744
 
4571
4745
  // src/tools/glob.ts
4572
- var inputSchema7 = import_zod11.z.object({
4573
- pattern: import_zod11.z.string().min(1),
4574
- path: import_zod11.z.string().optional()
4746
+ var inputSchema7 = import_zod12.z.object({
4747
+ pattern: import_zod12.z.string().min(1),
4748
+ path: import_zod12.z.string().optional()
4575
4749
  });
4576
4750
  var MAX_RESULTS = 1e3;
4577
4751
  function createGlobTool(storage) {
@@ -4624,22 +4798,22 @@ function createGlobTool(storage) {
4624
4798
  // src/tools/grep.ts
4625
4799
  init_cjs_shims();
4626
4800
  var import_picomatch2 = __toESM(require("picomatch"), 1);
4627
- var import_zod12 = require("zod");
4628
- var inputSchema8 = import_zod12.z.object({
4629
- pattern: import_zod12.z.string().min(1),
4630
- path: import_zod12.z.string().optional(),
4631
- glob: import_zod12.z.string().optional(),
4632
- type: import_zod12.z.string().optional(),
4633
- output_mode: import_zod12.z.enum(["content", "files_with_matches", "count"]).optional(),
4634
- "-i": import_zod12.z.boolean().optional(),
4635
- "-n": import_zod12.z.boolean().optional(),
4636
- "-A": import_zod12.z.number().int().nonnegative().optional(),
4637
- "-B": import_zod12.z.number().int().nonnegative().optional(),
4638
- "-C": import_zod12.z.number().int().nonnegative().optional(),
4639
- context: import_zod12.z.number().int().nonnegative().optional(),
4640
- multiline: import_zod12.z.boolean().optional(),
4641
- head_limit: import_zod12.z.number().int().nonnegative().optional(),
4642
- offset: import_zod12.z.number().int().nonnegative().optional()
4801
+ var import_zod13 = require("zod");
4802
+ var inputSchema8 = import_zod13.z.object({
4803
+ pattern: import_zod13.z.string().min(1),
4804
+ path: import_zod13.z.string().optional(),
4805
+ glob: import_zod13.z.string().optional(),
4806
+ type: import_zod13.z.string().optional(),
4807
+ output_mode: import_zod13.z.enum(["content", "files_with_matches", "count"]).optional(),
4808
+ "-i": import_zod13.z.boolean().optional(),
4809
+ "-n": import_zod13.z.boolean().optional(),
4810
+ "-A": import_zod13.z.number().int().nonnegative().optional(),
4811
+ "-B": import_zod13.z.number().int().nonnegative().optional(),
4812
+ "-C": import_zod13.z.number().int().nonnegative().optional(),
4813
+ context: import_zod13.z.number().int().nonnegative().optional(),
4814
+ multiline: import_zod13.z.boolean().optional(),
4815
+ head_limit: import_zod13.z.number().int().nonnegative().optional(),
4816
+ offset: import_zod13.z.number().int().nonnegative().optional()
4643
4817
  });
4644
4818
  var MAX_FILES_SCANNED = 5e3;
4645
4819
  var MAX_MATCHES_PER_FILE = 100;
@@ -4863,10 +5037,10 @@ function formatJsResults(results, mode, limit) {
4863
5037
 
4864
5038
  // src/tools/webFetch.ts
4865
5039
  init_cjs_shims();
4866
- var import_zod13 = require("zod");
4867
- var inputSchema9 = import_zod13.z.object({
4868
- url: import_zod13.z.string().url(),
4869
- prompt: import_zod13.z.string().optional()
5040
+ var import_zod14 = require("zod");
5041
+ var inputSchema9 = import_zod14.z.object({
5042
+ url: import_zod14.z.string().url(),
5043
+ prompt: import_zod14.z.string().optional()
4870
5044
  });
4871
5045
  var MAX_OUTPUT_BYTES2 = 256 * 1024;
4872
5046
  function createWebFetchTool(options = {}) {
@@ -4995,10 +5169,10 @@ function normalizePath(p) {
4995
5169
 
4996
5170
  // src/tools/webSearch.ts
4997
5171
  init_cjs_shims();
4998
- var import_zod14 = require("zod");
4999
- var inputSchema10 = import_zod14.z.object({
5000
- query: import_zod14.z.string().min(2),
5001
- max_results: import_zod14.z.number().int().positive().optional()
5172
+ var import_zod15 = require("zod");
5173
+ var inputSchema10 = import_zod15.z.object({
5174
+ query: import_zod15.z.string().min(2),
5175
+ max_results: import_zod15.z.number().int().positive().optional()
5002
5176
  });
5003
5177
  function createWebSearchTool() {
5004
5178
  return defineTool({
@@ -5055,10 +5229,10 @@ function htmlToText2(html) {
5055
5229
 
5056
5230
  // src/tools/sleep.ts
5057
5231
  init_cjs_shims();
5058
- var import_zod15 = require("zod");
5059
- var inputSchema11 = import_zod15.z.object({
5060
- durationMs: import_zod15.z.number().int().nonnegative().max(3e5),
5061
- reason: import_zod15.z.string().optional()
5232
+ var import_zod16 = require("zod");
5233
+ var inputSchema11 = import_zod16.z.object({
5234
+ durationMs: import_zod16.z.number().int().nonnegative().max(3e5),
5235
+ reason: import_zod16.z.string().optional()
5062
5236
  });
5063
5237
  function createSleepTool() {
5064
5238
  return defineTool({
@@ -5098,10 +5272,10 @@ function createSleepTool() {
5098
5272
 
5099
5273
  // src/tools/toolSearch.ts
5100
5274
  init_cjs_shims();
5101
- var import_zod16 = require("zod");
5102
- var inputSchema12 = import_zod16.z.object({
5103
- query: import_zod16.z.string().min(1),
5104
- max_results: import_zod16.z.number().int().positive().optional()
5275
+ var import_zod17 = require("zod");
5276
+ var inputSchema12 = import_zod17.z.object({
5277
+ query: import_zod17.z.string().min(1),
5278
+ max_results: import_zod17.z.number().int().positive().optional()
5105
5279
  });
5106
5280
  function createToolSearchTool(registry) {
5107
5281
  return defineTool({
@@ -5144,11 +5318,11 @@ ${lines.join("\n")}`,
5144
5318
 
5145
5319
  // src/tools/memorize.ts
5146
5320
  init_cjs_shims();
5147
- var import_zod17 = require("zod");
5148
- var inputSchema13 = import_zod17.z.object({
5149
- text: import_zod17.z.string().min(1),
5150
- kind: import_zod17.z.enum(["rule", "lesson"]).default("lesson"),
5151
- topic: import_zod17.z.string().optional()
5321
+ var import_zod18 = require("zod");
5322
+ var inputSchema13 = import_zod18.z.object({
5323
+ text: import_zod18.z.string().min(1),
5324
+ kind: import_zod18.z.enum(["rule", "lesson"]).default("lesson"),
5325
+ topic: import_zod18.z.string().optional()
5152
5326
  });
5153
5327
  function createMemorizeTool(memory) {
5154
5328
  return defineTool({
@@ -5180,11 +5354,11 @@ function createMemorizeTool(memory) {
5180
5354
 
5181
5355
  // src/tools/recall.ts
5182
5356
  init_cjs_shims();
5183
- var import_zod18 = require("zod");
5184
- var inputSchema14 = import_zod18.z.object({
5185
- query: import_zod18.z.string().min(1),
5186
- scope: import_zod18.z.enum(["identity", "rules", "lessons", "all"]).default("all"),
5187
- topic: import_zod18.z.string().optional()
5357
+ var import_zod19 = require("zod");
5358
+ var inputSchema14 = import_zod19.z.object({
5359
+ query: import_zod19.z.string().min(1),
5360
+ scope: import_zod19.z.enum(["identity", "rules", "lessons", "all"]).default("all"),
5361
+ topic: import_zod19.z.string().optional()
5188
5362
  });
5189
5363
  function createRecallTool(memory) {
5190
5364
  return defineTool({
@@ -5239,13 +5413,13 @@ ${content}`,
5239
5413
 
5240
5414
  // src/tools/notebookEdit.ts
5241
5415
  init_cjs_shims();
5242
- var import_zod19 = require("zod");
5243
- var inputSchema15 = import_zod19.z.object({
5244
- notebook_path: import_zod19.z.string().min(1),
5245
- edit_mode: import_zod19.z.enum(["replace", "insert", "delete"]).default("replace"),
5246
- cell_index: import_zod19.z.number().int().nonnegative(),
5247
- new_source: import_zod19.z.string().optional(),
5248
- cell_type: import_zod19.z.enum(["code", "markdown"]).optional()
5416
+ var import_zod20 = require("zod");
5417
+ var inputSchema15 = import_zod20.z.object({
5418
+ notebook_path: import_zod20.z.string().min(1),
5419
+ edit_mode: import_zod20.z.enum(["replace", "insert", "delete"]).default("replace"),
5420
+ cell_index: import_zod20.z.number().int().nonnegative(),
5421
+ new_source: import_zod20.z.string().optional(),
5422
+ cell_type: import_zod20.z.enum(["code", "markdown"]).optional()
5249
5423
  });
5250
5424
  function createNotebookEditTool(storage) {
5251
5425
  return defineTool({
@@ -5400,12 +5574,12 @@ var TaskStore = class {
5400
5574
 
5401
5575
  // src/tools/tasks/tools.ts
5402
5576
  init_cjs_shims();
5403
- var import_zod20 = require("zod");
5404
- var TaskStatusEnum = import_zod20.z.enum(["pending", "in_progress", "completed", "deleted"]);
5405
- var createSchema = import_zod20.z.object({
5406
- subject: import_zod20.z.string().min(1),
5407
- description: import_zod20.z.string().default(""),
5408
- metadata: import_zod20.z.record(import_zod20.z.unknown()).optional()
5577
+ var import_zod21 = require("zod");
5578
+ var TaskStatusEnum = import_zod21.z.enum(["pending", "in_progress", "completed", "deleted"]);
5579
+ var createSchema = import_zod21.z.object({
5580
+ subject: import_zod21.z.string().min(1),
5581
+ description: import_zod21.z.string().default(""),
5582
+ metadata: import_zod21.z.record(import_zod21.z.unknown()).optional()
5409
5583
  });
5410
5584
  function createTaskCreateTool(store) {
5411
5585
  return defineTool({
@@ -5421,8 +5595,8 @@ function createTaskCreateTool(store) {
5421
5595
  }
5422
5596
  });
5423
5597
  }
5424
- var getSchema = import_zod20.z.object({
5425
- taskId: import_zod20.z.string().min(1)
5598
+ var getSchema = import_zod21.z.object({
5599
+ taskId: import_zod21.z.string().min(1)
5426
5600
  });
5427
5601
  function createTaskGetTool(store) {
5428
5602
  return defineTool({
@@ -5443,7 +5617,7 @@ ${task.description}`,
5443
5617
  }
5444
5618
  });
5445
5619
  }
5446
- var listSchema = import_zod20.z.object({
5620
+ var listSchema = import_zod21.z.object({
5447
5621
  status: TaskStatusEnum.optional()
5448
5622
  });
5449
5623
  function createTaskListTool(store) {
@@ -5469,12 +5643,12 @@ ${lines.join("\n")}`,
5469
5643
  }
5470
5644
  });
5471
5645
  }
5472
- var updateSchema = import_zod20.z.object({
5473
- taskId: import_zod20.z.string().min(1),
5474
- subject: import_zod20.z.string().min(1).optional(),
5646
+ var updateSchema = import_zod21.z.object({
5647
+ taskId: import_zod21.z.string().min(1),
5648
+ subject: import_zod21.z.string().min(1).optional(),
5475
5649
  status: TaskStatusEnum.optional(),
5476
- description: import_zod20.z.string().optional(),
5477
- metadata: import_zod20.z.record(import_zod20.z.unknown()).optional()
5650
+ description: import_zod21.z.string().optional(),
5651
+ metadata: import_zod21.z.record(import_zod21.z.unknown()).optional()
5478
5652
  });
5479
5653
  function createTaskUpdateTool(store) {
5480
5654
  return defineTool({
@@ -6321,7 +6495,7 @@ function wrapFetchWithHeadersProvider(baseFetch, headersProvider, staticHeaders)
6321
6495
 
6322
6496
  // src/mcp/toolAdapter.ts
6323
6497
  init_cjs_shims();
6324
- var import_zod21 = require("zod");
6498
+ var import_zod22 = require("zod");
6325
6499
  function mcpToolName(serverName, toolName) {
6326
6500
  return `mcp__${serverName}__${toolName}`;
6327
6501
  }
@@ -6330,7 +6504,7 @@ function adaptMcpTool(client, serverName, def) {
6330
6504
  return defineTool({
6331
6505
  name: registeredName,
6332
6506
  description: def.description.length > 0 ? def.description : `MCP tool ${serverName}/${def.name}`,
6333
- inputSchema: import_zod21.z.unknown(),
6507
+ inputSchema: import_zod22.z.unknown(),
6334
6508
  // Pass the MCP server's JSON Schema through to Anthropic verbatim,
6335
6509
  // bypassing Zod-to-JSON-Schema conversion (which would produce `{}`
6336
6510
  // for our `z.unknown()` Zod schema).
@@ -7439,11 +7613,11 @@ ${issues.join("\n")}` };
7439
7613
 
7440
7614
  // src/skills/skillPage.ts
7441
7615
  init_cjs_shims();
7442
- var import_zod22 = require("zod");
7616
+ var import_zod23 = require("zod");
7443
7617
  var SAFE_NAME3 = /^[a-zA-Z0-9_-]+$/;
7444
- var inputSchema16 = import_zod22.z.object({
7445
- skill: import_zod22.z.string().min(1),
7446
- page: import_zod22.z.string().min(1).optional()
7618
+ var inputSchema16 = import_zod23.z.object({
7619
+ skill: import_zod23.z.string().min(1),
7620
+ page: import_zod23.z.string().min(1).optional()
7447
7621
  });
7448
7622
  function createSkillPageTool(options) {
7449
7623
  return defineTool({
@@ -7499,6 +7673,203 @@ The skill "${skill}" has no pages.`;
7499
7673
  });
7500
7674
  }
7501
7675
 
7676
+ // src/tools/apiCall.ts
7677
+ init_cjs_shims();
7678
+ var import_zod24 = require("zod");
7679
+ var ALL_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
7680
+ var DEFAULT_MAX_BODY_BYTES = 256 * 1024;
7681
+ var DEFAULT_MAX_RESPONSE_BYTES = 100 * 1024;
7682
+ function createApiCallTool(opts) {
7683
+ if (opts.services.length === 0) {
7684
+ throw new Error("createApiCallTool: services list must be non-empty");
7685
+ }
7686
+ const services = /* @__PURE__ */ new Map();
7687
+ for (const svc of opts.services) {
7688
+ if (services.has(svc.name)) {
7689
+ throw new Error(`createApiCallTool: duplicate service name "${svc.name}"`);
7690
+ }
7691
+ services.set(svc.name, svc);
7692
+ }
7693
+ const serviceNames = [...services.keys()];
7694
+ for (const svc of opts.services) {
7695
+ if (svc.auth?.type === "custom" && opts.resolveAuth === void 0) {
7696
+ throw new Error(
7697
+ `createApiCallTool: service "${svc.name}" uses custom auth (id: ${svc.auth.id}) but no resolveAuth was supplied`
7698
+ );
7699
+ }
7700
+ }
7701
+ const fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
7702
+ const maxResponseBytes = opts.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
7703
+ const inputSchema17 = import_zod24.z.object({
7704
+ service: import_zod24.z.enum(serviceNames),
7705
+ method: import_zod24.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
7706
+ path: import_zod24.z.string().regex(/^\//, "path must start with /"),
7707
+ query: import_zod24.z.record(import_zod24.z.string(), import_zod24.z.string()).optional(),
7708
+ body: import_zod24.z.unknown().optional(),
7709
+ headers: import_zod24.z.record(import_zod24.z.string(), import_zod24.z.string()).optional()
7710
+ });
7711
+ const description = opts.toolDescription ?? `Call a configured external API. Services: ${serviceNames.join(", ")}. Auth is injected automatically \u2014 do not pass credentials via headers.`;
7712
+ return defineTool({
7713
+ name: opts.toolName ?? "ApiCall",
7714
+ description,
7715
+ inputSchema: inputSchema17,
7716
+ execute: async (input) => {
7717
+ const svc = services.get(input.service);
7718
+ if (!svc) {
7719
+ return errResult(`ERR_API_UNKNOWN_SERVICE: ${input.service}`);
7720
+ }
7721
+ const allowedMethods = svc.allowedMethods ?? ALL_METHODS;
7722
+ if (!allowedMethods.includes(input.method)) {
7723
+ return errResult(
7724
+ `ERR_API_METHOD_NOT_ALLOWED: ${input.method} not permitted for service ${svc.name}`
7725
+ );
7726
+ }
7727
+ if (!pathAllowed(input.path, svc.allowedPaths)) {
7728
+ return errResult(`ERR_API_PATH_NOT_ALLOWED: ${input.path} for service ${svc.name}`);
7729
+ }
7730
+ let bodyText;
7731
+ if (input.body !== void 0) {
7732
+ bodyText = JSON.stringify(input.body);
7733
+ const cap = svc.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
7734
+ if (byteLength(bodyText) > cap) {
7735
+ return errResult(`ERR_API_BODY_TOO_LARGE: exceeds ${cap} bytes`);
7736
+ }
7737
+ }
7738
+ let authHeaders;
7739
+ try {
7740
+ authHeaders = await resolveAuth({
7741
+ auth: svc.auth ?? { type: "none" },
7742
+ env: opts.env,
7743
+ resolver: opts.resolveAuth,
7744
+ ctx: { serviceName: svc.name, method: input.method, path: input.path }
7745
+ });
7746
+ } catch (err) {
7747
+ const raw2 = err instanceof Error ? err.message : String(err);
7748
+ const truncated = raw2.length > 200 ? raw2.slice(0, 200) + "\u2026" : raw2;
7749
+ return errResult(`ERR_API_RESOLVER_FAILED: ${truncated}`);
7750
+ }
7751
+ const userHeaders = sanitizeHeaders(input.headers ?? {}, authHeaders);
7752
+ const url = buildUrl(svc.baseUrl, input.path, input.query);
7753
+ await invokeHook(opts.onRequest, {
7754
+ service: svc.name,
7755
+ method: input.method,
7756
+ path: input.path
7757
+ });
7758
+ const started = Date.now();
7759
+ let res;
7760
+ try {
7761
+ res = await fetchFn(url, {
7762
+ method: input.method,
7763
+ headers: {
7764
+ "Content-Type": "application/json",
7765
+ ...svc.defaultHeaders ?? {},
7766
+ ...userHeaders,
7767
+ ...authHeaders
7768
+ // wins last — model cannot override
7769
+ },
7770
+ ...bodyText !== void 0 ? { body: bodyText } : {}
7771
+ });
7772
+ } catch (err) {
7773
+ const msg = err instanceof Error ? err.message : String(err);
7774
+ return errResult(`network error: ${msg}`);
7775
+ }
7776
+ const raw = await res.text();
7777
+ const content = raw.length > maxResponseBytes ? raw.slice(0, maxResponseBytes) + "\n\u2026[TRUNCATED]" : raw;
7778
+ await invokeHook(opts.onResponse, {
7779
+ service: svc.name,
7780
+ method: input.method,
7781
+ path: input.path,
7782
+ status: res.status,
7783
+ latencyMs: Date.now() - started,
7784
+ bytesIn: raw.length
7785
+ });
7786
+ return {
7787
+ content,
7788
+ isError: !res.ok,
7789
+ metadata: { status: res.status, service: svc.name }
7790
+ };
7791
+ }
7792
+ });
7793
+ }
7794
+ function errResult(msg) {
7795
+ return { content: msg, isError: true };
7796
+ }
7797
+ function pathAllowed(path, allowed) {
7798
+ if (!allowed || allowed.length === 0) return true;
7799
+ for (const a of allowed) {
7800
+ if (typeof a === "string") {
7801
+ if (path.startsWith(a)) return true;
7802
+ } else if (a.test(path)) {
7803
+ return true;
7804
+ }
7805
+ }
7806
+ return false;
7807
+ }
7808
+ function byteLength(s) {
7809
+ return new TextEncoder().encode(s).byteLength;
7810
+ }
7811
+ function buildUrl(baseUrl, path, query) {
7812
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
7813
+ const url = new URL(base + path);
7814
+ for (const [k, v] of Object.entries(query ?? {})) {
7815
+ url.searchParams.set(k, v);
7816
+ }
7817
+ return url.toString();
7818
+ }
7819
+ function sanitizeHeaders(user, auth) {
7820
+ const banned = new Set(Object.keys(auth).map((k) => k.toLowerCase()));
7821
+ const out = {};
7822
+ for (const [k, v] of Object.entries(user)) {
7823
+ if (banned.has(k.toLowerCase())) continue;
7824
+ out[k] = v;
7825
+ }
7826
+ return out;
7827
+ }
7828
+ async function resolveAuth(args) {
7829
+ const { auth, env, resolver, ctx } = args;
7830
+ switch (auth.type) {
7831
+ case "none":
7832
+ return {};
7833
+ case "bearer": {
7834
+ const token = envLookup(env, auth.tokenRef);
7835
+ return { Authorization: `Bearer ${token}` };
7836
+ }
7837
+ case "header": {
7838
+ const value = envLookup(env, auth.valueRef);
7839
+ return { [auth.name]: value };
7840
+ }
7841
+ case "basic": {
7842
+ const u = envLookup(env, auth.userRef);
7843
+ const p = envLookup(env, auth.passRef);
7844
+ return { Authorization: `Basic ${base64(`${u}:${p}`)}` };
7845
+ }
7846
+ case "custom":
7847
+ if (resolver === void 0) {
7848
+ throw new Error(`custom auth id "${auth.id}" requires resolveAuth`);
7849
+ }
7850
+ return resolver(auth, ctx);
7851
+ }
7852
+ }
7853
+ function envLookup(env, ref) {
7854
+ if (env === void 0 || env[ref] === void 0) {
7855
+ throw new Error(`ERR_API_ENV_MISSING: ${ref}`);
7856
+ }
7857
+ return env[ref];
7858
+ }
7859
+ function base64(input) {
7860
+ if (typeof globalThis.btoa === "function") {
7861
+ return globalThis.btoa(input);
7862
+ }
7863
+ return Buffer.from(input, "utf8").toString("base64");
7864
+ }
7865
+ async function invokeHook(hook, event) {
7866
+ if (hook === void 0) return;
7867
+ try {
7868
+ await hook(event);
7869
+ } catch {
7870
+ }
7871
+ }
7872
+
7502
7873
  // src/skills/storageSkillSource.ts
7503
7874
  init_cjs_shims();
7504
7875
  var SAFE_NAME4 = /^[a-zA-Z0-9_-]+$/;
@@ -8274,6 +8645,7 @@ function rebuildMessagesFromEntries(entries) {
8274
8645
  init_cjs_shims();
8275
8646
  function toResponse(result, extra) {
8276
8647
  const timestamp = Date.now();
8648
+ const capsField = extra.capabilitiesMissing !== void 0 && extra.capabilitiesMissing.length > 0 ? { capabilitiesMissing: extra.capabilitiesMissing } : {};
8277
8649
  if (result.status === "done") {
8278
8650
  return {
8279
8651
  runId: extra.runId,
@@ -8285,7 +8657,8 @@ function toResponse(result, extra) {
8285
8657
  tokensUsed: result.tokensUsed,
8286
8658
  durationMs: extra.durationMs,
8287
8659
  output: result.output,
8288
- ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: 0 } } : {}
8660
+ ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: 0 } } : {},
8661
+ ...capsField
8289
8662
  },
8290
8663
  errors: [],
8291
8664
  timestamp
@@ -8304,7 +8677,8 @@ function toResponse(result, extra) {
8304
8677
  snapshot: result.snapshot,
8305
8678
  ...result.snapshot.pendingToolCall !== void 0 ? { pendingToolCall: result.snapshot.pendingToolCall } : {},
8306
8679
  pauseReason: result.reason,
8307
- ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: result.snapshot.lastShardIndex } } : {}
8680
+ ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: result.snapshot.lastShardIndex } } : {},
8681
+ ...capsField
8308
8682
  },
8309
8683
  errors: [],
8310
8684
  timestamp
@@ -8638,7 +9012,15 @@ var Engine = class {
8638
9012
  this.mcpManager = new McpManager(config.mcp, createLogger(config.logging), { samplingHandler });
8639
9013
  this.permissionPolicy = buildPermissionPolicy(config.permissions);
8640
9014
  }
8641
- async run(options) {
9015
+ /**
9016
+ * Run a task synchronously to completion. The `_internal` parameter is
9017
+ * reserved for the engine's own async wrappers (`start`, `resumeAsync`)
9018
+ * to request runner handoff (Plan 019) — external callers must leave
9019
+ * it unset. Sync callers never hand off; if they hit a Node-only tool
9020
+ * on a restricted runtime, the capability stub returns an error and
9021
+ * the model adapts.
9022
+ */
9023
+ async run(options, _internal) {
8642
9024
  const startTime = Date.now();
8643
9025
  const runId = options.runId ?? `run_${randomUUID()}`;
8644
9026
  const log = createLogger(this.config.logging);
@@ -8661,6 +9043,7 @@ var Engine = class {
8661
9043
  const coordinatorBase = isCoordinatorMode(this.config) ? getCoordinatorBasePrompt() : void 0;
8662
9044
  const skillSource = this.resolveSkillSource(options.skills, storage);
8663
9045
  const skillList = skillSource !== void 0 ? await skillSource.list() : void 0;
9046
+ const apiConfig = this.resolveApiConfig(options.api);
8664
9047
  let systemPrompt = await buildSystemPrompt({
8665
9048
  ...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
8666
9049
  memory,
@@ -8691,7 +9074,9 @@ var Engine = class {
8691
9074
  mcpTools,
8692
9075
  memory,
8693
9076
  ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
8694
- ...skillSource !== void 0 ? { skillSource } : {}
9077
+ ...skillSource !== void 0 ? { skillSource } : {},
9078
+ ...apiConfig !== void 0 ? { apiConfig } : {},
9079
+ ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
8695
9080
  });
8696
9081
  const writer = new TranscriptWriter({
8697
9082
  storage: storage.workspace,
@@ -8745,7 +9130,8 @@ var Engine = class {
8745
9130
  onProgress: this.buildHeartbeat(storage, runId, options.nodeId),
8746
9131
  ...options.tokenBudget !== void 0 ? { tokenBudget: options.tokenBudget } : {},
8747
9132
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
8748
- ...gate !== void 0 ? { gateBeforeTool: gate } : {}
9133
+ ...gate !== void 0 ? { gateBeforeTool: gate } : {},
9134
+ ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {}
8749
9135
  });
8750
9136
  const result = await this.finalizeResult(loopResult, writer, logPath, {
8751
9137
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -8753,18 +9139,20 @@ var Engine = class {
8753
9139
  });
8754
9140
  this.logRunEnd(log, runId, options.nodeId, result);
8755
9141
  await this.firePostRunHook(runId, options.nodeId, result, ctx, logPath);
9142
+ const capabilitiesMissing = ctx.getCapabilitiesMissing();
8756
9143
  return toResponse(result, {
8757
9144
  runId,
8758
9145
  nodeId: options.nodeId,
8759
9146
  durationMs: Date.now() - startTime,
8760
- logPath
9147
+ logPath,
9148
+ ...capabilitiesMissing.length > 0 ? { capabilitiesMissing } : {}
8761
9149
  });
8762
9150
  } finally {
8763
9151
  runTimeout.clear();
8764
9152
  await writer.close();
8765
9153
  }
8766
9154
  }
8767
- async resume(options) {
9155
+ async resume(options, _internal) {
8768
9156
  const startTime = Date.now();
8769
9157
  const log = createLogger(this.config.logging);
8770
9158
  const storage = await this.buildStorage();
@@ -8797,6 +9185,7 @@ var Engine = class {
8797
9185
  const coordinatorBase = isCoordinatorMode(this.config) ? getCoordinatorBasePrompt() : void 0;
8798
9186
  const skillSource = this.resolveSkillSource(options.skills, storage);
8799
9187
  const skillList = skillSource !== void 0 ? await skillSource.list() : void 0;
9188
+ const apiConfig = this.resolveApiConfig(options.api);
8800
9189
  let systemPrompt = await buildSystemPrompt({
8801
9190
  ...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
8802
9191
  memory,
@@ -8827,7 +9216,9 @@ var Engine = class {
8827
9216
  mcpTools,
8828
9217
  memory,
8829
9218
  ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
8830
- ...skillSource !== void 0 ? { skillSource } : {}
9219
+ ...skillSource !== void 0 ? { skillSource } : {},
9220
+ ...apiConfig !== void 0 ? { apiConfig } : {},
9221
+ ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
8831
9222
  });
8832
9223
  const priorState = await loadWriterState(storage.workspace, logPath);
8833
9224
  const writer = new TranscriptWriter({
@@ -8881,18 +9272,21 @@ var Engine = class {
8881
9272
  const answer = typeof options.gateAnswer === "string" ? options.gateAnswer : JSON.stringify(options.gateAnswer);
8882
9273
  await ctx.addToolResult(pending.toolUseId, answer, false);
8883
9274
  } else {
8884
- await ctx.addToolResult(
8885
- pending.toolUseId,
8886
- `APPROVAL_GATE_RELEASED: the prior ${pending.toolName} call was paused for human approval and has now been approved. Retry is required.`,
8887
- false
8888
- );
8889
9275
  const inputJson = JSON.stringify(pending.input ?? {}, null, 2);
8890
- await ctx.addUserMessage(
8891
- `The human has approved the paused ${pending.toolName} tool call. You MUST now re-issue the EXACT same tool call to complete the work \u2014 do not change the arguments, do not answer in text, do not declare the task done. Approved arguments (copy verbatim):
9276
+ await ctx.addMixedUserMessage([
9277
+ {
9278
+ type: "tool_result",
9279
+ tool_use_id: pending.toolUseId,
9280
+ content: `APPROVAL_GATE_RELEASED: the prior ${pending.toolName} call was paused for human approval and has now been approved. Retry is required.`
9281
+ },
9282
+ {
9283
+ type: "text",
9284
+ text: `The human has approved the paused ${pending.toolName} tool call. You MUST now re-issue the EXACT same tool call to complete the work \u2014 do not change the arguments, do not answer in text, do not declare the task done. Approved arguments (copy verbatim):
8892
9285
  \`\`\`json
8893
9286
  ${inputJson}
8894
9287
  \`\`\``
8895
- );
9288
+ }
9289
+ ]);
8896
9290
  }
8897
9291
  }
8898
9292
  await writer.setStatus("running");
@@ -8913,7 +9307,8 @@ ${inputJson}
8913
9307
  stopHooks: this.config.hooks.stopHooks,
8914
9308
  onProgress: this.buildHeartbeat(storage, snapshot.runId, snapshot.nodeId),
8915
9309
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
8916
- ...gate !== void 0 ? { gateBeforeTool: gate } : {}
9310
+ ...gate !== void 0 ? { gateBeforeTool: gate } : {},
9311
+ ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {}
8917
9312
  });
8918
9313
  const result = await this.finalizeResult(loopResult, writer, logPath, {
8919
9314
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -8921,11 +9316,13 @@ ${inputJson}
8921
9316
  });
8922
9317
  this.logRunEnd(log, snapshot.runId, snapshot.nodeId, result);
8923
9318
  await this.firePostRunHook(snapshot.runId, snapshot.nodeId, result, ctx, logPath);
9319
+ const capabilitiesMissing = ctx.getCapabilitiesMissing();
8924
9320
  return toResponse(result, {
8925
9321
  runId: snapshot.runId,
8926
9322
  nodeId: snapshot.nodeId,
8927
9323
  durationMs: Date.now() - startTime,
8928
- logPath
9324
+ logPath,
9325
+ ...capabilitiesMissing.length > 0 ? { capabilitiesMissing } : {}
8929
9326
  });
8930
9327
  } finally {
8931
9328
  runTimeout.clear();
@@ -8995,13 +9392,15 @@ ${inputJson}
8995
9392
  } : void 0;
8996
9393
  const initial = RunStateManager.initial(runId, options.nodeId, webhook);
8997
9394
  await stateManager.write(initial);
9395
+ const handoffEnabled = this.config.runner !== void 0;
8998
9396
  this.backgroundExecutor.schedule(runId, async (signal) => {
8999
9397
  await stateManager.update(runId, options.nodeId, { status: "running" });
9000
9398
  try {
9001
- const response = await this.run({ ...options, runId });
9399
+ const response = await this.run({ ...options, runId }, { handoffToRunner: handoffEnabled });
9002
9400
  if (signal.aborted) return;
9003
- await stateManager.finalize(runId, options.nodeId, response);
9004
- await this.maybeFireWebhook(stateManager, runId, options.nodeId, response);
9401
+ const postHandoff = await this.maybeHandoffToRunner(runId, options.nodeId, response);
9402
+ await stateManager.finalize(runId, options.nodeId, postHandoff);
9403
+ await this.maybeFireWebhook(stateManager, runId, options.nodeId, postHandoff);
9005
9404
  } catch (err) {
9006
9405
  if (signal.aborted) return;
9007
9406
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -9049,12 +9448,14 @@ ${inputJson}
9049
9448
  } : { ...RunStateManager.initial(options.runId, nodeId, webhook), status: "running" };
9050
9449
  await stateManager.write(next);
9051
9450
  const resumeNodeId = nodeId;
9451
+ const handoffEnabled = this.config.runner !== void 0;
9052
9452
  this.backgroundExecutor.schedule(options.runId, async (signal) => {
9053
9453
  try {
9054
- const response = await this.resume(options);
9454
+ const response = await this.resume(options, { handoffToRunner: handoffEnabled });
9055
9455
  if (signal.aborted) return;
9056
- await stateManager.finalize(options.runId, resumeNodeId, response);
9057
- await this.maybeFireWebhook(stateManager, options.runId, resumeNodeId, response);
9456
+ const postHandoff = await this.maybeHandoffToRunner(options.runId, resumeNodeId, response);
9457
+ await stateManager.finalize(options.runId, resumeNodeId, postHandoff);
9458
+ await this.maybeFireWebhook(stateManager, options.runId, resumeNodeId, postHandoff);
9058
9459
  } catch (err) {
9059
9460
  if (signal.aborted) return;
9060
9461
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -9243,6 +9644,68 @@ ${inputJson}
9243
9644
  }
9244
9645
  return orphaned;
9245
9646
  }
9647
+ // ---------- runner handoff (Plan 019) ----------
9648
+ /**
9649
+ * When the response indicates the run paused for runner handoff, POST
9650
+ * `{ runId }` to the configured runner URL. On success, return the
9651
+ * response unchanged — state stays `paused` and the runner will flip
9652
+ * it to `done` (or `failed`) on its side. On POST failure, convert
9653
+ * the response to `failed` with `ERR_RUNNER_UNREACHABLE` so the
9654
+ * caller sees a terminal state instead of a silent hang.
9655
+ *
9656
+ * Called only from `start()` / `resumeAsync()`. Sync `run()` never
9657
+ * produces a `handoff_to_runner` pause (see `_internal.handoffToRunner`).
9658
+ */
9659
+ async maybeHandoffToRunner(runId, nodeId, response) {
9660
+ if (response.status !== "paused") return response;
9661
+ if (response.meta.pauseReason !== "handoff_to_runner") return response;
9662
+ const runner = this.config.runner;
9663
+ if (runner === void 0) {
9664
+ return response;
9665
+ }
9666
+ const fetchFn = this.internals.fetch ?? globalThis.fetch.bind(globalThis);
9667
+ try {
9668
+ const res = await fetchFn(runner.url, {
9669
+ method: "POST",
9670
+ headers: {
9671
+ "Content-Type": "application/json",
9672
+ Authorization: `Bearer ${runner.secret}`
9673
+ },
9674
+ body: JSON.stringify({ runId })
9675
+ });
9676
+ if (!res.ok) {
9677
+ return {
9678
+ runId,
9679
+ status: "failed",
9680
+ data: null,
9681
+ meta: { nodeId },
9682
+ errors: [
9683
+ {
9684
+ code: "ERR_RUNNER_UNREACHABLE",
9685
+ message: `Runner returned HTTP ${res.status}`
9686
+ }
9687
+ ],
9688
+ timestamp: Date.now()
9689
+ };
9690
+ }
9691
+ return response;
9692
+ } catch (err) {
9693
+ const msg = err instanceof Error ? err.message : String(err);
9694
+ return {
9695
+ runId,
9696
+ status: "failed",
9697
+ data: null,
9698
+ meta: { nodeId },
9699
+ errors: [
9700
+ {
9701
+ code: "ERR_RUNNER_UNREACHABLE",
9702
+ message: `Runner handoff failed: ${msg}`
9703
+ }
9704
+ ],
9705
+ timestamp: Date.now()
9706
+ };
9707
+ }
9708
+ }
9246
9709
  // ---------- webhook helpers ----------
9247
9710
  async maybeFireWebhook(stateManager, runId, nodeId, response) {
9248
9711
  const state = await stateManager.read(runId, nodeId);
@@ -9386,6 +9849,11 @@ ${inputJson}
9386
9849
  if (disabled.has(name)) continue;
9387
9850
  if (wantAll || enabled.has(name)) names.add(name);
9388
9851
  }
9852
+ if ((this.config.api?.services.length ?? 0) > 0) {
9853
+ if (!disabled.has("ApiCall") && (wantAll || enabled.has("ApiCall"))) {
9854
+ names.add("ApiCall");
9855
+ }
9856
+ }
9389
9857
  for (const tool of this.config.tools.custom) {
9390
9858
  names.add(tool.name);
9391
9859
  }
@@ -9542,6 +10010,34 @@ ${inputJson}
9542
10010
  }
9543
10011
  return void 0;
9544
10012
  }
10013
+ /**
10014
+ * Plan 020 — resolve the effective ApiCall config for a run.
10015
+ *
10016
+ * Precedence (each field independently):
10017
+ * RunOptions.api.X > config.api.X
10018
+ *
10019
+ * If neither side provides any services, returns undefined so the
10020
+ * tool isn't registered at all. Env + resolveAuth + hooks flow
10021
+ * through untouched — they never hit the Zod schema.
10022
+ */
10023
+ resolveApiConfig(override) {
10024
+ const base = this.config.api;
10025
+ if (override === void 0 && base === void 0) return void 0;
10026
+ const services = override?.services ?? base?.services;
10027
+ if (services === void 0 || services.length === 0) return void 0;
10028
+ const env = override?.env ?? base?.env;
10029
+ const resolveAuth2 = override?.resolveAuth ?? base?.resolveAuth;
10030
+ const onRequest = override?.onRequest ?? base?.onRequest;
10031
+ const onResponse = override?.onResponse ?? base?.onResponse;
10032
+ return {
10033
+ services,
10034
+ ...env !== void 0 ? { env } : {},
10035
+ ...resolveAuth2 !== void 0 ? { resolveAuth: resolveAuth2 } : {},
10036
+ ...onRequest !== void 0 ? { onRequest } : {},
10037
+ ...onResponse !== void 0 ? { onResponse } : {},
10038
+ ...base?.maxResponseBytes !== void 0 ? { maxResponseBytes: base.maxResponseBytes } : {}
10039
+ };
10040
+ }
9545
10041
  /**
9546
10042
  * Build a throttled heartbeat callback for agentLoop's `onProgress` hook.
9547
10043
  *
@@ -9636,8 +10132,8 @@ function buildToolRegistry(options) {
9636
10132
  const fileTracker = new FileTracker();
9637
10133
  const spawnAvailable = canSpawnProcesses();
9638
10134
  const candidates = [
9639
- // Bash — requires child_process.spawn (Node.js only).
9640
- ...spawnAvailable ? [{ name: "Bash", tool: createBashTool() }] : [],
10135
+ // Bash — requires child_process.spawn. Stubbed on Workers.
10136
+ { name: "Bash", tool: withCapabilityCheck(createBashTool(), spawnAvailable) },
9641
10137
  {
9642
10138
  name: "Read",
9643
10139
  tool: createFileReadTool({ storage: storage.workspace, tracker: fileTracker })
@@ -9683,6 +10179,21 @@ function buildToolRegistry(options) {
9683
10179
  childRegistry.register(skillTool);
9684
10180
  }
9685
10181
  }
10182
+ if (options.apiConfig !== void 0 && options.apiConfig.services.length > 0) {
10183
+ if (!disabled.has("ApiCall") && (wantAll || enabled.has("ApiCall"))) {
10184
+ const apiTool = createApiCallTool({
10185
+ services: options.apiConfig.services,
10186
+ ...options.fetch !== void 0 ? { fetch: options.fetch } : {},
10187
+ ...options.apiConfig.env !== void 0 ? { env: options.apiConfig.env } : {},
10188
+ ...options.apiConfig.resolveAuth !== void 0 ? { resolveAuth: options.apiConfig.resolveAuth } : {},
10189
+ ...options.apiConfig.onRequest !== void 0 ? { onRequest: options.apiConfig.onRequest } : {},
10190
+ ...options.apiConfig.onResponse !== void 0 ? { onResponse: options.apiConfig.onResponse } : {},
10191
+ ...options.apiConfig.maxResponseBytes !== void 0 ? { maxResponseBytes: options.apiConfig.maxResponseBytes } : {}
10192
+ });
10193
+ registry.register(apiTool);
10194
+ childRegistry.register(apiTool);
10195
+ }
10196
+ }
9686
10197
  const agentTool = createAgentTool({
9687
10198
  storage: storage.workspace,
9688
10199
  client,
@@ -9805,6 +10316,8 @@ function resolveApiKey(config) {
9805
10316
  buildSystemPrompt,
9806
10317
  buildWorkerAgent,
9807
10318
  canSpawnProcesses,
10319
+ capabilityStub,
10320
+ createApiCallTool,
9808
10321
  createLogger,
9809
10322
  createModelAdapter,
9810
10323
  createSendMessageTool,
@@ -9836,6 +10349,7 @@ function resolveApiKey(config) {
9836
10349
  synthesizeSpec,
9837
10350
  toResponse,
9838
10351
  tryParseJSON,
9839
- validateOutput
10352
+ validateOutput,
10353
+ withCapabilityCheck
9840
10354
  });
9841
10355
  //# sourceMappingURL=index.cjs.map