@moxt-ai/mobius 0.0.299 → 0.0.301

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.
Files changed (86) hide show
  1. package/dist/acp/harness/claude/endpoint.d.ts.map +1 -1
  2. package/dist/acp/harness/claude/endpoint.js +2 -0
  3. package/dist/acp/harness/claude/endpoint.js.map +1 -1
  4. package/dist/acp/harness/claude/parent-lifeline.d.ts +3 -0
  5. package/dist/acp/harness/claude/parent-lifeline.d.ts.map +1 -0
  6. package/dist/acp/harness/claude/parent-lifeline.js +73 -0
  7. package/dist/acp/harness/claude/parent-lifeline.js.map +1 -0
  8. package/dist/acp/harness/codex/settings.d.ts.map +1 -1
  9. package/dist/acp/harness/codex/settings.js +4 -0
  10. package/dist/acp/harness/codex/settings.js.map +1 -1
  11. package/dist/acp/harness/pi/endpoint.d.ts.map +1 -1
  12. package/dist/acp/harness/pi/endpoint.js +1 -0
  13. package/dist/acp/harness/pi/endpoint.js.map +1 -1
  14. package/dist/acp/native-history/codex/native-history-events.d.ts.map +1 -1
  15. package/dist/acp/native-history/codex/native-history-events.js +10 -0
  16. package/dist/acp/native-history/codex/native-history-events.js.map +1 -1
  17. package/dist/cli.js +604 -60
  18. package/dist/cli.js.map +4 -4
  19. package/dist/generated/acp-session-store-migrations.d.ts +3 -0
  20. package/dist/generated/acp-session-store-migrations.d.ts.map +1 -1
  21. package/dist/generated/acp-session-store-migrations.js +5 -1
  22. package/dist/generated/acp-session-store-migrations.js.map +1 -1
  23. package/dist/runtime/command/registry/prompt.d.ts.map +1 -1
  24. package/dist/runtime/command/registry/prompt.js +6 -0
  25. package/dist/runtime/command/registry/prompt.js.map +1 -1
  26. package/dist/runtime/engine.d.ts +1 -0
  27. package/dist/runtime/engine.d.ts.map +1 -1
  28. package/dist/runtime/engine.js +8 -0
  29. package/dist/runtime/engine.js.map +1 -1
  30. package/dist/runtime/run.d.ts +1 -0
  31. package/dist/runtime/run.d.ts.map +1 -1
  32. package/dist/runtime/run.js +3 -0
  33. package/dist/runtime/run.js.map +1 -1
  34. package/dist/runtime.js +569 -53
  35. package/dist/runtime.js.map +4 -4
  36. package/dist/schema/acp-session-store/execution-receipt.d.ts +136 -0
  37. package/dist/schema/acp-session-store/execution-receipt.d.ts.map +1 -0
  38. package/dist/schema/acp-session-store/execution-receipt.js +12 -0
  39. package/dist/schema/acp-session-store/execution-receipt.js.map +1 -0
  40. package/dist/schema/acp-session-store/index.d.ts +1 -0
  41. package/dist/schema/acp-session-store/index.d.ts.map +1 -1
  42. package/dist/schema/acp-session-store/index.js +1 -0
  43. package/dist/schema/acp-session-store/index.js.map +1 -1
  44. package/dist/session/api/session-api.d.ts +4 -0
  45. package/dist/session/api/session-api.d.ts.map +1 -1
  46. package/dist/session/api/session-api.js.map +1 -1
  47. package/dist/session/api/session-layer.d.ts +2 -0
  48. package/dist/session/api/session-layer.d.ts.map +1 -1
  49. package/dist/session/api/session-layer.js +25 -4
  50. package/dist/session/api/session-layer.js.map +1 -1
  51. package/dist/session/history/session-event-store.d.ts +7 -4
  52. package/dist/session/history/session-event-store.d.ts.map +1 -1
  53. package/dist/session/history/session-event-store.js +13 -7
  54. package/dist/session/history/session-event-store.js.map +1 -1
  55. package/dist/session/history/session-history.d.ts +1 -4
  56. package/dist/session/history/session-history.d.ts.map +1 -1
  57. package/dist/session/history/session-history.js +1 -4
  58. package/dist/session/history/session-history.js.map +1 -1
  59. package/dist/session/live/agent-session-restore.d.ts +1 -0
  60. package/dist/session/live/agent-session-restore.d.ts.map +1 -1
  61. package/dist/session/live/agent-session.d.ts +2 -0
  62. package/dist/session/live/agent-session.d.ts.map +1 -1
  63. package/dist/session/live/agent-session.js +15 -4
  64. package/dist/session/live/agent-session.js.map +1 -1
  65. package/dist/session/live/session-restore.d.ts.map +1 -1
  66. package/dist/session/live/session-restore.js +37 -0
  67. package/dist/session/live/session-restore.js.map +1 -1
  68. package/dist/session/manager/session-manager.d.ts.map +1 -1
  69. package/dist/session/manager/session-manager.js +11 -3
  70. package/dist/session/manager/session-manager.js.map +1 -1
  71. package/dist/session/prompt/execute-prompt.d.ts.map +1 -1
  72. package/dist/session/prompt/execute-prompt.js +6 -1
  73. package/dist/session/prompt/execute-prompt.js.map +1 -1
  74. package/dist/session/prompt/prompt-lifecycle.d.ts +1 -0
  75. package/dist/session/prompt/prompt-lifecycle.d.ts.map +1 -1
  76. package/dist/session/prompt/prompt-lifecycle.js +9 -1
  77. package/dist/session/prompt/prompt-lifecycle.js.map +1 -1
  78. package/dist/session/turn/execution-receipts.d.ts +23 -2
  79. package/dist/session/turn/execution-receipts.d.ts.map +1 -1
  80. package/dist/session/turn/execution-receipts.js +161 -10
  81. package/dist/session/turn/execution-receipts.js.map +1 -1
  82. package/dist/storage/session/execution-receipt-table.d.ts +45 -0
  83. package/dist/storage/session/execution-receipt-table.d.ts.map +1 -0
  84. package/dist/storage/session/execution-receipt-table.js +64 -0
  85. package/dist/storage/session/execution-receipt-table.js.map +1 -0
  86. package/package.json +2 -2
package/dist/runtime.js CHANGED
@@ -21633,6 +21633,10 @@ var SessionPromptDisposition = Object.freeze({
21633
21633
  Reject: "reject",
21634
21634
  Steer: "steer"
21635
21635
  });
21636
+ var SessionHistoryPolicy = Object.freeze({
21637
+ Eager: "eager",
21638
+ ExecutionOnly: "execution-only"
21639
+ });
21636
21640
  var MAX_PROMPT_LENGTH = 1e5;
21637
21641
  var MAX_PROMPT_ATTACHMENT_LENGTH = 6e6;
21638
21642
  var promptImageSchema = external_exports.object({
@@ -21694,6 +21698,9 @@ function decodeSessionPromptCommand(encoded) {
21694
21698
  directoryId: readDirectoryIdentifier(value, "directoryId"),
21695
21699
  disposition,
21696
21700
  images,
21701
+ ...value["historyPolicy"] === void 0 ? {} : {
21702
+ historyPolicy: readHistoryPolicy(value)
21703
+ },
21697
21704
  kind: SESSION_PROMPT_KIND,
21698
21705
  prompt,
21699
21706
  resources,
@@ -21702,6 +21709,13 @@ function decodeSessionPromptCommand(encoded) {
21702
21709
  targetCommandId
21703
21710
  };
21704
21711
  }
21712
+ function readHistoryPolicy(value) {
21713
+ const field = value["historyPolicy"];
21714
+ if (field === SessionHistoryPolicy.Eager || field === SessionHistoryPolicy.ExecutionOnly) {
21715
+ return field;
21716
+ }
21717
+ throw new ProtocolMessageError("Invalid value field: historyPolicy");
21718
+ }
21705
21719
  function decodeSessionCancelCommand(encoded) {
21706
21720
  const value = parseJson(encoded);
21707
21721
  assertKind(value, SESSION_CANCEL_KIND);
@@ -22627,16 +22641,28 @@ import { fetch as fetch3 } from "undici";
22627
22641
  // package.json
22628
22642
  var package_default = {
22629
22643
  name: "@moxt-ai/mobius",
22630
- version: "0.0.299",
22644
+ version: "0.0.301",
22631
22645
  description: "Mobius CLI for pairing and running local AI agent services.",
22632
22646
  type: "module",
22633
22647
  bin: {
22634
22648
  mobius: "dist/cli.js"
22635
22649
  },
22636
22650
  exports: {
22637
- "./runtime": "./src/runtime.ts",
22638
- "./process": "./src/util/process/executable.ts",
22639
- "./filesystem": "./src/util/filesystem/private-path.ts"
22651
+ "./runtime": {
22652
+ "mobius-source": "./src/runtime.ts",
22653
+ types: "./dist/runtime.d.ts",
22654
+ default: "./dist/runtime.js"
22655
+ },
22656
+ "./process": {
22657
+ "mobius-source": "./src/util/process/executable.ts",
22658
+ types: "./dist/util/process/executable.d.ts",
22659
+ default: "./dist/util/process/executable.js"
22660
+ },
22661
+ "./filesystem": {
22662
+ "mobius-source": "./src/util/filesystem/private-path.ts",
22663
+ types: "./dist/util/filesystem/private-path.d.ts",
22664
+ default: "./dist/util/filesystem/private-path.js"
22665
+ }
22640
22666
  },
22641
22667
  files: [
22642
22668
  "dist"
@@ -22718,6 +22744,78 @@ var package_default = {
22718
22744
  homepage: "https://github.com/moxt-ai/mobius#readme"
22719
22745
  };
22720
22746
 
22747
+ // src/acp/harness/claude/parent-lifeline.ts
22748
+ import { spawn as spawn2 } from "node:child_process";
22749
+ import { Duplex } from "node:stream";
22750
+ var supervisor = `
22751
+ const { spawn, spawnSync } = require("node:child_process");
22752
+ const { Socket } = require("node:net");
22753
+ const [command, ...args] = process.argv.slice(1);
22754
+ const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"], detached: process.platform !== "win32", windowsHide: true });
22755
+ const lifeline = new Socket({ fd: 3, readable: true, writable: false });
22756
+ let exited = false;
22757
+ function terminate() {
22758
+ if (exited || child.pid === undefined) return;
22759
+ if (process.platform === "win32") {
22760
+ spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { timeout: 5000, windowsHide: true, stdio: "ignore" });
22761
+ } else {
22762
+ try { process.kill(-child.pid, "SIGKILL"); }
22763
+ catch (error) { if (error.code !== "ESRCH") throw error; }
22764
+ }
22765
+ }
22766
+ lifeline.on("end", terminate).on("error", terminate).resume();
22767
+ process.on("SIGTERM", terminate).on("SIGINT", terminate);
22768
+ process.stdin.pipe(child.stdin);
22769
+ child.stdin.on("error", () => {});
22770
+ child.stdout.pipe(process.stdout);
22771
+ child.stderr.pipe(process.stderr);
22772
+ child.once("error", (error) => { process.stderr.write(error.message); process.exitCode = 1; lifeline.destroy(); process.stdin.destroy(); });
22773
+ child.once("close", (code) => { exited = true; process.exitCode = code ?? 1; lifeline.destroy(); process.stdin.destroy(); });
22774
+ `;
22775
+ function spawnClaudeWithParentLifeline(options) {
22776
+ options.signal.throwIfAborted();
22777
+ const child = spawn2(process.execPath, ["-e", supervisor, "--", options.command, ...options.args], {
22778
+ cwd: options.cwd,
22779
+ env: options.env,
22780
+ stdio: ["pipe", "pipe", "pipe", "pipe"],
22781
+ windowsHide: true
22782
+ });
22783
+ const lifeline = child.stdio[3];
22784
+ if (child.stdin === null || child.stdout === null || child.stderr === null || !(lifeline instanceof Duplex)) {
22785
+ child.kill("SIGKILL");
22786
+ throw new Error("The Claude process supervisor has no parent lifeline");
22787
+ }
22788
+ let termination = "running";
22789
+ const stop = () => {
22790
+ termination = "requested";
22791
+ lifeline.end();
22792
+ return true;
22793
+ };
22794
+ options.signal.addEventListener("abort", stop, { once: true });
22795
+ if (options.signal.aborted) {
22796
+ stop();
22797
+ }
22798
+ child.once("close", () => options.signal.removeEventListener("abort", stop));
22799
+ child.stderr.pipe(process.stderr, { end: false });
22800
+ return {
22801
+ stdin: child.stdin,
22802
+ stdout: child.stdout,
22803
+ get killed() {
22804
+ return termination === "requested";
22805
+ },
22806
+ get exitCode() {
22807
+ return child.exitCode;
22808
+ },
22809
+ get signalCode() {
22810
+ return child.signalCode;
22811
+ },
22812
+ kill: stop,
22813
+ on: child.on.bind(child),
22814
+ once: child.once.bind(child),
22815
+ off: child.off.bind(child)
22816
+ };
22817
+ }
22818
+
22721
22819
  // src/acp/harness/claude/upstream/dist/acp-agent.js
22722
22820
  import { execFile as execFile3 } from "node:child_process";
22723
22821
  import { randomUUID as randomUUID3 } from "node:crypto";
@@ -62438,6 +62536,7 @@ async function waitForMcpAuthentication(sessions, sessionId, query, serverName,
62438
62536
  return false;
62439
62537
  }
62440
62538
  var ClaudeAcpAgent = class {
62539
+ spawnClaudeCodeProcess;
62441
62540
  sessions;
62442
62541
  client;
62443
62542
  clientCapabilities;
@@ -62454,7 +62553,8 @@ var ClaudeAcpAgent = class {
62454
62553
  * return "cancelled". See {@link DEFAULT_FORCE_CANCEL_GRACE_MS}. Mutable so
62455
62554
  * tests can shrink it. */
62456
62555
  forceCancelGraceMs = DEFAULT_FORCE_CANCEL_GRACE_MS;
62457
- constructor(client2, logger, environment = process.env) {
62556
+ constructor(client2, logger, environment = process.env, spawnClaudeCodeProcess) {
62557
+ this.spawnClaudeCodeProcess = spawnClaudeCodeProcess;
62458
62558
  this.sessions = {};
62459
62559
  this.client = client2;
62460
62560
  this.logger = logger ?? console;
@@ -65031,6 +65131,7 @@ ${message.api_refusal_explanation}` : "";
65031
65131
  supportedDialogKinds: [REFUSAL_FALLBACK_DIALOG_KIND]
65032
65132
  } : {},
65033
65133
  pathToClaudeCodeExecutable: this.environment.CLAUDE_CODE_EXECUTABLE ?? await claudeCliPath(),
65134
+ ...this.spawnClaudeCodeProcess === void 0 ? {} : { spawnClaudeCodeProcess: this.spawnClaudeCodeProcess },
65034
65135
  extraArgs: {
65035
65136
  ...userProvidedOptions?.extraArgs,
65036
65137
  "replay-user-messages": ""
@@ -66449,7 +66550,7 @@ async function runPromptWithCancellation(agent, params, signal) {
66449
66550
  function createClaudeAcpApp(options = {}) {
66450
66551
  let agent = null;
66451
66552
  const app = acpAgent({ name: "claude-code-acp" }).onConnect((connection) => {
66452
- agent = new ClaudeAcpAgent(new ClientConnection(connection.client, options.onSessionUpdate), options.logger, options.environment);
66553
+ agent = new ClaudeAcpAgent(new ClientConnection(connection.client, options.onSessionUpdate), options.logger, options.environment, options.spawnClaudeCodeProcess);
66453
66554
  options.onConnect?.(connection);
66454
66555
  }).onRequest(methods.agent.initialize, (ctx) => requiredAgent(agent).initialize(ctx.params)).onRequest(methods.agent.session.new, (ctx) => requiredAgent(agent).newSession(ctx.params)).onRequest(methods.agent.session.load, (ctx) => requiredAgent(agent).loadSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.fork, (ctx) => requiredAgent(agent).unstable_forkSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.list, (ctx) => requiredAgent(agent).listSessions(ctx.params, ctx.signal)).onRequest(methods.agent.session.delete, (ctx) => requiredAgent(agent).deleteSession(ctx.params)).onRequest(methods.agent.session.resume, (ctx) => requiredAgent(agent).resumeSession(ctx.params)).onRequest(methods.agent.session.close, (ctx) => requiredAgent(agent).closeSession(ctx.params)).onRequest(methods.agent.session.setMode, (ctx) => requiredAgent(agent).setSessionMode(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => requiredAgent(agent).setSessionConfigOption(ctx.params)).onRequest(methods.agent.authenticate, (ctx) => requiredAgent(agent).authenticate(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => requiredAgent(agent).unstable_listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => requiredAgent(agent).unstable_setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => requiredAgent(agent).unstable_disableProvider(ctx.params)).onRequest(methods.agent.logout, (ctx) => requiredAgent(agent).logout(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => runPromptWithCancellation(requiredAgent(agent), ctx.params, ctx.signal)).onNotification(methods.agent.session.cancel, (ctx) => requiredAgent(agent).cancel(ctx.params)).onRequest(STEER_METHOD, { parse: parseSteerRequest }, (ctx) => requiredAgent(agent).steer(ctx.params)).onRequest(RENAME_METHOD, { parse: parseRenameRequest }, (ctx) => requiredAgent(agent).renameSession(ctx.params)).onRequest(GOAL_CONTROL_METHOD, { parse: parseGoalRequest }, (ctx) => requiredAgent(agent).goal(ctx.params));
66455
66556
  return { agent: () => agent, app };
@@ -66574,6 +66675,7 @@ var ClaudeAcpEndpoint = class {
66574
66675
  this.terminated = termination.promise;
66575
66676
  this.#resolveTermination = termination.resolve;
66576
66677
  const claude = createClaudeAcpApp({
66678
+ spawnClaudeCodeProcess: spawnClaudeWithParentLifeline,
66577
66679
  environment: Object.freeze({
66578
66680
  ...process.env,
66579
66681
  ...options.environment,
@@ -66724,7 +66826,7 @@ var AcpConfigurationValueUnavailableError = class extends AcpExecutionError {
66724
66826
  };
66725
66827
 
66726
66828
  // src/acp/harness/codex/app-server-client.ts
66727
- import { spawn as spawn3 } from "node:child_process";
66829
+ import { spawn as spawn4 } from "node:child_process";
66728
66830
 
66729
66831
  // src/acp/agent/in-process-endpoint.ts
66730
66832
  function acpEndpointTerminationError(termination) {
@@ -67057,7 +67159,7 @@ var CodexAppServerClient = class {
67057
67159
  constructor(options) {
67058
67160
  this.#handlers = options.handlers;
67059
67161
  const launch = executableLaunch2(options.command, ["app-server"]);
67060
- this.#child = spawn3(launch.command, [...launch.arguments], {
67162
+ this.#child = spawn4(launch.command, [...launch.arguments], {
67061
67163
  cwd: options.cwd,
67062
67164
  ...platformSpawnOptions,
67063
67165
  env: { ...process.env, ...options.environment },
@@ -67444,12 +67546,12 @@ function presentToolItem(item) {
67444
67546
  };
67445
67547
  }
67446
67548
  if (item.type === "collabAgentToolCall") {
67447
- const spawn8 = item.tool === "spawnAgent";
67549
+ const spawn9 = item.tool === "spawnAgent";
67448
67550
  return {
67449
67551
  content: [],
67450
67552
  kind: "other",
67451
67553
  locations: [],
67452
- rawInput: spawn8 ? {
67554
+ rawInput: spawn9 ? {
67453
67555
  model: item.model,
67454
67556
  prompt: item.prompt,
67455
67557
  reasoningEffort: item.reasoningEffort,
@@ -67457,10 +67559,10 @@ function presentToolItem(item) {
67457
67559
  } : { receiverThreadIds: item.receiverThreadIds, senderThreadId: item.senderThreadId },
67458
67560
  rawOutput: {
67459
67561
  agentsStates: item.agentsStates,
67460
- ...spawn8 ? { receiverThreadIds: item.receiverThreadIds } : {}
67562
+ ...spawn9 ? { receiverThreadIds: item.receiverThreadIds } : {}
67461
67563
  },
67462
67564
  status: collaborationStatus(item.status),
67463
- title: spawn8 ? "Dispatch subagent" : "Wait for subagent"
67565
+ title: spawn9 ? "Dispatch subagent" : "Wait for subagent"
67464
67566
  };
67465
67567
  }
67466
67568
  if (item.type === "subAgentActivity" && item.kind === "started") {
@@ -68629,6 +68731,10 @@ var CodexSettings = class {
68629
68731
  approvalPolicy: mode.approvalPolicy,
68630
68732
  approvalsReviewer: mode.approvalsReviewer,
68631
68733
  sandboxPolicy: mode.sandboxPolicy,
68734
+ // Codex only streams its provider-authored reasoning summary when the turn opts in.
68735
+ // Keep the summary channel enabled so Thinking has visible content even when no raw
68736
+ // reasoning text is available.
68737
+ summary: "auto",
68632
68738
  // The collaboration mode rides on the turn: Codex applies it to the
68633
68739
  // thread, where it persists until changed. "Build" is Codex's `default`
68634
68740
  // mode, so it is only sent once a choice exists to apply.
@@ -69221,7 +69327,7 @@ import { basename as basename4, dirname as dirname7, isAbsolute as isAbsolute11,
69221
69327
  import { RequestError as RequestError7 } from "@agentclientprotocol/sdk";
69222
69328
 
69223
69329
  // src/acp/harness/pi/upstream/dist/pi-rpc/process.js
69224
- import { spawn as spawn4 } from "node:child_process";
69330
+ import { spawn as spawn5 } from "node:child_process";
69225
69331
  import * as readline from "node:readline";
69226
69332
 
69227
69333
  // src/acp/harness/pi/upstream/dist/pi-rpc/command.js
@@ -69338,7 +69444,7 @@ var PiRpcProcess = class _PiRpcProcess {
69338
69444
  args.push("--fork", params.origin.sessionPath);
69339
69445
  break;
69340
69446
  }
69341
- const child = spawn4(cmd, args, {
69447
+ const child = spawn5(cmd, args, {
69342
69448
  cwd: params.cwd,
69343
69449
  stdio: "pipe",
69344
69450
  env: params.environment,
@@ -72338,6 +72444,77 @@ ${JSON.stringify(stats, null, 2)}`;
72338
72444
  }, 0);
72339
72445
  return { sessionId: session.sessionId, configOptions, modes };
72340
72446
  }
72447
+ async resumeSession(params) {
72448
+ if (!isAbsolute11(params.cwd)) {
72449
+ throw RequestError7.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
72450
+ }
72451
+ this.sessions.close(params.sessionId);
72452
+ this.lastSessionCwd = params.cwd;
72453
+ const stored = this.findStoredSession(params.sessionId);
72454
+ if (!stored) {
72455
+ throw RequestError7.invalidParams(`Unknown sessionId: ${params.sessionId}`);
72456
+ }
72457
+ const session = await this.restoreSession(params.sessionId, {
72458
+ cwd: params.cwd,
72459
+ mcpServers: params.mcpServers
72460
+ });
72461
+ let configuration;
72462
+ try {
72463
+ configuration = await getSessionConfiguration(session.proc);
72464
+ } catch (err) {
72465
+ this.sessions.close(session.sessionId);
72466
+ throw err;
72467
+ }
72468
+ const { configOptions, models, modes } = configuration;
72469
+ const enableSkillCommands = getEnableSkillCommands(params.cwd, this.environment);
72470
+ const fileCommands = loadSlashCommands(params.cwd, this.environment);
72471
+ this.sessions.closeAllExcept?.(session.sessionId);
72472
+ this.store.upsert({
72473
+ sessionId: params.sessionId,
72474
+ cwd: params.cwd,
72475
+ sessionFile: stored.sessionFile
72476
+ });
72477
+ const response = {
72478
+ configOptions,
72479
+ models,
72480
+ modes,
72481
+ _meta: {
72482
+ piAcp: {
72483
+ startupInfo: null
72484
+ }
72485
+ }
72486
+ };
72487
+ setTimeout(() => {
72488
+ void (async () => {
72489
+ await session.publishContextUsage();
72490
+ try {
72491
+ const pi2 = await session.proc.getCommands();
72492
+ const { commands } = toAvailableCommandsFromPiGetCommands(pi2, {
72493
+ enableSkillCommands,
72494
+ includeExtensionCommands: false
72495
+ });
72496
+ await this.conn.sessionUpdate({
72497
+ sessionId: session.sessionId,
72498
+ update: {
72499
+ sessionUpdate: "available_commands_update",
72500
+ availableCommands: mergeCommands(commands, builtinAvailableCommands())
72501
+ }
72502
+ });
72503
+ return;
72504
+ } catch {
72505
+ }
72506
+ await this.conn.sessionUpdate({
72507
+ sessionId: session.sessionId,
72508
+ update: {
72509
+ sessionUpdate: "available_commands_update",
72510
+ availableCommands: mergeCommands(toAvailableCommands(fileCommands), builtinAvailableCommands())
72511
+ }
72512
+ });
72513
+ })().catch(() => {
72514
+ });
72515
+ }, 0);
72516
+ return response;
72517
+ }
72341
72518
  async loadSession(params) {
72342
72519
  if (!isAbsolute11(params.cwd)) {
72343
72520
  throw RequestError7.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
@@ -72921,6 +73098,9 @@ var PiAcpEndpoint = class {
72921
73098
  ).onRequest(
72922
73099
  methods5.agent.session.load,
72923
73100
  async (context) => await piAgentOrThrow(piAgent).loadSession(context.params)
73101
+ ).onRequest(
73102
+ methods5.agent.session.resume,
73103
+ async (context) => await piAgentOrThrow(piAgent).resumeSession(context.params)
72924
73104
  ).onRequest(
72925
73105
  methods5.agent.session.list,
72926
73106
  async (context) => await piAgentOrThrow(piAgent).listSessions(context.params)
@@ -73200,7 +73380,7 @@ var LocalAgentInventory = class {
73200
73380
  };
73201
73381
 
73202
73382
  // src/agent/discovery/acp-readiness-probe.ts
73203
- import { spawn as spawn5 } from "node:child_process";
73383
+ import { spawn as spawn6 } from "node:child_process";
73204
73384
  import {
73205
73385
  methods as methods8,
73206
73386
  ndJsonStream as ndJsonStream2,
@@ -73391,7 +73571,7 @@ async function probeAcpReadiness(specification, agent, environment, defaultWorks
73391
73571
  }
73392
73572
  }
73393
73573
  const launch = executableLaunch2(agent.adapterPath, agent.arguments);
73394
- const child = spawn5(launch.command, [...launch.arguments], {
73574
+ const child = spawn6(launch.command, [...launch.arguments], {
73395
73575
  cwd: defaultWorkspace,
73396
73576
  ...platformSpawnOptions,
73397
73577
  env: { ...environment, ...agent.environment },
@@ -75004,7 +75184,7 @@ var ClaudeAdapter = class {
75004
75184
  };
75005
75185
 
75006
75186
  // src/acp/native-history/codex/codex-history.ts
75007
- import { spawn as spawn6 } from "node:child_process";
75187
+ import { spawn as spawn7 } from "node:child_process";
75008
75188
 
75009
75189
  // src/acp/protocol/native-session.ts
75010
75190
  var path6 = external_exports.string().min(1).max(2048).refine((value) => !value.includes("\0"));
@@ -75701,7 +75881,7 @@ async function readCodexHistoryWithOptions(agent, cwd, nativeSessionId, signal,
75701
75881
  }
75702
75882
  signal.throwIfAborted();
75703
75883
  const launch = executableLaunch2(agent.agentIdentity.realPath, ["app-server"]);
75704
- const child = spawn6(launch.command, [...launch.arguments], {
75884
+ const child = spawn7(launch.command, [...launch.arguments], {
75705
75885
  cwd,
75706
75886
  env: { ...process.env, ...agent.environment },
75707
75887
  ...platformSpawnOptions,
@@ -75936,6 +76116,16 @@ function toolStatus2(value) {
75936
76116
  }
75937
76117
  }
75938
76118
  function activityEvent(activity2) {
76119
+ if (activity2.activityType === "reasoning") {
76120
+ return {
76121
+ payload: {
76122
+ content: { text: activity2.output, type: "text" },
76123
+ messageId: activity2.activityId,
76124
+ sessionUpdate: "agent_thought_chunk"
76125
+ },
76126
+ type: "acp"
76127
+ };
76128
+ }
75939
76129
  return {
75940
76130
  payload: {
75941
76131
  content: activity2.content,
@@ -81032,6 +81222,18 @@ var acpSession = sqliteTable("acp_session", {
81032
81222
  updatedAt: text("updated_at").notNull()
81033
81223
  });
81034
81224
 
81225
+ // src/schema/acp-session-store/execution-receipt.ts
81226
+ var executionReceipt = sqliteTable("execution_receipt", {
81227
+ commandId: text("command_id").primaryKey(),
81228
+ sessionId: text("session_id").notNull(),
81229
+ commandHash: text("command_hash").notNull(),
81230
+ attemptId: text("attempt_id").notNull(),
81231
+ state: text("state").notNull(),
81232
+ started: integer2("started", { mode: "boolean" }).notNull(),
81233
+ outcome: text("outcome"),
81234
+ updatedAt: text("updated_at").notNull()
81235
+ });
81236
+
81035
81237
  // src/schema/acp-session-store/session-live-event-position-allocation.ts
81036
81238
  var sessionLiveEventPositionAllocation = sqliteTable("session_live_event_position_allocation", {
81037
81239
  allocatedPosition: integer2("allocated_position").notNull().default(1e4),
@@ -81073,6 +81275,62 @@ var AcpSessionTable = class {
81073
81275
  };
81074
81276
  };
81075
81277
 
81278
+ // src/storage/session/execution-receipt-table.ts
81279
+ var receiptFields = external_exports.object({
81280
+ commandId: external_exports.string(),
81281
+ sessionId: external_exports.string(),
81282
+ commandHash: external_exports.string(),
81283
+ attemptId: external_exports.string(),
81284
+ updatedAt: external_exports.string()
81285
+ });
81286
+ var receiptSchema = external_exports.discriminatedUnion("state", [
81287
+ receiptFields.extend({ state: external_exports.literal("dispatching"), started: external_exports.literal(false), outcome: external_exports.null() }),
81288
+ receiptFields.extend({ state: external_exports.literal("running"), started: external_exports.literal(true), outcome: external_exports.string().nullable() }),
81289
+ receiptFields.extend({
81290
+ state: external_exports.enum(["succeeded", "failed", "cancelled"]),
81291
+ started: external_exports.boolean(),
81292
+ outcome: external_exports.string().nullable()
81293
+ })
81294
+ ]).readonly();
81295
+ var ExecutionReceiptTable = class {
81296
+ #database;
81297
+ constructor(database) {
81298
+ this.#database = database;
81299
+ }
81300
+ list = () => receiptSchema.array().parse(
81301
+ this.#database.orm.select().from(executionReceipt).orderBy(asc(executionReceipt.updatedAt), asc(executionReceipt.commandId)).all()
81302
+ );
81303
+ save = (receipt) => {
81304
+ this.#database.transaction(() => {
81305
+ this.#database.orm.insert(executionReceipt).values(receipt).onConflictDoUpdate({ target: executionReceipt.commandId, set: receipt }).run();
81306
+ if (receipt.state === "dispatching" || receipt.state === "running") {
81307
+ return;
81308
+ }
81309
+ const retained = this.#database.orm.select({ commandId: executionReceipt.commandId }).from(executionReceipt).where(
81310
+ and(
81311
+ eq2(executionReceipt.sessionId, receipt.sessionId),
81312
+ inArray(executionReceipt.state, ["succeeded", "failed", "cancelled"])
81313
+ )
81314
+ ).orderBy(desc(executionReceipt.updatedAt), desc(executionReceipt.commandId)).limit(1024).all();
81315
+ if (retained.length === 1024) {
81316
+ this.#database.orm.delete(executionReceipt).where(
81317
+ and(
81318
+ eq2(executionReceipt.sessionId, receipt.sessionId),
81319
+ inArray(executionReceipt.state, ["succeeded", "failed", "cancelled"]),
81320
+ notInArray(
81321
+ executionReceipt.commandId,
81322
+ retained.map((row) => row.commandId)
81323
+ )
81324
+ )
81325
+ ).run();
81326
+ }
81327
+ });
81328
+ };
81329
+ remove = (commandId) => {
81330
+ this.#database.orm.delete(executionReceipt).where(eq2(executionReceipt.commandId, commandId)).run();
81331
+ };
81332
+ };
81333
+
81076
81334
  // src/storage/session/live-event-position-table.ts
81077
81335
  var allocationRowSchema = external_exports.object({ allocatedPosition: external_exports.number().int().safe().nonnegative() });
81078
81336
  var LiveEventPositionTable = class {
@@ -81292,6 +81550,10 @@ var acpSessionStoreMigrations = [
81292
81550
  {
81293
81551
  "name": "0006_live_event_position_allocation.sql",
81294
81552
  "sql": "CREATE TABLE `session_live_event_position_allocation` (\n `allocated_position` integer DEFAULT 10000 NOT NULL,\n `session_id` text PRIMARY KEY NOT NULL\n);\n"
81553
+ },
81554
+ {
81555
+ "name": "0007_busy_excalibur.sql",
81556
+ "sql": "CREATE TABLE `execution_receipt` (\n `command_id` text PRIMARY KEY NOT NULL,\n `session_id` text NOT NULL,\n `command_hash` text NOT NULL,\n `attempt_id` text NOT NULL,\n `state` text NOT NULL,\n `started` integer NOT NULL,\n `outcome` text,\n `updated_at` text NOT NULL\n);\n"
81295
81557
  }
81296
81558
  ];
81297
81559
 
@@ -81946,12 +82208,12 @@ var SessionEvents = class {
81946
82208
  }
81947
82209
  };
81948
82210
  };
81949
- function liveEvent(sessionId, sessionGenerationId, eventId, position, event) {
82211
+ function liveEvent(sessionId, sessionGenerationId, eventId, position, event, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
81950
82212
  return {
81951
82213
  eventId,
81952
82214
  ...event,
81953
82215
  position: position.toString(),
81954
- recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
82216
+ recordedAt,
81955
82217
  sessionGenerationId,
81956
82218
  sessionId
81957
82219
  };
@@ -81989,7 +82251,7 @@ var MemorySessionEventStore = class {
81989
82251
  }
81990
82252
  return resolved;
81991
82253
  };
81992
- append = (sessionId, event) => {
82254
+ append = (sessionId, event, recordedAt) => {
81993
82255
  const session = this.#session(sessionId);
81994
82256
  const allocatedPosition = this.#allocatedPositionBySession.get(sessionId) ?? 9999;
81995
82257
  if (allocatedPosition === Number.MAX_SAFE_INTEGER) {
@@ -82002,7 +82264,8 @@ var MemorySessionEventStore = class {
82002
82264
  session.sessionGenerationId,
82003
82265
  this.#eventIds.allocate(sessionId, session.sessionGenerationId),
82004
82266
  position,
82005
- event
82267
+ event,
82268
+ recordedAt
82006
82269
  );
82007
82270
  session.apply(recorded, this.#resolver(session));
82008
82271
  return recorded;
@@ -82043,12 +82306,14 @@ var MemorySessionEventStore = class {
82043
82306
  };
82044
82307
  };
82045
82308
  var CachedSessionEventStore = class {
82309
+ #terminalOutcomes;
82046
82310
  #cacheTable;
82047
82311
  #eventIds;
82048
82312
  #liveEventPositions;
82049
82313
  #sessions = /* @__PURE__ */ new Map();
82050
82314
  #flushSchedule = { kind: "idle" };
82051
- constructor(cacheTable, liveEventPositions, eventIds = new InMemorySessionEventIdAllocator()) {
82315
+ constructor(cacheTable, liveEventPositions, eventIds = new InMemorySessionEventIdAllocator(), terminalOutcomes = () => []) {
82316
+ this.#terminalOutcomes = terminalOutcomes;
82052
82317
  this.#eventIds = eventIds;
82053
82318
  this.#liveEventPositions = liveEventPositions;
82054
82319
  this.#cacheTable = cacheTable;
@@ -82081,7 +82346,7 @@ var CachedSessionEventStore = class {
82081
82346
  }
82082
82347
  return resolved;
82083
82348
  };
82084
- append = (sessionId, event) => {
82349
+ append = (sessionId, event, recordedAt) => {
82085
82350
  if (this.#flushSchedule.kind === "closed") {
82086
82351
  throw new Error("The V2 Session event store is closed");
82087
82352
  }
@@ -82092,7 +82357,8 @@ var CachedSessionEventStore = class {
82092
82357
  session.sessionGenerationId,
82093
82358
  this.#eventIds.allocate(sessionId, session.sessionGenerationId),
82094
82359
  position,
82095
- event
82360
+ event,
82361
+ recordedAt
82096
82362
  );
82097
82363
  session.apply(recorded, this.#resolver(sessionId, session));
82098
82364
  this.#sessions.set(sessionId, session);
@@ -82181,6 +82447,9 @@ var CachedSessionEventStore = class {
82181
82447
  this.#deletePersisted(sessionId, previous?.sessionGenerationId);
82182
82448
  const sessionGenerationId = randomUUID11();
82183
82449
  this.#sessions.set(sessionId, new SessionEvents(sessionGenerationId));
82450
+ for (const { event, recordedAt } of this.#terminalOutcomes(sessionId)) {
82451
+ this.append(sessionId, event, recordedAt);
82452
+ }
82184
82453
  };
82185
82454
  materializedEventCount = (sessionId) => this.#sessions.get(sessionId)?.materializedCount ?? 0;
82186
82455
  close = () => {
@@ -82294,7 +82563,7 @@ function buildPromptContent(options, capabilities) {
82294
82563
  }
82295
82564
 
82296
82565
  // src/acp/connection/agent-connection.ts
82297
- import { spawn as spawn7 } from "node:child_process";
82566
+ import { spawn as spawn8 } from "node:child_process";
82298
82567
  import { ndJsonStream as ndJsonStream3, RequestError as RequestError9 } from "@agentclientprotocol/sdk";
82299
82568
  function requestErrorMessage(error62) {
82300
82569
  const fallback = truncate(error62.message, 500);
@@ -82404,7 +82673,7 @@ var AgentConnection = class {
82404
82673
  return;
82405
82674
  }
82406
82675
  const launch = executableLaunch2(agent.adapterPath, agent.arguments);
82407
- const child = spawn7(launch.command, [...launch.arguments], {
82676
+ const child = spawn8(launch.command, [...launch.arguments], {
82408
82677
  cwd,
82409
82678
  ...platformSpawnOptions,
82410
82679
  env: { ...process.env, ...agent.environment },
@@ -83678,6 +83947,19 @@ var RunningAgentTurn = class extends AgentTurnState {
83678
83947
 
83679
83948
  // src/session/live/session-restore.ts
83680
83949
  import { methods as methods10 } from "@agentclientprotocol/sdk";
83950
+ function hasMethodNotFoundCode(cause) {
83951
+ const visited = /* @__PURE__ */ new Set();
83952
+ let current = cause;
83953
+ while ((current instanceof Error || typeof current === "object" && current !== null) && !visited.has(current)) {
83954
+ visited.add(current);
83955
+ const code = Reflect.get(current, "code");
83956
+ if (code === -32601 || code === "METHOD_NOT_FOUND" || code === "method_not_found") {
83957
+ return true;
83958
+ }
83959
+ current = current instanceof Error ? current.cause : void 0;
83960
+ }
83961
+ return false;
83962
+ }
83681
83963
  async function restoreSession(session, signal, recovery, origin) {
83682
83964
  if (origin !== void 0) {
83683
83965
  session.setSessionTitle(origin.title);
@@ -83744,6 +84026,9 @@ async function restoreSession(session, signal, recovery, origin) {
83744
84026
  }
83745
84027
  session.setPrompted(true);
83746
84028
  session.setRestored(true);
84029
+ if (recovery.kind === "execution-only") {
84030
+ session.startExecutionOnlyCapture();
84031
+ }
83747
84032
  lifecycleResponse2 = forked;
83748
84033
  } else if (stored instanceof FoundStoredSession && stored.session.agentId === session.agentId && stored.session.cwd === session.cwd && arraysEqual(stored.session.additionalDirectories, session.additionalDirectories)) {
83749
84034
  session.selectNativeSession(stored.session.nativeSessionId);
@@ -83762,6 +84047,37 @@ async function restoreSession(session, signal, recovery, origin) {
83762
84047
  "session-load",
83763
84048
  async () => (await session.loadSession(signal, "ACP agent exited while loading a session")).response
83764
84049
  );
84050
+ } else if (recovery.kind === "execution-only") {
84051
+ try {
84052
+ lifecycleResponse2 = await session.metrics.measure(
84053
+ "session-resume",
84054
+ async () => await session.race(
84055
+ session.agentRequest(
84056
+ methods10.agent.session.resume,
84057
+ {
84058
+ additionalDirectories: [...session.additionalDirectories],
84059
+ cwd: session.cwd,
84060
+ mcpServers: [],
84061
+ sessionId: session.nativeSessionId()
84062
+ },
84063
+ { cancellationSignal: signal }
84064
+ ),
84065
+ "ACP agent exited while resuming a session",
84066
+ signal
84067
+ )
84068
+ );
84069
+ } catch (cause) {
84070
+ signal.throwIfAborted();
84071
+ if (cause instanceof AcpExecutionError && !hasMethodNotFoundCode(cause)) {
84072
+ throw cause;
84073
+ }
84074
+ throw new AcpExecutionError(
84075
+ "This Agent cannot continue a persisted session without replaying native history",
84076
+ { cause },
84077
+ "execution_only_unsupported"
84078
+ );
84079
+ }
84080
+ session.startExecutionOnlyCapture();
83765
84081
  } else if (session.capabilities.resumeSession) {
83766
84082
  lifecycleResponse2 = await session.metrics.measure(
83767
84083
  "session-resume",
@@ -83821,6 +84137,9 @@ async function restoreSession(session, signal, recovery, origin) {
83821
84137
  );
83822
84138
  session.selectNativeSession(created.sessionId);
83823
84139
  session.setNativeHistoryOwnership({ kind: "fresh-session" });
84140
+ if (recovery.kind === "execution-only") {
84141
+ session.startExecutionOnlyCapture();
84142
+ }
83824
84143
  lifecycleResponse2 = created;
83825
84144
  }
83826
84145
  session.restrictSessionCapabilities(lifecycleResponse2);
@@ -84098,6 +84417,16 @@ var AgentSession = class _AgentSession {
84098
84417
  }
84099
84418
  await this.#completedHistory(signal, "ACP agent exited while loading history");
84100
84419
  };
84420
+ /** Starts live event capture without asking the native Agent to replay its persisted history. */
84421
+ startExecutionOnlyCapture = () => {
84422
+ if (this.#sessionLoad.kind !== "not-requested") {
84423
+ return;
84424
+ }
84425
+ this.#sessionLoad = {
84426
+ history: new HistoryReplayCapture(this.#store.events, this.#mobiusSessionId),
84427
+ kind: "execution-only"
84428
+ };
84429
+ };
84101
84430
  loadHistoryEvents = async (eventLimit, cursor, signal) => {
84102
84431
  if (!this.#capabilities.loadSession) {
84103
84432
  throw new AcpExecutionError(
@@ -84348,7 +84677,7 @@ var AgentSession = class _AgentSession {
84348
84677
  response: {}
84349
84678
  };
84350
84679
  }
84351
- if (this.#capabilities.loadSession) {
84680
+ if (this.#capabilities.loadSession && this.#sessionLoad.kind !== "execution-only") {
84352
84681
  await this.#completedHistory(options.signal, "ACP agent exited while loading history before a prompt");
84353
84682
  }
84354
84683
  const prompt = this.buildPrompt(options);
@@ -84547,14 +84876,14 @@ var AgentSession = class _AgentSession {
84547
84876
  onElicitationComplete = (notification) => this.#interactions.onElicitationComplete(notification);
84548
84877
  onUpdate = (notification) => this.#updates.receive(notification);
84549
84878
  recordControlEvent = async (event) => {
84550
- if (this.#sessionLoad.kind !== "loaded") {
84879
+ if (this.#sessionLoad.kind !== "loaded" && this.#sessionLoad.kind !== "execution-only") {
84551
84880
  return false;
84552
84881
  }
84553
84882
  await this.recordLifecycleEvent(event);
84554
84883
  return true;
84555
84884
  };
84556
84885
  recordLifecycleEvent = async (event) => {
84557
- if (event.type !== "lifecycle-metadata" || this.#sessionLoad.kind !== "loaded") {
84886
+ if (event.type !== "lifecycle-metadata" || this.#sessionLoad.kind !== "loaded" && this.#sessionLoad.kind !== "execution-only") {
84558
84887
  return;
84559
84888
  }
84560
84889
  const liveEvent2 = this.#sessionLoad.history.recordLifecycle(event);
@@ -84575,7 +84904,7 @@ var AgentSession = class _AgentSession {
84575
84904
  const rawUpdate = decodeAcpSessionUpdate(JSON.stringify(externalized.event));
84576
84905
  if (this.#sessionLoad.kind === "capturing") {
84577
84906
  await this.#sessionLoad.history.record(update, rawUpdate);
84578
- } else if (this.#sessionLoad.kind === "loaded") {
84907
+ } else if (this.#sessionLoad.kind === "loaded" || this.#sessionLoad.kind === "execution-only") {
84579
84908
  const onSessionEvent = this.#getObserver().onSessionEvent;
84580
84909
  await this.#sessionLoad.history.record(update, rawUpdate, async (liveEvent2) => {
84581
84910
  await onSessionEvent?.(liveEvent2);
@@ -85493,13 +85822,14 @@ var SessionManager2 = class {
85493
85822
  }
85494
85823
  async #replaceExecutionPreparation(existing, options) {
85495
85824
  const desiredConfigOptions = existing.prepared().configOptions;
85825
+ const recovery = options.recovery ?? { kind: "interactive-only" };
85496
85826
  return await this.#executionProcesses.prepare(options.sessionId, async () => {
85497
85827
  await this.#retireSessionProcess(options.sessionId, existing, async () => await existing.close());
85498
85828
  const runtime = await this.#startRetainedSessionProcess(
85499
85829
  options,
85500
85830
  async () => await AgentSession.start(
85501
85831
  options,
85502
- { kind: "interactive-only" },
85832
+ recovery,
85503
85833
  () => this.#observer,
85504
85834
  this.#store,
85505
85835
  unmeasuredOperationMetrics,
@@ -85677,6 +86007,7 @@ var SessionManager2 = class {
85677
86007
  });
85678
86008
  });
85679
86009
  prepareExecutionSession = async (options) => this.#operations.run(options, async (options2) => {
86010
+ const recovery = options2.recovery ?? { kind: "interactive-only" };
85680
86011
  this.#assertOpen(options2.sessionId);
85681
86012
  const pending = this.#executionProcesses.pending(options2.sessionId);
85682
86013
  if (pending !== void 0) {
@@ -85685,7 +86016,7 @@ var SessionManager2 = class {
85685
86016
  }
85686
86017
  const existing = this.#executionProcesses.get(options2.sessionId);
85687
86018
  if (existing === void 0) {
85688
- return (await this.#startPreparation(options2, { kind: "interactive-only" })).prepared();
86019
+ return (await this.#startPreparation(options2, recovery)).prepared();
85689
86020
  }
85690
86021
  if (!existing.belongsTo(options2.agent.id, options2.cwd, options2.additionalDirectories)) {
85691
86022
  throw new AcpExecutionError(
@@ -85695,6 +86026,9 @@ var SessionManager2 = class {
85695
86026
  );
85696
86027
  }
85697
86028
  if (existing.usesEnvironment(options2.agent.environment)) {
86029
+ if (recovery.kind === "execution-only") {
86030
+ existing.startExecutionOnlyCapture();
86031
+ }
85698
86032
  this.#executionProcesses.touch(options2.sessionId, existing);
85699
86033
  return existing.prepared();
85700
86034
  }
@@ -85989,10 +86323,13 @@ var SessionManager2 = class {
85989
86323
  "session_identity_mismatch"
85990
86324
  );
85991
86325
  }
86326
+ if (options2.recovery?.kind === "execution-only") {
86327
+ existing.startExecutionOnlyCapture();
86328
+ }
85992
86329
  this.#executionProcesses.touch(options2.sessionId, existing);
85993
86330
  return existing;
85994
86331
  }
85995
- await this.prepareSession(options2);
86332
+ await this.#prepareSession(options2, options2.recovery ?? { kind: "interactive-only" }, "reopen");
85996
86333
  const started = this.#executionProcesses.get(options2.sessionId);
85997
86334
  if (started === void 0) {
85998
86335
  throw new AcpExecutionError("ACP session did not start");
@@ -86595,6 +86932,13 @@ var PromptLifecycle = class {
86595
86932
  }
86596
86933
  this.#queued.delete(commandId);
86597
86934
  };
86935
+ replayOutcome = async (sessionId, event) => {
86936
+ if (event.type !== "lifecycle-metadata" || !("commandId" in event.payload)) {
86937
+ return;
86938
+ }
86939
+ await this.#recordLifecycle(sessionId, event);
86940
+ this.publishTerminal(sessionId, event.payload.commandId);
86941
+ };
86598
86942
  queueChanged = async (sessionId) => {
86599
86943
  await this.#recordLifecycle(sessionId, {
86600
86944
  type: "lifecycle-metadata",
@@ -86628,7 +86972,7 @@ var PromptLifecycle = class {
86628
86972
  type: "lifecycle-metadata",
86629
86973
  payload: { kind: "prompt.failed-before-start", commandId, code, message }
86630
86974
  });
86631
- if (!recorded) {
86975
+ if (!recorded || code === SESSION_CLOSED_ERROR_CODE) {
86632
86976
  this.#sink.publishOperationFailure(sessionId, commandId, code, message);
86633
86977
  }
86634
86978
  };
@@ -86665,18 +87009,149 @@ function isTerminal(state) {
86665
87009
  }
86666
87010
  var ExecutionReceipts = class {
86667
87011
  #executions = /* @__PURE__ */ new Map();
87012
+ #recoveredCommands = /* @__PURE__ */ new Set();
86668
87013
  #terminalCountBySession = /* @__PURE__ */ new Map();
87014
+ #table;
87015
+ #closing = false;
87016
+ #activeCount = 0;
87017
+ #onCount;
87018
+ constructor(table, onCount = () => void 0) {
87019
+ this.#onCount = onCount;
87020
+ this.#table = table;
87021
+ for (const row of table?.list() ?? []) {
87022
+ this.#executions.set(row.commandId, row);
87023
+ this.#recoveredCommands.add(row.commandId);
87024
+ if (!isTerminal(row.state)) {
87025
+ this.#activeCount += 1;
87026
+ }
87027
+ if (isTerminal(row.state)) {
87028
+ this.#terminalCountBySession.set(
87029
+ row.sessionId,
87030
+ (this.#terminalCountBySession.get(row.sessionId) ?? 0) + 1
87031
+ );
87032
+ }
87033
+ }
87034
+ }
87035
+ #save = (commandId, receipt) => {
87036
+ if (!this.#closing) {
87037
+ this.#table?.save({ ...receipt, commandId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
87038
+ }
87039
+ const previous = this.#executions.get(commandId);
87040
+ const wasActive = previous !== void 0 && !isTerminal(previous.state);
87041
+ if (isTerminal(receipt.state)) {
87042
+ this.#executions.delete(commandId);
87043
+ }
87044
+ this.#executions.set(commandId, receipt);
87045
+ this.#activeCount += Number(!isTerminal(receipt.state)) - Number(wasActive);
87046
+ this.#onCount(this.#activeCount);
87047
+ if (isTerminal(receipt.state) && (previous === void 0 || !isTerminal(previous.state))) {
87048
+ this.#terminalCountBySession.set(
87049
+ receipt.sessionId,
87050
+ (this.#terminalCountBySession.get(receipt.sessionId) ?? 0) + 1
87051
+ );
87052
+ this.pruneTerminalReceipts(receipt.sessionId);
87053
+ }
87054
+ };
87055
+ beginShutdown = () => {
87056
+ this.#closing = true;
87057
+ };
87058
+ outcome = (commandId) => {
87059
+ const receipt = this.#executions.get(commandId);
87060
+ return receipt?.outcome === null || receipt === void 0 ? void 0 : sessionEventPayloadSchema.parse(JSON.parse(receipt.outcome));
87061
+ };
87062
+ recordOutcome = (event) => {
87063
+ if (event.type !== "lifecycle-metadata") {
87064
+ return;
87065
+ }
87066
+ const payload = event.payload;
87067
+ if (payload.kind !== "prompt.completed" && payload.kind !== "prompt.failed" && payload.kind !== "prompt.failed-before-start") {
87068
+ return;
87069
+ }
87070
+ const receipt = this.#executions.get(payload.commandId);
87071
+ if (receipt === void 0) {
87072
+ return;
87073
+ }
87074
+ const sanitized = payload.kind === "prompt.completed" ? payload : { ...payload, message: "The local Agent could not complete the request" };
87075
+ this.#save(payload.commandId, {
87076
+ ...receipt,
87077
+ state: payload.kind === "prompt.completed" ? payload.stopReason === "cancelled" ? "cancelled" : "succeeded" : "failed",
87078
+ outcome: JSON.stringify({ type: "lifecycle-metadata", payload: sanitized, recordedAt: event.recordedAt })
87079
+ });
87080
+ };
87081
+ recover = () => {
87082
+ const recovered = [];
87083
+ for (const [commandId, receipt] of [...this.#executions]) {
87084
+ const event = receipt.outcome === null ? {
87085
+ type: "lifecycle-metadata",
87086
+ payload: receipt.started ? {
87087
+ kind: "prompt.failed",
87088
+ commandId,
87089
+ attemptId: receipt.attemptId,
87090
+ code: "runtime_restarted",
87091
+ message: "The local runtime restarted while this request was running"
87092
+ } : {
87093
+ kind: "prompt.failed-before-start",
87094
+ commandId,
87095
+ code: "runtime_restarted",
87096
+ message: "The local runtime restarted before this request started"
87097
+ }
87098
+ } : sessionEventPayloadSchema.parse(JSON.parse(receipt.outcome));
87099
+ const recordedAt = receipt.outcome === null ? (/* @__PURE__ */ new Date()).toISOString() : external_exports.object({ recordedAt: external_exports.string() }).parse(JSON.parse(receipt.outcome)).recordedAt;
87100
+ recovered.push({ sessionId: receipt.sessionId, event, recordedAt });
87101
+ if (!isTerminal(receipt.state)) {
87102
+ const state = event.type === "lifecycle-metadata" && event.payload.kind === "prompt.completed" ? event.payload.stopReason === "cancelled" ? "cancelled" : "succeeded" : "failed";
87103
+ this.#save(commandId, { ...receipt, state, outcome: JSON.stringify({ ...event, recordedAt }) });
87104
+ }
87105
+ }
87106
+ return recovered;
87107
+ };
87108
+ terminalOutcomes = (sessionId) => [...this.#executions.values()].flatMap((receipt) => {
87109
+ if (receipt.sessionId !== sessionId || receipt.outcome === null) {
87110
+ return [];
87111
+ }
87112
+ const decoded = JSON.parse(receipt.outcome);
87113
+ return [
87114
+ {
87115
+ event: sessionEventPayloadSchema.parse(decoded),
87116
+ recordedAt: external_exports.object({ recordedAt: external_exports.string() }).parse(decoded).recordedAt
87117
+ }
87118
+ ];
87119
+ });
86669
87120
  claimExecution = (commandId, sessionId, commandJson) => {
86670
87121
  const commandHash = createHash6("sha256").update(commandJson).digest("hex");
86671
- const existing = this.#executions.get(commandId);
87122
+ let existing = this.#executions.get(commandId);
87123
+ const previous = this.outcome(commandId);
87124
+ if (existing?.sessionId === sessionId && !existing.started && previous?.type === "lifecycle-metadata" && previous.payload.kind === "prompt.failed-before-start" && previous.payload.code === "runtime_restarted") {
87125
+ this.#executions.delete(commandId);
87126
+ this.#recoveredCommands.delete(commandId);
87127
+ this.#table?.remove(commandId);
87128
+ this.#terminalCountBySession.set(
87129
+ sessionId,
87130
+ Math.max(0, (this.#terminalCountBySession.get(sessionId) ?? 1) - 1)
87131
+ );
87132
+ existing = void 0;
87133
+ }
86672
87134
  if (existing !== void 0) {
86673
87135
  if (existing.sessionId !== sessionId || existing.commandHash !== commandHash) {
86674
87136
  return { kind: "conflict" };
86675
87137
  }
86676
- return { attemptId: existing.attemptId, commandId, kind: "duplicate", state: existing.state };
87138
+ return {
87139
+ attemptId: existing.attemptId,
87140
+ commandId,
87141
+ kind: "duplicate",
87142
+ state: existing.state,
87143
+ outcome: this.#recoveredCommands.has(commandId) ? this.outcome(commandId) : void 0
87144
+ };
86677
87145
  }
86678
87146
  const attemptId = randomUUID13();
86679
- this.#executions.set(commandId, { attemptId, commandHash, sessionId, state: "dispatching" });
87147
+ this.#save(commandId, {
87148
+ attemptId,
87149
+ commandHash,
87150
+ sessionId,
87151
+ state: "dispatching",
87152
+ started: false,
87153
+ outcome: null
87154
+ });
86680
87155
  return { attemptId, commandId, kind: "accepted", state: "dispatching" };
86681
87156
  };
86682
87157
  transitionExecution = (commandId, state) => {
@@ -86691,16 +87166,13 @@ var ExecutionReceipts = class {
86691
87166
  if (!allowed) {
86692
87167
  throw new Error("The runtime execution transition is invalid");
86693
87168
  }
86694
- this.#executions.set(commandId, { ...current, state });
86695
- if (isTerminal(state)) {
86696
- this.#terminalCountBySession.set(
86697
- current.sessionId,
86698
- (this.#terminalCountBySession.get(current.sessionId) ?? 0) + 1
86699
- );
86700
- this.pruneTerminalReceipts(current.sessionId);
87169
+ if (state === "running") {
87170
+ this.#save(commandId, { ...current, state, started: true });
87171
+ } else if (isTerminal(state)) {
87172
+ this.#save(commandId, { ...current, state });
86701
87173
  }
86702
87174
  };
86703
- /** Receipts disappear when the runtime process exits. */
87175
+ /** Only commands without a terminal result can still be cancelled. */
86704
87176
  findUnsettledAttemptId = (commandId, sessionId) => {
86705
87177
  const receipt = this.#executions.get(commandId);
86706
87178
  return receipt?.sessionId === sessionId && !isTerminal(receipt.state) ? receipt.attemptId : null;
@@ -86711,7 +87183,9 @@ var ExecutionReceipts = class {
86711
87183
  };
86712
87184
  close = () => {
86713
87185
  this.#executions.clear();
87186
+ this.#recoveredCommands.clear();
86714
87187
  this.#terminalCountBySession.clear();
87188
+ this.#activeCount = 0;
86715
87189
  };
86716
87190
  pruneTerminalReceipts = (sessionId) => {
86717
87191
  if ((this.#terminalCountBySession.get(sessionId) ?? 0) <= MAX_TERMINAL_RECEIPTS) {
@@ -86720,6 +87194,8 @@ var ExecutionReceipts = class {
86720
87194
  for (const [commandId, receipt] of this.#executions) {
86721
87195
  if (receipt.sessionId === sessionId && isTerminal(receipt.state)) {
86722
87196
  this.#executions.delete(commandId);
87197
+ this.#recoveredCommands.delete(commandId);
87198
+ this.#table?.remove(commandId);
86723
87199
  const remaining = (this.#terminalCountBySession.get(sessionId) ?? 1) - 1;
86724
87200
  if (remaining > 0) {
86725
87201
  this.#terminalCountBySession.set(sessionId, remaining);
@@ -87247,10 +87723,14 @@ function buildSessionLayer(options) {
87247
87723
  const databases = channelSessionDatabasePaths(stateDirectory, channelId);
87248
87724
  const sessionDatabase = openAcpSessionDatabase(databases.sessions);
87249
87725
  let events;
87726
+ let operations;
87250
87727
  try {
87728
+ operations = new ExecutionReceipts(new ExecutionReceiptTable(sessionDatabase), options.onExecutionCountChange);
87251
87729
  events = new CachedSessionEventStore(
87252
87730
  new SessionEventCacheTable(openSessionEventCacheDatabase(databases.events)),
87253
- new SqliteSessionLiveEventPosition(new LiveEventPositionTable(sessionDatabase))
87731
+ new SqliteSessionLiveEventPosition(new LiveEventPositionTable(sessionDatabase)),
87732
+ void 0,
87733
+ (sessionId) => operations.terminalOutcomes(sessionId)
87254
87734
  );
87255
87735
  } catch (error62) {
87256
87736
  sessionDatabase.close();
@@ -87258,11 +87738,13 @@ function buildSessionLayer(options) {
87258
87738
  }
87259
87739
  const sessionStore = new SqliteSessionStore(new AcpSessionTable(sessionDatabase), events);
87260
87740
  const externalContent = new LocalExternalContentStore(stateDirectory);
87261
- const operations = new ExecutionReceipts();
87262
87741
  const activeSessions = new SessionTurns(operations, options.turnActivity);
87263
87742
  const directories = new WorkspaceDirectoryService(bindingId, defaultWorkspace);
87264
87743
  const manager = new SessionManager2({
87265
- observer: new SessionLayerObserver((event) => eventProjection.record(event), sink.publishInfo),
87744
+ observer: new SessionLayerObserver((event) => {
87745
+ operations.recordOutcome(event);
87746
+ eventProjection.record(event);
87747
+ }, sink.publishInfo),
87266
87748
  store: sessionStore,
87267
87749
  adapters: harnessAdapters,
87268
87750
  sessionIdentity: "native",
@@ -87283,7 +87765,15 @@ function buildSessionLayer(options) {
87283
87765
  });
87284
87766
  const eventProjection = new PromptLifecycle(
87285
87767
  sink,
87286
- (sessionId, event) => manager.recordLifecycleEvent(sessionId, event),
87768
+ async (sessionId, event) => {
87769
+ if (await manager.recordLifecycleEvent(sessionId, event)) {
87770
+ return true;
87771
+ }
87772
+ const recorded = events.append(sessionId, event);
87773
+ operations.recordOutcome(recorded);
87774
+ eventProjection.record(recorded);
87775
+ return true;
87776
+ },
87287
87777
  activeSessions.recordSessionEvent
87288
87778
  );
87289
87779
  const nativeSessions = new NativeSessionService(
@@ -87306,7 +87796,13 @@ function buildSessionLayer(options) {
87306
87796
  );
87307
87797
  })
87308
87798
  );
87799
+ const recoveredEvents = operations.recover().map(({ sessionId, event, recordedAt }) => events.append(sessionId, event, recordedAt));
87309
87800
  return {
87801
+ publishRecoveredOutcomes: () => {
87802
+ for (const event of recoveredEvents) {
87803
+ sink.publish(event);
87804
+ }
87805
+ },
87310
87806
  activeSessions,
87311
87807
  directories,
87312
87808
  eventProjection,
@@ -89103,12 +89599,14 @@ async function executePrompt({
89103
89599
  const appliedSettings = command.sessionConfigurations === void 0 ? void 0 : agentForSessionConfiguration(agent, command.sessionConfigurations);
89104
89600
  const executionAgent = appliedSettings?.agent ?? agent;
89105
89601
  const sessionConfigurations = appliedSettings?.sessionConfigurations;
89602
+ const recovery = command.historyPolicy === SessionHistoryPolicy.ExecutionOnly ? { kind: "execution-only" } : { kind: "interactive-only" };
89106
89603
  const preparation = {
89107
89604
  additionalDirectories: resolved.additionalDirectories,
89108
89605
  agent: executionAgent,
89109
89606
  cwd: resolved.cwd,
89110
89607
  sessionId: command.sessionId,
89111
- signal
89608
+ signal,
89609
+ recovery
89112
89610
  };
89113
89611
  let prepared = await sessions.prepareExecutionSession(preparation);
89114
89612
  if (command.sessionConfigurations !== void 0) {
@@ -89177,7 +89675,8 @@ async function executePrompt({
89177
89675
  resources: attachments.resources,
89178
89676
  sessionId: command.sessionId,
89179
89677
  signal,
89180
- targetCommandId: command.targetCommandId
89678
+ targetCommandId: command.targetCommandId,
89679
+ recovery
89181
89680
  });
89182
89681
  stopReason = result.stopReason;
89183
89682
  }
@@ -89346,6 +89845,12 @@ var sessionPromptCommand = {
89346
89845
  return;
89347
89846
  }
89348
89847
  if (receipt.kind === "duplicate") {
89848
+ const outcome = receipt.outcome;
89849
+ if (outcome !== void 0) {
89850
+ void context.eventProjection.replayOutcome(message.sessionId, outcome).catch((cause) => {
89851
+ console.error("Could not replay the persisted execution outcome", cause);
89852
+ });
89853
+ }
89349
89854
  return;
89350
89855
  }
89351
89856
  const turn = context.sessionTurns.start(message, receipt.attemptId);
@@ -90471,7 +90976,8 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
90471
90976
  resolveAgent: (agentId, resolveSignal) => resolveCatalogAgent(config2, agentId, resolveSignal),
90472
90977
  sink: publisher,
90473
90978
  stateDirectory: config2.localContext.stateDirectory,
90474
- timer
90979
+ timer,
90980
+ onExecutionCountChange: options.onExecutionCountChange ?? (() => void 0)
90475
90981
  });
90476
90982
  const {
90477
90983
  activeSessions,
@@ -90541,6 +91047,7 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
90541
91047
  reportUpgradeAvailability,
90542
91048
  diagnostics,
90543
91049
  onConnected: () => {
91050
+ layer.publishRecoveredOutcomes();
90544
91051
  if (catalogPublished) {
90545
91052
  options.onConnectionStatus?.("online");
90546
91053
  }
@@ -90563,6 +91070,7 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
90563
91070
  }
90564
91071
  } finally {
90565
91072
  shutdown.abort();
91073
+ operations.beginShutdown();
90566
91074
  await activeSessions.close();
90567
91075
  try {
90568
91076
  await catalogPublication;
@@ -90699,6 +91207,10 @@ async function createMobiusRuntimeEngine(connection, signal, environment = proce
90699
91207
  var MobiusRuntimeEngine = class {
90700
91208
  #config;
90701
91209
  #options;
91210
+ #activeTurnCount = 0;
91211
+ get activeTurnCount() {
91212
+ return this.#activeTurnCount;
91213
+ }
90702
91214
  #state = new IdleMobiusRuntimeState();
90703
91215
  constructor(config2, options = {}) {
90704
91216
  validateRuntimeConfiguration(config2);
@@ -90714,11 +91226,15 @@ var MobiusRuntimeEngine = class {
90714
91226
  try {
90715
91227
  await runRuntime(this.#config, signal, void 0, {
90716
91228
  ...this.#options.turnActivity === void 0 ? {} : { turnActivity: this.#options.turnActivity },
91229
+ onExecutionCountChange: (count) => {
91230
+ this.#activeTurnCount = count;
91231
+ },
90717
91232
  ...this.#options.onConnectionStatus === void 0 ? {} : { onConnectionStatus: this.#options.onConnectionStatus },
90718
91233
  ...this.#options.reportUpgradeAvailability === void 0 ? {} : { reportUpgradeAvailability: this.#options.reportUpgradeAvailability },
90719
91234
  ...this.#options.processLogsDirectory === void 0 ? {} : { processLogsDirectory: this.#options.processLogsDirectory }
90720
91235
  });
90721
91236
  } finally {
91237
+ this.#activeTurnCount = 0;
90722
91238
  this.#state = new FinishedMobiusRuntimeState();
90723
91239
  }
90724
91240
  };