@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/index.js CHANGED
@@ -19006,7 +19006,9 @@ var sessionCapabilitiesSchema = external_exports.strictObject({
19006
19006
  imagePrompt: external_exports.boolean(),
19007
19007
  listSessions: external_exports.boolean(),
19008
19008
  loadSession: external_exports.boolean(),
19009
+ promptQueueing: external_exports.boolean().default(false),
19009
19010
  resumeSession: external_exports.boolean(),
19011
+ steering: external_exports.boolean().default(false),
19010
19012
  unstableForkSession: external_exports.boolean().default(false)
19011
19013
  }).readonly();
19012
19014
  var sessionCommandSchema = external_exports.strictObject({
@@ -19348,6 +19350,11 @@ var AGENT_ACTIVITY_LOCATION_LIMIT = 12;
19348
19350
  var AGENT_ACTIVITY_LOCATION_LENGTH_LIMIT = 1024;
19349
19351
  var SESSION_COMPLETED_KIND = "session.completed";
19350
19352
  var SESSION_FAILED_KIND = "session.failed";
19353
+ var SessionPromptDisposition = Object.freeze({
19354
+ Queue: "queue",
19355
+ Reject: "reject",
19356
+ Steer: "steer"
19357
+ });
19351
19358
 
19352
19359
  // ../protocol/src/codecs/messages.ts
19353
19360
  var IDENTIFIER_PATTERN2 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
@@ -19422,6 +19429,19 @@ function readAllowedString(value, key, allowedValues) {
19422
19429
  }
19423
19430
  return field;
19424
19431
  }
19432
+ function readPromptDisposition(value) {
19433
+ const field = value["disposition"];
19434
+ if (field === void 0 || field === SessionPromptDisposition.Reject) {
19435
+ return SessionPromptDisposition.Reject;
19436
+ }
19437
+ if (field === SessionPromptDisposition.Queue) {
19438
+ return SessionPromptDisposition.Queue;
19439
+ }
19440
+ if (field === SessionPromptDisposition.Steer) {
19441
+ return SessionPromptDisposition.Steer;
19442
+ }
19443
+ throw new ProtocolMessageError("Invalid value field: disposition");
19444
+ }
19425
19445
  function readSequence(value, key) {
19426
19446
  const field = value[key];
19427
19447
  if (typeof field !== "number" || !Number.isSafeInteger(field) || field < 0) {
@@ -19546,11 +19566,13 @@ function decodeSessionPromptCommand(encoded) {
19546
19566
  "bindingId",
19547
19567
  "commandId",
19548
19568
  "directoryId",
19569
+ "disposition",
19549
19570
  "images",
19550
19571
  "kind",
19551
19572
  "prompt",
19552
19573
  "resources",
19553
- "sessionId"
19574
+ "sessionId",
19575
+ "targetCommandId"
19554
19576
  ]);
19555
19577
  assertKind(value, SESSION_PROMPT_KIND);
19556
19578
  const prompt = readText(value, "prompt", MAX_PROMPT_LENGTH);
@@ -19563,17 +19585,25 @@ function decodeSessionPromptCommand(encoded) {
19563
19585
  if (prompt.trim().length === 0 && images.length === 0 && resources.length === 0) {
19564
19586
  throw new ProtocolMessageError("Prompt must contain content");
19565
19587
  }
19588
+ const disposition = readPromptDisposition(value);
19589
+ const rawTargetCommandId = value["targetCommandId"];
19590
+ const targetCommandId = rawTargetCommandId === void 0 ? "" : readText(value, "targetCommandId", 128);
19591
+ if (disposition === SessionPromptDisposition.Reject && targetCommandId.length > 0 || disposition !== SessionPromptDisposition.Reject && !IDENTIFIER_PATTERN2.test(targetCommandId)) {
19592
+ throw new ProtocolMessageError("Prompt disposition has an invalid target command");
19593
+ }
19566
19594
  return {
19567
19595
  additionalDirectoryIds: readDirectoryIdentifiers(value, "additionalDirectoryIds", 16),
19568
19596
  agentId: readIdentifier(value, "agentId"),
19569
19597
  bindingId: readIdentifier(value, "bindingId"),
19570
19598
  commandId: readIdentifier(value, "commandId"),
19571
19599
  directoryId: readDirectoryIdentifier(value, "directoryId"),
19600
+ disposition,
19572
19601
  images,
19573
19602
  kind: SESSION_PROMPT_KIND,
19574
19603
  prompt,
19575
19604
  resources,
19576
- sessionId: readIdentifier(value, "sessionId")
19605
+ sessionId: readIdentifier(value, "sessionId"),
19606
+ targetCommandId
19577
19607
  };
19578
19608
  }
19579
19609
  function decodeSessionCancelCommand(encoded) {
@@ -19623,11 +19653,12 @@ function decodeSessionStartedEvent(encoded) {
19623
19653
  }
19624
19654
  function decodeAgentMessageDeltaEvent(encoded) {
19625
19655
  const value = parseJson2(encoded);
19626
- assertExactKeys(value, ["commandId", "kind", "sessionId", "text"]);
19656
+ assertExactKeys(value, ["commandId", "kind", "messageId", "sessionId", "text"]);
19627
19657
  assertKind(value, AGENT_MESSAGE_DELTA_KIND);
19628
19658
  return {
19629
19659
  commandId: readIdentifier(value, "commandId"),
19630
19660
  kind: AGENT_MESSAGE_DELTA_KIND,
19661
+ messageId: value["messageId"] === void 0 ? "" : readText(value, "messageId", 255),
19631
19662
  sessionId: readIdentifier(value, "sessionId"),
19632
19663
  text: readString(value, "text", MAX_EVENT_TEXT_LENGTH)
19633
19664
  };
@@ -20273,6 +20304,8 @@ var sessionHistorySchema = external_exports.discriminatedUnion("status", [
20273
20304
  var NATIVE_SESSION_REQUEST_KIND = "native.session.request";
20274
20305
  var NATIVE_SESSION_RESULT_KIND = "native.session.result";
20275
20306
  var NATIVE_SESSION_CANCEL_KIND = "native.session.cancel";
20307
+ var NATIVE_SESSION_TURN_FORK_REQUEST_KIND = "native.session.turn-fork.request";
20308
+ var NATIVE_SESSION_TURN_FORK_KIND = "native.session.turn-fork";
20276
20309
  var identifier = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/);
20277
20310
  var directoryId = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,2047}$/);
20278
20311
  var envelope = {
@@ -20287,6 +20320,28 @@ var identity = {
20287
20320
  bindingId: identifier,
20288
20321
  directoryId
20289
20322
  };
20323
+ var turnForkIdentity = {
20324
+ additionalDirectoryIds: external_exports.array(directoryId).max(16).readonly(),
20325
+ agentId: identifier,
20326
+ bindingId: identifier,
20327
+ directoryId,
20328
+ requestId: identifier,
20329
+ sessionId: identifier,
20330
+ sourceCommandId: identifier,
20331
+ targetSessionId: identifier
20332
+ };
20333
+ var nativeSessionTurnForkRequestSchema = external_exports.strictObject({
20334
+ ...turnForkIdentity,
20335
+ kind: external_exports.literal(NATIVE_SESSION_TURN_FORK_REQUEST_KIND)
20336
+ }).readonly();
20337
+ var nativeSessionTurnForkCommandSchema = external_exports.strictObject({
20338
+ ...turnForkIdentity,
20339
+ kind: external_exports.literal(NATIVE_SESSION_TURN_FORK_KIND),
20340
+ messageFingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
20341
+ messageId: external_exports.string().min(1).max(255),
20342
+ messageOccurrence: external_exports.number().int().positive(),
20343
+ title: external_exports.string().max(500)
20344
+ }).readonly();
20290
20345
  var nativeSessionCommandSchema = external_exports.discriminatedUnion("action", [
20291
20346
  external_exports.strictObject({
20292
20347
  ...identity,
@@ -20361,6 +20416,7 @@ function decode4(schema, encoded) {
20361
20416
  var decodeNativeSessionCommand = (encoded) => decode4(nativeSessionCommandSchema, encoded);
20362
20417
  var encodeNativeSessionResult = (value) => JSON.stringify(nativeSessionResultSchema.parse(value));
20363
20418
  var decodeNativeSessionCancel = (encoded) => decode4(nativeSessionCancelSchema, encoded);
20419
+ var decodeNativeSessionTurnForkCommand = (encoded) => decode4(nativeSessionTurnForkCommandSchema, encoded);
20364
20420
 
20365
20421
  // ../protocol/src/session-events.ts
20366
20422
  var SESSION_EVENT_KIND = "session.event";
@@ -20449,6 +20505,16 @@ var decodeSessionEventRejection = (encoded) => decode5(encoded, eventRejectionSc
20449
20505
  import { realpathSync, statSync } from "node:fs";
20450
20506
 
20451
20507
  // src/acp/session-capabilities.ts
20508
+ function isRecord3(value) {
20509
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20510
+ }
20511
+ function hasTrueChildField(value, childKey, fieldKey) {
20512
+ if (!isRecord3(value)) {
20513
+ return false;
20514
+ }
20515
+ const child = Reflect.get(value, childKey);
20516
+ return isRecord3(child) && Reflect.get(child, fieldKey) === true;
20517
+ }
20452
20518
  function unsupportedSessionCapabilities() {
20453
20519
  return Object.freeze({
20454
20520
  additionalDirectories: false,
@@ -20459,11 +20525,14 @@ function unsupportedSessionCapabilities() {
20459
20525
  imagePrompt: false,
20460
20526
  listSessions: false,
20461
20527
  loadSession: false,
20528
+ promptQueueing: false,
20462
20529
  resumeSession: false,
20530
+ steering: false,
20463
20531
  unstableForkSession: false
20464
20532
  });
20465
20533
  }
20466
- function normalizeAcpCapabilities(capabilities) {
20534
+ function normalizeAcpCapabilities(initialized) {
20535
+ const capabilities = initialized.agentCapabilities ?? {};
20467
20536
  const session = capabilities.sessionCapabilities;
20468
20537
  const prompt = capabilities.promptCapabilities;
20469
20538
  return Object.freeze({
@@ -20475,7 +20544,9 @@ function normalizeAcpCapabilities(capabilities) {
20475
20544
  imagePrompt: prompt?.image === true,
20476
20545
  listSessions: session?.list != null,
20477
20546
  loadSession: capabilities.loadSession === true,
20547
+ promptQueueing: hasTrueChildField(capabilities._meta, "claudeCode", "promptQueueing"),
20478
20548
  resumeSession: session?.resume != null,
20549
+ steering: hasTrueChildField(initialized._meta, "steering", "supported"),
20479
20550
  unstableForkSession: session?.fork != null
20480
20551
  });
20481
20552
  }
@@ -21036,9 +21107,9 @@ function readStateDirectory(environment = process.env) {
21036
21107
  }
21037
21108
 
21038
21109
  // src/config.ts
21039
- function loadDefaultWorkspace() {
21040
- const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = process.env;
21041
- return configuredWorkspace ?? join3(readStateDirectory(), "workspace");
21110
+ function loadDefaultWorkspace(environment = process.env) {
21111
+ const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = environment;
21112
+ return configuredWorkspace ?? join3(readStateDirectory(environment), "workspace");
21042
21113
  }
21043
21114
  function createDaemonConfig(connection, agents = [], installableAgents = []) {
21044
21115
  return {
@@ -21092,7 +21163,7 @@ import {
21092
21163
 
21093
21164
  // src/acp/activity.ts
21094
21165
  var TRUNCATION_MARKER = "\n\u2026 output truncated by Mobius";
21095
- function isRecord3(value) {
21166
+ function isRecord4(value) {
21096
21167
  return typeof value === "object" && value !== null && !Array.isArray(value);
21097
21168
  }
21098
21169
  var AcpActivity = class _AcpActivity {
@@ -21359,7 +21430,7 @@ function contentPhase(content) {
21359
21430
  return "";
21360
21431
  }
21361
21432
  const codex = metadata["codex"];
21362
- if (!isRecord3(codex)) {
21433
+ if (!isRecord4(codex)) {
21363
21434
  return "";
21364
21435
  }
21365
21436
  const phase = codex["phase"];
@@ -21597,7 +21668,7 @@ var SessionConfigProtocolError = class extends Error {
21597
21668
  this.name = "SessionConfigProtocolError";
21598
21669
  }
21599
21670
  };
21600
- function isRecord4(value) {
21671
+ function isRecord5(value) {
21601
21672
  return typeof value === "object" && value !== null && !Array.isArray(value);
21602
21673
  }
21603
21674
  function readString2(value, fallback = "") {
@@ -21624,7 +21695,7 @@ function normalizeConfigValues(value) {
21624
21695
  }
21625
21696
  const normalized = [];
21626
21697
  for (const entry of value) {
21627
- if (!isRecord4(entry)) {
21698
+ if (!isRecord5(entry)) {
21628
21699
  continue;
21629
21700
  }
21630
21701
  const groupedOptions = entry["options"];
@@ -21632,7 +21703,7 @@ function normalizeConfigValues(value) {
21632
21703
  const groupId = readString2(entry["group"]);
21633
21704
  const groupName = readString2(entry["name"]);
21634
21705
  for (const groupedOption of groupedOptions) {
21635
- if (isRecord4(groupedOption)) {
21706
+ if (isRecord5(groupedOption)) {
21636
21707
  normalized.push(normalizeConfigValue(groupedOption, groupId, groupName));
21637
21708
  }
21638
21709
  }
@@ -21646,7 +21717,7 @@ function normalizeConfigOptions(value, modes) {
21646
21717
  const normalized = [];
21647
21718
  if (Array.isArray(value)) {
21648
21719
  for (const option2 of value) {
21649
- if (!isRecord4(option2)) {
21720
+ if (!isRecord5(option2)) {
21650
21721
  continue;
21651
21722
  }
21652
21723
  const id = truncate2(readString2(option2["id"]), 128);
@@ -21685,12 +21756,12 @@ function normalizeConfigOptions(value, modes) {
21685
21756
  }
21686
21757
  }
21687
21758
  }
21688
- if (isRecord4(modes)) {
21759
+ if (isRecord5(modes)) {
21689
21760
  const values = [];
21690
21761
  const availableModes = modes["availableModes"];
21691
21762
  if (Array.isArray(availableModes)) {
21692
21763
  for (const mode of availableModes) {
21693
- if (!isRecord4(mode)) {
21764
+ if (!isRecord5(mode)) {
21694
21765
  continue;
21695
21766
  }
21696
21767
  const id = truncate2(readString2(mode["id"]), 500);
@@ -21768,7 +21839,7 @@ function ensureReasoningEffortOption(current) {
21768
21839
  return [...current.slice(0, MAXIMUM_CONFIG_OPTIONS - 2), model, unavailable];
21769
21840
  }
21770
21841
  function normalizeStandardLifecycle(response) {
21771
- const lifecycle = isRecord4(response) ? response : {};
21842
+ const lifecycle = isRecord5(response) ? response : {};
21772
21843
  return ensureReasoningEffortOption(normalizeConfigOptions(lifecycle["configOptions"], lifecycle["modes"]));
21773
21844
  }
21774
21845
  function replaceStandardConfigOptions(current, value) {
@@ -21810,7 +21881,7 @@ var StandardSessionConfigProtocol = class {
21810
21881
  "ACP agent exited while changing session configuration",
21811
21882
  change.signal
21812
21883
  );
21813
- if (!isRecord4(response) || !Array.isArray(response["configOptions"])) {
21884
+ if (!isRecord5(response) || !Array.isArray(response["configOptions"])) {
21814
21885
  throw new SessionConfigProtocolError("The Agent returned an invalid session configuration response");
21815
21886
  }
21816
21887
  return replaceStandardConfigOptions(current, response["configOptions"]);
@@ -21824,18 +21895,18 @@ var standardSessionConfigProtocol = new StandardSessionConfigProtocol();
21824
21895
  var LEGACY_MODEL_CONFIG_ID = "grok_build_legacy_model";
21825
21896
  var LEGACY_REASONING_EFFORT_CONFIG_ID = "grok_build_legacy_reasoning_effort";
21826
21897
  function legacyOptions(response) {
21827
- if (!isRecord4(response)) {
21898
+ if (!isRecord5(response)) {
21828
21899
  return [];
21829
21900
  }
21830
21901
  const metadata = response["_meta"];
21831
- if (!isRecord4(metadata)) {
21902
+ if (!isRecord5(metadata)) {
21832
21903
  return [];
21833
21904
  }
21834
21905
  const sessionConfig = metadata["x.ai/sessionConfig"];
21835
- if (!isRecord4(sessionConfig) || !Array.isArray(sessionConfig["options"])) {
21906
+ if (!isRecord5(sessionConfig) || !Array.isArray(sessionConfig["options"])) {
21836
21907
  return [];
21837
21908
  }
21838
- return sessionConfig["options"].slice(0, 500).filter(isRecord4);
21909
+ return sessionConfig["options"].slice(0, 500).filter(isRecord5);
21839
21910
  }
21840
21911
  function legacyValue(option2) {
21841
21912
  const value = readString2(option2["id"]);
@@ -21848,7 +21919,7 @@ function legacyValue(option2) {
21848
21919
  };
21849
21920
  }
21850
21921
  function modelsRecord(response) {
21851
- if (!isRecord4(response) || !isRecord4(response["models"])) {
21922
+ if (!isRecord5(response) || !isRecord5(response["models"])) {
21852
21923
  return {};
21853
21924
  }
21854
21925
  return response["models"];
@@ -21867,7 +21938,7 @@ function legacyModelOptions(response) {
21867
21938
  }
21868
21939
  if (values.length === 0 && Array.isArray(models["availableModels"])) {
21869
21940
  for (const model of models["availableModels"]) {
21870
- if (!isRecord4(model)) {
21941
+ if (!isRecord5(model)) {
21871
21942
  continue;
21872
21943
  }
21873
21944
  const value = readString2(model["modelId"]).slice(0, 500);
@@ -21947,7 +22018,7 @@ function legacyReasoningEffortOptions(response) {
21947
22018
  return [result];
21948
22019
  }
21949
22020
  function normalizeGrokLifecycle(response) {
21950
- const lifecycle = isRecord4(response) ? response : {};
22021
+ const lifecycle = isRecord5(response) ? response : {};
21951
22022
  const standard = normalizeConfigOptions(lifecycle["configOptions"], lifecycle["modes"]);
21952
22023
  const normalized = [...standard];
21953
22024
  if (!normalized.some((option2) => option2.category === "model")) {
@@ -21966,11 +22037,11 @@ function updateCurrentValue2(current, category, value) {
21966
22037
  return current.map((option2) => option2.category === category ? { ...option2, currentValue: value } : option2);
21967
22038
  }
21968
22039
  function validateGrokResponse(response, modelId) {
21969
- if (!isRecord4(response) || !isRecord4(response["_meta"])) {
22040
+ if (!isRecord5(response) || !isRecord5(response["_meta"])) {
21970
22041
  throw new SessionConfigProtocolError("Grok Build returned an invalid session configuration response");
21971
22042
  }
21972
22043
  const model = response["_meta"]["model"];
21973
- const appliedModel = isRecord4(model) ? model["Ok"] : model;
22044
+ const appliedModel = isRecord5(model) ? model["Ok"] : model;
21974
22045
  if (appliedModel !== modelId) {
21975
22046
  throw new SessionConfigProtocolError("Grok Build returned an invalid session configuration response");
21976
22047
  }
@@ -22391,7 +22462,7 @@ function createWritableAcpStream(output2) {
22391
22462
  if (outputFailure !== void 0) {
22392
22463
  return Promise.reject(outputFailure);
22393
22464
  }
22394
- return new Promise((resolve5, reject) => {
22465
+ return new Promise((resolve6, reject) => {
22395
22466
  pendingWriteFailures.add(reject);
22396
22467
  output2.write(chunk, (error61) => {
22397
22468
  pendingWriteFailures.delete(reject);
@@ -22404,7 +22475,7 @@ function createWritableAcpStream(output2) {
22404
22475
  reject(outputFailure);
22405
22476
  return;
22406
22477
  }
22407
- resolve5();
22478
+ resolve6();
22408
22479
  });
22409
22480
  });
22410
22481
  }
@@ -22413,7 +22484,7 @@ function createWritableAcpStream(output2) {
22413
22484
  function truncate3(value, maximumLength) {
22414
22485
  return value.length <= maximumLength ? value : `${value.slice(0, Math.max(0, maximumLength - 1))}\u2026`;
22415
22486
  }
22416
- function isRecord5(value) {
22487
+ function isRecord6(value) {
22417
22488
  return typeof value === "object" && value !== null && !Array.isArray(value);
22418
22489
  }
22419
22490
  function readString4(value, fallback = "") {
@@ -22422,6 +22493,11 @@ function readString4(value, fallback = "") {
22422
22493
  function arraysEqual(left, right) {
22423
22494
  return left.length === right.length && left.every((value, index) => value === right[index]);
22424
22495
  }
22496
+ function recordsEqual(left, right) {
22497
+ const leftEntries = Object.entries(left);
22498
+ const rightEntries = Object.entries(right);
22499
+ return leftEntries.length === rightEntries.length && leftEntries.every(([name, value]) => right[name] === value);
22500
+ }
22425
22501
  function elicitationFieldType(value) {
22426
22502
  if (value === "boolean") {
22427
22503
  return "boolean";
@@ -22442,7 +22518,7 @@ function normalizeElicitationOptions(schema) {
22442
22518
  const oneOf = schema["oneOf"];
22443
22519
  if (Array.isArray(oneOf)) {
22444
22520
  for (const option2 of oneOf) {
22445
- if (isRecord5(option2) && typeof option2["const"] === "string") {
22521
+ if (isRecord6(option2) && typeof option2["const"] === "string") {
22446
22522
  normalized.push({
22447
22523
  description: truncate3(readString4(option2["description"]), 500),
22448
22524
  label: truncate3(readString4(option2["title"], option2["const"]), 255),
@@ -22462,7 +22538,7 @@ function normalizeElicitationOptions(schema) {
22462
22538
  return normalized.slice(0, 200);
22463
22539
  }
22464
22540
  const items = schema["items"];
22465
- if (!isRecord5(items)) {
22541
+ if (!isRecord6(items)) {
22466
22542
  return normalized;
22467
22543
  }
22468
22544
  const anyOf = items["anyOf"];
@@ -22475,7 +22551,7 @@ function numericString(value) {
22475
22551
  return typeof value === "number" && Number.isFinite(value) ? value.toString() : "";
22476
22552
  }
22477
22553
  function normalizeElicitationFields(requestedSchema) {
22478
- if (!isRecord5(requestedSchema) || !isRecord5(requestedSchema["properties"])) {
22554
+ if (!isRecord6(requestedSchema) || !isRecord6(requestedSchema["properties"])) {
22479
22555
  return [];
22480
22556
  }
22481
22557
  const properties = requestedSchema["properties"];
@@ -22485,7 +22561,7 @@ function normalizeElicitationFields(requestedSchema) {
22485
22561
  );
22486
22562
  const normalized = [];
22487
22563
  for (const [name, property] of Object.entries(properties)) {
22488
- if (!isRecord5(property)) {
22564
+ if (!isRecord6(property)) {
22489
22565
  continue;
22490
22566
  }
22491
22567
  const type = elicitationFieldType(property["type"]);
@@ -22576,7 +22652,7 @@ function validateElicitationContent(encoded, requestedSchema) {
22576
22652
  "invalid_elicitation_response"
22577
22653
  );
22578
22654
  }
22579
- if (!isRecord5(decoded) || !isRecord5(requestedSchema) || !isRecord5(requestedSchema["properties"])) {
22655
+ if (!isRecord6(decoded) || !isRecord6(requestedSchema) || !isRecord6(requestedSchema["properties"])) {
22580
22656
  throw new AcpExecutionError(
22581
22657
  "Elicitation response does not match the requested form",
22582
22658
  {},
@@ -22596,7 +22672,7 @@ function validateElicitationContent(encoded, requestedSchema) {
22596
22672
  const validated = {};
22597
22673
  for (const [name, value] of Object.entries(decoded)) {
22598
22674
  const property = properties[name];
22599
- if (!isRecord5(property)) {
22675
+ if (!isRecord6(property)) {
22600
22676
  throw new AcpExecutionError(
22601
22677
  `Elicitation field ${name} was not requested`,
22602
22678
  {},
@@ -22738,6 +22814,7 @@ var AcpProcessSession = class _AcpProcessSession {
22738
22814
  #child;
22739
22815
  #connection;
22740
22816
  #cwd;
22817
+ #environment;
22741
22818
  #getObserver;
22742
22819
  #mobiusSessionId;
22743
22820
  #pendingElicitations = /* @__PURE__ */ new Map();
@@ -22759,7 +22836,9 @@ var AcpProcessSession = class _AcpProcessSession {
22759
22836
  imagePrompt: false,
22760
22837
  listSessions: false,
22761
22838
  loadSession: false,
22839
+ promptQueueing: false,
22762
22840
  resumeSession: false,
22841
+ steering: false,
22763
22842
  unstableForkSession: false
22764
22843
  };
22765
22844
  #commands = [];
@@ -22779,6 +22858,7 @@ var AcpProcessSession = class _AcpProcessSession {
22779
22858
  this.#additionalDirectories = [...additionalDirectories];
22780
22859
  this.#agentId = agent.id;
22781
22860
  this.#cwd = cwd;
22861
+ this.#environment = agent.environment;
22782
22862
  this.#getObserver = getObserver;
22783
22863
  this.#mobiusSessionId = mobiusSessionId;
22784
22864
  this.#sessionConfigProtocol = sessionConfigProtocolForAgent(agent.id);
@@ -22802,9 +22882,9 @@ var AcpProcessSession = class _AcpProcessSession {
22802
22882
  stderr.on("data", (chunk) => {
22803
22883
  process.stderr.write(`[${agent.id}-acp] ${chunk}`);
22804
22884
  });
22805
- this.#processExit = new Promise((resolve5) => {
22806
- this.#child.once("error", resolve5);
22807
- this.#child.once("exit", resolve5);
22885
+ this.#processExit = new Promise((resolve6) => {
22886
+ this.#child.once("error", resolve6);
22887
+ this.#child.once("exit", resolve6);
22808
22888
  });
22809
22889
  const stream = ndJsonStream(createWritableAcpStream(stdin), readableWebStream(stdout));
22810
22890
  this.#connection = createAcpClient(this).connect(stream);
@@ -22864,7 +22944,18 @@ var AcpProcessSession = class _AcpProcessSession {
22864
22944
  store,
22865
22945
  async (runtime) => await runtime.#initializeAgent(options.signal)
22866
22946
  );
22947
+ static startSessionForkAtTurn = async (options, sourceNativeSessionId, forkPoint, getObserver, store) => await _AcpProcessSession.#start(
22948
+ options,
22949
+ getObserver,
22950
+ store,
22951
+ async (runtime) => await runtime.initialize(
22952
+ options.signal,
22953
+ { kind: "fork", nativeSessionId: sourceNativeSessionId },
22954
+ forkPoint
22955
+ )
22956
+ );
22867
22957
  belongsTo = (agentId, cwd, additionalDirectories) => this.#agentId === agentId && this.#cwd === cwd && arraysEqual(this.#additionalDirectories, additionalDirectories);
22958
+ usesEnvironment = (environment) => recordsEqual(this.#environment, environment);
22868
22959
  canRebind = () => !this.#prompted && !this.#turn.running;
22869
22960
  prepared = () => new AcpPreparedSession(
22870
22961
  this.#agentName,
@@ -22930,15 +23021,14 @@ var AcpProcessSession = class _AcpProcessSession {
22930
23021
  "unsupported_acp_protocol"
22931
23022
  );
22932
23023
  }
22933
- const capabilities = initialized.agentCapabilities ?? {};
22934
- this.#capabilities = normalizeAcpCapabilities(capabilities);
23024
+ this.#capabilities = normalizeAcpCapabilities(initialized);
22935
23025
  this.#agentName = truncate3(initialized.agentInfo?.title ?? initialized.agentInfo?.name ?? this.#agentId, 255);
22936
23026
  this.#agentVersion = truncate3(
22937
23027
  this.#verifiedAgentVersion.length > 0 ? this.#verifiedAgentVersion : initialized.agentInfo?.version ?? "",
22938
23028
  128
22939
23029
  );
22940
23030
  }
22941
- initialize = async (signal, origin) => {
23031
+ initialize = async (signal, origin, forkPoint) => {
22942
23032
  await this.#initializeAgent(signal);
22943
23033
  let stored = origin?.kind === "import" ? new FoundAcpStoredSession(
22944
23034
  new AcpStoredSession(
@@ -22970,6 +23060,13 @@ var AcpProcessSession = class _AcpProcessSession {
22970
23060
  "native_session_unsupported"
22971
23061
  );
22972
23062
  }
23063
+ if (this.#additionalDirectories.length > 0 && !this.#capabilities.additionalDirectories) {
23064
+ throw new AcpExecutionError(
23065
+ "This Agent cannot restore additional working directories",
23066
+ {},
23067
+ "native_session_unsupported"
23068
+ );
23069
+ }
22973
23070
  const forked = await this.race(
22974
23071
  this.#connection.agent.request(
22975
23072
  methods3.agent.session.fork,
@@ -22977,7 +23074,21 @@ var AcpProcessSession = class _AcpProcessSession {
22977
23074
  sessionId: nativeSessionIdSchema.parse(origin.nativeSessionId),
22978
23075
  cwd: this.#cwd,
22979
23076
  additionalDirectories: [...this.#additionalDirectories],
22980
- mcpServers: []
23077
+ mcpServers: [],
23078
+ ...forkPoint === void 0 ? {} : {
23079
+ _meta: {
23080
+ jetbrains: {
23081
+ air: {
23082
+ fork: {
23083
+ version: 1,
23084
+ messageFingerprint: forkPoint.messageFingerprint,
23085
+ messageId: forkPoint.messageId,
23086
+ messageOccurrence: forkPoint.messageOccurrence
23087
+ }
23088
+ }
23089
+ }
23090
+ }
23091
+ }
22981
23092
  },
22982
23093
  { cancellationSignal: signal }
22983
23094
  ),
@@ -23182,10 +23293,7 @@ var AcpProcessSession = class _AcpProcessSession {
23182
23293
  }
23183
23294
  return this.#configOptions;
23184
23295
  };
23185
- prompt = async (options) => {
23186
- if (this.#turn.running) {
23187
- throw new AcpExecutionError("Another prompt is already running in this conversation", {}, "session_busy");
23188
- }
23296
+ buildPrompt = (options) => {
23189
23297
  if (options.images.length > 0 && !this.#capabilities.imagePrompt) {
23190
23298
  throw new AcpExecutionError(
23191
23299
  "The selected agent does not accept image prompts",
@@ -23234,6 +23342,13 @@ var AcpProcessSession = class _AcpProcessSession {
23234
23342
  if (prompt.length === 0) {
23235
23343
  throw new AcpExecutionError("A prompt must contain text or an attachment", {}, "empty_prompt");
23236
23344
  }
23345
+ return prompt;
23346
+ };
23347
+ prompt = async (options) => {
23348
+ if (this.#turn.running) {
23349
+ throw new AcpExecutionError("Another prompt is already running in this conversation", {}, "session_busy");
23350
+ }
23351
+ const prompt = this.buildPrompt(options);
23237
23352
  this.#store.write(
23238
23353
  new AcpStoredSession(
23239
23354
  this.#mobiusSessionId,
@@ -23292,6 +23407,54 @@ var AcpProcessSession = class _AcpProcessSession {
23292
23407
  this.#turn = new IdleAcpTurn();
23293
23408
  }
23294
23409
  };
23410
+ steer = async (options) => {
23411
+ if (!this.#capabilities.steering) {
23412
+ throw new AcpExecutionError(
23413
+ "The selected agent does not support mid-turn steering",
23414
+ {},
23415
+ "steering_unsupported"
23416
+ );
23417
+ }
23418
+ if (!this.#turn.running || this.#turn.commandId !== options.targetCommandId) {
23419
+ throw new AcpExecutionError("The target turn is no longer active", {}, "steering_target_unavailable");
23420
+ }
23421
+ const prompt = this.buildPrompt(options);
23422
+ const response = await this.race(
23423
+ this.#connection.agent.request("_session/steering", {
23424
+ _meta: { steering: { idleBehavior: "promptRequired" } },
23425
+ prompt,
23426
+ sessionId: this.#nativeSessionId
23427
+ }),
23428
+ "ACP agent exited during steering",
23429
+ options.signal
23430
+ );
23431
+ if (!isRecord6(response)) {
23432
+ throw new AcpExecutionError(
23433
+ "ACP agent returned an invalid steering response",
23434
+ {},
23435
+ "acp_invalid_steering_response"
23436
+ );
23437
+ }
23438
+ const outcome = response["outcome"];
23439
+ if (outcome === "injected" || outcome === "startedNewTurn") {
23440
+ return outcome;
23441
+ }
23442
+ if (outcome === "promptRequired") {
23443
+ throw new AcpExecutionError(
23444
+ "The target turn finished before steering could be injected",
23445
+ {},
23446
+ "steering_target_unavailable"
23447
+ );
23448
+ }
23449
+ if (outcome === "failed") {
23450
+ throw new AcpExecutionError("The Agent could not inject steering", {}, "steering_failed");
23451
+ }
23452
+ throw new AcpExecutionError(
23453
+ "ACP agent returned an invalid steering response",
23454
+ {},
23455
+ "acp_invalid_steering_response"
23456
+ );
23457
+ };
23295
23458
  cancel = async () => {
23296
23459
  const capturedProcesses = await captureProcessTree(this.#child);
23297
23460
  try {
@@ -23532,7 +23695,7 @@ var AcpProcessSession = class _AcpProcessSession {
23532
23695
  );
23533
23696
  } else {
23534
23697
  await this.#turn.activities.finishNarrative();
23535
- await this.#turn.onText(update.content.text);
23698
+ await this.#turn.onText(update.content.text, truncate3(update.messageId ?? "", 255));
23536
23699
  }
23537
23700
  } else {
23538
23701
  await this.#turn.activities.finishNarrative();
@@ -23758,6 +23921,7 @@ var AcpProcessSession = class _AcpProcessSession {
23758
23921
  };
23759
23922
  };
23760
23923
  var AcpSessionManager = class {
23924
+ #activeCommandIds = /* @__PURE__ */ new Map();
23761
23925
  #nativeOperations = /* @__PURE__ */ new Set();
23762
23926
  #nativeBrowserObserver = new NullAcpSessionObserver();
23763
23927
  #nativeBrowserPreparing = /* @__PURE__ */ new Map();
@@ -23826,6 +23990,25 @@ var AcpSessionManager = class {
23826
23990
  this.#preparing.set(options.sessionId, operation);
23827
23991
  return await operation;
23828
23992
  }
23993
+ async #startSessionForkAtTurnPreparation(options, sourceNativeSessionId, forkPoint) {
23994
+ const operation = (async () => {
23995
+ try {
23996
+ const runtime = await AcpProcessSession.startSessionForkAtTurn(
23997
+ options,
23998
+ sourceNativeSessionId,
23999
+ forkPoint,
24000
+ () => this.#observer,
24001
+ this.#store
24002
+ );
24003
+ this.#sessions.set(options.sessionId, runtime);
24004
+ return runtime;
24005
+ } finally {
24006
+ this.#preparing.delete(options.sessionId);
24007
+ }
24008
+ })();
24009
+ this.#preparing.set(options.sessionId, operation);
24010
+ return await operation;
24011
+ }
23829
24012
  async #replacePreparation(existing, options) {
23830
24013
  const operation = (async () => {
23831
24014
  try {
@@ -23842,6 +24025,58 @@ var AcpSessionManager = class {
23842
24025
  this.#preparing.set(options.sessionId, operation);
23843
24026
  return await operation;
23844
24027
  }
24028
+ async #replaceExecutionPreparation(existing, options) {
24029
+ const desiredConfigOptions = existing.prepared().configOptions;
24030
+ const operation = (async () => {
24031
+ try {
24032
+ this.#sessions.delete(options.sessionId);
24033
+ await existing.close();
24034
+ this.#store.remove(options.sessionId);
24035
+ const runtime = await AcpProcessSession.start(options, () => this.#observer, this.#store);
24036
+ try {
24037
+ for (const desired of desiredConfigOptions) {
24038
+ const current = runtime.prepared().configOptions.find((option2) => option2.id === desired.id);
24039
+ if (current === void 0 || current.type !== desired.type) {
24040
+ continue;
24041
+ }
24042
+ if (desired.type === "boolean") {
24043
+ if (current.booleanValue !== desired.booleanValue) {
24044
+ await runtime.configure({
24045
+ ...options,
24046
+ booleanValue: desired.booleanValue,
24047
+ configId: desired.id,
24048
+ requestId: `execution-prepare-${desired.id}`,
24049
+ value: "",
24050
+ valueType: "boolean"
24051
+ });
24052
+ }
24053
+ continue;
24054
+ }
24055
+ if (current.currentValue !== desired.currentValue && current.values.some((value) => value.value === desired.currentValue)) {
24056
+ await runtime.configure({
24057
+ ...options,
24058
+ booleanValue: false,
24059
+ configId: desired.id,
24060
+ requestId: `execution-prepare-${desired.id}`,
24061
+ value: desired.currentValue,
24062
+ valueType: "select"
24063
+ });
24064
+ }
24065
+ }
24066
+ } catch (error61) {
24067
+ await runtime.close();
24068
+ this.#store.remove(options.sessionId);
24069
+ throw error61;
24070
+ }
24071
+ this.#sessions.set(options.sessionId, runtime);
24072
+ return runtime;
24073
+ } finally {
24074
+ this.#preparing.delete(options.sessionId);
24075
+ }
24076
+ })();
24077
+ this.#preparing.set(options.sessionId, operation);
24078
+ return await operation;
24079
+ }
23845
24080
  prepareSession = async (options) => {
23846
24081
  const stored = this.#store.read(options.sessionId);
23847
24082
  if (stored instanceof DeletedAcpStoredSession) {
@@ -23880,6 +24115,35 @@ var AcpSessionManager = class {
23880
24115
  const runtime = await this.#startPreparation(options);
23881
24116
  return runtime.prepared();
23882
24117
  };
24118
+ prepareExecutionSession = async (options) => {
24119
+ const pending = this.#preparing.get(options.sessionId);
24120
+ if (pending !== void 0) {
24121
+ await pending;
24122
+ return await this.prepareExecutionSession(options);
24123
+ }
24124
+ const existing = this.#sessions.get(options.sessionId);
24125
+ if (existing === void 0) {
24126
+ return (await this.#startPreparation(options)).prepared();
24127
+ }
24128
+ if (!existing.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
24129
+ throw new AcpExecutionError(
24130
+ "The agent or workspace cannot change after a conversation has started",
24131
+ {},
24132
+ "session_identity_mismatch"
24133
+ );
24134
+ }
24135
+ if (existing.usesEnvironment(options.agent.environment)) {
24136
+ return existing.prepared();
24137
+ }
24138
+ if (!existing.canRebind()) {
24139
+ throw new AcpExecutionError(
24140
+ "The execution environment cannot change after a conversation has started",
24141
+ {},
24142
+ "session_identity_mismatch"
24143
+ );
24144
+ }
24145
+ return (await this.#replaceExecutionPreparation(existing, options)).prepared();
24146
+ };
23883
24147
  prepareNativeSessionBrowser = async (options) => {
23884
24148
  const pending = this.#nativeBrowserPreparing.get(options.sessionId);
23885
24149
  if (pending !== void 0) {
@@ -23924,6 +24188,47 @@ var AcpSessionManager = class {
23924
24188
  }
23925
24189
  }
23926
24190
  };
24191
+ steerPrompt = async (options) => {
24192
+ if (this.#activeCommandIds.get(options.sessionId) !== options.targetCommandId) {
24193
+ throw new AcpExecutionError("The target turn is no longer active", {}, "steering_target_unavailable");
24194
+ }
24195
+ const pending = this.#preparing.get(options.sessionId);
24196
+ if (pending !== void 0) {
24197
+ await pending;
24198
+ }
24199
+ if (this.#activeCommandIds.get(options.sessionId) !== options.targetCommandId) {
24200
+ throw new AcpExecutionError("The target turn is no longer active", {}, "steering_target_unavailable");
24201
+ }
24202
+ const runtime = this.#sessions.get(options.sessionId);
24203
+ if (runtime === void 0 || !runtime.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
24204
+ throw new AcpExecutionError(
24205
+ "The target turn execution context is unavailable",
24206
+ {},
24207
+ "steering_target_unavailable"
24208
+ );
24209
+ }
24210
+ const deadlineController = new AbortController();
24211
+ const deadline = setTimeout(
24212
+ () => deadlineController.abort(
24213
+ new AcpExecutionError("The steering request exceeded its deadline", {}, "steering_timeout")
24214
+ ),
24215
+ options.agent.promptTimeoutMilliseconds
24216
+ );
24217
+ deadline.unref();
24218
+ try {
24219
+ return await runtime.steer({
24220
+ ...options,
24221
+ signal: AbortSignal.any([options.signal, deadlineController.signal])
24222
+ });
24223
+ } catch (error61) {
24224
+ if (error61 instanceof AcpExecutionError) {
24225
+ throw error61;
24226
+ }
24227
+ throw new AcpExecutionError("ACP agent steering failed", { cause: error61 }, "steering_failed");
24228
+ } finally {
24229
+ clearTimeout(deadline);
24230
+ }
24231
+ };
23927
24232
  runPrompt = async (options) => {
23928
24233
  const stored = this.#store.read(options.sessionId);
23929
24234
  if (stored instanceof FoundAcpStoredSession && this.#nativeOperations.has(`${stored.session.agentId}:${stored.session.nativeSessionId}`)) {
@@ -23948,6 +24253,7 @@ var AcpSessionManager = class {
23948
24253
  signal: AbortSignal.any([options.signal, deadlineController.signal])
23949
24254
  };
23950
24255
  this.#activeSessionIds.add(options.sessionId);
24256
+ this.#activeCommandIds.set(options.sessionId, options.commandId);
23951
24257
  try {
23952
24258
  if (this.#cancellationRequests.has(options.sessionId)) {
23953
24259
  return { stopReason: "cancelled" };
@@ -23987,6 +24293,9 @@ var AcpSessionManager = class {
23987
24293
  } finally {
23988
24294
  clearTimeout(deadline);
23989
24295
  this.#activeSessionIds.delete(options.sessionId);
24296
+ if (this.#activeCommandIds.get(options.sessionId) === options.commandId) {
24297
+ this.#activeCommandIds.delete(options.sessionId);
24298
+ }
23990
24299
  this.#cancellationRequests.delete(options.sessionId);
23991
24300
  }
23992
24301
  };
@@ -24011,6 +24320,7 @@ var AcpSessionManager = class {
24011
24320
  this.#nativeBrowserSessions.clear();
24012
24321
  await Promise.all([...sessions, ...nativeBrowserSessions].map(async (session) => await session.close()));
24013
24322
  };
24323
+ hasPreparedSession = (sessionId, agentId, cwd, additionalDirectories) => this.#sessions.get(sessionId)?.belongsTo(agentId, cwd, additionalDirectories) === true;
24014
24324
  ensureSession = async (options) => {
24015
24325
  const existing = this.#sessions.get(options.sessionId);
24016
24326
  if (existing !== void 0) {
@@ -24119,6 +24429,68 @@ var AcpSessionManager = class {
24119
24429
  this.#nativeOperations.delete(key);
24120
24430
  }
24121
24431
  };
24432
+ forkSessionAtTurn = async (options, sourceSessionId, forkPoint) => {
24433
+ if (sourceSessionId === options.sessionId) {
24434
+ throw new AcpExecutionError(
24435
+ "The fork destination must be a new conversation",
24436
+ {},
24437
+ "session_identity_mismatch"
24438
+ );
24439
+ }
24440
+ const source = this.#store.read(sourceSessionId);
24441
+ if (!(source instanceof FoundAcpStoredSession) || !source.session.prompted) {
24442
+ throw new AcpExecutionError(
24443
+ "The source conversation has no resumable native Agent session",
24444
+ {},
24445
+ "native_session_not_found"
24446
+ );
24447
+ }
24448
+ if (source.session.agentId !== options.agent.id || source.session.cwd !== options.cwd || !arraysEqual(source.session.additionalDirectories, options.additionalDirectories)) {
24449
+ throw new AcpExecutionError(
24450
+ "The source conversation execution target does not match the fork request",
24451
+ {},
24452
+ "native_session_target_changed"
24453
+ );
24454
+ }
24455
+ const key = `${options.agent.id}:${source.session.nativeSessionId}`;
24456
+ const linked = this.#store.findNative(options.agent.id, source.session.nativeSessionId);
24457
+ if (this.#nativeOperations.has(key) || linked.some((entry) => this.#activeSessionIds.has(entry.sessionId) || this.#preparing.has(entry.sessionId))) {
24458
+ throw new AcpExecutionError(
24459
+ "Wait for the source conversation's running operation to finish",
24460
+ {},
24461
+ "session_busy"
24462
+ );
24463
+ }
24464
+ 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) {
24465
+ throw new AcpExecutionError("The destination conversation already exists", {}, "session_identity_mismatch");
24466
+ }
24467
+ this.#nativeOperations.add(key);
24468
+ try {
24469
+ const runtime = await this.#startSessionForkAtTurnPreparation(
24470
+ options,
24471
+ source.session.nativeSessionId,
24472
+ forkPoint
24473
+ );
24474
+ const openedKey = `${options.agent.id}:${runtime.nativeSessionId()}`;
24475
+ this.#nativeOperations.add(openedKey);
24476
+ try {
24477
+ return {
24478
+ history: await runtime.loadHistory(options.signal),
24479
+ prepared: runtime.prepared(),
24480
+ sessionId: options.sessionId
24481
+ };
24482
+ } catch (error61) {
24483
+ this.#sessions.delete(options.sessionId);
24484
+ this.#store.remove(options.sessionId);
24485
+ await runtime.closeProcess();
24486
+ throw error61;
24487
+ } finally {
24488
+ this.#nativeOperations.delete(openedKey);
24489
+ }
24490
+ } finally {
24491
+ this.#nativeOperations.delete(key);
24492
+ }
24493
+ };
24122
24494
  deleteNativeSession = async (options, nativeSessionId) => {
24123
24495
  const runtime = await this.#ensureNativeSessionBrowser(options);
24124
24496
  const key = `${options.agent.id}:${nativeSessionId}`;
@@ -24263,7 +24635,8 @@ var LocalBotGrant = class {
24263
24635
  revocationEpoch;
24264
24636
  workspaceIdentityDigest;
24265
24637
  workspacePath;
24266
- constructor(localGrantId, botId, behaviorRevisionId, behaviorDigest, bindingId, bindingRevision, agent, agentIdentityDigest, workspacePath, workspaceIdentityDigest, allowedDeliveryTargetIds, expiresAt, revocationEpoch) {
24638
+ workspaceRef;
24639
+ constructor(localGrantId, botId, behaviorRevisionId, behaviorDigest, bindingId, bindingRevision, agent, agentIdentityDigest, workspaceRef, workspacePath, workspaceIdentityDigest, allowedDeliveryTargetIds, expiresAt, revocationEpoch) {
24267
24640
  this.localGrantId = localGrantId;
24268
24641
  this.botId = botId;
24269
24642
  this.behaviorRevisionId = behaviorRevisionId;
@@ -24272,6 +24645,7 @@ var LocalBotGrant = class {
24272
24645
  this.bindingRevision = bindingRevision;
24273
24646
  this.agent = agent;
24274
24647
  this.agentIdentityDigest = agentIdentityDigest;
24648
+ this.workspaceRef = workspaceRef;
24275
24649
  this.workspacePath = workspacePath;
24276
24650
  this.workspaceIdentityDigest = workspaceIdentityDigest;
24277
24651
  this.allowedDeliveryTargetIds = Object.freeze([...allowedDeliveryTargetIds]);
@@ -24456,7 +24830,7 @@ var LocalBotGrantStore = class {
24456
24830
  WHERE local_grant_id = ? AND bot_id = ?
24457
24831
  AND behavior_revision_id = ? AND behavior_digest = ?
24458
24832
  AND binding_id = ? AND binding_revision = ?
24459
- AND agent_ref = ? AND workspace_ref = ?
24833
+ AND agent_ref = ?
24460
24834
  AND revoked_at IS NULL AND expires_at > ?`
24461
24835
  ).get(
24462
24836
  command.localGrantId,
@@ -24466,7 +24840,6 @@ var LocalBotGrantStore = class {
24466
24840
  command.bindingId,
24467
24841
  command.bindingRevision,
24468
24842
  command.agentId,
24469
- command.directoryId,
24470
24843
  now
24471
24844
  );
24472
24845
  if (row === void 0) {
@@ -24489,7 +24862,8 @@ var LocalBotGrantStore = class {
24489
24862
  revalidateExecutable(agent.agentIdentity)
24490
24863
  ]);
24491
24864
  signal.throwIfAborted();
24492
- const workspacePath = await directories.resolve(bindingAuthorityId, command.directoryId, signal);
24865
+ const workspaceRef = rowString(row, "workspace_ref");
24866
+ const workspacePath = await directories.resolve(bindingAuthorityId, workspaceRef, signal);
24493
24867
  const currentAgentDigest = agentDigest(agent);
24494
24868
  const currentWorkspaceDigest = await workspaceDigest(workspacePath);
24495
24869
  signal.throwIfAborted();
@@ -24515,6 +24889,7 @@ var LocalBotGrantStore = class {
24515
24889
  command.bindingRevision,
24516
24890
  agent,
24517
24891
  currentAgentDigest,
24892
+ workspaceRef,
24518
24893
  workspacePath,
24519
24894
  currentWorkspaceDigest,
24520
24895
  approvedTargets,
@@ -24683,7 +25058,7 @@ var BotMessageCommandError = class extends Error {
24683
25058
  this.name = "BotMessageCommandError";
24684
25059
  }
24685
25060
  };
24686
- function isRecord6(value) {
25061
+ function isRecord7(value) {
24687
25062
  return typeof value === "object" && value !== null && !Array.isArray(value);
24688
25063
  }
24689
25064
  function requiredEnvironment(environment, name) {
@@ -24734,21 +25109,21 @@ function decodeResponse(encoded) {
24734
25109
  cause
24735
25110
  });
24736
25111
  }
24737
- if (!isRecord6(value) || typeof value["ok"] !== "boolean") {
25112
+ if (!isRecord7(value) || typeof value["ok"] !== "boolean") {
24738
25113
  throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
24739
25114
  }
24740
25115
  if (value["ok"] === true && typeof value["outboundMessageId"] === "string") {
24741
25116
  return value["outboundMessageId"];
24742
25117
  }
24743
25118
  const error61 = value["error"];
24744
- if (value["ok"] === false && isRecord6(error61) && typeof error61["code"] === "string" && typeof error61["message"] === "string") {
25119
+ if (value["ok"] === false && isRecord7(error61) && typeof error61["code"] === "string" && typeof error61["message"] === "string") {
24745
25120
  throw new BotMessageCommandError(error61["code"], error61["message"]);
24746
25121
  }
24747
25122
  throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
24748
25123
  }
24749
25124
  async function requestLocalBotService(socketPath, payload, signal) {
24750
25125
  signal.throwIfAborted();
24751
- return await new Promise((resolve5, reject) => {
25126
+ return await new Promise((resolve6, reject) => {
24752
25127
  const socket = createConnection(socketPath);
24753
25128
  const chunks = [];
24754
25129
  let length = 0;
@@ -24797,7 +25172,7 @@ async function requestLocalBotService(socketPath, payload, signal) {
24797
25172
  finished = true;
24798
25173
  signal.removeEventListener("abort", abort);
24799
25174
  try {
24800
- resolve5(decodeResponse(Buffer.concat(chunks).toString("utf8")));
25175
+ resolve6(decodeResponse(Buffer.concat(chunks).toString("utf8")));
24801
25176
  } catch (error61) {
24802
25177
  reject(error61);
24803
25178
  } finally {
@@ -24850,7 +25225,7 @@ var BotOutboundIpcRequest = class {
24850
25225
  this.text = text;
24851
25226
  }
24852
25227
  };
24853
- function isRecord7(value) {
25228
+ function isRecord8(value) {
24854
25229
  return typeof value === "object" && value !== null && !Array.isArray(value);
24855
25230
  }
24856
25231
  function decodeRequest(encoded) {
@@ -24860,7 +25235,7 @@ function decodeRequest(encoded) {
24860
25235
  } catch (cause) {
24861
25236
  throw new LocalBotAuthorizationError("invalid_request", "The local Bot request is invalid", { cause });
24862
25237
  }
24863
- if (!isRecord7(value)) {
25238
+ if (!isRecord8(value)) {
24864
25239
  throw new LocalBotAuthorizationError("invalid_request", "The local Bot request is invalid");
24865
25240
  }
24866
25241
  const keys = Object.keys(value);
@@ -24910,10 +25285,10 @@ async function decodeRelayResponse(response) {
24910
25285
  cause
24911
25286
  });
24912
25287
  }
24913
- if (response.status === 202 && isRecord7(value) && typeof value["outboundMessageId"] === "string") {
25288
+ if (response.status === 202 && isRecord8(value) && typeof value["outboundMessageId"] === "string") {
24914
25289
  return value["outboundMessageId"];
24915
25290
  }
24916
- if (isRecord7(value) && isRecord7(value["error"])) {
25291
+ if (isRecord8(value) && isRecord8(value["error"])) {
24917
25292
  const error61 = value["error"];
24918
25293
  if (typeof error61["code"] === "string" && typeof error61["message"] === "string") {
24919
25294
  throw new LocalBotAuthorizationError(error61["code"], error61["message"]);
@@ -24946,17 +25321,17 @@ var BotOutboundIpcServer = class {
24946
25321
  if (this.#usesFilesystemSocket) {
24947
25322
  await mkdir(dirname3(this.#socketPath), { mode: 448, recursive: true });
24948
25323
  await unlink(this.#socketPath).catch((error61) => {
24949
- if (!isRecord7(error61) || error61["code"] !== "ENOENT") {
25324
+ if (!isRecord8(error61) || error61["code"] !== "ENOENT") {
24950
25325
  throw error61;
24951
25326
  }
24952
25327
  });
24953
25328
  }
24954
- await new Promise((resolve5, reject) => {
25329
+ await new Promise((resolve6, reject) => {
24955
25330
  const fail = (error61) => reject(error61);
24956
25331
  this.#server.once("error", fail);
24957
25332
  this.#server.listen(this.#socketPath, () => {
24958
25333
  this.#server.removeListener("error", fail);
24959
- resolve5();
25334
+ resolve6();
24960
25335
  });
24961
25336
  });
24962
25337
  if (this.#usesFilesystemSocket) {
@@ -24969,10 +25344,10 @@ var BotOutboundIpcServer = class {
24969
25344
  };
24970
25345
  close = async () => {
24971
25346
  if (this.#server.listening) {
24972
- await new Promise((resolve5, reject) => {
25347
+ await new Promise((resolve6, reject) => {
24973
25348
  this.#server.close((error61) => {
24974
25349
  if (error61 === void 0) {
24975
- resolve5();
25350
+ resolve6();
24976
25351
  return;
24977
25352
  }
24978
25353
  reject(error61);
@@ -24981,7 +25356,7 @@ var BotOutboundIpcServer = class {
24981
25356
  }
24982
25357
  if (this.#usesFilesystemSocket) {
24983
25358
  await unlink(this.#socketPath).catch((error61) => {
24984
- if (!isRecord7(error61) || error61["code"] !== "ENOENT") {
25359
+ if (!isRecord8(error61) || error61["code"] !== "ENOENT") {
24985
25360
  throw error61;
24986
25361
  }
24987
25362
  });
@@ -25228,7 +25603,7 @@ var ManagedNpmPackageError = class extends Error {
25228
25603
  this.name = "ManagedNpmPackageError";
25229
25604
  }
25230
25605
  };
25231
- function isRecord8(value) {
25606
+ function isRecord9(value) {
25232
25607
  return typeof value === "object" && value !== null && !Array.isArray(value);
25233
25608
  }
25234
25609
  function packagePath(root, packageName) {
@@ -25250,7 +25625,7 @@ async function readJsonRecord(path2, maximumBytes) {
25250
25625
  );
25251
25626
  }
25252
25627
  const value = JSON.parse(await handle.readFile({ encoding: "utf8" }));
25253
- if (!isRecord8(value)) {
25628
+ if (!isRecord9(value)) {
25254
25629
  throw new ManagedNpmPackageError(
25255
25630
  "managed_package_verification_failed",
25256
25631
  "The managed npm package metadata is invalid"
@@ -25276,7 +25651,7 @@ function readBinPath(packageMetadata, executableName) {
25276
25651
  if (typeof bin === "string") {
25277
25652
  return bin;
25278
25653
  }
25279
- if (isRecord8(bin) && typeof bin[executableName] === "string") {
25654
+ if (isRecord9(bin) && typeof bin[executableName] === "string") {
25280
25655
  return bin[executableName];
25281
25656
  }
25282
25657
  throw new ManagedNpmPackageError(
@@ -25292,7 +25667,7 @@ function verifyPackageLockEntry(lock, definition, verifyDependencyClosure) {
25292
25667
  );
25293
25668
  }
25294
25669
  const packages = lock["packages"];
25295
- if (!isRecord8(packages)) {
25670
+ if (!isRecord9(packages)) {
25296
25671
  throw new ManagedNpmPackageError(
25297
25672
  "managed_package_verification_failed",
25298
25673
  "The managed npm package lockfile is invalid"
@@ -25306,7 +25681,7 @@ function verifyPackageLockEntry(lock, definition, verifyDependencyClosure) {
25306
25681
  if (verifyDependencyClosure && normalizedKey.length > 0) {
25307
25682
  let resolved;
25308
25683
  try {
25309
- if (!normalizedKey.split("/").includes("node_modules") || !isRecord8(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") {
25684
+ if (!normalizedKey.split("/").includes("node_modules") || !isRecord9(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") {
25310
25685
  throw new Error("Invalid locked dependency");
25311
25686
  }
25312
25687
  resolved = new URL(candidate["resolved"]);
@@ -25329,7 +25704,7 @@ function verifyPackageLockEntry(lock, definition, verifyDependencyClosure) {
25329
25704
  matches += 1;
25330
25705
  }
25331
25706
  }
25332
- if (matches !== 1 || !isRecord8(entry) || entry["integrity"] !== definition.integrity || entry["resolved"] !== definition.tarballUrl || entry["version"] !== definition.version) {
25707
+ if (matches !== 1 || !isRecord9(entry) || entry["integrity"] !== definition.integrity || entry["resolved"] !== definition.tarballUrl || entry["version"] !== definition.version) {
25333
25708
  throw new ManagedNpmPackageError(
25334
25709
  "managed_package_verification_failed",
25335
25710
  "The managed npm package lockfile does not match the pinned artifact"
@@ -25959,7 +26334,7 @@ var ManagedAgentArchiveError = class extends Error {
25959
26334
  this.name = "ManagedAgentArchiveError";
25960
26335
  }
25961
26336
  };
25962
- function isRecord9(value) {
26337
+ function isRecord10(value) {
25963
26338
  return typeof value === "object" && value !== null && !Array.isArray(value);
25964
26339
  }
25965
26340
  function isContainedPath3(root, candidate) {
@@ -26016,7 +26391,7 @@ async function readManifest(path2) {
26016
26391
  );
26017
26392
  }
26018
26393
  const parsed = JSON.parse(await handle.readFile({ encoding: "utf8" }));
26019
- if (!isRecord9(parsed)) {
26394
+ if (!isRecord10(parsed)) {
26020
26395
  throw new ManagedAgentArchiveError(
26021
26396
  "managed_archive_verification_failed",
26022
26397
  "The managed Agent archive manifest is invalid"
@@ -26709,7 +27084,7 @@ async function probeAcpReadiness(specification, agent, environment, defaultWorks
26709
27084
  await specification.verifyAcp(connection, initialized, defaultWorkspace, controller.signal);
26710
27085
  return new VerifiedAcpReadiness(
26711
27086
  normalizedVersion(initialized.agentInfo?.version ?? ""),
26712
- normalizeAcpCapabilities(initialized.agentCapabilities ?? {})
27087
+ normalizeAcpCapabilities(initialized)
26713
27088
  );
26714
27089
  } catch (error61) {
26715
27090
  if (signal.aborted) {
@@ -26895,7 +27270,9 @@ var IDENTIFIER_PATTERN6 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
26895
27270
  var TOKEN_PATTERN2 = /^[a-zA-Z0-9_-]{32,256}$/;
26896
27271
  var MAX_CONNECTION_FILE_LENGTH = 16 * 1024;
26897
27272
  var CLAIM_RESPONSE_KEYS = ["channelId", "daemonToken", "runtimeId"];
26898
- var SAVED_CONNECTION_KEYS = ["defaultWorkspace", "channelId", "daemonToken", "relayOrigin"];
27273
+ var REDEEM_RESPONSE_KEYS = ["channelId", "runtimeCredential", "runtimeId"];
27274
+ var LEGACY_SAVED_CONNECTION_KEYS = ["defaultWorkspace", "channelId", "daemonToken", "relayOrigin"];
27275
+ var SAVED_CONNECTION_KEYS = ["defaultWorkspace", "channelId", "relayOrigin", "runtimeCredential"];
26899
27276
  var PairingClientError = class extends Error {
26900
27277
  code;
26901
27278
  constructor(code, message, options = {}) {
@@ -26904,7 +27281,7 @@ var PairingClientError = class extends Error {
26904
27281
  this.name = "PairingClientError";
26905
27282
  }
26906
27283
  };
26907
- function isRecord10(value) {
27284
+ function isRecord11(value) {
26908
27285
  return typeof value === "object" && value !== null && !Array.isArray(value);
26909
27286
  }
26910
27287
  function hasExactKeys(value, expectedKeys) {
@@ -26932,7 +27309,7 @@ function readSingleOption(arguments_, option2) {
26932
27309
  function assertKnownArguments(arguments_) {
26933
27310
  for (let index = 0; index < arguments_.length; index += 2) {
26934
27311
  const option2 = arguments_[index];
26935
- if (option2 !== "--url" && option2 !== "--code" && option2 !== "--workspace") {
27312
+ if (option2 !== "--url" && option2 !== "--code" && option2 !== "--pairing" && option2 !== "--workspace") {
26936
27313
  throw new PairingClientError("invalid_arguments", `Unknown pairing option: ${option2 ?? "missing option"}`);
26937
27314
  }
26938
27315
  }
@@ -26972,10 +27349,17 @@ function parsePairCommand(arguments_) {
26972
27349
  assertKnownArguments(arguments_);
26973
27350
  return {
26974
27351
  code: parsePairingCode(readSingleOption(arguments_, "--code")),
27352
+ pairingId: arguments_.includes("--pairing") ? validatePairingId(readSingleOption(arguments_, "--pairing")) : "",
26975
27353
  relayOrigin: parseRelayOrigin(readSingleOption(arguments_, "--url")),
26976
27354
  workspace: arguments_.includes("--workspace") ? readSingleOption(arguments_, "--workspace") : loadDefaultWorkspace()
26977
27355
  };
26978
27356
  }
27357
+ function validatePairingId(value) {
27358
+ if (!IDENTIFIER_PATTERN6.test(value)) {
27359
+ throw new PairingClientError("invalid_pairing_id", "The Runtime pairing ID is invalid");
27360
+ }
27361
+ return value;
27362
+ }
26979
27363
  function parseChannelArgument(arguments_) {
26980
27364
  if (arguments_.length !== 2 || arguments_[0] !== "--channel") {
26981
27365
  throw new PairingClientError("invalid_arguments", "Usage: mobius start --channel <channel-id>");
@@ -27020,9 +27404,9 @@ async function readJsonResponse(response) {
27020
27404
  }
27021
27405
  }
27022
27406
  function readRelayError(value) {
27023
- if (isRecord10(value)) {
27407
+ if (isRecord11(value)) {
27024
27408
  const error61 = value["error"];
27025
- if (isRecord10(error61)) {
27409
+ if (isRecord11(error61)) {
27026
27410
  const code = error61["code"];
27027
27411
  const message = error61["message"];
27028
27412
  if (typeof code === "string" && typeof message === "string") {
@@ -27032,17 +27416,17 @@ function readRelayError(value) {
27032
27416
  }
27033
27417
  return new PairingClientError("pairing_failed", "The relay could not pair this machine");
27034
27418
  }
27035
- function decodeClaimResponse(value) {
27036
- if (!isRecord10(value) || !hasExactKeys(value, CLAIM_RESPONSE_KEYS)) {
27419
+ function decodePairingResponse(value, credentialField, expectedKeys) {
27420
+ if (!isRecord11(value) || !hasExactKeys(value, expectedKeys)) {
27037
27421
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
27038
27422
  }
27039
27423
  const channelId = value["channelId"];
27040
- const daemonToken2 = value["daemonToken"];
27424
+ const runtimeCredential = value[credentialField];
27041
27425
  const runtimeId = value["runtimeId"];
27042
- if (typeof channelId !== "string" || !IDENTIFIER_PATTERN6.test(channelId) || typeof daemonToken2 !== "string" || !TOKEN_PATTERN2.test(daemonToken2) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN6.test(runtimeId)) {
27426
+ if (typeof channelId !== "string" || !IDENTIFIER_PATTERN6.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN6.test(runtimeId)) {
27043
27427
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
27044
27428
  }
27045
- return { channelId, daemonToken: daemonToken2, runtimeId };
27429
+ return { channelId, runtimeCredential, runtimeId };
27046
27430
  }
27047
27431
  async function claimPairing(options, defaultWorkspace, localAgents, installableAgents, signal) {
27048
27432
  const agents = localAgents.map((agent) => ({
@@ -27052,7 +27436,7 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
27052
27436
  id: agent.id,
27053
27437
  label: agent.label
27054
27438
  }));
27055
- const endpoint = mobiusApiUrl(options.relayOrigin, "/pairings/claim");
27439
+ const endpoint = options.pairingId.length > 0 ? mobiusApiUrl(options.relayOrigin, `/v1/runtime-pairings/${encodeURIComponent(options.pairingId)}/redeem`) : mobiusApiUrl(options.relayOrigin, "/pairings/claim");
27056
27440
  let response;
27057
27441
  try {
27058
27442
  response = await fetch3(endpoint, {
@@ -27075,10 +27459,10 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
27075
27459
  if (!response.ok) {
27076
27460
  throw readRelayError(value);
27077
27461
  }
27078
- return decodeClaimResponse(value);
27462
+ return options.pairingId.length > 0 ? decodePairingResponse(value, "runtimeCredential", REDEEM_RESPONSE_KEYS) : decodePairingResponse(value, "daemonToken", CLAIM_RESPONSE_KEYS);
27079
27463
  }
27080
- async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWorkspace) {
27081
- const connectionsDirectory = join14(readStateDirectory(), "connections");
27464
+ async function persistConnection(channelId, relayOrigin, runtimeCredential, defaultWorkspace, environment) {
27465
+ const connectionsDirectory = join14(readStateDirectory(environment), "connections");
27082
27466
  await mkdir4(connectionsDirectory, { mode: 448, recursive: true });
27083
27467
  const connectionPath = join14(connectionsDirectory, `${channelId}.json`);
27084
27468
  const temporaryPath = join14(connectionsDirectory, `.${channelId}.${randomUUID3()}.tmp`);
@@ -27088,8 +27472,8 @@ async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWo
27088
27472
  {
27089
27473
  defaultWorkspace,
27090
27474
  channelId,
27091
- daemonToken: daemonToken2,
27092
- relayOrigin: relayOrigin.toString()
27475
+ relayOrigin: relayOrigin.toString(),
27476
+ runtimeCredential
27093
27477
  },
27094
27478
  null,
27095
27479
  2
@@ -27101,25 +27485,25 @@ async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWo
27101
27485
  return connectionPath;
27102
27486
  }
27103
27487
  function decodeSavedConnection(value, expectedChannelId) {
27104
- if (!isRecord10(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS)) {
27488
+ if (!isRecord11(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS) && !hasExactKeys(value, LEGACY_SAVED_CONNECTION_KEYS)) {
27105
27489
  throw new PairingClientError("invalid_connection", "The saved connection is invalid");
27106
27490
  }
27107
27491
  const defaultWorkspace = value["defaultWorkspace"];
27108
27492
  const channelId = value["channelId"];
27109
- const daemonToken2 = value["daemonToken"];
27493
+ const runtimeCredential = value["runtimeCredential"] ?? value["daemonToken"];
27110
27494
  const relayOrigin = value["relayOrigin"];
27111
- if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN6.test(channelId) || typeof daemonToken2 !== "string" || !TOKEN_PATTERN2.test(daemonToken2) || typeof relayOrigin !== "string") {
27495
+ if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN6.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof relayOrigin !== "string") {
27112
27496
  throw new PairingClientError("invalid_connection", "The saved connection is invalid");
27113
27497
  }
27114
27498
  return {
27115
27499
  defaultWorkspace,
27116
27500
  channelId,
27117
- daemonToken: daemonToken2,
27118
- relayOrigin: parseRelayOrigin(relayOrigin)
27501
+ relayOrigin: parseRelayOrigin(relayOrigin),
27502
+ runtimeCredential
27119
27503
  };
27120
27504
  }
27121
- async function readSavedConnection(channelId) {
27122
- const connectionPath = join14(readStateDirectory(), "connections", `${channelId}.json`);
27505
+ async function readSavedConnection(channelId, environment) {
27506
+ const connectionPath = join14(readStateDirectory(environment), "connections", `${channelId}.json`);
27123
27507
  let encoded = "";
27124
27508
  const handle = await open4(connectionPath, "r");
27125
27509
  try {
@@ -27141,8 +27525,8 @@ async function readSavedConnection(channelId) {
27141
27525
  return {
27142
27526
  defaultWorkspace: await resolveDefaultWorkspace(connection.defaultWorkspace),
27143
27527
  channelId: connection.channelId,
27144
- daemonToken: connection.daemonToken,
27145
- relayOrigin: connection.relayOrigin
27528
+ relayOrigin: connection.relayOrigin,
27529
+ runtimeCredential: connection.runtimeCredential
27146
27530
  };
27147
27531
  }
27148
27532
  function assertInventoryUsable(inventory) {
@@ -27150,15 +27534,12 @@ function assertInventoryUsable(inventory) {
27150
27534
  throw new PairingClientError("no_supported_agent", "No supported Agent is available for local discovery");
27151
27535
  }
27152
27536
  }
27153
- function buildPairedDaemon(connection, connectionPath, inventory) {
27537
+ function buildPairedDaemon(connection, connectionPath, inventory, environment) {
27154
27538
  assertInventoryUsable(inventory);
27155
- const relayUrl = mobiusApiUrl(
27156
- connection.relayOrigin,
27157
- `/channels/${encodeURIComponent(connection.channelId)}/daemon`
27158
- );
27539
+ const relayUrl = mobiusApiUrl(connection.relayOrigin, "/v1/runtimes/connect");
27159
27540
  relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
27160
- relayUrl.searchParams.set("token", connection.daemonToken);
27161
- const auditDatabasePath = join14(readStateDirectory(), "audit", `${connection.channelId}.sqlite`);
27541
+ relayUrl.searchParams.set("token", connection.runtimeCredential);
27542
+ const auditDatabasePath = join14(readStateDirectory(environment), "audit", `${connection.channelId}.sqlite`);
27162
27543
  return {
27163
27544
  channelUrl: new URL(`/channels/${encodeURIComponent(connection.channelId)}`, connection.relayOrigin).toString(),
27164
27545
  config: createDaemonConfig(
@@ -27174,12 +27555,11 @@ function buildPairedDaemon(connection, connectionPath, inventory) {
27174
27555
  connectionPath
27175
27556
  };
27176
27557
  }
27177
- async function pairLocalMachine(arguments_, signal = new AbortController().signal) {
27178
- const options = parsePairCommand(arguments_);
27558
+ async function pairRuntime(options, signal, environment) {
27179
27559
  const defaultWorkspace = await resolveDefaultWorkspace(options.workspace);
27180
27560
  const inventory = await discoverLocalAgentInventory({
27181
27561
  defaultWorkspace,
27182
- environment: process.env,
27562
+ environment,
27183
27563
  signal
27184
27564
  });
27185
27565
  assertInventoryUsable(inventory);
@@ -27188,35 +27568,55 @@ async function pairLocalMachine(arguments_, signal = new AbortController().signa
27188
27568
  const connectionPath = await persistConnection(
27189
27569
  claim2.channelId,
27190
27570
  options.relayOrigin,
27191
- claim2.daemonToken,
27192
- defaultWorkspace
27571
+ claim2.runtimeCredential,
27572
+ defaultWorkspace,
27573
+ environment
27193
27574
  );
27194
27575
  return buildPairedDaemon(
27195
27576
  {
27196
27577
  defaultWorkspace,
27197
27578
  channelId: claim2.channelId,
27198
- daemonToken: claim2.daemonToken,
27199
- relayOrigin: options.relayOrigin
27579
+ relayOrigin: options.relayOrigin,
27580
+ runtimeCredential: claim2.runtimeCredential
27200
27581
  },
27201
27582
  connectionPath,
27202
- inventory
27583
+ inventory,
27584
+ environment
27203
27585
  );
27204
27586
  }
27205
- async function resumeLocalMachine(arguments_, signal = new AbortController().signal) {
27206
- const channelId = parseChannelArgument(arguments_);
27207
- const connectionPath = join14(readStateDirectory(), "connections", `${channelId}.json`);
27208
- const connection = await readSavedConnection(channelId);
27587
+ async function pairMobiusRuntime(options, signal, environment = process.env) {
27588
+ return await pairRuntime(
27589
+ {
27590
+ code: parsePairingCode(options.pairingCode),
27591
+ pairingId: validatePairingId(options.pairingId),
27592
+ relayOrigin: parseRelayOrigin(options.relayOrigin),
27593
+ workspace: options.defaultWorkspace
27594
+ },
27595
+ signal,
27596
+ environment
27597
+ );
27598
+ }
27599
+ async function pairLocalMachine(arguments_, signal = new AbortController().signal) {
27600
+ return await pairRuntime(parsePairCommand(arguments_), signal, process.env);
27601
+ }
27602
+ async function resumeMobiusRuntime(channelId, signal, environment = process.env) {
27603
+ const normalizedChannelId = validatePairingId(channelId);
27604
+ const connectionPath = join14(readStateDirectory(environment), "connections", `${normalizedChannelId}.json`);
27605
+ const connection = await readSavedConnection(normalizedChannelId, environment);
27209
27606
  const inventory = await discoverLocalAgentInventory({
27210
27607
  defaultWorkspace: connection.defaultWorkspace,
27211
- environment: process.env,
27608
+ environment,
27212
27609
  signal
27213
27610
  });
27214
- return buildPairedDaemon(connection, connectionPath, inventory);
27611
+ return buildPairedDaemon(connection, connectionPath, inventory, environment);
27612
+ }
27613
+ async function resumeLocalMachine(arguments_, signal = new AbortController().signal) {
27614
+ return await resumeMobiusRuntime(parseChannelArgument(arguments_), signal, process.env);
27215
27615
  }
27216
27616
 
27217
27617
  // src/run-daemon.ts
27218
27618
  import { randomUUID as randomUUID6 } from "node:crypto";
27219
- import { basename as basename3, join as join16 } from "node:path";
27619
+ import { basename as basename4, join as join16 } from "node:path";
27220
27620
  import { styleText } from "node:util";
27221
27621
  import { fetch as fetch6, WebSocket as WebSocket2 } from "undici";
27222
27622
 
@@ -27230,14 +27630,19 @@ var AgentCatalogPublishError = class extends Error {
27230
27630
  this.name = "AgentCatalogPublishError";
27231
27631
  }
27232
27632
  };
27233
- function catalogEndpoint(relayUrl) {
27234
- const endpoint = new URL(relayUrl);
27633
+ function catalogEndpoint(config2) {
27634
+ const endpoint = new URL(config2.relayUrl);
27235
27635
  endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
27236
- if (!endpoint.pathname.endsWith("/daemon")) {
27237
- throw new AgentCatalogPublishError("The Relay URL is invalid");
27636
+ if (endpoint.pathname.endsWith("/daemon")) {
27637
+ endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/catalog`;
27638
+ return endpoint;
27238
27639
  }
27239
- endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/catalog`;
27240
- return endpoint;
27640
+ const runtimePath = "/v1/runtimes/connect";
27641
+ if (endpoint.pathname.endsWith(runtimePath)) {
27642
+ endpoint.pathname = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}/catalog`;
27643
+ return endpoint;
27644
+ }
27645
+ throw new AgentCatalogPublishError("The Relay URL is invalid");
27241
27646
  }
27242
27647
  async function publishAgentCatalog(config2, signal) {
27243
27648
  signal.throwIfAborted();
@@ -27246,7 +27651,7 @@ async function publishAgentCatalog(config2, signal) {
27246
27651
  let response;
27247
27652
  const catalog = config2.catalog.read();
27248
27653
  try {
27249
- response = await fetch4(catalogEndpoint(config2.relayUrl), {
27654
+ response = await fetch4(catalogEndpoint(config2), {
27250
27655
  body: JSON.stringify({
27251
27656
  agents: catalog.agents.map((agent) => ({
27252
27657
  agentPath: agent.agentIdentity.requestedPath,
@@ -27929,6 +28334,7 @@ var EmbeddedBotContextApplicator = class {
27929
28334
 
27930
28335
  // src/native-sessions/service.ts
27931
28336
  import { randomUUID as randomUUID4 } from "node:crypto";
28337
+ import { basename as basename3 } from "node:path";
27932
28338
 
27933
28339
  // src/native-sessions/codex-history.ts
27934
28340
  import { spawn as spawn4 } from "node:child_process";
@@ -28406,16 +28812,84 @@ var NativeSessionService = class {
28406
28812
  history: opened.history
28407
28813
  };
28408
28814
  };
28409
- authorizeEntry = async (command, options, info) => {
28410
- try {
28411
- const primary = await this.#directories.identify(command.bindingId, info.cwd, options.signal);
28412
- const additionalDirectories = [];
28413
- const additionalEntries = [];
28414
- const paths = /* @__PURE__ */ new Set([primary.path]);
28415
- for (const path2 of info.additionalDirectories) {
28416
- const directory = await this.#directories.identify(command.bindingId, path2, options.signal);
28417
- if (paths.has(directory.path)) {
28418
- throw new LocalAuthorizationError("Native session roots must be distinct");
28815
+ executeTurnFork = async (command, signal) => {
28816
+ signal.throwIfAborted();
28817
+ const cwd = await this.#directories.resolve(this.#config.bindingId, command.directoryId, signal);
28818
+ const additionalDirectories = [];
28819
+ const additionalEntries = [];
28820
+ const resolvedPaths = /* @__PURE__ */ new Set([cwd]);
28821
+ for (const directoryId2 of command.additionalDirectoryIds) {
28822
+ const path2 = await this.#directories.resolve(this.#config.bindingId, directoryId2, signal);
28823
+ if (resolvedPaths.has(path2)) {
28824
+ throw new LocalAuthorizationError("Workspace directories must resolve to distinct locations");
28825
+ }
28826
+ resolvedPaths.add(path2);
28827
+ additionalDirectories.push(path2);
28828
+ additionalEntries.push({ directoryId: directoryId2, name: basename3(path2) });
28829
+ }
28830
+ const listing = await this.#directories.list(this.#config.bindingId, command.directoryId, signal);
28831
+ const opened = await this.#sessions.forkSessionAtTurn(
28832
+ {
28833
+ additionalDirectories,
28834
+ agent: this.#config.catalog.find(command.agentId),
28835
+ cwd,
28836
+ sessionId: command.targetSessionId,
28837
+ signal
28838
+ },
28839
+ command.sessionId,
28840
+ {
28841
+ messageFingerprint: command.messageFingerprint,
28842
+ messageId: command.messageId,
28843
+ messageOccurrence: command.messageOccurrence
28844
+ }
28845
+ );
28846
+ signal.throwIfAborted();
28847
+ const ready = {
28848
+ additionalDirectories: additionalEntries,
28849
+ agentId: command.agentId,
28850
+ agentName: opened.prepared.agentName,
28851
+ agentVersion: opened.prepared.agentVersion,
28852
+ capabilities: opened.prepared.capabilities,
28853
+ commands: opened.prepared.commands,
28854
+ configOptions: opened.prepared.configOptions,
28855
+ directoryId: command.directoryId,
28856
+ directoryLabel: listing.directoryLabel,
28857
+ kind: "session.ready",
28858
+ requestId: command.requestId,
28859
+ restored: true,
28860
+ sessionId: opened.sessionId
28861
+ };
28862
+ this.#audit.record({
28863
+ commandId: command.requestId,
28864
+ details: {
28865
+ agentId: command.agentId,
28866
+ messageId: command.messageId,
28867
+ sourceCommandId: command.sourceCommandId
28868
+ },
28869
+ event: "native_session_turn_forked",
28870
+ sessionId: command.sessionId
28871
+ });
28872
+ return {
28873
+ action: "fork",
28874
+ history: opened.history,
28875
+ kind: "native.session.result",
28876
+ ready,
28877
+ requestId: command.requestId,
28878
+ sessionId: command.sessionId,
28879
+ targetSessionId: command.targetSessionId,
28880
+ title: command.title
28881
+ };
28882
+ };
28883
+ authorizeEntry = async (command, options, info) => {
28884
+ try {
28885
+ const primary = await this.#directories.identify(command.bindingId, info.cwd, options.signal);
28886
+ const additionalDirectories = [];
28887
+ const additionalEntries = [];
28888
+ const paths = /* @__PURE__ */ new Set([primary.path]);
28889
+ for (const path2 of info.additionalDirectories) {
28890
+ const directory = await this.#directories.identify(command.bindingId, path2, options.signal);
28891
+ if (paths.has(directory.path)) {
28892
+ throw new LocalAuthorizationError("Native session roots must be distinct");
28419
28893
  }
28420
28894
  paths.add(directory.path);
28421
28895
  additionalDirectories.push(directory.path);
@@ -28547,10 +29021,17 @@ function parseReference(resource) {
28547
29021
  function downloadUrl(config2, command, reference) {
28548
29022
  const url2 = new URL(config2.relayUrl);
28549
29023
  url2.protocol = url2.protocol === "wss:" ? "https:" : "http:";
28550
- if (!url2.pathname.endsWith("/daemon")) {
28551
- throw new PromptArtifactError("artifact_endpoint_invalid", "The Relay artifact endpoint is invalid");
29024
+ let channelPath = "";
29025
+ if (url2.pathname.endsWith("/daemon")) {
29026
+ channelPath = url2.pathname.slice(0, -"/daemon".length);
29027
+ } else {
29028
+ const runtimePath = "/v1/runtimes/connect";
29029
+ if (!url2.pathname.endsWith(runtimePath)) {
29030
+ throw new PromptArtifactError("artifact_endpoint_invalid", "The Relay artifact endpoint is invalid");
29031
+ }
29032
+ channelPath = `${url2.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
28552
29033
  }
28553
- url2.pathname = `${url2.pathname.slice(0, -"/daemon".length)}/sessions/${encodeURIComponent(command.sessionId)}/commands/${encodeURIComponent(command.commandId)}/artifacts/${encodeURIComponent(reference.artifactId)}/content`;
29034
+ url2.pathname = `${channelPath}/sessions/${encodeURIComponent(command.sessionId)}/commands/${encodeURIComponent(command.commandId)}/artifacts/${encodeURIComponent(reference.artifactId)}/content`;
28554
29035
  url2.searchParams.delete("liveness");
28555
29036
  return url2;
28556
29037
  }
@@ -28708,9 +29189,17 @@ var RelayConnectionError = class extends Error {
28708
29189
  };
28709
29190
  function connectRelay(url2, signal) {
28710
29191
  signal.throwIfAborted();
28711
- return new Promise((resolve5, reject) => {
28712
- const socket = new WebSocket(url2, {
28713
- dispatcher: environmentProxyDispatcher
29192
+ return new Promise((resolve6, reject) => {
29193
+ const endpoint = new URL(url2);
29194
+ const headers = {};
29195
+ if (endpoint.pathname.endsWith("/v1/runtimes/connect")) {
29196
+ const runtimeCredential = endpoint.searchParams.get("token") ?? "";
29197
+ endpoint.searchParams.delete("token");
29198
+ headers["authorization"] = `Bearer ${runtimeCredential}`;
29199
+ }
29200
+ const socket = new WebSocket(endpoint, {
29201
+ dispatcher: environmentProxyDispatcher,
29202
+ headers
28714
29203
  });
28715
29204
  let finished = false;
28716
29205
  const timeout = setTimeout(onTimeout, CONNECTION_TIMEOUT_MILLISECONDS);
@@ -28749,7 +29238,7 @@ function connectRelay(url2, signal) {
28749
29238
  }
28750
29239
  finished = true;
28751
29240
  cleanup();
28752
- resolve5(socket);
29241
+ resolve6(socket);
28753
29242
  };
28754
29243
  function onTimeout() {
28755
29244
  fail(new RelayConnectionError("relay_connection_timeout", "Relay connection timed out"));
@@ -28970,6 +29459,11 @@ var SessionEventOutbox = class {
28970
29459
  }
28971
29460
  this.#database.prepare(`UPDATE relay_turn_receipts SET state = ? WHERE command_id = ?`).run(state, commandId);
28972
29461
  };
29462
+ isTerminalExecution = (commandId, sessionId) => {
29463
+ const row = this.#database.prepare(`SELECT state FROM relay_turn_receipts WHERE command_id = ? AND session_id = ?`).get(commandId, sessionId);
29464
+ const state = typeof row === "object" && row !== null ? Reflect.get(row, "state") : void 0;
29465
+ return state === "succeeded" || state === "failed" || state === "cancelled";
29466
+ };
28973
29467
  acknowledge = (acknowledgement) => {
28974
29468
  this.#database.prepare(
28975
29469
  `DELETE FROM relay_session_event_outbox
@@ -29326,10 +29820,17 @@ var ActiveTurnReconciliationError = class extends Error {
29326
29820
  function activeTurnReconciliationEndpoint(config2, turn) {
29327
29821
  const endpoint = new URL(config2.relayUrl);
29328
29822
  endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
29329
- if (!endpoint.pathname.endsWith("/daemon")) {
29330
- throw new ActiveTurnReconciliationError("The Relay URL is invalid");
29823
+ let channelPath = "";
29824
+ if (endpoint.pathname.endsWith("/daemon")) {
29825
+ channelPath = endpoint.pathname.slice(0, -"/daemon".length);
29826
+ } else {
29827
+ const runtimePath = "/v1/runtimes/connect";
29828
+ if (!endpoint.pathname.endsWith(runtimePath)) {
29829
+ throw new ActiveTurnReconciliationError("The Relay URL is invalid");
29830
+ }
29831
+ channelPath = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
29331
29832
  }
29332
- endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
29833
+ endpoint.pathname = `${channelPath}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
29333
29834
  endpoint.searchParams.delete("liveness");
29334
29835
  return endpoint;
29335
29836
  }
@@ -29482,7 +29983,7 @@ async function resolveSessionDirectories(directories, bindingId, directoryId2, a
29482
29983
  additionalDirectories.push(directory);
29483
29984
  additionalDirectoryEntries.push({
29484
29985
  directoryId: additionalDirectoryId,
29485
- name: basename3(directory)
29986
+ name: basename4(directory)
29486
29987
  });
29487
29988
  }
29488
29989
  return new ResolvedSessionDirectories(cwd, additionalDirectories, additionalDirectoryEntries);
@@ -29631,68 +30132,96 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
29631
30132
  attemptId
29632
30133
  );
29633
30134
  const attachments = await resolvePromptArtifacts(config2, command, signal);
29634
- const result = await sessions.runPrompt({
29635
- additionalDirectories: resolved.additionalDirectories,
29636
- agent,
29637
- commandId: command.commandId,
29638
- cwd: resolved.cwd,
29639
- images: attachments.images,
29640
- onActivity: (activity) => {
29641
- publisher.recordAttempt(
29642
- command.sessionId,
29643
- encodeAgentActivityEvent({
29644
- activityId: activity.activityId,
29645
- activityType: activity.activityType,
29646
- commandId: command.commandId,
29647
- content: activity.content,
29648
- detail: activity.detail,
29649
- input: activity.input,
29650
- kind: AGENT_ACTIVITY_KIND,
29651
- locations: activity.locations,
29652
- name: activity.name,
29653
- output: activity.output,
29654
- sequence: activity.sequence,
29655
- sessionId: command.sessionId,
29656
- status: activity.status,
29657
- title: activity.title,
29658
- toolKind: activity.toolKind
29659
- }),
29660
- attemptId
29661
- );
29662
- return Promise.resolve();
29663
- },
29664
- onText: (text) => {
29665
- publisher.recordAttempt(
29666
- command.sessionId,
29667
- encodeAgentMessageDeltaEvent({
29668
- commandId: command.commandId,
29669
- kind: AGENT_MESSAGE_DELTA_KIND,
29670
- sessionId: command.sessionId,
29671
- text
29672
- }),
29673
- attemptId
30135
+ let stopReason;
30136
+ if (command.disposition === SessionPromptDisposition.Steer) {
30137
+ await sessions.steerPrompt({
30138
+ additionalDirectories: resolved.additionalDirectories,
30139
+ agent,
30140
+ commandId: command.commandId,
30141
+ cwd: resolved.cwd,
30142
+ images: attachments.images,
30143
+ prompt: command.prompt,
30144
+ resources: attachments.resources,
30145
+ sessionId: command.sessionId,
30146
+ signal,
30147
+ targetCommandId: command.targetCommandId
30148
+ });
30149
+ stopReason = "steered";
30150
+ } else {
30151
+ if (command.disposition === SessionPromptDisposition.Queue && !outbox.isTerminalExecution(command.targetCommandId, command.sessionId)) {
30152
+ throw new AcpExecutionError(
30153
+ "The queued prompt target is unavailable",
30154
+ {},
30155
+ "prompt_queue_target_unavailable"
29674
30156
  );
29675
- return Promise.resolve();
29676
- },
29677
- prompt: command.prompt,
29678
- resources: attachments.resources,
29679
- sessionId: command.sessionId,
29680
- signal
29681
- });
30157
+ }
30158
+ const result = await sessions.runPrompt({
30159
+ additionalDirectories: resolved.additionalDirectories,
30160
+ agent,
30161
+ commandId: command.commandId,
30162
+ cwd: resolved.cwd,
30163
+ disposition: command.disposition,
30164
+ images: attachments.images,
30165
+ onActivity: (activity) => {
30166
+ publisher.recordAttempt(
30167
+ command.sessionId,
30168
+ encodeAgentActivityEvent({
30169
+ activityId: activity.activityId,
30170
+ activityType: activity.activityType,
30171
+ commandId: command.commandId,
30172
+ content: activity.content,
30173
+ detail: activity.detail,
30174
+ input: activity.input,
30175
+ kind: AGENT_ACTIVITY_KIND,
30176
+ locations: activity.locations,
30177
+ name: activity.name,
30178
+ output: activity.output,
30179
+ sequence: activity.sequence,
30180
+ sessionId: command.sessionId,
30181
+ status: activity.status,
30182
+ title: activity.title,
30183
+ toolKind: activity.toolKind
30184
+ }),
30185
+ attemptId
30186
+ );
30187
+ return Promise.resolve();
30188
+ },
30189
+ onText: (text, messageId) => {
30190
+ publisher.recordAttempt(
30191
+ command.sessionId,
30192
+ encodeAgentMessageDeltaEvent({
30193
+ commandId: command.commandId,
30194
+ kind: AGENT_MESSAGE_DELTA_KIND,
30195
+ messageId,
30196
+ sessionId: command.sessionId,
30197
+ text
30198
+ }),
30199
+ attemptId
30200
+ );
30201
+ return Promise.resolve();
30202
+ },
30203
+ prompt: command.prompt,
30204
+ resources: attachments.resources,
30205
+ sessionId: command.sessionId,
30206
+ signal,
30207
+ targetCommandId: command.targetCommandId
30208
+ });
30209
+ stopReason = result.stopReason;
30210
+ }
29682
30211
  publisher.recordAttempt(
29683
30212
  command.sessionId,
29684
30213
  encodeSessionCompletedEvent({
29685
30214
  commandId: command.commandId,
29686
30215
  kind: SESSION_COMPLETED_KIND,
29687
30216
  sessionId: command.sessionId,
29688
- stopReason: result.stopReason
30217
+ stopReason
29689
30218
  }),
29690
30219
  attemptId
29691
30220
  );
29692
- outbox.transitionExecution(command.commandId, result.stopReason === "cancelled" ? "cancelled" : "succeeded");
30221
+ outbox.transitionExecution(command.commandId, stopReason === "cancelled" ? "cancelled" : "succeeded");
29693
30222
  audit.record({
29694
30223
  commandId: command.commandId,
29695
- event: result.stopReason === "cancelled" ? "session_cancelled" : "session_completed",
30224
+ event: stopReason === "cancelled" ? "session_cancelled" : "session_completed",
29696
30225
  sessionId: command.sessionId
29697
30226
  });
29698
30227
  } catch (error61) {
@@ -29736,12 +30265,6 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
29736
30265
  }
29737
30266
  async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, signal) {
29738
30267
  try {
29739
- if (command.additionalDirectoryIds.length > 0) {
29740
- throw new LocalBotAuthorizationError(
29741
- "binding_not_approved",
29742
- "The Bot execution requested unapproved additional directories"
29743
- );
29744
- }
29745
30268
  const deadline = Date.parse(command.executionDeadlineAt);
29746
30269
  const remaining = deadline - Date.now();
29747
30270
  if (!Number.isSafeInteger(deadline) || remaining <= 0) {
@@ -29755,6 +30278,32 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29755
30278
  Date.now(),
29756
30279
  signal
29757
30280
  );
30281
+ const requestedExecutionTargetOverridden = command.directoryId !== grant.workspaceRef || command.additionalDirectoryIds.length > 0;
30282
+ if (requestedExecutionTargetOverridden && !(command.replyRoute instanceof OwnerBotReplyRoute)) {
30283
+ throw new LocalBotAuthorizationError(
30284
+ "binding_not_approved",
30285
+ "Only a paired owner conversation can override the approved Bot workspace"
30286
+ );
30287
+ }
30288
+ const resolved = await resolveSessionDirectories(
30289
+ directories,
30290
+ config2.bindingId,
30291
+ command.directoryId,
30292
+ command.additionalDirectoryIds,
30293
+ signal
30294
+ );
30295
+ const executionTargetOverridden = resolved.cwd !== grant.workspacePath || resolved.additionalDirectories.length > 0;
30296
+ if (executionTargetOverridden && !sessions.hasPreparedSession(
30297
+ command.sessionId,
30298
+ grant.agent.id,
30299
+ resolved.cwd,
30300
+ resolved.additionalDirectories
30301
+ )) {
30302
+ throw new LocalBotAuthorizationError(
30303
+ "local_approval_required",
30304
+ "Open this Bot conversation in the paired local Web app before using the selected workspace"
30305
+ );
30306
+ }
29758
30307
  const attachments = await resolvePromptArtifacts(config2, command, signal);
29759
30308
  const applied = await applicator.apply(command, grant, attachments.resources, signal);
29760
30309
  const lease = contexts.activate(command);
@@ -29762,6 +30311,13 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29762
30311
  const deadlineSignal = AbortSignal.timeout(remaining);
29763
30312
  const executionSignal = AbortSignal.any([signal, deadlineSignal]);
29764
30313
  const agent = botExecutionAgent(grant.agent, lease.context.contextHandle, ipc.socketPath);
30314
+ await sessions.prepareExecutionSession({
30315
+ additionalDirectories: resolved.additionalDirectories,
30316
+ agent,
30317
+ cwd: resolved.cwd,
30318
+ sessionId: command.sessionId,
30319
+ signal: executionSignal
30320
+ });
29765
30321
  audit.record({
29766
30322
  commandId: command.commandId,
29767
30323
  details: {
@@ -29786,10 +30342,10 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29786
30342
  attemptId
29787
30343
  );
29788
30344
  const result = await sessions.runPrompt({
29789
- additionalDirectories: [],
30345
+ additionalDirectories: resolved.additionalDirectories,
29790
30346
  agent,
29791
30347
  commandId: command.commandId,
29792
- cwd: grant.workspacePath,
30348
+ cwd: resolved.cwd,
29793
30349
  images: attachments.images,
29794
30350
  onActivity: (activity) => {
29795
30351
  publisher.recordAttempt(
@@ -29815,12 +30371,13 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29815
30371
  );
29816
30372
  return Promise.resolve();
29817
30373
  },
29818
- onText: (text) => {
30374
+ onText: (text, messageId) => {
29819
30375
  publisher.recordAttempt(
29820
30376
  command.sessionId,
29821
30377
  encodeAgentMessageDeltaEvent({
29822
30378
  commandId: command.commandId,
29823
30379
  kind: AGENT_MESSAGE_DELTA_KIND,
30380
+ messageId,
29824
30381
  sessionId: command.sessionId,
29825
30382
  text
29826
30383
  }),
@@ -29886,12 +30443,12 @@ function waitForRelayRetry(signal, timer) {
29886
30443
  if (signal.aborted) {
29887
30444
  return Promise.resolve();
29888
30445
  }
29889
- return new Promise((resolve5) => {
30446
+ return new Promise((resolve6) => {
29890
30447
  const retry = timer.schedule(finish, RELAY_RECONNECT_DELAY_MILLISECONDS);
29891
30448
  function finish() {
29892
30449
  retry.cancel();
29893
30450
  signal.removeEventListener("abort", finish);
29894
- resolve5();
30451
+ resolve6();
29895
30452
  }
29896
30453
  signal.addEventListener("abort", finish, { once: true });
29897
30454
  });
@@ -29928,7 +30485,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
29928
30485
  const nativeRequests = /* @__PURE__ */ new Map();
29929
30486
  installationObserver.connect(socket);
29930
30487
  try {
29931
- await new Promise((resolve5, reject) => {
30488
+ await new Promise((resolve6, reject) => {
29932
30489
  let finished = false;
29933
30490
  const cleanup = () => {
29934
30491
  connectionRefresh.close();
@@ -29947,7 +30504,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
29947
30504
  }
29948
30505
  finished = true;
29949
30506
  cleanup();
29950
- resolve5();
30507
+ resolve6();
29951
30508
  };
29952
30509
  const rejectConnection = () => {
29953
30510
  if (finished) {
@@ -30127,7 +30684,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30127
30684
  try {
30128
30685
  operationSignal.throwIfAborted();
30129
30686
  if (previous.length > 0) {
30130
- await new Promise((resolve6, reject2) => {
30687
+ await new Promise((resolve7, reject2) => {
30131
30688
  const cancel = () => reject2(operationSignal.reason);
30132
30689
  operationSignal.addEventListener("abort", cancel, {
30133
30690
  once: true
@@ -30135,7 +30692,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30135
30692
  void Promise.all(previous.map((request) => request.completion)).then(
30136
30693
  () => {
30137
30694
  operationSignal.removeEventListener("abort", cancel);
30138
- resolve6();
30695
+ resolve7();
30139
30696
  },
30140
30697
  (error61) => {
30141
30698
  operationSignal.removeEventListener("abort", cancel);
@@ -30175,6 +30732,86 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30175
30732
  });
30176
30733
  return;
30177
30734
  }
30735
+ if (kind === NATIVE_SESSION_TURN_FORK_KIND) {
30736
+ const command2 = decodeNativeSessionTurnForkCommand(encoded);
30737
+ const previous = [...nativeRequests.values()].filter(
30738
+ (request) => request.agentId === command2.agentId
30739
+ );
30740
+ if (nativeRequests.has(command2.requestId) || nativeRequests.size >= 4 || previous.some((request) => !request.controller.signal.aborted)) {
30741
+ reportOperationFailure(
30742
+ socket,
30743
+ command2.requestId,
30744
+ command2.sessionId,
30745
+ new AcpExecutionError(
30746
+ "Another native session operation is running",
30747
+ {},
30748
+ "native_session_busy"
30749
+ )
30750
+ );
30751
+ return;
30752
+ }
30753
+ const controller = new AbortController();
30754
+ const operationSignal = AbortSignal.any([connectionController.signal, controller.signal]);
30755
+ const timeout = timer.schedule(
30756
+ () => controller.abort(
30757
+ new AcpExecutionError(
30758
+ "Native session operation timed out",
30759
+ {},
30760
+ "native_session_timeout"
30761
+ )
30762
+ ),
30763
+ 9e4
30764
+ );
30765
+ const completion = (async () => {
30766
+ try {
30767
+ operationSignal.throwIfAborted();
30768
+ if (previous.length > 0) {
30769
+ await new Promise((resolve7, reject2) => {
30770
+ const cancel = () => reject2(operationSignal.reason);
30771
+ operationSignal.addEventListener("abort", cancel, { once: true });
30772
+ void Promise.all(previous.map((request) => request.completion)).then(
30773
+ () => {
30774
+ operationSignal.removeEventListener("abort", cancel);
30775
+ resolve7();
30776
+ },
30777
+ (error61) => {
30778
+ operationSignal.removeEventListener("abort", cancel);
30779
+ reject2(error61);
30780
+ }
30781
+ );
30782
+ });
30783
+ }
30784
+ operationSignal.throwIfAborted();
30785
+ const result = await nativeSessions.executeTurnFork(command2, operationSignal);
30786
+ if (socket.readyState === WebSocket2.OPEN) {
30787
+ send(socket, encodeNativeSessionResult(result));
30788
+ }
30789
+ } catch (error61) {
30790
+ const failure = operationSignal.aborted ? controller.signal.reason ?? new AcpExecutionError(
30791
+ "Native session operation cancelled",
30792
+ {},
30793
+ "native_session_cancelled"
30794
+ ) : error61;
30795
+ audit.record({
30796
+ commandId: command2.requestId,
30797
+ details: { code: operationErrorCode(failure) },
30798
+ event: "native_session_turn_fork_failed",
30799
+ sessionId: command2.sessionId
30800
+ });
30801
+ reportOperationFailure(socket, command2.requestId, command2.sessionId, failure);
30802
+ } finally {
30803
+ timeout.cancel();
30804
+ nativeRequests.delete(command2.requestId);
30805
+ }
30806
+ })();
30807
+ nativeRequests.set(command2.requestId, {
30808
+ agentId: command2.agentId,
30809
+ completion,
30810
+ controller,
30811
+ sessionId: command2.sessionId
30812
+ });
30813
+ return;
30814
+ }
30178
30815
  if (kind === RUNTIME_LIVENESS_READY_KIND) {
30179
30816
  decodeRuntimeLivenessReadyEvent(encoded);
30180
30817
  connectionLiveness.start();
@@ -30328,7 +30965,9 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30328
30965
  }
30329
30966
  const turn = new ActiveDaemonTurn(command, receipt.attemptId);
30330
30967
  activeTurns.set(command.commandId, turn);
30331
- activeAttemptIds.set(command.sessionId, receipt.attemptId);
30968
+ if (command.disposition !== SessionPromptDisposition.Steer) {
30969
+ activeAttemptIds.set(command.sessionId, receipt.attemptId);
30970
+ }
30332
30971
  void executePrompt(
30333
30972
  outbox,
30334
30973
  publisher,
@@ -30515,6 +31154,147 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
30515
31154
  }
30516
31155
  }
30517
31156
  }
31157
+
31158
+ // src/runtime.ts
31159
+ import { mkdir as mkdir5, realpath as realpath8, stat as stat8 } from "node:fs/promises";
31160
+ import { dirname as dirname8, join as join17, resolve as resolve5 } from "node:path";
31161
+ var APP_CREDENTIAL_PATTERN = /^mapp_[a-zA-Z0-9_-]{22}_[a-zA-Z0-9_-]{43}$/;
31162
+ var CONVERSATION_CREDENTIAL_PATTERN = /^mconv_[a-zA-Z0-9_-]{22}_[a-zA-Z0-9_-]{43}$/;
31163
+ var IDENTIFIER_PATTERN8 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
31164
+ var RUNTIME_CREDENTIAL_PATTERN = /^[a-zA-Z0-9_-]{32,256}$/;
31165
+ var MobiusRuntimeConfigurationError = class extends Error {
31166
+ code = "runtime_configuration_invalid";
31167
+ constructor(message, options = {}) {
31168
+ super(message, options);
31169
+ this.name = "MobiusRuntimeConfigurationError";
31170
+ }
31171
+ };
31172
+ var MobiusRuntimeState = class {
31173
+ };
31174
+ var IdleMobiusRuntimeState = class extends MobiusRuntimeState {
31175
+ };
31176
+ var RunningMobiusRuntimeState = class extends MobiusRuntimeState {
31177
+ };
31178
+ var FinishedMobiusRuntimeState = class extends MobiusRuntimeState {
31179
+ };
31180
+ function validateRuntimeRelay(relayUrl) {
31181
+ if (relayUrl.protocol !== "ws:" && relayUrl.protocol !== "wss:") {
31182
+ throw new MobiusRuntimeConfigurationError("The Runtime relay URL must use WebSocket transport");
31183
+ }
31184
+ const credential = relayUrl.searchParams.get("token") ?? "";
31185
+ if (credential.length === 0) {
31186
+ throw new MobiusRuntimeConfigurationError("The Runtime relay credential is required");
31187
+ }
31188
+ if (APP_CREDENTIAL_PATTERN.test(credential)) {
31189
+ throw new MobiusRuntimeConfigurationError("An AppCredential cannot be held by a local Runtime");
31190
+ }
31191
+ if (CONVERSATION_CREDENTIAL_PATTERN.test(credential)) {
31192
+ throw new MobiusRuntimeConfigurationError("A ConversationAccessToken cannot be held by a local Runtime");
31193
+ }
31194
+ if (!RUNTIME_CREDENTIAL_PATTERN.test(credential)) {
31195
+ throw new MobiusRuntimeConfigurationError("The Runtime relay credential is invalid");
31196
+ }
31197
+ }
31198
+ function validateRuntimeConfiguration(config2) {
31199
+ validateRuntimeRelay(config2.relayUrl);
31200
+ }
31201
+ function requiredEnvironmentValue(environment, name) {
31202
+ const value = environment[name]?.trim();
31203
+ if (value === void 0 || value.length === 0) {
31204
+ throw new MobiusRuntimeConfigurationError(`${name} is required`);
31205
+ }
31206
+ return value;
31207
+ }
31208
+ function relayUrlFromConnection(connection) {
31209
+ let relayUrl;
31210
+ try {
31211
+ relayUrl = new URL(connection.relayUrl);
31212
+ } catch (error61) {
31213
+ throw new MobiusRuntimeConfigurationError("The Runtime relay URL is invalid", { cause: error61 });
31214
+ }
31215
+ relayUrl.searchParams.set("token", connection.runtimeCredential);
31216
+ validateRuntimeRelay(relayUrl);
31217
+ return relayUrl;
31218
+ }
31219
+ function loadMobiusRuntimeConnection(environment = process.env) {
31220
+ const channelId = requiredEnvironmentValue(environment, "MOBIUS_CHANNEL_ID");
31221
+ if (!IDENTIFIER_PATTERN8.test(channelId)) {
31222
+ throw new MobiusRuntimeConfigurationError("MOBIUS_CHANNEL_ID is invalid");
31223
+ }
31224
+ return Object.freeze({
31225
+ auditDatabasePath: environment["MOBIUS_AUDIT_DATABASE"]?.trim() || join17(readStateDirectory(environment), "audit", `${channelId}.sqlite`),
31226
+ channelId,
31227
+ defaultWorkspace: loadDefaultWorkspace(environment),
31228
+ relayUrl: requiredEnvironmentValue(environment, "MOBIUS_RELAY_URL"),
31229
+ runtimeCredential: requiredEnvironmentValue(environment, "MOBIUS_RELAY_TOKEN")
31230
+ });
31231
+ }
31232
+ async function resolveWorkspace(path2) {
31233
+ const requested = resolve5(path2);
31234
+ try {
31235
+ await mkdir5(requested, { recursive: true, mode: 448 });
31236
+ const resolved = await realpath8(requested);
31237
+ const metadata = await stat8(resolved);
31238
+ if (!metadata.isDirectory()) {
31239
+ throw new MobiusRuntimeConfigurationError("The Runtime workspace is not a directory");
31240
+ }
31241
+ return resolved;
31242
+ } catch (error61) {
31243
+ if (error61 instanceof MobiusRuntimeConfigurationError) {
31244
+ throw error61;
31245
+ }
31246
+ throw new MobiusRuntimeConfigurationError("The Runtime workspace cannot be resolved", { cause: error61 });
31247
+ }
31248
+ }
31249
+ async function createMobiusRuntimeEngine(connection, signal, environment = process.env) {
31250
+ signal.throwIfAborted();
31251
+ const relayUrl = relayUrlFromConnection(connection);
31252
+ const defaultWorkspace = await resolveWorkspace(connection.defaultWorkspace);
31253
+ const auditDatabasePath = resolve5(connection.auditDatabasePath);
31254
+ await mkdir5(dirname8(auditDatabasePath), { recursive: true, mode: 448 });
31255
+ const inventory = await discoverLocalAgentInventory({
31256
+ defaultWorkspace,
31257
+ environment,
31258
+ signal
31259
+ });
31260
+ if (inventory.available.length + inventory.installable.length === 0) {
31261
+ throw new MobiusRuntimeConfigurationError("No supported Agent is available for the local Runtime");
31262
+ }
31263
+ return new MobiusRuntimeEngine(
31264
+ createDaemonConfig(
31265
+ {
31266
+ auditDatabasePath,
31267
+ channelId: connection.channelId,
31268
+ defaultWorkspace,
31269
+ relayUrl
31270
+ },
31271
+ inventory.readyAgents(),
31272
+ inventory.installable
31273
+ )
31274
+ );
31275
+ }
31276
+ var MobiusRuntimeEngine = class {
31277
+ #config;
31278
+ #state = new IdleMobiusRuntimeState();
31279
+ constructor(config2) {
31280
+ validateRuntimeConfiguration(config2);
31281
+ this.#config = config2;
31282
+ }
31283
+ run = async (signal) => {
31284
+ if (!(this.#state instanceof IdleMobiusRuntimeState)) {
31285
+ throw new MobiusRuntimeConfigurationError("A Mobius Runtime engine can only be started once");
31286
+ }
31287
+ signal.throwIfAborted();
31288
+ this.#state = new RunningMobiusRuntimeState();
31289
+ const audit = new AuditStore(this.#config.auditDatabasePath);
31290
+ try {
31291
+ await runDaemon(this.#config, audit, signal);
31292
+ } finally {
31293
+ audit.close();
31294
+ this.#state = new FinishedMobiusRuntimeState();
31295
+ }
31296
+ };
31297
+ };
30518
31298
  export {
30519
31299
  AcpExecutionError,
30520
31300
  AuditStore,
@@ -30522,12 +31302,18 @@ export {
30522
31302
  BotMessageCommandError,
30523
31303
  BotOutboundIpcServer,
30524
31304
  LocalAuthorizationError,
31305
+ MobiusRuntimeConfigurationError,
31306
+ MobiusRuntimeEngine,
30525
31307
  PairingClientError,
30526
31308
  createAcpClient,
31309
+ createMobiusRuntimeEngine,
30527
31310
  executeBotMessageCommand,
30528
31311
  loadDaemonConfig,
31312
+ loadMobiusRuntimeConnection,
30529
31313
  pairLocalMachine,
31314
+ pairMobiusRuntime,
30530
31315
  resumeLocalMachine,
31316
+ resumeMobiusRuntime,
30531
31317
  runAcpPrompt,
30532
31318
  runDaemon
30533
31319
  };