la-machina-engine 0.3.0 → 0.5.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();
@@ -940,7 +943,8 @@ var ModelProviderEnum = import_zod.z.enum([
940
943
  ]);
941
944
  var StorageProviderEnum = import_zod.z.enum(["local", "r2", "r2-binding"]);
942
945
  var MemoryModeEnum = import_zod.z.enum(["off", "read-only", "read-write"]);
943
- var MemoryScopeEnum = import_zod.z.enum(["workspace", "global"]);
946
+ var MemoryScopeUserEnum = import_zod.z.enum(["workspace", "global"]);
947
+ var MemoryScopeResolvedEnum = import_zod.z.enum(["workspace"]);
944
948
  var FlushPolicyEnum = import_zod.z.enum(["turn-end", "entry", "manual"]);
945
949
  var LogLevelEnum = import_zod.z.enum(["silent", "error", "warn", "info", "debug"]);
946
950
  var R2ConfigResolved = import_zod.z.object({
@@ -979,7 +983,7 @@ var StorageConfigResolved = import_zod.z.object({
979
983
  });
980
984
  var MemoryConfigResolved = import_zod.z.object({
981
985
  mode: MemoryModeEnum,
982
- scope: MemoryScopeEnum
986
+ scope: MemoryScopeResolvedEnum
983
987
  }).strict();
984
988
  var ToolsConfigResolved = import_zod.z.object({
985
989
  enabled: import_zod.z.array(import_zod.z.string()),
@@ -1110,6 +1114,42 @@ var LoggingConfigResolved = import_zod.z.object({
1110
1114
  level: LogLevelEnum,
1111
1115
  sink: LogSinkSchema
1112
1116
  }).strict();
1117
+ var ApiHttpMethodEnum = import_zod.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]);
1118
+ var ApiAuthSchema = import_zod.z.discriminatedUnion("type", [
1119
+ import_zod.z.object({ type: import_zod.z.literal("none") }).strict(),
1120
+ import_zod.z.object({ type: import_zod.z.literal("bearer"), tokenRef: import_zod.z.string().min(1) }).strict(),
1121
+ import_zod.z.object({
1122
+ type: import_zod.z.literal("header"),
1123
+ name: import_zod.z.string().min(1),
1124
+ valueRef: import_zod.z.string().min(1)
1125
+ }).strict(),
1126
+ import_zod.z.object({
1127
+ type: import_zod.z.literal("basic"),
1128
+ userRef: import_zod.z.string().min(1),
1129
+ passRef: import_zod.z.string().min(1)
1130
+ }).strict(),
1131
+ import_zod.z.object({ type: import_zod.z.literal("custom"), id: import_zod.z.string().min(1) }).strict()
1132
+ ]);
1133
+ var ApiServiceSchema = import_zod.z.object({
1134
+ name: import_zod.z.string().min(1),
1135
+ description: import_zod.z.string().optional(),
1136
+ baseUrl: import_zod.z.string().url(),
1137
+ auth: ApiAuthSchema.optional(),
1138
+ // Allow both strings and RegExp values via z.union — RegExp is
1139
+ // serialized as its source when cloned, so we accept both.
1140
+ allowedPaths: import_zod.z.array(import_zod.z.union([import_zod.z.string(), import_zod.z.instanceof(RegExp)])).optional(),
1141
+ allowedMethods: import_zod.z.array(ApiHttpMethodEnum).optional(),
1142
+ defaultHeaders: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
1143
+ maxBodyBytes: import_zod.z.number().int().positive().optional()
1144
+ }).strict();
1145
+ var ApiConfigResolved = import_zod.z.object({
1146
+ services: import_zod.z.array(ApiServiceSchema),
1147
+ maxResponseBytes: import_zod.z.number().int().positive().optional()
1148
+ }).strict();
1149
+ var RunnerConfigResolved = import_zod.z.object({
1150
+ url: import_zod.z.string().url(),
1151
+ secret: import_zod.z.string().min(1, "runner.secret cannot be empty")
1152
+ }).strict();
1113
1153
  var ResolvedConfigSchema = import_zod.z.object({
1114
1154
  model: ModelConfigResolved,
1115
1155
  storage: StorageConfigResolved,
@@ -1125,7 +1165,9 @@ var ResolvedConfigSchema = import_zod.z.object({
1125
1165
  permissions: PermissionsConfigResolved,
1126
1166
  compaction: CompactionConfigResolved,
1127
1167
  coordinator: CoordinatorConfigResolved,
1128
- orchestrator: OrchestratorConfigResolved
1168
+ orchestrator: OrchestratorConfigResolved,
1169
+ runner: RunnerConfigResolved.optional(),
1170
+ api: ApiConfigResolved.optional()
1129
1171
  }).strict();
1130
1172
  var R2ConfigUser = R2ConfigResolved.partial();
1131
1173
  var ModelConfigUser = ModelConfigResolved.partial();
@@ -1136,7 +1178,10 @@ var StorageConfigUser = import_zod.z.object({
1136
1178
  r2: R2ConfigUser.optional(),
1137
1179
  r2Binding: R2BucketBindingShape.optional()
1138
1180
  }).strict();
1139
- var MemoryConfigUser = MemoryConfigResolved.partial();
1181
+ var MemoryConfigUser = import_zod.z.object({
1182
+ mode: MemoryModeEnum.optional(),
1183
+ scope: MemoryScopeUserEnum.optional()
1184
+ }).strict();
1140
1185
  var ToolsConfigUser = ToolsConfigResolved.partial();
1141
1186
  var AgentsConfigUser = AgentsConfigResolved.partial();
1142
1187
  var SkillsConfigUser = SkillsConfigResolved.partial();
@@ -1166,6 +1211,8 @@ var McpConfigUser = import_zod.z.object({
1166
1211
  shutdownTimeoutMs: import_zod.z.number().int().positive().optional()
1167
1212
  }).strict();
1168
1213
  var PermissionsConfigUser = PermissionsConfigResolved.partial();
1214
+ var RunnerConfigUser = RunnerConfigResolved;
1215
+ var ApiConfigUser = ApiConfigResolved.partial();
1169
1216
  var CompactionConfigUser = CompactionConfigResolved.partial();
1170
1217
  var CoordinatorConfigUser = CoordinatorConfigResolved.partial();
1171
1218
  var OrchestratorConfigUser = OrchestratorConfigResolved.deepPartial();
@@ -1184,7 +1231,9 @@ var UserConfigSchema = import_zod.z.object({
1184
1231
  permissions: PermissionsConfigUser.optional(),
1185
1232
  compaction: CompactionConfigUser.optional(),
1186
1233
  coordinator: CoordinatorConfigUser.optional(),
1187
- orchestrator: OrchestratorConfigUser.optional()
1234
+ orchestrator: OrchestratorConfigUser.optional(),
1235
+ runner: RunnerConfigUser.optional(),
1236
+ api: ApiConfigUser.optional()
1188
1237
  }).strict();
1189
1238
 
1190
1239
  // src/config/merge.ts
@@ -1229,10 +1278,49 @@ function deepMerge(base, override) {
1229
1278
  }
1230
1279
  return result;
1231
1280
  }
1281
+ var API_RUNTIME_KEYS = ["env", "resolveAuth", "onRequest", "onResponse"];
1282
+ function splitApiRuntime(user) {
1283
+ const api = user.api;
1284
+ if (api === void 0) return { stripped: user, runtime: {} };
1285
+ const runtime = {};
1286
+ const schemaSafe = {};
1287
+ for (const [k, v] of Object.entries(api)) {
1288
+ if (API_RUNTIME_KEYS.includes(k)) {
1289
+ runtime[k] = v;
1290
+ } else {
1291
+ schemaSafe[k] = v;
1292
+ }
1293
+ }
1294
+ const clone = { ...user, api: schemaSafe };
1295
+ return { stripped: clone, runtime };
1296
+ }
1297
+ function coerceDeprecatedMemoryScope(user) {
1298
+ const scope = user.memory?.scope;
1299
+ if (scope !== "global") return user;
1300
+ console.warn(
1301
+ '[la-machina] config.memory.scope: "global" is deprecated and has been rewritten to "workspace". Cross-tenant memory sharing was removed in Plan 022 \u2014 the workspace is the tenant root. This field value will be rejected outright in a future major release.'
1302
+ );
1303
+ const memory = user.memory ?? {};
1304
+ return {
1305
+ ...user,
1306
+ memory: { ...memory, scope: "workspace" }
1307
+ };
1308
+ }
1232
1309
  function mergeConfig(user) {
1233
- const validatedUser = UserConfigSchema.parse(user);
1310
+ const withCoercedScope = coerceDeprecatedMemoryScope(user);
1311
+ const { stripped, runtime } = splitApiRuntime(withCoercedScope);
1312
+ const validatedUser = UserConfigSchema.parse(stripped);
1234
1313
  const merged = deepMerge(DEFAULTS, validatedUser);
1235
- return ResolvedConfigSchema.parse(merged);
1314
+ const resolved = ResolvedConfigSchema.parse(merged);
1315
+ if (resolved.api !== void 0 && Object.keys(runtime).length > 0) {
1316
+ const mutableResolved = resolved;
1317
+ mutableResolved.api = {
1318
+ ...resolved.api,
1319
+ ...runtime
1320
+ };
1321
+ } else if (Object.keys(runtime).length > 0) {
1322
+ }
1323
+ return resolved;
1236
1324
  }
1237
1325
 
1238
1326
  // src/engine/engine.ts
@@ -1676,8 +1764,8 @@ function toAISdkMessages(messages) {
1676
1764
  textParts.push({ type: "text", text: block.text });
1677
1765
  }
1678
1766
  }
1679
- if (textParts.length > 0) out.push({ role: "user", content: textParts });
1680
1767
  if (toolResults.length > 0) out.push({ role: "tool", content: toolResults });
1768
+ if (textParts.length > 0) out.push({ role: "user", content: textParts });
1681
1769
  } else if (role === "assistant") {
1682
1770
  if (typeof content === "string") {
1683
1771
  out.push({ role: "assistant", content });
@@ -1875,6 +1963,68 @@ function hasProcessLifecycle() {
1875
1963
  return typeof process !== "undefined" && typeof process.on === "function" && typeof process.removeListener === "function" && detectRuntime() === "node";
1876
1964
  }
1877
1965
 
1966
+ // src/tools/capabilityStub.ts
1967
+ init_cjs_shims();
1968
+ var import_zod2 = require("zod");
1969
+
1970
+ // src/tools/contract.ts
1971
+ init_cjs_shims();
1972
+ function defineTool(tool) {
1973
+ return tool;
1974
+ }
1975
+ var ToolRegistry = class {
1976
+ tools = /* @__PURE__ */ new Map();
1977
+ register(tool) {
1978
+ if (typeof tool.name !== "string" || tool.name.length === 0) {
1979
+ throw new Error("ToolRegistry: tool.name must be a non-empty string");
1980
+ }
1981
+ if (this.tools.has(tool.name)) {
1982
+ throw new Error(`ToolRegistry: "${tool.name}" is already registered`);
1983
+ }
1984
+ this.tools.set(tool.name, tool);
1985
+ }
1986
+ registerAll(tools) {
1987
+ for (const tool of tools) this.register(tool);
1988
+ }
1989
+ unregister(name) {
1990
+ this.tools.delete(name);
1991
+ }
1992
+ get(name) {
1993
+ return this.tools.get(name);
1994
+ }
1995
+ has(name) {
1996
+ return this.tools.has(name);
1997
+ }
1998
+ list() {
1999
+ return Array.from(this.tools.values());
2000
+ }
2001
+ count() {
2002
+ return this.tools.size;
2003
+ }
2004
+ };
2005
+
2006
+ // src/tools/capabilityStub.ts
2007
+ var anyInput = import_zod2.z.unknown();
2008
+ function capabilityStub(original) {
2009
+ return defineTool({
2010
+ name: original.name,
2011
+ description: original.description,
2012
+ inputSchema: anyInput,
2013
+ isCapabilityStub: true,
2014
+ execute: async () => ({
2015
+ isError: true,
2016
+ 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.`,
2017
+ metadata: { capabilityMissing: original.name }
2018
+ })
2019
+ });
2020
+ }
2021
+ function withCapabilityCheck(tool, spawnAvailable) {
2022
+ if (tool.requiresNode === true && !spawnAvailable) {
2023
+ return capabilityStub(tool);
2024
+ }
2025
+ return tool;
2026
+ }
2027
+
1878
2028
  // src/subagent/registry.ts
1879
2029
  init_cjs_shims();
1880
2030
 
@@ -2057,7 +2207,7 @@ var SubagentRegistry = class _SubagentRegistry {
2057
2207
 
2058
2208
  // src/tools/agent.ts
2059
2209
  init_cjs_shims();
2060
- var import_zod5 = require("zod");
2210
+ var import_zod6 = require("zod");
2061
2211
 
2062
2212
  // src/subagent/runner.ts
2063
2213
  init_cjs_shims();
@@ -2068,48 +2218,49 @@ var import_zod_to_json_schema = require("zod-to-json-schema");
2068
2218
 
2069
2219
  // src/transcript/snapshot.ts
2070
2220
  init_cjs_shims();
2071
- var import_zod2 = require("zod");
2221
+ var import_zod3 = require("zod");
2072
2222
  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()
2223
+ var TokenUsageSchema = import_zod3.z.object({
2224
+ input: import_zod3.z.number().int().nonnegative(),
2225
+ output: import_zod3.z.number().int().nonnegative(),
2226
+ cacheCreationInput: import_zod3.z.number().int().nonnegative().optional(),
2227
+ cacheReadInput: import_zod3.z.number().int().nonnegative().optional()
2078
2228
  }).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 })
2229
+ var PendingToolCallSchema = import_zod3.z.object({
2230
+ toolName: import_zod3.z.string().min(1),
2231
+ toolUseId: import_zod3.z.string().min(1),
2232
+ input: import_zod3.z.unknown(),
2233
+ calledAt: import_zod3.z.string().datetime({ offset: true })
2084
2234
  }).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),
2235
+ var PendingSubagentSchema = import_zod3.z.lazy(
2236
+ () => import_zod3.z.object({
2237
+ subagentType: import_zod3.z.string().min(1),
2238
+ parentToolUseId: import_zod3.z.string().min(1),
2089
2239
  childSnapshot: RunSnapshotSchema
2090
2240
  }).strict()
2091
2241
  );
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([
2242
+ var RunSnapshotSchema = import_zod3.z.lazy(
2243
+ () => import_zod3.z.object({
2244
+ version: import_zod3.z.literal(1),
2245
+ status: import_zod3.z.literal("paused"),
2246
+ runId: import_zod3.z.string().min(1),
2247
+ nodeId: import_zod3.z.string().min(1),
2248
+ pausedAt: import_zod3.z.string().datetime({ offset: true }),
2249
+ pauseReason: import_zod3.z.enum([
2100
2250
  "gate_required",
2101
2251
  "subagent_gate_required",
2252
+ "handoff_to_runner",
2102
2253
  "max_turns",
2103
2254
  "explicit",
2104
2255
  "timeout"
2105
2256
  ]),
2106
- messageCount: import_zod2.z.number().int().nonnegative(),
2107
- lastShardIndex: import_zod2.z.number().int().nonnegative(),
2108
- lastMessageUuid: import_zod2.z.string().uuid(),
2257
+ messageCount: import_zod3.z.number().int().nonnegative(),
2258
+ lastShardIndex: import_zod3.z.number().int().nonnegative(),
2259
+ lastMessageUuid: import_zod3.z.string().uuid(),
2109
2260
  pendingToolCall: PendingToolCallSchema.optional(),
2110
2261
  pendingSubagent: PendingSubagentSchema.optional(),
2111
2262
  tokensUsedSoFar: TokenUsageSchema,
2112
- turnsUsed: import_zod2.z.number().int().nonnegative()
2263
+ turnsUsed: import_zod3.z.number().int().nonnegative()
2113
2264
  }).strict()
2114
2265
  );
2115
2266
  function snapshotPath(logPath) {
@@ -2837,6 +2988,27 @@ async function agentLoop(options) {
2837
2988
  }
2838
2989
  }
2839
2990
  }
2991
+ if (options.handoffToRunner === true) {
2992
+ for (const call of toolCallsToDispatch) {
2993
+ const tool = options.registry?.get(call.name);
2994
+ if (tool?.isCapabilityStub === true) {
2995
+ const paused = await pauseHere({
2996
+ ctx,
2997
+ transcript,
2998
+ reason: "handoff_to_runner",
2999
+ pendingToolCall: {
3000
+ toolName: call.name,
3001
+ toolUseId: call.id,
3002
+ input: call.input,
3003
+ calledAt: (/* @__PURE__ */ new Date()).toISOString()
3004
+ },
3005
+ storage: options.storage,
3006
+ subagentRegistry: options.subagentRegistry
3007
+ });
3008
+ return paused;
3009
+ }
3010
+ }
3011
+ }
2840
3012
  const firstTool = toolCallsToDispatch[0]?.name;
2841
3013
  await fireProgress("tool_dispatch", firstTool);
2842
3014
  const streamExec = new StreamingToolExecutor(executor);
@@ -2847,6 +3019,10 @@ async function agentLoop(options) {
2847
3019
  }
2848
3020
  try {
2849
3021
  for await (const { id, result } of streamExec.results()) {
3022
+ const missing = result.metadata?.capabilityMissing;
3023
+ if (typeof missing === "string") {
3024
+ ctx.recordCapabilityMissing(missing);
3025
+ }
2850
3026
  await ctx.addToolResult(id, truncateToolResult(result.content), result.isError === true);
2851
3027
  }
2852
3028
  } catch (err) {
@@ -3105,6 +3281,12 @@ var RunContext = class {
3105
3281
  turnCount = 0;
3106
3282
  tokensUsed = { input: 0, output: 0 };
3107
3283
  lastUuid = null;
3284
+ /**
3285
+ * Plan 019 — names of tools whose capability-stub returned an
3286
+ * `isError` result during this run. Aggregated and surfaced on the
3287
+ * response's `meta.capabilitiesMissing` (deduped, insertion-ordered).
3288
+ */
3289
+ capabilitiesMissing = /* @__PURE__ */ new Set();
3108
3290
  constructor(options) {
3109
3291
  this.runId = options.runId;
3110
3292
  this.nodeId = options.nodeId;
@@ -3145,6 +3327,42 @@ var RunContext = class {
3145
3327
  this.episodes.logTurn(this.turnCount, "assistant", summary);
3146
3328
  }
3147
3329
  }
3330
+ /**
3331
+ * Inject a tool_result + a follow-up text block as a SINGLE user
3332
+ * message. Used by the resume() synthetic-release path when a
3333
+ * paused run is resumed without an explicit `gateAnswer`: we need
3334
+ * to both satisfy the tool_use↔tool_result pairing AND give the
3335
+ * model a retry instruction, in the same user turn.
3336
+ *
3337
+ * Splitting this into `addToolResult(...) + addUserMessage(...)`
3338
+ * produces two consecutive user messages, which the AI SDK's
3339
+ * openai-compatible adapter rejects with
3340
+ * `MissingToolResultsError`. Combining them into one user message
3341
+ * is the portable shape that works on every provider we support.
3342
+ *
3343
+ * Writes a single `user` transcript entry carrying the mixed
3344
+ * content, so `rebuildMessagesFromEntries` reconstructs the same
3345
+ * single-message shape on resume.
3346
+ */
3347
+ async addMixedUserMessage(blocks) {
3348
+ this.messages.push({ role: "user", content: blocks });
3349
+ await this.writeEntry({
3350
+ type: "user",
3351
+ uuid: this.nextUuid(),
3352
+ parentUuid: this.lastUuid,
3353
+ ts: this.now(),
3354
+ message: { role: "user", content: blocks }
3355
+ });
3356
+ const summary = blocks.map((b) => {
3357
+ const x = b;
3358
+ if (x.type === "text") return x.text ?? "";
3359
+ if (x.type === "tool_result") {
3360
+ return typeof x.content === "string" ? x.content : JSON.stringify(x.content);
3361
+ }
3362
+ return "";
3363
+ }).filter((s) => s.length > 0).join("\n");
3364
+ this.episodes?.logTurn(this.turnCount, "user", summary);
3365
+ }
3148
3366
  /**
3149
3367
  * Append a tool result to the conversation. The Anthropic Messages
3150
3368
  * API requires tool_result blocks to be wrapped in a user message,
@@ -3190,6 +3408,14 @@ var RunContext = class {
3190
3408
  getTokensUsed() {
3191
3409
  return this.tokensUsed;
3192
3410
  }
3411
+ /** Plan 019 — record that a capability-stubbed tool fired during this run. */
3412
+ recordCapabilityMissing(toolName) {
3413
+ this.capabilitiesMissing.add(toolName);
3414
+ }
3415
+ /** Plan 019 — capability-stubbed tool names observed this run (deduped). */
3416
+ getCapabilitiesMissing() {
3417
+ return [...this.capabilitiesMissing];
3418
+ }
3193
3419
  shouldContinue() {
3194
3420
  return this.turnCount < this.maxTurns;
3195
3421
  }
@@ -3363,9 +3589,9 @@ init_cjs_shims();
3363
3589
 
3364
3590
  // src/transcript/entries.ts
3365
3591
  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 });
3592
+ var import_zod4 = require("zod");
3593
+ var UuidSchema = import_zod4.z.string().uuid();
3594
+ var IsoTsSchema = import_zod4.z.string().datetime({ offset: true });
3369
3595
  var TimelineBase = {
3370
3596
  uuid: UuidSchema,
3371
3597
  parentUuid: UuidSchema.nullable(),
@@ -3375,55 +3601,55 @@ var SessionBase = {
3375
3601
  uuid: UuidSchema,
3376
3602
  ts: IsoTsSchema
3377
3603
  };
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())
3604
+ var MessageSchema = import_zod4.z.object({
3605
+ role: import_zod4.z.enum(["user", "assistant"]),
3606
+ content: import_zod4.z.array(import_zod4.z.unknown())
3381
3607
  }).strict();
3382
- var UserEntrySchema = import_zod3.z.object({
3383
- type: import_zod3.z.literal("user"),
3608
+ var UserEntrySchema = import_zod4.z.object({
3609
+ type: import_zod4.z.literal("user"),
3384
3610
  ...TimelineBase,
3385
3611
  message: MessageSchema
3386
3612
  }).strict();
3387
- var AssistantEntrySchema = import_zod3.z.object({
3388
- type: import_zod3.z.literal("assistant"),
3613
+ var AssistantEntrySchema = import_zod4.z.object({
3614
+ type: import_zod4.z.literal("assistant"),
3389
3615
  ...TimelineBase,
3390
3616
  message: MessageSchema
3391
3617
  }).strict();
3392
- var ToolResultEntrySchema = import_zod3.z.object({
3393
- type: import_zod3.z.literal("tool_result"),
3618
+ var ToolResultEntrySchema = import_zod4.z.object({
3619
+ type: import_zod4.z.literal("tool_result"),
3394
3620
  ...TimelineBase,
3395
- toolUseId: import_zod3.z.string().min(1),
3396
- content: import_zod3.z.unknown(),
3397
- isError: import_zod3.z.boolean().optional()
3621
+ toolUseId: import_zod4.z.string().min(1),
3622
+ content: import_zod4.z.unknown(),
3623
+ isError: import_zod4.z.boolean().optional()
3398
3624
  }).strict();
3399
- var SubagentSpawnEntrySchema = import_zod3.z.object({
3400
- type: import_zod3.z.literal("subagent_spawn"),
3625
+ var SubagentSpawnEntrySchema = import_zod4.z.object({
3626
+ type: import_zod4.z.literal("subagent_spawn"),
3401
3627
  ...TimelineBase,
3402
- agentId: import_zod3.z.string().min(1),
3403
- agentType: import_zod3.z.string().min(1)
3628
+ agentId: import_zod4.z.string().min(1),
3629
+ agentType: import_zod4.z.string().min(1)
3404
3630
  }).strict();
3405
- var SubagentDoneEntrySchema = import_zod3.z.object({
3406
- type: import_zod3.z.literal("subagent_done"),
3631
+ var SubagentDoneEntrySchema = import_zod4.z.object({
3632
+ type: import_zod4.z.literal("subagent_done"),
3407
3633
  ...TimelineBase,
3408
- agentId: import_zod3.z.string().min(1),
3409
- output: import_zod3.z.string()
3634
+ agentId: import_zod4.z.string().min(1),
3635
+ output: import_zod4.z.string()
3410
3636
  }).strict();
3411
- var MetaEntrySchema = import_zod3.z.object({
3412
- type: import_zod3.z.literal("meta"),
3637
+ var MetaEntrySchema = import_zod4.z.object({
3638
+ type: import_zod4.z.literal("meta"),
3413
3639
  ...SessionBase,
3414
- key: import_zod3.z.string().min(1),
3415
- value: import_zod3.z.unknown()
3640
+ key: import_zod4.z.string().min(1),
3641
+ value: import_zod4.z.unknown()
3416
3642
  }).strict();
3417
- var ErrorEntrySchema = import_zod3.z.object({
3418
- type: import_zod3.z.literal("error"),
3643
+ var ErrorEntrySchema = import_zod4.z.object({
3644
+ type: import_zod4.z.literal("error"),
3419
3645
  ...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()
3646
+ error: import_zod4.z.object({
3647
+ code: import_zod4.z.string().min(1),
3648
+ message: import_zod4.z.string(),
3649
+ stack: import_zod4.z.string().optional()
3424
3650
  }).strict()
3425
3651
  }).strict();
3426
- var EntrySchema = import_zod3.z.discriminatedUnion("type", [
3652
+ var EntrySchema = import_zod4.z.discriminatedUnion("type", [
3427
3653
  UserEntrySchema,
3428
3654
  AssistantEntrySchema,
3429
3655
  ToolResultEntrySchema,
@@ -3455,16 +3681,16 @@ function parseEntryLine(line) {
3455
3681
 
3456
3682
  // src/transcript/meta.ts
3457
3683
  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()
3684
+ var import_zod5 = require("zod");
3685
+ var TranscriptMetaSchema = import_zod5.z.object({
3686
+ version: import_zod5.z.literal(1),
3687
+ status: import_zod5.z.enum(["pending", "running", "paused", "done", "failed"]),
3688
+ startedAt: import_zod5.z.string().datetime({ offset: true }),
3689
+ updatedAt: import_zod5.z.string().datetime({ offset: true }),
3690
+ turnCount: import_zod5.z.number().int().nonnegative(),
3691
+ messageCount: import_zod5.z.number().int().nonnegative(),
3692
+ lastShardIndex: import_zod5.z.number().int().nonnegative().nullable(),
3693
+ shardCount: import_zod5.z.number().int().nonnegative()
3468
3694
  }).strict();
3469
3695
  var META_FILENAME = "meta.json";
3470
3696
  function metaPath(logPath) {
@@ -3812,47 +4038,11 @@ Output format:
3812
4038
  Directive: ${directive}`;
3813
4039
  }
3814
4040
 
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
4041
  // 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()
4042
+ var inputSchema = import_zod6.z.object({
4043
+ description: import_zod6.z.string().min(1),
4044
+ subagent_type: import_zod6.z.string().optional(),
4045
+ run_in_background: import_zod6.z.boolean().optional()
3856
4046
  });
3857
4047
  function createAgentTool(options) {
3858
4048
  if (options.agents.length === 0) {
@@ -4057,7 +4247,7 @@ function handlePausedResult(result, agentId, subagentType) {
4057
4247
 
4058
4248
  // src/tools/bash.ts
4059
4249
  init_cjs_shims();
4060
- var import_zod6 = require("zod");
4250
+ var import_zod7 = require("zod");
4061
4251
  var _spawn = null;
4062
4252
  async function getSpawn() {
4063
4253
  if (_spawn === null) {
@@ -4069,15 +4259,16 @@ async function getSpawn() {
4069
4259
  var MAX_OUTPUT_BYTES = 512 * 1024;
4070
4260
  var SIGKILL_GRACE_MS = 500;
4071
4261
  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()
4262
+ var inputSchema2 = import_zod7.z.object({
4263
+ command: import_zod7.z.string().min(1),
4264
+ cwd: import_zod7.z.string().min(1).optional()
4075
4265
  });
4076
4266
  function createBashTool() {
4077
4267
  return defineTool({
4078
4268
  name: "Bash",
4079
4269
  description: "Execute a shell command via /bin/sh -c. Returns combined stdout+stderr and the exit code.",
4080
4270
  inputSchema: inputSchema2,
4271
+ requiresNode: true,
4081
4272
  execute: async ({ command, cwd }, ctx) => {
4082
4273
  if (BLOCKED_DEVICE_PATHS.test(command)) {
4083
4274
  return {
@@ -4157,10 +4348,10 @@ function createBashTool() {
4157
4348
 
4158
4349
  // src/tools/sendMessage.ts
4159
4350
  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.")
4351
+ var import_zod8 = require("zod");
4352
+ var inputSchema3 = import_zod8.z.object({
4353
+ to: import_zod8.z.string().min(1).describe("Agent name (subagent_type) or agentId to send the message to."),
4354
+ message: import_zod8.z.string().min(1).describe("Message content to deliver to the target agent.")
4164
4355
  });
4165
4356
  function createSendMessageTool(options) {
4166
4357
  return defineTool({
@@ -4211,12 +4402,12 @@ function createSendMessageTool(options) {
4211
4402
 
4212
4403
  // src/tools/fileEdit.ts
4213
4404
  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()
4405
+ var import_zod9 = require("zod");
4406
+ var inputSchema4 = import_zod9.z.object({
4407
+ path: import_zod9.z.string().min(1),
4408
+ old_string: import_zod9.z.string().min(1),
4409
+ new_string: import_zod9.z.string(),
4410
+ replace_all: import_zod9.z.boolean().optional()
4220
4411
  });
4221
4412
  function createFileEditTool(storage) {
4222
4413
  return defineTool({
@@ -4300,13 +4491,13 @@ function normalizeQuotes(s) {
4300
4491
 
4301
4492
  // src/tools/fileRead.ts
4302
4493
  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(),
4494
+ var import_zod10 = require("zod");
4495
+ var inputSchema5 = import_zod10.z.object({
4496
+ path: import_zod10.z.string().min(1),
4497
+ offset: import_zod10.z.number().int().positive().optional(),
4498
+ limit: import_zod10.z.number().int().positive().optional(),
4308
4499
  /** PDF page range, e.g. "1-5", "3", "10-20". Max 20 pages per request. */
4309
- pages: import_zod9.z.string().optional()
4500
+ pages: import_zod10.z.string().optional()
4310
4501
  });
4311
4502
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"]);
4312
4503
  var PDF_EXTENSION = ".pdf";
@@ -4474,7 +4665,7 @@ async function readImage(storage, filePath, ext) {
4474
4665
  ".svg": "image/svg+xml"
4475
4666
  };
4476
4667
  const mimeType = mimeMap[ext] ?? "application/octet-stream";
4477
- const base64 = buffer.toString("base64");
4668
+ const base642 = buffer.toString("base64");
4478
4669
  return {
4479
4670
  content: `[Image: ${filePath} (${mimeType}, ${(buffer.length / 1024).toFixed(1)}KB)]
4480
4671
 
@@ -4483,17 +4674,17 @@ Base64 data is included in the metadata for visual analysis.`,
4483
4674
  format: "image",
4484
4675
  mimeType,
4485
4676
  bytes: buffer.length,
4486
- base64
4677
+ base64: base642
4487
4678
  }
4488
4679
  };
4489
4680
  }
4490
4681
 
4491
4682
  // src/tools/fileWrite.ts
4492
4683
  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()
4684
+ var import_zod11 = require("zod");
4685
+ var inputSchema6 = import_zod11.z.object({
4686
+ path: import_zod11.z.string().min(1),
4687
+ content: import_zod11.z.string()
4497
4688
  });
4498
4689
  function createFileWriteTool(storageOrOptions) {
4499
4690
  const opts = "readFile" in storageOrOptions ? { storage: storageOrOptions } : storageOrOptions;
@@ -4532,7 +4723,7 @@ function createFileWriteTool(storageOrOptions) {
4532
4723
  // src/tools/glob.ts
4533
4724
  init_cjs_shims();
4534
4725
  var import_picomatch = __toESM(require("picomatch"), 1);
4535
- var import_zod11 = require("zod");
4726
+ var import_zod12 = require("zod");
4536
4727
 
4537
4728
  // src/tools/walkAdapter.ts
4538
4729
  init_cjs_shims();
@@ -4569,9 +4760,9 @@ async function* walkAdapter(adapter, startPath, options = {}) {
4569
4760
  }
4570
4761
 
4571
4762
  // 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()
4763
+ var inputSchema7 = import_zod12.z.object({
4764
+ pattern: import_zod12.z.string().min(1),
4765
+ path: import_zod12.z.string().optional()
4575
4766
  });
4576
4767
  var MAX_RESULTS = 1e3;
4577
4768
  function createGlobTool(storage) {
@@ -4624,22 +4815,22 @@ function createGlobTool(storage) {
4624
4815
  // src/tools/grep.ts
4625
4816
  init_cjs_shims();
4626
4817
  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()
4818
+ var import_zod13 = require("zod");
4819
+ var inputSchema8 = import_zod13.z.object({
4820
+ pattern: import_zod13.z.string().min(1),
4821
+ path: import_zod13.z.string().optional(),
4822
+ glob: import_zod13.z.string().optional(),
4823
+ type: import_zod13.z.string().optional(),
4824
+ output_mode: import_zod13.z.enum(["content", "files_with_matches", "count"]).optional(),
4825
+ "-i": import_zod13.z.boolean().optional(),
4826
+ "-n": import_zod13.z.boolean().optional(),
4827
+ "-A": import_zod13.z.number().int().nonnegative().optional(),
4828
+ "-B": import_zod13.z.number().int().nonnegative().optional(),
4829
+ "-C": import_zod13.z.number().int().nonnegative().optional(),
4830
+ context: import_zod13.z.number().int().nonnegative().optional(),
4831
+ multiline: import_zod13.z.boolean().optional(),
4832
+ head_limit: import_zod13.z.number().int().nonnegative().optional(),
4833
+ offset: import_zod13.z.number().int().nonnegative().optional()
4643
4834
  });
4644
4835
  var MAX_FILES_SCANNED = 5e3;
4645
4836
  var MAX_MATCHES_PER_FILE = 100;
@@ -4863,10 +5054,10 @@ function formatJsResults(results, mode, limit) {
4863
5054
 
4864
5055
  // src/tools/webFetch.ts
4865
5056
  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()
5057
+ var import_zod14 = require("zod");
5058
+ var inputSchema9 = import_zod14.z.object({
5059
+ url: import_zod14.z.string().url(),
5060
+ prompt: import_zod14.z.string().optional()
4870
5061
  });
4871
5062
  var MAX_OUTPUT_BYTES2 = 256 * 1024;
4872
5063
  function createWebFetchTool(options = {}) {
@@ -4995,10 +5186,10 @@ function normalizePath(p) {
4995
5186
 
4996
5187
  // src/tools/webSearch.ts
4997
5188
  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()
5189
+ var import_zod15 = require("zod");
5190
+ var inputSchema10 = import_zod15.z.object({
5191
+ query: import_zod15.z.string().min(2),
5192
+ max_results: import_zod15.z.number().int().positive().optional()
5002
5193
  });
5003
5194
  function createWebSearchTool() {
5004
5195
  return defineTool({
@@ -5055,10 +5246,10 @@ function htmlToText2(html) {
5055
5246
 
5056
5247
  // src/tools/sleep.ts
5057
5248
  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()
5249
+ var import_zod16 = require("zod");
5250
+ var inputSchema11 = import_zod16.z.object({
5251
+ durationMs: import_zod16.z.number().int().nonnegative().max(3e5),
5252
+ reason: import_zod16.z.string().optional()
5062
5253
  });
5063
5254
  function createSleepTool() {
5064
5255
  return defineTool({
@@ -5098,10 +5289,10 @@ function createSleepTool() {
5098
5289
 
5099
5290
  // src/tools/toolSearch.ts
5100
5291
  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()
5292
+ var import_zod17 = require("zod");
5293
+ var inputSchema12 = import_zod17.z.object({
5294
+ query: import_zod17.z.string().min(1),
5295
+ max_results: import_zod17.z.number().int().positive().optional()
5105
5296
  });
5106
5297
  function createToolSearchTool(registry) {
5107
5298
  return defineTool({
@@ -5144,11 +5335,11 @@ ${lines.join("\n")}`,
5144
5335
 
5145
5336
  // src/tools/memorize.ts
5146
5337
  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()
5338
+ var import_zod18 = require("zod");
5339
+ var inputSchema13 = import_zod18.z.object({
5340
+ text: import_zod18.z.string().min(1),
5341
+ kind: import_zod18.z.enum(["rule", "lesson"]).default("lesson"),
5342
+ topic: import_zod18.z.string().optional()
5152
5343
  });
5153
5344
  function createMemorizeTool(memory) {
5154
5345
  return defineTool({
@@ -5180,11 +5371,11 @@ function createMemorizeTool(memory) {
5180
5371
 
5181
5372
  // src/tools/recall.ts
5182
5373
  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()
5374
+ var import_zod19 = require("zod");
5375
+ var inputSchema14 = import_zod19.z.object({
5376
+ query: import_zod19.z.string().min(1),
5377
+ scope: import_zod19.z.enum(["identity", "rules", "lessons", "all"]).default("all"),
5378
+ topic: import_zod19.z.string().optional()
5188
5379
  });
5189
5380
  function createRecallTool(memory) {
5190
5381
  return defineTool({
@@ -5239,13 +5430,13 @@ ${content}`,
5239
5430
 
5240
5431
  // src/tools/notebookEdit.ts
5241
5432
  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()
5433
+ var import_zod20 = require("zod");
5434
+ var inputSchema15 = import_zod20.z.object({
5435
+ notebook_path: import_zod20.z.string().min(1),
5436
+ edit_mode: import_zod20.z.enum(["replace", "insert", "delete"]).default("replace"),
5437
+ cell_index: import_zod20.z.number().int().nonnegative(),
5438
+ new_source: import_zod20.z.string().optional(),
5439
+ cell_type: import_zod20.z.enum(["code", "markdown"]).optional()
5249
5440
  });
5250
5441
  function createNotebookEditTool(storage) {
5251
5442
  return defineTool({
@@ -5400,12 +5591,12 @@ var TaskStore = class {
5400
5591
 
5401
5592
  // src/tools/tasks/tools.ts
5402
5593
  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()
5594
+ var import_zod21 = require("zod");
5595
+ var TaskStatusEnum = import_zod21.z.enum(["pending", "in_progress", "completed", "deleted"]);
5596
+ var createSchema = import_zod21.z.object({
5597
+ subject: import_zod21.z.string().min(1),
5598
+ description: import_zod21.z.string().default(""),
5599
+ metadata: import_zod21.z.record(import_zod21.z.unknown()).optional()
5409
5600
  });
5410
5601
  function createTaskCreateTool(store) {
5411
5602
  return defineTool({
@@ -5421,8 +5612,8 @@ function createTaskCreateTool(store) {
5421
5612
  }
5422
5613
  });
5423
5614
  }
5424
- var getSchema = import_zod20.z.object({
5425
- taskId: import_zod20.z.string().min(1)
5615
+ var getSchema = import_zod21.z.object({
5616
+ taskId: import_zod21.z.string().min(1)
5426
5617
  });
5427
5618
  function createTaskGetTool(store) {
5428
5619
  return defineTool({
@@ -5443,7 +5634,7 @@ ${task.description}`,
5443
5634
  }
5444
5635
  });
5445
5636
  }
5446
- var listSchema = import_zod20.z.object({
5637
+ var listSchema = import_zod21.z.object({
5447
5638
  status: TaskStatusEnum.optional()
5448
5639
  });
5449
5640
  function createTaskListTool(store) {
@@ -5469,12 +5660,12 @@ ${lines.join("\n")}`,
5469
5660
  }
5470
5661
  });
5471
5662
  }
5472
- var updateSchema = import_zod20.z.object({
5473
- taskId: import_zod20.z.string().min(1),
5474
- subject: import_zod20.z.string().min(1).optional(),
5663
+ var updateSchema = import_zod21.z.object({
5664
+ taskId: import_zod21.z.string().min(1),
5665
+ subject: import_zod21.z.string().min(1).optional(),
5475
5666
  status: TaskStatusEnum.optional(),
5476
- description: import_zod20.z.string().optional(),
5477
- metadata: import_zod20.z.record(import_zod20.z.unknown()).optional()
5667
+ description: import_zod21.z.string().optional(),
5668
+ metadata: import_zod21.z.record(import_zod21.z.unknown()).optional()
5478
5669
  });
5479
5670
  function createTaskUpdateTool(store) {
5480
5671
  return defineTool({
@@ -6321,7 +6512,7 @@ function wrapFetchWithHeadersProvider(baseFetch, headersProvider, staticHeaders)
6321
6512
 
6322
6513
  // src/mcp/toolAdapter.ts
6323
6514
  init_cjs_shims();
6324
- var import_zod21 = require("zod");
6515
+ var import_zod22 = require("zod");
6325
6516
  function mcpToolName(serverName, toolName) {
6326
6517
  return `mcp__${serverName}__${toolName}`;
6327
6518
  }
@@ -6330,7 +6521,7 @@ function adaptMcpTool(client, serverName, def) {
6330
6521
  return defineTool({
6331
6522
  name: registeredName,
6332
6523
  description: def.description.length > 0 ? def.description : `MCP tool ${serverName}/${def.name}`,
6333
- inputSchema: import_zod21.z.unknown(),
6524
+ inputSchema: import_zod22.z.unknown(),
6334
6525
  // Pass the MCP server's JSON Schema through to Anthropic verbatim,
6335
6526
  // bypassing Zod-to-JSON-Schema conversion (which would produce `{}`
6336
6527
  // for our `z.unknown()` Zod schema).
@@ -6991,7 +7182,7 @@ ${entries.map((e) => `- ${e}`).join("\n")}
6991
7182
  // src/memory/memoryConfig.ts
6992
7183
  function createSmartMemory(options) {
6993
7184
  const { storage, config } = options;
6994
- const adapter = config.scope === "global" ? storage.global : storage.workspace;
7185
+ const adapter = storage.workspace;
6995
7186
  const hippocampus = new Hippocampus(adapter, "smart-memory");
6996
7187
  const writesEnabled = config.mode === "read-write";
6997
7188
  const readsEnabled = config.mode !== "off";
@@ -7362,18 +7553,7 @@ ${lessons}`);
7362
7553
  }
7363
7554
  async function collectSkills(storage, skillsDir) {
7364
7555
  const workspace = await loadSkills(storage.workspace, skillsDir);
7365
- const global = await loadSkills(storage.global, skillsDir);
7366
- const seen = /* @__PURE__ */ new Set();
7367
- const out = [];
7368
- for (const s of workspace) {
7369
- seen.add(s.name);
7370
- out.push({ name: s.name, description: s.description });
7371
- }
7372
- for (const s of global) {
7373
- if (seen.has(s.name)) continue;
7374
- out.push({ name: s.name, description: s.description });
7375
- }
7376
- return out;
7556
+ return workspace.map((s) => ({ name: s.name, description: s.description }));
7377
7557
  }
7378
7558
 
7379
7559
  // src/engine/jsonOutput.ts
@@ -7439,11 +7619,11 @@ ${issues.join("\n")}` };
7439
7619
 
7440
7620
  // src/skills/skillPage.ts
7441
7621
  init_cjs_shims();
7442
- var import_zod22 = require("zod");
7622
+ var import_zod23 = require("zod");
7443
7623
  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()
7624
+ var inputSchema16 = import_zod23.z.object({
7625
+ skill: import_zod23.z.string().min(1),
7626
+ page: import_zod23.z.string().min(1).optional()
7447
7627
  });
7448
7628
  function createSkillPageTool(options) {
7449
7629
  return defineTool({
@@ -7499,6 +7679,203 @@ The skill "${skill}" has no pages.`;
7499
7679
  });
7500
7680
  }
7501
7681
 
7682
+ // src/tools/apiCall.ts
7683
+ init_cjs_shims();
7684
+ var import_zod24 = require("zod");
7685
+ var ALL_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
7686
+ var DEFAULT_MAX_BODY_BYTES = 256 * 1024;
7687
+ var DEFAULT_MAX_RESPONSE_BYTES = 100 * 1024;
7688
+ function createApiCallTool(opts) {
7689
+ if (opts.services.length === 0) {
7690
+ throw new Error("createApiCallTool: services list must be non-empty");
7691
+ }
7692
+ const services = /* @__PURE__ */ new Map();
7693
+ for (const svc of opts.services) {
7694
+ if (services.has(svc.name)) {
7695
+ throw new Error(`createApiCallTool: duplicate service name "${svc.name}"`);
7696
+ }
7697
+ services.set(svc.name, svc);
7698
+ }
7699
+ const serviceNames = [...services.keys()];
7700
+ for (const svc of opts.services) {
7701
+ if (svc.auth?.type === "custom" && opts.resolveAuth === void 0) {
7702
+ throw new Error(
7703
+ `createApiCallTool: service "${svc.name}" uses custom auth (id: ${svc.auth.id}) but no resolveAuth was supplied`
7704
+ );
7705
+ }
7706
+ }
7707
+ const fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
7708
+ const maxResponseBytes = opts.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
7709
+ const inputSchema17 = import_zod24.z.object({
7710
+ service: import_zod24.z.enum(serviceNames),
7711
+ method: import_zod24.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
7712
+ path: import_zod24.z.string().regex(/^\//, "path must start with /"),
7713
+ query: import_zod24.z.record(import_zod24.z.string(), import_zod24.z.string()).optional(),
7714
+ body: import_zod24.z.unknown().optional(),
7715
+ headers: import_zod24.z.record(import_zod24.z.string(), import_zod24.z.string()).optional()
7716
+ });
7717
+ const description = opts.toolDescription ?? `Call a configured external API. Services: ${serviceNames.join(", ")}. Auth is injected automatically \u2014 do not pass credentials via headers.`;
7718
+ return defineTool({
7719
+ name: opts.toolName ?? "ApiCall",
7720
+ description,
7721
+ inputSchema: inputSchema17,
7722
+ execute: async (input) => {
7723
+ const svc = services.get(input.service);
7724
+ if (!svc) {
7725
+ return errResult(`ERR_API_UNKNOWN_SERVICE: ${input.service}`);
7726
+ }
7727
+ const allowedMethods = svc.allowedMethods ?? ALL_METHODS;
7728
+ if (!allowedMethods.includes(input.method)) {
7729
+ return errResult(
7730
+ `ERR_API_METHOD_NOT_ALLOWED: ${input.method} not permitted for service ${svc.name}`
7731
+ );
7732
+ }
7733
+ if (!pathAllowed(input.path, svc.allowedPaths)) {
7734
+ return errResult(`ERR_API_PATH_NOT_ALLOWED: ${input.path} for service ${svc.name}`);
7735
+ }
7736
+ let bodyText;
7737
+ if (input.body !== void 0) {
7738
+ bodyText = JSON.stringify(input.body);
7739
+ const cap = svc.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
7740
+ if (byteLength(bodyText) > cap) {
7741
+ return errResult(`ERR_API_BODY_TOO_LARGE: exceeds ${cap} bytes`);
7742
+ }
7743
+ }
7744
+ let authHeaders;
7745
+ try {
7746
+ authHeaders = await resolveAuth({
7747
+ auth: svc.auth ?? { type: "none" },
7748
+ env: opts.env,
7749
+ resolver: opts.resolveAuth,
7750
+ ctx: { serviceName: svc.name, method: input.method, path: input.path }
7751
+ });
7752
+ } catch (err) {
7753
+ const raw2 = err instanceof Error ? err.message : String(err);
7754
+ const truncated = raw2.length > 200 ? raw2.slice(0, 200) + "\u2026" : raw2;
7755
+ return errResult(`ERR_API_RESOLVER_FAILED: ${truncated}`);
7756
+ }
7757
+ const userHeaders = sanitizeHeaders(input.headers ?? {}, authHeaders);
7758
+ const url = buildUrl(svc.baseUrl, input.path, input.query);
7759
+ await invokeHook(opts.onRequest, {
7760
+ service: svc.name,
7761
+ method: input.method,
7762
+ path: input.path
7763
+ });
7764
+ const started = Date.now();
7765
+ let res;
7766
+ try {
7767
+ res = await fetchFn(url, {
7768
+ method: input.method,
7769
+ headers: {
7770
+ "Content-Type": "application/json",
7771
+ ...svc.defaultHeaders ?? {},
7772
+ ...userHeaders,
7773
+ ...authHeaders
7774
+ // wins last — model cannot override
7775
+ },
7776
+ ...bodyText !== void 0 ? { body: bodyText } : {}
7777
+ });
7778
+ } catch (err) {
7779
+ const msg = err instanceof Error ? err.message : String(err);
7780
+ return errResult(`network error: ${msg}`);
7781
+ }
7782
+ const raw = await res.text();
7783
+ const content = raw.length > maxResponseBytes ? raw.slice(0, maxResponseBytes) + "\n\u2026[TRUNCATED]" : raw;
7784
+ await invokeHook(opts.onResponse, {
7785
+ service: svc.name,
7786
+ method: input.method,
7787
+ path: input.path,
7788
+ status: res.status,
7789
+ latencyMs: Date.now() - started,
7790
+ bytesIn: raw.length
7791
+ });
7792
+ return {
7793
+ content,
7794
+ isError: !res.ok,
7795
+ metadata: { status: res.status, service: svc.name }
7796
+ };
7797
+ }
7798
+ });
7799
+ }
7800
+ function errResult(msg) {
7801
+ return { content: msg, isError: true };
7802
+ }
7803
+ function pathAllowed(path, allowed) {
7804
+ if (!allowed || allowed.length === 0) return true;
7805
+ for (const a of allowed) {
7806
+ if (typeof a === "string") {
7807
+ if (path.startsWith(a)) return true;
7808
+ } else if (a.test(path)) {
7809
+ return true;
7810
+ }
7811
+ }
7812
+ return false;
7813
+ }
7814
+ function byteLength(s) {
7815
+ return new TextEncoder().encode(s).byteLength;
7816
+ }
7817
+ function buildUrl(baseUrl, path, query) {
7818
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
7819
+ const url = new URL(base + path);
7820
+ for (const [k, v] of Object.entries(query ?? {})) {
7821
+ url.searchParams.set(k, v);
7822
+ }
7823
+ return url.toString();
7824
+ }
7825
+ function sanitizeHeaders(user, auth) {
7826
+ const banned = new Set(Object.keys(auth).map((k) => k.toLowerCase()));
7827
+ const out = {};
7828
+ for (const [k, v] of Object.entries(user)) {
7829
+ if (banned.has(k.toLowerCase())) continue;
7830
+ out[k] = v;
7831
+ }
7832
+ return out;
7833
+ }
7834
+ async function resolveAuth(args) {
7835
+ const { auth, env, resolver, ctx } = args;
7836
+ switch (auth.type) {
7837
+ case "none":
7838
+ return {};
7839
+ case "bearer": {
7840
+ const token = envLookup(env, auth.tokenRef);
7841
+ return { Authorization: `Bearer ${token}` };
7842
+ }
7843
+ case "header": {
7844
+ const value = envLookup(env, auth.valueRef);
7845
+ return { [auth.name]: value };
7846
+ }
7847
+ case "basic": {
7848
+ const u = envLookup(env, auth.userRef);
7849
+ const p = envLookup(env, auth.passRef);
7850
+ return { Authorization: `Basic ${base64(`${u}:${p}`)}` };
7851
+ }
7852
+ case "custom":
7853
+ if (resolver === void 0) {
7854
+ throw new Error(`custom auth id "${auth.id}" requires resolveAuth`);
7855
+ }
7856
+ return resolver(auth, ctx);
7857
+ }
7858
+ }
7859
+ function envLookup(env, ref) {
7860
+ if (env === void 0 || env[ref] === void 0) {
7861
+ throw new Error(`ERR_API_ENV_MISSING: ${ref}`);
7862
+ }
7863
+ return env[ref];
7864
+ }
7865
+ function base64(input) {
7866
+ if (typeof globalThis.btoa === "function") {
7867
+ return globalThis.btoa(input);
7868
+ }
7869
+ return Buffer.from(input, "utf8").toString("base64");
7870
+ }
7871
+ async function invokeHook(hook, event) {
7872
+ if (hook === void 0) return;
7873
+ try {
7874
+ await hook(event);
7875
+ } catch {
7876
+ }
7877
+ }
7878
+
7502
7879
  // src/skills/storageSkillSource.ts
7503
7880
  init_cjs_shims();
7504
7881
  var SAFE_NAME4 = /^[a-zA-Z0-9_-]+$/;
@@ -7510,14 +7887,8 @@ var StorageSkillSource = class {
7510
7887
  this.baseDir = options.baseDir ?? "skills";
7511
7888
  }
7512
7889
  async list() {
7513
- const [workspace, global] = await Promise.all([
7514
- loadSkills(this.storage.workspace, this.baseDir),
7515
- loadSkills(this.storage.global, this.baseDir)
7516
- ]);
7517
- const byName = /* @__PURE__ */ new Map();
7518
- for (const s of global) byName.set(s.name, s);
7519
- for (const s of workspace) byName.set(s.name, s);
7520
- return Array.from(byName.values()).map((s) => ({
7890
+ const loaded = await loadSkills(this.storage.workspace, this.baseDir);
7891
+ return loaded.map((s) => ({
7521
7892
  name: s.name,
7522
7893
  description: s.description,
7523
7894
  hasPages: s.hasPages
@@ -7525,8 +7896,7 @@ var StorageSkillSource = class {
7525
7896
  }
7526
7897
  async getSkillFile(skill) {
7527
7898
  if (!SAFE_NAME4.test(skill)) return null;
7528
- const found = await this.findFile(skill, "SKILL.md");
7529
- return found;
7899
+ return this.findFile(skill, "SKILL.md");
7530
7900
  }
7531
7901
  async getPage(skill, page) {
7532
7902
  if (!SAFE_NAME4.test(skill)) return null;
@@ -7535,9 +7905,7 @@ var StorageSkillSource = class {
7535
7905
  }
7536
7906
  async listPages(skill) {
7537
7907
  if (!SAFE_NAME4.test(skill)) return [];
7538
- const wsPages = await this.listPagesIn(this.storage.workspace, skill);
7539
- if (wsPages.length > 0) return wsPages;
7540
- return this.listPagesIn(this.storage.global, skill);
7908
+ return this.listPagesIn(this.storage.workspace, skill);
7541
7909
  }
7542
7910
  async listPagesIn(adapter, skill) {
7543
7911
  const pagesDir = `${this.baseDir}/${skill}/pages`;
@@ -7550,9 +7918,7 @@ var StorageSkillSource = class {
7550
7918
  }
7551
7919
  async findFile(skill, relative) {
7552
7920
  const path = `${this.baseDir}/${skill}/${relative}`;
7553
- const fromWorkspace = await this.readIfExists(this.storage.workspace, path);
7554
- if (fromWorkspace !== null) return fromWorkspace;
7555
- return this.readIfExists(this.storage.global, path);
7921
+ return this.readIfExists(this.storage.workspace, path);
7556
7922
  }
7557
7923
  async readIfExists(adapter, path) {
7558
7924
  try {
@@ -8135,7 +8501,6 @@ async function createEngineStorage(config) {
8135
8501
  }
8136
8502
  async function createLocalStorage(config) {
8137
8503
  const path = await import("path");
8138
- const globalRoot = path.join(config.rootPath, ENGINE_DATA_FOLDER);
8139
8504
  const workspaceRoot = path.join(
8140
8505
  config.rootPath,
8141
8506
  WORKSPACES_FOLDER,
@@ -8143,7 +8508,6 @@ async function createLocalStorage(config) {
8143
8508
  ENGINE_DATA_FOLDER
8144
8509
  );
8145
8510
  return {
8146
- global: new LocalStorageAdapter(globalRoot),
8147
8511
  workspace: new LocalStorageAdapter(workspaceRoot)
8148
8512
  };
8149
8513
  }
@@ -8152,10 +8516,8 @@ function createR2Storage(config) {
8152
8516
  throw new StorageError('storage.r2 is required when storage.provider === "r2"');
8153
8517
  }
8154
8518
  const rootPrefix = config.rootPath.replace(/^\/+|\/+$/g, "");
8155
- const globalPrefix = `${rootPrefix}/${ENGINE_DATA_FOLDER}`;
8156
8519
  const workspacePrefix = `${rootPrefix}/${WORKSPACES_FOLDER}/${config.workspaceId}/${ENGINE_DATA_FOLDER}`;
8157
8520
  return {
8158
- global: new R2StorageAdapter(config.r2, globalPrefix),
8159
8521
  workspace: new R2StorageAdapter(config.r2, workspacePrefix)
8160
8522
  };
8161
8523
  }
@@ -8164,10 +8526,8 @@ function createR2BindingStorage(config) {
8164
8526
  throw new StorageError('storage.r2Binding is required when storage.provider === "r2-binding"');
8165
8527
  }
8166
8528
  const rootPrefix = config.rootPath.replace(/^\/+|\/+$/g, "");
8167
- const globalPrefix = `${rootPrefix}/${ENGINE_DATA_FOLDER}`;
8168
8529
  const workspacePrefix = `${rootPrefix}/${WORKSPACES_FOLDER}/${config.workspaceId}/${ENGINE_DATA_FOLDER}`;
8169
8530
  return {
8170
- global: new R2BindingStorageAdapter(config.r2Binding, globalPrefix),
8171
8531
  workspace: new R2BindingStorageAdapter(config.r2Binding, workspacePrefix)
8172
8532
  };
8173
8533
  }
@@ -8274,6 +8634,7 @@ function rebuildMessagesFromEntries(entries) {
8274
8634
  init_cjs_shims();
8275
8635
  function toResponse(result, extra) {
8276
8636
  const timestamp = Date.now();
8637
+ const capsField = extra.capabilitiesMissing !== void 0 && extra.capabilitiesMissing.length > 0 ? { capabilitiesMissing: extra.capabilitiesMissing } : {};
8277
8638
  if (result.status === "done") {
8278
8639
  return {
8279
8640
  runId: extra.runId,
@@ -8285,7 +8646,8 @@ function toResponse(result, extra) {
8285
8646
  tokensUsed: result.tokensUsed,
8286
8647
  durationMs: extra.durationMs,
8287
8648
  output: result.output,
8288
- ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: 0 } } : {}
8649
+ ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: 0 } } : {},
8650
+ ...capsField
8289
8651
  },
8290
8652
  errors: [],
8291
8653
  timestamp
@@ -8304,7 +8666,8 @@ function toResponse(result, extra) {
8304
8666
  snapshot: result.snapshot,
8305
8667
  ...result.snapshot.pendingToolCall !== void 0 ? { pendingToolCall: result.snapshot.pendingToolCall } : {},
8306
8668
  pauseReason: result.reason,
8307
- ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: result.snapshot.lastShardIndex } } : {}
8669
+ ...extra.logPath !== void 0 ? { transcript: { path: extra.logPath, lastShardIndex: result.snapshot.lastShardIndex } } : {},
8670
+ ...capsField
8308
8671
  },
8309
8672
  errors: [],
8310
8673
  timestamp
@@ -8638,7 +9001,15 @@ var Engine = class {
8638
9001
  this.mcpManager = new McpManager(config.mcp, createLogger(config.logging), { samplingHandler });
8639
9002
  this.permissionPolicy = buildPermissionPolicy(config.permissions);
8640
9003
  }
8641
- async run(options) {
9004
+ /**
9005
+ * Run a task synchronously to completion. The `_internal` parameter is
9006
+ * reserved for the engine's own async wrappers (`start`, `resumeAsync`)
9007
+ * to request runner handoff (Plan 019) — external callers must leave
9008
+ * it unset. Sync callers never hand off; if they hit a Node-only tool
9009
+ * on a restricted runtime, the capability stub returns an error and
9010
+ * the model adapts.
9011
+ */
9012
+ async run(options, _internal) {
8642
9013
  const startTime = Date.now();
8643
9014
  const runId = options.runId ?? `run_${randomUUID()}`;
8644
9015
  const log = createLogger(this.config.logging);
@@ -8661,6 +9032,7 @@ var Engine = class {
8661
9032
  const coordinatorBase = isCoordinatorMode(this.config) ? getCoordinatorBasePrompt() : void 0;
8662
9033
  const skillSource = this.resolveSkillSource(options.skills, storage);
8663
9034
  const skillList = skillSource !== void 0 ? await skillSource.list() : void 0;
9035
+ const apiConfig = this.resolveApiConfig(options.api);
8664
9036
  let systemPrompt = await buildSystemPrompt({
8665
9037
  ...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
8666
9038
  memory,
@@ -8691,7 +9063,9 @@ var Engine = class {
8691
9063
  mcpTools,
8692
9064
  memory,
8693
9065
  ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
8694
- ...skillSource !== void 0 ? { skillSource } : {}
9066
+ ...skillSource !== void 0 ? { skillSource } : {},
9067
+ ...apiConfig !== void 0 ? { apiConfig } : {},
9068
+ ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
8695
9069
  });
8696
9070
  const writer = new TranscriptWriter({
8697
9071
  storage: storage.workspace,
@@ -8745,7 +9119,8 @@ var Engine = class {
8745
9119
  onProgress: this.buildHeartbeat(storage, runId, options.nodeId),
8746
9120
  ...options.tokenBudget !== void 0 ? { tokenBudget: options.tokenBudget } : {},
8747
9121
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
8748
- ...gate !== void 0 ? { gateBeforeTool: gate } : {}
9122
+ ...gate !== void 0 ? { gateBeforeTool: gate } : {},
9123
+ ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {}
8749
9124
  });
8750
9125
  const result = await this.finalizeResult(loopResult, writer, logPath, {
8751
9126
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -8753,18 +9128,20 @@ var Engine = class {
8753
9128
  });
8754
9129
  this.logRunEnd(log, runId, options.nodeId, result);
8755
9130
  await this.firePostRunHook(runId, options.nodeId, result, ctx, logPath);
9131
+ const capabilitiesMissing = ctx.getCapabilitiesMissing();
8756
9132
  return toResponse(result, {
8757
9133
  runId,
8758
9134
  nodeId: options.nodeId,
8759
9135
  durationMs: Date.now() - startTime,
8760
- logPath
9136
+ logPath,
9137
+ ...capabilitiesMissing.length > 0 ? { capabilitiesMissing } : {}
8761
9138
  });
8762
9139
  } finally {
8763
9140
  runTimeout.clear();
8764
9141
  await writer.close();
8765
9142
  }
8766
9143
  }
8767
- async resume(options) {
9144
+ async resume(options, _internal) {
8768
9145
  const startTime = Date.now();
8769
9146
  const log = createLogger(this.config.logging);
8770
9147
  const storage = await this.buildStorage();
@@ -8797,6 +9174,7 @@ var Engine = class {
8797
9174
  const coordinatorBase = isCoordinatorMode(this.config) ? getCoordinatorBasePrompt() : void 0;
8798
9175
  const skillSource = this.resolveSkillSource(options.skills, storage);
8799
9176
  const skillList = skillSource !== void 0 ? await skillSource.list() : void 0;
9177
+ const apiConfig = this.resolveApiConfig(options.api);
8800
9178
  let systemPrompt = await buildSystemPrompt({
8801
9179
  ...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
8802
9180
  memory,
@@ -8827,7 +9205,9 @@ var Engine = class {
8827
9205
  mcpTools,
8828
9206
  memory,
8829
9207
  ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
8830
- ...skillSource !== void 0 ? { skillSource } : {}
9208
+ ...skillSource !== void 0 ? { skillSource } : {},
9209
+ ...apiConfig !== void 0 ? { apiConfig } : {},
9210
+ ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
8831
9211
  });
8832
9212
  const priorState = await loadWriterState(storage.workspace, logPath);
8833
9213
  const writer = new TranscriptWriter({
@@ -8881,18 +9261,21 @@ var Engine = class {
8881
9261
  const answer = typeof options.gateAnswer === "string" ? options.gateAnswer : JSON.stringify(options.gateAnswer);
8882
9262
  await ctx.addToolResult(pending.toolUseId, answer, false);
8883
9263
  } 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
9264
  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):
9265
+ await ctx.addMixedUserMessage([
9266
+ {
9267
+ type: "tool_result",
9268
+ tool_use_id: pending.toolUseId,
9269
+ content: `APPROVAL_GATE_RELEASED: the prior ${pending.toolName} call was paused for human approval and has now been approved. Retry is required.`
9270
+ },
9271
+ {
9272
+ type: "text",
9273
+ 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
9274
  \`\`\`json
8893
9275
  ${inputJson}
8894
9276
  \`\`\``
8895
- );
9277
+ }
9278
+ ]);
8896
9279
  }
8897
9280
  }
8898
9281
  await writer.setStatus("running");
@@ -8913,7 +9296,8 @@ ${inputJson}
8913
9296
  stopHooks: this.config.hooks.stopHooks,
8914
9297
  onProgress: this.buildHeartbeat(storage, snapshot.runId, snapshot.nodeId),
8915
9298
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
8916
- ...gate !== void 0 ? { gateBeforeTool: gate } : {}
9299
+ ...gate !== void 0 ? { gateBeforeTool: gate } : {},
9300
+ ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {}
8917
9301
  });
8918
9302
  const result = await this.finalizeResult(loopResult, writer, logPath, {
8919
9303
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -8921,11 +9305,13 @@ ${inputJson}
8921
9305
  });
8922
9306
  this.logRunEnd(log, snapshot.runId, snapshot.nodeId, result);
8923
9307
  await this.firePostRunHook(snapshot.runId, snapshot.nodeId, result, ctx, logPath);
9308
+ const capabilitiesMissing = ctx.getCapabilitiesMissing();
8924
9309
  return toResponse(result, {
8925
9310
  runId: snapshot.runId,
8926
9311
  nodeId: snapshot.nodeId,
8927
9312
  durationMs: Date.now() - startTime,
8928
- logPath
9313
+ logPath,
9314
+ ...capabilitiesMissing.length > 0 ? { capabilitiesMissing } : {}
8929
9315
  });
8930
9316
  } finally {
8931
9317
  runTimeout.clear();
@@ -8995,13 +9381,15 @@ ${inputJson}
8995
9381
  } : void 0;
8996
9382
  const initial = RunStateManager.initial(runId, options.nodeId, webhook);
8997
9383
  await stateManager.write(initial);
9384
+ const handoffEnabled = this.config.runner !== void 0;
8998
9385
  this.backgroundExecutor.schedule(runId, async (signal) => {
8999
9386
  await stateManager.update(runId, options.nodeId, { status: "running" });
9000
9387
  try {
9001
- const response = await this.run({ ...options, runId });
9388
+ const response = await this.run({ ...options, runId }, { handoffToRunner: handoffEnabled });
9002
9389
  if (signal.aborted) return;
9003
- await stateManager.finalize(runId, options.nodeId, response);
9004
- await this.maybeFireWebhook(stateManager, runId, options.nodeId, response);
9390
+ const postHandoff = await this.maybeHandoffToRunner(runId, options.nodeId, response);
9391
+ await stateManager.finalize(runId, options.nodeId, postHandoff);
9392
+ await this.maybeFireWebhook(stateManager, runId, options.nodeId, postHandoff);
9005
9393
  } catch (err) {
9006
9394
  if (signal.aborted) return;
9007
9395
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -9049,12 +9437,14 @@ ${inputJson}
9049
9437
  } : { ...RunStateManager.initial(options.runId, nodeId, webhook), status: "running" };
9050
9438
  await stateManager.write(next);
9051
9439
  const resumeNodeId = nodeId;
9440
+ const handoffEnabled = this.config.runner !== void 0;
9052
9441
  this.backgroundExecutor.schedule(options.runId, async (signal) => {
9053
9442
  try {
9054
- const response = await this.resume(options);
9443
+ const response = await this.resume(options, { handoffToRunner: handoffEnabled });
9055
9444
  if (signal.aborted) return;
9056
- await stateManager.finalize(options.runId, resumeNodeId, response);
9057
- await this.maybeFireWebhook(stateManager, options.runId, resumeNodeId, response);
9445
+ const postHandoff = await this.maybeHandoffToRunner(options.runId, resumeNodeId, response);
9446
+ await stateManager.finalize(options.runId, resumeNodeId, postHandoff);
9447
+ await this.maybeFireWebhook(stateManager, options.runId, resumeNodeId, postHandoff);
9058
9448
  } catch (err) {
9059
9449
  if (signal.aborted) return;
9060
9450
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -9243,6 +9633,68 @@ ${inputJson}
9243
9633
  }
9244
9634
  return orphaned;
9245
9635
  }
9636
+ // ---------- runner handoff (Plan 019) ----------
9637
+ /**
9638
+ * When the response indicates the run paused for runner handoff, POST
9639
+ * `{ runId }` to the configured runner URL. On success, return the
9640
+ * response unchanged — state stays `paused` and the runner will flip
9641
+ * it to `done` (or `failed`) on its side. On POST failure, convert
9642
+ * the response to `failed` with `ERR_RUNNER_UNREACHABLE` so the
9643
+ * caller sees a terminal state instead of a silent hang.
9644
+ *
9645
+ * Called only from `start()` / `resumeAsync()`. Sync `run()` never
9646
+ * produces a `handoff_to_runner` pause (see `_internal.handoffToRunner`).
9647
+ */
9648
+ async maybeHandoffToRunner(runId, nodeId, response) {
9649
+ if (response.status !== "paused") return response;
9650
+ if (response.meta.pauseReason !== "handoff_to_runner") return response;
9651
+ const runner = this.config.runner;
9652
+ if (runner === void 0) {
9653
+ return response;
9654
+ }
9655
+ const fetchFn = this.internals.fetch ?? globalThis.fetch.bind(globalThis);
9656
+ try {
9657
+ const res = await fetchFn(runner.url, {
9658
+ method: "POST",
9659
+ headers: {
9660
+ "Content-Type": "application/json",
9661
+ Authorization: `Bearer ${runner.secret}`
9662
+ },
9663
+ body: JSON.stringify({ runId })
9664
+ });
9665
+ if (!res.ok) {
9666
+ return {
9667
+ runId,
9668
+ status: "failed",
9669
+ data: null,
9670
+ meta: { nodeId },
9671
+ errors: [
9672
+ {
9673
+ code: "ERR_RUNNER_UNREACHABLE",
9674
+ message: `Runner returned HTTP ${res.status}`
9675
+ }
9676
+ ],
9677
+ timestamp: Date.now()
9678
+ };
9679
+ }
9680
+ return response;
9681
+ } catch (err) {
9682
+ const msg = err instanceof Error ? err.message : String(err);
9683
+ return {
9684
+ runId,
9685
+ status: "failed",
9686
+ data: null,
9687
+ meta: { nodeId },
9688
+ errors: [
9689
+ {
9690
+ code: "ERR_RUNNER_UNREACHABLE",
9691
+ message: `Runner handoff failed: ${msg}`
9692
+ }
9693
+ ],
9694
+ timestamp: Date.now()
9695
+ };
9696
+ }
9697
+ }
9246
9698
  // ---------- webhook helpers ----------
9247
9699
  async maybeFireWebhook(stateManager, runId, nodeId, response) {
9248
9700
  const state = await stateManager.read(runId, nodeId);
@@ -9386,6 +9838,11 @@ ${inputJson}
9386
9838
  if (disabled.has(name)) continue;
9387
9839
  if (wantAll || enabled.has(name)) names.add(name);
9388
9840
  }
9841
+ if ((this.config.api?.services.length ?? 0) > 0) {
9842
+ if (!disabled.has("ApiCall") && (wantAll || enabled.has("ApiCall"))) {
9843
+ names.add("ApiCall");
9844
+ }
9845
+ }
9389
9846
  for (const tool of this.config.tools.custom) {
9390
9847
  names.add(tool.name);
9391
9848
  }
@@ -9542,6 +9999,34 @@ ${inputJson}
9542
9999
  }
9543
10000
  return void 0;
9544
10001
  }
10002
+ /**
10003
+ * Plan 020 — resolve the effective ApiCall config for a run.
10004
+ *
10005
+ * Precedence (each field independently):
10006
+ * RunOptions.api.X > config.api.X
10007
+ *
10008
+ * If neither side provides any services, returns undefined so the
10009
+ * tool isn't registered at all. Env + resolveAuth + hooks flow
10010
+ * through untouched — they never hit the Zod schema.
10011
+ */
10012
+ resolveApiConfig(override) {
10013
+ const base = this.config.api;
10014
+ if (override === void 0 && base === void 0) return void 0;
10015
+ const services = override?.services ?? base?.services;
10016
+ if (services === void 0 || services.length === 0) return void 0;
10017
+ const env = override?.env ?? base?.env;
10018
+ const resolveAuth2 = override?.resolveAuth ?? base?.resolveAuth;
10019
+ const onRequest = override?.onRequest ?? base?.onRequest;
10020
+ const onResponse = override?.onResponse ?? base?.onResponse;
10021
+ return {
10022
+ services,
10023
+ ...env !== void 0 ? { env } : {},
10024
+ ...resolveAuth2 !== void 0 ? { resolveAuth: resolveAuth2 } : {},
10025
+ ...onRequest !== void 0 ? { onRequest } : {},
10026
+ ...onResponse !== void 0 ? { onResponse } : {},
10027
+ ...base?.maxResponseBytes !== void 0 ? { maxResponseBytes: base.maxResponseBytes } : {}
10028
+ };
10029
+ }
9545
10030
  /**
9546
10031
  * Build a throttled heartbeat callback for agentLoop's `onProgress` hook.
9547
10032
  *
@@ -9636,8 +10121,8 @@ function buildToolRegistry(options) {
9636
10121
  const fileTracker = new FileTracker();
9637
10122
  const spawnAvailable = canSpawnProcesses();
9638
10123
  const candidates = [
9639
- // Bash — requires child_process.spawn (Node.js only).
9640
- ...spawnAvailable ? [{ name: "Bash", tool: createBashTool() }] : [],
10124
+ // Bash — requires child_process.spawn. Stubbed on Workers.
10125
+ { name: "Bash", tool: withCapabilityCheck(createBashTool(), spawnAvailable) },
9641
10126
  {
9642
10127
  name: "Read",
9643
10128
  tool: createFileReadTool({ storage: storage.workspace, tracker: fileTracker })
@@ -9683,6 +10168,21 @@ function buildToolRegistry(options) {
9683
10168
  childRegistry.register(skillTool);
9684
10169
  }
9685
10170
  }
10171
+ if (options.apiConfig !== void 0 && options.apiConfig.services.length > 0) {
10172
+ if (!disabled.has("ApiCall") && (wantAll || enabled.has("ApiCall"))) {
10173
+ const apiTool = createApiCallTool({
10174
+ services: options.apiConfig.services,
10175
+ ...options.fetch !== void 0 ? { fetch: options.fetch } : {},
10176
+ ...options.apiConfig.env !== void 0 ? { env: options.apiConfig.env } : {},
10177
+ ...options.apiConfig.resolveAuth !== void 0 ? { resolveAuth: options.apiConfig.resolveAuth } : {},
10178
+ ...options.apiConfig.onRequest !== void 0 ? { onRequest: options.apiConfig.onRequest } : {},
10179
+ ...options.apiConfig.onResponse !== void 0 ? { onResponse: options.apiConfig.onResponse } : {},
10180
+ ...options.apiConfig.maxResponseBytes !== void 0 ? { maxResponseBytes: options.apiConfig.maxResponseBytes } : {}
10181
+ });
10182
+ registry.register(apiTool);
10183
+ childRegistry.register(apiTool);
10184
+ }
10185
+ }
9686
10186
  const agentTool = createAgentTool({
9687
10187
  storage: storage.workspace,
9688
10188
  client,
@@ -9805,6 +10305,8 @@ function resolveApiKey(config) {
9805
10305
  buildSystemPrompt,
9806
10306
  buildWorkerAgent,
9807
10307
  canSpawnProcesses,
10308
+ capabilityStub,
10309
+ createApiCallTool,
9808
10310
  createLogger,
9809
10311
  createModelAdapter,
9810
10312
  createSendMessageTool,
@@ -9836,6 +10338,7 @@ function resolveApiKey(config) {
9836
10338
  synthesizeSpec,
9837
10339
  toResponse,
9838
10340
  tryParseJSON,
9839
- validateOutput
10341
+ validateOutput,
10342
+ withCapabilityCheck
9840
10343
  });
9841
10344
  //# sourceMappingURL=index.cjs.map