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.js CHANGED
@@ -24,23 +24,23 @@ var init_esm_shims = __esm({
24
24
  });
25
25
 
26
26
  // src/orchestrator/types.ts
27
- import { z as z23 } from "zod";
27
+ import { z as z25 } from "zod";
28
28
  var PlanStepSchema, PlanSchema;
29
29
  var init_types = __esm({
30
30
  "src/orchestrator/types.ts"() {
31
31
  "use strict";
32
32
  init_esm_shims();
33
- PlanStepSchema = z23.object({
34
- id: z23.string().min(1),
35
- description: z23.string().min(1),
36
- action: z23.enum(["research", "implement", "verify", "review", "custom"]),
37
- files: z23.array(z23.string()).optional(),
38
- spec: z23.string().optional(),
39
- dependsOn: z23.array(z23.string()).optional()
33
+ PlanStepSchema = z25.object({
34
+ id: z25.string().min(1),
35
+ description: z25.string().min(1),
36
+ action: z25.enum(["research", "implement", "verify", "review", "custom"]),
37
+ files: z25.array(z25.string()).optional(),
38
+ spec: z25.string().optional(),
39
+ dependsOn: z25.array(z25.string()).optional()
40
40
  });
41
- PlanSchema = z23.object({
42
- summary: z23.string().min(1),
43
- steps: z23.array(PlanStepSchema).min(1)
41
+ PlanSchema = z25.object({
42
+ summary: z25.string().min(1),
43
+ steps: z25.array(PlanStepSchema).min(1)
44
44
  });
45
45
  }
46
46
  });
@@ -1014,6 +1014,42 @@ var LoggingConfigResolved = z.object({
1014
1014
  level: LogLevelEnum,
1015
1015
  sink: LogSinkSchema
1016
1016
  }).strict();
1017
+ var ApiHttpMethodEnum = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]);
1018
+ var ApiAuthSchema = z.discriminatedUnion("type", [
1019
+ z.object({ type: z.literal("none") }).strict(),
1020
+ z.object({ type: z.literal("bearer"), tokenRef: z.string().min(1) }).strict(),
1021
+ z.object({
1022
+ type: z.literal("header"),
1023
+ name: z.string().min(1),
1024
+ valueRef: z.string().min(1)
1025
+ }).strict(),
1026
+ z.object({
1027
+ type: z.literal("basic"),
1028
+ userRef: z.string().min(1),
1029
+ passRef: z.string().min(1)
1030
+ }).strict(),
1031
+ z.object({ type: z.literal("custom"), id: z.string().min(1) }).strict()
1032
+ ]);
1033
+ var ApiServiceSchema = z.object({
1034
+ name: z.string().min(1),
1035
+ description: z.string().optional(),
1036
+ baseUrl: z.string().url(),
1037
+ auth: ApiAuthSchema.optional(),
1038
+ // Allow both strings and RegExp values via z.union — RegExp is
1039
+ // serialized as its source when cloned, so we accept both.
1040
+ allowedPaths: z.array(z.union([z.string(), z.instanceof(RegExp)])).optional(),
1041
+ allowedMethods: z.array(ApiHttpMethodEnum).optional(),
1042
+ defaultHeaders: z.record(z.string(), z.string()).optional(),
1043
+ maxBodyBytes: z.number().int().positive().optional()
1044
+ }).strict();
1045
+ var ApiConfigResolved = z.object({
1046
+ services: z.array(ApiServiceSchema),
1047
+ maxResponseBytes: z.number().int().positive().optional()
1048
+ }).strict();
1049
+ var RunnerConfigResolved = z.object({
1050
+ url: z.string().url(),
1051
+ secret: z.string().min(1, "runner.secret cannot be empty")
1052
+ }).strict();
1017
1053
  var ResolvedConfigSchema = z.object({
1018
1054
  model: ModelConfigResolved,
1019
1055
  storage: StorageConfigResolved,
@@ -1029,7 +1065,9 @@ var ResolvedConfigSchema = z.object({
1029
1065
  permissions: PermissionsConfigResolved,
1030
1066
  compaction: CompactionConfigResolved,
1031
1067
  coordinator: CoordinatorConfigResolved,
1032
- orchestrator: OrchestratorConfigResolved
1068
+ orchestrator: OrchestratorConfigResolved,
1069
+ runner: RunnerConfigResolved.optional(),
1070
+ api: ApiConfigResolved.optional()
1033
1071
  }).strict();
1034
1072
  var R2ConfigUser = R2ConfigResolved.partial();
1035
1073
  var ModelConfigUser = ModelConfigResolved.partial();
@@ -1070,6 +1108,8 @@ var McpConfigUser = z.object({
1070
1108
  shutdownTimeoutMs: z.number().int().positive().optional()
1071
1109
  }).strict();
1072
1110
  var PermissionsConfigUser = PermissionsConfigResolved.partial();
1111
+ var RunnerConfigUser = RunnerConfigResolved;
1112
+ var ApiConfigUser = ApiConfigResolved.partial();
1073
1113
  var CompactionConfigUser = CompactionConfigResolved.partial();
1074
1114
  var CoordinatorConfigUser = CoordinatorConfigResolved.partial();
1075
1115
  var OrchestratorConfigUser = OrchestratorConfigResolved.deepPartial();
@@ -1088,7 +1128,9 @@ var UserConfigSchema = z.object({
1088
1128
  permissions: PermissionsConfigUser.optional(),
1089
1129
  compaction: CompactionConfigUser.optional(),
1090
1130
  coordinator: CoordinatorConfigUser.optional(),
1091
- orchestrator: OrchestratorConfigUser.optional()
1131
+ orchestrator: OrchestratorConfigUser.optional(),
1132
+ runner: RunnerConfigUser.optional(),
1133
+ api: ApiConfigUser.optional()
1092
1134
  }).strict();
1093
1135
 
1094
1136
  // src/config/merge.ts
@@ -1133,10 +1175,36 @@ function deepMerge(base, override) {
1133
1175
  }
1134
1176
  return result;
1135
1177
  }
1178
+ var API_RUNTIME_KEYS = ["env", "resolveAuth", "onRequest", "onResponse"];
1179
+ function splitApiRuntime(user) {
1180
+ const api = user.api;
1181
+ if (api === void 0) return { stripped: user, runtime: {} };
1182
+ const runtime = {};
1183
+ const schemaSafe = {};
1184
+ for (const [k, v] of Object.entries(api)) {
1185
+ if (API_RUNTIME_KEYS.includes(k)) {
1186
+ runtime[k] = v;
1187
+ } else {
1188
+ schemaSafe[k] = v;
1189
+ }
1190
+ }
1191
+ const clone = { ...user, api: schemaSafe };
1192
+ return { stripped: clone, runtime };
1193
+ }
1136
1194
  function mergeConfig(user) {
1137
- const validatedUser = UserConfigSchema.parse(user);
1195
+ const { stripped, runtime } = splitApiRuntime(user);
1196
+ const validatedUser = UserConfigSchema.parse(stripped);
1138
1197
  const merged = deepMerge(DEFAULTS, validatedUser);
1139
- return ResolvedConfigSchema.parse(merged);
1198
+ const resolved = ResolvedConfigSchema.parse(merged);
1199
+ if (resolved.api !== void 0 && Object.keys(runtime).length > 0) {
1200
+ const mutableResolved = resolved;
1201
+ mutableResolved.api = {
1202
+ ...resolved.api,
1203
+ ...runtime
1204
+ };
1205
+ } else if (Object.keys(runtime).length > 0) {
1206
+ }
1207
+ return resolved;
1140
1208
  }
1141
1209
 
1142
1210
  // src/engine/engine.ts
@@ -1585,8 +1653,8 @@ function toAISdkMessages(messages) {
1585
1653
  textParts.push({ type: "text", text: block.text });
1586
1654
  }
1587
1655
  }
1588
- if (textParts.length > 0) out.push({ role: "user", content: textParts });
1589
1656
  if (toolResults.length > 0) out.push({ role: "tool", content: toolResults });
1657
+ if (textParts.length > 0) out.push({ role: "user", content: textParts });
1590
1658
  } else if (role === "assistant") {
1591
1659
  if (typeof content === "string") {
1592
1660
  out.push({ role: "assistant", content });
@@ -1784,6 +1852,68 @@ function hasProcessLifecycle() {
1784
1852
  return typeof process !== "undefined" && typeof process.on === "function" && typeof process.removeListener === "function" && detectRuntime() === "node";
1785
1853
  }
1786
1854
 
1855
+ // src/tools/capabilityStub.ts
1856
+ init_esm_shims();
1857
+ import { z as z2 } from "zod";
1858
+
1859
+ // src/tools/contract.ts
1860
+ init_esm_shims();
1861
+ function defineTool(tool) {
1862
+ return tool;
1863
+ }
1864
+ var ToolRegistry = class {
1865
+ tools = /* @__PURE__ */ new Map();
1866
+ register(tool) {
1867
+ if (typeof tool.name !== "string" || tool.name.length === 0) {
1868
+ throw new Error("ToolRegistry: tool.name must be a non-empty string");
1869
+ }
1870
+ if (this.tools.has(tool.name)) {
1871
+ throw new Error(`ToolRegistry: "${tool.name}" is already registered`);
1872
+ }
1873
+ this.tools.set(tool.name, tool);
1874
+ }
1875
+ registerAll(tools) {
1876
+ for (const tool of tools) this.register(tool);
1877
+ }
1878
+ unregister(name) {
1879
+ this.tools.delete(name);
1880
+ }
1881
+ get(name) {
1882
+ return this.tools.get(name);
1883
+ }
1884
+ has(name) {
1885
+ return this.tools.has(name);
1886
+ }
1887
+ list() {
1888
+ return Array.from(this.tools.values());
1889
+ }
1890
+ count() {
1891
+ return this.tools.size;
1892
+ }
1893
+ };
1894
+
1895
+ // src/tools/capabilityStub.ts
1896
+ var anyInput = z2.unknown();
1897
+ function capabilityStub(original) {
1898
+ return defineTool({
1899
+ name: original.name,
1900
+ description: original.description,
1901
+ inputSchema: anyInput,
1902
+ isCapabilityStub: true,
1903
+ execute: async () => ({
1904
+ isError: true,
1905
+ 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.`,
1906
+ metadata: { capabilityMissing: original.name }
1907
+ })
1908
+ });
1909
+ }
1910
+ function withCapabilityCheck(tool, spawnAvailable) {
1911
+ if (tool.requiresNode === true && !spawnAvailable) {
1912
+ return capabilityStub(tool);
1913
+ }
1914
+ return tool;
1915
+ }
1916
+
1787
1917
  // src/subagent/registry.ts
1788
1918
  init_esm_shims();
1789
1919
 
@@ -1966,7 +2096,7 @@ var SubagentRegistry = class _SubagentRegistry {
1966
2096
 
1967
2097
  // src/tools/agent.ts
1968
2098
  init_esm_shims();
1969
- import { z as z5 } from "zod";
2099
+ import { z as z6 } from "zod";
1970
2100
 
1971
2101
  // src/subagent/runner.ts
1972
2102
  init_esm_shims();
@@ -1977,48 +2107,49 @@ import { zodToJsonSchema } from "zod-to-json-schema";
1977
2107
 
1978
2108
  // src/transcript/snapshot.ts
1979
2109
  init_esm_shims();
1980
- import { z as z2 } from "zod";
2110
+ import { z as z3 } from "zod";
1981
2111
  var SNAPSHOT_FILENAME = "snapshot.json";
1982
- var TokenUsageSchema = z2.object({
1983
- input: z2.number().int().nonnegative(),
1984
- output: z2.number().int().nonnegative(),
1985
- cacheCreationInput: z2.number().int().nonnegative().optional(),
1986
- cacheReadInput: z2.number().int().nonnegative().optional()
2112
+ var TokenUsageSchema = z3.object({
2113
+ input: z3.number().int().nonnegative(),
2114
+ output: z3.number().int().nonnegative(),
2115
+ cacheCreationInput: z3.number().int().nonnegative().optional(),
2116
+ cacheReadInput: z3.number().int().nonnegative().optional()
1987
2117
  }).strict();
1988
- var PendingToolCallSchema = z2.object({
1989
- toolName: z2.string().min(1),
1990
- toolUseId: z2.string().min(1),
1991
- input: z2.unknown(),
1992
- calledAt: z2.string().datetime({ offset: true })
2118
+ var PendingToolCallSchema = z3.object({
2119
+ toolName: z3.string().min(1),
2120
+ toolUseId: z3.string().min(1),
2121
+ input: z3.unknown(),
2122
+ calledAt: z3.string().datetime({ offset: true })
1993
2123
  }).strict();
1994
- var PendingSubagentSchema = z2.lazy(
1995
- () => z2.object({
1996
- subagentType: z2.string().min(1),
1997
- parentToolUseId: z2.string().min(1),
2124
+ var PendingSubagentSchema = z3.lazy(
2125
+ () => z3.object({
2126
+ subagentType: z3.string().min(1),
2127
+ parentToolUseId: z3.string().min(1),
1998
2128
  childSnapshot: RunSnapshotSchema
1999
2129
  }).strict()
2000
2130
  );
2001
- var RunSnapshotSchema = z2.lazy(
2002
- () => z2.object({
2003
- version: z2.literal(1),
2004
- status: z2.literal("paused"),
2005
- runId: z2.string().min(1),
2006
- nodeId: z2.string().min(1),
2007
- pausedAt: z2.string().datetime({ offset: true }),
2008
- pauseReason: z2.enum([
2131
+ var RunSnapshotSchema = z3.lazy(
2132
+ () => z3.object({
2133
+ version: z3.literal(1),
2134
+ status: z3.literal("paused"),
2135
+ runId: z3.string().min(1),
2136
+ nodeId: z3.string().min(1),
2137
+ pausedAt: z3.string().datetime({ offset: true }),
2138
+ pauseReason: z3.enum([
2009
2139
  "gate_required",
2010
2140
  "subagent_gate_required",
2141
+ "handoff_to_runner",
2011
2142
  "max_turns",
2012
2143
  "explicit",
2013
2144
  "timeout"
2014
2145
  ]),
2015
- messageCount: z2.number().int().nonnegative(),
2016
- lastShardIndex: z2.number().int().nonnegative(),
2017
- lastMessageUuid: z2.string().uuid(),
2146
+ messageCount: z3.number().int().nonnegative(),
2147
+ lastShardIndex: z3.number().int().nonnegative(),
2148
+ lastMessageUuid: z3.string().uuid(),
2018
2149
  pendingToolCall: PendingToolCallSchema.optional(),
2019
2150
  pendingSubagent: PendingSubagentSchema.optional(),
2020
2151
  tokensUsedSoFar: TokenUsageSchema,
2021
- turnsUsed: z2.number().int().nonnegative()
2152
+ turnsUsed: z3.number().int().nonnegative()
2022
2153
  }).strict()
2023
2154
  );
2024
2155
  function snapshotPath(logPath) {
@@ -2746,6 +2877,27 @@ async function agentLoop(options) {
2746
2877
  }
2747
2878
  }
2748
2879
  }
2880
+ if (options.handoffToRunner === true) {
2881
+ for (const call of toolCallsToDispatch) {
2882
+ const tool = options.registry?.get(call.name);
2883
+ if (tool?.isCapabilityStub === true) {
2884
+ const paused = await pauseHere({
2885
+ ctx,
2886
+ transcript,
2887
+ reason: "handoff_to_runner",
2888
+ pendingToolCall: {
2889
+ toolName: call.name,
2890
+ toolUseId: call.id,
2891
+ input: call.input,
2892
+ calledAt: (/* @__PURE__ */ new Date()).toISOString()
2893
+ },
2894
+ storage: options.storage,
2895
+ subagentRegistry: options.subagentRegistry
2896
+ });
2897
+ return paused;
2898
+ }
2899
+ }
2900
+ }
2749
2901
  const firstTool = toolCallsToDispatch[0]?.name;
2750
2902
  await fireProgress("tool_dispatch", firstTool);
2751
2903
  const streamExec = new StreamingToolExecutor(executor);
@@ -2756,6 +2908,10 @@ async function agentLoop(options) {
2756
2908
  }
2757
2909
  try {
2758
2910
  for await (const { id, result } of streamExec.results()) {
2911
+ const missing = result.metadata?.capabilityMissing;
2912
+ if (typeof missing === "string") {
2913
+ ctx.recordCapabilityMissing(missing);
2914
+ }
2759
2915
  await ctx.addToolResult(id, truncateToolResult(result.content), result.isError === true);
2760
2916
  }
2761
2917
  } catch (err) {
@@ -3014,6 +3170,12 @@ var RunContext = class {
3014
3170
  turnCount = 0;
3015
3171
  tokensUsed = { input: 0, output: 0 };
3016
3172
  lastUuid = null;
3173
+ /**
3174
+ * Plan 019 — names of tools whose capability-stub returned an
3175
+ * `isError` result during this run. Aggregated and surfaced on the
3176
+ * response's `meta.capabilitiesMissing` (deduped, insertion-ordered).
3177
+ */
3178
+ capabilitiesMissing = /* @__PURE__ */ new Set();
3017
3179
  constructor(options) {
3018
3180
  this.runId = options.runId;
3019
3181
  this.nodeId = options.nodeId;
@@ -3054,6 +3216,42 @@ var RunContext = class {
3054
3216
  this.episodes.logTurn(this.turnCount, "assistant", summary);
3055
3217
  }
3056
3218
  }
3219
+ /**
3220
+ * Inject a tool_result + a follow-up text block as a SINGLE user
3221
+ * message. Used by the resume() synthetic-release path when a
3222
+ * paused run is resumed without an explicit `gateAnswer`: we need
3223
+ * to both satisfy the tool_use↔tool_result pairing AND give the
3224
+ * model a retry instruction, in the same user turn.
3225
+ *
3226
+ * Splitting this into `addToolResult(...) + addUserMessage(...)`
3227
+ * produces two consecutive user messages, which the AI SDK's
3228
+ * openai-compatible adapter rejects with
3229
+ * `MissingToolResultsError`. Combining them into one user message
3230
+ * is the portable shape that works on every provider we support.
3231
+ *
3232
+ * Writes a single `user` transcript entry carrying the mixed
3233
+ * content, so `rebuildMessagesFromEntries` reconstructs the same
3234
+ * single-message shape on resume.
3235
+ */
3236
+ async addMixedUserMessage(blocks) {
3237
+ this.messages.push({ role: "user", content: blocks });
3238
+ await this.writeEntry({
3239
+ type: "user",
3240
+ uuid: this.nextUuid(),
3241
+ parentUuid: this.lastUuid,
3242
+ ts: this.now(),
3243
+ message: { role: "user", content: blocks }
3244
+ });
3245
+ const summary = blocks.map((b) => {
3246
+ const x = b;
3247
+ if (x.type === "text") return x.text ?? "";
3248
+ if (x.type === "tool_result") {
3249
+ return typeof x.content === "string" ? x.content : JSON.stringify(x.content);
3250
+ }
3251
+ return "";
3252
+ }).filter((s) => s.length > 0).join("\n");
3253
+ this.episodes?.logTurn(this.turnCount, "user", summary);
3254
+ }
3057
3255
  /**
3058
3256
  * Append a tool result to the conversation. The Anthropic Messages
3059
3257
  * API requires tool_result blocks to be wrapped in a user message,
@@ -3099,6 +3297,14 @@ var RunContext = class {
3099
3297
  getTokensUsed() {
3100
3298
  return this.tokensUsed;
3101
3299
  }
3300
+ /** Plan 019 — record that a capability-stubbed tool fired during this run. */
3301
+ recordCapabilityMissing(toolName) {
3302
+ this.capabilitiesMissing.add(toolName);
3303
+ }
3304
+ /** Plan 019 — capability-stubbed tool names observed this run (deduped). */
3305
+ getCapabilitiesMissing() {
3306
+ return [...this.capabilitiesMissing];
3307
+ }
3102
3308
  shouldContinue() {
3103
3309
  return this.turnCount < this.maxTurns;
3104
3310
  }
@@ -3272,9 +3478,9 @@ init_esm_shims();
3272
3478
 
3273
3479
  // src/transcript/entries.ts
3274
3480
  init_esm_shims();
3275
- import { z as z3 } from "zod";
3276
- var UuidSchema = z3.string().uuid();
3277
- var IsoTsSchema = z3.string().datetime({ offset: true });
3481
+ import { z as z4 } from "zod";
3482
+ var UuidSchema = z4.string().uuid();
3483
+ var IsoTsSchema = z4.string().datetime({ offset: true });
3278
3484
  var TimelineBase = {
3279
3485
  uuid: UuidSchema,
3280
3486
  parentUuid: UuidSchema.nullable(),
@@ -3284,55 +3490,55 @@ var SessionBase = {
3284
3490
  uuid: UuidSchema,
3285
3491
  ts: IsoTsSchema
3286
3492
  };
3287
- var MessageSchema = z3.object({
3288
- role: z3.enum(["user", "assistant"]),
3289
- content: z3.array(z3.unknown())
3493
+ var MessageSchema = z4.object({
3494
+ role: z4.enum(["user", "assistant"]),
3495
+ content: z4.array(z4.unknown())
3290
3496
  }).strict();
3291
- var UserEntrySchema = z3.object({
3292
- type: z3.literal("user"),
3497
+ var UserEntrySchema = z4.object({
3498
+ type: z4.literal("user"),
3293
3499
  ...TimelineBase,
3294
3500
  message: MessageSchema
3295
3501
  }).strict();
3296
- var AssistantEntrySchema = z3.object({
3297
- type: z3.literal("assistant"),
3502
+ var AssistantEntrySchema = z4.object({
3503
+ type: z4.literal("assistant"),
3298
3504
  ...TimelineBase,
3299
3505
  message: MessageSchema
3300
3506
  }).strict();
3301
- var ToolResultEntrySchema = z3.object({
3302
- type: z3.literal("tool_result"),
3507
+ var ToolResultEntrySchema = z4.object({
3508
+ type: z4.literal("tool_result"),
3303
3509
  ...TimelineBase,
3304
- toolUseId: z3.string().min(1),
3305
- content: z3.unknown(),
3306
- isError: z3.boolean().optional()
3510
+ toolUseId: z4.string().min(1),
3511
+ content: z4.unknown(),
3512
+ isError: z4.boolean().optional()
3307
3513
  }).strict();
3308
- var SubagentSpawnEntrySchema = z3.object({
3309
- type: z3.literal("subagent_spawn"),
3514
+ var SubagentSpawnEntrySchema = z4.object({
3515
+ type: z4.literal("subagent_spawn"),
3310
3516
  ...TimelineBase,
3311
- agentId: z3.string().min(1),
3312
- agentType: z3.string().min(1)
3517
+ agentId: z4.string().min(1),
3518
+ agentType: z4.string().min(1)
3313
3519
  }).strict();
3314
- var SubagentDoneEntrySchema = z3.object({
3315
- type: z3.literal("subagent_done"),
3520
+ var SubagentDoneEntrySchema = z4.object({
3521
+ type: z4.literal("subagent_done"),
3316
3522
  ...TimelineBase,
3317
- agentId: z3.string().min(1),
3318
- output: z3.string()
3523
+ agentId: z4.string().min(1),
3524
+ output: z4.string()
3319
3525
  }).strict();
3320
- var MetaEntrySchema = z3.object({
3321
- type: z3.literal("meta"),
3526
+ var MetaEntrySchema = z4.object({
3527
+ type: z4.literal("meta"),
3322
3528
  ...SessionBase,
3323
- key: z3.string().min(1),
3324
- value: z3.unknown()
3529
+ key: z4.string().min(1),
3530
+ value: z4.unknown()
3325
3531
  }).strict();
3326
- var ErrorEntrySchema = z3.object({
3327
- type: z3.literal("error"),
3532
+ var ErrorEntrySchema = z4.object({
3533
+ type: z4.literal("error"),
3328
3534
  ...SessionBase,
3329
- error: z3.object({
3330
- code: z3.string().min(1),
3331
- message: z3.string(),
3332
- stack: z3.string().optional()
3535
+ error: z4.object({
3536
+ code: z4.string().min(1),
3537
+ message: z4.string(),
3538
+ stack: z4.string().optional()
3333
3539
  }).strict()
3334
3540
  }).strict();
3335
- var EntrySchema = z3.discriminatedUnion("type", [
3541
+ var EntrySchema = z4.discriminatedUnion("type", [
3336
3542
  UserEntrySchema,
3337
3543
  AssistantEntrySchema,
3338
3544
  ToolResultEntrySchema,
@@ -3364,16 +3570,16 @@ function parseEntryLine(line) {
3364
3570
 
3365
3571
  // src/transcript/meta.ts
3366
3572
  init_esm_shims();
3367
- import { z as z4 } from "zod";
3368
- var TranscriptMetaSchema = z4.object({
3369
- version: z4.literal(1),
3370
- status: z4.enum(["pending", "running", "paused", "done", "failed"]),
3371
- startedAt: z4.string().datetime({ offset: true }),
3372
- updatedAt: z4.string().datetime({ offset: true }),
3373
- turnCount: z4.number().int().nonnegative(),
3374
- messageCount: z4.number().int().nonnegative(),
3375
- lastShardIndex: z4.number().int().nonnegative().nullable(),
3376
- shardCount: z4.number().int().nonnegative()
3573
+ import { z as z5 } from "zod";
3574
+ var TranscriptMetaSchema = z5.object({
3575
+ version: z5.literal(1),
3576
+ status: z5.enum(["pending", "running", "paused", "done", "failed"]),
3577
+ startedAt: z5.string().datetime({ offset: true }),
3578
+ updatedAt: z5.string().datetime({ offset: true }),
3579
+ turnCount: z5.number().int().nonnegative(),
3580
+ messageCount: z5.number().int().nonnegative(),
3581
+ lastShardIndex: z5.number().int().nonnegative().nullable(),
3582
+ shardCount: z5.number().int().nonnegative()
3377
3583
  }).strict();
3378
3584
  var META_FILENAME = "meta.json";
3379
3585
  function metaPath(logPath) {
@@ -3721,47 +3927,11 @@ Output format:
3721
3927
  Directive: ${directive}`;
3722
3928
  }
3723
3929
 
3724
- // src/tools/contract.ts
3725
- init_esm_shims();
3726
- function defineTool(tool) {
3727
- return tool;
3728
- }
3729
- var ToolRegistry = class {
3730
- tools = /* @__PURE__ */ new Map();
3731
- register(tool) {
3732
- if (typeof tool.name !== "string" || tool.name.length === 0) {
3733
- throw new Error("ToolRegistry: tool.name must be a non-empty string");
3734
- }
3735
- if (this.tools.has(tool.name)) {
3736
- throw new Error(`ToolRegistry: "${tool.name}" is already registered`);
3737
- }
3738
- this.tools.set(tool.name, tool);
3739
- }
3740
- registerAll(tools) {
3741
- for (const tool of tools) this.register(tool);
3742
- }
3743
- unregister(name) {
3744
- this.tools.delete(name);
3745
- }
3746
- get(name) {
3747
- return this.tools.get(name);
3748
- }
3749
- has(name) {
3750
- return this.tools.has(name);
3751
- }
3752
- list() {
3753
- return Array.from(this.tools.values());
3754
- }
3755
- count() {
3756
- return this.tools.size;
3757
- }
3758
- };
3759
-
3760
3930
  // src/tools/agent.ts
3761
- var inputSchema = z5.object({
3762
- description: z5.string().min(1),
3763
- subagent_type: z5.string().optional(),
3764
- run_in_background: z5.boolean().optional()
3931
+ var inputSchema = z6.object({
3932
+ description: z6.string().min(1),
3933
+ subagent_type: z6.string().optional(),
3934
+ run_in_background: z6.boolean().optional()
3765
3935
  });
3766
3936
  function createAgentTool(options) {
3767
3937
  if (options.agents.length === 0) {
@@ -3966,7 +4136,7 @@ function handlePausedResult(result, agentId, subagentType) {
3966
4136
 
3967
4137
  // src/tools/bash.ts
3968
4138
  init_esm_shims();
3969
- import { z as z6 } from "zod";
4139
+ import { z as z7 } from "zod";
3970
4140
  var _spawn = null;
3971
4141
  async function getSpawn() {
3972
4142
  if (_spawn === null) {
@@ -3978,15 +4148,16 @@ async function getSpawn() {
3978
4148
  var MAX_OUTPUT_BYTES = 512 * 1024;
3979
4149
  var SIGKILL_GRACE_MS = 500;
3980
4150
  var BLOCKED_DEVICE_PATHS = /\b(\/dev\/zero|\/dev\/random|\/dev\/urandom|\/proc\/kcore|\/dev\/sda|\/dev\/mem)\b/;
3981
- var inputSchema2 = z6.object({
3982
- command: z6.string().min(1),
3983
- cwd: z6.string().min(1).optional()
4151
+ var inputSchema2 = z7.object({
4152
+ command: z7.string().min(1),
4153
+ cwd: z7.string().min(1).optional()
3984
4154
  });
3985
4155
  function createBashTool() {
3986
4156
  return defineTool({
3987
4157
  name: "Bash",
3988
4158
  description: "Execute a shell command via /bin/sh -c. Returns combined stdout+stderr and the exit code.",
3989
4159
  inputSchema: inputSchema2,
4160
+ requiresNode: true,
3990
4161
  execute: async ({ command, cwd }, ctx) => {
3991
4162
  if (BLOCKED_DEVICE_PATHS.test(command)) {
3992
4163
  return {
@@ -4066,10 +4237,10 @@ function createBashTool() {
4066
4237
 
4067
4238
  // src/tools/sendMessage.ts
4068
4239
  init_esm_shims();
4069
- import { z as z7 } from "zod";
4070
- var inputSchema3 = z7.object({
4071
- to: z7.string().min(1).describe("Agent name (subagent_type) or agentId to send the message to."),
4072
- message: z7.string().min(1).describe("Message content to deliver to the target agent.")
4240
+ import { z as z8 } from "zod";
4241
+ var inputSchema3 = z8.object({
4242
+ to: z8.string().min(1).describe("Agent name (subagent_type) or agentId to send the message to."),
4243
+ message: z8.string().min(1).describe("Message content to deliver to the target agent.")
4073
4244
  });
4074
4245
  function createSendMessageTool(options) {
4075
4246
  return defineTool({
@@ -4120,12 +4291,12 @@ function createSendMessageTool(options) {
4120
4291
 
4121
4292
  // src/tools/fileEdit.ts
4122
4293
  init_esm_shims();
4123
- import { z as z8 } from "zod";
4124
- var inputSchema4 = z8.object({
4125
- path: z8.string().min(1),
4126
- old_string: z8.string().min(1),
4127
- new_string: z8.string(),
4128
- replace_all: z8.boolean().optional()
4294
+ import { z as z9 } from "zod";
4295
+ var inputSchema4 = z9.object({
4296
+ path: z9.string().min(1),
4297
+ old_string: z9.string().min(1),
4298
+ new_string: z9.string(),
4299
+ replace_all: z9.boolean().optional()
4129
4300
  });
4130
4301
  function createFileEditTool(storage) {
4131
4302
  return defineTool({
@@ -4209,13 +4380,13 @@ function normalizeQuotes(s) {
4209
4380
 
4210
4381
  // src/tools/fileRead.ts
4211
4382
  init_esm_shims();
4212
- import { z as z9 } from "zod";
4213
- var inputSchema5 = z9.object({
4214
- path: z9.string().min(1),
4215
- offset: z9.number().int().positive().optional(),
4216
- limit: z9.number().int().positive().optional(),
4383
+ import { z as z10 } from "zod";
4384
+ var inputSchema5 = z10.object({
4385
+ path: z10.string().min(1),
4386
+ offset: z10.number().int().positive().optional(),
4387
+ limit: z10.number().int().positive().optional(),
4217
4388
  /** PDF page range, e.g. "1-5", "3", "10-20". Max 20 pages per request. */
4218
- pages: z9.string().optional()
4389
+ pages: z10.string().optional()
4219
4390
  });
4220
4391
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"]);
4221
4392
  var PDF_EXTENSION = ".pdf";
@@ -4383,7 +4554,7 @@ async function readImage(storage, filePath, ext) {
4383
4554
  ".svg": "image/svg+xml"
4384
4555
  };
4385
4556
  const mimeType = mimeMap[ext] ?? "application/octet-stream";
4386
- const base64 = buffer.toString("base64");
4557
+ const base642 = buffer.toString("base64");
4387
4558
  return {
4388
4559
  content: `[Image: ${filePath} (${mimeType}, ${(buffer.length / 1024).toFixed(1)}KB)]
4389
4560
 
@@ -4392,17 +4563,17 @@ Base64 data is included in the metadata for visual analysis.`,
4392
4563
  format: "image",
4393
4564
  mimeType,
4394
4565
  bytes: buffer.length,
4395
- base64
4566
+ base64: base642
4396
4567
  }
4397
4568
  };
4398
4569
  }
4399
4570
 
4400
4571
  // src/tools/fileWrite.ts
4401
4572
  init_esm_shims();
4402
- import { z as z10 } from "zod";
4403
- var inputSchema6 = z10.object({
4404
- path: z10.string().min(1),
4405
- content: z10.string()
4573
+ import { z as z11 } from "zod";
4574
+ var inputSchema6 = z11.object({
4575
+ path: z11.string().min(1),
4576
+ content: z11.string()
4406
4577
  });
4407
4578
  function createFileWriteTool(storageOrOptions) {
4408
4579
  const opts = "readFile" in storageOrOptions ? { storage: storageOrOptions } : storageOrOptions;
@@ -4441,7 +4612,7 @@ function createFileWriteTool(storageOrOptions) {
4441
4612
  // src/tools/glob.ts
4442
4613
  init_esm_shims();
4443
4614
  import picomatch from "picomatch";
4444
- import { z as z11 } from "zod";
4615
+ import { z as z12 } from "zod";
4445
4616
 
4446
4617
  // src/tools/walkAdapter.ts
4447
4618
  init_esm_shims();
@@ -4478,9 +4649,9 @@ async function* walkAdapter(adapter, startPath, options = {}) {
4478
4649
  }
4479
4650
 
4480
4651
  // src/tools/glob.ts
4481
- var inputSchema7 = z11.object({
4482
- pattern: z11.string().min(1),
4483
- path: z11.string().optional()
4652
+ var inputSchema7 = z12.object({
4653
+ pattern: z12.string().min(1),
4654
+ path: z12.string().optional()
4484
4655
  });
4485
4656
  var MAX_RESULTS = 1e3;
4486
4657
  function createGlobTool(storage) {
@@ -4533,22 +4704,22 @@ function createGlobTool(storage) {
4533
4704
  // src/tools/grep.ts
4534
4705
  init_esm_shims();
4535
4706
  import picomatch2 from "picomatch";
4536
- import { z as z12 } from "zod";
4537
- var inputSchema8 = z12.object({
4538
- pattern: z12.string().min(1),
4539
- path: z12.string().optional(),
4540
- glob: z12.string().optional(),
4541
- type: z12.string().optional(),
4542
- output_mode: z12.enum(["content", "files_with_matches", "count"]).optional(),
4543
- "-i": z12.boolean().optional(),
4544
- "-n": z12.boolean().optional(),
4545
- "-A": z12.number().int().nonnegative().optional(),
4546
- "-B": z12.number().int().nonnegative().optional(),
4547
- "-C": z12.number().int().nonnegative().optional(),
4548
- context: z12.number().int().nonnegative().optional(),
4549
- multiline: z12.boolean().optional(),
4550
- head_limit: z12.number().int().nonnegative().optional(),
4551
- offset: z12.number().int().nonnegative().optional()
4707
+ import { z as z13 } from "zod";
4708
+ var inputSchema8 = z13.object({
4709
+ pattern: z13.string().min(1),
4710
+ path: z13.string().optional(),
4711
+ glob: z13.string().optional(),
4712
+ type: z13.string().optional(),
4713
+ output_mode: z13.enum(["content", "files_with_matches", "count"]).optional(),
4714
+ "-i": z13.boolean().optional(),
4715
+ "-n": z13.boolean().optional(),
4716
+ "-A": z13.number().int().nonnegative().optional(),
4717
+ "-B": z13.number().int().nonnegative().optional(),
4718
+ "-C": z13.number().int().nonnegative().optional(),
4719
+ context: z13.number().int().nonnegative().optional(),
4720
+ multiline: z13.boolean().optional(),
4721
+ head_limit: z13.number().int().nonnegative().optional(),
4722
+ offset: z13.number().int().nonnegative().optional()
4552
4723
  });
4553
4724
  var MAX_FILES_SCANNED = 5e3;
4554
4725
  var MAX_MATCHES_PER_FILE = 100;
@@ -4772,10 +4943,10 @@ function formatJsResults(results, mode, limit) {
4772
4943
 
4773
4944
  // src/tools/webFetch.ts
4774
4945
  init_esm_shims();
4775
- import { z as z13 } from "zod";
4776
- var inputSchema9 = z13.object({
4777
- url: z13.string().url(),
4778
- prompt: z13.string().optional()
4946
+ import { z as z14 } from "zod";
4947
+ var inputSchema9 = z14.object({
4948
+ url: z14.string().url(),
4949
+ prompt: z14.string().optional()
4779
4950
  });
4780
4951
  var MAX_OUTPUT_BYTES2 = 256 * 1024;
4781
4952
  function createWebFetchTool(options = {}) {
@@ -4904,10 +5075,10 @@ function normalizePath(p) {
4904
5075
 
4905
5076
  // src/tools/webSearch.ts
4906
5077
  init_esm_shims();
4907
- import { z as z14 } from "zod";
4908
- var inputSchema10 = z14.object({
4909
- query: z14.string().min(2),
4910
- max_results: z14.number().int().positive().optional()
5078
+ import { z as z15 } from "zod";
5079
+ var inputSchema10 = z15.object({
5080
+ query: z15.string().min(2),
5081
+ max_results: z15.number().int().positive().optional()
4911
5082
  });
4912
5083
  function createWebSearchTool() {
4913
5084
  return defineTool({
@@ -4964,10 +5135,10 @@ function htmlToText2(html) {
4964
5135
 
4965
5136
  // src/tools/sleep.ts
4966
5137
  init_esm_shims();
4967
- import { z as z15 } from "zod";
4968
- var inputSchema11 = z15.object({
4969
- durationMs: z15.number().int().nonnegative().max(3e5),
4970
- reason: z15.string().optional()
5138
+ import { z as z16 } from "zod";
5139
+ var inputSchema11 = z16.object({
5140
+ durationMs: z16.number().int().nonnegative().max(3e5),
5141
+ reason: z16.string().optional()
4971
5142
  });
4972
5143
  function createSleepTool() {
4973
5144
  return defineTool({
@@ -5007,10 +5178,10 @@ function createSleepTool() {
5007
5178
 
5008
5179
  // src/tools/toolSearch.ts
5009
5180
  init_esm_shims();
5010
- import { z as z16 } from "zod";
5011
- var inputSchema12 = z16.object({
5012
- query: z16.string().min(1),
5013
- max_results: z16.number().int().positive().optional()
5181
+ import { z as z17 } from "zod";
5182
+ var inputSchema12 = z17.object({
5183
+ query: z17.string().min(1),
5184
+ max_results: z17.number().int().positive().optional()
5014
5185
  });
5015
5186
  function createToolSearchTool(registry) {
5016
5187
  return defineTool({
@@ -5053,11 +5224,11 @@ ${lines.join("\n")}`,
5053
5224
 
5054
5225
  // src/tools/memorize.ts
5055
5226
  init_esm_shims();
5056
- import { z as z17 } from "zod";
5057
- var inputSchema13 = z17.object({
5058
- text: z17.string().min(1),
5059
- kind: z17.enum(["rule", "lesson"]).default("lesson"),
5060
- topic: z17.string().optional()
5227
+ import { z as z18 } from "zod";
5228
+ var inputSchema13 = z18.object({
5229
+ text: z18.string().min(1),
5230
+ kind: z18.enum(["rule", "lesson"]).default("lesson"),
5231
+ topic: z18.string().optional()
5061
5232
  });
5062
5233
  function createMemorizeTool(memory) {
5063
5234
  return defineTool({
@@ -5089,11 +5260,11 @@ function createMemorizeTool(memory) {
5089
5260
 
5090
5261
  // src/tools/recall.ts
5091
5262
  init_esm_shims();
5092
- import { z as z18 } from "zod";
5093
- var inputSchema14 = z18.object({
5094
- query: z18.string().min(1),
5095
- scope: z18.enum(["identity", "rules", "lessons", "all"]).default("all"),
5096
- topic: z18.string().optional()
5263
+ import { z as z19 } from "zod";
5264
+ var inputSchema14 = z19.object({
5265
+ query: z19.string().min(1),
5266
+ scope: z19.enum(["identity", "rules", "lessons", "all"]).default("all"),
5267
+ topic: z19.string().optional()
5097
5268
  });
5098
5269
  function createRecallTool(memory) {
5099
5270
  return defineTool({
@@ -5148,13 +5319,13 @@ ${content}`,
5148
5319
 
5149
5320
  // src/tools/notebookEdit.ts
5150
5321
  init_esm_shims();
5151
- import { z as z19 } from "zod";
5152
- var inputSchema15 = z19.object({
5153
- notebook_path: z19.string().min(1),
5154
- edit_mode: z19.enum(["replace", "insert", "delete"]).default("replace"),
5155
- cell_index: z19.number().int().nonnegative(),
5156
- new_source: z19.string().optional(),
5157
- cell_type: z19.enum(["code", "markdown"]).optional()
5322
+ import { z as z20 } from "zod";
5323
+ var inputSchema15 = z20.object({
5324
+ notebook_path: z20.string().min(1),
5325
+ edit_mode: z20.enum(["replace", "insert", "delete"]).default("replace"),
5326
+ cell_index: z20.number().int().nonnegative(),
5327
+ new_source: z20.string().optional(),
5328
+ cell_type: z20.enum(["code", "markdown"]).optional()
5158
5329
  });
5159
5330
  function createNotebookEditTool(storage) {
5160
5331
  return defineTool({
@@ -5309,12 +5480,12 @@ var TaskStore = class {
5309
5480
 
5310
5481
  // src/tools/tasks/tools.ts
5311
5482
  init_esm_shims();
5312
- import { z as z20 } from "zod";
5313
- var TaskStatusEnum = z20.enum(["pending", "in_progress", "completed", "deleted"]);
5314
- var createSchema = z20.object({
5315
- subject: z20.string().min(1),
5316
- description: z20.string().default(""),
5317
- metadata: z20.record(z20.unknown()).optional()
5483
+ import { z as z21 } from "zod";
5484
+ var TaskStatusEnum = z21.enum(["pending", "in_progress", "completed", "deleted"]);
5485
+ var createSchema = z21.object({
5486
+ subject: z21.string().min(1),
5487
+ description: z21.string().default(""),
5488
+ metadata: z21.record(z21.unknown()).optional()
5318
5489
  });
5319
5490
  function createTaskCreateTool(store) {
5320
5491
  return defineTool({
@@ -5330,8 +5501,8 @@ function createTaskCreateTool(store) {
5330
5501
  }
5331
5502
  });
5332
5503
  }
5333
- var getSchema = z20.object({
5334
- taskId: z20.string().min(1)
5504
+ var getSchema = z21.object({
5505
+ taskId: z21.string().min(1)
5335
5506
  });
5336
5507
  function createTaskGetTool(store) {
5337
5508
  return defineTool({
@@ -5352,7 +5523,7 @@ ${task.description}`,
5352
5523
  }
5353
5524
  });
5354
5525
  }
5355
- var listSchema = z20.object({
5526
+ var listSchema = z21.object({
5356
5527
  status: TaskStatusEnum.optional()
5357
5528
  });
5358
5529
  function createTaskListTool(store) {
@@ -5378,12 +5549,12 @@ ${lines.join("\n")}`,
5378
5549
  }
5379
5550
  });
5380
5551
  }
5381
- var updateSchema = z20.object({
5382
- taskId: z20.string().min(1),
5383
- subject: z20.string().min(1).optional(),
5552
+ var updateSchema = z21.object({
5553
+ taskId: z21.string().min(1),
5554
+ subject: z21.string().min(1).optional(),
5384
5555
  status: TaskStatusEnum.optional(),
5385
- description: z20.string().optional(),
5386
- metadata: z20.record(z20.unknown()).optional()
5556
+ description: z21.string().optional(),
5557
+ metadata: z21.record(z21.unknown()).optional()
5387
5558
  });
5388
5559
  function createTaskUpdateTool(store) {
5389
5560
  return defineTool({
@@ -6232,7 +6403,7 @@ function wrapFetchWithHeadersProvider(baseFetch, headersProvider, staticHeaders)
6232
6403
 
6233
6404
  // src/mcp/toolAdapter.ts
6234
6405
  init_esm_shims();
6235
- import { z as z21 } from "zod";
6406
+ import { z as z22 } from "zod";
6236
6407
  function mcpToolName(serverName, toolName) {
6237
6408
  return `mcp__${serverName}__${toolName}`;
6238
6409
  }
@@ -6241,7 +6412,7 @@ function adaptMcpTool(client, serverName, def) {
6241
6412
  return defineTool({
6242
6413
  name: registeredName,
6243
6414
  description: def.description.length > 0 ? def.description : `MCP tool ${serverName}/${def.name}`,
6244
- inputSchema: z21.unknown(),
6415
+ inputSchema: z22.unknown(),
6245
6416
  // Pass the MCP server's JSON Schema through to Anthropic verbatim,
6246
6417
  // bypassing Zod-to-JSON-Schema conversion (which would produce `{}`
6247
6418
  // for our `z.unknown()` Zod schema).
@@ -7350,11 +7521,11 @@ ${issues.join("\n")}` };
7350
7521
 
7351
7522
  // src/skills/skillPage.ts
7352
7523
  init_esm_shims();
7353
- import { z as z22 } from "zod";
7524
+ import { z as z23 } from "zod";
7354
7525
  var SAFE_NAME3 = /^[a-zA-Z0-9_-]+$/;
7355
- var inputSchema16 = z22.object({
7356
- skill: z22.string().min(1),
7357
- page: z22.string().min(1).optional()
7526
+ var inputSchema16 = z23.object({
7527
+ skill: z23.string().min(1),
7528
+ page: z23.string().min(1).optional()
7358
7529
  });
7359
7530
  function createSkillPageTool(options) {
7360
7531
  return defineTool({
@@ -7410,6 +7581,203 @@ The skill "${skill}" has no pages.`;
7410
7581
  });
7411
7582
  }
7412
7583
 
7584
+ // src/tools/apiCall.ts
7585
+ init_esm_shims();
7586
+ import { z as z24 } from "zod";
7587
+ var ALL_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
7588
+ var DEFAULT_MAX_BODY_BYTES = 256 * 1024;
7589
+ var DEFAULT_MAX_RESPONSE_BYTES = 100 * 1024;
7590
+ function createApiCallTool(opts) {
7591
+ if (opts.services.length === 0) {
7592
+ throw new Error("createApiCallTool: services list must be non-empty");
7593
+ }
7594
+ const services = /* @__PURE__ */ new Map();
7595
+ for (const svc of opts.services) {
7596
+ if (services.has(svc.name)) {
7597
+ throw new Error(`createApiCallTool: duplicate service name "${svc.name}"`);
7598
+ }
7599
+ services.set(svc.name, svc);
7600
+ }
7601
+ const serviceNames = [...services.keys()];
7602
+ for (const svc of opts.services) {
7603
+ if (svc.auth?.type === "custom" && opts.resolveAuth === void 0) {
7604
+ throw new Error(
7605
+ `createApiCallTool: service "${svc.name}" uses custom auth (id: ${svc.auth.id}) but no resolveAuth was supplied`
7606
+ );
7607
+ }
7608
+ }
7609
+ const fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
7610
+ const maxResponseBytes = opts.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
7611
+ const inputSchema17 = z24.object({
7612
+ service: z24.enum(serviceNames),
7613
+ method: z24.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
7614
+ path: z24.string().regex(/^\//, "path must start with /"),
7615
+ query: z24.record(z24.string(), z24.string()).optional(),
7616
+ body: z24.unknown().optional(),
7617
+ headers: z24.record(z24.string(), z24.string()).optional()
7618
+ });
7619
+ const description = opts.toolDescription ?? `Call a configured external API. Services: ${serviceNames.join(", ")}. Auth is injected automatically \u2014 do not pass credentials via headers.`;
7620
+ return defineTool({
7621
+ name: opts.toolName ?? "ApiCall",
7622
+ description,
7623
+ inputSchema: inputSchema17,
7624
+ execute: async (input) => {
7625
+ const svc = services.get(input.service);
7626
+ if (!svc) {
7627
+ return errResult(`ERR_API_UNKNOWN_SERVICE: ${input.service}`);
7628
+ }
7629
+ const allowedMethods = svc.allowedMethods ?? ALL_METHODS;
7630
+ if (!allowedMethods.includes(input.method)) {
7631
+ return errResult(
7632
+ `ERR_API_METHOD_NOT_ALLOWED: ${input.method} not permitted for service ${svc.name}`
7633
+ );
7634
+ }
7635
+ if (!pathAllowed(input.path, svc.allowedPaths)) {
7636
+ return errResult(`ERR_API_PATH_NOT_ALLOWED: ${input.path} for service ${svc.name}`);
7637
+ }
7638
+ let bodyText;
7639
+ if (input.body !== void 0) {
7640
+ bodyText = JSON.stringify(input.body);
7641
+ const cap = svc.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
7642
+ if (byteLength(bodyText) > cap) {
7643
+ return errResult(`ERR_API_BODY_TOO_LARGE: exceeds ${cap} bytes`);
7644
+ }
7645
+ }
7646
+ let authHeaders;
7647
+ try {
7648
+ authHeaders = await resolveAuth({
7649
+ auth: svc.auth ?? { type: "none" },
7650
+ env: opts.env,
7651
+ resolver: opts.resolveAuth,
7652
+ ctx: { serviceName: svc.name, method: input.method, path: input.path }
7653
+ });
7654
+ } catch (err) {
7655
+ const raw2 = err instanceof Error ? err.message : String(err);
7656
+ const truncated = raw2.length > 200 ? raw2.slice(0, 200) + "\u2026" : raw2;
7657
+ return errResult(`ERR_API_RESOLVER_FAILED: ${truncated}`);
7658
+ }
7659
+ const userHeaders = sanitizeHeaders(input.headers ?? {}, authHeaders);
7660
+ const url = buildUrl(svc.baseUrl, input.path, input.query);
7661
+ await invokeHook(opts.onRequest, {
7662
+ service: svc.name,
7663
+ method: input.method,
7664
+ path: input.path
7665
+ });
7666
+ const started = Date.now();
7667
+ let res;
7668
+ try {
7669
+ res = await fetchFn(url, {
7670
+ method: input.method,
7671
+ headers: {
7672
+ "Content-Type": "application/json",
7673
+ ...svc.defaultHeaders ?? {},
7674
+ ...userHeaders,
7675
+ ...authHeaders
7676
+ // wins last — model cannot override
7677
+ },
7678
+ ...bodyText !== void 0 ? { body: bodyText } : {}
7679
+ });
7680
+ } catch (err) {
7681
+ const msg = err instanceof Error ? err.message : String(err);
7682
+ return errResult(`network error: ${msg}`);
7683
+ }
7684
+ const raw = await res.text();
7685
+ const content = raw.length > maxResponseBytes ? raw.slice(0, maxResponseBytes) + "\n\u2026[TRUNCATED]" : raw;
7686
+ await invokeHook(opts.onResponse, {
7687
+ service: svc.name,
7688
+ method: input.method,
7689
+ path: input.path,
7690
+ status: res.status,
7691
+ latencyMs: Date.now() - started,
7692
+ bytesIn: raw.length
7693
+ });
7694
+ return {
7695
+ content,
7696
+ isError: !res.ok,
7697
+ metadata: { status: res.status, service: svc.name }
7698
+ };
7699
+ }
7700
+ });
7701
+ }
7702
+ function errResult(msg) {
7703
+ return { content: msg, isError: true };
7704
+ }
7705
+ function pathAllowed(path2, allowed) {
7706
+ if (!allowed || allowed.length === 0) return true;
7707
+ for (const a of allowed) {
7708
+ if (typeof a === "string") {
7709
+ if (path2.startsWith(a)) return true;
7710
+ } else if (a.test(path2)) {
7711
+ return true;
7712
+ }
7713
+ }
7714
+ return false;
7715
+ }
7716
+ function byteLength(s) {
7717
+ return new TextEncoder().encode(s).byteLength;
7718
+ }
7719
+ function buildUrl(baseUrl, path2, query) {
7720
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
7721
+ const url = new URL(base + path2);
7722
+ for (const [k, v] of Object.entries(query ?? {})) {
7723
+ url.searchParams.set(k, v);
7724
+ }
7725
+ return url.toString();
7726
+ }
7727
+ function sanitizeHeaders(user, auth) {
7728
+ const banned = new Set(Object.keys(auth).map((k) => k.toLowerCase()));
7729
+ const out = {};
7730
+ for (const [k, v] of Object.entries(user)) {
7731
+ if (banned.has(k.toLowerCase())) continue;
7732
+ out[k] = v;
7733
+ }
7734
+ return out;
7735
+ }
7736
+ async function resolveAuth(args) {
7737
+ const { auth, env, resolver, ctx } = args;
7738
+ switch (auth.type) {
7739
+ case "none":
7740
+ return {};
7741
+ case "bearer": {
7742
+ const token = envLookup(env, auth.tokenRef);
7743
+ return { Authorization: `Bearer ${token}` };
7744
+ }
7745
+ case "header": {
7746
+ const value = envLookup(env, auth.valueRef);
7747
+ return { [auth.name]: value };
7748
+ }
7749
+ case "basic": {
7750
+ const u = envLookup(env, auth.userRef);
7751
+ const p = envLookup(env, auth.passRef);
7752
+ return { Authorization: `Basic ${base64(`${u}:${p}`)}` };
7753
+ }
7754
+ case "custom":
7755
+ if (resolver === void 0) {
7756
+ throw new Error(`custom auth id "${auth.id}" requires resolveAuth`);
7757
+ }
7758
+ return resolver(auth, ctx);
7759
+ }
7760
+ }
7761
+ function envLookup(env, ref) {
7762
+ if (env === void 0 || env[ref] === void 0) {
7763
+ throw new Error(`ERR_API_ENV_MISSING: ${ref}`);
7764
+ }
7765
+ return env[ref];
7766
+ }
7767
+ function base64(input) {
7768
+ if (typeof globalThis.btoa === "function") {
7769
+ return globalThis.btoa(input);
7770
+ }
7771
+ return Buffer.from(input, "utf8").toString("base64");
7772
+ }
7773
+ async function invokeHook(hook, event) {
7774
+ if (hook === void 0) return;
7775
+ try {
7776
+ await hook(event);
7777
+ } catch {
7778
+ }
7779
+ }
7780
+
7413
7781
  // src/skills/storageSkillSource.ts
7414
7782
  init_esm_shims();
7415
7783
  var SAFE_NAME4 = /^[a-zA-Z0-9_-]+$/;
@@ -8192,6 +8560,7 @@ function rebuildMessagesFromEntries(entries) {
8192
8560
  init_esm_shims();
8193
8561
  function toResponse(result, extra) {
8194
8562
  const timestamp = Date.now();
8563
+ const capsField = extra.capabilitiesMissing !== void 0 && extra.capabilitiesMissing.length > 0 ? { capabilitiesMissing: extra.capabilitiesMissing } : {};
8195
8564
  if (result.status === "done") {
8196
8565
  return {
8197
8566
  runId: extra.runId,
@@ -8203,7 +8572,8 @@ function toResponse(result, extra) {
8203
8572
  tokensUsed: result.tokensUsed,
8204
8573
  durationMs: extra.durationMs,
8205
8574
  output: result.output,
8206
- ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: 0 } } : {}
8575
+ ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: 0 } } : {},
8576
+ ...capsField
8207
8577
  },
8208
8578
  errors: [],
8209
8579
  timestamp
@@ -8222,7 +8592,8 @@ function toResponse(result, extra) {
8222
8592
  snapshot: result.snapshot,
8223
8593
  ...result.snapshot.pendingToolCall !== void 0 ? { pendingToolCall: result.snapshot.pendingToolCall } : {},
8224
8594
  pauseReason: result.reason,
8225
- ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: result.snapshot.lastShardIndex } } : {}
8595
+ ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: result.snapshot.lastShardIndex } } : {},
8596
+ ...capsField
8226
8597
  },
8227
8598
  errors: [],
8228
8599
  timestamp
@@ -8556,7 +8927,15 @@ var Engine = class {
8556
8927
  this.mcpManager = new McpManager(config.mcp, createLogger(config.logging), { samplingHandler });
8557
8928
  this.permissionPolicy = buildPermissionPolicy(config.permissions);
8558
8929
  }
8559
- async run(options) {
8930
+ /**
8931
+ * Run a task synchronously to completion. The `_internal` parameter is
8932
+ * reserved for the engine's own async wrappers (`start`, `resumeAsync`)
8933
+ * to request runner handoff (Plan 019) — external callers must leave
8934
+ * it unset. Sync callers never hand off; if they hit a Node-only tool
8935
+ * on a restricted runtime, the capability stub returns an error and
8936
+ * the model adapts.
8937
+ */
8938
+ async run(options, _internal) {
8560
8939
  const startTime = Date.now();
8561
8940
  const runId = options.runId ?? `run_${randomUUID()}`;
8562
8941
  const log = createLogger(this.config.logging);
@@ -8579,6 +8958,7 @@ var Engine = class {
8579
8958
  const coordinatorBase = isCoordinatorMode(this.config) ? getCoordinatorBasePrompt() : void 0;
8580
8959
  const skillSource = this.resolveSkillSource(options.skills, storage);
8581
8960
  const skillList = skillSource !== void 0 ? await skillSource.list() : void 0;
8961
+ const apiConfig = this.resolveApiConfig(options.api);
8582
8962
  let systemPrompt = await buildSystemPrompt({
8583
8963
  ...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
8584
8964
  memory,
@@ -8609,7 +8989,9 @@ var Engine = class {
8609
8989
  mcpTools,
8610
8990
  memory,
8611
8991
  ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
8612
- ...skillSource !== void 0 ? { skillSource } : {}
8992
+ ...skillSource !== void 0 ? { skillSource } : {},
8993
+ ...apiConfig !== void 0 ? { apiConfig } : {},
8994
+ ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
8613
8995
  });
8614
8996
  const writer = new TranscriptWriter({
8615
8997
  storage: storage.workspace,
@@ -8663,7 +9045,8 @@ var Engine = class {
8663
9045
  onProgress: this.buildHeartbeat(storage, runId, options.nodeId),
8664
9046
  ...options.tokenBudget !== void 0 ? { tokenBudget: options.tokenBudget } : {},
8665
9047
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
8666
- ...gate !== void 0 ? { gateBeforeTool: gate } : {}
9048
+ ...gate !== void 0 ? { gateBeforeTool: gate } : {},
9049
+ ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {}
8667
9050
  });
8668
9051
  const result = await this.finalizeResult(loopResult, writer, logPath, {
8669
9052
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -8671,18 +9054,20 @@ var Engine = class {
8671
9054
  });
8672
9055
  this.logRunEnd(log, runId, options.nodeId, result);
8673
9056
  await this.firePostRunHook(runId, options.nodeId, result, ctx, logPath);
9057
+ const capabilitiesMissing = ctx.getCapabilitiesMissing();
8674
9058
  return toResponse(result, {
8675
9059
  runId,
8676
9060
  nodeId: options.nodeId,
8677
9061
  durationMs: Date.now() - startTime,
8678
- logPath
9062
+ logPath,
9063
+ ...capabilitiesMissing.length > 0 ? { capabilitiesMissing } : {}
8679
9064
  });
8680
9065
  } finally {
8681
9066
  runTimeout.clear();
8682
9067
  await writer.close();
8683
9068
  }
8684
9069
  }
8685
- async resume(options) {
9070
+ async resume(options, _internal) {
8686
9071
  const startTime = Date.now();
8687
9072
  const log = createLogger(this.config.logging);
8688
9073
  const storage = await this.buildStorage();
@@ -8715,6 +9100,7 @@ var Engine = class {
8715
9100
  const coordinatorBase = isCoordinatorMode(this.config) ? getCoordinatorBasePrompt() : void 0;
8716
9101
  const skillSource = this.resolveSkillSource(options.skills, storage);
8717
9102
  const skillList = skillSource !== void 0 ? await skillSource.list() : void 0;
9103
+ const apiConfig = this.resolveApiConfig(options.api);
8718
9104
  let systemPrompt = await buildSystemPrompt({
8719
9105
  ...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
8720
9106
  memory,
@@ -8745,7 +9131,9 @@ var Engine = class {
8745
9131
  mcpTools,
8746
9132
  memory,
8747
9133
  ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
8748
- ...skillSource !== void 0 ? { skillSource } : {}
9134
+ ...skillSource !== void 0 ? { skillSource } : {},
9135
+ ...apiConfig !== void 0 ? { apiConfig } : {},
9136
+ ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
8749
9137
  });
8750
9138
  const priorState = await loadWriterState(storage.workspace, logPath);
8751
9139
  const writer = new TranscriptWriter({
@@ -8799,18 +9187,21 @@ var Engine = class {
8799
9187
  const answer = typeof options.gateAnswer === "string" ? options.gateAnswer : JSON.stringify(options.gateAnswer);
8800
9188
  await ctx.addToolResult(pending.toolUseId, answer, false);
8801
9189
  } else {
8802
- await ctx.addToolResult(
8803
- pending.toolUseId,
8804
- `APPROVAL_GATE_RELEASED: the prior ${pending.toolName} call was paused for human approval and has now been approved. Retry is required.`,
8805
- false
8806
- );
8807
9190
  const inputJson = JSON.stringify(pending.input ?? {}, null, 2);
8808
- await ctx.addUserMessage(
8809
- `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):
9191
+ await ctx.addMixedUserMessage([
9192
+ {
9193
+ type: "tool_result",
9194
+ tool_use_id: pending.toolUseId,
9195
+ content: `APPROVAL_GATE_RELEASED: the prior ${pending.toolName} call was paused for human approval and has now been approved. Retry is required.`
9196
+ },
9197
+ {
9198
+ type: "text",
9199
+ 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):
8810
9200
  \`\`\`json
8811
9201
  ${inputJson}
8812
9202
  \`\`\``
8813
- );
9203
+ }
9204
+ ]);
8814
9205
  }
8815
9206
  }
8816
9207
  await writer.setStatus("running");
@@ -8831,7 +9222,8 @@ ${inputJson}
8831
9222
  stopHooks: this.config.hooks.stopHooks,
8832
9223
  onProgress: this.buildHeartbeat(storage, snapshot.runId, snapshot.nodeId),
8833
9224
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
8834
- ...gate !== void 0 ? { gateBeforeTool: gate } : {}
9225
+ ...gate !== void 0 ? { gateBeforeTool: gate } : {},
9226
+ ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {}
8835
9227
  });
8836
9228
  const result = await this.finalizeResult(loopResult, writer, logPath, {
8837
9229
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -8839,11 +9231,13 @@ ${inputJson}
8839
9231
  });
8840
9232
  this.logRunEnd(log, snapshot.runId, snapshot.nodeId, result);
8841
9233
  await this.firePostRunHook(snapshot.runId, snapshot.nodeId, result, ctx, logPath);
9234
+ const capabilitiesMissing = ctx.getCapabilitiesMissing();
8842
9235
  return toResponse(result, {
8843
9236
  runId: snapshot.runId,
8844
9237
  nodeId: snapshot.nodeId,
8845
9238
  durationMs: Date.now() - startTime,
8846
- logPath
9239
+ logPath,
9240
+ ...capabilitiesMissing.length > 0 ? { capabilitiesMissing } : {}
8847
9241
  });
8848
9242
  } finally {
8849
9243
  runTimeout.clear();
@@ -8913,13 +9307,15 @@ ${inputJson}
8913
9307
  } : void 0;
8914
9308
  const initial = RunStateManager.initial(runId, options.nodeId, webhook);
8915
9309
  await stateManager.write(initial);
9310
+ const handoffEnabled = this.config.runner !== void 0;
8916
9311
  this.backgroundExecutor.schedule(runId, async (signal) => {
8917
9312
  await stateManager.update(runId, options.nodeId, { status: "running" });
8918
9313
  try {
8919
- const response = await this.run({ ...options, runId });
9314
+ const response = await this.run({ ...options, runId }, { handoffToRunner: handoffEnabled });
8920
9315
  if (signal.aborted) return;
8921
- await stateManager.finalize(runId, options.nodeId, response);
8922
- await this.maybeFireWebhook(stateManager, runId, options.nodeId, response);
9316
+ const postHandoff = await this.maybeHandoffToRunner(runId, options.nodeId, response);
9317
+ await stateManager.finalize(runId, options.nodeId, postHandoff);
9318
+ await this.maybeFireWebhook(stateManager, runId, options.nodeId, postHandoff);
8923
9319
  } catch (err) {
8924
9320
  if (signal.aborted) return;
8925
9321
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -8967,12 +9363,14 @@ ${inputJson}
8967
9363
  } : { ...RunStateManager.initial(options.runId, nodeId, webhook), status: "running" };
8968
9364
  await stateManager.write(next);
8969
9365
  const resumeNodeId = nodeId;
9366
+ const handoffEnabled = this.config.runner !== void 0;
8970
9367
  this.backgroundExecutor.schedule(options.runId, async (signal) => {
8971
9368
  try {
8972
- const response = await this.resume(options);
9369
+ const response = await this.resume(options, { handoffToRunner: handoffEnabled });
8973
9370
  if (signal.aborted) return;
8974
- await stateManager.finalize(options.runId, resumeNodeId, response);
8975
- await this.maybeFireWebhook(stateManager, options.runId, resumeNodeId, response);
9371
+ const postHandoff = await this.maybeHandoffToRunner(options.runId, resumeNodeId, response);
9372
+ await stateManager.finalize(options.runId, resumeNodeId, postHandoff);
9373
+ await this.maybeFireWebhook(stateManager, options.runId, resumeNodeId, postHandoff);
8976
9374
  } catch (err) {
8977
9375
  if (signal.aborted) return;
8978
9376
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -9161,6 +9559,68 @@ ${inputJson}
9161
9559
  }
9162
9560
  return orphaned;
9163
9561
  }
9562
+ // ---------- runner handoff (Plan 019) ----------
9563
+ /**
9564
+ * When the response indicates the run paused for runner handoff, POST
9565
+ * `{ runId }` to the configured runner URL. On success, return the
9566
+ * response unchanged — state stays `paused` and the runner will flip
9567
+ * it to `done` (or `failed`) on its side. On POST failure, convert
9568
+ * the response to `failed` with `ERR_RUNNER_UNREACHABLE` so the
9569
+ * caller sees a terminal state instead of a silent hang.
9570
+ *
9571
+ * Called only from `start()` / `resumeAsync()`. Sync `run()` never
9572
+ * produces a `handoff_to_runner` pause (see `_internal.handoffToRunner`).
9573
+ */
9574
+ async maybeHandoffToRunner(runId, nodeId, response) {
9575
+ if (response.status !== "paused") return response;
9576
+ if (response.meta.pauseReason !== "handoff_to_runner") return response;
9577
+ const runner = this.config.runner;
9578
+ if (runner === void 0) {
9579
+ return response;
9580
+ }
9581
+ const fetchFn = this.internals.fetch ?? globalThis.fetch.bind(globalThis);
9582
+ try {
9583
+ const res = await fetchFn(runner.url, {
9584
+ method: "POST",
9585
+ headers: {
9586
+ "Content-Type": "application/json",
9587
+ Authorization: `Bearer ${runner.secret}`
9588
+ },
9589
+ body: JSON.stringify({ runId })
9590
+ });
9591
+ if (!res.ok) {
9592
+ return {
9593
+ runId,
9594
+ status: "failed",
9595
+ data: null,
9596
+ meta: { nodeId },
9597
+ errors: [
9598
+ {
9599
+ code: "ERR_RUNNER_UNREACHABLE",
9600
+ message: `Runner returned HTTP ${res.status}`
9601
+ }
9602
+ ],
9603
+ timestamp: Date.now()
9604
+ };
9605
+ }
9606
+ return response;
9607
+ } catch (err) {
9608
+ const msg = err instanceof Error ? err.message : String(err);
9609
+ return {
9610
+ runId,
9611
+ status: "failed",
9612
+ data: null,
9613
+ meta: { nodeId },
9614
+ errors: [
9615
+ {
9616
+ code: "ERR_RUNNER_UNREACHABLE",
9617
+ message: `Runner handoff failed: ${msg}`
9618
+ }
9619
+ ],
9620
+ timestamp: Date.now()
9621
+ };
9622
+ }
9623
+ }
9164
9624
  // ---------- webhook helpers ----------
9165
9625
  async maybeFireWebhook(stateManager, runId, nodeId, response) {
9166
9626
  const state = await stateManager.read(runId, nodeId);
@@ -9304,6 +9764,11 @@ ${inputJson}
9304
9764
  if (disabled.has(name)) continue;
9305
9765
  if (wantAll || enabled.has(name)) names.add(name);
9306
9766
  }
9767
+ if ((this.config.api?.services.length ?? 0) > 0) {
9768
+ if (!disabled.has("ApiCall") && (wantAll || enabled.has("ApiCall"))) {
9769
+ names.add("ApiCall");
9770
+ }
9771
+ }
9307
9772
  for (const tool of this.config.tools.custom) {
9308
9773
  names.add(tool.name);
9309
9774
  }
@@ -9460,6 +9925,34 @@ ${inputJson}
9460
9925
  }
9461
9926
  return void 0;
9462
9927
  }
9928
+ /**
9929
+ * Plan 020 — resolve the effective ApiCall config for a run.
9930
+ *
9931
+ * Precedence (each field independently):
9932
+ * RunOptions.api.X > config.api.X
9933
+ *
9934
+ * If neither side provides any services, returns undefined so the
9935
+ * tool isn't registered at all. Env + resolveAuth + hooks flow
9936
+ * through untouched — they never hit the Zod schema.
9937
+ */
9938
+ resolveApiConfig(override) {
9939
+ const base = this.config.api;
9940
+ if (override === void 0 && base === void 0) return void 0;
9941
+ const services = override?.services ?? base?.services;
9942
+ if (services === void 0 || services.length === 0) return void 0;
9943
+ const env = override?.env ?? base?.env;
9944
+ const resolveAuth2 = override?.resolveAuth ?? base?.resolveAuth;
9945
+ const onRequest = override?.onRequest ?? base?.onRequest;
9946
+ const onResponse = override?.onResponse ?? base?.onResponse;
9947
+ return {
9948
+ services,
9949
+ ...env !== void 0 ? { env } : {},
9950
+ ...resolveAuth2 !== void 0 ? { resolveAuth: resolveAuth2 } : {},
9951
+ ...onRequest !== void 0 ? { onRequest } : {},
9952
+ ...onResponse !== void 0 ? { onResponse } : {},
9953
+ ...base?.maxResponseBytes !== void 0 ? { maxResponseBytes: base.maxResponseBytes } : {}
9954
+ };
9955
+ }
9463
9956
  /**
9464
9957
  * Build a throttled heartbeat callback for agentLoop's `onProgress` hook.
9465
9958
  *
@@ -9554,8 +10047,8 @@ function buildToolRegistry(options) {
9554
10047
  const fileTracker = new FileTracker();
9555
10048
  const spawnAvailable = canSpawnProcesses();
9556
10049
  const candidates = [
9557
- // Bash — requires child_process.spawn (Node.js only).
9558
- ...spawnAvailable ? [{ name: "Bash", tool: createBashTool() }] : [],
10050
+ // Bash — requires child_process.spawn. Stubbed on Workers.
10051
+ { name: "Bash", tool: withCapabilityCheck(createBashTool(), spawnAvailable) },
9559
10052
  {
9560
10053
  name: "Read",
9561
10054
  tool: createFileReadTool({ storage: storage.workspace, tracker: fileTracker })
@@ -9601,6 +10094,21 @@ function buildToolRegistry(options) {
9601
10094
  childRegistry.register(skillTool);
9602
10095
  }
9603
10096
  }
10097
+ if (options.apiConfig !== void 0 && options.apiConfig.services.length > 0) {
10098
+ if (!disabled.has("ApiCall") && (wantAll || enabled.has("ApiCall"))) {
10099
+ const apiTool = createApiCallTool({
10100
+ services: options.apiConfig.services,
10101
+ ...options.fetch !== void 0 ? { fetch: options.fetch } : {},
10102
+ ...options.apiConfig.env !== void 0 ? { env: options.apiConfig.env } : {},
10103
+ ...options.apiConfig.resolveAuth !== void 0 ? { resolveAuth: options.apiConfig.resolveAuth } : {},
10104
+ ...options.apiConfig.onRequest !== void 0 ? { onRequest: options.apiConfig.onRequest } : {},
10105
+ ...options.apiConfig.onResponse !== void 0 ? { onResponse: options.apiConfig.onResponse } : {},
10106
+ ...options.apiConfig.maxResponseBytes !== void 0 ? { maxResponseBytes: options.apiConfig.maxResponseBytes } : {}
10107
+ });
10108
+ registry.register(apiTool);
10109
+ childRegistry.register(apiTool);
10110
+ }
10111
+ }
9604
10112
  const agentTool = createAgentTool({
9605
10113
  storage: storage.workspace,
9606
10114
  client,
@@ -9722,6 +10230,8 @@ export {
9722
10230
  buildSystemPrompt,
9723
10231
  buildWorkerAgent,
9724
10232
  canSpawnProcesses,
10233
+ capabilityStub,
10234
+ createApiCallTool,
9725
10235
  createLogger,
9726
10236
  createModelAdapter,
9727
10237
  createSendMessageTool,
@@ -9753,6 +10263,7 @@ export {
9753
10263
  synthesizeSpec,
9754
10264
  toResponse,
9755
10265
  tryParseJSON,
9756
- validateOutput
10266
+ validateOutput,
10267
+ withCapabilityCheck
9757
10268
  };
9758
10269
  //# sourceMappingURL=index.js.map