@moxt-ai/mobius 0.0.23 → 0.0.25

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 (53) hide show
  1. package/dist/acp/session-capabilities.d.ts +2 -2
  2. package/dist/acp/session-capabilities.d.ts.map +1 -1
  3. package/dist/acp/session-capabilities.js +16 -1
  4. package/dist/acp/session-capabilities.js.map +1 -1
  5. package/dist/acp/session-manager.d.ts +47 -6
  6. package/dist/acp/session-manager.d.ts.map +1 -1
  7. package/dist/acp/session-manager.js +250 -8
  8. package/dist/acp/session-manager.js.map +1 -1
  9. package/dist/agent-catalog/acp-readiness-probe.js +1 -1
  10. package/dist/agent-catalog/acp-readiness-probe.js.map +1 -1
  11. package/dist/agent-catalog/publish-agent-catalog.d.ts.map +1 -1
  12. package/dist/agent-catalog/publish-agent-catalog.js +12 -7
  13. package/dist/agent-catalog/publish-agent-catalog.js.map +1 -1
  14. package/dist/bots/local-bot-grant-store.d.ts +2 -1
  15. package/dist/bots/local-bot-grant-store.d.ts.map +1 -1
  16. package/dist/bots/local-bot-grant-store.js +8 -5
  17. package/dist/bots/local-bot-grant-store.js.map +1 -1
  18. package/dist/cli.js +949 -260
  19. package/dist/cli.js.map +4 -4
  20. package/dist/config.d.ts +1 -1
  21. package/dist/config.d.ts.map +1 -1
  22. package/dist/config.js +3 -3
  23. package/dist/config.js.map +1 -1
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1002 -216
  27. package/dist/index.js.map +4 -4
  28. package/dist/native-sessions/service.d.ts +2 -1
  29. package/dist/native-sessions/service.d.ts.map +1 -1
  30. package/dist/native-sessions/service.js +65 -0
  31. package/dist/native-sessions/service.js.map +1 -1
  32. package/dist/pairing/pair-local-machine.d.ts +8 -0
  33. package/dist/pairing/pair-local-machine.d.ts.map +1 -1
  34. package/dist/pairing/pair-local-machine.js +64 -37
  35. package/dist/pairing/pair-local-machine.js.map +1 -1
  36. package/dist/prompt-artifact-resolver.d.ts.map +1 -1
  37. package/dist/prompt-artifact-resolver.js +11 -3
  38. package/dist/prompt-artifact-resolver.js.map +1 -1
  39. package/dist/relay-connection/connect.d.ts.map +1 -1
  40. package/dist/relay-connection/connect.js +9 -1
  41. package/dist/relay-connection/connect.js.map +1 -1
  42. package/dist/relay-connection/session-event-outbox.d.ts +1 -0
  43. package/dist/relay-connection/session-event-outbox.d.ts.map +1 -1
  44. package/dist/relay-connection/session-event-outbox.js +7 -0
  45. package/dist/relay-connection/session-event-outbox.js.map +1 -1
  46. package/dist/run-daemon.d.ts.map +1 -1
  47. package/dist/run-daemon.js +165 -54
  48. package/dist/run-daemon.js.map +1 -1
  49. package/dist/runtime.d.ts +23 -0
  50. package/dist/runtime.d.ts.map +1 -0
  51. package/dist/runtime.js +31046 -0
  52. package/dist/runtime.js.map +7 -0
  53. package/package.json +6 -2
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import { mkdir as mkdir5 } from "node:fs/promises";
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "@moxt-ai/mobius",
14
- version: "0.0.23",
14
+ version: "0.0.25",
15
15
  description: "Mobius CLI for pairing and running local AI agent services.",
16
16
  type: "module",
17
17
  bin: {
@@ -21,6 +21,10 @@ var package_default = {
21
21
  ".": {
22
22
  types: "./dist/index.d.ts",
23
23
  import: "./dist/index.js"
24
+ },
25
+ "./runtime": {
26
+ types: "./dist/runtime.d.ts",
27
+ import: "./dist/runtime.js"
24
28
  }
25
29
  },
26
30
  files: [
@@ -38,7 +42,7 @@ var package_default = {
38
42
  directory: "packages/cli"
39
43
  },
40
44
  scripts: {
41
- build: "tsc --project tsconfig.build.json && esbuild src/cli.ts src/index.ts --bundle --platform=node --format=esm --sourcemap --outdir=dist --external:@agentclientprotocol/* --external:undici",
45
+ build: "tsc --project tsconfig.build.json && esbuild src/cli.ts src/index.ts src/runtime.ts --bundle --platform=node --format=esm --sourcemap --outdir=dist --external:@agentclientprotocol/* --external:undici",
42
46
  dev: "node --watch src/cli.ts",
43
47
  prepack: "pnpm build",
44
48
  start: "node dist/cli.js",
@@ -71,6 +75,16 @@ var package_default = {
71
75
  import { realpathSync, statSync } from "node:fs";
72
76
 
73
77
  // src/acp/session-capabilities.ts
78
+ function isRecord(value) {
79
+ return typeof value === "object" && value !== null && !Array.isArray(value);
80
+ }
81
+ function hasTrueChildField(value, childKey, fieldKey) {
82
+ if (!isRecord(value)) {
83
+ return false;
84
+ }
85
+ const child = Reflect.get(value, childKey);
86
+ return isRecord(child) && Reflect.get(child, fieldKey) === true;
87
+ }
74
88
  function unsupportedSessionCapabilities() {
75
89
  return Object.freeze({
76
90
  additionalDirectories: false,
@@ -81,11 +95,14 @@ function unsupportedSessionCapabilities() {
81
95
  imagePrompt: false,
82
96
  listSessions: false,
83
97
  loadSession: false,
98
+ promptQueueing: false,
84
99
  resumeSession: false,
100
+ steering: false,
85
101
  unstableForkSession: false
86
102
  });
87
103
  }
88
- function normalizeAcpCapabilities(capabilities) {
104
+ function normalizeAcpCapabilities(initialized) {
105
+ const capabilities = initialized.agentCapabilities ?? {};
89
106
  const session = capabilities.sessionCapabilities;
90
107
  const prompt = capabilities.promptCapabilities;
91
108
  return Object.freeze({
@@ -97,7 +114,9 @@ function normalizeAcpCapabilities(capabilities) {
97
114
  imagePrompt: prompt?.image === true,
98
115
  listSessions: session?.list != null,
99
116
  loadSession: capabilities.loadSession === true,
117
+ promptQueueing: hasTrueChildField(capabilities._meta, "claudeCode", "promptQueueing"),
100
118
  resumeSession: session?.resume != null,
119
+ steering: hasTrueChildField(initialized._meta, "steering", "supported"),
101
120
  unstableForkSession: session?.fork != null
102
121
  });
103
122
  }
@@ -865,7 +884,7 @@ var ManagedNpmPackageError = class extends Error {
865
884
  this.name = "ManagedNpmPackageError";
866
885
  }
867
886
  };
868
- function isRecord(value) {
887
+ function isRecord2(value) {
869
888
  return typeof value === "object" && value !== null && !Array.isArray(value);
870
889
  }
871
890
  function packagePath(root, packageName) {
@@ -887,7 +906,7 @@ async function readJsonRecord(path2, maximumBytes) {
887
906
  );
888
907
  }
889
908
  const value = JSON.parse(await handle.readFile({ encoding: "utf8" }));
890
- if (!isRecord(value)) {
909
+ if (!isRecord2(value)) {
891
910
  throw new ManagedNpmPackageError(
892
911
  "managed_package_verification_failed",
893
912
  "The managed npm package metadata is invalid"
@@ -913,7 +932,7 @@ function readBinPath(packageMetadata, executableName) {
913
932
  if (typeof bin === "string") {
914
933
  return bin;
915
934
  }
916
- if (isRecord(bin) && typeof bin[executableName] === "string") {
935
+ if (isRecord2(bin) && typeof bin[executableName] === "string") {
917
936
  return bin[executableName];
918
937
  }
919
938
  throw new ManagedNpmPackageError(
@@ -929,7 +948,7 @@ function verifyPackageLockEntry(lock, definition, verifyDependencyClosure) {
929
948
  );
930
949
  }
931
950
  const packages = lock["packages"];
932
- if (!isRecord(packages)) {
951
+ if (!isRecord2(packages)) {
933
952
  throw new ManagedNpmPackageError(
934
953
  "managed_package_verification_failed",
935
954
  "The managed npm package lockfile is invalid"
@@ -943,7 +962,7 @@ function verifyPackageLockEntry(lock, definition, verifyDependencyClosure) {
943
962
  if (verifyDependencyClosure && normalizedKey.length > 0) {
944
963
  let resolved;
945
964
  try {
946
- if (!normalizedKey.split("/").includes("node_modules") || !isRecord(candidate) || typeof candidate["version"] !== "string" || candidate["version"].length === 0 || typeof candidate["integrity"] !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(candidate["integrity"]) || typeof candidate["resolved"] !== "string") {
965
+ if (!normalizedKey.split("/").includes("node_modules") || !isRecord2(candidate) || typeof candidate["version"] !== "string" || candidate["version"].length === 0 || typeof candidate["integrity"] !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(candidate["integrity"]) || typeof candidate["resolved"] !== "string") {
947
966
  throw new Error("Invalid locked dependency");
948
967
  }
949
968
  resolved = new URL(candidate["resolved"]);
@@ -966,7 +985,7 @@ function verifyPackageLockEntry(lock, definition, verifyDependencyClosure) {
966
985
  matches += 1;
967
986
  }
968
987
  }
969
- if (matches !== 1 || !isRecord(entry) || entry["integrity"] !== definition.integrity || entry["resolved"] !== definition.tarballUrl || entry["version"] !== definition.version) {
988
+ if (matches !== 1 || !isRecord2(entry) || entry["integrity"] !== definition.integrity || entry["resolved"] !== definition.tarballUrl || entry["version"] !== definition.version) {
970
989
  throw new ManagedNpmPackageError(
971
990
  "managed_package_verification_failed",
972
991
  "The managed npm package lockfile does not match the pinned artifact"
@@ -1607,7 +1626,7 @@ var ManagedAgentArchiveError = class extends Error {
1607
1626
  this.name = "ManagedAgentArchiveError";
1608
1627
  }
1609
1628
  };
1610
- function isRecord2(value) {
1629
+ function isRecord3(value) {
1611
1630
  return typeof value === "object" && value !== null && !Array.isArray(value);
1612
1631
  }
1613
1632
  function isContainedPath3(root, candidate) {
@@ -1664,7 +1683,7 @@ async function readManifest(path2) {
1664
1683
  );
1665
1684
  }
1666
1685
  const parsed = JSON.parse(await handle.readFile({ encoding: "utf8" }));
1667
- if (!isRecord2(parsed)) {
1686
+ if (!isRecord3(parsed)) {
1668
1687
  throw new ManagedAgentArchiveError(
1669
1688
  "managed_archive_verification_failed",
1670
1689
  "The managed Agent archive manifest is invalid"
@@ -2384,7 +2403,7 @@ async function probeAcpReadiness(specification, agent, environment, defaultWorks
2384
2403
  await specification.verifyAcp(connection, initialized, defaultWorkspace, controller.signal);
2385
2404
  return new VerifiedAcpReadiness(
2386
2405
  normalizedVersion(initialized.agentInfo?.version ?? ""),
2387
- normalizeAcpCapabilities(initialized.agentCapabilities ?? {})
2406
+ normalizeAcpCapabilities(initialized)
2388
2407
  );
2389
2408
  } catch (error61) {
2390
2409
  if (signal.aborted) {
@@ -2563,54 +2582,6 @@ async function discoverLocalAgentInventory(options) {
2563
2582
  return new LocalAgentInventory(discoveries);
2564
2583
  }
2565
2584
 
2566
- // src/audit/audit-store.ts
2567
- import { mkdirSync } from "node:fs";
2568
- import { dirname as dirname3 } from "node:path";
2569
- import { DatabaseSync } from "node:sqlite";
2570
- var AuditStore = class {
2571
- database;
2572
- constructor(path2) {
2573
- mkdirSync(dirname3(path2), { recursive: true });
2574
- this.database = new DatabaseSync(path2);
2575
- this.database.exec(`
2576
- CREATE TABLE IF NOT EXISTS audit_events (
2577
- id INTEGER PRIMARY KEY AUTOINCREMENT,
2578
- recorded_at TEXT NOT NULL,
2579
- event TEXT NOT NULL,
2580
- command_id TEXT NOT NULL,
2581
- session_id TEXT NOT NULL,
2582
- details_json TEXT NOT NULL DEFAULT '{}'
2583
- ) STRICT
2584
- `);
2585
- const columns = this.database.prepare("PRAGMA table_info(audit_events)").all();
2586
- if (!columns.some((column) => Reflect.get(column, "name") === "details_json")) {
2587
- this.database.exec("ALTER TABLE audit_events ADD COLUMN details_json TEXT NOT NULL DEFAULT '{}'");
2588
- }
2589
- }
2590
- record(record2) {
2591
- this.database.prepare(
2592
- `
2593
- INSERT INTO audit_events (
2594
- recorded_at,
2595
- event,
2596
- command_id,
2597
- session_id,
2598
- details_json
2599
- ) VALUES (?, ?, ?, ?, ?)
2600
- `
2601
- ).run(
2602
- (/* @__PURE__ */ new Date()).toISOString(),
2603
- record2.event,
2604
- record2.commandId,
2605
- record2.sessionId,
2606
- JSON.stringify(record2.details ?? {})
2607
- );
2608
- }
2609
- close() {
2610
- this.database.close();
2611
- }
2612
- };
2613
-
2614
2585
  // src/bots/bot-message-command.ts
2615
2586
  import { randomUUID } from "node:crypto";
2616
2587
  import { createConnection } from "node:net";
@@ -2624,7 +2595,7 @@ var BotMessageCommandError = class extends Error {
2624
2595
  this.name = "BotMessageCommandError";
2625
2596
  }
2626
2597
  };
2627
- function isRecord3(value) {
2598
+ function isRecord4(value) {
2628
2599
  return typeof value === "object" && value !== null && !Array.isArray(value);
2629
2600
  }
2630
2601
  function requiredEnvironment(environment, name) {
@@ -2675,14 +2646,14 @@ function decodeResponse(encoded) {
2675
2646
  cause
2676
2647
  });
2677
2648
  }
2678
- if (!isRecord3(value) || typeof value["ok"] !== "boolean") {
2649
+ if (!isRecord4(value) || typeof value["ok"] !== "boolean") {
2679
2650
  throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
2680
2651
  }
2681
2652
  if (value["ok"] === true && typeof value["outboundMessageId"] === "string") {
2682
2653
  return value["outboundMessageId"];
2683
2654
  }
2684
2655
  const error61 = value["error"];
2685
- if (value["ok"] === false && isRecord3(error61) && typeof error61["code"] === "string" && typeof error61["message"] === "string") {
2656
+ if (value["ok"] === false && isRecord4(error61) && typeof error61["code"] === "string" && typeof error61["message"] === "string") {
2686
2657
  throw new BotMessageCommandError(error61["code"], error61["message"]);
2687
2658
  }
2688
2659
  throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
@@ -21743,7 +21714,9 @@ var sessionCapabilitiesSchema = external_exports.strictObject({
21743
21714
  imagePrompt: external_exports.boolean(),
21744
21715
  listSessions: external_exports.boolean(),
21745
21716
  loadSession: external_exports.boolean(),
21717
+ promptQueueing: external_exports.boolean().default(false),
21746
21718
  resumeSession: external_exports.boolean(),
21719
+ steering: external_exports.boolean().default(false),
21747
21720
  unstableForkSession: external_exports.boolean().default(false)
21748
21721
  }).readonly();
21749
21722
  var sessionCommandSchema = external_exports.strictObject({
@@ -22085,6 +22058,11 @@ var AGENT_ACTIVITY_LOCATION_LIMIT = 12;
22085
22058
  var AGENT_ACTIVITY_LOCATION_LENGTH_LIMIT = 1024;
22086
22059
  var SESSION_COMPLETED_KIND = "session.completed";
22087
22060
  var SESSION_FAILED_KIND = "session.failed";
22061
+ var SessionPromptDisposition = Object.freeze({
22062
+ Queue: "queue",
22063
+ Reject: "reject",
22064
+ Steer: "steer"
22065
+ });
22088
22066
 
22089
22067
  // ../protocol/src/codecs/messages.ts
22090
22068
  var IDENTIFIER_PATTERN2 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
@@ -22114,7 +22092,7 @@ var ProtocolMessageError = class extends Error {
22114
22092
  this.name = "ProtocolMessageError";
22115
22093
  }
22116
22094
  };
22117
- function isRecord4(value) {
22095
+ function isRecord5(value) {
22118
22096
  return typeof value === "object" && value !== null && !Array.isArray(value);
22119
22097
  }
22120
22098
  function parseJson2(encoded) {
@@ -22126,7 +22104,7 @@ function parseJson2(encoded) {
22126
22104
  cause: error61
22127
22105
  });
22128
22106
  }
22129
- if (!isRecord4(value)) {
22107
+ if (!isRecord5(value)) {
22130
22108
  throw new ProtocolMessageError("Message must be a JSON object");
22131
22109
  }
22132
22110
  return value;
@@ -22159,6 +22137,19 @@ function readAllowedString(value, key, allowedValues) {
22159
22137
  }
22160
22138
  return field;
22161
22139
  }
22140
+ function readPromptDisposition(value) {
22141
+ const field = value["disposition"];
22142
+ if (field === void 0 || field === SessionPromptDisposition.Reject) {
22143
+ return SessionPromptDisposition.Reject;
22144
+ }
22145
+ if (field === SessionPromptDisposition.Queue) {
22146
+ return SessionPromptDisposition.Queue;
22147
+ }
22148
+ if (field === SessionPromptDisposition.Steer) {
22149
+ return SessionPromptDisposition.Steer;
22150
+ }
22151
+ throw new ProtocolMessageError("Invalid value field: disposition");
22152
+ }
22162
22153
  function readSequence(value, key) {
22163
22154
  const field = value[key];
22164
22155
  if (typeof field !== "number" || !Number.isSafeInteger(field) || field < 0) {
@@ -22210,7 +22201,7 @@ function readActivityContent(value) {
22210
22201
  }
22211
22202
  function readActivityDetail(value) {
22212
22203
  const field = value["detail"];
22213
- if (!isRecord4(field)) {
22204
+ if (!isRecord5(field)) {
22214
22205
  throw new ProtocolMessageError("Invalid activity detail field");
22215
22206
  }
22216
22207
  const kind = readString(field, "kind", 32);
@@ -22283,11 +22274,13 @@ function decodeSessionPromptCommand(encoded) {
22283
22274
  "bindingId",
22284
22275
  "commandId",
22285
22276
  "directoryId",
22277
+ "disposition",
22286
22278
  "images",
22287
22279
  "kind",
22288
22280
  "prompt",
22289
22281
  "resources",
22290
- "sessionId"
22282
+ "sessionId",
22283
+ "targetCommandId"
22291
22284
  ]);
22292
22285
  assertKind(value, SESSION_PROMPT_KIND);
22293
22286
  const prompt = readText(value, "prompt", MAX_PROMPT_LENGTH);
@@ -22300,17 +22293,25 @@ function decodeSessionPromptCommand(encoded) {
22300
22293
  if (prompt.trim().length === 0 && images.length === 0 && resources.length === 0) {
22301
22294
  throw new ProtocolMessageError("Prompt must contain content");
22302
22295
  }
22296
+ const disposition = readPromptDisposition(value);
22297
+ const rawTargetCommandId = value["targetCommandId"];
22298
+ const targetCommandId = rawTargetCommandId === void 0 ? "" : readText(value, "targetCommandId", 128);
22299
+ if (disposition === SessionPromptDisposition.Reject && targetCommandId.length > 0 || disposition !== SessionPromptDisposition.Reject && !IDENTIFIER_PATTERN2.test(targetCommandId)) {
22300
+ throw new ProtocolMessageError("Prompt disposition has an invalid target command");
22301
+ }
22303
22302
  return {
22304
22303
  additionalDirectoryIds: readDirectoryIdentifiers(value, "additionalDirectoryIds", 16),
22305
22304
  agentId: readIdentifier(value, "agentId"),
22306
22305
  bindingId: readIdentifier(value, "bindingId"),
22307
22306
  commandId: readIdentifier(value, "commandId"),
22308
22307
  directoryId: readDirectoryIdentifier(value, "directoryId"),
22308
+ disposition,
22309
22309
  images,
22310
22310
  kind: SESSION_PROMPT_KIND,
22311
22311
  prompt,
22312
22312
  resources,
22313
- sessionId: readIdentifier(value, "sessionId")
22313
+ sessionId: readIdentifier(value, "sessionId"),
22314
+ targetCommandId
22314
22315
  };
22315
22316
  }
22316
22317
  function decodeSessionCancelCommand(encoded) {
@@ -22360,11 +22361,12 @@ function decodeSessionStartedEvent(encoded) {
22360
22361
  }
22361
22362
  function decodeAgentMessageDeltaEvent(encoded) {
22362
22363
  const value = parseJson2(encoded);
22363
- assertExactKeys(value, ["commandId", "kind", "sessionId", "text"]);
22364
+ assertExactKeys(value, ["commandId", "kind", "messageId", "sessionId", "text"]);
22364
22365
  assertKind(value, AGENT_MESSAGE_DELTA_KIND);
22365
22366
  return {
22366
22367
  commandId: readIdentifier(value, "commandId"),
22367
22368
  kind: AGENT_MESSAGE_DELTA_KIND,
22369
+ messageId: value["messageId"] === void 0 ? "" : readText(value, "messageId", 255),
22368
22370
  sessionId: readIdentifier(value, "sessionId"),
22369
22371
  text: readString(value, "text", MAX_EVENT_TEXT_LENGTH)
22370
22372
  };
@@ -22629,7 +22631,7 @@ var BotSessionPromptCommand = class {
22629
22631
  this.replyRoute = replyRoute;
22630
22632
  }
22631
22633
  };
22632
- function isRecord5(value) {
22634
+ function isRecord6(value) {
22633
22635
  return typeof value === "object" && value !== null && !Array.isArray(value);
22634
22636
  }
22635
22637
  function parseJson3(encoded) {
@@ -22639,7 +22641,7 @@ function parseJson3(encoded) {
22639
22641
  } catch (cause) {
22640
22642
  throw new ProtocolMessageError("Message is not valid JSON", { cause });
22641
22643
  }
22642
- if (!isRecord5(value)) {
22644
+ if (!isRecord6(value)) {
22643
22645
  throw new ProtocolMessageError("Message must be a JSON object");
22644
22646
  }
22645
22647
  return value;
@@ -22724,7 +22726,7 @@ function readTargets(value, key) {
22724
22726
  const identifiers = /* @__PURE__ */ new Set();
22725
22727
  const aliases = /* @__PURE__ */ new Set();
22726
22728
  for (const candidate of field) {
22727
- if (!isRecord5(candidate)) {
22729
+ if (!isRecord6(candidate)) {
22728
22730
  throw new ProtocolMessageError(`Invalid Bot delivery targets: ${key}`);
22729
22731
  }
22730
22732
  assertExactKeys2(candidate, ["alias", "deliveryTargetId"]);
@@ -22740,7 +22742,7 @@ function readTargets(value, key) {
22740
22742
  return Object.freeze(targets);
22741
22743
  }
22742
22744
  function readCompletionPolicy(value) {
22743
- if (!isRecord5(value)) {
22745
+ if (!isRecord6(value)) {
22744
22746
  throw new ProtocolMessageError("Invalid Bot completion policy");
22745
22747
  }
22746
22748
  const kind = readText2(value, "kind", 32, false);
@@ -22758,7 +22760,7 @@ function readCompletionPolicy(value) {
22758
22760
  throw new ProtocolMessageError("Invalid Bot completion policy");
22759
22761
  }
22760
22762
  function readReplyRoute(value) {
22761
- if (!isRecord5(value)) {
22763
+ if (!isRecord6(value)) {
22762
22764
  throw new ProtocolMessageError("Invalid Bot reply route");
22763
22765
  }
22764
22766
  const kind = readText2(value, "kind", 32, false);
@@ -23010,6 +23012,8 @@ var sessionHistorySchema = external_exports.discriminatedUnion("status", [
23010
23012
  var NATIVE_SESSION_REQUEST_KIND = "native.session.request";
23011
23013
  var NATIVE_SESSION_RESULT_KIND = "native.session.result";
23012
23014
  var NATIVE_SESSION_CANCEL_KIND = "native.session.cancel";
23015
+ var NATIVE_SESSION_TURN_FORK_REQUEST_KIND = "native.session.turn-fork.request";
23016
+ var NATIVE_SESSION_TURN_FORK_KIND = "native.session.turn-fork";
23013
23017
  var identifier = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/);
23014
23018
  var directoryId = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,2047}$/);
23015
23019
  var envelope = {
@@ -23024,6 +23028,28 @@ var identity = {
23024
23028
  bindingId: identifier,
23025
23029
  directoryId
23026
23030
  };
23031
+ var turnForkIdentity = {
23032
+ additionalDirectoryIds: external_exports.array(directoryId).max(16).readonly(),
23033
+ agentId: identifier,
23034
+ bindingId: identifier,
23035
+ directoryId,
23036
+ requestId: identifier,
23037
+ sessionId: identifier,
23038
+ sourceCommandId: identifier,
23039
+ targetSessionId: identifier
23040
+ };
23041
+ var nativeSessionTurnForkRequestSchema = external_exports.strictObject({
23042
+ ...turnForkIdentity,
23043
+ kind: external_exports.literal(NATIVE_SESSION_TURN_FORK_REQUEST_KIND)
23044
+ }).readonly();
23045
+ var nativeSessionTurnForkCommandSchema = external_exports.strictObject({
23046
+ ...turnForkIdentity,
23047
+ kind: external_exports.literal(NATIVE_SESSION_TURN_FORK_KIND),
23048
+ messageFingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
23049
+ messageId: external_exports.string().min(1).max(255),
23050
+ messageOccurrence: external_exports.number().int().positive(),
23051
+ title: external_exports.string().max(500)
23052
+ }).readonly();
23027
23053
  var nativeSessionCommandSchema = external_exports.discriminatedUnion("action", [
23028
23054
  external_exports.strictObject({
23029
23055
  ...identity,
@@ -23098,6 +23124,7 @@ function decode4(schema, encoded) {
23098
23124
  var decodeNativeSessionCommand = (encoded) => decode4(nativeSessionCommandSchema, encoded);
23099
23125
  var encodeNativeSessionResult = (value) => JSON.stringify(nativeSessionResultSchema.parse(value));
23100
23126
  var decodeNativeSessionCancel = (encoded) => decode4(nativeSessionCancelSchema, encoded);
23127
+ var decodeNativeSessionTurnForkCommand = (encoded) => decode4(nativeSessionTurnForkCommandSchema, encoded);
23101
23128
 
23102
23129
  // ../protocol/src/session-events.ts
23103
23130
  var SESSION_EVENT_KIND = "session.event";
@@ -23183,9 +23210,9 @@ var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAckno
23183
23210
  var decodeSessionEventRejection = (encoded) => decode5(encoded, eventRejectionSchema);
23184
23211
 
23185
23212
  // src/config.ts
23186
- function loadDefaultWorkspace() {
23187
- const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = process.env;
23188
- return configuredWorkspace ?? join13(readStateDirectory(), "workspace");
23213
+ function loadDefaultWorkspace(environment = process.env) {
23214
+ const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = environment;
23215
+ return configuredWorkspace ?? join13(readStateDirectory(environment), "workspace");
23189
23216
  }
23190
23217
  function createDaemonConfig(connection, agents = [], installableAgents = []) {
23191
23218
  return {
@@ -23212,7 +23239,9 @@ var IDENTIFIER_PATTERN5 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
23212
23239
  var TOKEN_PATTERN2 = /^[a-zA-Z0-9_-]{32,256}$/;
23213
23240
  var MAX_CONNECTION_FILE_LENGTH = 16 * 1024;
23214
23241
  var CLAIM_RESPONSE_KEYS = ["channelId", "daemonToken", "runtimeId"];
23215
- var SAVED_CONNECTION_KEYS = ["defaultWorkspace", "channelId", "daemonToken", "relayOrigin"];
23242
+ var REDEEM_RESPONSE_KEYS = ["channelId", "runtimeCredential", "runtimeId"];
23243
+ var LEGACY_SAVED_CONNECTION_KEYS = ["defaultWorkspace", "channelId", "daemonToken", "relayOrigin"];
23244
+ var SAVED_CONNECTION_KEYS = ["defaultWorkspace", "channelId", "relayOrigin", "runtimeCredential"];
23216
23245
  var PairingClientError = class extends Error {
23217
23246
  code;
23218
23247
  constructor(code, message, options = {}) {
@@ -23221,7 +23250,7 @@ var PairingClientError = class extends Error {
23221
23250
  this.name = "PairingClientError";
23222
23251
  }
23223
23252
  };
23224
- function isRecord6(value) {
23253
+ function isRecord7(value) {
23225
23254
  return typeof value === "object" && value !== null && !Array.isArray(value);
23226
23255
  }
23227
23256
  function hasExactKeys(value, expectedKeys) {
@@ -23249,7 +23278,7 @@ function readSingleOption(arguments_, option2) {
23249
23278
  function assertKnownArguments(arguments_) {
23250
23279
  for (let index = 0; index < arguments_.length; index += 2) {
23251
23280
  const option2 = arguments_[index];
23252
- if (option2 !== "--url" && option2 !== "--code" && option2 !== "--workspace") {
23281
+ if (option2 !== "--url" && option2 !== "--code" && option2 !== "--pairing" && option2 !== "--workspace") {
23253
23282
  throw new PairingClientError("invalid_arguments", `Unknown pairing option: ${option2 ?? "missing option"}`);
23254
23283
  }
23255
23284
  }
@@ -23289,10 +23318,17 @@ function parsePairCommand(arguments_) {
23289
23318
  assertKnownArguments(arguments_);
23290
23319
  return {
23291
23320
  code: parsePairingCode(readSingleOption(arguments_, "--code")),
23321
+ pairingId: arguments_.includes("--pairing") ? validatePairingId(readSingleOption(arguments_, "--pairing")) : "",
23292
23322
  relayOrigin: parseRelayOrigin(readSingleOption(arguments_, "--url")),
23293
23323
  workspace: arguments_.includes("--workspace") ? readSingleOption(arguments_, "--workspace") : loadDefaultWorkspace()
23294
23324
  };
23295
23325
  }
23326
+ function validatePairingId(value) {
23327
+ if (!IDENTIFIER_PATTERN5.test(value)) {
23328
+ throw new PairingClientError("invalid_pairing_id", "The Runtime pairing ID is invalid");
23329
+ }
23330
+ return value;
23331
+ }
23296
23332
  function parseChannelArgument(arguments_) {
23297
23333
  if (arguments_.length !== 2 || arguments_[0] !== "--channel") {
23298
23334
  throw new PairingClientError("invalid_arguments", "Usage: mobius start --channel <channel-id>");
@@ -23337,9 +23373,9 @@ async function readJsonResponse(response) {
23337
23373
  }
23338
23374
  }
23339
23375
  function readRelayError(value) {
23340
- if (isRecord6(value)) {
23376
+ if (isRecord7(value)) {
23341
23377
  const error61 = value["error"];
23342
- if (isRecord6(error61)) {
23378
+ if (isRecord7(error61)) {
23343
23379
  const code = error61["code"];
23344
23380
  const message = error61["message"];
23345
23381
  if (typeof code === "string" && typeof message === "string") {
@@ -23349,17 +23385,17 @@ function readRelayError(value) {
23349
23385
  }
23350
23386
  return new PairingClientError("pairing_failed", "The relay could not pair this machine");
23351
23387
  }
23352
- function decodeClaimResponse(value) {
23353
- if (!isRecord6(value) || !hasExactKeys(value, CLAIM_RESPONSE_KEYS)) {
23388
+ function decodePairingResponse(value, credentialField, expectedKeys) {
23389
+ if (!isRecord7(value) || !hasExactKeys(value, expectedKeys)) {
23354
23390
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
23355
23391
  }
23356
23392
  const channelId = value["channelId"];
23357
- const daemonToken2 = value["daemonToken"];
23393
+ const runtimeCredential = value[credentialField];
23358
23394
  const runtimeId = value["runtimeId"];
23359
- if (typeof channelId !== "string" || !IDENTIFIER_PATTERN5.test(channelId) || typeof daemonToken2 !== "string" || !TOKEN_PATTERN2.test(daemonToken2) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN5.test(runtimeId)) {
23395
+ if (typeof channelId !== "string" || !IDENTIFIER_PATTERN5.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN5.test(runtimeId)) {
23360
23396
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
23361
23397
  }
23362
- return { channelId, daemonToken: daemonToken2, runtimeId };
23398
+ return { channelId, runtimeCredential, runtimeId };
23363
23399
  }
23364
23400
  async function claimPairing(options, defaultWorkspace, localAgents, installableAgents, signal) {
23365
23401
  const agents = localAgents.map((agent) => ({
@@ -23369,7 +23405,7 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
23369
23405
  id: agent.id,
23370
23406
  label: agent.label
23371
23407
  }));
23372
- const endpoint = mobiusApiUrl(options.relayOrigin, "/pairings/claim");
23408
+ const endpoint = options.pairingId.length > 0 ? mobiusApiUrl(options.relayOrigin, `/v1/runtime-pairings/${encodeURIComponent(options.pairingId)}/redeem`) : mobiusApiUrl(options.relayOrigin, "/pairings/claim");
23373
23409
  let response;
23374
23410
  try {
23375
23411
  response = await fetch2(endpoint, {
@@ -23392,10 +23428,10 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
23392
23428
  if (!response.ok) {
23393
23429
  throw readRelayError(value);
23394
23430
  }
23395
- return decodeClaimResponse(value);
23431
+ return options.pairingId.length > 0 ? decodePairingResponse(value, "runtimeCredential", REDEEM_RESPONSE_KEYS) : decodePairingResponse(value, "daemonToken", CLAIM_RESPONSE_KEYS);
23396
23432
  }
23397
- async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWorkspace) {
23398
- const connectionsDirectory = join14(readStateDirectory(), "connections");
23433
+ async function persistConnection(channelId, relayOrigin, runtimeCredential, defaultWorkspace, environment) {
23434
+ const connectionsDirectory = join14(readStateDirectory(environment), "connections");
23399
23435
  await mkdir3(connectionsDirectory, { mode: 448, recursive: true });
23400
23436
  const connectionPath = join14(connectionsDirectory, `${channelId}.json`);
23401
23437
  const temporaryPath = join14(connectionsDirectory, `.${channelId}.${randomUUID2()}.tmp`);
@@ -23405,8 +23441,8 @@ async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWo
23405
23441
  {
23406
23442
  defaultWorkspace,
23407
23443
  channelId,
23408
- daemonToken: daemonToken2,
23409
- relayOrigin: relayOrigin.toString()
23444
+ relayOrigin: relayOrigin.toString(),
23445
+ runtimeCredential
23410
23446
  },
23411
23447
  null,
23412
23448
  2
@@ -23418,25 +23454,25 @@ async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWo
23418
23454
  return connectionPath;
23419
23455
  }
23420
23456
  function decodeSavedConnection(value, expectedChannelId) {
23421
- if (!isRecord6(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS)) {
23457
+ if (!isRecord7(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS) && !hasExactKeys(value, LEGACY_SAVED_CONNECTION_KEYS)) {
23422
23458
  throw new PairingClientError("invalid_connection", "The saved connection is invalid");
23423
23459
  }
23424
23460
  const defaultWorkspace = value["defaultWorkspace"];
23425
23461
  const channelId = value["channelId"];
23426
- const daemonToken2 = value["daemonToken"];
23462
+ const runtimeCredential = value["runtimeCredential"] ?? value["daemonToken"];
23427
23463
  const relayOrigin = value["relayOrigin"];
23428
- if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN5.test(channelId) || typeof daemonToken2 !== "string" || !TOKEN_PATTERN2.test(daemonToken2) || typeof relayOrigin !== "string") {
23464
+ if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN5.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof relayOrigin !== "string") {
23429
23465
  throw new PairingClientError("invalid_connection", "The saved connection is invalid");
23430
23466
  }
23431
23467
  return {
23432
23468
  defaultWorkspace,
23433
23469
  channelId,
23434
- daemonToken: daemonToken2,
23435
- relayOrigin: parseRelayOrigin(relayOrigin)
23470
+ relayOrigin: parseRelayOrigin(relayOrigin),
23471
+ runtimeCredential
23436
23472
  };
23437
23473
  }
23438
- async function readSavedConnection(channelId) {
23439
- const connectionPath = join14(readStateDirectory(), "connections", `${channelId}.json`);
23474
+ async function readSavedConnection(channelId, environment) {
23475
+ const connectionPath = join14(readStateDirectory(environment), "connections", `${channelId}.json`);
23440
23476
  let encoded = "";
23441
23477
  const handle = await open4(connectionPath, "r");
23442
23478
  try {
@@ -23458,8 +23494,8 @@ async function readSavedConnection(channelId) {
23458
23494
  return {
23459
23495
  defaultWorkspace: await resolveDefaultWorkspace(connection.defaultWorkspace),
23460
23496
  channelId: connection.channelId,
23461
- daemonToken: connection.daemonToken,
23462
- relayOrigin: connection.relayOrigin
23497
+ relayOrigin: connection.relayOrigin,
23498
+ runtimeCredential: connection.runtimeCredential
23463
23499
  };
23464
23500
  }
23465
23501
  function assertInventoryUsable(inventory) {
@@ -23467,15 +23503,12 @@ function assertInventoryUsable(inventory) {
23467
23503
  throw new PairingClientError("no_supported_agent", "No supported Agent is available for local discovery");
23468
23504
  }
23469
23505
  }
23470
- function buildPairedDaemon(connection, connectionPath, inventory) {
23506
+ function buildPairedDaemon(connection, connectionPath, inventory, environment) {
23471
23507
  assertInventoryUsable(inventory);
23472
- const relayUrl = mobiusApiUrl(
23473
- connection.relayOrigin,
23474
- `/channels/${encodeURIComponent(connection.channelId)}/daemon`
23475
- );
23508
+ const relayUrl = mobiusApiUrl(connection.relayOrigin, "/v1/runtimes/connect");
23476
23509
  relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
23477
- relayUrl.searchParams.set("token", connection.daemonToken);
23478
- const auditDatabasePath = join14(readStateDirectory(), "audit", `${connection.channelId}.sqlite`);
23510
+ relayUrl.searchParams.set("token", connection.runtimeCredential);
23511
+ const auditDatabasePath = join14(readStateDirectory(environment), "audit", `${connection.channelId}.sqlite`);
23479
23512
  return {
23480
23513
  channelUrl: new URL(`/channels/${encodeURIComponent(connection.channelId)}`, connection.relayOrigin).toString(),
23481
23514
  config: createDaemonConfig(
@@ -23491,12 +23524,11 @@ function buildPairedDaemon(connection, connectionPath, inventory) {
23491
23524
  connectionPath
23492
23525
  };
23493
23526
  }
23494
- async function pairLocalMachine(arguments_, signal = new AbortController().signal) {
23495
- const options = parsePairCommand(arguments_);
23527
+ async function pairRuntime(options, signal, environment) {
23496
23528
  const defaultWorkspace = await resolveDefaultWorkspace(options.workspace);
23497
23529
  const inventory = await discoverLocalAgentInventory({
23498
23530
  defaultWorkspace,
23499
- environment: process.env,
23531
+ environment,
23500
23532
  signal
23501
23533
  });
23502
23534
  assertInventoryUsable(inventory);
@@ -23505,35 +23537,91 @@ async function pairLocalMachine(arguments_, signal = new AbortController().signa
23505
23537
  const connectionPath = await persistConnection(
23506
23538
  claim2.channelId,
23507
23539
  options.relayOrigin,
23508
- claim2.daemonToken,
23509
- defaultWorkspace
23540
+ claim2.runtimeCredential,
23541
+ defaultWorkspace,
23542
+ environment
23510
23543
  );
23511
23544
  return buildPairedDaemon(
23512
23545
  {
23513
23546
  defaultWorkspace,
23514
23547
  channelId: claim2.channelId,
23515
- daemonToken: claim2.daemonToken,
23516
- relayOrigin: options.relayOrigin
23548
+ relayOrigin: options.relayOrigin,
23549
+ runtimeCredential: claim2.runtimeCredential
23517
23550
  },
23518
23551
  connectionPath,
23519
- inventory
23552
+ inventory,
23553
+ environment
23520
23554
  );
23521
23555
  }
23522
- async function resumeLocalMachine(arguments_, signal = new AbortController().signal) {
23523
- const channelId = parseChannelArgument(arguments_);
23524
- const connectionPath = join14(readStateDirectory(), "connections", `${channelId}.json`);
23525
- const connection = await readSavedConnection(channelId);
23556
+ async function pairLocalMachine(arguments_, signal = new AbortController().signal) {
23557
+ return await pairRuntime(parsePairCommand(arguments_), signal, process.env);
23558
+ }
23559
+ async function resumeMobiusRuntime(channelId, signal, environment = process.env) {
23560
+ const normalizedChannelId = validatePairingId(channelId);
23561
+ const connectionPath = join14(readStateDirectory(environment), "connections", `${normalizedChannelId}.json`);
23562
+ const connection = await readSavedConnection(normalizedChannelId, environment);
23526
23563
  const inventory = await discoverLocalAgentInventory({
23527
23564
  defaultWorkspace: connection.defaultWorkspace,
23528
- environment: process.env,
23565
+ environment,
23529
23566
  signal
23530
23567
  });
23531
- return buildPairedDaemon(connection, connectionPath, inventory);
23568
+ return buildPairedDaemon(connection, connectionPath, inventory, environment);
23569
+ }
23570
+ async function resumeLocalMachine(arguments_, signal = new AbortController().signal) {
23571
+ return await resumeMobiusRuntime(parseChannelArgument(arguments_), signal, process.env);
23532
23572
  }
23533
23573
 
23574
+ // src/audit/audit-store.ts
23575
+ import { mkdirSync } from "node:fs";
23576
+ import { dirname as dirname3 } from "node:path";
23577
+ import { DatabaseSync } from "node:sqlite";
23578
+ var AuditStore = class {
23579
+ database;
23580
+ constructor(path2) {
23581
+ mkdirSync(dirname3(path2), { recursive: true });
23582
+ this.database = new DatabaseSync(path2);
23583
+ this.database.exec(`
23584
+ CREATE TABLE IF NOT EXISTS audit_events (
23585
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
23586
+ recorded_at TEXT NOT NULL,
23587
+ event TEXT NOT NULL,
23588
+ command_id TEXT NOT NULL,
23589
+ session_id TEXT NOT NULL,
23590
+ details_json TEXT NOT NULL DEFAULT '{}'
23591
+ ) STRICT
23592
+ `);
23593
+ const columns = this.database.prepare("PRAGMA table_info(audit_events)").all();
23594
+ if (!columns.some((column) => Reflect.get(column, "name") === "details_json")) {
23595
+ this.database.exec("ALTER TABLE audit_events ADD COLUMN details_json TEXT NOT NULL DEFAULT '{}'");
23596
+ }
23597
+ }
23598
+ record(record2) {
23599
+ this.database.prepare(
23600
+ `
23601
+ INSERT INTO audit_events (
23602
+ recorded_at,
23603
+ event,
23604
+ command_id,
23605
+ session_id,
23606
+ details_json
23607
+ ) VALUES (?, ?, ?, ?, ?)
23608
+ `
23609
+ ).run(
23610
+ (/* @__PURE__ */ new Date()).toISOString(),
23611
+ record2.event,
23612
+ record2.commandId,
23613
+ record2.sessionId,
23614
+ JSON.stringify(record2.details ?? {})
23615
+ );
23616
+ }
23617
+ close() {
23618
+ this.database.close();
23619
+ }
23620
+ };
23621
+
23534
23622
  // src/run-daemon.ts
23535
23623
  import { randomUUID as randomUUID6 } from "node:crypto";
23536
- import { basename as basename3, join as join16 } from "node:path";
23624
+ import { basename as basename4, join as join16 } from "node:path";
23537
23625
  import { styleText } from "node:util";
23538
23626
  import { fetch as fetch6, WebSocket as WebSocket2 } from "undici";
23539
23627
 
@@ -23548,7 +23636,7 @@ import {
23548
23636
 
23549
23637
  // src/acp/activity.ts
23550
23638
  var TRUNCATION_MARKER = "\n\u2026 output truncated by Mobius";
23551
- function isRecord7(value) {
23639
+ function isRecord8(value) {
23552
23640
  return typeof value === "object" && value !== null && !Array.isArray(value);
23553
23641
  }
23554
23642
  var AcpActivity = class _AcpActivity {
@@ -23815,7 +23903,7 @@ function contentPhase(content) {
23815
23903
  return "";
23816
23904
  }
23817
23905
  const codex = metadata["codex"];
23818
- if (!isRecord7(codex)) {
23906
+ if (!isRecord8(codex)) {
23819
23907
  return "";
23820
23908
  }
23821
23909
  const phase = codex["phase"];
@@ -24053,7 +24141,7 @@ var SessionConfigProtocolError = class extends Error {
24053
24141
  this.name = "SessionConfigProtocolError";
24054
24142
  }
24055
24143
  };
24056
- function isRecord8(value) {
24144
+ function isRecord9(value) {
24057
24145
  return typeof value === "object" && value !== null && !Array.isArray(value);
24058
24146
  }
24059
24147
  function readString2(value, fallback = "") {
@@ -24080,7 +24168,7 @@ function normalizeConfigValues(value) {
24080
24168
  }
24081
24169
  const normalized = [];
24082
24170
  for (const entry of value) {
24083
- if (!isRecord8(entry)) {
24171
+ if (!isRecord9(entry)) {
24084
24172
  continue;
24085
24173
  }
24086
24174
  const groupedOptions = entry["options"];
@@ -24088,7 +24176,7 @@ function normalizeConfigValues(value) {
24088
24176
  const groupId = readString2(entry["group"]);
24089
24177
  const groupName = readString2(entry["name"]);
24090
24178
  for (const groupedOption of groupedOptions) {
24091
- if (isRecord8(groupedOption)) {
24179
+ if (isRecord9(groupedOption)) {
24092
24180
  normalized.push(normalizeConfigValue(groupedOption, groupId, groupName));
24093
24181
  }
24094
24182
  }
@@ -24102,7 +24190,7 @@ function normalizeConfigOptions(value, modes) {
24102
24190
  const normalized = [];
24103
24191
  if (Array.isArray(value)) {
24104
24192
  for (const option2 of value) {
24105
- if (!isRecord8(option2)) {
24193
+ if (!isRecord9(option2)) {
24106
24194
  continue;
24107
24195
  }
24108
24196
  const id = truncate2(readString2(option2["id"]), 128);
@@ -24141,12 +24229,12 @@ function normalizeConfigOptions(value, modes) {
24141
24229
  }
24142
24230
  }
24143
24231
  }
24144
- if (isRecord8(modes)) {
24232
+ if (isRecord9(modes)) {
24145
24233
  const values = [];
24146
24234
  const availableModes = modes["availableModes"];
24147
24235
  if (Array.isArray(availableModes)) {
24148
24236
  for (const mode of availableModes) {
24149
- if (!isRecord8(mode)) {
24237
+ if (!isRecord9(mode)) {
24150
24238
  continue;
24151
24239
  }
24152
24240
  const id = truncate2(readString2(mode["id"]), 500);
@@ -24224,7 +24312,7 @@ function ensureReasoningEffortOption(current) {
24224
24312
  return [...current.slice(0, MAXIMUM_CONFIG_OPTIONS - 2), model, unavailable];
24225
24313
  }
24226
24314
  function normalizeStandardLifecycle(response) {
24227
- const lifecycle = isRecord8(response) ? response : {};
24315
+ const lifecycle = isRecord9(response) ? response : {};
24228
24316
  return ensureReasoningEffortOption(normalizeConfigOptions(lifecycle["configOptions"], lifecycle["modes"]));
24229
24317
  }
24230
24318
  function replaceStandardConfigOptions(current, value) {
@@ -24266,7 +24354,7 @@ var StandardSessionConfigProtocol = class {
24266
24354
  "ACP agent exited while changing session configuration",
24267
24355
  change.signal
24268
24356
  );
24269
- if (!isRecord8(response) || !Array.isArray(response["configOptions"])) {
24357
+ if (!isRecord9(response) || !Array.isArray(response["configOptions"])) {
24270
24358
  throw new SessionConfigProtocolError("The Agent returned an invalid session configuration response");
24271
24359
  }
24272
24360
  return replaceStandardConfigOptions(current, response["configOptions"]);
@@ -24280,18 +24368,18 @@ var standardSessionConfigProtocol = new StandardSessionConfigProtocol();
24280
24368
  var LEGACY_MODEL_CONFIG_ID = "grok_build_legacy_model";
24281
24369
  var LEGACY_REASONING_EFFORT_CONFIG_ID = "grok_build_legacy_reasoning_effort";
24282
24370
  function legacyOptions(response) {
24283
- if (!isRecord8(response)) {
24371
+ if (!isRecord9(response)) {
24284
24372
  return [];
24285
24373
  }
24286
24374
  const metadata = response["_meta"];
24287
- if (!isRecord8(metadata)) {
24375
+ if (!isRecord9(metadata)) {
24288
24376
  return [];
24289
24377
  }
24290
24378
  const sessionConfig = metadata["x.ai/sessionConfig"];
24291
- if (!isRecord8(sessionConfig) || !Array.isArray(sessionConfig["options"])) {
24379
+ if (!isRecord9(sessionConfig) || !Array.isArray(sessionConfig["options"])) {
24292
24380
  return [];
24293
24381
  }
24294
- return sessionConfig["options"].slice(0, 500).filter(isRecord8);
24382
+ return sessionConfig["options"].slice(0, 500).filter(isRecord9);
24295
24383
  }
24296
24384
  function legacyValue(option2) {
24297
24385
  const value = readString2(option2["id"]);
@@ -24304,7 +24392,7 @@ function legacyValue(option2) {
24304
24392
  };
24305
24393
  }
24306
24394
  function modelsRecord(response) {
24307
- if (!isRecord8(response) || !isRecord8(response["models"])) {
24395
+ if (!isRecord9(response) || !isRecord9(response["models"])) {
24308
24396
  return {};
24309
24397
  }
24310
24398
  return response["models"];
@@ -24323,7 +24411,7 @@ function legacyModelOptions(response) {
24323
24411
  }
24324
24412
  if (values.length === 0 && Array.isArray(models["availableModels"])) {
24325
24413
  for (const model of models["availableModels"]) {
24326
- if (!isRecord8(model)) {
24414
+ if (!isRecord9(model)) {
24327
24415
  continue;
24328
24416
  }
24329
24417
  const value = readString2(model["modelId"]).slice(0, 500);
@@ -24403,7 +24491,7 @@ function legacyReasoningEffortOptions(response) {
24403
24491
  return [result];
24404
24492
  }
24405
24493
  function normalizeGrokLifecycle(response) {
24406
- const lifecycle = isRecord8(response) ? response : {};
24494
+ const lifecycle = isRecord9(response) ? response : {};
24407
24495
  const standard = normalizeConfigOptions(lifecycle["configOptions"], lifecycle["modes"]);
24408
24496
  const normalized = [...standard];
24409
24497
  if (!normalized.some((option2) => option2.category === "model")) {
@@ -24422,11 +24510,11 @@ function updateCurrentValue2(current, category, value) {
24422
24510
  return current.map((option2) => option2.category === category ? { ...option2, currentValue: value } : option2);
24423
24511
  }
24424
24512
  function validateGrokResponse(response, modelId) {
24425
- if (!isRecord8(response) || !isRecord8(response["_meta"])) {
24513
+ if (!isRecord9(response) || !isRecord9(response["_meta"])) {
24426
24514
  throw new SessionConfigProtocolError("Grok Build returned an invalid session configuration response");
24427
24515
  }
24428
24516
  const model = response["_meta"]["model"];
24429
- const appliedModel = isRecord8(model) ? model["Ok"] : model;
24517
+ const appliedModel = isRecord9(model) ? model["Ok"] : model;
24430
24518
  if (appliedModel !== modelId) {
24431
24519
  throw new SessionConfigProtocolError("Grok Build returned an invalid session configuration response");
24432
24520
  }
@@ -24869,7 +24957,7 @@ function createWritableAcpStream(output2) {
24869
24957
  function truncate3(value, maximumLength) {
24870
24958
  return value.length <= maximumLength ? value : `${value.slice(0, Math.max(0, maximumLength - 1))}\u2026`;
24871
24959
  }
24872
- function isRecord9(value) {
24960
+ function isRecord10(value) {
24873
24961
  return typeof value === "object" && value !== null && !Array.isArray(value);
24874
24962
  }
24875
24963
  function readString4(value, fallback = "") {
@@ -24878,6 +24966,11 @@ function readString4(value, fallback = "") {
24878
24966
  function arraysEqual(left, right) {
24879
24967
  return left.length === right.length && left.every((value, index) => value === right[index]);
24880
24968
  }
24969
+ function recordsEqual(left, right) {
24970
+ const leftEntries = Object.entries(left);
24971
+ const rightEntries = Object.entries(right);
24972
+ return leftEntries.length === rightEntries.length && leftEntries.every(([name, value]) => right[name] === value);
24973
+ }
24881
24974
  function elicitationFieldType(value) {
24882
24975
  if (value === "boolean") {
24883
24976
  return "boolean";
@@ -24898,7 +24991,7 @@ function normalizeElicitationOptions(schema) {
24898
24991
  const oneOf = schema["oneOf"];
24899
24992
  if (Array.isArray(oneOf)) {
24900
24993
  for (const option2 of oneOf) {
24901
- if (isRecord9(option2) && typeof option2["const"] === "string") {
24994
+ if (isRecord10(option2) && typeof option2["const"] === "string") {
24902
24995
  normalized.push({
24903
24996
  description: truncate3(readString4(option2["description"]), 500),
24904
24997
  label: truncate3(readString4(option2["title"], option2["const"]), 255),
@@ -24918,7 +25011,7 @@ function normalizeElicitationOptions(schema) {
24918
25011
  return normalized.slice(0, 200);
24919
25012
  }
24920
25013
  const items = schema["items"];
24921
- if (!isRecord9(items)) {
25014
+ if (!isRecord10(items)) {
24922
25015
  return normalized;
24923
25016
  }
24924
25017
  const anyOf = items["anyOf"];
@@ -24931,7 +25024,7 @@ function numericString(value) {
24931
25024
  return typeof value === "number" && Number.isFinite(value) ? value.toString() : "";
24932
25025
  }
24933
25026
  function normalizeElicitationFields(requestedSchema) {
24934
- if (!isRecord9(requestedSchema) || !isRecord9(requestedSchema["properties"])) {
25027
+ if (!isRecord10(requestedSchema) || !isRecord10(requestedSchema["properties"])) {
24935
25028
  return [];
24936
25029
  }
24937
25030
  const properties = requestedSchema["properties"];
@@ -24941,7 +25034,7 @@ function normalizeElicitationFields(requestedSchema) {
24941
25034
  );
24942
25035
  const normalized = [];
24943
25036
  for (const [name, property] of Object.entries(properties)) {
24944
- if (!isRecord9(property)) {
25037
+ if (!isRecord10(property)) {
24945
25038
  continue;
24946
25039
  }
24947
25040
  const type = elicitationFieldType(property["type"]);
@@ -25032,7 +25125,7 @@ function validateElicitationContent(encoded, requestedSchema) {
25032
25125
  "invalid_elicitation_response"
25033
25126
  );
25034
25127
  }
25035
- if (!isRecord9(decoded) || !isRecord9(requestedSchema) || !isRecord9(requestedSchema["properties"])) {
25128
+ if (!isRecord10(decoded) || !isRecord10(requestedSchema) || !isRecord10(requestedSchema["properties"])) {
25036
25129
  throw new AcpExecutionError(
25037
25130
  "Elicitation response does not match the requested form",
25038
25131
  {},
@@ -25052,7 +25145,7 @@ function validateElicitationContent(encoded, requestedSchema) {
25052
25145
  const validated = {};
25053
25146
  for (const [name, value] of Object.entries(decoded)) {
25054
25147
  const property = properties[name];
25055
- if (!isRecord9(property)) {
25148
+ if (!isRecord10(property)) {
25056
25149
  throw new AcpExecutionError(
25057
25150
  `Elicitation field ${name} was not requested`,
25058
25151
  {},
@@ -25194,6 +25287,7 @@ var AcpProcessSession = class _AcpProcessSession {
25194
25287
  #child;
25195
25288
  #connection;
25196
25289
  #cwd;
25290
+ #environment;
25197
25291
  #getObserver;
25198
25292
  #mobiusSessionId;
25199
25293
  #pendingElicitations = /* @__PURE__ */ new Map();
@@ -25215,7 +25309,9 @@ var AcpProcessSession = class _AcpProcessSession {
25215
25309
  imagePrompt: false,
25216
25310
  listSessions: false,
25217
25311
  loadSession: false,
25312
+ promptQueueing: false,
25218
25313
  resumeSession: false,
25314
+ steering: false,
25219
25315
  unstableForkSession: false
25220
25316
  };
25221
25317
  #commands = [];
@@ -25235,6 +25331,7 @@ var AcpProcessSession = class _AcpProcessSession {
25235
25331
  this.#additionalDirectories = [...additionalDirectories];
25236
25332
  this.#agentId = agent.id;
25237
25333
  this.#cwd = cwd;
25334
+ this.#environment = agent.environment;
25238
25335
  this.#getObserver = getObserver;
25239
25336
  this.#mobiusSessionId = mobiusSessionId;
25240
25337
  this.#sessionConfigProtocol = sessionConfigProtocolForAgent(agent.id);
@@ -25320,7 +25417,18 @@ var AcpProcessSession = class _AcpProcessSession {
25320
25417
  store,
25321
25418
  async (runtime) => await runtime.#initializeAgent(options.signal)
25322
25419
  );
25420
+ static startSessionForkAtTurn = async (options, sourceNativeSessionId, forkPoint, getObserver, store) => await _AcpProcessSession.#start(
25421
+ options,
25422
+ getObserver,
25423
+ store,
25424
+ async (runtime) => await runtime.initialize(
25425
+ options.signal,
25426
+ { kind: "fork", nativeSessionId: sourceNativeSessionId },
25427
+ forkPoint
25428
+ )
25429
+ );
25323
25430
  belongsTo = (agentId, cwd, additionalDirectories) => this.#agentId === agentId && this.#cwd === cwd && arraysEqual(this.#additionalDirectories, additionalDirectories);
25431
+ usesEnvironment = (environment) => recordsEqual(this.#environment, environment);
25324
25432
  canRebind = () => !this.#prompted && !this.#turn.running;
25325
25433
  prepared = () => new AcpPreparedSession(
25326
25434
  this.#agentName,
@@ -25386,15 +25494,14 @@ var AcpProcessSession = class _AcpProcessSession {
25386
25494
  "unsupported_acp_protocol"
25387
25495
  );
25388
25496
  }
25389
- const capabilities = initialized.agentCapabilities ?? {};
25390
- this.#capabilities = normalizeAcpCapabilities(capabilities);
25497
+ this.#capabilities = normalizeAcpCapabilities(initialized);
25391
25498
  this.#agentName = truncate3(initialized.agentInfo?.title ?? initialized.agentInfo?.name ?? this.#agentId, 255);
25392
25499
  this.#agentVersion = truncate3(
25393
25500
  this.#verifiedAgentVersion.length > 0 ? this.#verifiedAgentVersion : initialized.agentInfo?.version ?? "",
25394
25501
  128
25395
25502
  );
25396
25503
  }
25397
- initialize = async (signal, origin) => {
25504
+ initialize = async (signal, origin, forkPoint) => {
25398
25505
  await this.#initializeAgent(signal);
25399
25506
  let stored = origin?.kind === "import" ? new FoundAcpStoredSession(
25400
25507
  new AcpStoredSession(
@@ -25426,6 +25533,13 @@ var AcpProcessSession = class _AcpProcessSession {
25426
25533
  "native_session_unsupported"
25427
25534
  );
25428
25535
  }
25536
+ if (this.#additionalDirectories.length > 0 && !this.#capabilities.additionalDirectories) {
25537
+ throw new AcpExecutionError(
25538
+ "This Agent cannot restore additional working directories",
25539
+ {},
25540
+ "native_session_unsupported"
25541
+ );
25542
+ }
25429
25543
  const forked = await this.race(
25430
25544
  this.#connection.agent.request(
25431
25545
  methods6.agent.session.fork,
@@ -25433,7 +25547,21 @@ var AcpProcessSession = class _AcpProcessSession {
25433
25547
  sessionId: nativeSessionIdSchema.parse(origin.nativeSessionId),
25434
25548
  cwd: this.#cwd,
25435
25549
  additionalDirectories: [...this.#additionalDirectories],
25436
- mcpServers: []
25550
+ mcpServers: [],
25551
+ ...forkPoint === void 0 ? {} : {
25552
+ _meta: {
25553
+ jetbrains: {
25554
+ air: {
25555
+ fork: {
25556
+ version: 1,
25557
+ messageFingerprint: forkPoint.messageFingerprint,
25558
+ messageId: forkPoint.messageId,
25559
+ messageOccurrence: forkPoint.messageOccurrence
25560
+ }
25561
+ }
25562
+ }
25563
+ }
25564
+ }
25437
25565
  },
25438
25566
  { cancellationSignal: signal }
25439
25567
  ),
@@ -25638,10 +25766,7 @@ var AcpProcessSession = class _AcpProcessSession {
25638
25766
  }
25639
25767
  return this.#configOptions;
25640
25768
  };
25641
- prompt = async (options) => {
25642
- if (this.#turn.running) {
25643
- throw new AcpExecutionError("Another prompt is already running in this conversation", {}, "session_busy");
25644
- }
25769
+ buildPrompt = (options) => {
25645
25770
  if (options.images.length > 0 && !this.#capabilities.imagePrompt) {
25646
25771
  throw new AcpExecutionError(
25647
25772
  "The selected agent does not accept image prompts",
@@ -25690,6 +25815,13 @@ var AcpProcessSession = class _AcpProcessSession {
25690
25815
  if (prompt.length === 0) {
25691
25816
  throw new AcpExecutionError("A prompt must contain text or an attachment", {}, "empty_prompt");
25692
25817
  }
25818
+ return prompt;
25819
+ };
25820
+ prompt = async (options) => {
25821
+ if (this.#turn.running) {
25822
+ throw new AcpExecutionError("Another prompt is already running in this conversation", {}, "session_busy");
25823
+ }
25824
+ const prompt = this.buildPrompt(options);
25693
25825
  this.#store.write(
25694
25826
  new AcpStoredSession(
25695
25827
  this.#mobiusSessionId,
@@ -25748,6 +25880,54 @@ var AcpProcessSession = class _AcpProcessSession {
25748
25880
  this.#turn = new IdleAcpTurn();
25749
25881
  }
25750
25882
  };
25883
+ steer = async (options) => {
25884
+ if (!this.#capabilities.steering) {
25885
+ throw new AcpExecutionError(
25886
+ "The selected agent does not support mid-turn steering",
25887
+ {},
25888
+ "steering_unsupported"
25889
+ );
25890
+ }
25891
+ if (!this.#turn.running || this.#turn.commandId !== options.targetCommandId) {
25892
+ throw new AcpExecutionError("The target turn is no longer active", {}, "steering_target_unavailable");
25893
+ }
25894
+ const prompt = this.buildPrompt(options);
25895
+ const response = await this.race(
25896
+ this.#connection.agent.request("_session/steering", {
25897
+ _meta: { steering: { idleBehavior: "promptRequired" } },
25898
+ prompt,
25899
+ sessionId: this.#nativeSessionId
25900
+ }),
25901
+ "ACP agent exited during steering",
25902
+ options.signal
25903
+ );
25904
+ if (!isRecord10(response)) {
25905
+ throw new AcpExecutionError(
25906
+ "ACP agent returned an invalid steering response",
25907
+ {},
25908
+ "acp_invalid_steering_response"
25909
+ );
25910
+ }
25911
+ const outcome = response["outcome"];
25912
+ if (outcome === "injected" || outcome === "startedNewTurn") {
25913
+ return outcome;
25914
+ }
25915
+ if (outcome === "promptRequired") {
25916
+ throw new AcpExecutionError(
25917
+ "The target turn finished before steering could be injected",
25918
+ {},
25919
+ "steering_target_unavailable"
25920
+ );
25921
+ }
25922
+ if (outcome === "failed") {
25923
+ throw new AcpExecutionError("The Agent could not inject steering", {}, "steering_failed");
25924
+ }
25925
+ throw new AcpExecutionError(
25926
+ "ACP agent returned an invalid steering response",
25927
+ {},
25928
+ "acp_invalid_steering_response"
25929
+ );
25930
+ };
25751
25931
  cancel = async () => {
25752
25932
  const capturedProcesses = await captureProcessTree(this.#child);
25753
25933
  try {
@@ -25988,7 +26168,7 @@ var AcpProcessSession = class _AcpProcessSession {
25988
26168
  );
25989
26169
  } else {
25990
26170
  await this.#turn.activities.finishNarrative();
25991
- await this.#turn.onText(update.content.text);
26171
+ await this.#turn.onText(update.content.text, truncate3(update.messageId ?? "", 255));
25992
26172
  }
25993
26173
  } else {
25994
26174
  await this.#turn.activities.finishNarrative();
@@ -26214,6 +26394,7 @@ var AcpProcessSession = class _AcpProcessSession {
26214
26394
  };
26215
26395
  };
26216
26396
  var AcpSessionManager = class {
26397
+ #activeCommandIds = /* @__PURE__ */ new Map();
26217
26398
  #nativeOperations = /* @__PURE__ */ new Set();
26218
26399
  #nativeBrowserObserver = new NullAcpSessionObserver();
26219
26400
  #nativeBrowserPreparing = /* @__PURE__ */ new Map();
@@ -26282,6 +26463,25 @@ var AcpSessionManager = class {
26282
26463
  this.#preparing.set(options.sessionId, operation);
26283
26464
  return await operation;
26284
26465
  }
26466
+ async #startSessionForkAtTurnPreparation(options, sourceNativeSessionId, forkPoint) {
26467
+ const operation = (async () => {
26468
+ try {
26469
+ const runtime = await AcpProcessSession.startSessionForkAtTurn(
26470
+ options,
26471
+ sourceNativeSessionId,
26472
+ forkPoint,
26473
+ () => this.#observer,
26474
+ this.#store
26475
+ );
26476
+ this.#sessions.set(options.sessionId, runtime);
26477
+ return runtime;
26478
+ } finally {
26479
+ this.#preparing.delete(options.sessionId);
26480
+ }
26481
+ })();
26482
+ this.#preparing.set(options.sessionId, operation);
26483
+ return await operation;
26484
+ }
26285
26485
  async #replacePreparation(existing, options) {
26286
26486
  const operation = (async () => {
26287
26487
  try {
@@ -26298,6 +26498,58 @@ var AcpSessionManager = class {
26298
26498
  this.#preparing.set(options.sessionId, operation);
26299
26499
  return await operation;
26300
26500
  }
26501
+ async #replaceExecutionPreparation(existing, options) {
26502
+ const desiredConfigOptions = existing.prepared().configOptions;
26503
+ const operation = (async () => {
26504
+ try {
26505
+ this.#sessions.delete(options.sessionId);
26506
+ await existing.close();
26507
+ this.#store.remove(options.sessionId);
26508
+ const runtime = await AcpProcessSession.start(options, () => this.#observer, this.#store);
26509
+ try {
26510
+ for (const desired of desiredConfigOptions) {
26511
+ const current = runtime.prepared().configOptions.find((option2) => option2.id === desired.id);
26512
+ if (current === void 0 || current.type !== desired.type) {
26513
+ continue;
26514
+ }
26515
+ if (desired.type === "boolean") {
26516
+ if (current.booleanValue !== desired.booleanValue) {
26517
+ await runtime.configure({
26518
+ ...options,
26519
+ booleanValue: desired.booleanValue,
26520
+ configId: desired.id,
26521
+ requestId: `execution-prepare-${desired.id}`,
26522
+ value: "",
26523
+ valueType: "boolean"
26524
+ });
26525
+ }
26526
+ continue;
26527
+ }
26528
+ if (current.currentValue !== desired.currentValue && current.values.some((value) => value.value === desired.currentValue)) {
26529
+ await runtime.configure({
26530
+ ...options,
26531
+ booleanValue: false,
26532
+ configId: desired.id,
26533
+ requestId: `execution-prepare-${desired.id}`,
26534
+ value: desired.currentValue,
26535
+ valueType: "select"
26536
+ });
26537
+ }
26538
+ }
26539
+ } catch (error61) {
26540
+ await runtime.close();
26541
+ this.#store.remove(options.sessionId);
26542
+ throw error61;
26543
+ }
26544
+ this.#sessions.set(options.sessionId, runtime);
26545
+ return runtime;
26546
+ } finally {
26547
+ this.#preparing.delete(options.sessionId);
26548
+ }
26549
+ })();
26550
+ this.#preparing.set(options.sessionId, operation);
26551
+ return await operation;
26552
+ }
26301
26553
  prepareSession = async (options) => {
26302
26554
  const stored = this.#store.read(options.sessionId);
26303
26555
  if (stored instanceof DeletedAcpStoredSession) {
@@ -26336,16 +26588,45 @@ var AcpSessionManager = class {
26336
26588
  const runtime = await this.#startPreparation(options);
26337
26589
  return runtime.prepared();
26338
26590
  };
26339
- prepareNativeSessionBrowser = async (options) => {
26340
- const pending = this.#nativeBrowserPreparing.get(options.sessionId);
26591
+ prepareExecutionSession = async (options) => {
26592
+ const pending = this.#preparing.get(options.sessionId);
26341
26593
  if (pending !== void 0) {
26342
- const runtime2 = await pending;
26343
- if (runtime2.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
26344
- return runtime2.prepared();
26345
- }
26346
- return await this.prepareNativeSessionBrowser(options);
26594
+ await pending;
26595
+ return await this.prepareExecutionSession(options);
26347
26596
  }
26348
- const existing = this.#nativeBrowserSessions.get(options.sessionId);
26597
+ const existing = this.#sessions.get(options.sessionId);
26598
+ if (existing === void 0) {
26599
+ return (await this.#startPreparation(options)).prepared();
26600
+ }
26601
+ if (!existing.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
26602
+ throw new AcpExecutionError(
26603
+ "The agent or workspace cannot change after a conversation has started",
26604
+ {},
26605
+ "session_identity_mismatch"
26606
+ );
26607
+ }
26608
+ if (existing.usesEnvironment(options.agent.environment)) {
26609
+ return existing.prepared();
26610
+ }
26611
+ if (!existing.canRebind()) {
26612
+ throw new AcpExecutionError(
26613
+ "The execution environment cannot change after a conversation has started",
26614
+ {},
26615
+ "session_identity_mismatch"
26616
+ );
26617
+ }
26618
+ return (await this.#replaceExecutionPreparation(existing, options)).prepared();
26619
+ };
26620
+ prepareNativeSessionBrowser = async (options) => {
26621
+ const pending = this.#nativeBrowserPreparing.get(options.sessionId);
26622
+ if (pending !== void 0) {
26623
+ const runtime2 = await pending;
26624
+ if (runtime2.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
26625
+ return runtime2.prepared();
26626
+ }
26627
+ return await this.prepareNativeSessionBrowser(options);
26628
+ }
26629
+ const existing = this.#nativeBrowserSessions.get(options.sessionId);
26349
26630
  if (existing !== void 0) {
26350
26631
  if (existing.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
26351
26632
  return existing.prepared();
@@ -26380,6 +26661,47 @@ var AcpSessionManager = class {
26380
26661
  }
26381
26662
  }
26382
26663
  };
26664
+ steerPrompt = async (options) => {
26665
+ if (this.#activeCommandIds.get(options.sessionId) !== options.targetCommandId) {
26666
+ throw new AcpExecutionError("The target turn is no longer active", {}, "steering_target_unavailable");
26667
+ }
26668
+ const pending = this.#preparing.get(options.sessionId);
26669
+ if (pending !== void 0) {
26670
+ await pending;
26671
+ }
26672
+ if (this.#activeCommandIds.get(options.sessionId) !== options.targetCommandId) {
26673
+ throw new AcpExecutionError("The target turn is no longer active", {}, "steering_target_unavailable");
26674
+ }
26675
+ const runtime = this.#sessions.get(options.sessionId);
26676
+ if (runtime === void 0 || !runtime.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
26677
+ throw new AcpExecutionError(
26678
+ "The target turn execution context is unavailable",
26679
+ {},
26680
+ "steering_target_unavailable"
26681
+ );
26682
+ }
26683
+ const deadlineController = new AbortController();
26684
+ const deadline = setTimeout(
26685
+ () => deadlineController.abort(
26686
+ new AcpExecutionError("The steering request exceeded its deadline", {}, "steering_timeout")
26687
+ ),
26688
+ options.agent.promptTimeoutMilliseconds
26689
+ );
26690
+ deadline.unref();
26691
+ try {
26692
+ return await runtime.steer({
26693
+ ...options,
26694
+ signal: AbortSignal.any([options.signal, deadlineController.signal])
26695
+ });
26696
+ } catch (error61) {
26697
+ if (error61 instanceof AcpExecutionError) {
26698
+ throw error61;
26699
+ }
26700
+ throw new AcpExecutionError("ACP agent steering failed", { cause: error61 }, "steering_failed");
26701
+ } finally {
26702
+ clearTimeout(deadline);
26703
+ }
26704
+ };
26383
26705
  runPrompt = async (options) => {
26384
26706
  const stored = this.#store.read(options.sessionId);
26385
26707
  if (stored instanceof FoundAcpStoredSession && this.#nativeOperations.has(`${stored.session.agentId}:${stored.session.nativeSessionId}`)) {
@@ -26404,6 +26726,7 @@ var AcpSessionManager = class {
26404
26726
  signal: AbortSignal.any([options.signal, deadlineController.signal])
26405
26727
  };
26406
26728
  this.#activeSessionIds.add(options.sessionId);
26729
+ this.#activeCommandIds.set(options.sessionId, options.commandId);
26407
26730
  try {
26408
26731
  if (this.#cancellationRequests.has(options.sessionId)) {
26409
26732
  return { stopReason: "cancelled" };
@@ -26443,6 +26766,9 @@ var AcpSessionManager = class {
26443
26766
  } finally {
26444
26767
  clearTimeout(deadline);
26445
26768
  this.#activeSessionIds.delete(options.sessionId);
26769
+ if (this.#activeCommandIds.get(options.sessionId) === options.commandId) {
26770
+ this.#activeCommandIds.delete(options.sessionId);
26771
+ }
26446
26772
  this.#cancellationRequests.delete(options.sessionId);
26447
26773
  }
26448
26774
  };
@@ -26467,6 +26793,7 @@ var AcpSessionManager = class {
26467
26793
  this.#nativeBrowserSessions.clear();
26468
26794
  await Promise.all([...sessions, ...nativeBrowserSessions].map(async (session) => await session.close()));
26469
26795
  };
26796
+ hasPreparedSession = (sessionId, agentId, cwd, additionalDirectories) => this.#sessions.get(sessionId)?.belongsTo(agentId, cwd, additionalDirectories) === true;
26470
26797
  ensureSession = async (options) => {
26471
26798
  const existing = this.#sessions.get(options.sessionId);
26472
26799
  if (existing !== void 0) {
@@ -26575,6 +26902,68 @@ var AcpSessionManager = class {
26575
26902
  this.#nativeOperations.delete(key);
26576
26903
  }
26577
26904
  };
26905
+ forkSessionAtTurn = async (options, sourceSessionId, forkPoint) => {
26906
+ if (sourceSessionId === options.sessionId) {
26907
+ throw new AcpExecutionError(
26908
+ "The fork destination must be a new conversation",
26909
+ {},
26910
+ "session_identity_mismatch"
26911
+ );
26912
+ }
26913
+ const source = this.#store.read(sourceSessionId);
26914
+ if (!(source instanceof FoundAcpStoredSession) || !source.session.prompted) {
26915
+ throw new AcpExecutionError(
26916
+ "The source conversation has no resumable native Agent session",
26917
+ {},
26918
+ "native_session_not_found"
26919
+ );
26920
+ }
26921
+ if (source.session.agentId !== options.agent.id || source.session.cwd !== options.cwd || !arraysEqual(source.session.additionalDirectories, options.additionalDirectories)) {
26922
+ throw new AcpExecutionError(
26923
+ "The source conversation execution target does not match the fork request",
26924
+ {},
26925
+ "native_session_target_changed"
26926
+ );
26927
+ }
26928
+ const key = `${options.agent.id}:${source.session.nativeSessionId}`;
26929
+ const linked = this.#store.findNative(options.agent.id, source.session.nativeSessionId);
26930
+ if (this.#nativeOperations.has(key) || linked.some((entry) => this.#activeSessionIds.has(entry.sessionId) || this.#preparing.has(entry.sessionId))) {
26931
+ throw new AcpExecutionError(
26932
+ "Wait for the source conversation's running operation to finish",
26933
+ {},
26934
+ "session_busy"
26935
+ );
26936
+ }
26937
+ if (this.#sessions.has(options.sessionId) || this.#preparing.has(options.sessionId) || this.#store.read(options.sessionId) instanceof FoundAcpStoredSession || this.#store.read(options.sessionId) instanceof DeletedAcpStoredSession) {
26938
+ throw new AcpExecutionError("The destination conversation already exists", {}, "session_identity_mismatch");
26939
+ }
26940
+ this.#nativeOperations.add(key);
26941
+ try {
26942
+ const runtime = await this.#startSessionForkAtTurnPreparation(
26943
+ options,
26944
+ source.session.nativeSessionId,
26945
+ forkPoint
26946
+ );
26947
+ const openedKey = `${options.agent.id}:${runtime.nativeSessionId()}`;
26948
+ this.#nativeOperations.add(openedKey);
26949
+ try {
26950
+ return {
26951
+ history: await runtime.loadHistory(options.signal),
26952
+ prepared: runtime.prepared(),
26953
+ sessionId: options.sessionId
26954
+ };
26955
+ } catch (error61) {
26956
+ this.#sessions.delete(options.sessionId);
26957
+ this.#store.remove(options.sessionId);
26958
+ await runtime.closeProcess();
26959
+ throw error61;
26960
+ } finally {
26961
+ this.#nativeOperations.delete(openedKey);
26962
+ }
26963
+ } finally {
26964
+ this.#nativeOperations.delete(key);
26965
+ }
26966
+ };
26578
26967
  deleteNativeSession = async (options, nativeSessionId) => {
26579
26968
  const runtime = await this.#ensureNativeSessionBrowser(options);
26580
26969
  const key = `${options.agent.id}:${nativeSessionId}`;
@@ -26620,14 +27009,19 @@ var AgentCatalogPublishError = class extends Error {
26620
27009
  this.name = "AgentCatalogPublishError";
26621
27010
  }
26622
27011
  };
26623
- function catalogEndpoint(relayUrl) {
26624
- const endpoint = new URL(relayUrl);
27012
+ function catalogEndpoint(config2) {
27013
+ const endpoint = new URL(config2.relayUrl);
26625
27014
  endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
26626
- if (!endpoint.pathname.endsWith("/daemon")) {
26627
- throw new AgentCatalogPublishError("The Relay URL is invalid");
27015
+ if (endpoint.pathname.endsWith("/daemon")) {
27016
+ endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/catalog`;
27017
+ return endpoint;
26628
27018
  }
26629
- endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/catalog`;
26630
- return endpoint;
27019
+ const runtimePath = "/v1/runtimes/connect";
27020
+ if (endpoint.pathname.endsWith(runtimePath)) {
27021
+ endpoint.pathname = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}/catalog`;
27022
+ return endpoint;
27023
+ }
27024
+ throw new AgentCatalogPublishError("The Relay URL is invalid");
26631
27025
  }
26632
27026
  async function publishAgentCatalog(config2, signal) {
26633
27027
  signal.throwIfAborted();
@@ -26636,7 +27030,7 @@ async function publishAgentCatalog(config2, signal) {
26636
27030
  let response;
26637
27031
  const catalog = config2.catalog.read();
26638
27032
  try {
26639
- response = await fetch3(catalogEndpoint(config2.relayUrl), {
27033
+ response = await fetch3(catalogEndpoint(config2), {
26640
27034
  body: JSON.stringify({
26641
27035
  agents: catalog.agents.map((agent) => ({
26642
27036
  agentPath: agent.agentIdentity.requestedPath,
@@ -27317,7 +27711,8 @@ var LocalBotGrant = class {
27317
27711
  revocationEpoch;
27318
27712
  workspaceIdentityDigest;
27319
27713
  workspacePath;
27320
- constructor(localGrantId, botId, behaviorRevisionId, behaviorDigest, bindingId, bindingRevision, agent, agentIdentityDigest, workspacePath, workspaceIdentityDigest, allowedDeliveryTargetIds, expiresAt, revocationEpoch) {
27714
+ workspaceRef;
27715
+ constructor(localGrantId, botId, behaviorRevisionId, behaviorDigest, bindingId, bindingRevision, agent, agentIdentityDigest, workspaceRef, workspacePath, workspaceIdentityDigest, allowedDeliveryTargetIds, expiresAt, revocationEpoch) {
27321
27716
  this.localGrantId = localGrantId;
27322
27717
  this.botId = botId;
27323
27718
  this.behaviorRevisionId = behaviorRevisionId;
@@ -27326,6 +27721,7 @@ var LocalBotGrant = class {
27326
27721
  this.bindingRevision = bindingRevision;
27327
27722
  this.agent = agent;
27328
27723
  this.agentIdentityDigest = agentIdentityDigest;
27724
+ this.workspaceRef = workspaceRef;
27329
27725
  this.workspacePath = workspacePath;
27330
27726
  this.workspaceIdentityDigest = workspaceIdentityDigest;
27331
27727
  this.allowedDeliveryTargetIds = Object.freeze([...allowedDeliveryTargetIds]);
@@ -27510,7 +27906,7 @@ var LocalBotGrantStore = class {
27510
27906
  WHERE local_grant_id = ? AND bot_id = ?
27511
27907
  AND behavior_revision_id = ? AND behavior_digest = ?
27512
27908
  AND binding_id = ? AND binding_revision = ?
27513
- AND agent_ref = ? AND workspace_ref = ?
27909
+ AND agent_ref = ?
27514
27910
  AND revoked_at IS NULL AND expires_at > ?`
27515
27911
  ).get(
27516
27912
  command.localGrantId,
@@ -27520,7 +27916,6 @@ var LocalBotGrantStore = class {
27520
27916
  command.bindingId,
27521
27917
  command.bindingRevision,
27522
27918
  command.agentId,
27523
- command.directoryId,
27524
27919
  now
27525
27920
  );
27526
27921
  if (row === void 0) {
@@ -27543,7 +27938,8 @@ var LocalBotGrantStore = class {
27543
27938
  revalidateExecutable(agent.agentIdentity)
27544
27939
  ]);
27545
27940
  signal.throwIfAborted();
27546
- const workspacePath = await directories.resolve(bindingAuthorityId, command.directoryId, signal);
27941
+ const workspaceRef = rowString(row, "workspace_ref");
27942
+ const workspacePath = await directories.resolve(bindingAuthorityId, workspaceRef, signal);
27547
27943
  const currentAgentDigest = agentDigest(agent);
27548
27944
  const currentWorkspaceDigest = await workspaceDigest(workspacePath);
27549
27945
  signal.throwIfAborted();
@@ -27569,6 +27965,7 @@ var LocalBotGrantStore = class {
27569
27965
  command.bindingRevision,
27570
27966
  agent,
27571
27967
  currentAgentDigest,
27968
+ workspaceRef,
27572
27969
  workspacePath,
27573
27970
  currentWorkspaceDigest,
27574
27971
  approvedTargets,
@@ -27783,7 +28180,7 @@ var BotOutboundIpcRequest = class {
27783
28180
  this.text = text;
27784
28181
  }
27785
28182
  };
27786
- function isRecord10(value) {
28183
+ function isRecord11(value) {
27787
28184
  return typeof value === "object" && value !== null && !Array.isArray(value);
27788
28185
  }
27789
28186
  function decodeRequest(encoded) {
@@ -27793,7 +28190,7 @@ function decodeRequest(encoded) {
27793
28190
  } catch (cause) {
27794
28191
  throw new LocalBotAuthorizationError("invalid_request", "The local Bot request is invalid", { cause });
27795
28192
  }
27796
- if (!isRecord10(value)) {
28193
+ if (!isRecord11(value)) {
27797
28194
  throw new LocalBotAuthorizationError("invalid_request", "The local Bot request is invalid");
27798
28195
  }
27799
28196
  const keys = Object.keys(value);
@@ -27843,10 +28240,10 @@ async function decodeRelayResponse(response) {
27843
28240
  cause
27844
28241
  });
27845
28242
  }
27846
- if (response.status === 202 && isRecord10(value) && typeof value["outboundMessageId"] === "string") {
28243
+ if (response.status === 202 && isRecord11(value) && typeof value["outboundMessageId"] === "string") {
27847
28244
  return value["outboundMessageId"];
27848
28245
  }
27849
- if (isRecord10(value) && isRecord10(value["error"])) {
28246
+ if (isRecord11(value) && isRecord11(value["error"])) {
27850
28247
  const error61 = value["error"];
27851
28248
  if (typeof error61["code"] === "string" && typeof error61["message"] === "string") {
27852
28249
  throw new LocalBotAuthorizationError(error61["code"], error61["message"]);
@@ -27879,7 +28276,7 @@ var BotOutboundIpcServer = class {
27879
28276
  if (this.#usesFilesystemSocket) {
27880
28277
  await mkdir4(dirname6(this.#socketPath), { mode: 448, recursive: true });
27881
28278
  await unlink(this.#socketPath).catch((error61) => {
27882
- if (!isRecord10(error61) || error61["code"] !== "ENOENT") {
28279
+ if (!isRecord11(error61) || error61["code"] !== "ENOENT") {
27883
28280
  throw error61;
27884
28281
  }
27885
28282
  });
@@ -27914,7 +28311,7 @@ var BotOutboundIpcServer = class {
27914
28311
  }
27915
28312
  if (this.#usesFilesystemSocket) {
27916
28313
  await unlink(this.#socketPath).catch((error61) => {
27917
- if (!isRecord10(error61) || error61["code"] !== "ENOENT") {
28314
+ if (!isRecord11(error61) || error61["code"] !== "ENOENT") {
27918
28315
  throw error61;
27919
28316
  }
27920
28317
  });
@@ -28002,6 +28399,7 @@ var BotOutboundIpcServer = class {
28002
28399
 
28003
28400
  // src/native-sessions/service.ts
28004
28401
  import { randomUUID as randomUUID4 } from "node:crypto";
28402
+ import { basename as basename3 } from "node:path";
28005
28403
 
28006
28404
  // src/native-sessions/codex-history.ts
28007
28405
  import { spawn as spawn4 } from "node:child_process";
@@ -28479,6 +28877,74 @@ var NativeSessionService = class {
28479
28877
  history: opened.history
28480
28878
  };
28481
28879
  };
28880
+ executeTurnFork = async (command, signal) => {
28881
+ signal.throwIfAborted();
28882
+ const cwd = await this.#directories.resolve(this.#config.bindingId, command.directoryId, signal);
28883
+ const additionalDirectories = [];
28884
+ const additionalEntries = [];
28885
+ const resolvedPaths = /* @__PURE__ */ new Set([cwd]);
28886
+ for (const directoryId2 of command.additionalDirectoryIds) {
28887
+ const path2 = await this.#directories.resolve(this.#config.bindingId, directoryId2, signal);
28888
+ if (resolvedPaths.has(path2)) {
28889
+ throw new LocalAuthorizationError("Workspace directories must resolve to distinct locations");
28890
+ }
28891
+ resolvedPaths.add(path2);
28892
+ additionalDirectories.push(path2);
28893
+ additionalEntries.push({ directoryId: directoryId2, name: basename3(path2) });
28894
+ }
28895
+ const listing = await this.#directories.list(this.#config.bindingId, command.directoryId, signal);
28896
+ const opened = await this.#sessions.forkSessionAtTurn(
28897
+ {
28898
+ additionalDirectories,
28899
+ agent: this.#config.catalog.find(command.agentId),
28900
+ cwd,
28901
+ sessionId: command.targetSessionId,
28902
+ signal
28903
+ },
28904
+ command.sessionId,
28905
+ {
28906
+ messageFingerprint: command.messageFingerprint,
28907
+ messageId: command.messageId,
28908
+ messageOccurrence: command.messageOccurrence
28909
+ }
28910
+ );
28911
+ signal.throwIfAborted();
28912
+ const ready = {
28913
+ additionalDirectories: additionalEntries,
28914
+ agentId: command.agentId,
28915
+ agentName: opened.prepared.agentName,
28916
+ agentVersion: opened.prepared.agentVersion,
28917
+ capabilities: opened.prepared.capabilities,
28918
+ commands: opened.prepared.commands,
28919
+ configOptions: opened.prepared.configOptions,
28920
+ directoryId: command.directoryId,
28921
+ directoryLabel: listing.directoryLabel,
28922
+ kind: "session.ready",
28923
+ requestId: command.requestId,
28924
+ restored: true,
28925
+ sessionId: opened.sessionId
28926
+ };
28927
+ this.#audit.record({
28928
+ commandId: command.requestId,
28929
+ details: {
28930
+ agentId: command.agentId,
28931
+ messageId: command.messageId,
28932
+ sourceCommandId: command.sourceCommandId
28933
+ },
28934
+ event: "native_session_turn_forked",
28935
+ sessionId: command.sessionId
28936
+ });
28937
+ return {
28938
+ action: "fork",
28939
+ history: opened.history,
28940
+ kind: "native.session.result",
28941
+ ready,
28942
+ requestId: command.requestId,
28943
+ sessionId: command.sessionId,
28944
+ targetSessionId: command.targetSessionId,
28945
+ title: command.title
28946
+ };
28947
+ };
28482
28948
  authorizeEntry = async (command, options, info) => {
28483
28949
  try {
28484
28950
  const primary = await this.#directories.identify(command.bindingId, info.cwd, options.signal);
@@ -28620,10 +29086,17 @@ function parseReference(resource) {
28620
29086
  function downloadUrl(config2, command, reference) {
28621
29087
  const url2 = new URL(config2.relayUrl);
28622
29088
  url2.protocol = url2.protocol === "wss:" ? "https:" : "http:";
28623
- if (!url2.pathname.endsWith("/daemon")) {
28624
- throw new PromptArtifactError("artifact_endpoint_invalid", "The Relay artifact endpoint is invalid");
29089
+ let channelPath = "";
29090
+ if (url2.pathname.endsWith("/daemon")) {
29091
+ channelPath = url2.pathname.slice(0, -"/daemon".length);
29092
+ } else {
29093
+ const runtimePath = "/v1/runtimes/connect";
29094
+ if (!url2.pathname.endsWith(runtimePath)) {
29095
+ throw new PromptArtifactError("artifact_endpoint_invalid", "The Relay artifact endpoint is invalid");
29096
+ }
29097
+ channelPath = `${url2.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
28625
29098
  }
28626
- url2.pathname = `${url2.pathname.slice(0, -"/daemon".length)}/sessions/${encodeURIComponent(command.sessionId)}/commands/${encodeURIComponent(command.commandId)}/artifacts/${encodeURIComponent(reference.artifactId)}/content`;
29099
+ url2.pathname = `${channelPath}/sessions/${encodeURIComponent(command.sessionId)}/commands/${encodeURIComponent(command.commandId)}/artifacts/${encodeURIComponent(reference.artifactId)}/content`;
28627
29100
  url2.searchParams.delete("liveness");
28628
29101
  return url2;
28629
29102
  }
@@ -28782,8 +29255,16 @@ var RelayConnectionError = class extends Error {
28782
29255
  function connectRelay(url2, signal) {
28783
29256
  signal.throwIfAborted();
28784
29257
  return new Promise((resolve5, reject) => {
28785
- const socket = new WebSocket(url2, {
28786
- dispatcher: environmentProxyDispatcher
29258
+ const endpoint = new URL(url2);
29259
+ const headers = {};
29260
+ if (endpoint.pathname.endsWith("/v1/runtimes/connect")) {
29261
+ const runtimeCredential = endpoint.searchParams.get("token") ?? "";
29262
+ endpoint.searchParams.delete("token");
29263
+ headers["authorization"] = `Bearer ${runtimeCredential}`;
29264
+ }
29265
+ const socket = new WebSocket(endpoint, {
29266
+ dispatcher: environmentProxyDispatcher,
29267
+ headers
28787
29268
  });
28788
29269
  let finished = false;
28789
29270
  const timeout = setTimeout(onTimeout, CONNECTION_TIMEOUT_MILLISECONDS);
@@ -29043,6 +29524,11 @@ var SessionEventOutbox = class {
29043
29524
  }
29044
29525
  this.#database.prepare(`UPDATE relay_turn_receipts SET state = ? WHERE command_id = ?`).run(state, commandId);
29045
29526
  };
29527
+ isTerminalExecution = (commandId, sessionId) => {
29528
+ const row = this.#database.prepare(`SELECT state FROM relay_turn_receipts WHERE command_id = ? AND session_id = ?`).get(commandId, sessionId);
29529
+ const state = typeof row === "object" && row !== null ? Reflect.get(row, "state") : void 0;
29530
+ return state === "succeeded" || state === "failed" || state === "cancelled";
29531
+ };
29046
29532
  acknowledge = (acknowledgement) => {
29047
29533
  this.#database.prepare(
29048
29534
  `DELETE FROM relay_session_event_outbox
@@ -29399,10 +29885,17 @@ var ActiveTurnReconciliationError = class extends Error {
29399
29885
  function activeTurnReconciliationEndpoint(config2, turn) {
29400
29886
  const endpoint = new URL(config2.relayUrl);
29401
29887
  endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
29402
- if (!endpoint.pathname.endsWith("/daemon")) {
29403
- throw new ActiveTurnReconciliationError("The Relay URL is invalid");
29888
+ let channelPath = "";
29889
+ if (endpoint.pathname.endsWith("/daemon")) {
29890
+ channelPath = endpoint.pathname.slice(0, -"/daemon".length);
29891
+ } else {
29892
+ const runtimePath = "/v1/runtimes/connect";
29893
+ if (!endpoint.pathname.endsWith(runtimePath)) {
29894
+ throw new ActiveTurnReconciliationError("The Relay URL is invalid");
29895
+ }
29896
+ channelPath = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
29404
29897
  }
29405
- endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
29898
+ endpoint.pathname = `${channelPath}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
29406
29899
  endpoint.searchParams.delete("liveness");
29407
29900
  return endpoint;
29408
29901
  }
@@ -29555,7 +30048,7 @@ async function resolveSessionDirectories(directories, bindingId, directoryId2, a
29555
30048
  additionalDirectories.push(directory);
29556
30049
  additionalDirectoryEntries.push({
29557
30050
  directoryId: additionalDirectoryId,
29558
- name: basename3(directory)
30051
+ name: basename4(directory)
29559
30052
  });
29560
30053
  }
29561
30054
  return new ResolvedSessionDirectories(cwd, additionalDirectories, additionalDirectoryEntries);
@@ -29704,68 +30197,96 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
29704
30197
  attemptId
29705
30198
  );
29706
30199
  const attachments = await resolvePromptArtifacts(config2, command, signal);
29707
- const result = await sessions.runPrompt({
29708
- additionalDirectories: resolved.additionalDirectories,
29709
- agent,
29710
- commandId: command.commandId,
29711
- cwd: resolved.cwd,
29712
- images: attachments.images,
29713
- onActivity: (activity) => {
29714
- publisher.recordAttempt(
29715
- command.sessionId,
29716
- encodeAgentActivityEvent({
29717
- activityId: activity.activityId,
29718
- activityType: activity.activityType,
29719
- commandId: command.commandId,
29720
- content: activity.content,
29721
- detail: activity.detail,
29722
- input: activity.input,
29723
- kind: AGENT_ACTIVITY_KIND,
29724
- locations: activity.locations,
29725
- name: activity.name,
29726
- output: activity.output,
29727
- sequence: activity.sequence,
29728
- sessionId: command.sessionId,
29729
- status: activity.status,
29730
- title: activity.title,
29731
- toolKind: activity.toolKind
29732
- }),
29733
- attemptId
29734
- );
29735
- return Promise.resolve();
29736
- },
29737
- onText: (text) => {
29738
- publisher.recordAttempt(
29739
- command.sessionId,
29740
- encodeAgentMessageDeltaEvent({
29741
- commandId: command.commandId,
29742
- kind: AGENT_MESSAGE_DELTA_KIND,
29743
- sessionId: command.sessionId,
29744
- text
29745
- }),
29746
- attemptId
30200
+ let stopReason;
30201
+ if (command.disposition === SessionPromptDisposition.Steer) {
30202
+ await sessions.steerPrompt({
30203
+ additionalDirectories: resolved.additionalDirectories,
30204
+ agent,
30205
+ commandId: command.commandId,
30206
+ cwd: resolved.cwd,
30207
+ images: attachments.images,
30208
+ prompt: command.prompt,
30209
+ resources: attachments.resources,
30210
+ sessionId: command.sessionId,
30211
+ signal,
30212
+ targetCommandId: command.targetCommandId
30213
+ });
30214
+ stopReason = "steered";
30215
+ } else {
30216
+ if (command.disposition === SessionPromptDisposition.Queue && !outbox.isTerminalExecution(command.targetCommandId, command.sessionId)) {
30217
+ throw new AcpExecutionError(
30218
+ "The queued prompt target is unavailable",
30219
+ {},
30220
+ "prompt_queue_target_unavailable"
29747
30221
  );
29748
- return Promise.resolve();
29749
- },
29750
- prompt: command.prompt,
29751
- resources: attachments.resources,
29752
- sessionId: command.sessionId,
29753
- signal
29754
- });
30222
+ }
30223
+ const result = await sessions.runPrompt({
30224
+ additionalDirectories: resolved.additionalDirectories,
30225
+ agent,
30226
+ commandId: command.commandId,
30227
+ cwd: resolved.cwd,
30228
+ disposition: command.disposition,
30229
+ images: attachments.images,
30230
+ onActivity: (activity) => {
30231
+ publisher.recordAttempt(
30232
+ command.sessionId,
30233
+ encodeAgentActivityEvent({
30234
+ activityId: activity.activityId,
30235
+ activityType: activity.activityType,
30236
+ commandId: command.commandId,
30237
+ content: activity.content,
30238
+ detail: activity.detail,
30239
+ input: activity.input,
30240
+ kind: AGENT_ACTIVITY_KIND,
30241
+ locations: activity.locations,
30242
+ name: activity.name,
30243
+ output: activity.output,
30244
+ sequence: activity.sequence,
30245
+ sessionId: command.sessionId,
30246
+ status: activity.status,
30247
+ title: activity.title,
30248
+ toolKind: activity.toolKind
30249
+ }),
30250
+ attemptId
30251
+ );
30252
+ return Promise.resolve();
30253
+ },
30254
+ onText: (text, messageId) => {
30255
+ publisher.recordAttempt(
30256
+ command.sessionId,
30257
+ encodeAgentMessageDeltaEvent({
30258
+ commandId: command.commandId,
30259
+ kind: AGENT_MESSAGE_DELTA_KIND,
30260
+ messageId,
30261
+ sessionId: command.sessionId,
30262
+ text
30263
+ }),
30264
+ attemptId
30265
+ );
30266
+ return Promise.resolve();
30267
+ },
30268
+ prompt: command.prompt,
30269
+ resources: attachments.resources,
30270
+ sessionId: command.sessionId,
30271
+ signal,
30272
+ targetCommandId: command.targetCommandId
30273
+ });
30274
+ stopReason = result.stopReason;
30275
+ }
29755
30276
  publisher.recordAttempt(
29756
30277
  command.sessionId,
29757
30278
  encodeSessionCompletedEvent({
29758
30279
  commandId: command.commandId,
29759
30280
  kind: SESSION_COMPLETED_KIND,
29760
30281
  sessionId: command.sessionId,
29761
- stopReason: result.stopReason
30282
+ stopReason
29762
30283
  }),
29763
30284
  attemptId
29764
30285
  );
29765
- outbox.transitionExecution(command.commandId, result.stopReason === "cancelled" ? "cancelled" : "succeeded");
30286
+ outbox.transitionExecution(command.commandId, stopReason === "cancelled" ? "cancelled" : "succeeded");
29766
30287
  audit.record({
29767
30288
  commandId: command.commandId,
29768
- event: result.stopReason === "cancelled" ? "session_cancelled" : "session_completed",
30289
+ event: stopReason === "cancelled" ? "session_cancelled" : "session_completed",
29769
30290
  sessionId: command.sessionId
29770
30291
  });
29771
30292
  } catch (error61) {
@@ -29809,12 +30330,6 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
29809
30330
  }
29810
30331
  async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, signal) {
29811
30332
  try {
29812
- if (command.additionalDirectoryIds.length > 0) {
29813
- throw new LocalBotAuthorizationError(
29814
- "binding_not_approved",
29815
- "The Bot execution requested unapproved additional directories"
29816
- );
29817
- }
29818
30333
  const deadline = Date.parse(command.executionDeadlineAt);
29819
30334
  const remaining = deadline - Date.now();
29820
30335
  if (!Number.isSafeInteger(deadline) || remaining <= 0) {
@@ -29828,6 +30343,32 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29828
30343
  Date.now(),
29829
30344
  signal
29830
30345
  );
30346
+ const requestedExecutionTargetOverridden = command.directoryId !== grant.workspaceRef || command.additionalDirectoryIds.length > 0;
30347
+ if (requestedExecutionTargetOverridden && !(command.replyRoute instanceof OwnerBotReplyRoute)) {
30348
+ throw new LocalBotAuthorizationError(
30349
+ "binding_not_approved",
30350
+ "Only a paired owner conversation can override the approved Bot workspace"
30351
+ );
30352
+ }
30353
+ const resolved = await resolveSessionDirectories(
30354
+ directories,
30355
+ config2.bindingId,
30356
+ command.directoryId,
30357
+ command.additionalDirectoryIds,
30358
+ signal
30359
+ );
30360
+ const executionTargetOverridden = resolved.cwd !== grant.workspacePath || resolved.additionalDirectories.length > 0;
30361
+ if (executionTargetOverridden && !sessions.hasPreparedSession(
30362
+ command.sessionId,
30363
+ grant.agent.id,
30364
+ resolved.cwd,
30365
+ resolved.additionalDirectories
30366
+ )) {
30367
+ throw new LocalBotAuthorizationError(
30368
+ "local_approval_required",
30369
+ "Open this Bot conversation in the paired local Web app before using the selected workspace"
30370
+ );
30371
+ }
29831
30372
  const attachments = await resolvePromptArtifacts(config2, command, signal);
29832
30373
  const applied = await applicator.apply(command, grant, attachments.resources, signal);
29833
30374
  const lease = contexts.activate(command);
@@ -29835,6 +30376,13 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29835
30376
  const deadlineSignal = AbortSignal.timeout(remaining);
29836
30377
  const executionSignal = AbortSignal.any([signal, deadlineSignal]);
29837
30378
  const agent = botExecutionAgent(grant.agent, lease.context.contextHandle, ipc.socketPath);
30379
+ await sessions.prepareExecutionSession({
30380
+ additionalDirectories: resolved.additionalDirectories,
30381
+ agent,
30382
+ cwd: resolved.cwd,
30383
+ sessionId: command.sessionId,
30384
+ signal: executionSignal
30385
+ });
29838
30386
  audit.record({
29839
30387
  commandId: command.commandId,
29840
30388
  details: {
@@ -29859,10 +30407,10 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29859
30407
  attemptId
29860
30408
  );
29861
30409
  const result = await sessions.runPrompt({
29862
- additionalDirectories: [],
30410
+ additionalDirectories: resolved.additionalDirectories,
29863
30411
  agent,
29864
30412
  commandId: command.commandId,
29865
- cwd: grant.workspacePath,
30413
+ cwd: resolved.cwd,
29866
30414
  images: attachments.images,
29867
30415
  onActivity: (activity) => {
29868
30416
  publisher.recordAttempt(
@@ -29888,12 +30436,13 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29888
30436
  );
29889
30437
  return Promise.resolve();
29890
30438
  },
29891
- onText: (text) => {
30439
+ onText: (text, messageId) => {
29892
30440
  publisher.recordAttempt(
29893
30441
  command.sessionId,
29894
30442
  encodeAgentMessageDeltaEvent({
29895
30443
  commandId: command.commandId,
29896
30444
  kind: AGENT_MESSAGE_DELTA_KIND,
30445
+ messageId,
29897
30446
  sessionId: command.sessionId,
29898
30447
  text
29899
30448
  }),
@@ -30248,6 +30797,86 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30248
30797
  });
30249
30798
  return;
30250
30799
  }
30800
+ if (kind === NATIVE_SESSION_TURN_FORK_KIND) {
30801
+ const command2 = decodeNativeSessionTurnForkCommand(encoded);
30802
+ const previous = [...nativeRequests.values()].filter(
30803
+ (request) => request.agentId === command2.agentId
30804
+ );
30805
+ if (nativeRequests.has(command2.requestId) || nativeRequests.size >= 4 || previous.some((request) => !request.controller.signal.aborted)) {
30806
+ reportOperationFailure(
30807
+ socket,
30808
+ command2.requestId,
30809
+ command2.sessionId,
30810
+ new AcpExecutionError(
30811
+ "Another native session operation is running",
30812
+ {},
30813
+ "native_session_busy"
30814
+ )
30815
+ );
30816
+ return;
30817
+ }
30818
+ const controller = new AbortController();
30819
+ const operationSignal = AbortSignal.any([connectionController.signal, controller.signal]);
30820
+ const timeout = timer.schedule(
30821
+ () => controller.abort(
30822
+ new AcpExecutionError(
30823
+ "Native session operation timed out",
30824
+ {},
30825
+ "native_session_timeout"
30826
+ )
30827
+ ),
30828
+ 9e4
30829
+ );
30830
+ const completion = (async () => {
30831
+ try {
30832
+ operationSignal.throwIfAborted();
30833
+ if (previous.length > 0) {
30834
+ await new Promise((resolve6, reject2) => {
30835
+ const cancel = () => reject2(operationSignal.reason);
30836
+ operationSignal.addEventListener("abort", cancel, { once: true });
30837
+ void Promise.all(previous.map((request) => request.completion)).then(
30838
+ () => {
30839
+ operationSignal.removeEventListener("abort", cancel);
30840
+ resolve6();
30841
+ },
30842
+ (error61) => {
30843
+ operationSignal.removeEventListener("abort", cancel);
30844
+ reject2(error61);
30845
+ }
30846
+ );
30847
+ });
30848
+ }
30849
+ operationSignal.throwIfAborted();
30850
+ const result = await nativeSessions.executeTurnFork(command2, operationSignal);
30851
+ if (socket.readyState === WebSocket2.OPEN) {
30852
+ send(socket, encodeNativeSessionResult(result));
30853
+ }
30854
+ } catch (error61) {
30855
+ const failure = operationSignal.aborted ? controller.signal.reason ?? new AcpExecutionError(
30856
+ "Native session operation cancelled",
30857
+ {},
30858
+ "native_session_cancelled"
30859
+ ) : error61;
30860
+ audit.record({
30861
+ commandId: command2.requestId,
30862
+ details: { code: operationErrorCode(failure) },
30863
+ event: "native_session_turn_fork_failed",
30864
+ sessionId: command2.sessionId
30865
+ });
30866
+ reportOperationFailure(socket, command2.requestId, command2.sessionId, failure);
30867
+ } finally {
30868
+ timeout.cancel();
30869
+ nativeRequests.delete(command2.requestId);
30870
+ }
30871
+ })();
30872
+ nativeRequests.set(command2.requestId, {
30873
+ agentId: command2.agentId,
30874
+ completion,
30875
+ controller,
30876
+ sessionId: command2.sessionId
30877
+ });
30878
+ return;
30879
+ }
30251
30880
  if (kind === RUNTIME_LIVENESS_READY_KIND) {
30252
30881
  decodeRuntimeLivenessReadyEvent(encoded);
30253
30882
  connectionLiveness.start();
@@ -30401,7 +31030,9 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30401
31030
  }
30402
31031
  const turn = new ActiveDaemonTurn(command, receipt.attemptId);
30403
31032
  activeTurns.set(command.commandId, turn);
30404
- activeAttemptIds.set(command.sessionId, receipt.attemptId);
31033
+ if (command.disposition !== SessionPromptDisposition.Steer) {
31034
+ activeAttemptIds.set(command.sessionId, receipt.attemptId);
31035
+ }
30405
31036
  void executePrompt(
30406
31037
  outbox,
30407
31038
  publisher,
@@ -30589,6 +31220,69 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
30589
31220
  }
30590
31221
  }
30591
31222
 
31223
+ // src/runtime.ts
31224
+ var APP_CREDENTIAL_PATTERN = /^mapp_[a-zA-Z0-9_-]{22}_[a-zA-Z0-9_-]{43}$/;
31225
+ var CONVERSATION_CREDENTIAL_PATTERN = /^mconv_[a-zA-Z0-9_-]{22}_[a-zA-Z0-9_-]{43}$/;
31226
+ var RUNTIME_CREDENTIAL_PATTERN = /^[a-zA-Z0-9_-]{32,256}$/;
31227
+ var MobiusRuntimeConfigurationError = class extends Error {
31228
+ code = "runtime_configuration_invalid";
31229
+ constructor(message, options = {}) {
31230
+ super(message, options);
31231
+ this.name = "MobiusRuntimeConfigurationError";
31232
+ }
31233
+ };
31234
+ var MobiusRuntimeState = class {
31235
+ };
31236
+ var IdleMobiusRuntimeState = class extends MobiusRuntimeState {
31237
+ };
31238
+ var RunningMobiusRuntimeState = class extends MobiusRuntimeState {
31239
+ };
31240
+ var FinishedMobiusRuntimeState = class extends MobiusRuntimeState {
31241
+ };
31242
+ function validateRuntimeRelay(relayUrl) {
31243
+ if (relayUrl.protocol !== "ws:" && relayUrl.protocol !== "wss:") {
31244
+ throw new MobiusRuntimeConfigurationError("The Runtime relay URL must use WebSocket transport");
31245
+ }
31246
+ const credential = relayUrl.searchParams.get("token") ?? "";
31247
+ if (credential.length === 0) {
31248
+ throw new MobiusRuntimeConfigurationError("The Runtime relay credential is required");
31249
+ }
31250
+ if (APP_CREDENTIAL_PATTERN.test(credential)) {
31251
+ throw new MobiusRuntimeConfigurationError("An AppCredential cannot be held by a local Runtime");
31252
+ }
31253
+ if (CONVERSATION_CREDENTIAL_PATTERN.test(credential)) {
31254
+ throw new MobiusRuntimeConfigurationError("A ConversationAccessToken cannot be held by a local Runtime");
31255
+ }
31256
+ if (!RUNTIME_CREDENTIAL_PATTERN.test(credential)) {
31257
+ throw new MobiusRuntimeConfigurationError("The Runtime relay credential is invalid");
31258
+ }
31259
+ }
31260
+ function validateRuntimeConfiguration(config2) {
31261
+ validateRuntimeRelay(config2.relayUrl);
31262
+ }
31263
+ var MobiusRuntimeEngine = class {
31264
+ #config;
31265
+ #state = new IdleMobiusRuntimeState();
31266
+ constructor(config2) {
31267
+ validateRuntimeConfiguration(config2);
31268
+ this.#config = config2;
31269
+ }
31270
+ run = async (signal) => {
31271
+ if (!(this.#state instanceof IdleMobiusRuntimeState)) {
31272
+ throw new MobiusRuntimeConfigurationError("A Mobius Runtime engine can only be started once");
31273
+ }
31274
+ signal.throwIfAborted();
31275
+ this.#state = new RunningMobiusRuntimeState();
31276
+ const audit = new AuditStore(this.#config.auditDatabasePath);
31277
+ try {
31278
+ await runDaemon(this.#config, audit, signal);
31279
+ } finally {
31280
+ audit.close();
31281
+ this.#state = new FinishedMobiusRuntimeState();
31282
+ }
31283
+ };
31284
+ };
31285
+
30592
31286
  // src/cli.ts
30593
31287
  var shutdown = new AbortController();
30594
31288
  process.once("SIGINT", () => shutdown.abort());
@@ -30596,7 +31290,7 @@ process.once("SIGTERM", () => shutdown.abort());
30596
31290
  var USAGE = `Mobius CLI
30597
31291
 
30598
31292
  Usage:
30599
- mobius pair --url <relay-url> --code <pairing-code> [--workspace <directory>]
31293
+ mobius pair --url <relay-url> --pairing <runtime-pairing-id> --code <pairing-code> [--workspace <directory>]
30600
31294
  mobius start --channel <channel-id>
30601
31295
  mobius agents [--json]
30602
31296
  mobius bot message send --target <current|alias> --text <message>
@@ -30669,12 +31363,7 @@ async function main() {
30669
31363
  process.stdout.write(`Default workspace: ${config2.defaultWorkspace}
30670
31364
 
30671
31365
  `);
30672
- const audit = new AuditStore(config2.auditDatabasePath);
30673
- try {
30674
- await runDaemon(config2, audit, shutdown.signal);
30675
- } finally {
30676
- audit.close();
30677
- }
31366
+ await new MobiusRuntimeEngine(config2).run(shutdown.signal);
30678
31367
  }
30679
31368
  try {
30680
31369
  await main();