la-machina-engine 0.7.3 → 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
@@ -1817,7 +1830,11 @@ var AnthropicClient = class {
1817
1830
  messages: request.messages,
1818
1831
  stream: true,
1819
1832
  ...request.system !== void 0 ? { system: request.system } : {},
1820
- ...request.tools !== void 0 ? { tools: request.tools } : {}
1833
+ ...request.tools !== void 0 ? { tools: request.tools } : {},
1834
+ // Plan 025 — `'required'` maps to Anthropic's `tool_choice: { type: 'any' }`,
1835
+ // which forces the model to call SOME tool but doesn't pin which.
1836
+ // `'auto'` is the SDK default — omit to let it through unchanged.
1837
+ ...request.toolChoice === "required" ? { tool_choice: { type: "any" } } : {}
1821
1838
  };
1822
1839
  const requestOptions = {};
1823
1840
  if (betas.length > 0) {
@@ -1999,6 +2016,9 @@ var AISdkAdapter = class {
1999
2016
  tools,
2000
2017
  ...request.maxTokens !== void 0 ? { maxOutputTokens: request.maxTokens } : {},
2001
2018
  ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
2019
+ // Plan 025 — pass through `'required'` so the AI SDK forwards it
2020
+ // to the provider as that provider's "force tool call" flag.
2021
+ ...request.toolChoice === "required" ? { toolChoice: "required" } : {},
2002
2022
  maxRetries: this.options.maxRetries ?? 2
2003
2023
  });
2004
2024
  for await (const event of result.fullStream) {
@@ -2618,6 +2638,17 @@ function ensureToolResultPairing(messages) {
2618
2638
  return messages;
2619
2639
  }
2620
2640
 
2641
+ // src/engine/lastTurnGuard.ts
2642
+ init_cjs_shims();
2643
+ var LAST_TURN_INSTRUCTION_JSON = "SYSTEM NOTE: This is your final allowed turn. Emit ONLY the JSON object that satisfies the output schema in the system prompt. Do not call any more tools. Do not write any explanation, narration, or markdown \u2014 only the raw JSON.";
2644
+ var LAST_TURN_INSTRUCTION_TEXT = "SYSTEM NOTE: This is your final allowed turn. Stop calling tools and deliver your final answer now. The next turn will not happen.";
2645
+ function lastTurnInstruction(outputFormat) {
2646
+ return outputFormat === "json" ? LAST_TURN_INSTRUCTION_JSON : LAST_TURN_INSTRUCTION_TEXT;
2647
+ }
2648
+ function shouldInjectLastTurnInstruction(opts) {
2649
+ return opts.turnCount + 1 === opts.maxTurns;
2650
+ }
2651
+
2621
2652
  // src/compact/compactor.ts
2622
2653
  init_cjs_shims();
2623
2654
 
@@ -3007,6 +3038,30 @@ function isSubagentPausedError(err) {
3007
3038
  }
3008
3039
 
3009
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
+ }
3010
3065
  var DEFAULT_COMPACTION = {
3011
3066
  strategy: "drop-middle",
3012
3067
  threshold: 0.85,
@@ -3049,6 +3104,8 @@ async function agentLoop(options) {
3049
3104
  };
3050
3105
  for (; ; ) {
3051
3106
  compactedThisTurn = false;
3107
+ const turnStartedAt = Date.now();
3108
+ const turnStartTokens = { ...ctx.getTokensUsed() };
3052
3109
  if (options.runSignal?.aborted === true) {
3053
3110
  return failed(new RunTimeoutError(options.runTimeoutMs ?? 0), transcript);
3054
3111
  }
@@ -3058,6 +3115,12 @@ async function agentLoop(options) {
3058
3115
  if (options.tokenBudget !== void 0) {
3059
3116
  const used = ctx.getTokensUsed();
3060
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
+ });
3061
3124
  return {
3062
3125
  status: "done",
3063
3126
  output: lastAssistantText || "[Token budget exhausted]",
@@ -3109,15 +3172,37 @@ async function agentLoop(options) {
3109
3172
  const toolCalls = [];
3110
3173
  let stopReason = null;
3111
3174
  let turnUsage = { input: 0, output: 0 };
3175
+ let messagesForApi = messages;
3176
+ if (shouldInjectLastTurnInstruction({
3177
+ turnCount: ctx.getTurnCount(),
3178
+ maxTurns: ctx.getMaxTurns()
3179
+ })) {
3180
+ const instruction = lastTurnInstruction(options.outputFormat);
3181
+ messagesForApi = [
3182
+ ...messages,
3183
+ {
3184
+ role: "user",
3185
+ content: [{ type: "text", text: instruction }]
3186
+ }
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
+ });
3195
+ }
3112
3196
  const normalizedMessages = normalizeMessages(
3113
- messages
3197
+ messagesForApi
3114
3198
  );
3115
3199
  try {
3116
3200
  for await (const event of client.streamMessage({
3117
3201
  messages: normalizedMessages,
3118
3202
  system,
3119
3203
  tools: anthropicTools,
3120
- ...escalatedMaxTokens !== void 0 ? { maxTokens: escalatedMaxTokens } : {}
3204
+ ...escalatedMaxTokens !== void 0 ? { maxTokens: escalatedMaxTokens } : {},
3205
+ ...options.toolChoice !== void 0 ? { toolChoice: options.toolChoice } : {}
3121
3206
  })) {
3122
3207
  const handled = consumeEvent(event);
3123
3208
  if (handled.text !== void 0) textBlocks.push(handled.text);
@@ -3141,6 +3226,13 @@ async function agentLoop(options) {
3141
3226
  system
3142
3227
  });
3143
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
+ });
3144
3236
  ctx.rehydrate({
3145
3237
  messages: emergency.messages,
3146
3238
  turnCount: ctx.getTurnCount(),
@@ -3168,6 +3260,14 @@ async function agentLoop(options) {
3168
3260
  }
3169
3261
  const retryAfter = err instanceof RateLimitError && err.retryAfterMs ? err.retryAfterMs : BASE_BACKOFF_MS * Math.pow(2, apiRetryCount - 1);
3170
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
+ });
3171
3271
  await new Promise((r) => setTimeout(r, delay));
3172
3272
  continue;
3173
3273
  }
@@ -3319,6 +3419,15 @@ async function agentLoop(options) {
3319
3419
  apiRetryCount = 0;
3320
3420
  consecutive529 = 0;
3321
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
+ });
3322
3431
  await fireProgress("idle");
3323
3432
  if (options.postTurnHooks && options.postTurnHooks.length > 0) {
3324
3433
  await dispatchHooks(options.postTurnHooks, {
@@ -3350,6 +3459,15 @@ async function agentLoop(options) {
3350
3459
  continue;
3351
3460
  }
3352
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
+ });
3353
3471
  if (options.postTurnHooks && options.postTurnHooks.length > 0) {
3354
3472
  await dispatchHooks(options.postTurnHooks, {
3355
3473
  runId: ctx.runId,
@@ -3376,11 +3494,25 @@ async function agentLoop(options) {
3376
3494
  if (!maxTokensEscalated) {
3377
3495
  maxTokensEscalated = true;
3378
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
+ });
3379
3504
  continue;
3380
3505
  }
3381
3506
  if (recoveryCount < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) {
3382
3507
  recoveryCount++;
3383
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
+ });
3384
3516
  await ctx.addUserMessage(
3385
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."
3386
3518
  );
@@ -3668,6 +3800,9 @@ var RunContext = class {
3668
3800
  getTurnCount() {
3669
3801
  return this.turnCount;
3670
3802
  }
3803
+ getMaxTurns() {
3804
+ return this.maxTurns;
3805
+ }
3671
3806
  getTokensUsed() {
3672
3807
  return this.tokensUsed;
3673
3808
  }
@@ -4692,6 +4827,7 @@ function createSendMessageTool(options) {
4692
4827
 
4693
4828
  // src/engine/engine.ts
4694
4829
  init_contract();
4830
+ var import_zod_to_json_schema4 = require("zod-to-json-schema");
4695
4831
 
4696
4832
  // src/tools/fileEdit.ts
4697
4833
  init_cjs_shims();
@@ -9657,6 +9793,227 @@ var TranscriptReader = class {
9657
9793
  }
9658
9794
  };
9659
9795
 
9796
+ // src/engine/runToolFilter.ts
9797
+ init_cjs_shims();
9798
+ function applyRunToolFilter(registry, options) {
9799
+ const stripAll = options.toolChoice === "none" || options.tools !== void 0 && options.tools.length === 0;
9800
+ if (stripAll) {
9801
+ if (options.toolChoice === "required") {
9802
+ throw new EngineError(
9803
+ "ERR_TOOL_CHOICE_CONFLICT",
9804
+ "toolChoice: 'required' is incompatible with an empty tool set (received tools: [] or toolChoice: 'none')."
9805
+ );
9806
+ }
9807
+ for (const tool of registry.list()) {
9808
+ registry.unregister(tool.name);
9809
+ }
9810
+ return;
9811
+ }
9812
+ if (options.tools === void 0) return;
9813
+ const allow = new Set(options.tools);
9814
+ for (const tool of registry.list()) {
9815
+ if (!allow.has(tool.name)) registry.unregister(tool.name);
9816
+ }
9817
+ if (options.toolChoice === "required" && registry.count() === 0) {
9818
+ throw new EngineError(
9819
+ "ERR_TOOL_CHOICE_CONFLICT",
9820
+ "toolChoice: 'required' was requested but no tools matched the per-run allowlist after applying config filters."
9821
+ );
9822
+ }
9823
+ }
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
+
9660
10017
  // src/engine/rehydrate.ts
9661
10018
  init_cjs_shims();
9662
10019
  function rebuildMessagesFromEntries(entries) {
@@ -10133,6 +10490,38 @@ var Engine = class {
10133
10490
  ...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
10134
10491
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
10135
10492
  });
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
+ });
10136
10525
  const writer = new TranscriptWriter({
10137
10526
  storage: storage.workspace,
10138
10527
  logPath,
@@ -10187,7 +10576,17 @@ var Engine = class {
10187
10576
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
10188
10577
  ...gate !== void 0 ? { gateBeforeTool: gate } : {},
10189
10578
  ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {},
10190
- ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {}
10579
+ ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {},
10580
+ // Plan 025 — output mode + tool-choice plumbed down so the
10581
+ // last-turn guard picks the right instruction text and the
10582
+ // model adapter can pass `'required'` to the provider. `'none'`
10583
+ // is already handled above by stripping the tool list, so the
10584
+ // loop only ever sees `'auto'` or `'required'`.
10585
+ ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
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
10191
10590
  });
10192
10591
  const result = await this.finalizeResult(loopResult, writer, logPath, {
10193
10592
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -10195,6 +10594,7 @@ var Engine = class {
10195
10594
  });
10196
10595
  this.logRunEnd(log, runId, options.nodeId, result);
10197
10596
  await this.firePostRunHook(runId, options.nodeId, result, ctx, logPath);
10597
+ await this.writeInspectToolsSummary(inspect, storage.workspace, logPath);
10198
10598
  const capabilitiesMissing = ctx.getCapabilitiesMissing();
10199
10599
  return toResponse(result, {
10200
10600
  runId,
@@ -10280,6 +10680,43 @@ var Engine = class {
10280
10680
  ...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
10281
10681
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
10282
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
+ });
10283
10720
  const priorState = await loadWriterState(storage.workspace, logPath);
10284
10721
  const writer = new TranscriptWriter({
10285
10722
  storage: storage.workspace,
@@ -10369,7 +10806,10 @@ ${inputJson}
10369
10806
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
10370
10807
  ...gate !== void 0 ? { gateBeforeTool: gate } : {},
10371
10808
  ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {},
10372
- ...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
10373
10813
  });
10374
10814
  const result = await this.finalizeResult(loopResult, writer, logPath, {
10375
10815
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
@@ -10377,6 +10817,7 @@ ${inputJson}
10377
10817
  });
10378
10818
  this.logRunEnd(log, snapshot.runId, snapshot.nodeId, result);
10379
10819
  await this.firePostRunHook(snapshot.runId, snapshot.nodeId, result, ctx, logPath);
10820
+ await this.writeInspectToolsSummary(inspect, storage.workspace, logPath);
10380
10821
  const capabilitiesMissing = ctx.getCapabilitiesMissing();
10381
10822
  return toResponse(result, {
10382
10823
  runId: snapshot.runId,
@@ -11220,7 +11661,147 @@ ${inputJson}
11220
11661
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
11221
11662
  });
11222
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
+ }
11223
11799
  };
11800
+ function redactHeadersInline(headers) {
11801
+ const out = {};
11802
+ for (const k of Object.keys(headers)) out[k] = "<redacted>";
11803
+ return out;
11804
+ }
11224
11805
  function buildToolRegistry(options) {
11225
11806
  const {
11226
11807
  config,
@@ -11411,6 +11992,11 @@ init_retry();
11411
11992
  init_snapshot();
11412
11993
  init_synthesize();
11413
11994
  init_types();
11995
+
11996
+ // src/inspect/index.ts
11997
+ init_cjs_shims();
11998
+
11999
+ // src/index.ts
11414
12000
  var ENGINE_VERSION = "0.0.0";
11415
12001
  function initEngine(user = {}) {
11416
12002
  let resolved;
@@ -11450,6 +12036,7 @@ function resolveApiKey(config) {
11450
12036
  EpisodicMemory,
11451
12037
  Hippocampus,
11452
12038
  InlineSkillSource,
12039
+ InspectWriter,
11453
12040
  LocalStorageAdapter,
11454
12041
  MAX_ATTEMPTS,
11455
12042
  McpClient,
@@ -11457,6 +12044,7 @@ function resolveApiKey(config) {
11457
12044
  McpManager,
11458
12045
  McpProtocolError,
11459
12046
  McpTimeoutError,
12047
+ NULL_INSPECT_WRITER,
11460
12048
  NodeBackgroundExecutor,
11461
12049
  NotImplementedError,
11462
12050
  PlanSchema,