@moxt-ai/mobius 0.0.300 → 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 (80) 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/pi/endpoint.d.ts.map +1 -1
  9. package/dist/acp/harness/pi/endpoint.js +1 -0
  10. package/dist/acp/harness/pi/endpoint.js.map +1 -1
  11. package/dist/cli.js +590 -60
  12. package/dist/cli.js.map +4 -4
  13. package/dist/generated/acp-session-store-migrations.d.ts +3 -0
  14. package/dist/generated/acp-session-store-migrations.d.ts.map +1 -1
  15. package/dist/generated/acp-session-store-migrations.js +5 -1
  16. package/dist/generated/acp-session-store-migrations.js.map +1 -1
  17. package/dist/runtime/command/registry/prompt.d.ts.map +1 -1
  18. package/dist/runtime/command/registry/prompt.js +6 -0
  19. package/dist/runtime/command/registry/prompt.js.map +1 -1
  20. package/dist/runtime/engine.d.ts +1 -0
  21. package/dist/runtime/engine.d.ts.map +1 -1
  22. package/dist/runtime/engine.js +8 -0
  23. package/dist/runtime/engine.js.map +1 -1
  24. package/dist/runtime/run.d.ts +1 -0
  25. package/dist/runtime/run.d.ts.map +1 -1
  26. package/dist/runtime/run.js +3 -0
  27. package/dist/runtime/run.js.map +1 -1
  28. package/dist/runtime.js +555 -53
  29. package/dist/runtime.js.map +4 -4
  30. package/dist/schema/acp-session-store/execution-receipt.d.ts +136 -0
  31. package/dist/schema/acp-session-store/execution-receipt.d.ts.map +1 -0
  32. package/dist/schema/acp-session-store/execution-receipt.js +12 -0
  33. package/dist/schema/acp-session-store/execution-receipt.js.map +1 -0
  34. package/dist/schema/acp-session-store/index.d.ts +1 -0
  35. package/dist/schema/acp-session-store/index.d.ts.map +1 -1
  36. package/dist/schema/acp-session-store/index.js +1 -0
  37. package/dist/schema/acp-session-store/index.js.map +1 -1
  38. package/dist/session/api/session-api.d.ts +4 -0
  39. package/dist/session/api/session-api.d.ts.map +1 -1
  40. package/dist/session/api/session-api.js.map +1 -1
  41. package/dist/session/api/session-layer.d.ts +2 -0
  42. package/dist/session/api/session-layer.d.ts.map +1 -1
  43. package/dist/session/api/session-layer.js +25 -4
  44. package/dist/session/api/session-layer.js.map +1 -1
  45. package/dist/session/history/session-event-store.d.ts +7 -4
  46. package/dist/session/history/session-event-store.d.ts.map +1 -1
  47. package/dist/session/history/session-event-store.js +13 -7
  48. package/dist/session/history/session-event-store.js.map +1 -1
  49. package/dist/session/history/session-history.d.ts +1 -4
  50. package/dist/session/history/session-history.d.ts.map +1 -1
  51. package/dist/session/history/session-history.js +1 -4
  52. package/dist/session/history/session-history.js.map +1 -1
  53. package/dist/session/live/agent-session-restore.d.ts +1 -0
  54. package/dist/session/live/agent-session-restore.d.ts.map +1 -1
  55. package/dist/session/live/agent-session.d.ts +2 -0
  56. package/dist/session/live/agent-session.d.ts.map +1 -1
  57. package/dist/session/live/agent-session.js +15 -4
  58. package/dist/session/live/agent-session.js.map +1 -1
  59. package/dist/session/live/session-restore.d.ts.map +1 -1
  60. package/dist/session/live/session-restore.js +37 -0
  61. package/dist/session/live/session-restore.js.map +1 -1
  62. package/dist/session/manager/session-manager.d.ts.map +1 -1
  63. package/dist/session/manager/session-manager.js +11 -3
  64. package/dist/session/manager/session-manager.js.map +1 -1
  65. package/dist/session/prompt/execute-prompt.d.ts.map +1 -1
  66. package/dist/session/prompt/execute-prompt.js +6 -1
  67. package/dist/session/prompt/execute-prompt.js.map +1 -1
  68. package/dist/session/prompt/prompt-lifecycle.d.ts +1 -0
  69. package/dist/session/prompt/prompt-lifecycle.d.ts.map +1 -1
  70. package/dist/session/prompt/prompt-lifecycle.js +9 -1
  71. package/dist/session/prompt/prompt-lifecycle.js.map +1 -1
  72. package/dist/session/turn/execution-receipts.d.ts +23 -2
  73. package/dist/session/turn/execution-receipts.d.ts.map +1 -1
  74. package/dist/session/turn/execution-receipts.js +161 -10
  75. package/dist/session/turn/execution-receipts.js.map +1 -1
  76. package/dist/storage/session/execution-receipt-table.d.ts +45 -0
  77. package/dist/storage/session/execution-receipt-table.d.ts.map +1 -0
  78. package/dist/storage/session/execution-receipt-table.js +64 -0
  79. package/dist/storage/session/execution-receipt-table.js.map +1 -0
  80. 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.300",
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") {
@@ -69225,7 +69327,7 @@ import { basename as basename4, dirname as dirname7, isAbsolute as isAbsolute11,
69225
69327
  import { RequestError as RequestError7 } from "@agentclientprotocol/sdk";
69226
69328
 
69227
69329
  // src/acp/harness/pi/upstream/dist/pi-rpc/process.js
69228
- import { spawn as spawn4 } from "node:child_process";
69330
+ import { spawn as spawn5 } from "node:child_process";
69229
69331
  import * as readline from "node:readline";
69230
69332
 
69231
69333
  // src/acp/harness/pi/upstream/dist/pi-rpc/command.js
@@ -69342,7 +69444,7 @@ var PiRpcProcess = class _PiRpcProcess {
69342
69444
  args.push("--fork", params.origin.sessionPath);
69343
69445
  break;
69344
69446
  }
69345
- const child = spawn4(cmd, args, {
69447
+ const child = spawn5(cmd, args, {
69346
69448
  cwd: params.cwd,
69347
69449
  stdio: "pipe",
69348
69450
  env: params.environment,
@@ -72342,6 +72444,77 @@ ${JSON.stringify(stats, null, 2)}`;
72342
72444
  }, 0);
72343
72445
  return { sessionId: session.sessionId, configOptions, modes };
72344
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
+ }
72345
72518
  async loadSession(params) {
72346
72519
  if (!isAbsolute11(params.cwd)) {
72347
72520
  throw RequestError7.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
@@ -72925,6 +73098,9 @@ var PiAcpEndpoint = class {
72925
73098
  ).onRequest(
72926
73099
  methods5.agent.session.load,
72927
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)
72928
73104
  ).onRequest(
72929
73105
  methods5.agent.session.list,
72930
73106
  async (context) => await piAgentOrThrow(piAgent).listSessions(context.params)
@@ -73204,7 +73380,7 @@ var LocalAgentInventory = class {
73204
73380
  };
73205
73381
 
73206
73382
  // src/agent/discovery/acp-readiness-probe.ts
73207
- import { spawn as spawn5 } from "node:child_process";
73383
+ import { spawn as spawn6 } from "node:child_process";
73208
73384
  import {
73209
73385
  methods as methods8,
73210
73386
  ndJsonStream as ndJsonStream2,
@@ -73395,7 +73571,7 @@ async function probeAcpReadiness(specification, agent, environment, defaultWorks
73395
73571
  }
73396
73572
  }
73397
73573
  const launch = executableLaunch2(agent.adapterPath, agent.arguments);
73398
- const child = spawn5(launch.command, [...launch.arguments], {
73574
+ const child = spawn6(launch.command, [...launch.arguments], {
73399
73575
  cwd: defaultWorkspace,
73400
73576
  ...platformSpawnOptions,
73401
73577
  env: { ...environment, ...agent.environment },
@@ -75008,7 +75184,7 @@ var ClaudeAdapter = class {
75008
75184
  };
75009
75185
 
75010
75186
  // src/acp/native-history/codex/codex-history.ts
75011
- import { spawn as spawn6 } from "node:child_process";
75187
+ import { spawn as spawn7 } from "node:child_process";
75012
75188
 
75013
75189
  // src/acp/protocol/native-session.ts
75014
75190
  var path6 = external_exports.string().min(1).max(2048).refine((value) => !value.includes("\0"));
@@ -75705,7 +75881,7 @@ async function readCodexHistoryWithOptions(agent, cwd, nativeSessionId, signal,
75705
75881
  }
75706
75882
  signal.throwIfAborted();
75707
75883
  const launch = executableLaunch2(agent.agentIdentity.realPath, ["app-server"]);
75708
- const child = spawn6(launch.command, [...launch.arguments], {
75884
+ const child = spawn7(launch.command, [...launch.arguments], {
75709
75885
  cwd,
75710
75886
  env: { ...process.env, ...agent.environment },
75711
75887
  ...platformSpawnOptions,
@@ -81046,6 +81222,18 @@ var acpSession = sqliteTable("acp_session", {
81046
81222
  updatedAt: text("updated_at").notNull()
81047
81223
  });
81048
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
+
81049
81237
  // src/schema/acp-session-store/session-live-event-position-allocation.ts
81050
81238
  var sessionLiveEventPositionAllocation = sqliteTable("session_live_event_position_allocation", {
81051
81239
  allocatedPosition: integer2("allocated_position").notNull().default(1e4),
@@ -81087,6 +81275,62 @@ var AcpSessionTable = class {
81087
81275
  };
81088
81276
  };
81089
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
+
81090
81334
  // src/storage/session/live-event-position-table.ts
81091
81335
  var allocationRowSchema = external_exports.object({ allocatedPosition: external_exports.number().int().safe().nonnegative() });
81092
81336
  var LiveEventPositionTable = class {
@@ -81306,6 +81550,10 @@ var acpSessionStoreMigrations = [
81306
81550
  {
81307
81551
  "name": "0006_live_event_position_allocation.sql",
81308
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"
81309
81557
  }
81310
81558
  ];
81311
81559
 
@@ -81960,12 +82208,12 @@ var SessionEvents = class {
81960
82208
  }
81961
82209
  };
81962
82210
  };
81963
- function liveEvent(sessionId, sessionGenerationId, eventId, position, event) {
82211
+ function liveEvent(sessionId, sessionGenerationId, eventId, position, event, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
81964
82212
  return {
81965
82213
  eventId,
81966
82214
  ...event,
81967
82215
  position: position.toString(),
81968
- recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
82216
+ recordedAt,
81969
82217
  sessionGenerationId,
81970
82218
  sessionId
81971
82219
  };
@@ -82003,7 +82251,7 @@ var MemorySessionEventStore = class {
82003
82251
  }
82004
82252
  return resolved;
82005
82253
  };
82006
- append = (sessionId, event) => {
82254
+ append = (sessionId, event, recordedAt) => {
82007
82255
  const session = this.#session(sessionId);
82008
82256
  const allocatedPosition = this.#allocatedPositionBySession.get(sessionId) ?? 9999;
82009
82257
  if (allocatedPosition === Number.MAX_SAFE_INTEGER) {
@@ -82016,7 +82264,8 @@ var MemorySessionEventStore = class {
82016
82264
  session.sessionGenerationId,
82017
82265
  this.#eventIds.allocate(sessionId, session.sessionGenerationId),
82018
82266
  position,
82019
- event
82267
+ event,
82268
+ recordedAt
82020
82269
  );
82021
82270
  session.apply(recorded, this.#resolver(session));
82022
82271
  return recorded;
@@ -82057,12 +82306,14 @@ var MemorySessionEventStore = class {
82057
82306
  };
82058
82307
  };
82059
82308
  var CachedSessionEventStore = class {
82309
+ #terminalOutcomes;
82060
82310
  #cacheTable;
82061
82311
  #eventIds;
82062
82312
  #liveEventPositions;
82063
82313
  #sessions = /* @__PURE__ */ new Map();
82064
82314
  #flushSchedule = { kind: "idle" };
82065
- constructor(cacheTable, liveEventPositions, eventIds = new InMemorySessionEventIdAllocator()) {
82315
+ constructor(cacheTable, liveEventPositions, eventIds = new InMemorySessionEventIdAllocator(), terminalOutcomes = () => []) {
82316
+ this.#terminalOutcomes = terminalOutcomes;
82066
82317
  this.#eventIds = eventIds;
82067
82318
  this.#liveEventPositions = liveEventPositions;
82068
82319
  this.#cacheTable = cacheTable;
@@ -82095,7 +82346,7 @@ var CachedSessionEventStore = class {
82095
82346
  }
82096
82347
  return resolved;
82097
82348
  };
82098
- append = (sessionId, event) => {
82349
+ append = (sessionId, event, recordedAt) => {
82099
82350
  if (this.#flushSchedule.kind === "closed") {
82100
82351
  throw new Error("The V2 Session event store is closed");
82101
82352
  }
@@ -82106,7 +82357,8 @@ var CachedSessionEventStore = class {
82106
82357
  session.sessionGenerationId,
82107
82358
  this.#eventIds.allocate(sessionId, session.sessionGenerationId),
82108
82359
  position,
82109
- event
82360
+ event,
82361
+ recordedAt
82110
82362
  );
82111
82363
  session.apply(recorded, this.#resolver(sessionId, session));
82112
82364
  this.#sessions.set(sessionId, session);
@@ -82195,6 +82447,9 @@ var CachedSessionEventStore = class {
82195
82447
  this.#deletePersisted(sessionId, previous?.sessionGenerationId);
82196
82448
  const sessionGenerationId = randomUUID11();
82197
82449
  this.#sessions.set(sessionId, new SessionEvents(sessionGenerationId));
82450
+ for (const { event, recordedAt } of this.#terminalOutcomes(sessionId)) {
82451
+ this.append(sessionId, event, recordedAt);
82452
+ }
82198
82453
  };
82199
82454
  materializedEventCount = (sessionId) => this.#sessions.get(sessionId)?.materializedCount ?? 0;
82200
82455
  close = () => {
@@ -82308,7 +82563,7 @@ function buildPromptContent(options, capabilities) {
82308
82563
  }
82309
82564
 
82310
82565
  // src/acp/connection/agent-connection.ts
82311
- import { spawn as spawn7 } from "node:child_process";
82566
+ import { spawn as spawn8 } from "node:child_process";
82312
82567
  import { ndJsonStream as ndJsonStream3, RequestError as RequestError9 } from "@agentclientprotocol/sdk";
82313
82568
  function requestErrorMessage(error62) {
82314
82569
  const fallback = truncate(error62.message, 500);
@@ -82418,7 +82673,7 @@ var AgentConnection = class {
82418
82673
  return;
82419
82674
  }
82420
82675
  const launch = executableLaunch2(agent.adapterPath, agent.arguments);
82421
- const child = spawn7(launch.command, [...launch.arguments], {
82676
+ const child = spawn8(launch.command, [...launch.arguments], {
82422
82677
  cwd,
82423
82678
  ...platformSpawnOptions,
82424
82679
  env: { ...process.env, ...agent.environment },
@@ -83692,6 +83947,19 @@ var RunningAgentTurn = class extends AgentTurnState {
83692
83947
 
83693
83948
  // src/session/live/session-restore.ts
83694
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
+ }
83695
83963
  async function restoreSession(session, signal, recovery, origin) {
83696
83964
  if (origin !== void 0) {
83697
83965
  session.setSessionTitle(origin.title);
@@ -83758,6 +84026,9 @@ async function restoreSession(session, signal, recovery, origin) {
83758
84026
  }
83759
84027
  session.setPrompted(true);
83760
84028
  session.setRestored(true);
84029
+ if (recovery.kind === "execution-only") {
84030
+ session.startExecutionOnlyCapture();
84031
+ }
83761
84032
  lifecycleResponse2 = forked;
83762
84033
  } else if (stored instanceof FoundStoredSession && stored.session.agentId === session.agentId && stored.session.cwd === session.cwd && arraysEqual(stored.session.additionalDirectories, session.additionalDirectories)) {
83763
84034
  session.selectNativeSession(stored.session.nativeSessionId);
@@ -83776,6 +84047,37 @@ async function restoreSession(session, signal, recovery, origin) {
83776
84047
  "session-load",
83777
84048
  async () => (await session.loadSession(signal, "ACP agent exited while loading a session")).response
83778
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();
83779
84081
  } else if (session.capabilities.resumeSession) {
83780
84082
  lifecycleResponse2 = await session.metrics.measure(
83781
84083
  "session-resume",
@@ -83835,6 +84137,9 @@ async function restoreSession(session, signal, recovery, origin) {
83835
84137
  );
83836
84138
  session.selectNativeSession(created.sessionId);
83837
84139
  session.setNativeHistoryOwnership({ kind: "fresh-session" });
84140
+ if (recovery.kind === "execution-only") {
84141
+ session.startExecutionOnlyCapture();
84142
+ }
83838
84143
  lifecycleResponse2 = created;
83839
84144
  }
83840
84145
  session.restrictSessionCapabilities(lifecycleResponse2);
@@ -84112,6 +84417,16 @@ var AgentSession = class _AgentSession {
84112
84417
  }
84113
84418
  await this.#completedHistory(signal, "ACP agent exited while loading history");
84114
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
+ };
84115
84430
  loadHistoryEvents = async (eventLimit, cursor, signal) => {
84116
84431
  if (!this.#capabilities.loadSession) {
84117
84432
  throw new AcpExecutionError(
@@ -84362,7 +84677,7 @@ var AgentSession = class _AgentSession {
84362
84677
  response: {}
84363
84678
  };
84364
84679
  }
84365
- if (this.#capabilities.loadSession) {
84680
+ if (this.#capabilities.loadSession && this.#sessionLoad.kind !== "execution-only") {
84366
84681
  await this.#completedHistory(options.signal, "ACP agent exited while loading history before a prompt");
84367
84682
  }
84368
84683
  const prompt = this.buildPrompt(options);
@@ -84561,14 +84876,14 @@ var AgentSession = class _AgentSession {
84561
84876
  onElicitationComplete = (notification) => this.#interactions.onElicitationComplete(notification);
84562
84877
  onUpdate = (notification) => this.#updates.receive(notification);
84563
84878
  recordControlEvent = async (event) => {
84564
- if (this.#sessionLoad.kind !== "loaded") {
84879
+ if (this.#sessionLoad.kind !== "loaded" && this.#sessionLoad.kind !== "execution-only") {
84565
84880
  return false;
84566
84881
  }
84567
84882
  await this.recordLifecycleEvent(event);
84568
84883
  return true;
84569
84884
  };
84570
84885
  recordLifecycleEvent = async (event) => {
84571
- if (event.type !== "lifecycle-metadata" || this.#sessionLoad.kind !== "loaded") {
84886
+ if (event.type !== "lifecycle-metadata" || this.#sessionLoad.kind !== "loaded" && this.#sessionLoad.kind !== "execution-only") {
84572
84887
  return;
84573
84888
  }
84574
84889
  const liveEvent2 = this.#sessionLoad.history.recordLifecycle(event);
@@ -84589,7 +84904,7 @@ var AgentSession = class _AgentSession {
84589
84904
  const rawUpdate = decodeAcpSessionUpdate(JSON.stringify(externalized.event));
84590
84905
  if (this.#sessionLoad.kind === "capturing") {
84591
84906
  await this.#sessionLoad.history.record(update, rawUpdate);
84592
- } else if (this.#sessionLoad.kind === "loaded") {
84907
+ } else if (this.#sessionLoad.kind === "loaded" || this.#sessionLoad.kind === "execution-only") {
84593
84908
  const onSessionEvent = this.#getObserver().onSessionEvent;
84594
84909
  await this.#sessionLoad.history.record(update, rawUpdate, async (liveEvent2) => {
84595
84910
  await onSessionEvent?.(liveEvent2);
@@ -85507,13 +85822,14 @@ var SessionManager2 = class {
85507
85822
  }
85508
85823
  async #replaceExecutionPreparation(existing, options) {
85509
85824
  const desiredConfigOptions = existing.prepared().configOptions;
85825
+ const recovery = options.recovery ?? { kind: "interactive-only" };
85510
85826
  return await this.#executionProcesses.prepare(options.sessionId, async () => {
85511
85827
  await this.#retireSessionProcess(options.sessionId, existing, async () => await existing.close());
85512
85828
  const runtime = await this.#startRetainedSessionProcess(
85513
85829
  options,
85514
85830
  async () => await AgentSession.start(
85515
85831
  options,
85516
- { kind: "interactive-only" },
85832
+ recovery,
85517
85833
  () => this.#observer,
85518
85834
  this.#store,
85519
85835
  unmeasuredOperationMetrics,
@@ -85691,6 +86007,7 @@ var SessionManager2 = class {
85691
86007
  });
85692
86008
  });
85693
86009
  prepareExecutionSession = async (options) => this.#operations.run(options, async (options2) => {
86010
+ const recovery = options2.recovery ?? { kind: "interactive-only" };
85694
86011
  this.#assertOpen(options2.sessionId);
85695
86012
  const pending = this.#executionProcesses.pending(options2.sessionId);
85696
86013
  if (pending !== void 0) {
@@ -85699,7 +86016,7 @@ var SessionManager2 = class {
85699
86016
  }
85700
86017
  const existing = this.#executionProcesses.get(options2.sessionId);
85701
86018
  if (existing === void 0) {
85702
- return (await this.#startPreparation(options2, { kind: "interactive-only" })).prepared();
86019
+ return (await this.#startPreparation(options2, recovery)).prepared();
85703
86020
  }
85704
86021
  if (!existing.belongsTo(options2.agent.id, options2.cwd, options2.additionalDirectories)) {
85705
86022
  throw new AcpExecutionError(
@@ -85709,6 +86026,9 @@ var SessionManager2 = class {
85709
86026
  );
85710
86027
  }
85711
86028
  if (existing.usesEnvironment(options2.agent.environment)) {
86029
+ if (recovery.kind === "execution-only") {
86030
+ existing.startExecutionOnlyCapture();
86031
+ }
85712
86032
  this.#executionProcesses.touch(options2.sessionId, existing);
85713
86033
  return existing.prepared();
85714
86034
  }
@@ -86003,10 +86323,13 @@ var SessionManager2 = class {
86003
86323
  "session_identity_mismatch"
86004
86324
  );
86005
86325
  }
86326
+ if (options2.recovery?.kind === "execution-only") {
86327
+ existing.startExecutionOnlyCapture();
86328
+ }
86006
86329
  this.#executionProcesses.touch(options2.sessionId, existing);
86007
86330
  return existing;
86008
86331
  }
86009
- await this.prepareSession(options2);
86332
+ await this.#prepareSession(options2, options2.recovery ?? { kind: "interactive-only" }, "reopen");
86010
86333
  const started = this.#executionProcesses.get(options2.sessionId);
86011
86334
  if (started === void 0) {
86012
86335
  throw new AcpExecutionError("ACP session did not start");
@@ -86609,6 +86932,13 @@ var PromptLifecycle = class {
86609
86932
  }
86610
86933
  this.#queued.delete(commandId);
86611
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
+ };
86612
86942
  queueChanged = async (sessionId) => {
86613
86943
  await this.#recordLifecycle(sessionId, {
86614
86944
  type: "lifecycle-metadata",
@@ -86642,7 +86972,7 @@ var PromptLifecycle = class {
86642
86972
  type: "lifecycle-metadata",
86643
86973
  payload: { kind: "prompt.failed-before-start", commandId, code, message }
86644
86974
  });
86645
- if (!recorded) {
86975
+ if (!recorded || code === SESSION_CLOSED_ERROR_CODE) {
86646
86976
  this.#sink.publishOperationFailure(sessionId, commandId, code, message);
86647
86977
  }
86648
86978
  };
@@ -86679,18 +87009,149 @@ function isTerminal(state) {
86679
87009
  }
86680
87010
  var ExecutionReceipts = class {
86681
87011
  #executions = /* @__PURE__ */ new Map();
87012
+ #recoveredCommands = /* @__PURE__ */ new Set();
86682
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
+ });
86683
87120
  claimExecution = (commandId, sessionId, commandJson) => {
86684
87121
  const commandHash = createHash6("sha256").update(commandJson).digest("hex");
86685
- 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
+ }
86686
87134
  if (existing !== void 0) {
86687
87135
  if (existing.sessionId !== sessionId || existing.commandHash !== commandHash) {
86688
87136
  return { kind: "conflict" };
86689
87137
  }
86690
- 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
+ };
86691
87145
  }
86692
87146
  const attemptId = randomUUID13();
86693
- 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
+ });
86694
87155
  return { attemptId, commandId, kind: "accepted", state: "dispatching" };
86695
87156
  };
86696
87157
  transitionExecution = (commandId, state) => {
@@ -86705,16 +87166,13 @@ var ExecutionReceipts = class {
86705
87166
  if (!allowed) {
86706
87167
  throw new Error("The runtime execution transition is invalid");
86707
87168
  }
86708
- this.#executions.set(commandId, { ...current, state });
86709
- if (isTerminal(state)) {
86710
- this.#terminalCountBySession.set(
86711
- current.sessionId,
86712
- (this.#terminalCountBySession.get(current.sessionId) ?? 0) + 1
86713
- );
86714
- 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 });
86715
87173
  }
86716
87174
  };
86717
- /** Receipts disappear when the runtime process exits. */
87175
+ /** Only commands without a terminal result can still be cancelled. */
86718
87176
  findUnsettledAttemptId = (commandId, sessionId) => {
86719
87177
  const receipt = this.#executions.get(commandId);
86720
87178
  return receipt?.sessionId === sessionId && !isTerminal(receipt.state) ? receipt.attemptId : null;
@@ -86725,7 +87183,9 @@ var ExecutionReceipts = class {
86725
87183
  };
86726
87184
  close = () => {
86727
87185
  this.#executions.clear();
87186
+ this.#recoveredCommands.clear();
86728
87187
  this.#terminalCountBySession.clear();
87188
+ this.#activeCount = 0;
86729
87189
  };
86730
87190
  pruneTerminalReceipts = (sessionId) => {
86731
87191
  if ((this.#terminalCountBySession.get(sessionId) ?? 0) <= MAX_TERMINAL_RECEIPTS) {
@@ -86734,6 +87194,8 @@ var ExecutionReceipts = class {
86734
87194
  for (const [commandId, receipt] of this.#executions) {
86735
87195
  if (receipt.sessionId === sessionId && isTerminal(receipt.state)) {
86736
87196
  this.#executions.delete(commandId);
87197
+ this.#recoveredCommands.delete(commandId);
87198
+ this.#table?.remove(commandId);
86737
87199
  const remaining = (this.#terminalCountBySession.get(sessionId) ?? 1) - 1;
86738
87200
  if (remaining > 0) {
86739
87201
  this.#terminalCountBySession.set(sessionId, remaining);
@@ -87261,10 +87723,14 @@ function buildSessionLayer(options) {
87261
87723
  const databases = channelSessionDatabasePaths(stateDirectory, channelId);
87262
87724
  const sessionDatabase = openAcpSessionDatabase(databases.sessions);
87263
87725
  let events;
87726
+ let operations;
87264
87727
  try {
87728
+ operations = new ExecutionReceipts(new ExecutionReceiptTable(sessionDatabase), options.onExecutionCountChange);
87265
87729
  events = new CachedSessionEventStore(
87266
87730
  new SessionEventCacheTable(openSessionEventCacheDatabase(databases.events)),
87267
- new SqliteSessionLiveEventPosition(new LiveEventPositionTable(sessionDatabase))
87731
+ new SqliteSessionLiveEventPosition(new LiveEventPositionTable(sessionDatabase)),
87732
+ void 0,
87733
+ (sessionId) => operations.terminalOutcomes(sessionId)
87268
87734
  );
87269
87735
  } catch (error62) {
87270
87736
  sessionDatabase.close();
@@ -87272,11 +87738,13 @@ function buildSessionLayer(options) {
87272
87738
  }
87273
87739
  const sessionStore = new SqliteSessionStore(new AcpSessionTable(sessionDatabase), events);
87274
87740
  const externalContent = new LocalExternalContentStore(stateDirectory);
87275
- const operations = new ExecutionReceipts();
87276
87741
  const activeSessions = new SessionTurns(operations, options.turnActivity);
87277
87742
  const directories = new WorkspaceDirectoryService(bindingId, defaultWorkspace);
87278
87743
  const manager = new SessionManager2({
87279
- 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),
87280
87748
  store: sessionStore,
87281
87749
  adapters: harnessAdapters,
87282
87750
  sessionIdentity: "native",
@@ -87297,7 +87765,15 @@ function buildSessionLayer(options) {
87297
87765
  });
87298
87766
  const eventProjection = new PromptLifecycle(
87299
87767
  sink,
87300
- (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
+ },
87301
87777
  activeSessions.recordSessionEvent
87302
87778
  );
87303
87779
  const nativeSessions = new NativeSessionService(
@@ -87320,7 +87796,13 @@ function buildSessionLayer(options) {
87320
87796
  );
87321
87797
  })
87322
87798
  );
87799
+ const recoveredEvents = operations.recover().map(({ sessionId, event, recordedAt }) => events.append(sessionId, event, recordedAt));
87323
87800
  return {
87801
+ publishRecoveredOutcomes: () => {
87802
+ for (const event of recoveredEvents) {
87803
+ sink.publish(event);
87804
+ }
87805
+ },
87324
87806
  activeSessions,
87325
87807
  directories,
87326
87808
  eventProjection,
@@ -89117,12 +89599,14 @@ async function executePrompt({
89117
89599
  const appliedSettings = command.sessionConfigurations === void 0 ? void 0 : agentForSessionConfiguration(agent, command.sessionConfigurations);
89118
89600
  const executionAgent = appliedSettings?.agent ?? agent;
89119
89601
  const sessionConfigurations = appliedSettings?.sessionConfigurations;
89602
+ const recovery = command.historyPolicy === SessionHistoryPolicy.ExecutionOnly ? { kind: "execution-only" } : { kind: "interactive-only" };
89120
89603
  const preparation = {
89121
89604
  additionalDirectories: resolved.additionalDirectories,
89122
89605
  agent: executionAgent,
89123
89606
  cwd: resolved.cwd,
89124
89607
  sessionId: command.sessionId,
89125
- signal
89608
+ signal,
89609
+ recovery
89126
89610
  };
89127
89611
  let prepared = await sessions.prepareExecutionSession(preparation);
89128
89612
  if (command.sessionConfigurations !== void 0) {
@@ -89191,7 +89675,8 @@ async function executePrompt({
89191
89675
  resources: attachments.resources,
89192
89676
  sessionId: command.sessionId,
89193
89677
  signal,
89194
- targetCommandId: command.targetCommandId
89678
+ targetCommandId: command.targetCommandId,
89679
+ recovery
89195
89680
  });
89196
89681
  stopReason = result.stopReason;
89197
89682
  }
@@ -89360,6 +89845,12 @@ var sessionPromptCommand = {
89360
89845
  return;
89361
89846
  }
89362
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
+ }
89363
89854
  return;
89364
89855
  }
89365
89856
  const turn = context.sessionTurns.start(message, receipt.attemptId);
@@ -90485,7 +90976,8 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
90485
90976
  resolveAgent: (agentId, resolveSignal) => resolveCatalogAgent(config2, agentId, resolveSignal),
90486
90977
  sink: publisher,
90487
90978
  stateDirectory: config2.localContext.stateDirectory,
90488
- timer
90979
+ timer,
90980
+ onExecutionCountChange: options.onExecutionCountChange ?? (() => void 0)
90489
90981
  });
90490
90982
  const {
90491
90983
  activeSessions,
@@ -90555,6 +91047,7 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
90555
91047
  reportUpgradeAvailability,
90556
91048
  diagnostics,
90557
91049
  onConnected: () => {
91050
+ layer.publishRecoveredOutcomes();
90558
91051
  if (catalogPublished) {
90559
91052
  options.onConnectionStatus?.("online");
90560
91053
  }
@@ -90577,6 +91070,7 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
90577
91070
  }
90578
91071
  } finally {
90579
91072
  shutdown.abort();
91073
+ operations.beginShutdown();
90580
91074
  await activeSessions.close();
90581
91075
  try {
90582
91076
  await catalogPublication;
@@ -90713,6 +91207,10 @@ async function createMobiusRuntimeEngine(connection, signal, environment = proce
90713
91207
  var MobiusRuntimeEngine = class {
90714
91208
  #config;
90715
91209
  #options;
91210
+ #activeTurnCount = 0;
91211
+ get activeTurnCount() {
91212
+ return this.#activeTurnCount;
91213
+ }
90716
91214
  #state = new IdleMobiusRuntimeState();
90717
91215
  constructor(config2, options = {}) {
90718
91216
  validateRuntimeConfiguration(config2);
@@ -90728,11 +91226,15 @@ var MobiusRuntimeEngine = class {
90728
91226
  try {
90729
91227
  await runRuntime(this.#config, signal, void 0, {
90730
91228
  ...this.#options.turnActivity === void 0 ? {} : { turnActivity: this.#options.turnActivity },
91229
+ onExecutionCountChange: (count) => {
91230
+ this.#activeTurnCount = count;
91231
+ },
90731
91232
  ...this.#options.onConnectionStatus === void 0 ? {} : { onConnectionStatus: this.#options.onConnectionStatus },
90732
91233
  ...this.#options.reportUpgradeAvailability === void 0 ? {} : { reportUpgradeAvailability: this.#options.reportUpgradeAvailability },
90733
91234
  ...this.#options.processLogsDirectory === void 0 ? {} : { processLogsDirectory: this.#options.processLogsDirectory }
90734
91235
  });
90735
91236
  } finally {
91237
+ this.#activeTurnCount = 0;
90736
91238
  this.#state = new FinishedMobiusRuntimeState();
90737
91239
  }
90738
91240
  };