@moxt-ai/mobius 0.0.22 → 0.0.24

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.
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import { mkdir as mkdir5 } from "node:fs/promises";
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "@moxt-ai/mobius",
14
- version: "0.0.22",
14
+ version: "0.0.24",
15
15
  description: "Mobius CLI for pairing and running local AI agent services.",
16
16
  type: "module",
17
17
  bin: {
@@ -71,6 +71,16 @@ var package_default = {
71
71
  import { realpathSync, statSync } from "node:fs";
72
72
 
73
73
  // src/acp/session-capabilities.ts
74
+ function isRecord(value) {
75
+ return typeof value === "object" && value !== null && !Array.isArray(value);
76
+ }
77
+ function hasTrueChildField(value, childKey, fieldKey) {
78
+ if (!isRecord(value)) {
79
+ return false;
80
+ }
81
+ const child = Reflect.get(value, childKey);
82
+ return isRecord(child) && Reflect.get(child, fieldKey) === true;
83
+ }
74
84
  function unsupportedSessionCapabilities() {
75
85
  return Object.freeze({
76
86
  additionalDirectories: false,
@@ -81,11 +91,14 @@ function unsupportedSessionCapabilities() {
81
91
  imagePrompt: false,
82
92
  listSessions: false,
83
93
  loadSession: false,
94
+ promptQueueing: false,
84
95
  resumeSession: false,
96
+ steering: false,
85
97
  unstableForkSession: false
86
98
  });
87
99
  }
88
- function normalizeAcpCapabilities(capabilities) {
100
+ function normalizeAcpCapabilities(initialized) {
101
+ const capabilities = initialized.agentCapabilities ?? {};
89
102
  const session = capabilities.sessionCapabilities;
90
103
  const prompt = capabilities.promptCapabilities;
91
104
  return Object.freeze({
@@ -97,7 +110,9 @@ function normalizeAcpCapabilities(capabilities) {
97
110
  imagePrompt: prompt?.image === true,
98
111
  listSessions: session?.list != null,
99
112
  loadSession: capabilities.loadSession === true,
113
+ promptQueueing: hasTrueChildField(capabilities._meta, "claudeCode", "promptQueueing"),
100
114
  resumeSession: session?.resume != null,
115
+ steering: hasTrueChildField(initialized._meta, "steering", "supported"),
101
116
  unstableForkSession: session?.fork != null
102
117
  });
103
118
  }
@@ -865,7 +880,7 @@ var ManagedNpmPackageError = class extends Error {
865
880
  this.name = "ManagedNpmPackageError";
866
881
  }
867
882
  };
868
- function isRecord(value) {
883
+ function isRecord2(value) {
869
884
  return typeof value === "object" && value !== null && !Array.isArray(value);
870
885
  }
871
886
  function packagePath(root, packageName) {
@@ -887,7 +902,7 @@ async function readJsonRecord(path2, maximumBytes) {
887
902
  );
888
903
  }
889
904
  const value = JSON.parse(await handle.readFile({ encoding: "utf8" }));
890
- if (!isRecord(value)) {
905
+ if (!isRecord2(value)) {
891
906
  throw new ManagedNpmPackageError(
892
907
  "managed_package_verification_failed",
893
908
  "The managed npm package metadata is invalid"
@@ -913,7 +928,7 @@ function readBinPath(packageMetadata, executableName) {
913
928
  if (typeof bin === "string") {
914
929
  return bin;
915
930
  }
916
- if (isRecord(bin) && typeof bin[executableName] === "string") {
931
+ if (isRecord2(bin) && typeof bin[executableName] === "string") {
917
932
  return bin[executableName];
918
933
  }
919
934
  throw new ManagedNpmPackageError(
@@ -929,7 +944,7 @@ function verifyPackageLockEntry(lock, definition, verifyDependencyClosure) {
929
944
  );
930
945
  }
931
946
  const packages = lock["packages"];
932
- if (!isRecord(packages)) {
947
+ if (!isRecord2(packages)) {
933
948
  throw new ManagedNpmPackageError(
934
949
  "managed_package_verification_failed",
935
950
  "The managed npm package lockfile is invalid"
@@ -943,7 +958,7 @@ function verifyPackageLockEntry(lock, definition, verifyDependencyClosure) {
943
958
  if (verifyDependencyClosure && normalizedKey.length > 0) {
944
959
  let resolved;
945
960
  try {
946
- if (!normalizedKey.split("/").includes("node_modules") || !isRecord(candidate) || typeof candidate["version"] !== "string" || candidate["version"].length === 0 || typeof candidate["integrity"] !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(candidate["integrity"]) || typeof candidate["resolved"] !== "string") {
961
+ if (!normalizedKey.split("/").includes("node_modules") || !isRecord2(candidate) || typeof candidate["version"] !== "string" || candidate["version"].length === 0 || typeof candidate["integrity"] !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(candidate["integrity"]) || typeof candidate["resolved"] !== "string") {
947
962
  throw new Error("Invalid locked dependency");
948
963
  }
949
964
  resolved = new URL(candidate["resolved"]);
@@ -966,7 +981,7 @@ function verifyPackageLockEntry(lock, definition, verifyDependencyClosure) {
966
981
  matches += 1;
967
982
  }
968
983
  }
969
- if (matches !== 1 || !isRecord(entry) || entry["integrity"] !== definition.integrity || entry["resolved"] !== definition.tarballUrl || entry["version"] !== definition.version) {
984
+ if (matches !== 1 || !isRecord2(entry) || entry["integrity"] !== definition.integrity || entry["resolved"] !== definition.tarballUrl || entry["version"] !== definition.version) {
970
985
  throw new ManagedNpmPackageError(
971
986
  "managed_package_verification_failed",
972
987
  "The managed npm package lockfile does not match the pinned artifact"
@@ -1607,7 +1622,7 @@ var ManagedAgentArchiveError = class extends Error {
1607
1622
  this.name = "ManagedAgentArchiveError";
1608
1623
  }
1609
1624
  };
1610
- function isRecord2(value) {
1625
+ function isRecord3(value) {
1611
1626
  return typeof value === "object" && value !== null && !Array.isArray(value);
1612
1627
  }
1613
1628
  function isContainedPath3(root, candidate) {
@@ -1664,7 +1679,7 @@ async function readManifest(path2) {
1664
1679
  );
1665
1680
  }
1666
1681
  const parsed = JSON.parse(await handle.readFile({ encoding: "utf8" }));
1667
- if (!isRecord2(parsed)) {
1682
+ if (!isRecord3(parsed)) {
1668
1683
  throw new ManagedAgentArchiveError(
1669
1684
  "managed_archive_verification_failed",
1670
1685
  "The managed Agent archive manifest is invalid"
@@ -2384,7 +2399,7 @@ async function probeAcpReadiness(specification, agent, environment, defaultWorks
2384
2399
  await specification.verifyAcp(connection, initialized, defaultWorkspace, controller.signal);
2385
2400
  return new VerifiedAcpReadiness(
2386
2401
  normalizedVersion(initialized.agentInfo?.version ?? ""),
2387
- normalizeAcpCapabilities(initialized.agentCapabilities ?? {})
2402
+ normalizeAcpCapabilities(initialized)
2388
2403
  );
2389
2404
  } catch (error61) {
2390
2405
  if (signal.aborted) {
@@ -2624,7 +2639,7 @@ var BotMessageCommandError = class extends Error {
2624
2639
  this.name = "BotMessageCommandError";
2625
2640
  }
2626
2641
  };
2627
- function isRecord3(value) {
2642
+ function isRecord4(value) {
2628
2643
  return typeof value === "object" && value !== null && !Array.isArray(value);
2629
2644
  }
2630
2645
  function requiredEnvironment(environment, name) {
@@ -2675,14 +2690,14 @@ function decodeResponse(encoded) {
2675
2690
  cause
2676
2691
  });
2677
2692
  }
2678
- if (!isRecord3(value) || typeof value["ok"] !== "boolean") {
2693
+ if (!isRecord4(value) || typeof value["ok"] !== "boolean") {
2679
2694
  throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
2680
2695
  }
2681
2696
  if (value["ok"] === true && typeof value["outboundMessageId"] === "string") {
2682
2697
  return value["outboundMessageId"];
2683
2698
  }
2684
2699
  const error61 = value["error"];
2685
- if (value["ok"] === false && isRecord3(error61) && typeof error61["code"] === "string" && typeof error61["message"] === "string") {
2700
+ if (value["ok"] === false && isRecord4(error61) && typeof error61["code"] === "string" && typeof error61["message"] === "string") {
2686
2701
  throw new BotMessageCommandError(error61["code"], error61["message"]);
2687
2702
  }
2688
2703
  throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
@@ -21743,7 +21758,9 @@ var sessionCapabilitiesSchema = external_exports.strictObject({
21743
21758
  imagePrompt: external_exports.boolean(),
21744
21759
  listSessions: external_exports.boolean(),
21745
21760
  loadSession: external_exports.boolean(),
21761
+ promptQueueing: external_exports.boolean().default(false),
21746
21762
  resumeSession: external_exports.boolean(),
21763
+ steering: external_exports.boolean().default(false),
21747
21764
  unstableForkSession: external_exports.boolean().default(false)
21748
21765
  }).readonly();
21749
21766
  var sessionCommandSchema = external_exports.strictObject({
@@ -22085,6 +22102,11 @@ var AGENT_ACTIVITY_LOCATION_LIMIT = 12;
22085
22102
  var AGENT_ACTIVITY_LOCATION_LENGTH_LIMIT = 1024;
22086
22103
  var SESSION_COMPLETED_KIND = "session.completed";
22087
22104
  var SESSION_FAILED_KIND = "session.failed";
22105
+ var SessionPromptDisposition = Object.freeze({
22106
+ Queue: "queue",
22107
+ Reject: "reject",
22108
+ Steer: "steer"
22109
+ });
22088
22110
 
22089
22111
  // ../protocol/src/codecs/messages.ts
22090
22112
  var IDENTIFIER_PATTERN2 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
@@ -22114,7 +22136,7 @@ var ProtocolMessageError = class extends Error {
22114
22136
  this.name = "ProtocolMessageError";
22115
22137
  }
22116
22138
  };
22117
- function isRecord4(value) {
22139
+ function isRecord5(value) {
22118
22140
  return typeof value === "object" && value !== null && !Array.isArray(value);
22119
22141
  }
22120
22142
  function parseJson2(encoded) {
@@ -22126,7 +22148,7 @@ function parseJson2(encoded) {
22126
22148
  cause: error61
22127
22149
  });
22128
22150
  }
22129
- if (!isRecord4(value)) {
22151
+ if (!isRecord5(value)) {
22130
22152
  throw new ProtocolMessageError("Message must be a JSON object");
22131
22153
  }
22132
22154
  return value;
@@ -22159,6 +22181,19 @@ function readAllowedString(value, key, allowedValues) {
22159
22181
  }
22160
22182
  return field;
22161
22183
  }
22184
+ function readPromptDisposition(value) {
22185
+ const field = value["disposition"];
22186
+ if (field === void 0 || field === SessionPromptDisposition.Reject) {
22187
+ return SessionPromptDisposition.Reject;
22188
+ }
22189
+ if (field === SessionPromptDisposition.Queue) {
22190
+ return SessionPromptDisposition.Queue;
22191
+ }
22192
+ if (field === SessionPromptDisposition.Steer) {
22193
+ return SessionPromptDisposition.Steer;
22194
+ }
22195
+ throw new ProtocolMessageError("Invalid value field: disposition");
22196
+ }
22162
22197
  function readSequence(value, key) {
22163
22198
  const field = value[key];
22164
22199
  if (typeof field !== "number" || !Number.isSafeInteger(field) || field < 0) {
@@ -22210,7 +22245,7 @@ function readActivityContent(value) {
22210
22245
  }
22211
22246
  function readActivityDetail(value) {
22212
22247
  const field = value["detail"];
22213
- if (!isRecord4(field)) {
22248
+ if (!isRecord5(field)) {
22214
22249
  throw new ProtocolMessageError("Invalid activity detail field");
22215
22250
  }
22216
22251
  const kind = readString(field, "kind", 32);
@@ -22283,11 +22318,13 @@ function decodeSessionPromptCommand(encoded) {
22283
22318
  "bindingId",
22284
22319
  "commandId",
22285
22320
  "directoryId",
22321
+ "disposition",
22286
22322
  "images",
22287
22323
  "kind",
22288
22324
  "prompt",
22289
22325
  "resources",
22290
- "sessionId"
22326
+ "sessionId",
22327
+ "targetCommandId"
22291
22328
  ]);
22292
22329
  assertKind(value, SESSION_PROMPT_KIND);
22293
22330
  const prompt = readText(value, "prompt", MAX_PROMPT_LENGTH);
@@ -22300,17 +22337,25 @@ function decodeSessionPromptCommand(encoded) {
22300
22337
  if (prompt.trim().length === 0 && images.length === 0 && resources.length === 0) {
22301
22338
  throw new ProtocolMessageError("Prompt must contain content");
22302
22339
  }
22340
+ const disposition = readPromptDisposition(value);
22341
+ const rawTargetCommandId = value["targetCommandId"];
22342
+ const targetCommandId = rawTargetCommandId === void 0 ? "" : readText(value, "targetCommandId", 128);
22343
+ if (disposition === SessionPromptDisposition.Reject && targetCommandId.length > 0 || disposition !== SessionPromptDisposition.Reject && !IDENTIFIER_PATTERN2.test(targetCommandId)) {
22344
+ throw new ProtocolMessageError("Prompt disposition has an invalid target command");
22345
+ }
22303
22346
  return {
22304
22347
  additionalDirectoryIds: readDirectoryIdentifiers(value, "additionalDirectoryIds", 16),
22305
22348
  agentId: readIdentifier(value, "agentId"),
22306
22349
  bindingId: readIdentifier(value, "bindingId"),
22307
22350
  commandId: readIdentifier(value, "commandId"),
22308
22351
  directoryId: readDirectoryIdentifier(value, "directoryId"),
22352
+ disposition,
22309
22353
  images,
22310
22354
  kind: SESSION_PROMPT_KIND,
22311
22355
  prompt,
22312
22356
  resources,
22313
- sessionId: readIdentifier(value, "sessionId")
22357
+ sessionId: readIdentifier(value, "sessionId"),
22358
+ targetCommandId
22314
22359
  };
22315
22360
  }
22316
22361
  function decodeSessionCancelCommand(encoded) {
@@ -22360,11 +22405,12 @@ function decodeSessionStartedEvent(encoded) {
22360
22405
  }
22361
22406
  function decodeAgentMessageDeltaEvent(encoded) {
22362
22407
  const value = parseJson2(encoded);
22363
- assertExactKeys(value, ["commandId", "kind", "sessionId", "text"]);
22408
+ assertExactKeys(value, ["commandId", "kind", "messageId", "sessionId", "text"]);
22364
22409
  assertKind(value, AGENT_MESSAGE_DELTA_KIND);
22365
22410
  return {
22366
22411
  commandId: readIdentifier(value, "commandId"),
22367
22412
  kind: AGENT_MESSAGE_DELTA_KIND,
22413
+ messageId: value["messageId"] === void 0 ? "" : readText(value, "messageId", 255),
22368
22414
  sessionId: readIdentifier(value, "sessionId"),
22369
22415
  text: readString(value, "text", MAX_EVENT_TEXT_LENGTH)
22370
22416
  };
@@ -22629,7 +22675,7 @@ var BotSessionPromptCommand = class {
22629
22675
  this.replyRoute = replyRoute;
22630
22676
  }
22631
22677
  };
22632
- function isRecord5(value) {
22678
+ function isRecord6(value) {
22633
22679
  return typeof value === "object" && value !== null && !Array.isArray(value);
22634
22680
  }
22635
22681
  function parseJson3(encoded) {
@@ -22639,7 +22685,7 @@ function parseJson3(encoded) {
22639
22685
  } catch (cause) {
22640
22686
  throw new ProtocolMessageError("Message is not valid JSON", { cause });
22641
22687
  }
22642
- if (!isRecord5(value)) {
22688
+ if (!isRecord6(value)) {
22643
22689
  throw new ProtocolMessageError("Message must be a JSON object");
22644
22690
  }
22645
22691
  return value;
@@ -22724,7 +22770,7 @@ function readTargets(value, key) {
22724
22770
  const identifiers = /* @__PURE__ */ new Set();
22725
22771
  const aliases = /* @__PURE__ */ new Set();
22726
22772
  for (const candidate of field) {
22727
- if (!isRecord5(candidate)) {
22773
+ if (!isRecord6(candidate)) {
22728
22774
  throw new ProtocolMessageError(`Invalid Bot delivery targets: ${key}`);
22729
22775
  }
22730
22776
  assertExactKeys2(candidate, ["alias", "deliveryTargetId"]);
@@ -22740,7 +22786,7 @@ function readTargets(value, key) {
22740
22786
  return Object.freeze(targets);
22741
22787
  }
22742
22788
  function readCompletionPolicy(value) {
22743
- if (!isRecord5(value)) {
22789
+ if (!isRecord6(value)) {
22744
22790
  throw new ProtocolMessageError("Invalid Bot completion policy");
22745
22791
  }
22746
22792
  const kind = readText2(value, "kind", 32, false);
@@ -22758,7 +22804,7 @@ function readCompletionPolicy(value) {
22758
22804
  throw new ProtocolMessageError("Invalid Bot completion policy");
22759
22805
  }
22760
22806
  function readReplyRoute(value) {
22761
- if (!isRecord5(value)) {
22807
+ if (!isRecord6(value)) {
22762
22808
  throw new ProtocolMessageError("Invalid Bot reply route");
22763
22809
  }
22764
22810
  const kind = readText2(value, "kind", 32, false);
@@ -23010,6 +23056,8 @@ var sessionHistorySchema = external_exports.discriminatedUnion("status", [
23010
23056
  var NATIVE_SESSION_REQUEST_KIND = "native.session.request";
23011
23057
  var NATIVE_SESSION_RESULT_KIND = "native.session.result";
23012
23058
  var NATIVE_SESSION_CANCEL_KIND = "native.session.cancel";
23059
+ var NATIVE_SESSION_TURN_FORK_REQUEST_KIND = "native.session.turn-fork.request";
23060
+ var NATIVE_SESSION_TURN_FORK_KIND = "native.session.turn-fork";
23013
23061
  var identifier = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/);
23014
23062
  var directoryId = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,2047}$/);
23015
23063
  var envelope = {
@@ -23024,6 +23072,28 @@ var identity = {
23024
23072
  bindingId: identifier,
23025
23073
  directoryId
23026
23074
  };
23075
+ var turnForkIdentity = {
23076
+ additionalDirectoryIds: external_exports.array(directoryId).max(16).readonly(),
23077
+ agentId: identifier,
23078
+ bindingId: identifier,
23079
+ directoryId,
23080
+ requestId: identifier,
23081
+ sessionId: identifier,
23082
+ sourceCommandId: identifier,
23083
+ targetSessionId: identifier
23084
+ };
23085
+ var nativeSessionTurnForkRequestSchema = external_exports.strictObject({
23086
+ ...turnForkIdentity,
23087
+ kind: external_exports.literal(NATIVE_SESSION_TURN_FORK_REQUEST_KIND)
23088
+ }).readonly();
23089
+ var nativeSessionTurnForkCommandSchema = external_exports.strictObject({
23090
+ ...turnForkIdentity,
23091
+ kind: external_exports.literal(NATIVE_SESSION_TURN_FORK_KIND),
23092
+ messageFingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
23093
+ messageId: external_exports.string().min(1).max(255),
23094
+ messageOccurrence: external_exports.number().int().positive(),
23095
+ title: external_exports.string().max(500)
23096
+ }).readonly();
23027
23097
  var nativeSessionCommandSchema = external_exports.discriminatedUnion("action", [
23028
23098
  external_exports.strictObject({
23029
23099
  ...identity,
@@ -23098,6 +23168,7 @@ function decode4(schema, encoded) {
23098
23168
  var decodeNativeSessionCommand = (encoded) => decode4(nativeSessionCommandSchema, encoded);
23099
23169
  var encodeNativeSessionResult = (value) => JSON.stringify(nativeSessionResultSchema.parse(value));
23100
23170
  var decodeNativeSessionCancel = (encoded) => decode4(nativeSessionCancelSchema, encoded);
23171
+ var decodeNativeSessionTurnForkCommand = (encoded) => decode4(nativeSessionTurnForkCommandSchema, encoded);
23101
23172
 
23102
23173
  // ../protocol/src/session-events.ts
23103
23174
  var SESSION_EVENT_KIND = "session.event";
@@ -23221,7 +23292,7 @@ var PairingClientError = class extends Error {
23221
23292
  this.name = "PairingClientError";
23222
23293
  }
23223
23294
  };
23224
- function isRecord6(value) {
23295
+ function isRecord7(value) {
23225
23296
  return typeof value === "object" && value !== null && !Array.isArray(value);
23226
23297
  }
23227
23298
  function hasExactKeys(value, expectedKeys) {
@@ -23337,9 +23408,9 @@ async function readJsonResponse(response) {
23337
23408
  }
23338
23409
  }
23339
23410
  function readRelayError(value) {
23340
- if (isRecord6(value)) {
23411
+ if (isRecord7(value)) {
23341
23412
  const error61 = value["error"];
23342
- if (isRecord6(error61)) {
23413
+ if (isRecord7(error61)) {
23343
23414
  const code = error61["code"];
23344
23415
  const message = error61["message"];
23345
23416
  if (typeof code === "string" && typeof message === "string") {
@@ -23350,7 +23421,7 @@ function readRelayError(value) {
23350
23421
  return new PairingClientError("pairing_failed", "The relay could not pair this machine");
23351
23422
  }
23352
23423
  function decodeClaimResponse(value) {
23353
- if (!isRecord6(value) || !hasExactKeys(value, CLAIM_RESPONSE_KEYS)) {
23424
+ if (!isRecord7(value) || !hasExactKeys(value, CLAIM_RESPONSE_KEYS)) {
23354
23425
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
23355
23426
  }
23356
23427
  const channelId = value["channelId"];
@@ -23418,7 +23489,7 @@ async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWo
23418
23489
  return connectionPath;
23419
23490
  }
23420
23491
  function decodeSavedConnection(value, expectedChannelId) {
23421
- if (!isRecord6(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS)) {
23492
+ if (!isRecord7(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS)) {
23422
23493
  throw new PairingClientError("invalid_connection", "The saved connection is invalid");
23423
23494
  }
23424
23495
  const defaultWorkspace = value["defaultWorkspace"];
@@ -23533,7 +23604,7 @@ async function resumeLocalMachine(arguments_, signal = new AbortController().sig
23533
23604
 
23534
23605
  // src/run-daemon.ts
23535
23606
  import { randomUUID as randomUUID6 } from "node:crypto";
23536
- import { basename as basename3, join as join16 } from "node:path";
23607
+ import { basename as basename4, join as join16 } from "node:path";
23537
23608
  import { styleText } from "node:util";
23538
23609
  import { fetch as fetch6, WebSocket as WebSocket2 } from "undici";
23539
23610
 
@@ -23548,7 +23619,7 @@ import {
23548
23619
 
23549
23620
  // src/acp/activity.ts
23550
23621
  var TRUNCATION_MARKER = "\n\u2026 output truncated by Mobius";
23551
- function isRecord7(value) {
23622
+ function isRecord8(value) {
23552
23623
  return typeof value === "object" && value !== null && !Array.isArray(value);
23553
23624
  }
23554
23625
  var AcpActivity = class _AcpActivity {
@@ -23815,7 +23886,7 @@ function contentPhase(content) {
23815
23886
  return "";
23816
23887
  }
23817
23888
  const codex = metadata["codex"];
23818
- if (!isRecord7(codex)) {
23889
+ if (!isRecord8(codex)) {
23819
23890
  return "";
23820
23891
  }
23821
23892
  const phase = codex["phase"];
@@ -24053,7 +24124,7 @@ var SessionConfigProtocolError = class extends Error {
24053
24124
  this.name = "SessionConfigProtocolError";
24054
24125
  }
24055
24126
  };
24056
- function isRecord8(value) {
24127
+ function isRecord9(value) {
24057
24128
  return typeof value === "object" && value !== null && !Array.isArray(value);
24058
24129
  }
24059
24130
  function readString2(value, fallback = "") {
@@ -24080,7 +24151,7 @@ function normalizeConfigValues(value) {
24080
24151
  }
24081
24152
  const normalized = [];
24082
24153
  for (const entry of value) {
24083
- if (!isRecord8(entry)) {
24154
+ if (!isRecord9(entry)) {
24084
24155
  continue;
24085
24156
  }
24086
24157
  const groupedOptions = entry["options"];
@@ -24088,7 +24159,7 @@ function normalizeConfigValues(value) {
24088
24159
  const groupId = readString2(entry["group"]);
24089
24160
  const groupName = readString2(entry["name"]);
24090
24161
  for (const groupedOption of groupedOptions) {
24091
- if (isRecord8(groupedOption)) {
24162
+ if (isRecord9(groupedOption)) {
24092
24163
  normalized.push(normalizeConfigValue(groupedOption, groupId, groupName));
24093
24164
  }
24094
24165
  }
@@ -24102,7 +24173,7 @@ function normalizeConfigOptions(value, modes) {
24102
24173
  const normalized = [];
24103
24174
  if (Array.isArray(value)) {
24104
24175
  for (const option2 of value) {
24105
- if (!isRecord8(option2)) {
24176
+ if (!isRecord9(option2)) {
24106
24177
  continue;
24107
24178
  }
24108
24179
  const id = truncate2(readString2(option2["id"]), 128);
@@ -24141,12 +24212,12 @@ function normalizeConfigOptions(value, modes) {
24141
24212
  }
24142
24213
  }
24143
24214
  }
24144
- if (isRecord8(modes)) {
24215
+ if (isRecord9(modes)) {
24145
24216
  const values = [];
24146
24217
  const availableModes = modes["availableModes"];
24147
24218
  if (Array.isArray(availableModes)) {
24148
24219
  for (const mode of availableModes) {
24149
- if (!isRecord8(mode)) {
24220
+ if (!isRecord9(mode)) {
24150
24221
  continue;
24151
24222
  }
24152
24223
  const id = truncate2(readString2(mode["id"]), 500);
@@ -24224,7 +24295,7 @@ function ensureReasoningEffortOption(current) {
24224
24295
  return [...current.slice(0, MAXIMUM_CONFIG_OPTIONS - 2), model, unavailable];
24225
24296
  }
24226
24297
  function normalizeStandardLifecycle(response) {
24227
- const lifecycle = isRecord8(response) ? response : {};
24298
+ const lifecycle = isRecord9(response) ? response : {};
24228
24299
  return ensureReasoningEffortOption(normalizeConfigOptions(lifecycle["configOptions"], lifecycle["modes"]));
24229
24300
  }
24230
24301
  function replaceStandardConfigOptions(current, value) {
@@ -24266,7 +24337,7 @@ var StandardSessionConfigProtocol = class {
24266
24337
  "ACP agent exited while changing session configuration",
24267
24338
  change.signal
24268
24339
  );
24269
- if (!isRecord8(response) || !Array.isArray(response["configOptions"])) {
24340
+ if (!isRecord9(response) || !Array.isArray(response["configOptions"])) {
24270
24341
  throw new SessionConfigProtocolError("The Agent returned an invalid session configuration response");
24271
24342
  }
24272
24343
  return replaceStandardConfigOptions(current, response["configOptions"]);
@@ -24280,18 +24351,18 @@ var standardSessionConfigProtocol = new StandardSessionConfigProtocol();
24280
24351
  var LEGACY_MODEL_CONFIG_ID = "grok_build_legacy_model";
24281
24352
  var LEGACY_REASONING_EFFORT_CONFIG_ID = "grok_build_legacy_reasoning_effort";
24282
24353
  function legacyOptions(response) {
24283
- if (!isRecord8(response)) {
24354
+ if (!isRecord9(response)) {
24284
24355
  return [];
24285
24356
  }
24286
24357
  const metadata = response["_meta"];
24287
- if (!isRecord8(metadata)) {
24358
+ if (!isRecord9(metadata)) {
24288
24359
  return [];
24289
24360
  }
24290
24361
  const sessionConfig = metadata["x.ai/sessionConfig"];
24291
- if (!isRecord8(sessionConfig) || !Array.isArray(sessionConfig["options"])) {
24362
+ if (!isRecord9(sessionConfig) || !Array.isArray(sessionConfig["options"])) {
24292
24363
  return [];
24293
24364
  }
24294
- return sessionConfig["options"].slice(0, 500).filter(isRecord8);
24365
+ return sessionConfig["options"].slice(0, 500).filter(isRecord9);
24295
24366
  }
24296
24367
  function legacyValue(option2) {
24297
24368
  const value = readString2(option2["id"]);
@@ -24304,7 +24375,7 @@ function legacyValue(option2) {
24304
24375
  };
24305
24376
  }
24306
24377
  function modelsRecord(response) {
24307
- if (!isRecord8(response) || !isRecord8(response["models"])) {
24378
+ if (!isRecord9(response) || !isRecord9(response["models"])) {
24308
24379
  return {};
24309
24380
  }
24310
24381
  return response["models"];
@@ -24323,7 +24394,7 @@ function legacyModelOptions(response) {
24323
24394
  }
24324
24395
  if (values.length === 0 && Array.isArray(models["availableModels"])) {
24325
24396
  for (const model of models["availableModels"]) {
24326
- if (!isRecord8(model)) {
24397
+ if (!isRecord9(model)) {
24327
24398
  continue;
24328
24399
  }
24329
24400
  const value = readString2(model["modelId"]).slice(0, 500);
@@ -24403,7 +24474,7 @@ function legacyReasoningEffortOptions(response) {
24403
24474
  return [result];
24404
24475
  }
24405
24476
  function normalizeGrokLifecycle(response) {
24406
- const lifecycle = isRecord8(response) ? response : {};
24477
+ const lifecycle = isRecord9(response) ? response : {};
24407
24478
  const standard = normalizeConfigOptions(lifecycle["configOptions"], lifecycle["modes"]);
24408
24479
  const normalized = [...standard];
24409
24480
  if (!normalized.some((option2) => option2.category === "model")) {
@@ -24422,11 +24493,11 @@ function updateCurrentValue2(current, category, value) {
24422
24493
  return current.map((option2) => option2.category === category ? { ...option2, currentValue: value } : option2);
24423
24494
  }
24424
24495
  function validateGrokResponse(response, modelId) {
24425
- if (!isRecord8(response) || !isRecord8(response["_meta"])) {
24496
+ if (!isRecord9(response) || !isRecord9(response["_meta"])) {
24426
24497
  throw new SessionConfigProtocolError("Grok Build returned an invalid session configuration response");
24427
24498
  }
24428
24499
  const model = response["_meta"]["model"];
24429
- const appliedModel = isRecord8(model) ? model["Ok"] : model;
24500
+ const appliedModel = isRecord9(model) ? model["Ok"] : model;
24430
24501
  if (appliedModel !== modelId) {
24431
24502
  throw new SessionConfigProtocolError("Grok Build returned an invalid session configuration response");
24432
24503
  }
@@ -24869,7 +24940,7 @@ function createWritableAcpStream(output2) {
24869
24940
  function truncate3(value, maximumLength) {
24870
24941
  return value.length <= maximumLength ? value : `${value.slice(0, Math.max(0, maximumLength - 1))}\u2026`;
24871
24942
  }
24872
- function isRecord9(value) {
24943
+ function isRecord10(value) {
24873
24944
  return typeof value === "object" && value !== null && !Array.isArray(value);
24874
24945
  }
24875
24946
  function readString4(value, fallback = "") {
@@ -24878,6 +24949,11 @@ function readString4(value, fallback = "") {
24878
24949
  function arraysEqual(left, right) {
24879
24950
  return left.length === right.length && left.every((value, index) => value === right[index]);
24880
24951
  }
24952
+ function recordsEqual(left, right) {
24953
+ const leftEntries = Object.entries(left);
24954
+ const rightEntries = Object.entries(right);
24955
+ return leftEntries.length === rightEntries.length && leftEntries.every(([name, value]) => right[name] === value);
24956
+ }
24881
24957
  function elicitationFieldType(value) {
24882
24958
  if (value === "boolean") {
24883
24959
  return "boolean";
@@ -24898,7 +24974,7 @@ function normalizeElicitationOptions(schema) {
24898
24974
  const oneOf = schema["oneOf"];
24899
24975
  if (Array.isArray(oneOf)) {
24900
24976
  for (const option2 of oneOf) {
24901
- if (isRecord9(option2) && typeof option2["const"] === "string") {
24977
+ if (isRecord10(option2) && typeof option2["const"] === "string") {
24902
24978
  normalized.push({
24903
24979
  description: truncate3(readString4(option2["description"]), 500),
24904
24980
  label: truncate3(readString4(option2["title"], option2["const"]), 255),
@@ -24918,7 +24994,7 @@ function normalizeElicitationOptions(schema) {
24918
24994
  return normalized.slice(0, 200);
24919
24995
  }
24920
24996
  const items = schema["items"];
24921
- if (!isRecord9(items)) {
24997
+ if (!isRecord10(items)) {
24922
24998
  return normalized;
24923
24999
  }
24924
25000
  const anyOf = items["anyOf"];
@@ -24931,7 +25007,7 @@ function numericString(value) {
24931
25007
  return typeof value === "number" && Number.isFinite(value) ? value.toString() : "";
24932
25008
  }
24933
25009
  function normalizeElicitationFields(requestedSchema) {
24934
- if (!isRecord9(requestedSchema) || !isRecord9(requestedSchema["properties"])) {
25010
+ if (!isRecord10(requestedSchema) || !isRecord10(requestedSchema["properties"])) {
24935
25011
  return [];
24936
25012
  }
24937
25013
  const properties = requestedSchema["properties"];
@@ -24941,7 +25017,7 @@ function normalizeElicitationFields(requestedSchema) {
24941
25017
  );
24942
25018
  const normalized = [];
24943
25019
  for (const [name, property] of Object.entries(properties)) {
24944
- if (!isRecord9(property)) {
25020
+ if (!isRecord10(property)) {
24945
25021
  continue;
24946
25022
  }
24947
25023
  const type = elicitationFieldType(property["type"]);
@@ -25032,7 +25108,7 @@ function validateElicitationContent(encoded, requestedSchema) {
25032
25108
  "invalid_elicitation_response"
25033
25109
  );
25034
25110
  }
25035
- if (!isRecord9(decoded) || !isRecord9(requestedSchema) || !isRecord9(requestedSchema["properties"])) {
25111
+ if (!isRecord10(decoded) || !isRecord10(requestedSchema) || !isRecord10(requestedSchema["properties"])) {
25036
25112
  throw new AcpExecutionError(
25037
25113
  "Elicitation response does not match the requested form",
25038
25114
  {},
@@ -25052,7 +25128,7 @@ function validateElicitationContent(encoded, requestedSchema) {
25052
25128
  const validated = {};
25053
25129
  for (const [name, value] of Object.entries(decoded)) {
25054
25130
  const property = properties[name];
25055
- if (!isRecord9(property)) {
25131
+ if (!isRecord10(property)) {
25056
25132
  throw new AcpExecutionError(
25057
25133
  `Elicitation field ${name} was not requested`,
25058
25134
  {},
@@ -25194,6 +25270,7 @@ var AcpProcessSession = class _AcpProcessSession {
25194
25270
  #child;
25195
25271
  #connection;
25196
25272
  #cwd;
25273
+ #environment;
25197
25274
  #getObserver;
25198
25275
  #mobiusSessionId;
25199
25276
  #pendingElicitations = /* @__PURE__ */ new Map();
@@ -25215,7 +25292,9 @@ var AcpProcessSession = class _AcpProcessSession {
25215
25292
  imagePrompt: false,
25216
25293
  listSessions: false,
25217
25294
  loadSession: false,
25295
+ promptQueueing: false,
25218
25296
  resumeSession: false,
25297
+ steering: false,
25219
25298
  unstableForkSession: false
25220
25299
  };
25221
25300
  #commands = [];
@@ -25235,6 +25314,7 @@ var AcpProcessSession = class _AcpProcessSession {
25235
25314
  this.#additionalDirectories = [...additionalDirectories];
25236
25315
  this.#agentId = agent.id;
25237
25316
  this.#cwd = cwd;
25317
+ this.#environment = agent.environment;
25238
25318
  this.#getObserver = getObserver;
25239
25319
  this.#mobiusSessionId = mobiusSessionId;
25240
25320
  this.#sessionConfigProtocol = sessionConfigProtocolForAgent(agent.id);
@@ -25320,7 +25400,18 @@ var AcpProcessSession = class _AcpProcessSession {
25320
25400
  store,
25321
25401
  async (runtime) => await runtime.#initializeAgent(options.signal)
25322
25402
  );
25403
+ static startSessionForkAtTurn = async (options, sourceNativeSessionId, forkPoint, getObserver, store) => await _AcpProcessSession.#start(
25404
+ options,
25405
+ getObserver,
25406
+ store,
25407
+ async (runtime) => await runtime.initialize(
25408
+ options.signal,
25409
+ { kind: "fork", nativeSessionId: sourceNativeSessionId },
25410
+ forkPoint
25411
+ )
25412
+ );
25323
25413
  belongsTo = (agentId, cwd, additionalDirectories) => this.#agentId === agentId && this.#cwd === cwd && arraysEqual(this.#additionalDirectories, additionalDirectories);
25414
+ usesEnvironment = (environment) => recordsEqual(this.#environment, environment);
25324
25415
  canRebind = () => !this.#prompted && !this.#turn.running;
25325
25416
  prepared = () => new AcpPreparedSession(
25326
25417
  this.#agentName,
@@ -25386,15 +25477,14 @@ var AcpProcessSession = class _AcpProcessSession {
25386
25477
  "unsupported_acp_protocol"
25387
25478
  );
25388
25479
  }
25389
- const capabilities = initialized.agentCapabilities ?? {};
25390
- this.#capabilities = normalizeAcpCapabilities(capabilities);
25480
+ this.#capabilities = normalizeAcpCapabilities(initialized);
25391
25481
  this.#agentName = truncate3(initialized.agentInfo?.title ?? initialized.agentInfo?.name ?? this.#agentId, 255);
25392
25482
  this.#agentVersion = truncate3(
25393
25483
  this.#verifiedAgentVersion.length > 0 ? this.#verifiedAgentVersion : initialized.agentInfo?.version ?? "",
25394
25484
  128
25395
25485
  );
25396
25486
  }
25397
- initialize = async (signal, origin) => {
25487
+ initialize = async (signal, origin, forkPoint) => {
25398
25488
  await this.#initializeAgent(signal);
25399
25489
  let stored = origin?.kind === "import" ? new FoundAcpStoredSession(
25400
25490
  new AcpStoredSession(
@@ -25426,6 +25516,13 @@ var AcpProcessSession = class _AcpProcessSession {
25426
25516
  "native_session_unsupported"
25427
25517
  );
25428
25518
  }
25519
+ if (this.#additionalDirectories.length > 0 && !this.#capabilities.additionalDirectories) {
25520
+ throw new AcpExecutionError(
25521
+ "This Agent cannot restore additional working directories",
25522
+ {},
25523
+ "native_session_unsupported"
25524
+ );
25525
+ }
25429
25526
  const forked = await this.race(
25430
25527
  this.#connection.agent.request(
25431
25528
  methods6.agent.session.fork,
@@ -25433,7 +25530,21 @@ var AcpProcessSession = class _AcpProcessSession {
25433
25530
  sessionId: nativeSessionIdSchema.parse(origin.nativeSessionId),
25434
25531
  cwd: this.#cwd,
25435
25532
  additionalDirectories: [...this.#additionalDirectories],
25436
- mcpServers: []
25533
+ mcpServers: [],
25534
+ ...forkPoint === void 0 ? {} : {
25535
+ _meta: {
25536
+ jetbrains: {
25537
+ air: {
25538
+ fork: {
25539
+ version: 1,
25540
+ messageFingerprint: forkPoint.messageFingerprint,
25541
+ messageId: forkPoint.messageId,
25542
+ messageOccurrence: forkPoint.messageOccurrence
25543
+ }
25544
+ }
25545
+ }
25546
+ }
25547
+ }
25437
25548
  },
25438
25549
  { cancellationSignal: signal }
25439
25550
  ),
@@ -25638,10 +25749,7 @@ var AcpProcessSession = class _AcpProcessSession {
25638
25749
  }
25639
25750
  return this.#configOptions;
25640
25751
  };
25641
- prompt = async (options) => {
25642
- if (this.#turn.running) {
25643
- throw new AcpExecutionError("Another prompt is already running in this conversation", {}, "session_busy");
25644
- }
25752
+ buildPrompt = (options) => {
25645
25753
  if (options.images.length > 0 && !this.#capabilities.imagePrompt) {
25646
25754
  throw new AcpExecutionError(
25647
25755
  "The selected agent does not accept image prompts",
@@ -25690,6 +25798,13 @@ var AcpProcessSession = class _AcpProcessSession {
25690
25798
  if (prompt.length === 0) {
25691
25799
  throw new AcpExecutionError("A prompt must contain text or an attachment", {}, "empty_prompt");
25692
25800
  }
25801
+ return prompt;
25802
+ };
25803
+ prompt = async (options) => {
25804
+ if (this.#turn.running) {
25805
+ throw new AcpExecutionError("Another prompt is already running in this conversation", {}, "session_busy");
25806
+ }
25807
+ const prompt = this.buildPrompt(options);
25693
25808
  this.#store.write(
25694
25809
  new AcpStoredSession(
25695
25810
  this.#mobiusSessionId,
@@ -25748,6 +25863,54 @@ var AcpProcessSession = class _AcpProcessSession {
25748
25863
  this.#turn = new IdleAcpTurn();
25749
25864
  }
25750
25865
  };
25866
+ steer = async (options) => {
25867
+ if (!this.#capabilities.steering) {
25868
+ throw new AcpExecutionError(
25869
+ "The selected agent does not support mid-turn steering",
25870
+ {},
25871
+ "steering_unsupported"
25872
+ );
25873
+ }
25874
+ if (!this.#turn.running || this.#turn.commandId !== options.targetCommandId) {
25875
+ throw new AcpExecutionError("The target turn is no longer active", {}, "steering_target_unavailable");
25876
+ }
25877
+ const prompt = this.buildPrompt(options);
25878
+ const response = await this.race(
25879
+ this.#connection.agent.request("_session/steering", {
25880
+ _meta: { steering: { idleBehavior: "promptRequired" } },
25881
+ prompt,
25882
+ sessionId: this.#nativeSessionId
25883
+ }),
25884
+ "ACP agent exited during steering",
25885
+ options.signal
25886
+ );
25887
+ if (!isRecord10(response)) {
25888
+ throw new AcpExecutionError(
25889
+ "ACP agent returned an invalid steering response",
25890
+ {},
25891
+ "acp_invalid_steering_response"
25892
+ );
25893
+ }
25894
+ const outcome = response["outcome"];
25895
+ if (outcome === "injected" || outcome === "startedNewTurn") {
25896
+ return outcome;
25897
+ }
25898
+ if (outcome === "promptRequired") {
25899
+ throw new AcpExecutionError(
25900
+ "The target turn finished before steering could be injected",
25901
+ {},
25902
+ "steering_target_unavailable"
25903
+ );
25904
+ }
25905
+ if (outcome === "failed") {
25906
+ throw new AcpExecutionError("The Agent could not inject steering", {}, "steering_failed");
25907
+ }
25908
+ throw new AcpExecutionError(
25909
+ "ACP agent returned an invalid steering response",
25910
+ {},
25911
+ "acp_invalid_steering_response"
25912
+ );
25913
+ };
25751
25914
  cancel = async () => {
25752
25915
  const capturedProcesses = await captureProcessTree(this.#child);
25753
25916
  try {
@@ -25988,7 +26151,7 @@ var AcpProcessSession = class _AcpProcessSession {
25988
26151
  );
25989
26152
  } else {
25990
26153
  await this.#turn.activities.finishNarrative();
25991
- await this.#turn.onText(update.content.text);
26154
+ await this.#turn.onText(update.content.text, truncate3(update.messageId ?? "", 255));
25992
26155
  }
25993
26156
  } else {
25994
26157
  await this.#turn.activities.finishNarrative();
@@ -26214,6 +26377,7 @@ var AcpProcessSession = class _AcpProcessSession {
26214
26377
  };
26215
26378
  };
26216
26379
  var AcpSessionManager = class {
26380
+ #activeCommandIds = /* @__PURE__ */ new Map();
26217
26381
  #nativeOperations = /* @__PURE__ */ new Set();
26218
26382
  #nativeBrowserObserver = new NullAcpSessionObserver();
26219
26383
  #nativeBrowserPreparing = /* @__PURE__ */ new Map();
@@ -26282,6 +26446,25 @@ var AcpSessionManager = class {
26282
26446
  this.#preparing.set(options.sessionId, operation);
26283
26447
  return await operation;
26284
26448
  }
26449
+ async #startSessionForkAtTurnPreparation(options, sourceNativeSessionId, forkPoint) {
26450
+ const operation = (async () => {
26451
+ try {
26452
+ const runtime = await AcpProcessSession.startSessionForkAtTurn(
26453
+ options,
26454
+ sourceNativeSessionId,
26455
+ forkPoint,
26456
+ () => this.#observer,
26457
+ this.#store
26458
+ );
26459
+ this.#sessions.set(options.sessionId, runtime);
26460
+ return runtime;
26461
+ } finally {
26462
+ this.#preparing.delete(options.sessionId);
26463
+ }
26464
+ })();
26465
+ this.#preparing.set(options.sessionId, operation);
26466
+ return await operation;
26467
+ }
26285
26468
  async #replacePreparation(existing, options) {
26286
26469
  const operation = (async () => {
26287
26470
  try {
@@ -26298,6 +26481,58 @@ var AcpSessionManager = class {
26298
26481
  this.#preparing.set(options.sessionId, operation);
26299
26482
  return await operation;
26300
26483
  }
26484
+ async #replaceExecutionPreparation(existing, options) {
26485
+ const desiredConfigOptions = existing.prepared().configOptions;
26486
+ const operation = (async () => {
26487
+ try {
26488
+ this.#sessions.delete(options.sessionId);
26489
+ await existing.close();
26490
+ this.#store.remove(options.sessionId);
26491
+ const runtime = await AcpProcessSession.start(options, () => this.#observer, this.#store);
26492
+ try {
26493
+ for (const desired of desiredConfigOptions) {
26494
+ const current = runtime.prepared().configOptions.find((option2) => option2.id === desired.id);
26495
+ if (current === void 0 || current.type !== desired.type) {
26496
+ continue;
26497
+ }
26498
+ if (desired.type === "boolean") {
26499
+ if (current.booleanValue !== desired.booleanValue) {
26500
+ await runtime.configure({
26501
+ ...options,
26502
+ booleanValue: desired.booleanValue,
26503
+ configId: desired.id,
26504
+ requestId: `execution-prepare-${desired.id}`,
26505
+ value: "",
26506
+ valueType: "boolean"
26507
+ });
26508
+ }
26509
+ continue;
26510
+ }
26511
+ if (current.currentValue !== desired.currentValue && current.values.some((value) => value.value === desired.currentValue)) {
26512
+ await runtime.configure({
26513
+ ...options,
26514
+ booleanValue: false,
26515
+ configId: desired.id,
26516
+ requestId: `execution-prepare-${desired.id}`,
26517
+ value: desired.currentValue,
26518
+ valueType: "select"
26519
+ });
26520
+ }
26521
+ }
26522
+ } catch (error61) {
26523
+ await runtime.close();
26524
+ this.#store.remove(options.sessionId);
26525
+ throw error61;
26526
+ }
26527
+ this.#sessions.set(options.sessionId, runtime);
26528
+ return runtime;
26529
+ } finally {
26530
+ this.#preparing.delete(options.sessionId);
26531
+ }
26532
+ })();
26533
+ this.#preparing.set(options.sessionId, operation);
26534
+ return await operation;
26535
+ }
26301
26536
  prepareSession = async (options) => {
26302
26537
  const stored = this.#store.read(options.sessionId);
26303
26538
  if (stored instanceof DeletedAcpStoredSession) {
@@ -26336,6 +26571,35 @@ var AcpSessionManager = class {
26336
26571
  const runtime = await this.#startPreparation(options);
26337
26572
  return runtime.prepared();
26338
26573
  };
26574
+ prepareExecutionSession = async (options) => {
26575
+ const pending = this.#preparing.get(options.sessionId);
26576
+ if (pending !== void 0) {
26577
+ await pending;
26578
+ return await this.prepareExecutionSession(options);
26579
+ }
26580
+ const existing = this.#sessions.get(options.sessionId);
26581
+ if (existing === void 0) {
26582
+ return (await this.#startPreparation(options)).prepared();
26583
+ }
26584
+ if (!existing.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
26585
+ throw new AcpExecutionError(
26586
+ "The agent or workspace cannot change after a conversation has started",
26587
+ {},
26588
+ "session_identity_mismatch"
26589
+ );
26590
+ }
26591
+ if (existing.usesEnvironment(options.agent.environment)) {
26592
+ return existing.prepared();
26593
+ }
26594
+ if (!existing.canRebind()) {
26595
+ throw new AcpExecutionError(
26596
+ "The execution environment cannot change after a conversation has started",
26597
+ {},
26598
+ "session_identity_mismatch"
26599
+ );
26600
+ }
26601
+ return (await this.#replaceExecutionPreparation(existing, options)).prepared();
26602
+ };
26339
26603
  prepareNativeSessionBrowser = async (options) => {
26340
26604
  const pending = this.#nativeBrowserPreparing.get(options.sessionId);
26341
26605
  if (pending !== void 0) {
@@ -26380,6 +26644,47 @@ var AcpSessionManager = class {
26380
26644
  }
26381
26645
  }
26382
26646
  };
26647
+ steerPrompt = async (options) => {
26648
+ if (this.#activeCommandIds.get(options.sessionId) !== options.targetCommandId) {
26649
+ throw new AcpExecutionError("The target turn is no longer active", {}, "steering_target_unavailable");
26650
+ }
26651
+ const pending = this.#preparing.get(options.sessionId);
26652
+ if (pending !== void 0) {
26653
+ await pending;
26654
+ }
26655
+ if (this.#activeCommandIds.get(options.sessionId) !== options.targetCommandId) {
26656
+ throw new AcpExecutionError("The target turn is no longer active", {}, "steering_target_unavailable");
26657
+ }
26658
+ const runtime = this.#sessions.get(options.sessionId);
26659
+ if (runtime === void 0 || !runtime.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
26660
+ throw new AcpExecutionError(
26661
+ "The target turn execution context is unavailable",
26662
+ {},
26663
+ "steering_target_unavailable"
26664
+ );
26665
+ }
26666
+ const deadlineController = new AbortController();
26667
+ const deadline = setTimeout(
26668
+ () => deadlineController.abort(
26669
+ new AcpExecutionError("The steering request exceeded its deadline", {}, "steering_timeout")
26670
+ ),
26671
+ options.agent.promptTimeoutMilliseconds
26672
+ );
26673
+ deadline.unref();
26674
+ try {
26675
+ return await runtime.steer({
26676
+ ...options,
26677
+ signal: AbortSignal.any([options.signal, deadlineController.signal])
26678
+ });
26679
+ } catch (error61) {
26680
+ if (error61 instanceof AcpExecutionError) {
26681
+ throw error61;
26682
+ }
26683
+ throw new AcpExecutionError("ACP agent steering failed", { cause: error61 }, "steering_failed");
26684
+ } finally {
26685
+ clearTimeout(deadline);
26686
+ }
26687
+ };
26383
26688
  runPrompt = async (options) => {
26384
26689
  const stored = this.#store.read(options.sessionId);
26385
26690
  if (stored instanceof FoundAcpStoredSession && this.#nativeOperations.has(`${stored.session.agentId}:${stored.session.nativeSessionId}`)) {
@@ -26404,6 +26709,7 @@ var AcpSessionManager = class {
26404
26709
  signal: AbortSignal.any([options.signal, deadlineController.signal])
26405
26710
  };
26406
26711
  this.#activeSessionIds.add(options.sessionId);
26712
+ this.#activeCommandIds.set(options.sessionId, options.commandId);
26407
26713
  try {
26408
26714
  if (this.#cancellationRequests.has(options.sessionId)) {
26409
26715
  return { stopReason: "cancelled" };
@@ -26443,6 +26749,9 @@ var AcpSessionManager = class {
26443
26749
  } finally {
26444
26750
  clearTimeout(deadline);
26445
26751
  this.#activeSessionIds.delete(options.sessionId);
26752
+ if (this.#activeCommandIds.get(options.sessionId) === options.commandId) {
26753
+ this.#activeCommandIds.delete(options.sessionId);
26754
+ }
26446
26755
  this.#cancellationRequests.delete(options.sessionId);
26447
26756
  }
26448
26757
  };
@@ -26467,6 +26776,7 @@ var AcpSessionManager = class {
26467
26776
  this.#nativeBrowserSessions.clear();
26468
26777
  await Promise.all([...sessions, ...nativeBrowserSessions].map(async (session) => await session.close()));
26469
26778
  };
26779
+ hasPreparedSession = (sessionId, agentId, cwd, additionalDirectories) => this.#sessions.get(sessionId)?.belongsTo(agentId, cwd, additionalDirectories) === true;
26470
26780
  ensureSession = async (options) => {
26471
26781
  const existing = this.#sessions.get(options.sessionId);
26472
26782
  if (existing !== void 0) {
@@ -26575,6 +26885,68 @@ var AcpSessionManager = class {
26575
26885
  this.#nativeOperations.delete(key);
26576
26886
  }
26577
26887
  };
26888
+ forkSessionAtTurn = async (options, sourceSessionId, forkPoint) => {
26889
+ if (sourceSessionId === options.sessionId) {
26890
+ throw new AcpExecutionError(
26891
+ "The fork destination must be a new conversation",
26892
+ {},
26893
+ "session_identity_mismatch"
26894
+ );
26895
+ }
26896
+ const source = this.#store.read(sourceSessionId);
26897
+ if (!(source instanceof FoundAcpStoredSession) || !source.session.prompted) {
26898
+ throw new AcpExecutionError(
26899
+ "The source conversation has no resumable native Agent session",
26900
+ {},
26901
+ "native_session_not_found"
26902
+ );
26903
+ }
26904
+ if (source.session.agentId !== options.agent.id || source.session.cwd !== options.cwd || !arraysEqual(source.session.additionalDirectories, options.additionalDirectories)) {
26905
+ throw new AcpExecutionError(
26906
+ "The source conversation execution target does not match the fork request",
26907
+ {},
26908
+ "native_session_target_changed"
26909
+ );
26910
+ }
26911
+ const key = `${options.agent.id}:${source.session.nativeSessionId}`;
26912
+ const linked = this.#store.findNative(options.agent.id, source.session.nativeSessionId);
26913
+ if (this.#nativeOperations.has(key) || linked.some((entry) => this.#activeSessionIds.has(entry.sessionId) || this.#preparing.has(entry.sessionId))) {
26914
+ throw new AcpExecutionError(
26915
+ "Wait for the source conversation's running operation to finish",
26916
+ {},
26917
+ "session_busy"
26918
+ );
26919
+ }
26920
+ 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) {
26921
+ throw new AcpExecutionError("The destination conversation already exists", {}, "session_identity_mismatch");
26922
+ }
26923
+ this.#nativeOperations.add(key);
26924
+ try {
26925
+ const runtime = await this.#startSessionForkAtTurnPreparation(
26926
+ options,
26927
+ source.session.nativeSessionId,
26928
+ forkPoint
26929
+ );
26930
+ const openedKey = `${options.agent.id}:${runtime.nativeSessionId()}`;
26931
+ this.#nativeOperations.add(openedKey);
26932
+ try {
26933
+ return {
26934
+ history: await runtime.loadHistory(options.signal),
26935
+ prepared: runtime.prepared(),
26936
+ sessionId: options.sessionId
26937
+ };
26938
+ } catch (error61) {
26939
+ this.#sessions.delete(options.sessionId);
26940
+ this.#store.remove(options.sessionId);
26941
+ await runtime.closeProcess();
26942
+ throw error61;
26943
+ } finally {
26944
+ this.#nativeOperations.delete(openedKey);
26945
+ }
26946
+ } finally {
26947
+ this.#nativeOperations.delete(key);
26948
+ }
26949
+ };
26578
26950
  deleteNativeSession = async (options, nativeSessionId) => {
26579
26951
  const runtime = await this.#ensureNativeSessionBrowser(options);
26580
26952
  const key = `${options.agent.id}:${nativeSessionId}`;
@@ -27317,7 +27689,8 @@ var LocalBotGrant = class {
27317
27689
  revocationEpoch;
27318
27690
  workspaceIdentityDigest;
27319
27691
  workspacePath;
27320
- constructor(localGrantId, botId, behaviorRevisionId, behaviorDigest, bindingId, bindingRevision, agent, agentIdentityDigest, workspacePath, workspaceIdentityDigest, allowedDeliveryTargetIds, expiresAt, revocationEpoch) {
27692
+ workspaceRef;
27693
+ constructor(localGrantId, botId, behaviorRevisionId, behaviorDigest, bindingId, bindingRevision, agent, agentIdentityDigest, workspaceRef, workspacePath, workspaceIdentityDigest, allowedDeliveryTargetIds, expiresAt, revocationEpoch) {
27321
27694
  this.localGrantId = localGrantId;
27322
27695
  this.botId = botId;
27323
27696
  this.behaviorRevisionId = behaviorRevisionId;
@@ -27326,6 +27699,7 @@ var LocalBotGrant = class {
27326
27699
  this.bindingRevision = bindingRevision;
27327
27700
  this.agent = agent;
27328
27701
  this.agentIdentityDigest = agentIdentityDigest;
27702
+ this.workspaceRef = workspaceRef;
27329
27703
  this.workspacePath = workspacePath;
27330
27704
  this.workspaceIdentityDigest = workspaceIdentityDigest;
27331
27705
  this.allowedDeliveryTargetIds = Object.freeze([...allowedDeliveryTargetIds]);
@@ -27510,7 +27884,7 @@ var LocalBotGrantStore = class {
27510
27884
  WHERE local_grant_id = ? AND bot_id = ?
27511
27885
  AND behavior_revision_id = ? AND behavior_digest = ?
27512
27886
  AND binding_id = ? AND binding_revision = ?
27513
- AND agent_ref = ? AND workspace_ref = ?
27887
+ AND agent_ref = ?
27514
27888
  AND revoked_at IS NULL AND expires_at > ?`
27515
27889
  ).get(
27516
27890
  command.localGrantId,
@@ -27520,7 +27894,6 @@ var LocalBotGrantStore = class {
27520
27894
  command.bindingId,
27521
27895
  command.bindingRevision,
27522
27896
  command.agentId,
27523
- command.directoryId,
27524
27897
  now
27525
27898
  );
27526
27899
  if (row === void 0) {
@@ -27543,7 +27916,8 @@ var LocalBotGrantStore = class {
27543
27916
  revalidateExecutable(agent.agentIdentity)
27544
27917
  ]);
27545
27918
  signal.throwIfAborted();
27546
- const workspacePath = await directories.resolve(bindingAuthorityId, command.directoryId, signal);
27919
+ const workspaceRef = rowString(row, "workspace_ref");
27920
+ const workspacePath = await directories.resolve(bindingAuthorityId, workspaceRef, signal);
27547
27921
  const currentAgentDigest = agentDigest(agent);
27548
27922
  const currentWorkspaceDigest = await workspaceDigest(workspacePath);
27549
27923
  signal.throwIfAborted();
@@ -27569,6 +27943,7 @@ var LocalBotGrantStore = class {
27569
27943
  command.bindingRevision,
27570
27944
  agent,
27571
27945
  currentAgentDigest,
27946
+ workspaceRef,
27572
27947
  workspacePath,
27573
27948
  currentWorkspaceDigest,
27574
27949
  approvedTargets,
@@ -27783,7 +28158,7 @@ var BotOutboundIpcRequest = class {
27783
28158
  this.text = text;
27784
28159
  }
27785
28160
  };
27786
- function isRecord10(value) {
28161
+ function isRecord11(value) {
27787
28162
  return typeof value === "object" && value !== null && !Array.isArray(value);
27788
28163
  }
27789
28164
  function decodeRequest(encoded) {
@@ -27793,7 +28168,7 @@ function decodeRequest(encoded) {
27793
28168
  } catch (cause) {
27794
28169
  throw new LocalBotAuthorizationError("invalid_request", "The local Bot request is invalid", { cause });
27795
28170
  }
27796
- if (!isRecord10(value)) {
28171
+ if (!isRecord11(value)) {
27797
28172
  throw new LocalBotAuthorizationError("invalid_request", "The local Bot request is invalid");
27798
28173
  }
27799
28174
  const keys = Object.keys(value);
@@ -27843,10 +28218,10 @@ async function decodeRelayResponse(response) {
27843
28218
  cause
27844
28219
  });
27845
28220
  }
27846
- if (response.status === 202 && isRecord10(value) && typeof value["outboundMessageId"] === "string") {
28221
+ if (response.status === 202 && isRecord11(value) && typeof value["outboundMessageId"] === "string") {
27847
28222
  return value["outboundMessageId"];
27848
28223
  }
27849
- if (isRecord10(value) && isRecord10(value["error"])) {
28224
+ if (isRecord11(value) && isRecord11(value["error"])) {
27850
28225
  const error61 = value["error"];
27851
28226
  if (typeof error61["code"] === "string" && typeof error61["message"] === "string") {
27852
28227
  throw new LocalBotAuthorizationError(error61["code"], error61["message"]);
@@ -27879,7 +28254,7 @@ var BotOutboundIpcServer = class {
27879
28254
  if (this.#usesFilesystemSocket) {
27880
28255
  await mkdir4(dirname6(this.#socketPath), { mode: 448, recursive: true });
27881
28256
  await unlink(this.#socketPath).catch((error61) => {
27882
- if (!isRecord10(error61) || error61["code"] !== "ENOENT") {
28257
+ if (!isRecord11(error61) || error61["code"] !== "ENOENT") {
27883
28258
  throw error61;
27884
28259
  }
27885
28260
  });
@@ -27914,7 +28289,7 @@ var BotOutboundIpcServer = class {
27914
28289
  }
27915
28290
  if (this.#usesFilesystemSocket) {
27916
28291
  await unlink(this.#socketPath).catch((error61) => {
27917
- if (!isRecord10(error61) || error61["code"] !== "ENOENT") {
28292
+ if (!isRecord11(error61) || error61["code"] !== "ENOENT") {
27918
28293
  throw error61;
27919
28294
  }
27920
28295
  });
@@ -28002,6 +28377,7 @@ var BotOutboundIpcServer = class {
28002
28377
 
28003
28378
  // src/native-sessions/service.ts
28004
28379
  import { randomUUID as randomUUID4 } from "node:crypto";
28380
+ import { basename as basename3 } from "node:path";
28005
28381
 
28006
28382
  // src/native-sessions/codex-history.ts
28007
28383
  import { spawn as spawn4 } from "node:child_process";
@@ -28479,6 +28855,74 @@ var NativeSessionService = class {
28479
28855
  history: opened.history
28480
28856
  };
28481
28857
  };
28858
+ executeTurnFork = async (command, signal) => {
28859
+ signal.throwIfAborted();
28860
+ const cwd = await this.#directories.resolve(this.#config.bindingId, command.directoryId, signal);
28861
+ const additionalDirectories = [];
28862
+ const additionalEntries = [];
28863
+ const resolvedPaths = /* @__PURE__ */ new Set([cwd]);
28864
+ for (const directoryId2 of command.additionalDirectoryIds) {
28865
+ const path2 = await this.#directories.resolve(this.#config.bindingId, directoryId2, signal);
28866
+ if (resolvedPaths.has(path2)) {
28867
+ throw new LocalAuthorizationError("Workspace directories must resolve to distinct locations");
28868
+ }
28869
+ resolvedPaths.add(path2);
28870
+ additionalDirectories.push(path2);
28871
+ additionalEntries.push({ directoryId: directoryId2, name: basename3(path2) });
28872
+ }
28873
+ const listing = await this.#directories.list(this.#config.bindingId, command.directoryId, signal);
28874
+ const opened = await this.#sessions.forkSessionAtTurn(
28875
+ {
28876
+ additionalDirectories,
28877
+ agent: this.#config.catalog.find(command.agentId),
28878
+ cwd,
28879
+ sessionId: command.targetSessionId,
28880
+ signal
28881
+ },
28882
+ command.sessionId,
28883
+ {
28884
+ messageFingerprint: command.messageFingerprint,
28885
+ messageId: command.messageId,
28886
+ messageOccurrence: command.messageOccurrence
28887
+ }
28888
+ );
28889
+ signal.throwIfAborted();
28890
+ const ready = {
28891
+ additionalDirectories: additionalEntries,
28892
+ agentId: command.agentId,
28893
+ agentName: opened.prepared.agentName,
28894
+ agentVersion: opened.prepared.agentVersion,
28895
+ capabilities: opened.prepared.capabilities,
28896
+ commands: opened.prepared.commands,
28897
+ configOptions: opened.prepared.configOptions,
28898
+ directoryId: command.directoryId,
28899
+ directoryLabel: listing.directoryLabel,
28900
+ kind: "session.ready",
28901
+ requestId: command.requestId,
28902
+ restored: true,
28903
+ sessionId: opened.sessionId
28904
+ };
28905
+ this.#audit.record({
28906
+ commandId: command.requestId,
28907
+ details: {
28908
+ agentId: command.agentId,
28909
+ messageId: command.messageId,
28910
+ sourceCommandId: command.sourceCommandId
28911
+ },
28912
+ event: "native_session_turn_forked",
28913
+ sessionId: command.sessionId
28914
+ });
28915
+ return {
28916
+ action: "fork",
28917
+ history: opened.history,
28918
+ kind: "native.session.result",
28919
+ ready,
28920
+ requestId: command.requestId,
28921
+ sessionId: command.sessionId,
28922
+ targetSessionId: command.targetSessionId,
28923
+ title: command.title
28924
+ };
28925
+ };
28482
28926
  authorizeEntry = async (command, options, info) => {
28483
28927
  try {
28484
28928
  const primary = await this.#directories.identify(command.bindingId, info.cwd, options.signal);
@@ -29043,6 +29487,11 @@ var SessionEventOutbox = class {
29043
29487
  }
29044
29488
  this.#database.prepare(`UPDATE relay_turn_receipts SET state = ? WHERE command_id = ?`).run(state, commandId);
29045
29489
  };
29490
+ isTerminalExecution = (commandId, sessionId) => {
29491
+ const row = this.#database.prepare(`SELECT state FROM relay_turn_receipts WHERE command_id = ? AND session_id = ?`).get(commandId, sessionId);
29492
+ const state = typeof row === "object" && row !== null ? Reflect.get(row, "state") : void 0;
29493
+ return state === "succeeded" || state === "failed" || state === "cancelled";
29494
+ };
29046
29495
  acknowledge = (acknowledgement) => {
29047
29496
  this.#database.prepare(
29048
29497
  `DELETE FROM relay_session_event_outbox
@@ -29555,7 +30004,7 @@ async function resolveSessionDirectories(directories, bindingId, directoryId2, a
29555
30004
  additionalDirectories.push(directory);
29556
30005
  additionalDirectoryEntries.push({
29557
30006
  directoryId: additionalDirectoryId,
29558
- name: basename3(directory)
30007
+ name: basename4(directory)
29559
30008
  });
29560
30009
  }
29561
30010
  return new ResolvedSessionDirectories(cwd, additionalDirectories, additionalDirectoryEntries);
@@ -29704,68 +30153,96 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
29704
30153
  attemptId
29705
30154
  );
29706
30155
  const attachments = await resolvePromptArtifacts(config2, command, signal);
29707
- const result = await sessions.runPrompt({
29708
- additionalDirectories: resolved.additionalDirectories,
29709
- agent,
29710
- commandId: command.commandId,
29711
- cwd: resolved.cwd,
29712
- images: attachments.images,
29713
- onActivity: (activity) => {
29714
- publisher.recordAttempt(
29715
- command.sessionId,
29716
- encodeAgentActivityEvent({
29717
- activityId: activity.activityId,
29718
- activityType: activity.activityType,
29719
- commandId: command.commandId,
29720
- content: activity.content,
29721
- detail: activity.detail,
29722
- input: activity.input,
29723
- kind: AGENT_ACTIVITY_KIND,
29724
- locations: activity.locations,
29725
- name: activity.name,
29726
- output: activity.output,
29727
- sequence: activity.sequence,
29728
- sessionId: command.sessionId,
29729
- status: activity.status,
29730
- title: activity.title,
29731
- toolKind: activity.toolKind
29732
- }),
29733
- attemptId
29734
- );
29735
- return Promise.resolve();
29736
- },
29737
- onText: (text) => {
29738
- publisher.recordAttempt(
29739
- command.sessionId,
29740
- encodeAgentMessageDeltaEvent({
29741
- commandId: command.commandId,
29742
- kind: AGENT_MESSAGE_DELTA_KIND,
29743
- sessionId: command.sessionId,
29744
- text
29745
- }),
29746
- attemptId
30156
+ let stopReason;
30157
+ if (command.disposition === SessionPromptDisposition.Steer) {
30158
+ await sessions.steerPrompt({
30159
+ additionalDirectories: resolved.additionalDirectories,
30160
+ agent,
30161
+ commandId: command.commandId,
30162
+ cwd: resolved.cwd,
30163
+ images: attachments.images,
30164
+ prompt: command.prompt,
30165
+ resources: attachments.resources,
30166
+ sessionId: command.sessionId,
30167
+ signal,
30168
+ targetCommandId: command.targetCommandId
30169
+ });
30170
+ stopReason = "steered";
30171
+ } else {
30172
+ if (command.disposition === SessionPromptDisposition.Queue && !outbox.isTerminalExecution(command.targetCommandId, command.sessionId)) {
30173
+ throw new AcpExecutionError(
30174
+ "The queued prompt target is unavailable",
30175
+ {},
30176
+ "prompt_queue_target_unavailable"
29747
30177
  );
29748
- return Promise.resolve();
29749
- },
29750
- prompt: command.prompt,
29751
- resources: attachments.resources,
29752
- sessionId: command.sessionId,
29753
- signal
29754
- });
30178
+ }
30179
+ const result = await sessions.runPrompt({
30180
+ additionalDirectories: resolved.additionalDirectories,
30181
+ agent,
30182
+ commandId: command.commandId,
30183
+ cwd: resolved.cwd,
30184
+ disposition: command.disposition,
30185
+ images: attachments.images,
30186
+ onActivity: (activity) => {
30187
+ publisher.recordAttempt(
30188
+ command.sessionId,
30189
+ encodeAgentActivityEvent({
30190
+ activityId: activity.activityId,
30191
+ activityType: activity.activityType,
30192
+ commandId: command.commandId,
30193
+ content: activity.content,
30194
+ detail: activity.detail,
30195
+ input: activity.input,
30196
+ kind: AGENT_ACTIVITY_KIND,
30197
+ locations: activity.locations,
30198
+ name: activity.name,
30199
+ output: activity.output,
30200
+ sequence: activity.sequence,
30201
+ sessionId: command.sessionId,
30202
+ status: activity.status,
30203
+ title: activity.title,
30204
+ toolKind: activity.toolKind
30205
+ }),
30206
+ attemptId
30207
+ );
30208
+ return Promise.resolve();
30209
+ },
30210
+ onText: (text, messageId) => {
30211
+ publisher.recordAttempt(
30212
+ command.sessionId,
30213
+ encodeAgentMessageDeltaEvent({
30214
+ commandId: command.commandId,
30215
+ kind: AGENT_MESSAGE_DELTA_KIND,
30216
+ messageId,
30217
+ sessionId: command.sessionId,
30218
+ text
30219
+ }),
30220
+ attemptId
30221
+ );
30222
+ return Promise.resolve();
30223
+ },
30224
+ prompt: command.prompt,
30225
+ resources: attachments.resources,
30226
+ sessionId: command.sessionId,
30227
+ signal,
30228
+ targetCommandId: command.targetCommandId
30229
+ });
30230
+ stopReason = result.stopReason;
30231
+ }
29755
30232
  publisher.recordAttempt(
29756
30233
  command.sessionId,
29757
30234
  encodeSessionCompletedEvent({
29758
30235
  commandId: command.commandId,
29759
30236
  kind: SESSION_COMPLETED_KIND,
29760
30237
  sessionId: command.sessionId,
29761
- stopReason: result.stopReason
30238
+ stopReason
29762
30239
  }),
29763
30240
  attemptId
29764
30241
  );
29765
- outbox.transitionExecution(command.commandId, result.stopReason === "cancelled" ? "cancelled" : "succeeded");
30242
+ outbox.transitionExecution(command.commandId, stopReason === "cancelled" ? "cancelled" : "succeeded");
29766
30243
  audit.record({
29767
30244
  commandId: command.commandId,
29768
- event: result.stopReason === "cancelled" ? "session_cancelled" : "session_completed",
30245
+ event: stopReason === "cancelled" ? "session_cancelled" : "session_completed",
29769
30246
  sessionId: command.sessionId
29770
30247
  });
29771
30248
  } catch (error61) {
@@ -29809,12 +30286,6 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
29809
30286
  }
29810
30287
  async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, signal) {
29811
30288
  try {
29812
- if (command.additionalDirectoryIds.length > 0) {
29813
- throw new LocalBotAuthorizationError(
29814
- "binding_not_approved",
29815
- "The Bot execution requested unapproved additional directories"
29816
- );
29817
- }
29818
30289
  const deadline = Date.parse(command.executionDeadlineAt);
29819
30290
  const remaining = deadline - Date.now();
29820
30291
  if (!Number.isSafeInteger(deadline) || remaining <= 0) {
@@ -29828,6 +30299,32 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29828
30299
  Date.now(),
29829
30300
  signal
29830
30301
  );
30302
+ const requestedExecutionTargetOverridden = command.directoryId !== grant.workspaceRef || command.additionalDirectoryIds.length > 0;
30303
+ if (requestedExecutionTargetOverridden && !(command.replyRoute instanceof OwnerBotReplyRoute)) {
30304
+ throw new LocalBotAuthorizationError(
30305
+ "binding_not_approved",
30306
+ "Only a paired owner conversation can override the approved Bot workspace"
30307
+ );
30308
+ }
30309
+ const resolved = await resolveSessionDirectories(
30310
+ directories,
30311
+ config2.bindingId,
30312
+ command.directoryId,
30313
+ command.additionalDirectoryIds,
30314
+ signal
30315
+ );
30316
+ const executionTargetOverridden = resolved.cwd !== grant.workspacePath || resolved.additionalDirectories.length > 0;
30317
+ if (executionTargetOverridden && !sessions.hasPreparedSession(
30318
+ command.sessionId,
30319
+ grant.agent.id,
30320
+ resolved.cwd,
30321
+ resolved.additionalDirectories
30322
+ )) {
30323
+ throw new LocalBotAuthorizationError(
30324
+ "local_approval_required",
30325
+ "Open this Bot conversation in the paired local Web app before using the selected workspace"
30326
+ );
30327
+ }
29831
30328
  const attachments = await resolvePromptArtifacts(config2, command, signal);
29832
30329
  const applied = await applicator.apply(command, grant, attachments.resources, signal);
29833
30330
  const lease = contexts.activate(command);
@@ -29835,6 +30332,13 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29835
30332
  const deadlineSignal = AbortSignal.timeout(remaining);
29836
30333
  const executionSignal = AbortSignal.any([signal, deadlineSignal]);
29837
30334
  const agent = botExecutionAgent(grant.agent, lease.context.contextHandle, ipc.socketPath);
30335
+ await sessions.prepareExecutionSession({
30336
+ additionalDirectories: resolved.additionalDirectories,
30337
+ agent,
30338
+ cwd: resolved.cwd,
30339
+ sessionId: command.sessionId,
30340
+ signal: executionSignal
30341
+ });
29838
30342
  audit.record({
29839
30343
  commandId: command.commandId,
29840
30344
  details: {
@@ -29859,10 +30363,10 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29859
30363
  attemptId
29860
30364
  );
29861
30365
  const result = await sessions.runPrompt({
29862
- additionalDirectories: [],
30366
+ additionalDirectories: resolved.additionalDirectories,
29863
30367
  agent,
29864
30368
  commandId: command.commandId,
29865
- cwd: grant.workspacePath,
30369
+ cwd: resolved.cwd,
29866
30370
  images: attachments.images,
29867
30371
  onActivity: (activity) => {
29868
30372
  publisher.recordAttempt(
@@ -29888,12 +30392,13 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
29888
30392
  );
29889
30393
  return Promise.resolve();
29890
30394
  },
29891
- onText: (text) => {
30395
+ onText: (text, messageId) => {
29892
30396
  publisher.recordAttempt(
29893
30397
  command.sessionId,
29894
30398
  encodeAgentMessageDeltaEvent({
29895
30399
  commandId: command.commandId,
29896
30400
  kind: AGENT_MESSAGE_DELTA_KIND,
30401
+ messageId,
29897
30402
  sessionId: command.sessionId,
29898
30403
  text
29899
30404
  }),
@@ -30088,6 +30593,14 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30088
30593
  }
30089
30594
  return;
30090
30595
  }
30596
+ if (kind === BOT_BINDING_APPROVED_KIND) {
30597
+ decodeBotExecutionBindingApprovedEvent(encoded);
30598
+ return;
30599
+ }
30600
+ if (kind === BOT_BINDING_UNAVAILABLE_KIND) {
30601
+ decodeBotExecutionBindingUnavailableEvent(encoded);
30602
+ return;
30603
+ }
30091
30604
  if (kind === BOT_BINDING_APPROVAL_REQUEST_KIND) {
30092
30605
  const request = decodeBotExecutionBindingApprovalRequest(encoded);
30093
30606
  void grants.approve(
@@ -30240,6 +30753,86 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30240
30753
  });
30241
30754
  return;
30242
30755
  }
30756
+ if (kind === NATIVE_SESSION_TURN_FORK_KIND) {
30757
+ const command2 = decodeNativeSessionTurnForkCommand(encoded);
30758
+ const previous = [...nativeRequests.values()].filter(
30759
+ (request) => request.agentId === command2.agentId
30760
+ );
30761
+ if (nativeRequests.has(command2.requestId) || nativeRequests.size >= 4 || previous.some((request) => !request.controller.signal.aborted)) {
30762
+ reportOperationFailure(
30763
+ socket,
30764
+ command2.requestId,
30765
+ command2.sessionId,
30766
+ new AcpExecutionError(
30767
+ "Another native session operation is running",
30768
+ {},
30769
+ "native_session_busy"
30770
+ )
30771
+ );
30772
+ return;
30773
+ }
30774
+ const controller = new AbortController();
30775
+ const operationSignal = AbortSignal.any([connectionController.signal, controller.signal]);
30776
+ const timeout = timer.schedule(
30777
+ () => controller.abort(
30778
+ new AcpExecutionError(
30779
+ "Native session operation timed out",
30780
+ {},
30781
+ "native_session_timeout"
30782
+ )
30783
+ ),
30784
+ 9e4
30785
+ );
30786
+ const completion = (async () => {
30787
+ try {
30788
+ operationSignal.throwIfAborted();
30789
+ if (previous.length > 0) {
30790
+ await new Promise((resolve6, reject2) => {
30791
+ const cancel = () => reject2(operationSignal.reason);
30792
+ operationSignal.addEventListener("abort", cancel, { once: true });
30793
+ void Promise.all(previous.map((request) => request.completion)).then(
30794
+ () => {
30795
+ operationSignal.removeEventListener("abort", cancel);
30796
+ resolve6();
30797
+ },
30798
+ (error61) => {
30799
+ operationSignal.removeEventListener("abort", cancel);
30800
+ reject2(error61);
30801
+ }
30802
+ );
30803
+ });
30804
+ }
30805
+ operationSignal.throwIfAborted();
30806
+ const result = await nativeSessions.executeTurnFork(command2, operationSignal);
30807
+ if (socket.readyState === WebSocket2.OPEN) {
30808
+ send(socket, encodeNativeSessionResult(result));
30809
+ }
30810
+ } catch (error61) {
30811
+ const failure = operationSignal.aborted ? controller.signal.reason ?? new AcpExecutionError(
30812
+ "Native session operation cancelled",
30813
+ {},
30814
+ "native_session_cancelled"
30815
+ ) : error61;
30816
+ audit.record({
30817
+ commandId: command2.requestId,
30818
+ details: { code: operationErrorCode(failure) },
30819
+ event: "native_session_turn_fork_failed",
30820
+ sessionId: command2.sessionId
30821
+ });
30822
+ reportOperationFailure(socket, command2.requestId, command2.sessionId, failure);
30823
+ } finally {
30824
+ timeout.cancel();
30825
+ nativeRequests.delete(command2.requestId);
30826
+ }
30827
+ })();
30828
+ nativeRequests.set(command2.requestId, {
30829
+ agentId: command2.agentId,
30830
+ completion,
30831
+ controller,
30832
+ sessionId: command2.sessionId
30833
+ });
30834
+ return;
30835
+ }
30243
30836
  if (kind === RUNTIME_LIVENESS_READY_KIND) {
30244
30837
  decodeRuntimeLivenessReadyEvent(encoded);
30245
30838
  connectionLiveness.start();
@@ -30393,7 +30986,9 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30393
30986
  }
30394
30987
  const turn = new ActiveDaemonTurn(command, receipt.attemptId);
30395
30988
  activeTurns.set(command.commandId, turn);
30396
- activeAttemptIds.set(command.sessionId, receipt.attemptId);
30989
+ if (command.disposition !== SessionPromptDisposition.Steer) {
30990
+ activeAttemptIds.set(command.sessionId, receipt.attemptId);
30991
+ }
30397
30992
  void executePrompt(
30398
30993
  outbox,
30399
30994
  publisher,