@moxt-ai/mobius 0.0.300 → 0.0.302

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 (83) 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/agent.d.ts.map +1 -1
  9. package/dist/acp/harness/codex/agent.js +5 -1
  10. package/dist/acp/harness/codex/agent.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/cli.js +595 -61
  15. package/dist/cli.js.map +4 -4
  16. package/dist/generated/acp-session-store-migrations.d.ts +3 -0
  17. package/dist/generated/acp-session-store-migrations.d.ts.map +1 -1
  18. package/dist/generated/acp-session-store-migrations.js +5 -1
  19. package/dist/generated/acp-session-store-migrations.js.map +1 -1
  20. package/dist/runtime/command/registry/prompt.d.ts.map +1 -1
  21. package/dist/runtime/command/registry/prompt.js +6 -0
  22. package/dist/runtime/command/registry/prompt.js.map +1 -1
  23. package/dist/runtime/engine.d.ts +1 -0
  24. package/dist/runtime/engine.d.ts.map +1 -1
  25. package/dist/runtime/engine.js +8 -0
  26. package/dist/runtime/engine.js.map +1 -1
  27. package/dist/runtime/run.d.ts +1 -0
  28. package/dist/runtime/run.d.ts.map +1 -1
  29. package/dist/runtime/run.js +3 -0
  30. package/dist/runtime/run.js.map +1 -1
  31. package/dist/runtime.js +560 -54
  32. package/dist/runtime.js.map +4 -4
  33. package/dist/schema/acp-session-store/execution-receipt.d.ts +136 -0
  34. package/dist/schema/acp-session-store/execution-receipt.d.ts.map +1 -0
  35. package/dist/schema/acp-session-store/execution-receipt.js +12 -0
  36. package/dist/schema/acp-session-store/execution-receipt.js.map +1 -0
  37. package/dist/schema/acp-session-store/index.d.ts +1 -0
  38. package/dist/schema/acp-session-store/index.d.ts.map +1 -1
  39. package/dist/schema/acp-session-store/index.js +1 -0
  40. package/dist/schema/acp-session-store/index.js.map +1 -1
  41. package/dist/session/api/session-api.d.ts +4 -0
  42. package/dist/session/api/session-api.d.ts.map +1 -1
  43. package/dist/session/api/session-api.js.map +1 -1
  44. package/dist/session/api/session-layer.d.ts +2 -0
  45. package/dist/session/api/session-layer.d.ts.map +1 -1
  46. package/dist/session/api/session-layer.js +25 -4
  47. package/dist/session/api/session-layer.js.map +1 -1
  48. package/dist/session/history/session-event-store.d.ts +7 -4
  49. package/dist/session/history/session-event-store.d.ts.map +1 -1
  50. package/dist/session/history/session-event-store.js +13 -7
  51. package/dist/session/history/session-event-store.js.map +1 -1
  52. package/dist/session/history/session-history.d.ts +1 -4
  53. package/dist/session/history/session-history.d.ts.map +1 -1
  54. package/dist/session/history/session-history.js +1 -4
  55. package/dist/session/history/session-history.js.map +1 -1
  56. package/dist/session/live/agent-session-restore.d.ts +1 -0
  57. package/dist/session/live/agent-session-restore.d.ts.map +1 -1
  58. package/dist/session/live/agent-session.d.ts +2 -0
  59. package/dist/session/live/agent-session.d.ts.map +1 -1
  60. package/dist/session/live/agent-session.js +15 -4
  61. package/dist/session/live/agent-session.js.map +1 -1
  62. package/dist/session/live/session-restore.d.ts.map +1 -1
  63. package/dist/session/live/session-restore.js +37 -0
  64. package/dist/session/live/session-restore.js.map +1 -1
  65. package/dist/session/manager/session-manager.d.ts.map +1 -1
  66. package/dist/session/manager/session-manager.js +11 -3
  67. package/dist/session/manager/session-manager.js.map +1 -1
  68. package/dist/session/prompt/execute-prompt.d.ts.map +1 -1
  69. package/dist/session/prompt/execute-prompt.js +6 -1
  70. package/dist/session/prompt/execute-prompt.js.map +1 -1
  71. package/dist/session/prompt/prompt-lifecycle.d.ts +1 -0
  72. package/dist/session/prompt/prompt-lifecycle.d.ts.map +1 -1
  73. package/dist/session/prompt/prompt-lifecycle.js +9 -1
  74. package/dist/session/prompt/prompt-lifecycle.js.map +1 -1
  75. package/dist/session/turn/execution-receipts.d.ts +23 -2
  76. package/dist/session/turn/execution-receipts.d.ts.map +1 -1
  77. package/dist/session/turn/execution-receipts.js +161 -10
  78. package/dist/session/turn/execution-receipts.js.map +1 -1
  79. package/dist/storage/session/execution-receipt-table.d.ts +45 -0
  80. package/dist/storage/session/execution-receipt-table.d.ts.map +1 -0
  81. package/dist/storage/session/execution-receipt-table.js +64 -0
  82. package/dist/storage/session/execution-receipt-table.js.map +1 -0
  83. 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.302",
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") {
@@ -68985,7 +69087,11 @@ var CodexAcpAgent = class {
68985
69087
  const decoded = threadListResponseSchema.parse(
68986
69088
  await this.#appServer.request(
68987
69089
  "thread/list",
68988
- { cursor: context.params.cursor ?? null },
69090
+ {
69091
+ cursor: context.params.cursor ?? null,
69092
+ // Codex defaults to interactive sources and otherwise hides exec handoffs.
69093
+ sourceKinds: ["cli", "vscode", "exec", "appServer"]
69094
+ },
68989
69095
  AbortSignal.any([context.signal, AbortSignal.timeout(SESSION_LIST_TIMEOUT_MILLISECONDS2)])
68990
69096
  )
68991
69097
  );
@@ -69225,7 +69331,7 @@ import { basename as basename4, dirname as dirname7, isAbsolute as isAbsolute11,
69225
69331
  import { RequestError as RequestError7 } from "@agentclientprotocol/sdk";
69226
69332
 
69227
69333
  // src/acp/harness/pi/upstream/dist/pi-rpc/process.js
69228
- import { spawn as spawn4 } from "node:child_process";
69334
+ import { spawn as spawn5 } from "node:child_process";
69229
69335
  import * as readline from "node:readline";
69230
69336
 
69231
69337
  // src/acp/harness/pi/upstream/dist/pi-rpc/command.js
@@ -69342,7 +69448,7 @@ var PiRpcProcess = class _PiRpcProcess {
69342
69448
  args.push("--fork", params.origin.sessionPath);
69343
69449
  break;
69344
69450
  }
69345
- const child = spawn4(cmd, args, {
69451
+ const child = spawn5(cmd, args, {
69346
69452
  cwd: params.cwd,
69347
69453
  stdio: "pipe",
69348
69454
  env: params.environment,
@@ -72342,6 +72448,77 @@ ${JSON.stringify(stats, null, 2)}`;
72342
72448
  }, 0);
72343
72449
  return { sessionId: session.sessionId, configOptions, modes };
72344
72450
  }
72451
+ async resumeSession(params) {
72452
+ if (!isAbsolute11(params.cwd)) {
72453
+ throw RequestError7.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
72454
+ }
72455
+ this.sessions.close(params.sessionId);
72456
+ this.lastSessionCwd = params.cwd;
72457
+ const stored = this.findStoredSession(params.sessionId);
72458
+ if (!stored) {
72459
+ throw RequestError7.invalidParams(`Unknown sessionId: ${params.sessionId}`);
72460
+ }
72461
+ const session = await this.restoreSession(params.sessionId, {
72462
+ cwd: params.cwd,
72463
+ mcpServers: params.mcpServers
72464
+ });
72465
+ let configuration;
72466
+ try {
72467
+ configuration = await getSessionConfiguration(session.proc);
72468
+ } catch (err) {
72469
+ this.sessions.close(session.sessionId);
72470
+ throw err;
72471
+ }
72472
+ const { configOptions, models, modes } = configuration;
72473
+ const enableSkillCommands = getEnableSkillCommands(params.cwd, this.environment);
72474
+ const fileCommands = loadSlashCommands(params.cwd, this.environment);
72475
+ this.sessions.closeAllExcept?.(session.sessionId);
72476
+ this.store.upsert({
72477
+ sessionId: params.sessionId,
72478
+ cwd: params.cwd,
72479
+ sessionFile: stored.sessionFile
72480
+ });
72481
+ const response = {
72482
+ configOptions,
72483
+ models,
72484
+ modes,
72485
+ _meta: {
72486
+ piAcp: {
72487
+ startupInfo: null
72488
+ }
72489
+ }
72490
+ };
72491
+ setTimeout(() => {
72492
+ void (async () => {
72493
+ await session.publishContextUsage();
72494
+ try {
72495
+ const pi2 = await session.proc.getCommands();
72496
+ const { commands } = toAvailableCommandsFromPiGetCommands(pi2, {
72497
+ enableSkillCommands,
72498
+ includeExtensionCommands: false
72499
+ });
72500
+ await this.conn.sessionUpdate({
72501
+ sessionId: session.sessionId,
72502
+ update: {
72503
+ sessionUpdate: "available_commands_update",
72504
+ availableCommands: mergeCommands(commands, builtinAvailableCommands())
72505
+ }
72506
+ });
72507
+ return;
72508
+ } catch {
72509
+ }
72510
+ await this.conn.sessionUpdate({
72511
+ sessionId: session.sessionId,
72512
+ update: {
72513
+ sessionUpdate: "available_commands_update",
72514
+ availableCommands: mergeCommands(toAvailableCommands(fileCommands), builtinAvailableCommands())
72515
+ }
72516
+ });
72517
+ })().catch(() => {
72518
+ });
72519
+ }, 0);
72520
+ return response;
72521
+ }
72345
72522
  async loadSession(params) {
72346
72523
  if (!isAbsolute11(params.cwd)) {
72347
72524
  throw RequestError7.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
@@ -72925,6 +73102,9 @@ var PiAcpEndpoint = class {
72925
73102
  ).onRequest(
72926
73103
  methods5.agent.session.load,
72927
73104
  async (context) => await piAgentOrThrow(piAgent).loadSession(context.params)
73105
+ ).onRequest(
73106
+ methods5.agent.session.resume,
73107
+ async (context) => await piAgentOrThrow(piAgent).resumeSession(context.params)
72928
73108
  ).onRequest(
72929
73109
  methods5.agent.session.list,
72930
73110
  async (context) => await piAgentOrThrow(piAgent).listSessions(context.params)
@@ -73204,7 +73384,7 @@ var LocalAgentInventory = class {
73204
73384
  };
73205
73385
 
73206
73386
  // src/agent/discovery/acp-readiness-probe.ts
73207
- import { spawn as spawn5 } from "node:child_process";
73387
+ import { spawn as spawn6 } from "node:child_process";
73208
73388
  import {
73209
73389
  methods as methods8,
73210
73390
  ndJsonStream as ndJsonStream2,
@@ -73395,7 +73575,7 @@ async function probeAcpReadiness(specification, agent, environment, defaultWorks
73395
73575
  }
73396
73576
  }
73397
73577
  const launch = executableLaunch2(agent.adapterPath, agent.arguments);
73398
- const child = spawn5(launch.command, [...launch.arguments], {
73578
+ const child = spawn6(launch.command, [...launch.arguments], {
73399
73579
  cwd: defaultWorkspace,
73400
73580
  ...platformSpawnOptions,
73401
73581
  env: { ...environment, ...agent.environment },
@@ -75008,7 +75188,7 @@ var ClaudeAdapter = class {
75008
75188
  };
75009
75189
 
75010
75190
  // src/acp/native-history/codex/codex-history.ts
75011
- import { spawn as spawn6 } from "node:child_process";
75191
+ import { spawn as spawn7 } from "node:child_process";
75012
75192
 
75013
75193
  // src/acp/protocol/native-session.ts
75014
75194
  var path6 = external_exports.string().min(1).max(2048).refine((value) => !value.includes("\0"));
@@ -75705,7 +75885,7 @@ async function readCodexHistoryWithOptions(agent, cwd, nativeSessionId, signal,
75705
75885
  }
75706
75886
  signal.throwIfAborted();
75707
75887
  const launch = executableLaunch2(agent.agentIdentity.realPath, ["app-server"]);
75708
- const child = spawn6(launch.command, [...launch.arguments], {
75888
+ const child = spawn7(launch.command, [...launch.arguments], {
75709
75889
  cwd,
75710
75890
  env: { ...process.env, ...agent.environment },
75711
75891
  ...platformSpawnOptions,
@@ -81046,6 +81226,18 @@ var acpSession = sqliteTable("acp_session", {
81046
81226
  updatedAt: text("updated_at").notNull()
81047
81227
  });
81048
81228
 
81229
+ // src/schema/acp-session-store/execution-receipt.ts
81230
+ var executionReceipt = sqliteTable("execution_receipt", {
81231
+ commandId: text("command_id").primaryKey(),
81232
+ sessionId: text("session_id").notNull(),
81233
+ commandHash: text("command_hash").notNull(),
81234
+ attemptId: text("attempt_id").notNull(),
81235
+ state: text("state").notNull(),
81236
+ started: integer2("started", { mode: "boolean" }).notNull(),
81237
+ outcome: text("outcome"),
81238
+ updatedAt: text("updated_at").notNull()
81239
+ });
81240
+
81049
81241
  // src/schema/acp-session-store/session-live-event-position-allocation.ts
81050
81242
  var sessionLiveEventPositionAllocation = sqliteTable("session_live_event_position_allocation", {
81051
81243
  allocatedPosition: integer2("allocated_position").notNull().default(1e4),
@@ -81087,6 +81279,62 @@ var AcpSessionTable = class {
81087
81279
  };
81088
81280
  };
81089
81281
 
81282
+ // src/storage/session/execution-receipt-table.ts
81283
+ var receiptFields = external_exports.object({
81284
+ commandId: external_exports.string(),
81285
+ sessionId: external_exports.string(),
81286
+ commandHash: external_exports.string(),
81287
+ attemptId: external_exports.string(),
81288
+ updatedAt: external_exports.string()
81289
+ });
81290
+ var receiptSchema = external_exports.discriminatedUnion("state", [
81291
+ receiptFields.extend({ state: external_exports.literal("dispatching"), started: external_exports.literal(false), outcome: external_exports.null() }),
81292
+ receiptFields.extend({ state: external_exports.literal("running"), started: external_exports.literal(true), outcome: external_exports.string().nullable() }),
81293
+ receiptFields.extend({
81294
+ state: external_exports.enum(["succeeded", "failed", "cancelled"]),
81295
+ started: external_exports.boolean(),
81296
+ outcome: external_exports.string().nullable()
81297
+ })
81298
+ ]).readonly();
81299
+ var ExecutionReceiptTable = class {
81300
+ #database;
81301
+ constructor(database) {
81302
+ this.#database = database;
81303
+ }
81304
+ list = () => receiptSchema.array().parse(
81305
+ this.#database.orm.select().from(executionReceipt).orderBy(asc(executionReceipt.updatedAt), asc(executionReceipt.commandId)).all()
81306
+ );
81307
+ save = (receipt) => {
81308
+ this.#database.transaction(() => {
81309
+ this.#database.orm.insert(executionReceipt).values(receipt).onConflictDoUpdate({ target: executionReceipt.commandId, set: receipt }).run();
81310
+ if (receipt.state === "dispatching" || receipt.state === "running") {
81311
+ return;
81312
+ }
81313
+ const retained = this.#database.orm.select({ commandId: executionReceipt.commandId }).from(executionReceipt).where(
81314
+ and(
81315
+ eq2(executionReceipt.sessionId, receipt.sessionId),
81316
+ inArray(executionReceipt.state, ["succeeded", "failed", "cancelled"])
81317
+ )
81318
+ ).orderBy(desc(executionReceipt.updatedAt), desc(executionReceipt.commandId)).limit(1024).all();
81319
+ if (retained.length === 1024) {
81320
+ this.#database.orm.delete(executionReceipt).where(
81321
+ and(
81322
+ eq2(executionReceipt.sessionId, receipt.sessionId),
81323
+ inArray(executionReceipt.state, ["succeeded", "failed", "cancelled"]),
81324
+ notInArray(
81325
+ executionReceipt.commandId,
81326
+ retained.map((row) => row.commandId)
81327
+ )
81328
+ )
81329
+ ).run();
81330
+ }
81331
+ });
81332
+ };
81333
+ remove = (commandId) => {
81334
+ this.#database.orm.delete(executionReceipt).where(eq2(executionReceipt.commandId, commandId)).run();
81335
+ };
81336
+ };
81337
+
81090
81338
  // src/storage/session/live-event-position-table.ts
81091
81339
  var allocationRowSchema = external_exports.object({ allocatedPosition: external_exports.number().int().safe().nonnegative() });
81092
81340
  var LiveEventPositionTable = class {
@@ -81306,6 +81554,10 @@ var acpSessionStoreMigrations = [
81306
81554
  {
81307
81555
  "name": "0006_live_event_position_allocation.sql",
81308
81556
  "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"
81557
+ },
81558
+ {
81559
+ "name": "0007_busy_excalibur.sql",
81560
+ "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
81561
  }
81310
81562
  ];
81311
81563
 
@@ -81960,12 +82212,12 @@ var SessionEvents = class {
81960
82212
  }
81961
82213
  };
81962
82214
  };
81963
- function liveEvent(sessionId, sessionGenerationId, eventId, position, event) {
82215
+ function liveEvent(sessionId, sessionGenerationId, eventId, position, event, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
81964
82216
  return {
81965
82217
  eventId,
81966
82218
  ...event,
81967
82219
  position: position.toString(),
81968
- recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
82220
+ recordedAt,
81969
82221
  sessionGenerationId,
81970
82222
  sessionId
81971
82223
  };
@@ -82003,7 +82255,7 @@ var MemorySessionEventStore = class {
82003
82255
  }
82004
82256
  return resolved;
82005
82257
  };
82006
- append = (sessionId, event) => {
82258
+ append = (sessionId, event, recordedAt) => {
82007
82259
  const session = this.#session(sessionId);
82008
82260
  const allocatedPosition = this.#allocatedPositionBySession.get(sessionId) ?? 9999;
82009
82261
  if (allocatedPosition === Number.MAX_SAFE_INTEGER) {
@@ -82016,7 +82268,8 @@ var MemorySessionEventStore = class {
82016
82268
  session.sessionGenerationId,
82017
82269
  this.#eventIds.allocate(sessionId, session.sessionGenerationId),
82018
82270
  position,
82019
- event
82271
+ event,
82272
+ recordedAt
82020
82273
  );
82021
82274
  session.apply(recorded, this.#resolver(session));
82022
82275
  return recorded;
@@ -82057,12 +82310,14 @@ var MemorySessionEventStore = class {
82057
82310
  };
82058
82311
  };
82059
82312
  var CachedSessionEventStore = class {
82313
+ #terminalOutcomes;
82060
82314
  #cacheTable;
82061
82315
  #eventIds;
82062
82316
  #liveEventPositions;
82063
82317
  #sessions = /* @__PURE__ */ new Map();
82064
82318
  #flushSchedule = { kind: "idle" };
82065
- constructor(cacheTable, liveEventPositions, eventIds = new InMemorySessionEventIdAllocator()) {
82319
+ constructor(cacheTable, liveEventPositions, eventIds = new InMemorySessionEventIdAllocator(), terminalOutcomes = () => []) {
82320
+ this.#terminalOutcomes = terminalOutcomes;
82066
82321
  this.#eventIds = eventIds;
82067
82322
  this.#liveEventPositions = liveEventPositions;
82068
82323
  this.#cacheTable = cacheTable;
@@ -82095,7 +82350,7 @@ var CachedSessionEventStore = class {
82095
82350
  }
82096
82351
  return resolved;
82097
82352
  };
82098
- append = (sessionId, event) => {
82353
+ append = (sessionId, event, recordedAt) => {
82099
82354
  if (this.#flushSchedule.kind === "closed") {
82100
82355
  throw new Error("The V2 Session event store is closed");
82101
82356
  }
@@ -82106,7 +82361,8 @@ var CachedSessionEventStore = class {
82106
82361
  session.sessionGenerationId,
82107
82362
  this.#eventIds.allocate(sessionId, session.sessionGenerationId),
82108
82363
  position,
82109
- event
82364
+ event,
82365
+ recordedAt
82110
82366
  );
82111
82367
  session.apply(recorded, this.#resolver(sessionId, session));
82112
82368
  this.#sessions.set(sessionId, session);
@@ -82195,6 +82451,9 @@ var CachedSessionEventStore = class {
82195
82451
  this.#deletePersisted(sessionId, previous?.sessionGenerationId);
82196
82452
  const sessionGenerationId = randomUUID11();
82197
82453
  this.#sessions.set(sessionId, new SessionEvents(sessionGenerationId));
82454
+ for (const { event, recordedAt } of this.#terminalOutcomes(sessionId)) {
82455
+ this.append(sessionId, event, recordedAt);
82456
+ }
82198
82457
  };
82199
82458
  materializedEventCount = (sessionId) => this.#sessions.get(sessionId)?.materializedCount ?? 0;
82200
82459
  close = () => {
@@ -82308,7 +82567,7 @@ function buildPromptContent(options, capabilities) {
82308
82567
  }
82309
82568
 
82310
82569
  // src/acp/connection/agent-connection.ts
82311
- import { spawn as spawn7 } from "node:child_process";
82570
+ import { spawn as spawn8 } from "node:child_process";
82312
82571
  import { ndJsonStream as ndJsonStream3, RequestError as RequestError9 } from "@agentclientprotocol/sdk";
82313
82572
  function requestErrorMessage(error62) {
82314
82573
  const fallback = truncate(error62.message, 500);
@@ -82418,7 +82677,7 @@ var AgentConnection = class {
82418
82677
  return;
82419
82678
  }
82420
82679
  const launch = executableLaunch2(agent.adapterPath, agent.arguments);
82421
- const child = spawn7(launch.command, [...launch.arguments], {
82680
+ const child = spawn8(launch.command, [...launch.arguments], {
82422
82681
  cwd,
82423
82682
  ...platformSpawnOptions,
82424
82683
  env: { ...process.env, ...agent.environment },
@@ -83692,6 +83951,19 @@ var RunningAgentTurn = class extends AgentTurnState {
83692
83951
 
83693
83952
  // src/session/live/session-restore.ts
83694
83953
  import { methods as methods10 } from "@agentclientprotocol/sdk";
83954
+ function hasMethodNotFoundCode(cause) {
83955
+ const visited = /* @__PURE__ */ new Set();
83956
+ let current = cause;
83957
+ while ((current instanceof Error || typeof current === "object" && current !== null) && !visited.has(current)) {
83958
+ visited.add(current);
83959
+ const code = Reflect.get(current, "code");
83960
+ if (code === -32601 || code === "METHOD_NOT_FOUND" || code === "method_not_found") {
83961
+ return true;
83962
+ }
83963
+ current = current instanceof Error ? current.cause : void 0;
83964
+ }
83965
+ return false;
83966
+ }
83695
83967
  async function restoreSession(session, signal, recovery, origin) {
83696
83968
  if (origin !== void 0) {
83697
83969
  session.setSessionTitle(origin.title);
@@ -83758,6 +84030,9 @@ async function restoreSession(session, signal, recovery, origin) {
83758
84030
  }
83759
84031
  session.setPrompted(true);
83760
84032
  session.setRestored(true);
84033
+ if (recovery.kind === "execution-only") {
84034
+ session.startExecutionOnlyCapture();
84035
+ }
83761
84036
  lifecycleResponse2 = forked;
83762
84037
  } else if (stored instanceof FoundStoredSession && stored.session.agentId === session.agentId && stored.session.cwd === session.cwd && arraysEqual(stored.session.additionalDirectories, session.additionalDirectories)) {
83763
84038
  session.selectNativeSession(stored.session.nativeSessionId);
@@ -83776,6 +84051,37 @@ async function restoreSession(session, signal, recovery, origin) {
83776
84051
  "session-load",
83777
84052
  async () => (await session.loadSession(signal, "ACP agent exited while loading a session")).response
83778
84053
  );
84054
+ } else if (recovery.kind === "execution-only") {
84055
+ try {
84056
+ lifecycleResponse2 = await session.metrics.measure(
84057
+ "session-resume",
84058
+ async () => await session.race(
84059
+ session.agentRequest(
84060
+ methods10.agent.session.resume,
84061
+ {
84062
+ additionalDirectories: [...session.additionalDirectories],
84063
+ cwd: session.cwd,
84064
+ mcpServers: [],
84065
+ sessionId: session.nativeSessionId()
84066
+ },
84067
+ { cancellationSignal: signal }
84068
+ ),
84069
+ "ACP agent exited while resuming a session",
84070
+ signal
84071
+ )
84072
+ );
84073
+ } catch (cause) {
84074
+ signal.throwIfAborted();
84075
+ if (cause instanceof AcpExecutionError && !hasMethodNotFoundCode(cause)) {
84076
+ throw cause;
84077
+ }
84078
+ throw new AcpExecutionError(
84079
+ "This Agent cannot continue a persisted session without replaying native history",
84080
+ { cause },
84081
+ "execution_only_unsupported"
84082
+ );
84083
+ }
84084
+ session.startExecutionOnlyCapture();
83779
84085
  } else if (session.capabilities.resumeSession) {
83780
84086
  lifecycleResponse2 = await session.metrics.measure(
83781
84087
  "session-resume",
@@ -83835,6 +84141,9 @@ async function restoreSession(session, signal, recovery, origin) {
83835
84141
  );
83836
84142
  session.selectNativeSession(created.sessionId);
83837
84143
  session.setNativeHistoryOwnership({ kind: "fresh-session" });
84144
+ if (recovery.kind === "execution-only") {
84145
+ session.startExecutionOnlyCapture();
84146
+ }
83838
84147
  lifecycleResponse2 = created;
83839
84148
  }
83840
84149
  session.restrictSessionCapabilities(lifecycleResponse2);
@@ -84112,6 +84421,16 @@ var AgentSession = class _AgentSession {
84112
84421
  }
84113
84422
  await this.#completedHistory(signal, "ACP agent exited while loading history");
84114
84423
  };
84424
+ /** Starts live event capture without asking the native Agent to replay its persisted history. */
84425
+ startExecutionOnlyCapture = () => {
84426
+ if (this.#sessionLoad.kind !== "not-requested") {
84427
+ return;
84428
+ }
84429
+ this.#sessionLoad = {
84430
+ history: new HistoryReplayCapture(this.#store.events, this.#mobiusSessionId),
84431
+ kind: "execution-only"
84432
+ };
84433
+ };
84115
84434
  loadHistoryEvents = async (eventLimit, cursor, signal) => {
84116
84435
  if (!this.#capabilities.loadSession) {
84117
84436
  throw new AcpExecutionError(
@@ -84362,7 +84681,7 @@ var AgentSession = class _AgentSession {
84362
84681
  response: {}
84363
84682
  };
84364
84683
  }
84365
- if (this.#capabilities.loadSession) {
84684
+ if (this.#capabilities.loadSession && this.#sessionLoad.kind !== "execution-only") {
84366
84685
  await this.#completedHistory(options.signal, "ACP agent exited while loading history before a prompt");
84367
84686
  }
84368
84687
  const prompt = this.buildPrompt(options);
@@ -84561,14 +84880,14 @@ var AgentSession = class _AgentSession {
84561
84880
  onElicitationComplete = (notification) => this.#interactions.onElicitationComplete(notification);
84562
84881
  onUpdate = (notification) => this.#updates.receive(notification);
84563
84882
  recordControlEvent = async (event) => {
84564
- if (this.#sessionLoad.kind !== "loaded") {
84883
+ if (this.#sessionLoad.kind !== "loaded" && this.#sessionLoad.kind !== "execution-only") {
84565
84884
  return false;
84566
84885
  }
84567
84886
  await this.recordLifecycleEvent(event);
84568
84887
  return true;
84569
84888
  };
84570
84889
  recordLifecycleEvent = async (event) => {
84571
- if (event.type !== "lifecycle-metadata" || this.#sessionLoad.kind !== "loaded") {
84890
+ if (event.type !== "lifecycle-metadata" || this.#sessionLoad.kind !== "loaded" && this.#sessionLoad.kind !== "execution-only") {
84572
84891
  return;
84573
84892
  }
84574
84893
  const liveEvent2 = this.#sessionLoad.history.recordLifecycle(event);
@@ -84589,7 +84908,7 @@ var AgentSession = class _AgentSession {
84589
84908
  const rawUpdate = decodeAcpSessionUpdate(JSON.stringify(externalized.event));
84590
84909
  if (this.#sessionLoad.kind === "capturing") {
84591
84910
  await this.#sessionLoad.history.record(update, rawUpdate);
84592
- } else if (this.#sessionLoad.kind === "loaded") {
84911
+ } else if (this.#sessionLoad.kind === "loaded" || this.#sessionLoad.kind === "execution-only") {
84593
84912
  const onSessionEvent = this.#getObserver().onSessionEvent;
84594
84913
  await this.#sessionLoad.history.record(update, rawUpdate, async (liveEvent2) => {
84595
84914
  await onSessionEvent?.(liveEvent2);
@@ -85507,13 +85826,14 @@ var SessionManager2 = class {
85507
85826
  }
85508
85827
  async #replaceExecutionPreparation(existing, options) {
85509
85828
  const desiredConfigOptions = existing.prepared().configOptions;
85829
+ const recovery = options.recovery ?? { kind: "interactive-only" };
85510
85830
  return await this.#executionProcesses.prepare(options.sessionId, async () => {
85511
85831
  await this.#retireSessionProcess(options.sessionId, existing, async () => await existing.close());
85512
85832
  const runtime = await this.#startRetainedSessionProcess(
85513
85833
  options,
85514
85834
  async () => await AgentSession.start(
85515
85835
  options,
85516
- { kind: "interactive-only" },
85836
+ recovery,
85517
85837
  () => this.#observer,
85518
85838
  this.#store,
85519
85839
  unmeasuredOperationMetrics,
@@ -85691,6 +86011,7 @@ var SessionManager2 = class {
85691
86011
  });
85692
86012
  });
85693
86013
  prepareExecutionSession = async (options) => this.#operations.run(options, async (options2) => {
86014
+ const recovery = options2.recovery ?? { kind: "interactive-only" };
85694
86015
  this.#assertOpen(options2.sessionId);
85695
86016
  const pending = this.#executionProcesses.pending(options2.sessionId);
85696
86017
  if (pending !== void 0) {
@@ -85699,7 +86020,7 @@ var SessionManager2 = class {
85699
86020
  }
85700
86021
  const existing = this.#executionProcesses.get(options2.sessionId);
85701
86022
  if (existing === void 0) {
85702
- return (await this.#startPreparation(options2, { kind: "interactive-only" })).prepared();
86023
+ return (await this.#startPreparation(options2, recovery)).prepared();
85703
86024
  }
85704
86025
  if (!existing.belongsTo(options2.agent.id, options2.cwd, options2.additionalDirectories)) {
85705
86026
  throw new AcpExecutionError(
@@ -85709,6 +86030,9 @@ var SessionManager2 = class {
85709
86030
  );
85710
86031
  }
85711
86032
  if (existing.usesEnvironment(options2.agent.environment)) {
86033
+ if (recovery.kind === "execution-only") {
86034
+ existing.startExecutionOnlyCapture();
86035
+ }
85712
86036
  this.#executionProcesses.touch(options2.sessionId, existing);
85713
86037
  return existing.prepared();
85714
86038
  }
@@ -86003,10 +86327,13 @@ var SessionManager2 = class {
86003
86327
  "session_identity_mismatch"
86004
86328
  );
86005
86329
  }
86330
+ if (options2.recovery?.kind === "execution-only") {
86331
+ existing.startExecutionOnlyCapture();
86332
+ }
86006
86333
  this.#executionProcesses.touch(options2.sessionId, existing);
86007
86334
  return existing;
86008
86335
  }
86009
- await this.prepareSession(options2);
86336
+ await this.#prepareSession(options2, options2.recovery ?? { kind: "interactive-only" }, "reopen");
86010
86337
  const started = this.#executionProcesses.get(options2.sessionId);
86011
86338
  if (started === void 0) {
86012
86339
  throw new AcpExecutionError("ACP session did not start");
@@ -86609,6 +86936,13 @@ var PromptLifecycle = class {
86609
86936
  }
86610
86937
  this.#queued.delete(commandId);
86611
86938
  };
86939
+ replayOutcome = async (sessionId, event) => {
86940
+ if (event.type !== "lifecycle-metadata" || !("commandId" in event.payload)) {
86941
+ return;
86942
+ }
86943
+ await this.#recordLifecycle(sessionId, event);
86944
+ this.publishTerminal(sessionId, event.payload.commandId);
86945
+ };
86612
86946
  queueChanged = async (sessionId) => {
86613
86947
  await this.#recordLifecycle(sessionId, {
86614
86948
  type: "lifecycle-metadata",
@@ -86642,7 +86976,7 @@ var PromptLifecycle = class {
86642
86976
  type: "lifecycle-metadata",
86643
86977
  payload: { kind: "prompt.failed-before-start", commandId, code, message }
86644
86978
  });
86645
- if (!recorded) {
86979
+ if (!recorded || code === SESSION_CLOSED_ERROR_CODE) {
86646
86980
  this.#sink.publishOperationFailure(sessionId, commandId, code, message);
86647
86981
  }
86648
86982
  };
@@ -86679,18 +87013,149 @@ function isTerminal(state) {
86679
87013
  }
86680
87014
  var ExecutionReceipts = class {
86681
87015
  #executions = /* @__PURE__ */ new Map();
87016
+ #recoveredCommands = /* @__PURE__ */ new Set();
86682
87017
  #terminalCountBySession = /* @__PURE__ */ new Map();
87018
+ #table;
87019
+ #closing = false;
87020
+ #activeCount = 0;
87021
+ #onCount;
87022
+ constructor(table, onCount = () => void 0) {
87023
+ this.#onCount = onCount;
87024
+ this.#table = table;
87025
+ for (const row of table?.list() ?? []) {
87026
+ this.#executions.set(row.commandId, row);
87027
+ this.#recoveredCommands.add(row.commandId);
87028
+ if (!isTerminal(row.state)) {
87029
+ this.#activeCount += 1;
87030
+ }
87031
+ if (isTerminal(row.state)) {
87032
+ this.#terminalCountBySession.set(
87033
+ row.sessionId,
87034
+ (this.#terminalCountBySession.get(row.sessionId) ?? 0) + 1
87035
+ );
87036
+ }
87037
+ }
87038
+ }
87039
+ #save = (commandId, receipt) => {
87040
+ if (!this.#closing) {
87041
+ this.#table?.save({ ...receipt, commandId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
87042
+ }
87043
+ const previous = this.#executions.get(commandId);
87044
+ const wasActive = previous !== void 0 && !isTerminal(previous.state);
87045
+ if (isTerminal(receipt.state)) {
87046
+ this.#executions.delete(commandId);
87047
+ }
87048
+ this.#executions.set(commandId, receipt);
87049
+ this.#activeCount += Number(!isTerminal(receipt.state)) - Number(wasActive);
87050
+ this.#onCount(this.#activeCount);
87051
+ if (isTerminal(receipt.state) && (previous === void 0 || !isTerminal(previous.state))) {
87052
+ this.#terminalCountBySession.set(
87053
+ receipt.sessionId,
87054
+ (this.#terminalCountBySession.get(receipt.sessionId) ?? 0) + 1
87055
+ );
87056
+ this.pruneTerminalReceipts(receipt.sessionId);
87057
+ }
87058
+ };
87059
+ beginShutdown = () => {
87060
+ this.#closing = true;
87061
+ };
87062
+ outcome = (commandId) => {
87063
+ const receipt = this.#executions.get(commandId);
87064
+ return receipt?.outcome === null || receipt === void 0 ? void 0 : sessionEventPayloadSchema.parse(JSON.parse(receipt.outcome));
87065
+ };
87066
+ recordOutcome = (event) => {
87067
+ if (event.type !== "lifecycle-metadata") {
87068
+ return;
87069
+ }
87070
+ const payload = event.payload;
87071
+ if (payload.kind !== "prompt.completed" && payload.kind !== "prompt.failed" && payload.kind !== "prompt.failed-before-start") {
87072
+ return;
87073
+ }
87074
+ const receipt = this.#executions.get(payload.commandId);
87075
+ if (receipt === void 0) {
87076
+ return;
87077
+ }
87078
+ const sanitized = payload.kind === "prompt.completed" ? payload : { ...payload, message: "The local Agent could not complete the request" };
87079
+ this.#save(payload.commandId, {
87080
+ ...receipt,
87081
+ state: payload.kind === "prompt.completed" ? payload.stopReason === "cancelled" ? "cancelled" : "succeeded" : "failed",
87082
+ outcome: JSON.stringify({ type: "lifecycle-metadata", payload: sanitized, recordedAt: event.recordedAt })
87083
+ });
87084
+ };
87085
+ recover = () => {
87086
+ const recovered = [];
87087
+ for (const [commandId, receipt] of [...this.#executions]) {
87088
+ const event = receipt.outcome === null ? {
87089
+ type: "lifecycle-metadata",
87090
+ payload: receipt.started ? {
87091
+ kind: "prompt.failed",
87092
+ commandId,
87093
+ attemptId: receipt.attemptId,
87094
+ code: "runtime_restarted",
87095
+ message: "The local runtime restarted while this request was running"
87096
+ } : {
87097
+ kind: "prompt.failed-before-start",
87098
+ commandId,
87099
+ code: "runtime_restarted",
87100
+ message: "The local runtime restarted before this request started"
87101
+ }
87102
+ } : sessionEventPayloadSchema.parse(JSON.parse(receipt.outcome));
87103
+ const recordedAt = receipt.outcome === null ? (/* @__PURE__ */ new Date()).toISOString() : external_exports.object({ recordedAt: external_exports.string() }).parse(JSON.parse(receipt.outcome)).recordedAt;
87104
+ recovered.push({ sessionId: receipt.sessionId, event, recordedAt });
87105
+ if (!isTerminal(receipt.state)) {
87106
+ const state = event.type === "lifecycle-metadata" && event.payload.kind === "prompt.completed" ? event.payload.stopReason === "cancelled" ? "cancelled" : "succeeded" : "failed";
87107
+ this.#save(commandId, { ...receipt, state, outcome: JSON.stringify({ ...event, recordedAt }) });
87108
+ }
87109
+ }
87110
+ return recovered;
87111
+ };
87112
+ terminalOutcomes = (sessionId) => [...this.#executions.values()].flatMap((receipt) => {
87113
+ if (receipt.sessionId !== sessionId || receipt.outcome === null) {
87114
+ return [];
87115
+ }
87116
+ const decoded = JSON.parse(receipt.outcome);
87117
+ return [
87118
+ {
87119
+ event: sessionEventPayloadSchema.parse(decoded),
87120
+ recordedAt: external_exports.object({ recordedAt: external_exports.string() }).parse(decoded).recordedAt
87121
+ }
87122
+ ];
87123
+ });
86683
87124
  claimExecution = (commandId, sessionId, commandJson) => {
86684
87125
  const commandHash = createHash6("sha256").update(commandJson).digest("hex");
86685
- const existing = this.#executions.get(commandId);
87126
+ let existing = this.#executions.get(commandId);
87127
+ const previous = this.outcome(commandId);
87128
+ if (existing?.sessionId === sessionId && !existing.started && previous?.type === "lifecycle-metadata" && previous.payload.kind === "prompt.failed-before-start" && previous.payload.code === "runtime_restarted") {
87129
+ this.#executions.delete(commandId);
87130
+ this.#recoveredCommands.delete(commandId);
87131
+ this.#table?.remove(commandId);
87132
+ this.#terminalCountBySession.set(
87133
+ sessionId,
87134
+ Math.max(0, (this.#terminalCountBySession.get(sessionId) ?? 1) - 1)
87135
+ );
87136
+ existing = void 0;
87137
+ }
86686
87138
  if (existing !== void 0) {
86687
87139
  if (existing.sessionId !== sessionId || existing.commandHash !== commandHash) {
86688
87140
  return { kind: "conflict" };
86689
87141
  }
86690
- return { attemptId: existing.attemptId, commandId, kind: "duplicate", state: existing.state };
87142
+ return {
87143
+ attemptId: existing.attemptId,
87144
+ commandId,
87145
+ kind: "duplicate",
87146
+ state: existing.state,
87147
+ outcome: this.#recoveredCommands.has(commandId) ? this.outcome(commandId) : void 0
87148
+ };
86691
87149
  }
86692
87150
  const attemptId = randomUUID13();
86693
- this.#executions.set(commandId, { attemptId, commandHash, sessionId, state: "dispatching" });
87151
+ this.#save(commandId, {
87152
+ attemptId,
87153
+ commandHash,
87154
+ sessionId,
87155
+ state: "dispatching",
87156
+ started: false,
87157
+ outcome: null
87158
+ });
86694
87159
  return { attemptId, commandId, kind: "accepted", state: "dispatching" };
86695
87160
  };
86696
87161
  transitionExecution = (commandId, state) => {
@@ -86705,16 +87170,13 @@ var ExecutionReceipts = class {
86705
87170
  if (!allowed) {
86706
87171
  throw new Error("The runtime execution transition is invalid");
86707
87172
  }
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);
87173
+ if (state === "running") {
87174
+ this.#save(commandId, { ...current, state, started: true });
87175
+ } else if (isTerminal(state)) {
87176
+ this.#save(commandId, { ...current, state });
86715
87177
  }
86716
87178
  };
86717
- /** Receipts disappear when the runtime process exits. */
87179
+ /** Only commands without a terminal result can still be cancelled. */
86718
87180
  findUnsettledAttemptId = (commandId, sessionId) => {
86719
87181
  const receipt = this.#executions.get(commandId);
86720
87182
  return receipt?.sessionId === sessionId && !isTerminal(receipt.state) ? receipt.attemptId : null;
@@ -86725,7 +87187,9 @@ var ExecutionReceipts = class {
86725
87187
  };
86726
87188
  close = () => {
86727
87189
  this.#executions.clear();
87190
+ this.#recoveredCommands.clear();
86728
87191
  this.#terminalCountBySession.clear();
87192
+ this.#activeCount = 0;
86729
87193
  };
86730
87194
  pruneTerminalReceipts = (sessionId) => {
86731
87195
  if ((this.#terminalCountBySession.get(sessionId) ?? 0) <= MAX_TERMINAL_RECEIPTS) {
@@ -86734,6 +87198,8 @@ var ExecutionReceipts = class {
86734
87198
  for (const [commandId, receipt] of this.#executions) {
86735
87199
  if (receipt.sessionId === sessionId && isTerminal(receipt.state)) {
86736
87200
  this.#executions.delete(commandId);
87201
+ this.#recoveredCommands.delete(commandId);
87202
+ this.#table?.remove(commandId);
86737
87203
  const remaining = (this.#terminalCountBySession.get(sessionId) ?? 1) - 1;
86738
87204
  if (remaining > 0) {
86739
87205
  this.#terminalCountBySession.set(sessionId, remaining);
@@ -87261,10 +87727,14 @@ function buildSessionLayer(options) {
87261
87727
  const databases = channelSessionDatabasePaths(stateDirectory, channelId);
87262
87728
  const sessionDatabase = openAcpSessionDatabase(databases.sessions);
87263
87729
  let events;
87730
+ let operations;
87264
87731
  try {
87732
+ operations = new ExecutionReceipts(new ExecutionReceiptTable(sessionDatabase), options.onExecutionCountChange);
87265
87733
  events = new CachedSessionEventStore(
87266
87734
  new SessionEventCacheTable(openSessionEventCacheDatabase(databases.events)),
87267
- new SqliteSessionLiveEventPosition(new LiveEventPositionTable(sessionDatabase))
87735
+ new SqliteSessionLiveEventPosition(new LiveEventPositionTable(sessionDatabase)),
87736
+ void 0,
87737
+ (sessionId) => operations.terminalOutcomes(sessionId)
87268
87738
  );
87269
87739
  } catch (error62) {
87270
87740
  sessionDatabase.close();
@@ -87272,11 +87742,13 @@ function buildSessionLayer(options) {
87272
87742
  }
87273
87743
  const sessionStore = new SqliteSessionStore(new AcpSessionTable(sessionDatabase), events);
87274
87744
  const externalContent = new LocalExternalContentStore(stateDirectory);
87275
- const operations = new ExecutionReceipts();
87276
87745
  const activeSessions = new SessionTurns(operations, options.turnActivity);
87277
87746
  const directories = new WorkspaceDirectoryService(bindingId, defaultWorkspace);
87278
87747
  const manager = new SessionManager2({
87279
- observer: new SessionLayerObserver((event) => eventProjection.record(event), sink.publishInfo),
87748
+ observer: new SessionLayerObserver((event) => {
87749
+ operations.recordOutcome(event);
87750
+ eventProjection.record(event);
87751
+ }, sink.publishInfo),
87280
87752
  store: sessionStore,
87281
87753
  adapters: harnessAdapters,
87282
87754
  sessionIdentity: "native",
@@ -87297,7 +87769,15 @@ function buildSessionLayer(options) {
87297
87769
  });
87298
87770
  const eventProjection = new PromptLifecycle(
87299
87771
  sink,
87300
- (sessionId, event) => manager.recordLifecycleEvent(sessionId, event),
87772
+ async (sessionId, event) => {
87773
+ if (await manager.recordLifecycleEvent(sessionId, event)) {
87774
+ return true;
87775
+ }
87776
+ const recorded = events.append(sessionId, event);
87777
+ operations.recordOutcome(recorded);
87778
+ eventProjection.record(recorded);
87779
+ return true;
87780
+ },
87301
87781
  activeSessions.recordSessionEvent
87302
87782
  );
87303
87783
  const nativeSessions = new NativeSessionService(
@@ -87320,7 +87800,13 @@ function buildSessionLayer(options) {
87320
87800
  );
87321
87801
  })
87322
87802
  );
87803
+ const recoveredEvents = operations.recover().map(({ sessionId, event, recordedAt }) => events.append(sessionId, event, recordedAt));
87323
87804
  return {
87805
+ publishRecoveredOutcomes: () => {
87806
+ for (const event of recoveredEvents) {
87807
+ sink.publish(event);
87808
+ }
87809
+ },
87324
87810
  activeSessions,
87325
87811
  directories,
87326
87812
  eventProjection,
@@ -89117,12 +89603,14 @@ async function executePrompt({
89117
89603
  const appliedSettings = command.sessionConfigurations === void 0 ? void 0 : agentForSessionConfiguration(agent, command.sessionConfigurations);
89118
89604
  const executionAgent = appliedSettings?.agent ?? agent;
89119
89605
  const sessionConfigurations = appliedSettings?.sessionConfigurations;
89606
+ const recovery = command.historyPolicy === SessionHistoryPolicy.ExecutionOnly ? { kind: "execution-only" } : { kind: "interactive-only" };
89120
89607
  const preparation = {
89121
89608
  additionalDirectories: resolved.additionalDirectories,
89122
89609
  agent: executionAgent,
89123
89610
  cwd: resolved.cwd,
89124
89611
  sessionId: command.sessionId,
89125
- signal
89612
+ signal,
89613
+ recovery
89126
89614
  };
89127
89615
  let prepared = await sessions.prepareExecutionSession(preparation);
89128
89616
  if (command.sessionConfigurations !== void 0) {
@@ -89191,7 +89679,8 @@ async function executePrompt({
89191
89679
  resources: attachments.resources,
89192
89680
  sessionId: command.sessionId,
89193
89681
  signal,
89194
- targetCommandId: command.targetCommandId
89682
+ targetCommandId: command.targetCommandId,
89683
+ recovery
89195
89684
  });
89196
89685
  stopReason = result.stopReason;
89197
89686
  }
@@ -89360,6 +89849,12 @@ var sessionPromptCommand = {
89360
89849
  return;
89361
89850
  }
89362
89851
  if (receipt.kind === "duplicate") {
89852
+ const outcome = receipt.outcome;
89853
+ if (outcome !== void 0) {
89854
+ void context.eventProjection.replayOutcome(message.sessionId, outcome).catch((cause) => {
89855
+ console.error("Could not replay the persisted execution outcome", cause);
89856
+ });
89857
+ }
89363
89858
  return;
89364
89859
  }
89365
89860
  const turn = context.sessionTurns.start(message, receipt.attemptId);
@@ -90485,7 +90980,8 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
90485
90980
  resolveAgent: (agentId, resolveSignal) => resolveCatalogAgent(config2, agentId, resolveSignal),
90486
90981
  sink: publisher,
90487
90982
  stateDirectory: config2.localContext.stateDirectory,
90488
- timer
90983
+ timer,
90984
+ onExecutionCountChange: options.onExecutionCountChange ?? (() => void 0)
90489
90985
  });
90490
90986
  const {
90491
90987
  activeSessions,
@@ -90555,6 +91051,7 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
90555
91051
  reportUpgradeAvailability,
90556
91052
  diagnostics,
90557
91053
  onConnected: () => {
91054
+ layer.publishRecoveredOutcomes();
90558
91055
  if (catalogPublished) {
90559
91056
  options.onConnectionStatus?.("online");
90560
91057
  }
@@ -90577,6 +91074,7 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
90577
91074
  }
90578
91075
  } finally {
90579
91076
  shutdown.abort();
91077
+ operations.beginShutdown();
90580
91078
  await activeSessions.close();
90581
91079
  try {
90582
91080
  await catalogPublication;
@@ -90713,6 +91211,10 @@ async function createMobiusRuntimeEngine(connection, signal, environment = proce
90713
91211
  var MobiusRuntimeEngine = class {
90714
91212
  #config;
90715
91213
  #options;
91214
+ #activeTurnCount = 0;
91215
+ get activeTurnCount() {
91216
+ return this.#activeTurnCount;
91217
+ }
90716
91218
  #state = new IdleMobiusRuntimeState();
90717
91219
  constructor(config2, options = {}) {
90718
91220
  validateRuntimeConfiguration(config2);
@@ -90728,11 +91230,15 @@ var MobiusRuntimeEngine = class {
90728
91230
  try {
90729
91231
  await runRuntime(this.#config, signal, void 0, {
90730
91232
  ...this.#options.turnActivity === void 0 ? {} : { turnActivity: this.#options.turnActivity },
91233
+ onExecutionCountChange: (count) => {
91234
+ this.#activeTurnCount = count;
91235
+ },
90731
91236
  ...this.#options.onConnectionStatus === void 0 ? {} : { onConnectionStatus: this.#options.onConnectionStatus },
90732
91237
  ...this.#options.reportUpgradeAvailability === void 0 ? {} : { reportUpgradeAvailability: this.#options.reportUpgradeAvailability },
90733
91238
  ...this.#options.processLogsDirectory === void 0 ? {} : { processLogsDirectory: this.#options.processLogsDirectory }
90734
91239
  });
90735
91240
  } finally {
91241
+ this.#activeTurnCount = 0;
90736
91242
  this.#state = new FinishedMobiusRuntimeState();
90737
91243
  }
90738
91244
  };