la-machina-engine 0.7.4 → 0.7.5

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]",
@@ -3132,13 +3177,21 @@ async function agentLoop(options) {
3132
3177
  turnCount: ctx.getTurnCount(),
3133
3178
  maxTurns: ctx.getMaxTurns()
3134
3179
  })) {
3180
+ const instruction = lastTurnInstruction(options.outputFormat);
3135
3181
  messagesForApi = [
3136
3182
  ...messages,
3137
3183
  {
3138
3184
  role: "user",
3139
- content: [{ type: "text", text: lastTurnInstruction(options.outputFormat) }]
3185
+ content: [{ type: "text", text: instruction }]
3140
3186
  }
3141
3187
  ];
3188
+ await options.inspect?.appendEvent({
3189
+ type: "last_turn_guard",
3190
+ turn: ctx.getTurnCount(),
3191
+ outputFormat: options.outputFormat ?? "text",
3192
+ instruction,
3193
+ ts: Date.now()
3194
+ });
3142
3195
  }
3143
3196
  const normalizedMessages = normalizeMessages(
3144
3197
  messagesForApi
@@ -3173,6 +3226,13 @@ async function agentLoop(options) {
3173
3226
  system
3174
3227
  });
3175
3228
  if (emergency.compacted) {
3229
+ await options.inspect?.appendEvent({
3230
+ type: "compaction_413",
3231
+ turn: ctx.getTurnCount(),
3232
+ droppedMessages: messages.length - emergency.messages.length,
3233
+ strategy: compactionConfig.strategy,
3234
+ ts: Date.now()
3235
+ });
3176
3236
  ctx.rehydrate({
3177
3237
  messages: emergency.messages,
3178
3238
  turnCount: ctx.getTurnCount(),
@@ -3200,6 +3260,14 @@ async function agentLoop(options) {
3200
3260
  }
3201
3261
  const retryAfter = err instanceof RateLimitError && err.retryAfterMs ? err.retryAfterMs : BASE_BACKOFF_MS * Math.pow(2, apiRetryCount - 1);
3202
3262
  const delay = Math.min(retryAfter, 3e4);
3263
+ await options.inspect?.appendEvent({
3264
+ type: "api_retry",
3265
+ turn: ctx.getTurnCount(),
3266
+ status: err instanceof Error && "status" in err ? err.status ?? null : null,
3267
+ attempt: apiRetryCount,
3268
+ delayMs: delay,
3269
+ ts: Date.now()
3270
+ });
3203
3271
  await new Promise((r) => setTimeout(r, delay));
3204
3272
  continue;
3205
3273
  }
@@ -3351,6 +3419,15 @@ async function agentLoop(options) {
3351
3419
  apiRetryCount = 0;
3352
3420
  consecutive529 = 0;
3353
3421
  await ctx.endTurn();
3422
+ await emitInspectTurn({
3423
+ inspect: options.inspect,
3424
+ turn: ctx.getTurnCount() - 1,
3425
+ startedAt: turnStartedAt,
3426
+ startTokens: turnStartTokens,
3427
+ endTokens: ctx.getTokensUsed(),
3428
+ stopReason,
3429
+ toolCallsCount: toolCallsToDispatch.length
3430
+ });
3354
3431
  await fireProgress("idle");
3355
3432
  if (options.postTurnHooks && options.postTurnHooks.length > 0) {
3356
3433
  await dispatchHooks(options.postTurnHooks, {
@@ -3382,6 +3459,15 @@ async function agentLoop(options) {
3382
3459
  continue;
3383
3460
  }
3384
3461
  await ctx.endTurn();
3462
+ await emitInspectTurn({
3463
+ inspect: options.inspect,
3464
+ turn: ctx.getTurnCount() - 1,
3465
+ startedAt: turnStartedAt,
3466
+ startTokens: turnStartTokens,
3467
+ endTokens: ctx.getTokensUsed(),
3468
+ stopReason,
3469
+ toolCallsCount: 0
3470
+ });
3385
3471
  if (options.postTurnHooks && options.postTurnHooks.length > 0) {
3386
3472
  await dispatchHooks(options.postTurnHooks, {
3387
3473
  runId: ctx.runId,
@@ -3408,11 +3494,25 @@ async function agentLoop(options) {
3408
3494
  if (!maxTokensEscalated) {
3409
3495
  maxTokensEscalated = true;
3410
3496
  escalatedMaxTokens = ESCALATED_MAX_TOKENS;
3497
+ await options.inspect?.appendEvent({
3498
+ type: "recovery_max_tokens",
3499
+ turn: ctx.getTurnCount(),
3500
+ attempt: 0,
3501
+ escalatedTo: ESCALATED_MAX_TOKENS,
3502
+ ts: Date.now()
3503
+ });
3411
3504
  continue;
3412
3505
  }
3413
3506
  if (recoveryCount < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) {
3414
3507
  recoveryCount++;
3415
3508
  escalatedMaxTokens = void 0;
3509
+ await options.inspect?.appendEvent({
3510
+ type: "recovery_max_tokens",
3511
+ turn: ctx.getTurnCount(),
3512
+ attempt: recoveryCount,
3513
+ escalatedTo: 0,
3514
+ ts: Date.now()
3515
+ });
3416
3516
  await ctx.addUserMessage(
3417
3517
  "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
3518
  );
@@ -4727,6 +4827,7 @@ function createSendMessageTool(options) {
4727
4827
 
4728
4828
  // src/engine/engine.ts
4729
4829
  init_contract();
4830
+ var import_zod_to_json_schema4 = require("zod-to-json-schema");
4730
4831
 
4731
4832
  // src/tools/fileEdit.ts
4732
4833
  init_cjs_shims();
@@ -9721,6 +9822,198 @@ function applyRunToolFilter(registry, options) {
9721
9822
  }
9722
9823
  }
9723
9824
 
9825
+ // src/inspect/writer.ts
9826
+ init_cjs_shims();
9827
+ var InspectWriter = class {
9828
+ storage;
9829
+ logger;
9830
+ enabled;
9831
+ /** Subdirectory holding all inspect files. Constant per writer. */
9832
+ dir;
9833
+ constructor(opts) {
9834
+ this.storage = opts.storage;
9835
+ if (opts.logger !== void 0) this.logger = opts.logger;
9836
+ this.enabled = opts.enabled;
9837
+ this.dir = `${opts.logPath}/inspect`;
9838
+ }
9839
+ /**
9840
+ * Write the eight start-snapshot files atomically (well, sequentially —
9841
+ * R2 has no multi-object atomic; failures partially-write and the
9842
+ * fail-soft path logs each one).
9843
+ */
9844
+ async writeStartSnapshot(input) {
9845
+ if (!this.enabled) return;
9846
+ await this.safeWrite("system-prompt.txt", input.systemPrompt);
9847
+ await this.safeWriteJson("tools.json", input.tools);
9848
+ await this.safeWriteJson("run-options.json", input.runOptions);
9849
+ await this.safeWriteJson("model-config.json", input.modelConfig);
9850
+ if (input.skills !== void 0) {
9851
+ await this.safeWriteJson("skills.json", input.skills);
9852
+ }
9853
+ if (input.mcpServers !== void 0) {
9854
+ await this.safeWriteJson("mcp-servers.json", input.mcpServers);
9855
+ }
9856
+ if (input.apiServices !== void 0) {
9857
+ await this.safeWriteJson("api-services.json", input.apiServices);
9858
+ }
9859
+ if (input.knowledge !== void 0) {
9860
+ await this.safeWriteJson("knowledge-scope.json", input.knowledge);
9861
+ }
9862
+ }
9863
+ /** Append one event line to events.jsonl. */
9864
+ async appendEvent(event) {
9865
+ if (!this.enabled) return;
9866
+ await this.safeAppend("events.jsonl", JSON.stringify(event) + "\n");
9867
+ }
9868
+ /** Append one row to turns.jsonl. */
9869
+ async appendTurn(row) {
9870
+ if (!this.enabled) return;
9871
+ await this.safeAppend("turns.jsonl", JSON.stringify(row) + "\n");
9872
+ }
9873
+ /** Write the terminal `tools-summary.json` once at run end. */
9874
+ async writeToolsSummary(summary) {
9875
+ if (!this.enabled) return;
9876
+ await this.safeWriteJson("tools-summary.json", summary);
9877
+ }
9878
+ // ---------- private fail-soft helpers ----------
9879
+ async safeWrite(name, content) {
9880
+ try {
9881
+ await this.storage.writeFile(`${this.dir}/${name}`, content);
9882
+ } catch (err) {
9883
+ this.logFailure("writeFile", name, err);
9884
+ }
9885
+ }
9886
+ async safeWriteJson(name, value) {
9887
+ try {
9888
+ await this.storage.writeFile(`${this.dir}/${name}`, JSON.stringify(value, null, 2));
9889
+ } catch (err) {
9890
+ this.logFailure("writeFile (json)", name, err);
9891
+ }
9892
+ }
9893
+ async safeAppend(name, line) {
9894
+ try {
9895
+ await this.storage.appendFile(`${this.dir}/${name}`, line);
9896
+ } catch (err) {
9897
+ this.logFailure("appendFile", name, err);
9898
+ }
9899
+ }
9900
+ logFailure(op, name, err) {
9901
+ if (this.logger === void 0) return;
9902
+ const msg = err instanceof Error ? err.message : String(err);
9903
+ this.logger.warn?.("inspect-writer failed", {
9904
+ op,
9905
+ file: `${this.dir}/${name}`,
9906
+ error: msg
9907
+ });
9908
+ }
9909
+ };
9910
+ var NULL_INSPECT_WRITER = {
9911
+ writeStartSnapshot: async () => void 0,
9912
+ appendEvent: async () => void 0,
9913
+ appendTurn: async () => void 0,
9914
+ writeToolsSummary: async () => void 0
9915
+ };
9916
+
9917
+ // src/inspect/scrub.ts
9918
+ init_cjs_shims();
9919
+ var import_zod_to_json_schema3 = require("zod-to-json-schema");
9920
+ function scrubModelConfig(cfg, opts = { redactBaseURL: false }) {
9921
+ const out = {
9922
+ schemaVersion: 1,
9923
+ provider: cfg.provider,
9924
+ modelId: cfg.modelId
9925
+ };
9926
+ if (!opts.redactBaseURL && cfg.baseURL !== void 0) out.baseURL = cfg.baseURL;
9927
+ if (cfg.temperature !== void 0) out.temperature = cfg.temperature;
9928
+ if (cfg.maxTokens !== void 0) out.maxTokens = cfg.maxTokens;
9929
+ if (cfg.maxRetries !== void 0) out.maxRetries = cfg.maxRetries;
9930
+ return out;
9931
+ }
9932
+ function scrubRunOptions(opts) {
9933
+ const out = {
9934
+ schemaVersion: 1,
9935
+ runId: opts.runId ?? "",
9936
+ nodeId: opts.nodeId,
9937
+ task: opts.task
9938
+ };
9939
+ if (opts.maxTurns !== void 0) out.maxTurns = opts.maxTurns;
9940
+ if (opts.outputFormat !== void 0) out.outputFormat = opts.outputFormat;
9941
+ if (opts.outputSchema !== void 0) out.outputSchema = serializeOutputSchema(opts.outputSchema);
9942
+ if (opts.tools !== void 0) out.tools = [...opts.tools];
9943
+ if (opts.toolChoice !== void 0) out.toolChoice = opts.toolChoice;
9944
+ if (opts.tokenBudget !== void 0) out.tokenBudget = opts.tokenBudget;
9945
+ if (opts.knowledge !== void 0) {
9946
+ const k = {};
9947
+ if (opts.knowledge.folders !== void 0) k.folders = [...opts.knowledge.folders];
9948
+ if (opts.knowledge.external !== void 0) {
9949
+ k.external = opts.knowledge.external.map((e) => {
9950
+ const link = { url: e.url };
9951
+ if (e.description !== void 0) link.description = e.description;
9952
+ return link;
9953
+ });
9954
+ }
9955
+ out.knowledge = k;
9956
+ }
9957
+ if (opts.api !== void 0 && opts.api.services !== void 0) {
9958
+ out.api = {
9959
+ services: opts.api.services.map((s) => {
9960
+ const svc = { name: s.name };
9961
+ if (s.description !== void 0) svc.description = s.description;
9962
+ if (s.baseUrl !== void 0) svc.baseUrl = s.baseUrl;
9963
+ return svc;
9964
+ })
9965
+ };
9966
+ }
9967
+ if (opts.skills !== void 0) {
9968
+ out.skills = opts.skills.map((s) => ({
9969
+ name: s.name,
9970
+ description: s.description ?? "",
9971
+ hasBody: "body" in s && typeof s.body === "string",
9972
+ hasUrl: "url" in s && typeof s.url === "string"
9973
+ }));
9974
+ }
9975
+ if (opts.compaction !== void 0) out.compaction = opts.compaction;
9976
+ if (opts.context !== void 0) out.context = { ...opts.context };
9977
+ return out;
9978
+ }
9979
+ function serializeOutputSchema(schema) {
9980
+ if (schema === null || typeof schema !== "object") return schema;
9981
+ const candidate = schema;
9982
+ if (candidate._def !== void 0 && typeof candidate.safeParse === "function") {
9983
+ try {
9984
+ return (0, import_zod_to_json_schema3.zodToJsonSchema)(schema, {
9985
+ target: "jsonSchema7",
9986
+ $refStrategy: "none"
9987
+ });
9988
+ } catch {
9989
+ return { error: "failed to serialize Zod schema for inspect" };
9990
+ }
9991
+ }
9992
+ return schema;
9993
+ }
9994
+ function scrubSkills(skills) {
9995
+ return {
9996
+ schemaVersion: 1,
9997
+ skills: skills.map((s) => ({
9998
+ name: s.name,
9999
+ description: s.description ?? "",
10000
+ hasPages: s.hasPages ?? false
10001
+ }))
10002
+ };
10003
+ }
10004
+ function scrubApiServices(services) {
10005
+ return {
10006
+ schemaVersion: 1,
10007
+ services: services.map((s) => {
10008
+ const out = { name: s.name };
10009
+ if (s.description !== void 0) out.description = s.description;
10010
+ if (s.baseUrl !== void 0) out.baseUrl = s.baseUrl;
10011
+ if (s.methods !== void 0) out.methods = s.methods.map((m) => m.name);
10012
+ return out;
10013
+ })
10014
+ };
10015
+ }
10016
+
9724
10017
  // src/engine/rehydrate.ts
9725
10018
  init_cjs_shims();
9726
10019
  function rebuildMessagesFromEntries(entries) {
@@ -10198,6 +10491,37 @@ var Engine = class {
10198
10491
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
10199
10492
  });
10200
10493
  applyRunToolFilter(registry, options);
10494
+ const inspect = this.buildInspectWriter(storage.workspace, logPath);
10495
+ await inspect.writeStartSnapshot({
10496
+ systemPrompt,
10497
+ tools: this.snapshotTools(registry, mcpTools),
10498
+ runOptions: scrubRunOptions({ ...options, runId }),
10499
+ modelConfig: scrubModelConfig(
10500
+ {
10501
+ provider: this.config.model.provider,
10502
+ modelId: this.config.model.modelId,
10503
+ ...this.config.model.baseURL !== void 0 ? { baseURL: this.config.model.baseURL } : {},
10504
+ ...this.config.model.temperature !== void 0 ? { temperature: this.config.model.temperature } : {},
10505
+ ...this.config.model.maxTokens !== void 0 ? { maxTokens: this.config.model.maxTokens } : {},
10506
+ ...this.config.model.maxRetries !== void 0 ? { maxRetries: this.config.model.maxRetries } : {}
10507
+ },
10508
+ { redactBaseURL: this.config.inspect.redactBaseURL }
10509
+ ),
10510
+ ...skillList !== void 0 ? { skills: scrubSkills(skillList) } : {},
10511
+ ...mcpTools.length > 0 ? { mcpServers: this.snapshotMcpServers(mcpTools) } : {},
10512
+ ...apiConfig !== void 0 && apiConfig.services.length > 0 ? { apiServices: scrubApiServices(apiConfig.services) } : {},
10513
+ ...knowledgeRuntime !== void 0 ? {
10514
+ knowledge: {
10515
+ schemaVersion: 1,
10516
+ folders: [...knowledgeRuntime.folders],
10517
+ external: knowledgeRuntime.external.map((e) => {
10518
+ const link = { url: e.url };
10519
+ if (e.description !== void 0) link.description = e.description;
10520
+ return link;
10521
+ })
10522
+ }
10523
+ } : {}
10524
+ });
10201
10525
  const writer = new TranscriptWriter({
10202
10526
  storage: storage.workspace,
10203
10527
  logPath,
@@ -10259,7 +10583,10 @@ var Engine = class {
10259
10583
  // is already handled above by stripping the tool list, so the
10260
10584
  // loop only ever sees `'auto'` or `'required'`.
10261
10585
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
10262
- ...options.toolChoice === "required" ? { toolChoice: "required" } : {}
10586
+ ...options.toolChoice === "required" ? { toolChoice: "required" } : {},
10587
+ // Plan 026 — pass the inspect writer so the loop can append
10588
+ // events / per-turn rows. Disabled-mode writer is a no-op.
10589
+ inspect
10263
10590
  });
10264
10591
  const result = await this.finalizeResult(loopResult, writer, logPath, {
10265
10592
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -10267,6 +10594,7 @@ var Engine = class {
10267
10594
  });
10268
10595
  this.logRunEnd(log, runId, options.nodeId, result);
10269
10596
  await this.firePostRunHook(runId, options.nodeId, result, ctx, logPath);
10597
+ await this.writeInspectToolsSummary(inspect, storage.workspace, logPath);
10270
10598
  const capabilitiesMissing = ctx.getCapabilitiesMissing();
10271
10599
  return toResponse(result, {
10272
10600
  runId,
@@ -10352,6 +10680,43 @@ var Engine = class {
10352
10680
  ...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
10353
10681
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
10354
10682
  });
10683
+ const inspect = this.buildInspectWriter(storage.workspace, logPath);
10684
+ await inspect.writeStartSnapshot({
10685
+ systemPrompt,
10686
+ tools: this.snapshotTools(registry, mcpTools),
10687
+ runOptions: scrubRunOptions({
10688
+ runId: snapshot.runId,
10689
+ nodeId: snapshot.nodeId,
10690
+ task: "[resumed run \u2014 original task in transcript]",
10691
+ ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
10692
+ ...options.outputSchema !== void 0 ? { outputSchema: options.outputSchema } : {}
10693
+ }),
10694
+ modelConfig: scrubModelConfig(
10695
+ {
10696
+ provider: this.config.model.provider,
10697
+ modelId: this.config.model.modelId,
10698
+ ...this.config.model.baseURL !== void 0 ? { baseURL: this.config.model.baseURL } : {},
10699
+ ...this.config.model.temperature !== void 0 ? { temperature: this.config.model.temperature } : {},
10700
+ ...this.config.model.maxTokens !== void 0 ? { maxTokens: this.config.model.maxTokens } : {},
10701
+ ...this.config.model.maxRetries !== void 0 ? { maxRetries: this.config.model.maxRetries } : {}
10702
+ },
10703
+ { redactBaseURL: this.config.inspect.redactBaseURL }
10704
+ ),
10705
+ ...skillList !== void 0 ? { skills: scrubSkills(skillList) } : {},
10706
+ ...mcpTools.length > 0 ? { mcpServers: this.snapshotMcpServers(mcpTools) } : {},
10707
+ ...apiConfig !== void 0 && apiConfig.services.length > 0 ? { apiServices: scrubApiServices(apiConfig.services) } : {},
10708
+ ...knowledgeRuntime !== void 0 ? {
10709
+ knowledge: {
10710
+ schemaVersion: 1,
10711
+ folders: [...knowledgeRuntime.folders],
10712
+ external: knowledgeRuntime.external.map((e) => {
10713
+ const link = { url: e.url };
10714
+ if (e.description !== void 0) link.description = e.description;
10715
+ return link;
10716
+ })
10717
+ }
10718
+ } : {}
10719
+ });
10355
10720
  const priorState = await loadWriterState(storage.workspace, logPath);
10356
10721
  const writer = new TranscriptWriter({
10357
10722
  storage: storage.workspace,
@@ -10441,7 +10806,10 @@ ${inputJson}
10441
10806
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
10442
10807
  ...gate !== void 0 ? { gateBeforeTool: gate } : {},
10443
10808
  ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {},
10444
- ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {}
10809
+ ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {},
10810
+ // Plan 026 — append resume's events / per-turn rows to the
10811
+ // existing inspect/ logs.
10812
+ inspect
10445
10813
  });
10446
10814
  const result = await this.finalizeResult(loopResult, writer, logPath, {
10447
10815
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -10449,6 +10817,7 @@ ${inputJson}
10449
10817
  });
10450
10818
  this.logRunEnd(log, snapshot.runId, snapshot.nodeId, result);
10451
10819
  await this.firePostRunHook(snapshot.runId, snapshot.nodeId, result, ctx, logPath);
10820
+ await this.writeInspectToolsSummary(inspect, storage.workspace, logPath);
10452
10821
  const capabilitiesMissing = ctx.getCapabilitiesMissing();
10453
10822
  return toResponse(result, {
10454
10823
  runId: snapshot.runId,
@@ -11292,7 +11661,147 @@ ${inputJson}
11292
11661
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
11293
11662
  });
11294
11663
  }
11664
+ // Plan 026 — inspect-bundle helpers ----------------------------
11665
+ buildInspectWriter(storage, logPath) {
11666
+ const inspect = this.config.inspect;
11667
+ return new InspectWriter({
11668
+ storage,
11669
+ logPath,
11670
+ enabled: inspect.enabled
11671
+ });
11672
+ }
11673
+ /**
11674
+ * Snapshot the live ToolRegistry into the inspect-bundle shape.
11675
+ * Tool source is inferred by name: MCP tools have the
11676
+ * `mcp__{server}__{tool}` prefix; everything else is core.
11677
+ */
11678
+ snapshotTools(registry, mcpTools) {
11679
+ const mcpNameSet = new Set(mcpTools.map((t) => t.name));
11680
+ return {
11681
+ schemaVersion: 1,
11682
+ tools: registry.list().map((t) => {
11683
+ const out = {
11684
+ name: t.name,
11685
+ description: t.description ?? "",
11686
+ inputSchema: this.toolInputSchemaToJson(t),
11687
+ source: mcpNameSet.has(t.name) ? "mcp" : "core",
11688
+ ...t.isCapabilityStub === true ? { isCapabilityStub: true } : {}
11689
+ };
11690
+ return out;
11691
+ })
11692
+ };
11693
+ }
11694
+ /** Convert a Zod input schema to JSON Schema for persistence. */
11695
+ toolInputSchemaToJson(t) {
11696
+ const raw = t.inputSchema;
11697
+ if (raw === void 0 || raw === null) return {};
11698
+ const candidate = raw;
11699
+ if (typeof candidate === "object" && candidate._def !== void 0 && typeof candidate.safeParse === "function") {
11700
+ try {
11701
+ const json2 = (0, import_zod_to_json_schema4.zodToJsonSchema)(raw, {
11702
+ target: "jsonSchema7",
11703
+ $refStrategy: "none"
11704
+ });
11705
+ return json2;
11706
+ } catch {
11707
+ return {};
11708
+ }
11709
+ }
11710
+ return raw;
11711
+ }
11712
+ /**
11713
+ * Plan 026 — aggregate `tools-summary.json` from the persisted
11714
+ * transcript shards. Walks meta.json for shard count, reads each
11715
+ * shard, counts every tool_use block by name. Best-effort; the
11716
+ * InspectWriter swallows write failures.
11717
+ */
11718
+ async writeInspectToolsSummary(inspect, storage, logPath) {
11719
+ const counts = /* @__PURE__ */ new Map();
11720
+ let total = 0;
11721
+ try {
11722
+ const metaRaw = await storage.readFile(`${logPath}/meta.json`);
11723
+ if (!metaRaw) return;
11724
+ const meta = JSON.parse(metaRaw);
11725
+ const shardCount = meta.shardCount ?? 0;
11726
+ for (let i = 0; i < shardCount; i++) {
11727
+ const name = `${String(i).padStart(6, "0")}.jsonl`;
11728
+ const text2 = await storage.readFile(`${logPath}/${name}`);
11729
+ if (!text2) continue;
11730
+ for (const line of text2.split("\n")) {
11731
+ if (!line) continue;
11732
+ try {
11733
+ const entry = JSON.parse(line);
11734
+ if (entry.type !== "assistant") continue;
11735
+ const content = entry.message?.content;
11736
+ if (!Array.isArray(content)) continue;
11737
+ for (const block of content) {
11738
+ const b = block;
11739
+ if (b.type === "tool_use" && typeof b.name === "string") {
11740
+ counts.set(b.name, (counts.get(b.name) ?? 0) + 1);
11741
+ total++;
11742
+ }
11743
+ }
11744
+ } catch {
11745
+ }
11746
+ }
11747
+ }
11748
+ } catch {
11749
+ return;
11750
+ }
11751
+ await inspect.writeToolsSummary({
11752
+ schemaVersion: 1,
11753
+ tools: [...counts.entries()].map(([name, callCount]) => ({ name, callCount })).sort((a, b) => b.callCount - a.callCount),
11754
+ totalCalls: total
11755
+ });
11756
+ }
11757
+ /**
11758
+ * Snapshot the resolved MCP servers. Right now `mcpManager.getTools()`
11759
+ * returns the flat tool list; we group them by server-name prefix to
11760
+ * recover which server exposed which tool.
11761
+ */
11762
+ snapshotMcpServers(mcpTools) {
11763
+ const byServer = /* @__PURE__ */ new Map();
11764
+ for (const t of mcpTools) {
11765
+ const m = t.name.match(/^mcp__([^_]+)__(.+)$/);
11766
+ const server = m?.[1] ?? "unknown";
11767
+ const list = byServer.get(server) ?? [];
11768
+ list.push(t.name);
11769
+ byServer.set(server, list);
11770
+ }
11771
+ const declaredMap = this.config.mcp.servers;
11772
+ return {
11773
+ schemaVersion: 1,
11774
+ servers: [...byServer.entries()].map(([name, tools]) => {
11775
+ const declared = declaredMap[name];
11776
+ const out = {
11777
+ name,
11778
+ // If the server isn't in the declared map (synthetic / dynamic),
11779
+ // default to 'http' as a best-effort label.
11780
+ type: declared?.type ?? "http",
11781
+ state: "connected",
11782
+ tools
11783
+ };
11784
+ if (declared !== void 0) {
11785
+ if (declared.type === "stdio") {
11786
+ out.command = declared.command;
11787
+ out.args = [...declared.args];
11788
+ } else {
11789
+ out.url = declared.url;
11790
+ if (declared.headers !== void 0) {
11791
+ out.headers = redactHeadersInline(declared.headers);
11792
+ }
11793
+ }
11794
+ }
11795
+ return out;
11796
+ })
11797
+ };
11798
+ }
11295
11799
  };
11800
+ function redactHeadersInline(headers) {
11801
+ const out = {};
11802
+ for (const k of Object.keys(headers)) out[k] = "<redacted>";
11803
+ return out;
11804
+ }
11296
11805
  function buildToolRegistry(options) {
11297
11806
  const {
11298
11807
  config,
@@ -11483,6 +11992,11 @@ init_retry();
11483
11992
  init_snapshot();
11484
11993
  init_synthesize();
11485
11994
  init_types();
11995
+
11996
+ // src/inspect/index.ts
11997
+ init_cjs_shims();
11998
+
11999
+ // src/index.ts
11486
12000
  var ENGINE_VERSION = "0.0.0";
11487
12001
  function initEngine(user = {}) {
11488
12002
  let resolved;
@@ -11522,6 +12036,7 @@ function resolveApiKey(config) {
11522
12036
  EpisodicMemory,
11523
12037
  Hippocampus,
11524
12038
  InlineSkillSource,
12039
+ InspectWriter,
11525
12040
  LocalStorageAdapter,
11526
12041
  MAX_ATTEMPTS,
11527
12042
  McpClient,
@@ -11529,6 +12044,7 @@ function resolveApiKey(config) {
11529
12044
  McpManager,
11530
12045
  McpProtocolError,
11531
12046
  McpTimeoutError,
12047
+ NULL_INSPECT_WRITER,
11532
12048
  NodeBackgroundExecutor,
11533
12049
  NotImplementedError,
11534
12050
  PlanSchema,