@moxt-ai/mobius 0.0.150 → 0.0.153

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 (61) hide show
  1. package/dist/acp/agent-runtime-manager.d.ts.map +1 -1
  2. package/dist/acp/agent-runtime-manager.js +8 -1
  3. package/dist/acp/agent-runtime-manager.js.map +1 -1
  4. package/dist/acp/agent-session.d.ts.map +1 -1
  5. package/dist/acp/agent-session.js +8 -4
  6. package/dist/acp/agent-session.js.map +1 -1
  7. package/dist/acp/session-contracts.d.ts +2 -1
  8. package/dist/acp/session-contracts.d.ts.map +1 -1
  9. package/dist/acp/session-contracts.js +3 -1
  10. package/dist/acp/session-contracts.js.map +1 -1
  11. package/dist/acp/session-store.d.ts +2 -1
  12. package/dist/acp/session-store.d.ts.map +1 -1
  13. package/dist/acp/session-store.js +6 -2
  14. package/dist/acp/session-store.js.map +1 -1
  15. package/dist/cli.js +274 -76
  16. package/dist/cli.js.map +3 -3
  17. package/dist/daemon-active-turn.d.ts +1 -0
  18. package/dist/daemon-active-turn.d.ts.map +1 -1
  19. package/dist/daemon-active-turn.js +1 -0
  20. package/dist/daemon-active-turn.js.map +1 -1
  21. package/dist/daemon-command-handler.d.ts.map +1 -1
  22. package/dist/daemon-command-handler.js +1 -0
  23. package/dist/daemon-command-handler.js.map +1 -1
  24. package/dist/daemon-prompt-executor.d.ts.map +1 -1
  25. package/dist/daemon-prompt-executor.js +28 -8
  26. package/dist/daemon-prompt-executor.js.map +1 -1
  27. package/dist/daemon-turn-registry.d.ts +15 -14
  28. package/dist/daemon-turn-registry.d.ts.map +1 -1
  29. package/dist/daemon-turn-registry.js +32 -34
  30. package/dist/daemon-turn-registry.js.map +1 -1
  31. package/dist/generated/acp-session-store-migrations.d.ts +3 -0
  32. package/dist/generated/acp-session-store-migrations.d.ts.map +1 -1
  33. package/dist/generated/acp-session-store-migrations.js +5 -1
  34. package/dist/generated/acp-session-store-migrations.js.map +1 -1
  35. package/dist/index.js +264 -73
  36. package/dist/index.js.map +3 -3
  37. package/dist/native-sessions/handle-native-requests.d.ts +2 -0
  38. package/dist/native-sessions/handle-native-requests.d.ts.map +1 -1
  39. package/dist/native-sessions/handle-native-requests.js +12 -1
  40. package/dist/native-sessions/handle-native-requests.js.map +1 -1
  41. package/dist/native-sessions/live-history-page.d.ts.map +1 -1
  42. package/dist/native-sessions/live-history-page.js +2 -0
  43. package/dist/native-sessions/live-history-page.js.map +1 -1
  44. package/dist/native-sessions/service.d.ts.map +1 -1
  45. package/dist/native-sessions/service.js +47 -0
  46. package/dist/native-sessions/service.js.map +1 -1
  47. package/dist/relay-connection/session-event-outbox.d.ts +1 -0
  48. package/dist/relay-connection/session-event-outbox.d.ts.map +1 -1
  49. package/dist/relay-connection/session-event-outbox.js +37 -22
  50. package/dist/relay-connection/session-event-outbox.js.map +1 -1
  51. package/dist/relay-connection/session-event-publisher.d.ts +1 -0
  52. package/dist/relay-connection/session-event-publisher.d.ts.map +1 -1
  53. package/dist/relay-connection/session-event-publisher.js +10 -2
  54. package/dist/relay-connection/session-event-publisher.js.map +1 -1
  55. package/dist/runtime.js +266 -75
  56. package/dist/runtime.js.map +3 -3
  57. package/dist/schema/acp-session-store/acp-session.d.ts +19 -0
  58. package/dist/schema/acp-session-store/acp-session.d.ts.map +1 -1
  59. package/dist/schema/acp-session-store/acp-session.js +1 -0
  60. package/dist/schema/acp-session-store/acp-session.js.map +1 -1
  61. package/package.json +10 -3
package/dist/cli.js CHANGED
@@ -3381,7 +3381,7 @@ var program = new Command();
3381
3381
  // package.json
3382
3382
  var package_default = {
3383
3383
  name: "@moxt-ai/mobius",
3384
- version: "0.0.150",
3384
+ version: "0.0.153",
3385
3385
  description: "Mobius CLI for pairing and running local AI agent services.",
3386
3386
  type: "module",
3387
3387
  bin: {
@@ -3416,7 +3416,9 @@ var package_default = {
3416
3416
  dev: "node --watch src/cli.ts",
3417
3417
  prepack: "pnpm build",
3418
3418
  start: "node dist/cli.js",
3419
- typecheck: "tsc --project tsconfig.json"
3419
+ typecheck: "tsc --project tsconfig.json && tsc --project tests/tsconfig.json",
3420
+ "test:controlled": "vitest run tests/sessions tests/runtime --maxWorkers=4",
3421
+ "test:agents": "vitest run tests/agents --maxWorkers=2"
3420
3422
  },
3421
3423
  dependencies: {
3422
3424
  "@agentclientprotocol/sdk": "^1.4.0",
@@ -3430,7 +3432,12 @@ var package_default = {
3430
3432
  "@types/better-sqlite3": "7.6.13",
3431
3433
  "@types/node": "^24.13.5",
3432
3434
  "drizzle-orm": "^0.45.2",
3433
- esbuild: "0.28.2"
3435
+ esbuild: "0.28.2",
3436
+ "@moxt-ai/mobius-sdk": "workspace:*",
3437
+ "@moxt-ai/mobius-test": "workspace:*",
3438
+ valibot: "1.5.0",
3439
+ ws: "^8.21.3",
3440
+ "@types/ws": "^8.18.1"
3434
3441
  },
3435
3442
  main: "dist/index.js",
3436
3443
  keywords: [],
@@ -29554,6 +29561,11 @@ var assistantHistoryMessageSchema = external_exports.strictObject({
29554
29561
  }).readonly();
29555
29562
  var historyMessage = external_exports.discriminatedUnion("role", [userHistoryMessage, assistantHistoryMessageSchema]);
29556
29563
  var availableSessionHistoryObjectSchema = external_exports.strictObject({
29564
+ streamPosition: external_exports.strictObject({
29565
+ commandId: identifierStringSchema,
29566
+ epoch: identifierStringSchema,
29567
+ sequence: external_exports.number().int().nonnegative()
29568
+ }).readonly().optional(),
29557
29569
  status: external_exports.literal("available"),
29558
29570
  messages: external_exports.array(historyMessage).max(SESSION_HISTORY_MESSAGE_LIMIT).readonly(),
29559
29571
  truncated: external_exports.boolean(),
@@ -29672,6 +29684,12 @@ var nativeSessionRequestSchema = external_exports.discriminatedUnion("action", [
29672
29684
  entryId: identifierStringSchema,
29673
29685
  targetSessionId: identifierStringSchema
29674
29686
  }).readonly(),
29687
+ external_exports.strictObject({
29688
+ ...identity,
29689
+ action: external_exports.literal("fork-recorded"),
29690
+ targetSessionId: identifierStringSchema,
29691
+ throughCommandId: identifierStringSchema
29692
+ }).readonly(),
29675
29693
  external_exports.strictObject({
29676
29694
  ...identity,
29677
29695
  action: external_exports.literal("delete"),
@@ -29695,6 +29713,12 @@ var nativeSessionCommandSchema = external_exports.discriminatedUnion("action", [
29695
29713
  entryId: identifierStringSchema,
29696
29714
  targetSessionId: identifierStringSchema
29697
29715
  }).readonly(),
29716
+ external_exports.strictObject({
29717
+ ...identity,
29718
+ action: external_exports.literal("fork-recorded"),
29719
+ targetSessionId: identifierStringSchema,
29720
+ throughCommandId: identifierStringSchema
29721
+ }).readonly(),
29698
29722
  external_exports.strictObject({
29699
29723
  ...identity,
29700
29724
  action: external_exports.literal("delete"),
@@ -29744,7 +29768,7 @@ var nativeSessionResultSchema = external_exports.discriminatedUnion("action", [
29744
29768
  }).readonly(),
29745
29769
  external_exports.strictObject({
29746
29770
  ...resultEnvelope,
29747
- action: external_exports.enum(["import", "fork"]),
29771
+ action: external_exports.enum(["import", "fork", "fork-recorded"]),
29748
29772
  targetSessionId: identifierStringSchema,
29749
29773
  ready: sessionReadyEventSchema,
29750
29774
  title: external_exports.string().max(500)
@@ -29793,6 +29817,7 @@ var SESSION_EVENT_KIND = "session.event";
29793
29817
  var SESSION_EVENT_ACK_KIND = "session.event.ack";
29794
29818
  var SESSION_EVENT_DEFERRED_KIND = "session.event.deferred";
29795
29819
  var SESSION_EVENT_REJECTED_KIND = "session.event.rejected";
29820
+ var SESSION_FORKED_KIND = "session.forked";
29796
29821
  var SessionEventRejectionCode = {
29797
29822
  IdempotencyConflict: "idempotency_conflict",
29798
29823
  InvalidEvent: "invalid_event",
@@ -29810,13 +29835,13 @@ var reliableDeliverySchema = external_exports.strictObject({
29810
29835
  sourceSeq: external_exports.string().regex(/^(?:0|[1-9][0-9]*)$/u).max(40),
29811
29836
  streamId: identifierStringSchema
29812
29837
  }).readonly();
29813
- var reliableEventEnvelopeSchema = external_exports.strictObject({
29838
+ var reliableSessionEventEnvelopeSchema = external_exports.strictObject({
29814
29839
  delivery: reliableDeliverySchema,
29815
29840
  kind: external_exports.literal(SESSION_EVENT_KIND),
29816
29841
  payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
29817
29842
  sessionId: identifierStringSchema
29818
29843
  }).readonly();
29819
- var eventEnvelopeSchema = external_exports.union([ephemeralEventEnvelopeSchema, reliableEventEnvelopeSchema]).readonly();
29844
+ var eventEnvelopeSchema = external_exports.union([ephemeralEventEnvelopeSchema, reliableSessionEventEnvelopeSchema]).readonly();
29820
29845
  var acknowledgementOutcomeSchema = external_exports.discriminatedUnion("kind", [
29821
29846
  external_exports.strictObject({ kind: external_exports.literal("relayed") }).readonly(),
29822
29847
  external_exports.strictObject({
@@ -29845,6 +29870,12 @@ var eventRejectedSchema = external_exports.strictObject({
29845
29870
  }).readonly();
29846
29871
  var SESSION_QUEUE_CHANGED_KIND = "session.queue.changed";
29847
29872
  var sessionQueueChangedSchema = external_exports.strictObject({ kind: external_exports.literal(SESSION_QUEUE_CHANGED_KIND), sessionId: identifierStringSchema }).readonly();
29873
+ var sessionForkedSchema = external_exports.strictObject({
29874
+ kind: external_exports.literal(SESSION_FORKED_KIND),
29875
+ sessionId: identifierStringSchema,
29876
+ sourceSessionId: identifierStringSchema,
29877
+ throughCommandId: identifierStringSchema
29878
+ }).readonly();
29848
29879
  function decode5(encoded, schema) {
29849
29880
  let value;
29850
29881
  try {
@@ -29882,6 +29913,8 @@ function decodeRecordedSessionEvent(encoded) {
29882
29913
  switch (kind) {
29883
29914
  case SESSION_QUEUE_CHANGED_KIND:
29884
29915
  return decode5(encoded, sessionQueueChangedSchema);
29916
+ case SESSION_FORKED_KIND:
29917
+ return decode5(encoded, sessionForkedSchema);
29885
29918
  case AGENT_ACTIVITY_KIND:
29886
29919
  return decodeAgentActivityEvent(encoded);
29887
29920
  case AGENT_ACTIVITY_NARRATION_KIND:
@@ -32686,13 +32719,15 @@ var AcpPreparedSession = class {
32686
32719
  capabilities;
32687
32720
  commands;
32688
32721
  configOptions;
32722
+ prompted;
32689
32723
  restored;
32690
- constructor(agentName, agentVersion, capabilities, commands, configOptions, restored) {
32724
+ constructor(agentName, agentVersion, capabilities, commands, configOptions, prompted, restored) {
32691
32725
  this.agentName = agentName;
32692
32726
  this.agentVersion = agentVersion;
32693
32727
  this.capabilities = capabilities;
32694
32728
  this.commands = commands;
32695
32729
  this.configOptions = configOptions;
32730
+ this.prompted = prompted;
32696
32731
  this.restored = restored;
32697
32732
  }
32698
32733
  };
@@ -38862,6 +38897,10 @@ var acpSessionStoreMigrations = [
38862
38897
  {
38863
38898
  "name": "0000_slimy_captain_midlands.sql",
38864
38899
  "sql": "CREATE TABLE `acp_session` (\n `session_id` text PRIMARY KEY NOT NULL,\n `agent_id` text NOT NULL,\n `native_session_id` text NOT NULL,\n `cwd` text NOT NULL,\n `additional_directories` text NOT NULL,\n `prompted` integer NOT NULL,\n `title` text NOT NULL,\n `updated_at` text NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `deleted_acp_session` (\n `session_id` text PRIMARY KEY NOT NULL\n);\n"
38900
+ },
38901
+ {
38902
+ "name": "0001_regular_ultimates.sql",
38903
+ "sql": "ALTER TABLE `acp_session` ADD `last_terminal_command_id` text;"
38865
38904
  }
38866
38905
  ];
38867
38906
 
@@ -38879,6 +38918,7 @@ var acpSession = sqliteTable("acp_session", {
38879
38918
  nativeSessionId: text("native_session_id").notNull(),
38880
38919
  cwd: text("cwd").notNull(),
38881
38920
  additionalDirectories: text("additional_directories").notNull(),
38921
+ lastTerminalCommandId: text("last_terminal_command_id"),
38882
38922
  prompted: integer2("prompted", { mode: "boolean" }).notNull(),
38883
38923
  title: text("title").notNull(),
38884
38924
  updatedAt: text("updated_at").notNull()
@@ -38894,15 +38934,17 @@ var AcpStoredSession = class {
38894
38934
  additionalDirectories;
38895
38935
  agentId;
38896
38936
  cwd;
38937
+ lastTerminalCommandId;
38897
38938
  nativeSessionId;
38898
38939
  prompted;
38899
38940
  sessionId;
38900
38941
  title;
38901
38942
  updatedAt;
38902
- constructor(sessionId, agentId, nativeSessionId, cwd, additionalDirectories, prompted, title2, updatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
38943
+ constructor(sessionId, agentId, nativeSessionId, cwd, additionalDirectories, prompted, title2, updatedAt = (/* @__PURE__ */ new Date()).toISOString(), lastTerminalCommandId = null) {
38903
38944
  this.additionalDirectories = [...additionalDirectories];
38904
38945
  this.agentId = agentId;
38905
38946
  this.cwd = cwd;
38947
+ this.lastTerminalCommandId = lastTerminalCommandId;
38906
38948
  this.nativeSessionId = nativeSessionId;
38907
38949
  this.prompted = prompted;
38908
38950
  this.sessionId = sessionId;
@@ -38963,6 +39005,7 @@ var storedSessionRowSchema = external_exports.object({
38963
39005
  additionalDirectories: external_exports.string(),
38964
39006
  agentId: external_exports.string(),
38965
39007
  cwd: external_exports.string(),
39008
+ lastTerminalCommandId: external_exports.string().nullable(),
38966
39009
  nativeSessionId: external_exports.string(),
38967
39010
  prompted: external_exports.boolean(),
38968
39011
  sessionId: external_exports.string(),
@@ -38982,7 +39025,8 @@ function decodeStoredSession(value) {
38982
39025
  additionalDirectories,
38983
39026
  row.prompted,
38984
39027
  row.title,
38985
- row.updatedAt
39028
+ row.updatedAt,
39029
+ row.lastTerminalCommandId
38986
39030
  );
38987
39031
  }
38988
39032
  var SqliteAcpSessionStore = class {
@@ -39049,6 +39093,7 @@ var SqliteAcpSessionStore = class {
39049
39093
  additionalDirectories: JSON.stringify(session.additionalDirectories),
39050
39094
  agentId: session.agentId,
39051
39095
  cwd: session.cwd,
39096
+ lastTerminalCommandId: session.lastTerminalCommandId,
39052
39097
  nativeSessionId: session.nativeSessionId,
39053
39098
  prompted: session.prompted,
39054
39099
  sessionId: session.sessionId,
@@ -39101,6 +39146,7 @@ var AgentSession = class _AgentSession {
39101
39146
  #commands = [];
39102
39147
  #configRevision = 0;
39103
39148
  #configOptions = [];
39149
+ #lastTerminalCommandId = null;
39104
39150
  #prompted = false;
39105
39151
  #nativePrompt = null;
39106
39152
  #sessionLoad = { kind: "not-requested" };
@@ -39204,6 +39250,7 @@ var AgentSession = class _AgentSession {
39204
39250
  this.#capabilities,
39205
39251
  this.#commands,
39206
39252
  this.#configOptions,
39253
+ this.#prompted,
39207
39254
  this.#restored
39208
39255
  );
39209
39256
  loadHistory = async (signal) => {
@@ -39437,6 +39484,7 @@ var AgentSession = class _AgentSession {
39437
39484
  } else if (stored instanceof FoundAcpStoredSession && stored.session.agentId === this.#agentId && stored.session.cwd === this.#cwd && arraysEqual(stored.session.additionalDirectories, this.#additionalDirectories)) {
39438
39485
  this.selectNativeSession(stored.session.nativeSessionId);
39439
39486
  this.#prompted = stored.session.prompted;
39487
+ this.#lastTerminalCommandId = stored.session.lastTerminalCommandId;
39440
39488
  this.#sessionTitle = stored.session.title;
39441
39489
  this.#restored = true;
39442
39490
  if (this.#capabilities.resumeSession) {
@@ -39510,7 +39558,9 @@ var AgentSession = class _AgentSession {
39510
39558
  this.#cwd,
39511
39559
  this.#additionalDirectories,
39512
39560
  this.#prompted,
39513
- this.#sessionTitle ?? ""
39561
+ this.#sessionTitle ?? "",
39562
+ (/* @__PURE__ */ new Date()).toISOString(),
39563
+ this.#lastTerminalCommandId
39514
39564
  )
39515
39565
  );
39516
39566
  };
@@ -39710,7 +39760,9 @@ var AgentSession = class _AgentSession {
39710
39760
  this.#cwd,
39711
39761
  this.#additionalDirectories,
39712
39762
  true,
39713
- this.#sessionTitle
39763
+ this.#sessionTitle,
39764
+ (/* @__PURE__ */ new Date()).toISOString(),
39765
+ this.#lastTerminalCommandId
39714
39766
  )
39715
39767
  );
39716
39768
  this.#prompted = true;
@@ -39762,6 +39814,20 @@ var AgentSession = class _AgentSession {
39762
39814
  usage: result.usage ?? null
39763
39815
  });
39764
39816
  }
39817
+ this.#lastTerminalCommandId = options.commandId;
39818
+ this.#store.write(
39819
+ new AcpStoredSession(
39820
+ this.#mobiusSessionId,
39821
+ this.#agentId,
39822
+ this.#updates.sessionId(),
39823
+ this.#cwd,
39824
+ this.#additionalDirectories,
39825
+ true,
39826
+ this.#sessionTitle ?? "",
39827
+ (/* @__PURE__ */ new Date()).toISOString(),
39828
+ this.#lastTerminalCommandId
39829
+ )
39830
+ );
39765
39831
  return result;
39766
39832
  } catch (error62) {
39767
39833
  await running.activities.finish();
@@ -39954,7 +40020,9 @@ var AgentSession = class _AgentSession {
39954
40020
  this.#cwd,
39955
40021
  this.#additionalDirectories,
39956
40022
  this.#prompted,
39957
- this.#sessionTitle ?? ""
40023
+ this.#sessionTitle ?? "",
40024
+ (/* @__PURE__ */ new Date()).toISOString(),
40025
+ this.#lastTerminalCommandId
39958
40026
  )
39959
40027
  );
39960
40028
  }
@@ -41000,9 +41068,17 @@ var AgentRuntimeManager = class {
41000
41068
  const openedKey = `${options2.agent.id}:${runtime.nativeSessionId()}`;
41001
41069
  this.#nativeOperations.add(openedKey);
41002
41070
  try {
41071
+ const prepared = runtime.prepared();
41072
+ if (origin.kind === "fork" && options2.agent.id === "claude") {
41073
+ await this.#retireSessionProcess(
41074
+ options2.sessionId,
41075
+ runtime,
41076
+ async () => await runtime.closeProcess()
41077
+ );
41078
+ }
41003
41079
  return {
41004
41080
  sessionId: options2.sessionId,
41005
- prepared: runtime.prepared()
41081
+ prepared
41006
41082
  };
41007
41083
  } finally {
41008
41084
  this.#nativeOperations.delete(openedKey);
@@ -42523,6 +42599,7 @@ var ActiveDaemonTurn = class {
42523
42599
  commandId: decodeIdentifier(this.command.commandId),
42524
42600
  epoch: this.#epoch,
42525
42601
  messages: this.timelineMessages(),
42602
+ sequence: this.#sequence,
42526
42603
  state: "running",
42527
42604
  truncated: false
42528
42605
  };
@@ -42550,7 +42627,7 @@ var ActiveDaemonTurn = class {
42550
42627
  };
42551
42628
 
42552
42629
  // src/daemon-turn-registry.ts
42553
- var COMPLETED_TURN_RECOVERY_LIMIT = 32;
42630
+ var MAX_SESSIONS_WITH_COMPLETED_RECOVERY = 32;
42554
42631
  var waitForTurnCompletion = (turn, waitSignal) => {
42555
42632
  waitSignal.throwIfAborted();
42556
42633
  return new Promise((resolve9, reject) => {
@@ -42576,46 +42653,46 @@ var waitForTurnCompletion = (turn, waitSignal) => {
42576
42653
  });
42577
42654
  };
42578
42655
  var DaemonTurnRegistry = class {
42579
- #turns = /* @__PURE__ */ new Map();
42580
- #completed = /* @__PURE__ */ new Map();
42656
+ #inFlightByCommandId = /* @__PURE__ */ new Map();
42657
+ #latestCompletedBySessionId = /* @__PURE__ */ new Map();
42581
42658
  #operations;
42582
42659
  constructor(operations) {
42583
42660
  this.#operations = operations;
42584
42661
  }
42585
42662
  start = (command, attemptId) => {
42586
42663
  const turn = new ActiveDaemonTurn(command, attemptId);
42587
- this.#turns.set(command.commandId, turn);
42664
+ this.#inFlightByCommandId.set(command.commandId, turn);
42588
42665
  return turn;
42589
42666
  };
42590
- get = (commandId) => this.#turns.get(commandId);
42667
+ get = (commandId) => this.#inFlightByCommandId.get(commandId);
42591
42668
  finish = (turn) => {
42592
42669
  const recovery = turn.recovery();
42593
- this.#turns.delete(turn.command.commandId);
42670
+ this.#inFlightByCommandId.delete(turn.command.commandId);
42594
42671
  if (recovery !== null) {
42595
- this.#completed.delete(turn.command.sessionId);
42596
- this.#completed.set(turn.command.sessionId, {
42597
- command: turn.command,
42598
- recovery: { ...recovery, state: "completed" }
42599
- });
42600
- while (this.#completed.size > COMPLETED_TURN_RECOVERY_LIMIT) {
42601
- const oldestSessionId = this.#completed.keys().next().value;
42602
- if (oldestSessionId === void 0) {
42603
- break;
42604
- }
42605
- this.#completed.delete(oldestSessionId);
42606
- }
42672
+ this.retainLatestCompletedRecovery(turn.command, { ...recovery, state: "completed" });
42607
42673
  }
42608
42674
  turn.finish();
42609
42675
  };
42676
+ retainLatestCompletedRecovery = (command, recovery) => {
42677
+ this.#latestCompletedBySessionId.delete(command.sessionId);
42678
+ this.#latestCompletedBySessionId.set(command.sessionId, { command, recovery });
42679
+ while (this.#latestCompletedBySessionId.size > MAX_SESSIONS_WITH_COMPLETED_RECOVERY) {
42680
+ const oldestSessionId = this.#latestCompletedBySessionId.keys().next().value;
42681
+ if (oldestSessionId === void 0) {
42682
+ break;
42683
+ }
42684
+ this.#latestCompletedBySessionId.delete(oldestSessionId);
42685
+ }
42686
+ };
42610
42687
  recordEvent = (event) => {
42611
42688
  if ("commandId" in event) {
42612
- const turn = this.#turns.get(event.commandId);
42689
+ const turn = this.#inFlightByCommandId.get(event.commandId);
42613
42690
  if (turn !== void 0 && turn.command.sessionId === event.sessionId) {
42614
42691
  return turn.recordEvent(event);
42615
42692
  }
42616
42693
  }
42617
42694
  if (event.kind === SESSION_PERMISSION_RESOLVED_KIND || event.kind === SESSION_ELICITATION_RESOLVED_KIND) {
42618
- for (const turn of this.#turns.values()) {
42695
+ for (const turn of this.#inFlightByCommandId.values()) {
42619
42696
  if (turn.matchesInteraction(event.interactionId, event.sessionId)) {
42620
42697
  return turn.recordEvent(event);
42621
42698
  }
@@ -42624,17 +42701,17 @@ var DaemonTurnRegistry = class {
42624
42701
  return event;
42625
42702
  };
42626
42703
  recordSteeringPrompt = (command) => {
42627
- const target = this.#turns.get(command.targetCommandId);
42704
+ const target = this.#inFlightByCommandId.get(command.targetCommandId);
42628
42705
  return target?.recordSteeringPrompt(command) ?? false;
42629
42706
  };
42630
42707
  executionSnapshot = async (matches) => {
42631
42708
  const matching = [];
42632
- for (const turn of this.#turns.values()) {
42709
+ for (const turn of this.#inFlightByCommandId.values()) {
42633
42710
  if (await matches(turn.command)) {
42634
42711
  matching.push(turn);
42635
42712
  }
42636
42713
  }
42637
- const current = matching.filter((turn) => this.#turns.get(turn.command.commandId) === turn);
42714
+ const current = matching.filter((turn) => this.#inFlightByCommandId.get(turn.command.commandId) === turn);
42638
42715
  return {
42639
42716
  turn: current.map((turn) => turn.snapshot()).find((snapshot) => snapshot !== null) ?? null,
42640
42717
  queued: current.filter((turn) => turn.isQueued()).map(({ command }) => ({
@@ -42645,7 +42722,7 @@ var DaemonTurnRegistry = class {
42645
42722
  };
42646
42723
  };
42647
42724
  snapshot = (matches) => {
42648
- for (const turn of this.#turns.values()) {
42725
+ for (const turn of this.#inFlightByCommandId.values()) {
42649
42726
  if (!matches(turn.command)) {
42650
42727
  continue;
42651
42728
  }
@@ -42657,7 +42734,7 @@ var DaemonTurnRegistry = class {
42657
42734
  return null;
42658
42735
  };
42659
42736
  recovery = (matches) => {
42660
- for (const turn of this.#turns.values()) {
42737
+ for (const turn of this.#inFlightByCommandId.values()) {
42661
42738
  if (!matches(turn.command)) {
42662
42739
  continue;
42663
42740
  }
@@ -42666,7 +42743,7 @@ var DaemonTurnRegistry = class {
42666
42743
  return recovery;
42667
42744
  }
42668
42745
  }
42669
- for (const completed of this.#completed.values()) {
42746
+ for (const completed of this.#latestCompletedBySessionId.values()) {
42670
42747
  if (matches(completed.command)) {
42671
42748
  return completed.recovery;
42672
42749
  }
@@ -42674,17 +42751,17 @@ var DaemonTurnRegistry = class {
42674
42751
  return null;
42675
42752
  };
42676
42753
  close = async () => {
42677
- const turns = [...this.#turns.values()];
42754
+ const turns = [...this.#inFlightByCommandId.values()];
42678
42755
  for (const turn of turns) {
42679
42756
  turn.abortDaemon();
42680
42757
  }
42681
42758
  await Promise.all(turns.map((turn) => turn.completion));
42682
- this.#completed.clear();
42759
+ this.#latestCompletedBySessionId.clear();
42683
42760
  };
42684
42761
  admitAndWait = async (turn, waitSignal, onQueued) => {
42685
42762
  const command = turn.command;
42686
42763
  waitSignal.throwIfAborted();
42687
- const head = () => [...this.#turns.values()].find(
42764
+ const head = () => [...this.#inFlightByCommandId.values()].find(
42688
42765
  (turn2) => turn2.command.sessionId === command.sessionId && turn2.command.disposition !== SessionPromptDisposition.Steer
42689
42766
  );
42690
42767
  if (command.disposition === SessionPromptDisposition.Queue) {
@@ -43413,6 +43490,7 @@ var readCodexFullHistory = async (agent, cwd, nativeSessionId, signal, nativeTur
43413
43490
 
43414
43491
  // src/native-sessions/service.ts
43415
43492
  import { randomUUID as randomUUID3 } from "node:crypto";
43493
+ import { basename as basename5 } from "node:path";
43416
43494
 
43417
43495
  // src/native-sessions/acp-history.ts
43418
43496
  async function readAcpHistory(sessions, directories, bindingId, options, nativeSessionId) {
@@ -43553,6 +43631,64 @@ var NativeSessionService = class {
43553
43631
  sessionId: command.sessionId
43554
43632
  };
43555
43633
  }
43634
+ if (command.action === "fork-recorded") {
43635
+ const source = this.#sessions.findSessions([command.sessionId])[0];
43636
+ if (source === void 0 || !source.prompted) {
43637
+ throw new AcpExecutionError(
43638
+ "The source conversation has no resumable native Agent session",
43639
+ {},
43640
+ "native_session_not_found"
43641
+ );
43642
+ }
43643
+ if (source.agentId !== command.agentId || source.cwd !== cwd || source.additionalDirectories.length !== additionalDirectories.length || source.additionalDirectories.some((path3, index) => path3 !== additionalDirectories[index])) {
43644
+ throw new AcpExecutionError(
43645
+ "The source conversation execution target does not match the fork request",
43646
+ {},
43647
+ "native_session_target_changed"
43648
+ );
43649
+ }
43650
+ if (source.lastTerminalCommandId !== command.throughCommandId) {
43651
+ throw new AcpExecutionError(
43652
+ "The selected Turn is no longer the source conversation head",
43653
+ {},
43654
+ "fork_point_stale"
43655
+ );
43656
+ }
43657
+ const opened2 = await this.#sessions.openNativeSession(
43658
+ { ...options, sessionId: command.targetSessionId },
43659
+ { kind: "fork", nativeSessionId: source.nativeSessionId }
43660
+ );
43661
+ signal.throwIfAborted();
43662
+ const directory = await this.#directories.list(this.#config.bindingId, command.directoryId, signal);
43663
+ const ready2 = {
43664
+ additionalDirectories: additionalDirectories.map((path3, index) => ({
43665
+ directoryId: command.additionalDirectoryIds[index] ?? command.directoryId,
43666
+ name: basename5(path3) || path3
43667
+ })),
43668
+ agentId: command.agentId,
43669
+ agentName: opened2.prepared.agentName,
43670
+ agentVersion: opened2.prepared.agentVersion,
43671
+ capabilities: opened2.prepared.capabilities,
43672
+ commands: opened2.prepared.commands,
43673
+ configOptions: opened2.prepared.configOptions,
43674
+ directoryId: command.directoryId,
43675
+ directoryLabel: directory.directoryLabel,
43676
+ kind: "session.ready",
43677
+ requestId: command.requestId,
43678
+ restored: true,
43679
+ sessionId: opened2.sessionId
43680
+ };
43681
+ this.record(command, "native_recorded_session_forked");
43682
+ return {
43683
+ action: "fork-recorded",
43684
+ kind: "native.session.result",
43685
+ ready: ready2,
43686
+ requestId: command.requestId,
43687
+ sessionId: command.sessionId,
43688
+ targetSessionId: command.targetSessionId,
43689
+ title: source.title || "Untitled session"
43690
+ };
43691
+ }
43556
43692
  const prepared = await this.#sessions.prepareNativeSessionBrowser(options);
43557
43693
  if (!prepared.capabilities.listSessions) {
43558
43694
  throw new AcpExecutionError(
@@ -44874,7 +45010,7 @@ function createAgentSettingsDiscoveryHandler(dependencies) {
44874
45010
  // src/artifacts/local-file-streamer.ts
44875
45011
  import { createReadStream as createReadStream2 } from "node:fs";
44876
45012
  import { realpath as realpath9, stat as stat8 } from "node:fs/promises";
44877
- import { basename as basename5, isAbsolute as isAbsolute10, normalize as normalize4, resolve as resolve7 } from "node:path";
45013
+ import { basename as basename6, isAbsolute as isAbsolute10, normalize as normalize4, resolve as resolve7 } from "node:path";
44878
45014
  import { fileURLToPath as fileURLToPath2 } from "node:url";
44879
45015
  var CHUNK_BYTES = 192 * 1024;
44880
45016
  function requestedPath(value) {
@@ -44925,7 +45061,7 @@ async function streamLocalFile(command, send2, signal, workingDirectory) {
44925
45061
  kind: FILE_BEGIN_KIND,
44926
45062
  mediaType: raster?.mimeType ?? "application/octet-stream",
44927
45063
  modifiedAt: before.mtime.toISOString(),
44928
- name: basename5(target) || "file",
45064
+ name: basename6(target) || "file",
44929
45065
  requestId: command.requestId,
44930
45066
  sessionId: command.sessionId
44931
45067
  });
@@ -44955,7 +45091,7 @@ async function streamLocalFile(command, send2, signal, workingDirectory) {
44955
45091
  }
44956
45092
 
44957
45093
  // src/authorization/resolve-session-directories.ts
44958
- import { basename as basename6 } from "node:path";
45094
+ import { basename as basename7 } from "node:path";
44959
45095
  var ResolvedSessionDirectories = class {
44960
45096
  additionalDirectories;
44961
45097
  additionalDirectoryEntries;
@@ -44980,7 +45116,7 @@ async function resolveSessionDirectories(directories, bindingId, directoryId2, a
44980
45116
  additionalDirectories.push(directory);
44981
45117
  additionalDirectoryEntries.push({
44982
45118
  directoryId: additionalDirectoryId,
44983
- name: basename6(directory)
45119
+ name: basename7(directory)
44984
45120
  });
44985
45121
  }
44986
45122
  return new ResolvedSessionDirectories(cwd, additionalDirectories, additionalDirectoryEntries);
@@ -45195,6 +45331,7 @@ function paginateActiveTurn(history, activeTurn, sessionId, endIndex, limit) {
45195
45331
  historicalCharacterLimit
45196
45332
  ) : null;
45197
45333
  return {
45334
+ streamPosition: { commandId: activeTurn.commandId, epoch: activeTurn.epoch, sequence: activeTurn.sequence },
45198
45335
  coveredCommandIds: historical?.coveredCommandIds ?? [],
45199
45336
  coveredMessageIds: [
45200
45337
  ...historical?.coveredMessageIds ?? [],
@@ -45227,6 +45364,7 @@ function mergeLiveSessionHistory(history, activeTurn, sessionId) {
45227
45364
  }
45228
45365
  return {
45229
45366
  coveredCommandIds: page.coveredCommandIds,
45367
+ ...page.streamPosition === void 0 ? {} : { streamPosition: page.streamPosition },
45230
45368
  coveredMessageIds: page.coveredMessageIds,
45231
45369
  messages: page.messages,
45232
45370
  status: "available",
@@ -45717,14 +45855,15 @@ async function executePrompt({
45717
45855
  const appliedSettings = command.sessionConfigurations === void 0 ? void 0 : agentForSessionConfiguration(agent, command.sessionConfigurations);
45718
45856
  const executionAgent = appliedSettings?.agent ?? agent;
45719
45857
  const sessionConfigurations = appliedSettings?.sessionConfigurations;
45858
+ const preparation = {
45859
+ additionalDirectories: resolved.additionalDirectories,
45860
+ agent: executionAgent,
45861
+ cwd: resolved.cwd,
45862
+ sessionId: command.sessionId,
45863
+ signal
45864
+ };
45865
+ let prepared = await sessions.prepareExecutionSession(preparation);
45720
45866
  if (command.sessionConfigurations !== void 0) {
45721
- const prepared = await sessions.prepareExecutionSession({
45722
- additionalDirectories: resolved.additionalDirectories,
45723
- agent: executionAgent,
45724
- cwd: resolved.cwd,
45725
- sessionId: command.sessionId,
45726
- signal
45727
- });
45728
45867
  if (availability.disposition === SessionPromptDisposition.Steer) {
45729
45868
  assertAppliedSessionConfiguration(prepared.configOptions, sessionConfigurations ?? []);
45730
45869
  } else {
@@ -45740,6 +45879,7 @@ async function executePrompt({
45740
45879
  command.sessionId,
45741
45880
  signal
45742
45881
  );
45882
+ prepared = await sessions.prepareExecutionSession(preparation);
45743
45883
  }
45744
45884
  }
45745
45885
  audit.record({
@@ -45758,6 +45898,24 @@ async function executePrompt({
45758
45898
  }
45759
45899
  }
45760
45900
  const stream = activateAttempt();
45901
+ if (!prepared.prompted) {
45902
+ const listing = await directories.list(config2.bindingId, command.directoryId, signal);
45903
+ publisher.publish(command.sessionId, {
45904
+ additionalDirectories: resolved.additionalDirectoryEntries,
45905
+ agentId: command.agentId,
45906
+ agentName: prepared.agentName,
45907
+ agentVersion: prepared.agentVersion,
45908
+ capabilities: prepared.capabilities,
45909
+ commands: prepared.commands,
45910
+ configOptions: prepared.configOptions,
45911
+ directoryId: command.directoryId,
45912
+ directoryLabel: listing.directoryLabel,
45913
+ kind: SESSION_READY_KIND,
45914
+ requestId: command.commandId,
45915
+ restored: prepared.restored,
45916
+ sessionId: command.sessionId
45917
+ });
45918
+ }
45761
45919
  publisher.publish(command.sessionId, {
45762
45920
  commandId: command.commandId,
45763
45921
  epoch: stream.epoch,
@@ -46011,7 +46169,7 @@ async function executeSessionConfigure(publisher, socket, command, config2, dire
46011
46169
  // src/native-sessions/handle-native-requests.ts
46012
46170
  import { WebSocket as WebSocket5 } from "undici";
46013
46171
  function createNativeSessionRequestHandler(dependencies) {
46014
- const { audit, diagnostics, nativeSessions, signal, socket, timer } = dependencies;
46172
+ const { audit, diagnostics, nativeSessions, publisher, signal, socket, timer } = dependencies;
46015
46173
  const pending = /* @__PURE__ */ new Map();
46016
46174
  const cancel = (command) => {
46017
46175
  const request = pending.get(command.requestId);
@@ -46063,6 +46221,17 @@ function createNativeSessionRequestHandler(dependencies) {
46063
46221
  }
46064
46222
  operationSignal.throwIfAborted();
46065
46223
  const result = await nativeSessions.execute(command, operationSignal);
46224
+ if (command.action === "fork-recorded" && result.action === "fork-recorded") {
46225
+ publisher.publishBatch(result.ready.sessionId, [
46226
+ result.ready,
46227
+ {
46228
+ kind: "session.forked",
46229
+ sessionId: result.ready.sessionId,
46230
+ sourceSessionId: command.sessionId,
46231
+ throughCommandId: command.throughCommandId
46232
+ }
46233
+ ]);
46234
+ }
46066
46235
  if (socket.readyState === WebSocket5.OPEN) {
46067
46236
  send(socket, result);
46068
46237
  }
@@ -46148,6 +46317,7 @@ function createDaemonCommandHandler({
46148
46317
  audit,
46149
46318
  diagnostics,
46150
46319
  nativeSessions,
46320
+ publisher,
46151
46321
  signal,
46152
46322
  socket,
46153
46323
  timer
@@ -46920,6 +47090,16 @@ var SessionEventOutbox = class {
46920
47090
  this.#database.close();
46921
47091
  };
46922
47092
  enqueue = (sessionId, payload, critical) => {
47093
+ const envelope2 = this.enqueueBatch(sessionId, [payload], critical)[0];
47094
+ if (envelope2 === void 0) {
47095
+ throw new Error("The Session event outbox did not enqueue the event");
47096
+ }
47097
+ return envelope2;
47098
+ };
47099
+ enqueueBatch = (sessionId, payloads, critical) => {
47100
+ if (payloads.length < 1 || payloads.length > 32) {
47101
+ throw new Error("A Session event batch must contain between 1 and 32 events");
47102
+ }
46923
47103
  this.#database.exec("BEGIN IMMEDIATE");
46924
47104
  try {
46925
47105
  const state = this.#database.prepare(
@@ -46928,39 +47108,52 @@ var SessionEventOutbox = class {
46928
47108
  WHERE singleton = 1`
46929
47109
  ).get();
46930
47110
  const sourceSeq = rowNumber(state, "next_source_seq");
46931
- if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + 1)) {
47111
+ if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + payloads.length)) {
46932
47112
  throw new Error("The Session event source sequence is exhausted");
46933
47113
  }
46934
- const envelope2 = {
46935
- delivery: {
46936
- eventId: randomUUID7(),
46937
- kind: "reliable",
46938
- sourceSeq: sourceSeq.toString(),
46939
- streamId: rowString(state, "stream_id")
46940
- },
46941
- kind: SESSION_EVENT_KIND,
46942
- payload,
46943
- sessionId
46944
- };
46945
- const encoded = encodeSessionEventEnvelope(envelope2);
46946
- const byteLength = Buffer.byteLength(encoded, "utf8");
47114
+ const streamId = rowString(state, "stream_id");
47115
+ const stored = payloads.map((payload, index) => {
47116
+ const envelope2 = {
47117
+ delivery: {
47118
+ eventId: randomUUID7(),
47119
+ kind: "reliable",
47120
+ sourceSeq: (sourceSeq + index).toString(),
47121
+ streamId
47122
+ },
47123
+ kind: SESSION_EVENT_KIND,
47124
+ payload,
47125
+ sessionId
47126
+ };
47127
+ const encoded = encodeSessionEventEnvelope(envelope2);
47128
+ return { byteLength: Buffer.byteLength(encoded, "utf8"), encoded, envelope: envelope2 };
47129
+ });
47130
+ const byteLength = stored.reduce((total, entry) => total + entry.byteLength, 0);
46947
47131
  const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
46948
47132
  if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
46949
47133
  throw new SessionEventOutboxCapacityError();
46950
47134
  }
46951
- this.#database.prepare(
47135
+ const insert = this.#database.prepare(
46952
47136
  `INSERT INTO relay_session_event_outbox (
46953
- event_id, session_id, source_seq, byte_length, envelope_json
46954
- ) VALUES (?, ?, ?, ?, ?)`
46955
- ).run(envelope2.delivery.eventId, sessionId, sourceSeq, byteLength, encoded);
47137
+ event_id, session_id, source_seq, byte_length, envelope_json
47138
+ ) VALUES (?, ?, ?, ?, ?)`
47139
+ );
47140
+ for (const entry of stored) {
47141
+ insert.run(
47142
+ entry.envelope.delivery.eventId,
47143
+ sessionId,
47144
+ Number(entry.envelope.delivery.sourceSeq),
47145
+ entry.byteLength,
47146
+ entry.encoded
47147
+ );
47148
+ }
46956
47149
  this.#database.prepare(
46957
47150
  `UPDATE relay_session_event_outbox_state
46958
- SET next_source_seq = next_source_seq + 1,
47151
+ SET next_source_seq = next_source_seq + ?,
46959
47152
  total_bytes = total_bytes + ?
46960
47153
  WHERE singleton = 1`
46961
- ).run(byteLength);
47154
+ ).run(stored.length, byteLength);
46962
47155
  this.#database.exec("COMMIT");
46963
- return envelope2;
47156
+ return stored.map((entry) => entry.envelope);
46964
47157
  } catch (error62) {
46965
47158
  this.#database.exec("ROLLBACK");
46966
47159
  throw error62;
@@ -47061,7 +47254,7 @@ import { WebSocket as WebSocket9 } from "undici";
47061
47254
  var SESSION_EVENT_REPLAY_WINDOW = 16;
47062
47255
  var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
47063
47256
  function reservesSessionEventCapacity(event) {
47064
- if (event.kind === SESSION_COMPLETED_KIND || event.kind === SESSION_FAILED_KIND) {
47257
+ if (event.kind === SESSION_COMPLETED_KIND || event.kind === SESSION_FAILED_KIND || event.kind === SESSION_FORKED_KIND || event.kind === SESSION_READY_KIND) {
47065
47258
  return true;
47066
47259
  }
47067
47260
  return event.kind === AGENT_ACTIVITY_KIND && (event.activityType === "progress" || event.activityType === "reasoning") && (event.status === "completed" || event.status === "failed");
@@ -47134,6 +47327,11 @@ var DaemonSessionEventPublisher = class {
47134
47327
  }
47135
47328
  this.#publishNextBatch();
47136
47329
  };
47330
+ publishBatch = (sessionId, events) => {
47331
+ const critical = events.some(reservesSessionEventCapacity);
47332
+ this.#outbox.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
47333
+ this.#publishNextBatch();
47334
+ };
47137
47335
  quarantine = (rejection) => {
47138
47336
  if (!this.#outbox.quarantine(rejection)) {
47139
47337
  return;