la-machina-engine 0.7.4 → 0.7.6

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
@@ -841,6 +841,7 @@ __export(index_exports, {
841
841
  EpisodicMemory: () => EpisodicMemory,
842
842
  Hippocampus: () => Hippocampus,
843
843
  InlineSkillSource: () => InlineSkillSource,
844
+ InspectWriter: () => InspectWriter,
844
845
  LocalStorageAdapter: () => LocalStorageAdapter,
845
846
  MAX_ATTEMPTS: () => MAX_ATTEMPTS,
846
847
  McpClient: () => McpClient,
@@ -848,6 +849,7 @@ __export(index_exports, {
848
849
  McpManager: () => McpManager,
849
850
  McpProtocolError: () => McpProtocolError,
850
851
  McpTimeoutError: () => McpTimeoutError,
852
+ NULL_INSPECT_WRITER: () => NULL_INSPECT_WRITER,
851
853
  NodeBackgroundExecutor: () => NodeBackgroundExecutor,
852
854
  NotImplementedError: () => NotImplementedError,
853
855
  PlanSchema: () => PlanSchema,
@@ -1022,6 +1024,10 @@ var DEFAULTS = {
1022
1024
  enableRollback: true,
1023
1025
  maxPlanSteps: 20,
1024
1026
  agentMaxTurns: 15
1027
+ },
1028
+ inspect: {
1029
+ enabled: true,
1030
+ redactBaseURL: false
1025
1031
  }
1026
1032
  };
1027
1033
 
@@ -1257,6 +1263,11 @@ var RunnerConfigResolved = import_zod.z.object({
1257
1263
  url: import_zod.z.string().url(),
1258
1264
  secret: import_zod.z.string().min(1, "runner.secret cannot be empty")
1259
1265
  }).strict();
1266
+ var InspectConfigResolved = import_zod.z.object({
1267
+ enabled: import_zod.z.boolean(),
1268
+ redactBaseURL: import_zod.z.boolean()
1269
+ }).strict();
1270
+ var InspectConfigUser = InspectConfigResolved.partial();
1260
1271
  var ResolvedConfigSchema = import_zod.z.object({
1261
1272
  model: ModelConfigResolved,
1262
1273
  storage: StorageConfigResolved,
@@ -1275,7 +1286,8 @@ var ResolvedConfigSchema = import_zod.z.object({
1275
1286
  orchestrator: OrchestratorConfigResolved,
1276
1287
  runner: RunnerConfigResolved.optional(),
1277
1288
  api: ApiConfigResolved.optional(),
1278
- knowledge: KnowledgeConfigResolved.optional()
1289
+ knowledge: KnowledgeConfigResolved.optional(),
1290
+ inspect: InspectConfigResolved
1279
1291
  }).strict();
1280
1292
  var R2ConfigUser = R2ConfigResolved.partial();
1281
1293
  var ModelConfigUser = ModelConfigResolved.partial();
@@ -1352,7 +1364,8 @@ var UserConfigSchema = import_zod.z.object({
1352
1364
  orchestrator: OrchestratorConfigUser.optional(),
1353
1365
  runner: RunnerConfigUser.optional(),
1354
1366
  api: ApiConfigUser.optional(),
1355
- knowledge: KnowledgeConfigUser.optional()
1367
+ knowledge: KnowledgeConfigUser.optional(),
1368
+ inspect: InspectConfigUser.optional()
1356
1369
  }).strict();
1357
1370
 
1358
1371
  // src/config/merge.ts
@@ -3025,6 +3038,30 @@ function isSubagentPausedError(err) {
3025
3038
  }
3026
3039
 
3027
3040
  // src/engine/agentLoop.ts
3041
+ async function emitInspectTurn(args) {
3042
+ const writer = args.inspect;
3043
+ if (writer === void 0) return;
3044
+ const completedAt = Date.now();
3045
+ const cacheCreate = args.endTokens.cacheCreationInput !== void 0 ? args.endTokens.cacheCreationInput - (args.startTokens.cacheCreationInput ?? 0) : void 0;
3046
+ const cacheRead = args.endTokens.cacheReadInput !== void 0 ? args.endTokens.cacheReadInput - (args.startTokens.cacheReadInput ?? 0) : void 0;
3047
+ const delta = {
3048
+ input: args.endTokens.input - args.startTokens.input,
3049
+ output: args.endTokens.output - args.startTokens.output,
3050
+ ...cacheCreate !== void 0 ? { cacheCreationInput: cacheCreate } : {},
3051
+ ...cacheRead !== void 0 ? { cacheReadInput: cacheRead } : {}
3052
+ };
3053
+ await writer.appendTurn({
3054
+ turn: args.turn,
3055
+ startedAt: args.startedAt,
3056
+ completedAt,
3057
+ durationMs: completedAt - args.startedAt,
3058
+ tokensUsed: delta,
3059
+ stopReason: args.stopReason,
3060
+ toolCallsCount: args.toolCallsCount,
3061
+ ...cacheCreate !== void 0 ? { cacheCreationInput: cacheCreate } : {},
3062
+ ...cacheRead !== void 0 ? { cacheReadInput: cacheRead } : {}
3063
+ });
3064
+ }
3028
3065
  var DEFAULT_COMPACTION = {
3029
3066
  strategy: "drop-middle",
3030
3067
  threshold: 0.85,
@@ -3067,6 +3104,8 @@ async function agentLoop(options) {
3067
3104
  };
3068
3105
  for (; ; ) {
3069
3106
  compactedThisTurn = false;
3107
+ const turnStartedAt = Date.now();
3108
+ const turnStartTokens = { ...ctx.getTokensUsed() };
3070
3109
  if (options.runSignal?.aborted === true) {
3071
3110
  return failed(new RunTimeoutError(options.runTimeoutMs ?? 0), transcript);
3072
3111
  }
@@ -3076,6 +3115,12 @@ async function agentLoop(options) {
3076
3115
  if (options.tokenBudget !== void 0) {
3077
3116
  const used = ctx.getTokensUsed();
3078
3117
  if (used.input + used.output >= options.tokenBudget) {
3118
+ await options.inspect?.appendEvent({
3119
+ type: "budget_hit",
3120
+ totalTokens: used.input + used.output,
3121
+ budget: options.tokenBudget,
3122
+ ts: Date.now()
3123
+ });
3079
3124
  return {
3080
3125
  status: "done",
3081
3126
  output: lastAssistantText || "[Token budget exhausted]",
@@ -3120,6 +3165,19 @@ async function agentLoop(options) {
3120
3165
  });
3121
3166
  if (compactResult.compacted) {
3122
3167
  messages = compactResult.messages;
3168
+ const used = ctx.getTokensUsed();
3169
+ const total = used.input + used.output;
3170
+ const ratio = total / contextLimit;
3171
+ const dropped = compactResult.result?.dropped ?? 0;
3172
+ await options.inspect?.appendEvent({
3173
+ type: "compaction_threshold",
3174
+ turn: ctx.getTurnCount(),
3175
+ ratio,
3176
+ threshold: compactionConfig.threshold,
3177
+ strategy: compactResult.result?.strategy ?? compactionConfig.strategy,
3178
+ dropped,
3179
+ ts: Date.now()
3180
+ });
3123
3181
  }
3124
3182
  await fireProgress("streaming");
3125
3183
  const textBlocks = [];
@@ -3132,13 +3190,21 @@ async function agentLoop(options) {
3132
3190
  turnCount: ctx.getTurnCount(),
3133
3191
  maxTurns: ctx.getMaxTurns()
3134
3192
  })) {
3193
+ const instruction = lastTurnInstruction(options.outputFormat);
3135
3194
  messagesForApi = [
3136
3195
  ...messages,
3137
3196
  {
3138
3197
  role: "user",
3139
- content: [{ type: "text", text: lastTurnInstruction(options.outputFormat) }]
3198
+ content: [{ type: "text", text: instruction }]
3140
3199
  }
3141
3200
  ];
3201
+ await options.inspect?.appendEvent({
3202
+ type: "last_turn_guard",
3203
+ turn: ctx.getTurnCount(),
3204
+ outputFormat: options.outputFormat ?? "text",
3205
+ instruction,
3206
+ ts: Date.now()
3207
+ });
3142
3208
  }
3143
3209
  const normalizedMessages = normalizeMessages(
3144
3210
  messagesForApi
@@ -3173,6 +3239,13 @@ async function agentLoop(options) {
3173
3239
  system
3174
3240
  });
3175
3241
  if (emergency.compacted) {
3242
+ await options.inspect?.appendEvent({
3243
+ type: "compaction_413",
3244
+ turn: ctx.getTurnCount(),
3245
+ droppedMessages: messages.length - emergency.messages.length,
3246
+ strategy: compactionConfig.strategy,
3247
+ ts: Date.now()
3248
+ });
3176
3249
  ctx.rehydrate({
3177
3250
  messages: emergency.messages,
3178
3251
  turnCount: ctx.getTurnCount(),
@@ -3200,6 +3273,14 @@ async function agentLoop(options) {
3200
3273
  }
3201
3274
  const retryAfter = err instanceof RateLimitError && err.retryAfterMs ? err.retryAfterMs : BASE_BACKOFF_MS * Math.pow(2, apiRetryCount - 1);
3202
3275
  const delay = Math.min(retryAfter, 3e4);
3276
+ await options.inspect?.appendEvent({
3277
+ type: "api_retry",
3278
+ turn: ctx.getTurnCount(),
3279
+ status: err instanceof Error && "status" in err ? err.status ?? null : null,
3280
+ attempt: apiRetryCount,
3281
+ delayMs: delay,
3282
+ ts: Date.now()
3283
+ });
3203
3284
  await new Promise((r) => setTimeout(r, delay));
3204
3285
  continue;
3205
3286
  }
@@ -3282,16 +3363,31 @@ async function agentLoop(options) {
3282
3363
  const firstTool = toolCallsToDispatch[0]?.name;
3283
3364
  await fireProgress("tool_dispatch", firstTool);
3284
3365
  const streamExec = new StreamingToolExecutor(executor);
3366
+ let anyConcurrent = false;
3285
3367
  for (const call of toolCallsToDispatch) {
3286
3368
  const tool = options.registry?.get(call.name);
3287
3369
  const safe = tool?.isConcurrencySafe?.(call.input) ?? false;
3370
+ if (safe) anyConcurrent = true;
3288
3371
  streamExec.addTool(call.id, call.name, call.input, safe);
3289
3372
  }
3373
+ await options.inspect?.appendEvent({
3374
+ type: "tool_batch",
3375
+ turn: ctx.getTurnCount(),
3376
+ toolNames: toolCallsToDispatch.map((c) => c.name),
3377
+ concurrent: anyConcurrent && toolCallsToDispatch.length > 1,
3378
+ ts: Date.now()
3379
+ });
3290
3380
  try {
3291
3381
  for await (const { id, result } of streamExec.results()) {
3292
3382
  const missing = result.metadata?.capabilityMissing;
3293
3383
  if (typeof missing === "string") {
3294
3384
  ctx.recordCapabilityMissing(missing);
3385
+ await options.inspect?.appendEvent({
3386
+ type: "capability_stub",
3387
+ turn: ctx.getTurnCount(),
3388
+ toolName: missing,
3389
+ ts: Date.now()
3390
+ });
3295
3391
  }
3296
3392
  const call = toolCallsToDispatch.find((c) => c.id === id);
3297
3393
  let contentForTranscript;
@@ -3351,6 +3447,15 @@ async function agentLoop(options) {
3351
3447
  apiRetryCount = 0;
3352
3448
  consecutive529 = 0;
3353
3449
  await ctx.endTurn();
3450
+ await emitInspectTurn({
3451
+ inspect: options.inspect,
3452
+ turn: ctx.getTurnCount() - 1,
3453
+ startedAt: turnStartedAt,
3454
+ startTokens: turnStartTokens,
3455
+ endTokens: ctx.getTokensUsed(),
3456
+ stopReason,
3457
+ toolCallsCount: toolCallsToDispatch.length
3458
+ });
3354
3459
  await fireProgress("idle");
3355
3460
  if (options.postTurnHooks && options.postTurnHooks.length > 0) {
3356
3461
  await dispatchHooks(options.postTurnHooks, {
@@ -3382,6 +3487,15 @@ async function agentLoop(options) {
3382
3487
  continue;
3383
3488
  }
3384
3489
  await ctx.endTurn();
3490
+ await emitInspectTurn({
3491
+ inspect: options.inspect,
3492
+ turn: ctx.getTurnCount() - 1,
3493
+ startedAt: turnStartedAt,
3494
+ startTokens: turnStartTokens,
3495
+ endTokens: ctx.getTokensUsed(),
3496
+ stopReason,
3497
+ toolCallsCount: 0
3498
+ });
3385
3499
  if (options.postTurnHooks && options.postTurnHooks.length > 0) {
3386
3500
  await dispatchHooks(options.postTurnHooks, {
3387
3501
  runId: ctx.runId,
@@ -3408,11 +3522,25 @@ async function agentLoop(options) {
3408
3522
  if (!maxTokensEscalated) {
3409
3523
  maxTokensEscalated = true;
3410
3524
  escalatedMaxTokens = ESCALATED_MAX_TOKENS;
3525
+ await options.inspect?.appendEvent({
3526
+ type: "recovery_max_tokens",
3527
+ turn: ctx.getTurnCount(),
3528
+ attempt: 0,
3529
+ escalatedTo: ESCALATED_MAX_TOKENS,
3530
+ ts: Date.now()
3531
+ });
3411
3532
  continue;
3412
3533
  }
3413
3534
  if (recoveryCount < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) {
3414
3535
  recoveryCount++;
3415
3536
  escalatedMaxTokens = void 0;
3537
+ await options.inspect?.appendEvent({
3538
+ type: "recovery_max_tokens",
3539
+ turn: ctx.getTurnCount(),
3540
+ attempt: recoveryCount,
3541
+ escalatedTo: 0,
3542
+ ts: Date.now()
3543
+ });
3416
3544
  await ctx.addUserMessage(
3417
3545
  "Output token limit hit. Resume directly \u2014 no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces."
3418
3546
  );
@@ -4408,25 +4536,45 @@ If omitted, defaults to "${defaultAgent.name}".`;
4408
4536
  const lastAssistant = [...parentMsgs].reverse().find((m) => m.role === "assistant");
4409
4537
  const assistantContent = lastAssistant ? Array.isArray(lastAssistant.content) ? lastAssistant.content : [] : [];
4410
4538
  const forkedMsgs = buildForkedMessages(taskDescription, assistantContent);
4411
- const result2 = await runAgent({
4412
- agentId: spawn2.agentId,
4413
- description: taskDescription,
4414
- storage: options.storage,
4415
- client: options.client,
4416
- registry: options.childRegistry,
4417
- parentLogPath: options.parentLogPath,
4418
- system: options.system,
4419
- // inherit parent's system prompt
4420
- maxTurns: options.maxTurns,
4421
- contextLimit: options.contextLimit,
4422
- turnTimeoutMs: options.turnTimeoutMs,
4423
- flushPolicy: options.flushPolicy,
4424
- idleFlushMs: options.idleFlushMs,
4425
- // Fork: prepend parent messages + forked messages
4426
- prependMessages: [...parentMsgs, ...forkedMsgs],
4427
- ...options.gateBeforeTool !== void 0 ? { gateBeforeTool: options.gateBeforeTool } : {},
4428
- ...options.toolResultOffload !== void 0 ? { toolResultOffload: options.toolResultOffload } : {}
4429
- });
4539
+ let result2;
4540
+ try {
4541
+ result2 = await runAgent({
4542
+ agentId: spawn2.agentId,
4543
+ description: taskDescription,
4544
+ storage: options.storage,
4545
+ client: options.client,
4546
+ registry: options.childRegistry,
4547
+ parentLogPath: options.parentLogPath,
4548
+ system: options.system,
4549
+ // inherit parent's system prompt
4550
+ maxTurns: options.maxTurns,
4551
+ contextLimit: options.contextLimit,
4552
+ turnTimeoutMs: options.turnTimeoutMs,
4553
+ flushPolicy: options.flushPolicy,
4554
+ idleFlushMs: options.idleFlushMs,
4555
+ // Fork: prepend parent messages + forked messages
4556
+ prependMessages: [...parentMsgs, ...forkedMsgs],
4557
+ ...options.gateBeforeTool !== void 0 ? { gateBeforeTool: options.gateBeforeTool } : {},
4558
+ ...options.toolResultOffload !== void 0 ? { toolResultOffload: options.toolResultOffload } : {}
4559
+ });
4560
+ await options.inspect?.appendEvent({
4561
+ type: "subagent_spawn_attempt",
4562
+ agentId: spawn2.agentId,
4563
+ subagentType: "fork",
4564
+ success: true,
4565
+ ts: Date.now()
4566
+ });
4567
+ } catch (err) {
4568
+ await options.inspect?.appendEvent({
4569
+ type: "subagent_spawn_attempt",
4570
+ agentId: spawn2.agentId,
4571
+ subagentType: "fork",
4572
+ success: false,
4573
+ error: err instanceof Error ? err.message : String(err),
4574
+ ts: Date.now()
4575
+ });
4576
+ throw err;
4577
+ }
4430
4578
  handlePausedResult(result2, spawn2.agentId, "fork");
4431
4579
  const t2 = result2;
4432
4580
  options.subagentRegistry.setStatus(spawn2.agentId, t2.isError ? "stopped" : "completed");
@@ -4480,7 +4628,7 @@ If omitted, defaults to "${defaultAgent.name}".`;
4480
4628
  const shouldRunAsync = run_in_background === true || options.coordinatorMode === true;
4481
4629
  if (shouldRunAsync) {
4482
4630
  options.subagentRegistry.setBackground(spawn.agentId);
4483
- const bgPromise = runAgent(runOpts).then((result2) => {
4631
+ const bgPromise = runAgent(runOpts).then(async (result2) => {
4484
4632
  if (result2.kind !== "terminal") {
4485
4633
  options.subagentRegistry.setStatus(spawn.agentId, "stopped");
4486
4634
  options.subagentRegistry.setBackgroundResult(spawn.agentId, {
@@ -4489,6 +4637,14 @@ If omitted, defaults to "${defaultAgent.name}".`;
4489
4637
  isError: true,
4490
4638
  description: taskDescription
4491
4639
  });
4640
+ await options.inspect?.appendEvent({
4641
+ type: "subagent_spawn_attempt",
4642
+ agentId: spawn.agentId,
4643
+ subagentType: typeName ?? "unknown",
4644
+ success: false,
4645
+ error: "background subagent paused (pause not supported for async)",
4646
+ ts: Date.now()
4647
+ });
4492
4648
  return;
4493
4649
  }
4494
4650
  options.subagentRegistry.setStatus(
@@ -4501,7 +4657,15 @@ If omitted, defaults to "${defaultAgent.name}".`;
4501
4657
  isError: result2.isError,
4502
4658
  description: taskDescription
4503
4659
  });
4504
- }).catch((err) => {
4660
+ await options.inspect?.appendEvent({
4661
+ type: "subagent_spawn_attempt",
4662
+ agentId: spawn.agentId,
4663
+ subagentType: typeName ?? "unknown",
4664
+ success: !result2.isError,
4665
+ ...result2.isError ? { error: "child returned isError" } : {},
4666
+ ts: Date.now()
4667
+ });
4668
+ }).catch(async (err) => {
4505
4669
  options.subagentRegistry.setStatus(spawn.agentId, "stopped");
4506
4670
  options.subagentRegistry.setBackgroundResult(spawn.agentId, {
4507
4671
  agentId: spawn.agentId,
@@ -4509,6 +4673,14 @@ If omitted, defaults to "${defaultAgent.name}".`;
4509
4673
  isError: true,
4510
4674
  description: taskDescription
4511
4675
  });
4676
+ await options.inspect?.appendEvent({
4677
+ type: "subagent_spawn_attempt",
4678
+ agentId: spawn.agentId,
4679
+ subagentType: typeName ?? "unknown",
4680
+ success: false,
4681
+ error: err instanceof Error ? err.message : String(err),
4682
+ ts: Date.now()
4683
+ });
4512
4684
  });
4513
4685
  options.subagentRegistry.setBackgroundPromise(spawn.agentId, bgPromise);
4514
4686
  return {
@@ -4524,7 +4696,27 @@ Use SendMessage with to: '${spawn.agentId}' to communicate with this agent.`,
4524
4696
  }
4525
4697
  };
4526
4698
  }
4527
- const result = await runAgent(runOpts);
4699
+ let result;
4700
+ try {
4701
+ result = await runAgent(runOpts);
4702
+ await options.inspect?.appendEvent({
4703
+ type: "subagent_spawn_attempt",
4704
+ agentId: spawn.agentId,
4705
+ subagentType: typeName ?? "unknown",
4706
+ success: true,
4707
+ ts: Date.now()
4708
+ });
4709
+ } catch (err) {
4710
+ await options.inspect?.appendEvent({
4711
+ type: "subagent_spawn_attempt",
4712
+ agentId: spawn.agentId,
4713
+ subagentType: typeName ?? "unknown",
4714
+ success: false,
4715
+ error: err instanceof Error ? err.message : String(err),
4716
+ ts: Date.now()
4717
+ });
4718
+ throw err;
4719
+ }
4528
4720
  handlePausedResult(result, spawn.agentId, typeName);
4529
4721
  const t = result;
4530
4722
  options.subagentRegistry.setStatus(spawn.agentId, t.isError ? "stopped" : "completed");
@@ -4727,6 +4919,7 @@ function createSendMessageTool(options) {
4727
4919
 
4728
4920
  // src/engine/engine.ts
4729
4921
  init_contract();
4922
+ var import_zod_to_json_schema4 = require("zod-to-json-schema");
4730
4923
 
4731
4924
  // src/tools/fileEdit.ts
4732
4925
  init_cjs_shims();
@@ -6858,7 +7051,7 @@ init_contract();
6858
7051
  function mcpToolName(serverName, toolName) {
6859
7052
  return `mcp__${serverName}__${toolName}`;
6860
7053
  }
6861
- function adaptMcpTool(client, serverName, def) {
7054
+ function adaptMcpTool(client, serverName, def, opts) {
6862
7055
  const registeredName = mcpToolName(serverName, def.name);
6863
7056
  return defineTool({
6864
7057
  name: registeredName,
@@ -6868,6 +7061,9 @@ function adaptMcpTool(client, serverName, def) {
6868
7061
  // bypassing Zod-to-JSON-Schema conversion (which would produce `{}`
6869
7062
  // for our `z.unknown()` Zod schema).
6870
7063
  anthropicSchemaOverride: def.inputSchema,
7064
+ // Plan 027 — stdio servers spawn subprocesses; flag so the engine's
7065
+ // capability-stub wrapper can swap them on Workers.
7066
+ ...opts.transport === "stdio" ? { requiresNode: true } : {},
6871
7067
  execute: async (input) => {
6872
7068
  try {
6873
7069
  const result = await client.callTool(def.name, input);
@@ -6911,6 +7107,8 @@ var McpManager = class {
6911
7107
  this.logger = logger;
6912
7108
  this.servers = Object.entries(config.servers).map(([name, serverConfig]) => ({
6913
7109
  name,
7110
+ // Plan 027 — capture transport for the stdio-handoff wiring.
7111
+ transport: serverConfig.type,
6914
7112
  client: new McpClient({
6915
7113
  serverName: name,
6916
7114
  config: serverConfig,
@@ -7050,7 +7248,9 @@ var McpManager = class {
7050
7248
  try {
7051
7249
  await server.client.connect();
7052
7250
  const defs = server.client.listTools();
7053
- server.tools = defs.map((def) => adaptMcpTool(server.client, server.name, def));
7251
+ server.tools = defs.map(
7252
+ (def) => adaptMcpTool(server.client, server.name, def, { transport: server.transport })
7253
+ );
7054
7254
  server.state = "connected";
7055
7255
  this.pendingConnected.push(server.name);
7056
7256
  } catch (err) {
@@ -9721,6 +9921,198 @@ function applyRunToolFilter(registry, options) {
9721
9921
  }
9722
9922
  }
9723
9923
 
9924
+ // src/inspect/writer.ts
9925
+ init_cjs_shims();
9926
+ var InspectWriter = class {
9927
+ storage;
9928
+ logger;
9929
+ enabled;
9930
+ /** Subdirectory holding all inspect files. Constant per writer. */
9931
+ dir;
9932
+ constructor(opts) {
9933
+ this.storage = opts.storage;
9934
+ if (opts.logger !== void 0) this.logger = opts.logger;
9935
+ this.enabled = opts.enabled;
9936
+ this.dir = `${opts.logPath}/inspect`;
9937
+ }
9938
+ /**
9939
+ * Write the eight start-snapshot files atomically (well, sequentially —
9940
+ * R2 has no multi-object atomic; failures partially-write and the
9941
+ * fail-soft path logs each one).
9942
+ */
9943
+ async writeStartSnapshot(input) {
9944
+ if (!this.enabled) return;
9945
+ await this.safeWrite("system-prompt.txt", input.systemPrompt);
9946
+ await this.safeWriteJson("tools.json", input.tools);
9947
+ await this.safeWriteJson("run-options.json", input.runOptions);
9948
+ await this.safeWriteJson("model-config.json", input.modelConfig);
9949
+ if (input.skills !== void 0) {
9950
+ await this.safeWriteJson("skills.json", input.skills);
9951
+ }
9952
+ if (input.mcpServers !== void 0) {
9953
+ await this.safeWriteJson("mcp-servers.json", input.mcpServers);
9954
+ }
9955
+ if (input.apiServices !== void 0) {
9956
+ await this.safeWriteJson("api-services.json", input.apiServices);
9957
+ }
9958
+ if (input.knowledge !== void 0) {
9959
+ await this.safeWriteJson("knowledge-scope.json", input.knowledge);
9960
+ }
9961
+ }
9962
+ /** Append one event line to events.jsonl. */
9963
+ async appendEvent(event) {
9964
+ if (!this.enabled) return;
9965
+ await this.safeAppend("events.jsonl", JSON.stringify(event) + "\n");
9966
+ }
9967
+ /** Append one row to turns.jsonl. */
9968
+ async appendTurn(row) {
9969
+ if (!this.enabled) return;
9970
+ await this.safeAppend("turns.jsonl", JSON.stringify(row) + "\n");
9971
+ }
9972
+ /** Write the terminal `tools-summary.json` once at run end. */
9973
+ async writeToolsSummary(summary) {
9974
+ if (!this.enabled) return;
9975
+ await this.safeWriteJson("tools-summary.json", summary);
9976
+ }
9977
+ // ---------- private fail-soft helpers ----------
9978
+ async safeWrite(name, content) {
9979
+ try {
9980
+ await this.storage.writeFile(`${this.dir}/${name}`, content);
9981
+ } catch (err) {
9982
+ this.logFailure("writeFile", name, err);
9983
+ }
9984
+ }
9985
+ async safeWriteJson(name, value) {
9986
+ try {
9987
+ await this.storage.writeFile(`${this.dir}/${name}`, JSON.stringify(value, null, 2));
9988
+ } catch (err) {
9989
+ this.logFailure("writeFile (json)", name, err);
9990
+ }
9991
+ }
9992
+ async safeAppend(name, line) {
9993
+ try {
9994
+ await this.storage.appendFile(`${this.dir}/${name}`, line);
9995
+ } catch (err) {
9996
+ this.logFailure("appendFile", name, err);
9997
+ }
9998
+ }
9999
+ logFailure(op, name, err) {
10000
+ if (this.logger === void 0) return;
10001
+ const msg = err instanceof Error ? err.message : String(err);
10002
+ this.logger.warn?.("inspect-writer failed", {
10003
+ op,
10004
+ file: `${this.dir}/${name}`,
10005
+ error: msg
10006
+ });
10007
+ }
10008
+ };
10009
+ var NULL_INSPECT_WRITER = {
10010
+ writeStartSnapshot: async () => void 0,
10011
+ appendEvent: async () => void 0,
10012
+ appendTurn: async () => void 0,
10013
+ writeToolsSummary: async () => void 0
10014
+ };
10015
+
10016
+ // src/inspect/scrub.ts
10017
+ init_cjs_shims();
10018
+ var import_zod_to_json_schema3 = require("zod-to-json-schema");
10019
+ function scrubModelConfig(cfg, opts = { redactBaseURL: false }) {
10020
+ const out = {
10021
+ schemaVersion: 1,
10022
+ provider: cfg.provider,
10023
+ modelId: cfg.modelId
10024
+ };
10025
+ if (!opts.redactBaseURL && cfg.baseURL !== void 0) out.baseURL = cfg.baseURL;
10026
+ if (cfg.temperature !== void 0) out.temperature = cfg.temperature;
10027
+ if (cfg.maxTokens !== void 0) out.maxTokens = cfg.maxTokens;
10028
+ if (cfg.maxRetries !== void 0) out.maxRetries = cfg.maxRetries;
10029
+ return out;
10030
+ }
10031
+ function scrubRunOptions(opts) {
10032
+ const out = {
10033
+ schemaVersion: 1,
10034
+ runId: opts.runId ?? "",
10035
+ nodeId: opts.nodeId,
10036
+ task: opts.task
10037
+ };
10038
+ if (opts.maxTurns !== void 0) out.maxTurns = opts.maxTurns;
10039
+ if (opts.outputFormat !== void 0) out.outputFormat = opts.outputFormat;
10040
+ if (opts.outputSchema !== void 0) out.outputSchema = serializeOutputSchema(opts.outputSchema);
10041
+ if (opts.tools !== void 0) out.tools = [...opts.tools];
10042
+ if (opts.toolChoice !== void 0) out.toolChoice = opts.toolChoice;
10043
+ if (opts.tokenBudget !== void 0) out.tokenBudget = opts.tokenBudget;
10044
+ if (opts.knowledge !== void 0) {
10045
+ const k = {};
10046
+ if (opts.knowledge.folders !== void 0) k.folders = [...opts.knowledge.folders];
10047
+ if (opts.knowledge.external !== void 0) {
10048
+ k.external = opts.knowledge.external.map((e) => {
10049
+ const link = { url: e.url };
10050
+ if (e.description !== void 0) link.description = e.description;
10051
+ return link;
10052
+ });
10053
+ }
10054
+ out.knowledge = k;
10055
+ }
10056
+ if (opts.api !== void 0 && opts.api.services !== void 0) {
10057
+ out.api = {
10058
+ services: opts.api.services.map((s) => {
10059
+ const svc = { name: s.name };
10060
+ if (s.description !== void 0) svc.description = s.description;
10061
+ if (s.baseUrl !== void 0) svc.baseUrl = s.baseUrl;
10062
+ return svc;
10063
+ })
10064
+ };
10065
+ }
10066
+ if (opts.skills !== void 0) {
10067
+ out.skills = opts.skills.map((s) => ({
10068
+ name: s.name,
10069
+ description: s.description ?? "",
10070
+ hasBody: "body" in s && typeof s.body === "string",
10071
+ hasUrl: "url" in s && typeof s.url === "string"
10072
+ }));
10073
+ }
10074
+ if (opts.compaction !== void 0) out.compaction = opts.compaction;
10075
+ if (opts.context !== void 0) out.context = { ...opts.context };
10076
+ return out;
10077
+ }
10078
+ function serializeOutputSchema(schema) {
10079
+ if (schema === null || typeof schema !== "object") return schema;
10080
+ const candidate = schema;
10081
+ if (candidate._def !== void 0 && typeof candidate.safeParse === "function") {
10082
+ try {
10083
+ return (0, import_zod_to_json_schema3.zodToJsonSchema)(schema, {
10084
+ target: "jsonSchema7",
10085
+ $refStrategy: "none"
10086
+ });
10087
+ } catch {
10088
+ return { error: "failed to serialize Zod schema for inspect" };
10089
+ }
10090
+ }
10091
+ return schema;
10092
+ }
10093
+ function scrubSkills(skills) {
10094
+ return {
10095
+ schemaVersion: 1,
10096
+ skills: skills.map((s) => ({
10097
+ name: s.name,
10098
+ description: s.description ?? "",
10099
+ hasPages: s.hasPages ?? false
10100
+ }))
10101
+ };
10102
+ }
10103
+ function scrubApiServices(services) {
10104
+ return {
10105
+ schemaVersion: 1,
10106
+ services: services.map((s) => {
10107
+ const out = { name: s.name };
10108
+ if (s.description !== void 0) out.description = s.description;
10109
+ if (s.baseUrl !== void 0) out.baseUrl = s.baseUrl;
10110
+ if (s.methods !== void 0) out.methods = s.methods.map((m) => m.name);
10111
+ return out;
10112
+ })
10113
+ };
10114
+ }
10115
+
9724
10116
  // src/engine/rehydrate.ts
9725
10117
  init_cjs_shims();
9726
10118
  function rebuildMessagesFromEntries(entries) {
@@ -10179,6 +10571,7 @@ var Engine = class {
10179
10571
  systemPrompt += "\n\n" + buildSchemaPrompt(options.outputSchema);
10180
10572
  }
10181
10573
  const gate = this.resolveGate();
10574
+ const inspect = this.buildInspectWriter(storage.workspace, logPath);
10182
10575
  const registry = buildToolRegistry({
10183
10576
  config: this.config,
10184
10577
  storage,
@@ -10190,6 +10583,7 @@ var Engine = class {
10190
10583
  agents,
10191
10584
  mcpTools,
10192
10585
  memory,
10586
+ inspect,
10193
10587
  ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
10194
10588
  ...skillSource !== void 0 ? { skillSource } : {},
10195
10589
  ...apiConfig !== void 0 ? { apiConfig } : {},
@@ -10198,6 +10592,36 @@ var Engine = class {
10198
10592
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
10199
10593
  });
10200
10594
  applyRunToolFilter(registry, options);
10595
+ await inspect.writeStartSnapshot({
10596
+ systemPrompt,
10597
+ tools: this.snapshotTools(registry, mcpTools),
10598
+ runOptions: scrubRunOptions({ ...options, runId }),
10599
+ modelConfig: scrubModelConfig(
10600
+ {
10601
+ provider: this.config.model.provider,
10602
+ modelId: this.config.model.modelId,
10603
+ ...this.config.model.baseURL !== void 0 ? { baseURL: this.config.model.baseURL } : {},
10604
+ ...this.config.model.temperature !== void 0 ? { temperature: this.config.model.temperature } : {},
10605
+ ...this.config.model.maxTokens !== void 0 ? { maxTokens: this.config.model.maxTokens } : {},
10606
+ ...this.config.model.maxRetries !== void 0 ? { maxRetries: this.config.model.maxRetries } : {}
10607
+ },
10608
+ { redactBaseURL: this.config.inspect.redactBaseURL }
10609
+ ),
10610
+ ...skillList !== void 0 ? { skills: scrubSkills(skillList) } : {},
10611
+ ...mcpTools.length > 0 ? { mcpServers: this.snapshotMcpServers(mcpTools) } : {},
10612
+ ...apiConfig !== void 0 && apiConfig.services.length > 0 ? { apiServices: scrubApiServices(apiConfig.services) } : {},
10613
+ ...knowledgeRuntime !== void 0 ? {
10614
+ knowledge: {
10615
+ schemaVersion: 1,
10616
+ folders: [...knowledgeRuntime.folders],
10617
+ external: knowledgeRuntime.external.map((e) => {
10618
+ const link = { url: e.url };
10619
+ if (e.description !== void 0) link.description = e.description;
10620
+ return link;
10621
+ })
10622
+ }
10623
+ } : {}
10624
+ });
10201
10625
  const writer = new TranscriptWriter({
10202
10626
  storage: storage.workspace,
10203
10627
  logPath,
@@ -10259,14 +10683,24 @@ var Engine = class {
10259
10683
  // is already handled above by stripping the tool list, so the
10260
10684
  // loop only ever sees `'auto'` or `'required'`.
10261
10685
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
10262
- ...options.toolChoice === "required" ? { toolChoice: "required" } : {}
10263
- });
10264
- const result = await this.finalizeResult(loopResult, writer, logPath, {
10265
- ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
10266
- ...options.outputSchema !== void 0 ? { outputSchema: options.outputSchema } : {}
10686
+ ...options.toolChoice === "required" ? { toolChoice: "required" } : {},
10687
+ // Plan 026 — pass the inspect writer so the loop can append
10688
+ // events / per-turn rows. Disabled-mode writer is a no-op.
10689
+ inspect
10267
10690
  });
10691
+ const result = await this.finalizeResult(
10692
+ loopResult,
10693
+ writer,
10694
+ logPath,
10695
+ {
10696
+ ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
10697
+ ...options.outputSchema !== void 0 ? { outputSchema: options.outputSchema } : {}
10698
+ },
10699
+ inspect
10700
+ );
10268
10701
  this.logRunEnd(log, runId, options.nodeId, result);
10269
10702
  await this.firePostRunHook(runId, options.nodeId, result, ctx, logPath);
10703
+ await this.writeInspectToolsSummary(inspect, storage.workspace, logPath);
10270
10704
  const capabilitiesMissing = ctx.getCapabilitiesMissing();
10271
10705
  return toResponse(result, {
10272
10706
  runId,
@@ -10334,6 +10768,7 @@ var Engine = class {
10334
10768
  systemPrompt += "\n\n" + buildSchemaPrompt(options.outputSchema);
10335
10769
  }
10336
10770
  const gate = this.resolveGate();
10771
+ const inspect = this.buildInspectWriter(storage.workspace, logPath);
10337
10772
  const registry = buildToolRegistry({
10338
10773
  config: this.config,
10339
10774
  storage,
@@ -10345,6 +10780,7 @@ var Engine = class {
10345
10780
  agents,
10346
10781
  mcpTools,
10347
10782
  memory,
10783
+ inspect,
10348
10784
  ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
10349
10785
  ...skillSource !== void 0 ? { skillSource } : {},
10350
10786
  ...apiConfig !== void 0 ? { apiConfig } : {},
@@ -10352,6 +10788,42 @@ var Engine = class {
10352
10788
  ...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
10353
10789
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
10354
10790
  });
10791
+ await inspect.writeStartSnapshot({
10792
+ systemPrompt,
10793
+ tools: this.snapshotTools(registry, mcpTools),
10794
+ runOptions: scrubRunOptions({
10795
+ runId: snapshot.runId,
10796
+ nodeId: snapshot.nodeId,
10797
+ task: "[resumed run \u2014 original task in transcript]",
10798
+ ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
10799
+ ...options.outputSchema !== void 0 ? { outputSchema: options.outputSchema } : {}
10800
+ }),
10801
+ modelConfig: scrubModelConfig(
10802
+ {
10803
+ provider: this.config.model.provider,
10804
+ modelId: this.config.model.modelId,
10805
+ ...this.config.model.baseURL !== void 0 ? { baseURL: this.config.model.baseURL } : {},
10806
+ ...this.config.model.temperature !== void 0 ? { temperature: this.config.model.temperature } : {},
10807
+ ...this.config.model.maxTokens !== void 0 ? { maxTokens: this.config.model.maxTokens } : {},
10808
+ ...this.config.model.maxRetries !== void 0 ? { maxRetries: this.config.model.maxRetries } : {}
10809
+ },
10810
+ { redactBaseURL: this.config.inspect.redactBaseURL }
10811
+ ),
10812
+ ...skillList !== void 0 ? { skills: scrubSkills(skillList) } : {},
10813
+ ...mcpTools.length > 0 ? { mcpServers: this.snapshotMcpServers(mcpTools) } : {},
10814
+ ...apiConfig !== void 0 && apiConfig.services.length > 0 ? { apiServices: scrubApiServices(apiConfig.services) } : {},
10815
+ ...knowledgeRuntime !== void 0 ? {
10816
+ knowledge: {
10817
+ schemaVersion: 1,
10818
+ folders: [...knowledgeRuntime.folders],
10819
+ external: knowledgeRuntime.external.map((e) => {
10820
+ const link = { url: e.url };
10821
+ if (e.description !== void 0) link.description = e.description;
10822
+ return link;
10823
+ })
10824
+ }
10825
+ } : {}
10826
+ });
10355
10827
  const priorState = await loadWriterState(storage.workspace, logPath);
10356
10828
  const writer = new TranscriptWriter({
10357
10829
  storage: storage.workspace,
@@ -10441,14 +10913,24 @@ ${inputJson}
10441
10913
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
10442
10914
  ...gate !== void 0 ? { gateBeforeTool: gate } : {},
10443
10915
  ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {},
10444
- ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {}
10445
- });
10446
- const result = await this.finalizeResult(loopResult, writer, logPath, {
10447
- ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
10448
- ...options.outputSchema !== void 0 ? { outputSchema: options.outputSchema } : {}
10916
+ ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {},
10917
+ // Plan 026 — append resume's events / per-turn rows to the
10918
+ // existing inspect/ logs.
10919
+ inspect
10449
10920
  });
10921
+ const result = await this.finalizeResult(
10922
+ loopResult,
10923
+ writer,
10924
+ logPath,
10925
+ {
10926
+ ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
10927
+ ...options.outputSchema !== void 0 ? { outputSchema: options.outputSchema } : {}
10928
+ },
10929
+ inspect
10930
+ );
10450
10931
  this.logRunEnd(log, snapshot.runId, snapshot.nodeId, result);
10451
10932
  await this.firePostRunHook(snapshot.runId, snapshot.nodeId, result, ctx, logPath);
10933
+ await this.writeInspectToolsSummary(inspect, storage.workspace, logPath);
10452
10934
  const capabilitiesMissing = ctx.getCapabilitiesMissing();
10453
10935
  return toResponse(result, {
10454
10936
  runId: snapshot.runId,
@@ -11096,7 +11578,7 @@ ${inputJson}
11096
11578
  };
11097
11579
  await dispatchHooks(this.config.hooks.postRun, event);
11098
11580
  }
11099
- async finalizeResult(loopResult, writer, _logPath, jsonOptions) {
11581
+ async finalizeResult(loopResult, writer, _logPath, jsonOptions, inspect) {
11100
11582
  if (loopResult.status === "done") {
11101
11583
  await writer.setStatus("done");
11102
11584
  let data;
@@ -11105,10 +11587,27 @@ ${inputJson}
11105
11587
  if (parsed.ok) {
11106
11588
  if (jsonOptions.outputSchema) {
11107
11589
  const validated = validateOutput(parsed.value, jsonOptions.outputSchema);
11108
- data = validated.ok ? validated.data : void 0;
11590
+ if (validated.ok) {
11591
+ data = validated.data;
11592
+ } else {
11593
+ await inspect?.appendEvent({
11594
+ type: "json_parse_failure",
11595
+ turn: loopResult.turns,
11596
+ validationError: validated.error,
11597
+ ts: Date.now()
11598
+ });
11599
+ data = void 0;
11600
+ }
11109
11601
  } else {
11110
11602
  data = parsed.value;
11111
11603
  }
11604
+ } else {
11605
+ await inspect?.appendEvent({
11606
+ type: "json_parse_failure",
11607
+ turn: loopResult.turns,
11608
+ parseError: parsed.error ?? "unknown parse error",
11609
+ ts: Date.now()
11610
+ });
11112
11611
  }
11113
11612
  }
11114
11613
  return {
@@ -11292,7 +11791,147 @@ ${inputJson}
11292
11791
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
11293
11792
  });
11294
11793
  }
11794
+ // Plan 026 — inspect-bundle helpers ----------------------------
11795
+ buildInspectWriter(storage, logPath) {
11796
+ const inspect = this.config.inspect;
11797
+ return new InspectWriter({
11798
+ storage,
11799
+ logPath,
11800
+ enabled: inspect.enabled
11801
+ });
11802
+ }
11803
+ /**
11804
+ * Snapshot the live ToolRegistry into the inspect-bundle shape.
11805
+ * Tool source is inferred by name: MCP tools have the
11806
+ * `mcp__{server}__{tool}` prefix; everything else is core.
11807
+ */
11808
+ snapshotTools(registry, mcpTools) {
11809
+ const mcpNameSet = new Set(mcpTools.map((t) => t.name));
11810
+ return {
11811
+ schemaVersion: 1,
11812
+ tools: registry.list().map((t) => {
11813
+ const out = {
11814
+ name: t.name,
11815
+ description: t.description ?? "",
11816
+ inputSchema: this.toolInputSchemaToJson(t),
11817
+ source: mcpNameSet.has(t.name) ? "mcp" : "core",
11818
+ ...t.isCapabilityStub === true ? { isCapabilityStub: true } : {}
11819
+ };
11820
+ return out;
11821
+ })
11822
+ };
11823
+ }
11824
+ /** Convert a Zod input schema to JSON Schema for persistence. */
11825
+ toolInputSchemaToJson(t) {
11826
+ const raw = t.inputSchema;
11827
+ if (raw === void 0 || raw === null) return {};
11828
+ const candidate = raw;
11829
+ if (typeof candidate === "object" && candidate._def !== void 0 && typeof candidate.safeParse === "function") {
11830
+ try {
11831
+ const json2 = (0, import_zod_to_json_schema4.zodToJsonSchema)(raw, {
11832
+ target: "jsonSchema7",
11833
+ $refStrategy: "none"
11834
+ });
11835
+ return json2;
11836
+ } catch {
11837
+ return {};
11838
+ }
11839
+ }
11840
+ return raw;
11841
+ }
11842
+ /**
11843
+ * Plan 026 — aggregate `tools-summary.json` from the persisted
11844
+ * transcript shards. Walks meta.json for shard count, reads each
11845
+ * shard, counts every tool_use block by name. Best-effort; the
11846
+ * InspectWriter swallows write failures.
11847
+ */
11848
+ async writeInspectToolsSummary(inspect, storage, logPath) {
11849
+ const counts = /* @__PURE__ */ new Map();
11850
+ let total = 0;
11851
+ try {
11852
+ const metaRaw = await storage.readFile(`${logPath}/meta.json`);
11853
+ if (!metaRaw) return;
11854
+ const meta = JSON.parse(metaRaw);
11855
+ const shardCount = meta.shardCount ?? 0;
11856
+ for (let i = 0; i < shardCount; i++) {
11857
+ const name = `${String(i).padStart(6, "0")}.jsonl`;
11858
+ const text2 = await storage.readFile(`${logPath}/${name}`);
11859
+ if (!text2) continue;
11860
+ for (const line of text2.split("\n")) {
11861
+ if (!line) continue;
11862
+ try {
11863
+ const entry = JSON.parse(line);
11864
+ if (entry.type !== "assistant") continue;
11865
+ const content = entry.message?.content;
11866
+ if (!Array.isArray(content)) continue;
11867
+ for (const block of content) {
11868
+ const b = block;
11869
+ if (b.type === "tool_use" && typeof b.name === "string") {
11870
+ counts.set(b.name, (counts.get(b.name) ?? 0) + 1);
11871
+ total++;
11872
+ }
11873
+ }
11874
+ } catch {
11875
+ }
11876
+ }
11877
+ }
11878
+ } catch {
11879
+ return;
11880
+ }
11881
+ await inspect.writeToolsSummary({
11882
+ schemaVersion: 1,
11883
+ tools: [...counts.entries()].map(([name, callCount]) => ({ name, callCount })).sort((a, b) => b.callCount - a.callCount),
11884
+ totalCalls: total
11885
+ });
11886
+ }
11887
+ /**
11888
+ * Snapshot the resolved MCP servers. Right now `mcpManager.getTools()`
11889
+ * returns the flat tool list; we group them by server-name prefix to
11890
+ * recover which server exposed which tool.
11891
+ */
11892
+ snapshotMcpServers(mcpTools) {
11893
+ const byServer = /* @__PURE__ */ new Map();
11894
+ for (const t of mcpTools) {
11895
+ const m = t.name.match(/^mcp__([^_]+)__(.+)$/);
11896
+ const server = m?.[1] ?? "unknown";
11897
+ const list = byServer.get(server) ?? [];
11898
+ list.push(t.name);
11899
+ byServer.set(server, list);
11900
+ }
11901
+ const declaredMap = this.config.mcp.servers;
11902
+ return {
11903
+ schemaVersion: 1,
11904
+ servers: [...byServer.entries()].map(([name, tools]) => {
11905
+ const declared = declaredMap[name];
11906
+ const out = {
11907
+ name,
11908
+ // If the server isn't in the declared map (synthetic / dynamic),
11909
+ // default to 'http' as a best-effort label.
11910
+ type: declared?.type ?? "http",
11911
+ state: "connected",
11912
+ tools
11913
+ };
11914
+ if (declared !== void 0) {
11915
+ if (declared.type === "stdio") {
11916
+ out.command = declared.command;
11917
+ out.args = [...declared.args];
11918
+ } else {
11919
+ out.url = declared.url;
11920
+ if (declared.headers !== void 0) {
11921
+ out.headers = redactHeadersInline(declared.headers);
11922
+ }
11923
+ }
11924
+ }
11925
+ return out;
11926
+ })
11927
+ };
11928
+ }
11295
11929
  };
11930
+ function redactHeadersInline(headers) {
11931
+ const out = {};
11932
+ for (const k of Object.keys(headers)) out[k] = "<redacted>";
11933
+ return out;
11934
+ }
11296
11935
  function buildToolRegistry(options) {
11297
11936
  const {
11298
11937
  config,
@@ -11446,7 +12085,10 @@ function buildToolRegistry(options) {
11446
12085
  agents,
11447
12086
  coordinatorMode: isCoordinatorMode(config),
11448
12087
  ...subagentGate !== void 0 ? { gateBeforeTool: subagentGate } : {},
11449
- ...options.toolResultOffload !== void 0 ? { toolResultOffload: options.toolResultOffload } : {}
12088
+ ...options.toolResultOffload !== void 0 ? { toolResultOffload: options.toolResultOffload } : {},
12089
+ // Plan 026 — Agent tool emits subagent_spawn_attempt to the
12090
+ // parent's events.jsonl on each runAgent call (success and failure).
12091
+ ...options.inspect !== void 0 ? { inspect: options.inspect } : {}
11450
12092
  });
11451
12093
  if (!disabled.has("Agent") && (wantAll || enabled.has("Agent"))) {
11452
12094
  registry.register(agentTool);
@@ -11462,8 +12104,9 @@ function buildToolRegistry(options) {
11462
12104
  for (const tool of mcpTools) {
11463
12105
  if (disabled.has(tool.name)) continue;
11464
12106
  if (wantAll || enabled.has(tool.name)) {
11465
- registry.register(tool);
11466
- childRegistry.register(tool);
12107
+ const wrapped = withCapabilityCheck(tool, spawnAvailable);
12108
+ registry.register(wrapped);
12109
+ childRegistry.register(wrapped);
11467
12110
  }
11468
12111
  }
11469
12112
  if (!disabled.has("ToolSearch") && (wantAll || enabled.has("ToolSearch"))) {
@@ -11483,6 +12126,11 @@ init_retry();
11483
12126
  init_snapshot();
11484
12127
  init_synthesize();
11485
12128
  init_types();
12129
+
12130
+ // src/inspect/index.ts
12131
+ init_cjs_shims();
12132
+
12133
+ // src/index.ts
11486
12134
  var ENGINE_VERSION = "0.0.0";
11487
12135
  function initEngine(user = {}) {
11488
12136
  let resolved;
@@ -11522,6 +12170,7 @@ function resolveApiKey(config) {
11522
12170
  EpisodicMemory,
11523
12171
  Hippocampus,
11524
12172
  InlineSkillSource,
12173
+ InspectWriter,
11525
12174
  LocalStorageAdapter,
11526
12175
  MAX_ATTEMPTS,
11527
12176
  McpClient,
@@ -11529,6 +12178,7 @@ function resolveApiKey(config) {
11529
12178
  McpManager,
11530
12179
  McpProtocolError,
11531
12180
  McpTimeoutError,
12181
+ NULL_INSPECT_WRITER,
11532
12182
  NodeBackgroundExecutor,
11533
12183
  NotImplementedError,
11534
12184
  PlanSchema,