acpx 0.17.1 → 0.19.0

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 (42) hide show
  1. package/dist/{agent-registry-evUoJINz.js → agent-registry-Ct2yWPW7.js} +18 -18
  2. package/dist/agent-registry-Ct2yWPW7.js.map +1 -0
  3. package/dist/agent-registry.d.ts.map +1 -1
  4. package/dist/agent-registry.js +1 -1
  5. package/dist/{cli-B2ycKzaO.js → cli-Bmki9cj5.js} +5 -5
  6. package/dist/{cli-B2ycKzaO.js.map → cli-Bmki9cj5.js.map} +1 -1
  7. package/dist/cli.js +167 -131
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{client-CkbBoSGd.d.ts → client-DzaUKazo.d.ts} +23 -15
  10. package/dist/client-DzaUKazo.d.ts.map +1 -0
  11. package/dist/{flows-xGyhmoHO.js → flows--Ewxn_fQ.js} +665 -460
  12. package/dist/flows--Ewxn_fQ.js.map +1 -0
  13. package/dist/flows.d.ts +19 -7
  14. package/dist/flows.d.ts.map +1 -1
  15. package/dist/flows.js +1 -1
  16. package/dist/{invocation-options-eZEBh4vd.js → invocation-options-DRzxHZmr.js} +34 -12
  17. package/dist/invocation-options-DRzxHZmr.js.map +1 -0
  18. package/dist/{ipc-D2rstexT.js → ipc-fMgj5vrO.js} +818 -302
  19. package/dist/ipc-fMgj5vrO.js.map +1 -0
  20. package/dist/{output-D2xL6wdp.js → output-D6kDs8BN.js} +30 -14
  21. package/dist/output-D6kDs8BN.js.map +1 -0
  22. package/dist/{queue-owner-runtime-Dgv3e2Tp.js → queue-owner-runtime-CLwjMYNL.js} +1448 -717
  23. package/dist/queue-owner-runtime-CLwjMYNL.js.map +1 -0
  24. package/dist/runtime.d.ts +57 -3
  25. package/dist/runtime.d.ts.map +1 -1
  26. package/dist/runtime.js +303 -166
  27. package/dist/runtime.js.map +1 -1
  28. package/dist/session-options-Dp6mzNSy.d.ts.map +1 -1
  29. package/dist/{session-watch-CTe9Bnbc.js → session-watch-BfdIo-n4.js} +5 -5
  30. package/dist/session-watch-BfdIo-n4.js.map +1 -0
  31. package/dist/{watch-DxuXEc8n.js → watch-DH-zXQH2.js} +2 -2
  32. package/dist/{watch-DxuXEc8n.js.map → watch-DH-zXQH2.js.map} +1 -1
  33. package/package.json +1 -1
  34. package/skills/acpx/SKILL.md +69 -30
  35. package/dist/agent-registry-evUoJINz.js.map +0 -1
  36. package/dist/client-CkbBoSGd.d.ts.map +0 -1
  37. package/dist/flows-xGyhmoHO.js.map +0 -1
  38. package/dist/invocation-options-eZEBh4vd.js.map +0 -1
  39. package/dist/ipc-D2rstexT.js.map +0 -1
  40. package/dist/output-D2xL6wdp.js.map +0 -1
  41. package/dist/queue-owner-runtime-Dgv3e2Tp.js.map +0 -1
  42. package/dist/session-watch-CTe9Bnbc.js.map +0 -1
package/dist/runtime.js CHANGED
@@ -1,7 +1,7 @@
1
- import { C as textPrompt, D as findSession, F as resolveSessionRecord, L as assertPersistedKeyPolicy, P as readSessionRecord, R as parseSessionRecord, gt as extractAcpError, mt as writePrivateJsonFile, t as tryCancelOnRunningOwner, vt as isAcpResourceNotFoundError, z as serializeSessionRecordForDisk } from "./ipc-D2rstexT.js";
2
- import { A as setDesiredModeId, B as recordClientOperation, C as withConnectedSession, D as applyInitialModelSelection, E as applyConfigOptionsToRecord, F as createInitialSessionRecord, G as AcpClient, H as recordSessionUpdate, I as reconcileAgentSessionId, J as RequestedModelUnsupportedError, K as REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE, L as cloneSessionAcpxState, M as sessionOptionsFromRecord, N as applyConversation, O as applyModelSelection, P as applyLifecycleSnapshotToRecord, R as cloneSessionConversation, T as applyConfigOptionSelection, U as trimConversationForRuntime, V as recordPromptSubmission, W as advertisedModelState, Y as isRequestedModelUnsupportedError, _ as cancelSessionPrompt, c as LiveSessionCheckpoint, ct as normalizeOutputError, h as ensureSession, it as assertControlAuthority, j as persistSessionOptions, k as applyRequestedModelIfAdvertised, l as runPromptTurn, ot as withTimeout, q as REQUESTED_MODEL_UNSUPPORTED_REASONS, r as sendSession, rt as TimeoutError, s as DISCARD_OUTPUT_FORMATTER, v as closeSession, w as connectAndLoadSession } from "./queue-owner-runtime-Dgv3e2Tp.js";
3
- import { D as normalizeAgentCommandInput, a as createAgentRegistry, i as DEFAULT_AGENT_NAME } from "./agent-registry-evUoJINz.js";
4
- import { t as watchSession } from "./watch-DxuXEc8n.js";
1
+ import { D as textPrompt, H as parseSessionRecord, R as readSessionRecord, St as isAcpResourceNotFoundError, U as serializeSessionRecordForDisk, V as assertPersistedKeyPolicy, bt as extractAcpError, j as findSession, t as tryCancelOnRunningOwner, vt as writePrivateJsonFile, z as resolveSessionRecord } from "./ipc-fMgj5vrO.js";
2
+ import { A as applyInitialModelSelection, B as cloneSessionAcpxState, C as setSessionModeOnOwner, D as connectAndLoadSession, E as withConnectedSession, F as sessionOptionsFromRecord, G as recordSessionUpdate, I as applyConversation, J as AcpClient, K as trimConversationForRuntime, L as applyLifecycleSnapshotToRecord, M as applyRequestedModelIfAdvertised, N as setDesiredModeId, O as applyConfigOptionSelection, P as persistSessionOptions, Q as isRequestedModelUnsupportedError, R as createInitialSessionRecord, T as setSessionModelOnOwner, U as recordClientOperation, V as cloneSessionConversation, W as recordPromptSubmission, X as REQUESTED_MODEL_UNSUPPORTED_REASONS, Y as REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE, Z as RequestedModelUnsupportedError, _ as cancelSessionPrompt, c as LiveSessionCheckpoint, dt as normalizeOutputError, h as ensureSession, j as applyModelSelection, k as applyConfigOptionsToRecord, l as runPromptTurn, lt as withTimeout, ot as TimeoutError, q as advertisedModelState, r as sendSession, s as DISCARD_OUTPUT_FORMATTER, st as assertControlAuthority, v as closeSession, x as setSessionConfigOptionOnOwner, z as reconcileAgentSessionId } from "./queue-owner-runtime-CLwjMYNL.js";
3
+ import { D as normalizeAgentCommandInput, a as createAgentRegistry, i as DEFAULT_AGENT_NAME } from "./agent-registry-Ct2yWPW7.js";
4
+ import { t as watchSession } from "./watch-DH-zXQH2.js";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import path from "node:path";
7
7
  import fs from "node:fs/promises";
@@ -22,6 +22,35 @@ function isAcpRuntimeError(value) {
22
22
  return value instanceof AcpRuntimeError;
23
23
  }
24
24
  //#endregion
25
+ //#region src/runtime/engine/controls.ts
26
+ function advertisedConfigOptionIds(record) {
27
+ const configOptions = record?.acpx?.config_options;
28
+ if (!configOptions) return;
29
+ return new Set(configOptions.map((option) => option.id).filter((id) => typeof id === "string" && id.trim().length > 0));
30
+ }
31
+ /** Caller-owned controls and the config option keys the session last advertised. */
32
+ function capabilitiesFromRecord(record) {
33
+ const advertisedIds = advertisedConfigOptionIds(record);
34
+ return {
35
+ controls: [
36
+ "session/set_mode",
37
+ "session/set_model",
38
+ "session/set_config_option",
39
+ "session/status"
40
+ ],
41
+ ...advertisedIds?.size ? { configOptionKeys: [...advertisedIds] } : {}
42
+ };
43
+ }
44
+ function resolveSupportedConfigOptionId(record, configId) {
45
+ const advertisedIds = advertisedConfigOptionIds(record);
46
+ if (!advertisedIds) return configId;
47
+ if (advertisedIds.has(configId)) return configId;
48
+ if (configId === "thinking" && advertisedIds.has("effort")) return "effort";
49
+ const supported = [...advertisedIds].toSorted();
50
+ const supportedText = supported.length > 0 ? supported.join(", ") : "none";
51
+ throw new AcpRuntimeError("ACP_BACKEND_UNSUPPORTED_CONTROL", `ACP session ${record.acpxRecordId} does not advertise config option '${configId}'. Supported config options: ${supportedText}.`);
52
+ }
53
+ //#endregion
25
54
  //#region src/runtime/public/shared.ts
26
55
  function isRecord(value) {
27
56
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -333,6 +362,7 @@ const TOOL_KINDS = /* @__PURE__ */ new Set([
333
362
  "execute",
334
363
  "fetch",
335
364
  "think",
365
+ "switch_mode",
336
366
  "other"
337
367
  ]);
338
368
  function createToolCallEvent(params) {
@@ -673,6 +703,8 @@ function createProbeClient(options, agentName, agentCommand, deps) {
673
703
  permissionMode: options.permissionMode,
674
704
  nonInteractivePermissions: options.nonInteractivePermissions,
675
705
  permissionPolicy: options.permissionPolicy,
706
+ fs: false,
707
+ terminal: false,
676
708
  processLifecycle: options.processLifecycle,
677
709
  processLaunchScope: {
678
710
  kind: "runtime-probe",
@@ -833,10 +865,15 @@ var AsyncEventQueue = class {
833
865
  return await waiter.promise;
834
866
  }
835
867
  async *iterate() {
836
- while (true) {
837
- const next = await this.next();
838
- if (!next) return;
839
- yield next;
868
+ try {
869
+ while (true) {
870
+ const next = await this.next();
871
+ if (!next) return;
872
+ yield next;
873
+ }
874
+ } finally {
875
+ this.close();
876
+ this.clear();
840
877
  }
841
878
  }
842
879
  };
@@ -918,20 +955,6 @@ function createRecordId(sessionKey, mode) {
918
955
  function resumePolicyForSessionMode(mode) {
919
956
  return mode === "persistent" ? "same-session-only" : "allow-new";
920
957
  }
921
- function advertisedConfigOptionIds(record) {
922
- const configOptions = record.acpx?.config_options;
923
- if (!configOptions) return;
924
- return new Set(configOptions.map((option) => option.id).filter((id) => typeof id === "string" && id.trim().length > 0));
925
- }
926
- function resolveSupportedConfigOptionId(record, configId) {
927
- const advertisedIds = advertisedConfigOptionIds(record);
928
- if (!advertisedIds) return configId;
929
- if (advertisedIds.has(configId)) return configId;
930
- if (configId === "thinking" && advertisedIds.has("effort")) return "effort";
931
- const supported = [...advertisedIds].toSorted();
932
- const supportedText = supported.length > 0 ? supported.join(", ") : "none";
933
- throw new AcpRuntimeError("ACP_BACKEND_UNSUPPORTED_CONTROL", `ACP session ${record.acpxRecordId} does not advertise config option '${configId}'. Supported config options: ${supportedText}.`);
934
- }
935
958
  async function startRuntimeClient(client, agent, timeoutMs) {
936
959
  try {
937
960
  await withTimeout(client.start(), timeoutMs);
@@ -969,6 +992,7 @@ var AcpRuntimeManager = class {
969
992
  options;
970
993
  deps;
971
994
  activeControllers = /* @__PURE__ */ new Map();
995
+ controlSessionRecords = /* @__PURE__ */ new Map();
972
996
  retainedSessionOwners = /* @__PURE__ */ new Map();
973
997
  retiringSessionOwners = /* @__PURE__ */ new Set();
974
998
  pendingOneShotRecordIds = /* @__PURE__ */ new Map();
@@ -1085,6 +1109,7 @@ var AcpRuntimeManager = class {
1085
1109
  createSessionOwner(input) {
1086
1110
  const owner = {
1087
1111
  ...input,
1112
+ checkpoint: new LiveSessionCheckpoint({ save: async () => this.saveSessionOwner(owner) }),
1088
1113
  bufferSessionUpdates: false,
1089
1114
  pendingSessionUpdates: []
1090
1115
  };
@@ -1094,13 +1119,26 @@ var AcpRuntimeManager = class {
1094
1119
  });
1095
1120
  return owner;
1096
1121
  }
1122
+ async saveSessionOwner(owner) {
1123
+ if (!owner.recordId) return;
1124
+ const active = owner.activeTurn?.turn;
1125
+ const target = active ?? owner.projection;
1126
+ if (!target) return;
1127
+ const { record, conversation } = target;
1128
+ if (active?.connected) record.acpx = active.acpxState;
1129
+ record.lastUsedAt = isoNow();
1130
+ applyConversation(record, conversation);
1131
+ applyLifecycleSnapshotToRecord(record, owner.client.getAgentLifecycleSnapshot());
1132
+ await this.refreshClosedState(record);
1133
+ await this.options.sessionStore.save(record);
1134
+ }
1097
1135
  routeOwnedSessionUpdate(owner, notification) {
1098
1136
  const active = owner.activeTurn;
1099
1137
  if (active) {
1100
1138
  const { task, turn } = active;
1101
1139
  if (turn.connected) {
1102
1140
  turn.acpxState = recordSessionUpdate(turn.conversation, turn.acpxState, notification);
1103
- turn.liveCheckpoint.request();
1141
+ owner.checkpoint.request();
1104
1142
  } else turn.record.acpx = recordSessionUpdate(turn.conversation, turn.record.acpx, notification);
1105
1143
  trimConversationForRuntime(turn.conversation);
1106
1144
  this.emitRuntimeTurnEvent(task, {
@@ -1121,7 +1159,7 @@ var AcpRuntimeManager = class {
1121
1159
  }
1122
1160
  projection.record.acpx = recordSessionUpdate(projection.conversation, projection.record.acpx, notification);
1123
1161
  trimConversationForRuntime(projection.conversation);
1124
- projection.checkpoint.request();
1162
+ owner.checkpoint.request();
1125
1163
  }
1126
1164
  routeOwnedClientOperation(owner, operation) {
1127
1165
  const active = owner.activeTurn;
@@ -1129,7 +1167,7 @@ var AcpRuntimeManager = class {
1129
1167
  const { task, turn } = active;
1130
1168
  if (turn.connected) {
1131
1169
  turn.acpxState = recordClientOperation(turn.conversation, turn.acpxState, operation);
1132
- turn.liveCheckpoint.request();
1170
+ owner.checkpoint.request();
1133
1171
  } else turn.record.acpx = recordClientOperation(turn.conversation, turn.record.acpx, operation);
1134
1172
  trimConversationForRuntime(turn.conversation);
1135
1173
  this.emitRuntimeTurnEvent(task, {
@@ -1141,15 +1179,7 @@ var AcpRuntimeManager = class {
1141
1179
  record.acpx = acpxState;
1142
1180
  owner.projection = {
1143
1181
  record,
1144
- conversation,
1145
- checkpoint: new LiveSessionCheckpoint({ save: async () => {
1146
- if (!owner.recordId) return;
1147
- record.lastUsedAt = isoNow();
1148
- applyConversation(record, conversation);
1149
- applyLifecycleSnapshotToRecord(record, owner.client.getAgentLifecycleSnapshot());
1150
- await this.refreshClosedState(record);
1151
- await this.options.sessionStore.save(record);
1152
- } })
1182
+ conversation
1153
1183
  };
1154
1184
  owner.activeTurn = void 0;
1155
1185
  this.drainPendingSessionUpdates(owner);
@@ -1159,7 +1189,7 @@ var AcpRuntimeManager = class {
1159
1189
  }
1160
1190
  async flushSessionOwner(owner) {
1161
1191
  await owner.client.waitForSessionUpdatesIdle?.().catch(() => {});
1162
- await owner.projection?.checkpoint.flush();
1192
+ await owner.checkpoint.flush();
1163
1193
  }
1164
1194
  removeRetainedSessionOwner(owner) {
1165
1195
  if (owner.recordId && this.retainedSessionOwners.get(owner.recordId) === owner) this.retainedSessionOwners.delete(owner.recordId);
@@ -1190,7 +1220,7 @@ var AcpRuntimeManager = class {
1190
1220
  await this.closeClient(owner.client).catch(() => {
1191
1221
  this.retiringSessionOwners.add(owner);
1192
1222
  });
1193
- await owner.projection?.checkpoint.flush().catch(() => {});
1223
+ await owner.checkpoint.flush().catch(() => {});
1194
1224
  try {
1195
1225
  owner.client.clearEventHandlers();
1196
1226
  } catch {}
@@ -1213,19 +1243,15 @@ var AcpRuntimeManager = class {
1213
1243
  const latest = await this.options.sessionStore.load(record.acpxRecordId).catch(() => void 0);
1214
1244
  record.closed = true;
1215
1245
  record.closedAt = latest?.closedAt ?? record.closedAt ?? isoNow();
1216
- if (latest?.acpx) record.acpx = {
1246
+ if (latest?.acpx?.reset_on_next_ensure === true) record.acpx = {
1217
1247
  ...record.acpx,
1218
- ...latest.acpx
1248
+ reset_on_next_ensure: true
1219
1249
  };
1220
1250
  return true;
1221
1251
  }
1222
1252
  async retainPersistentSessionOwnerAfterTurn(input) {
1223
- const { record, owner, conversation, acpxState } = input;
1224
- if (!this.canRetainPersistentSessionOwner(owner, record)) {
1225
- owner.activeTurn = void 0;
1226
- return false;
1227
- }
1228
- this.attachIdleProjection(owner, record, conversation, acpxState);
1253
+ const { record, owner } = input;
1254
+ if (!this.canRetainPersistentSessionOwner(owner, record)) return false;
1229
1255
  const previousOwner = this.retainedSessionOwners.get(record.acpxRecordId);
1230
1256
  this.retainedSessionOwners.set(record.acpxRecordId, owner);
1231
1257
  if (previousOwner && previousOwner !== owner) {
@@ -1262,7 +1288,13 @@ var AcpRuntimeManager = class {
1262
1288
  onPermissionRequest
1263
1289
  };
1264
1290
  }
1265
- async withRuntimeControlSession(record, sessionMode, run, replacingConfigOption, authority) {
1291
+ resolveClientCapabilities() {
1292
+ return {
1293
+ fs: this.options.fs,
1294
+ terminal: this.options.terminal
1295
+ };
1296
+ }
1297
+ async withRuntimeControlSession(record, sessionMode, run, replayOptions, authority) {
1266
1298
  await this.retrySessionCleanup(record.acpxRecordId);
1267
1299
  assertControlAuthority(authority);
1268
1300
  const owner = await this.readRetainedSessionOwner(record, { consume: false });
@@ -1276,20 +1308,27 @@ var AcpRuntimeManager = class {
1276
1308
  record: ownedRecord
1277
1309
  });
1278
1310
  this.refreshOwnedRecordLifecycle(owner, ownedRecord);
1279
- return {
1280
- value,
1281
- record: ownedRecord
1282
- };
1311
+ await owner.checkpoint.checkpoint();
1312
+ return value;
1283
1313
  } finally {
1284
1314
  await this.flushSessionOwner(owner);
1285
1315
  }
1286
1316
  }
1287
1317
  let controlClient;
1318
+ let controlledRecord = record;
1319
+ const checkpoint = new LiveSessionCheckpoint({ save: async () => this.options.sessionStore.save(controlledRecord) });
1288
1320
  try {
1289
- const result = await withConnectedSession({
1321
+ const result = await settleAttempt(async () => withConnectedSession({
1290
1322
  sessionRecordId: record.acpxRecordId,
1291
- loadRecord: async (sessionRecordId) => await this.requireRecord(sessionRecordId),
1292
- saveRecord: async (connectedRecord) => await this.options.sessionStore.save(connectedRecord),
1323
+ loadRecord: async (sessionRecordId) => {
1324
+ controlledRecord = await this.requireRecord(sessionRecordId);
1325
+ this.controlSessionRecords.set(sessionRecordId, {
1326
+ record: controlledRecord,
1327
+ checkpoint
1328
+ });
1329
+ return controlledRecord;
1330
+ },
1331
+ saveRecord: async () => checkpoint.checkpoint(),
1293
1332
  createClient: (options) => controlClient = this.createClient({
1294
1333
  ...options,
1295
1334
  processLifecycle: this.options.processLifecycle,
@@ -1306,19 +1345,23 @@ var AcpRuntimeManager = class {
1306
1345
  ...record,
1307
1346
  sessionKey: record.name ?? record.acpxRecordId
1308
1347
  }),
1348
+ ...this.resolveClientCapabilities(),
1309
1349
  elicitationModes: this.options.elicitationModes,
1310
1350
  verbose: this.options.verbose,
1311
1351
  timeoutMs: this.options.timeoutMs,
1312
1352
  resumePolicy: resumePolicyForSessionMode(sessionMode),
1313
- replacingConfigOption,
1353
+ replacingMode: replayOptions?.replacingMode,
1354
+ replacingConfigOption: replayOptions?.replacingConfigOption,
1314
1355
  authority,
1315
1356
  run
1316
- });
1317
- return {
1318
- value: result.value,
1319
- record: result.record
1320
- };
1357
+ }));
1358
+ this.controlSessionRecords.delete(record.acpxRecordId);
1359
+ const flushed = await settleAttempt(async () => checkpoint.flush());
1360
+ if (!result.ok) throw result.error;
1361
+ if (!flushed.ok) throw flushed.error;
1362
+ return result.value.value;
1321
1363
  } finally {
1364
+ this.controlSessionRecords.delete(record.acpxRecordId);
1322
1365
  if (controlClient) this.liveClients.delete(controlClient);
1323
1366
  }
1324
1367
  }
@@ -1340,6 +1383,13 @@ var AcpRuntimeManager = class {
1340
1383
  async withManagerLock(locks, key, run, authority) {
1341
1384
  this.assertOpen();
1342
1385
  assertControlAuthority(authority);
1386
+ return await this.serializeManagerOperation(locks, key, async () => {
1387
+ this.assertOpen();
1388
+ assertControlAuthority(authority);
1389
+ return await run();
1390
+ });
1391
+ }
1392
+ async serializeManagerOperation(locks, key, run) {
1343
1393
  const previous = locks.get(key) ?? Promise.resolve();
1344
1394
  let release;
1345
1395
  const gate = new Promise((resolve) => {
@@ -1349,8 +1399,6 @@ var AcpRuntimeManager = class {
1349
1399
  locks.set(key, tail);
1350
1400
  await previous;
1351
1401
  try {
1352
- this.assertOpen();
1353
- assertControlAuthority(authority);
1354
1402
  return await run();
1355
1403
  } finally {
1356
1404
  release();
@@ -1365,8 +1413,26 @@ var AcpRuntimeManager = class {
1365
1413
  agentCommand,
1366
1414
  agentArgv
1367
1415
  };
1416
+ if (input.mode !== "persistent") return await this.acquireRuntimeSession(input, agent);
1417
+ const compatible = await this.reuseCompatiblePersistentSession(input, agent);
1418
+ if (compatible) return compatible;
1419
+ this.assertRuntimeSessionIdle(input.sessionKey);
1420
+ return await this.withManagerLock(this.runtimeOperationLocks, input.sessionKey, async () => this.acquireRuntimeSession(input, agent));
1421
+ }
1422
+ async reuseCompatiblePersistentSession(input, agent) {
1423
+ const controlled = this.controlSessionRecords.get(input.sessionKey);
1424
+ const record = controlled ? controlled.record : await this.findSession(input.sessionKey);
1425
+ if (!record || this.closingActiveRecords.has(input.sessionKey) || !shouldReuseExistingRecord(record, {
1426
+ ...agent,
1427
+ resumeSessionId: input.resumeSessionId
1428
+ })) return;
1429
+ if (!record.closed) return record;
1430
+ if (!controlled || this.sessionTasks.has(input.sessionKey)) return;
1431
+ return await this.reuseRuntimeSession({ record }, controlled.checkpoint);
1432
+ }
1433
+ async acquireRuntimeSession(input, agent) {
1368
1434
  const existing = await this.loadExistingRuntimeSession(input);
1369
- if (existing && this.canReuseRuntimeSession(input, agent, existing)) return await this.reuseRuntimeSession(existing.record);
1435
+ if (existing && this.canReuseRuntimeSession(input, agent, existing)) return await this.reuseRuntimeSession(existing);
1370
1436
  await this.closeConflictingPersistentSession(input, existing?.owner);
1371
1437
  return await this.createOwnedRuntimeSession(input, agent);
1372
1438
  }
@@ -1397,17 +1463,26 @@ var AcpRuntimeManager = class {
1397
1463
  resumeSessionId: input.resumeSessionId
1398
1464
  })) return false;
1399
1465
  if (input.mode === "persistent") return true;
1400
- return Boolean(existing.owner && isDeepStrictEqual(sessionOptionsFromRecord(existing.record), input.sessionOptions));
1466
+ return Boolean(existing.owner && !existing.record.closed && this.pendingOneShotRecordIds.get(input.sessionKey) === existing.record.acpxRecordId && this.retainedSessionOwners.get(existing.record.acpxRecordId) === existing.owner && isDeepStrictEqual(sessionOptionsFromRecord(existing.record), input.sessionOptions));
1467
+ }
1468
+ assertRuntimeSessionIdle(recordId) {
1469
+ if (this.sessionTasks.has(recordId) || this.runtimeOperationLocks.has(recordId)) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", `Cannot ensure session ${recordId} with incompatible or closing state while its work is unfinished. Wait for existing work to finish or use a different session key.`);
1401
1470
  }
1402
- async reuseRuntimeSession(record) {
1471
+ async reuseRuntimeSession({ record, owner }, checkpoint = owner?.checkpoint) {
1472
+ if (!record.closed && !this.closingActiveRecords.has(record.acpxRecordId)) return record;
1403
1473
  record.closed = false;
1404
1474
  record.closedAt = void 0;
1405
1475
  this.closingActiveRecords.delete(record.acpxRecordId);
1406
- await this.options.sessionStore.save(record);
1476
+ if (checkpoint) await checkpoint.checkpoint();
1477
+ else await this.options.sessionStore.save(record);
1407
1478
  return record;
1408
1479
  }
1409
1480
  async closeConflictingPersistentSession(input, owner) {
1410
- if (input.mode === "persistent" && owner?.recordId) await this.closeRetainedSessionOwner(owner.recordId);
1481
+ if (input.mode === "persistent" && owner?.recordId) {
1482
+ this.removeRetainedSessionOwner(owner);
1483
+ this.retiringSessionOwners.add(owner);
1484
+ await this.retrySessionCleanup(owner.recordId);
1485
+ }
1411
1486
  }
1412
1487
  async createOwnedRuntimeSession(input, agent) {
1413
1488
  const { cwd, agentCommand, agentArgv } = agent;
@@ -1423,6 +1498,7 @@ var AcpRuntimeManager = class {
1423
1498
  ...agent,
1424
1499
  sessionKey: input.sessionKey
1425
1500
  }),
1501
+ ...this.resolveClientCapabilities(),
1426
1502
  elicitationModes: this.options.elicitationModes,
1427
1503
  processLifecycle: this.options.processLifecycle,
1428
1504
  processLaunchScope: {
@@ -1493,7 +1569,7 @@ var AcpRuntimeManager = class {
1493
1569
  }
1494
1570
  async retainInitializedSessionOwner(owner, record) {
1495
1571
  owner.recordId = record.acpxRecordId;
1496
- await owner.projection?.checkpoint.checkpoint();
1572
+ await owner.checkpoint.checkpoint();
1497
1573
  this.assertOpen();
1498
1574
  const previousOwner = this.retainedSessionOwners.get(record.acpxRecordId);
1499
1575
  this.retainedSessionOwners.set(record.acpxRecordId, owner);
@@ -1571,7 +1647,9 @@ var AcpRuntimeManager = class {
1571
1647
  }
1572
1648
  async closeRetainedOneShotHandle(handle) {
1573
1649
  const recordId = handle.acpxRecordId ?? handle.sessionKey;
1574
- if (this.retainedSessionOwners.get(recordId)?.mode === "oneshot") await this.closeRetainedSessionOwner(recordId);
1650
+ await this.serializeManagerOperation(this.runtimeOperationLocks, recordId, async () => {
1651
+ if (this.retainedSessionOwners.get(recordId)?.mode === "oneshot") await this.closeRetainedSessionOwner(recordId);
1652
+ });
1575
1653
  }
1576
1654
  async runRuntimeTurnTask(task) {
1577
1655
  let turn;
@@ -1594,7 +1672,7 @@ var AcpRuntimeManager = class {
1594
1672
  };
1595
1673
  else {
1596
1674
  const response = await this.runRuntimePrompt(task, turn, sessionId);
1597
- await this.saveCompletedRuntimeTurn(turn);
1675
+ this.updateCompletedRuntimeTurn(turn);
1598
1676
  terminalResult = {
1599
1677
  status: response.stopReason === "cancelled" ? "cancelled" : "completed",
1600
1678
  ...response.stopReason ? { stopReason: response.stopReason } : {},
@@ -1605,7 +1683,7 @@ var AcpRuntimeManager = class {
1605
1683
  } catch (error) {
1606
1684
  terminalResult = this.handleRuntimeTurnFailure(task, error);
1607
1685
  }
1608
- const finalization = await settleAttempt(async () => this.finalizeRuntimeTurn(task, turn));
1686
+ const finalization = await settleAttempt(async () => this.serializeManagerOperation(this.runtimeOperationLocks, task.input.handle.acpxRecordId ?? task.input.handle.sessionKey, async () => this.finalizeRuntimeTurn(task, turn)));
1609
1687
  if (!finalization.ok) {
1610
1688
  task.settleResult(this.failRuntimeTurn(task, finalization.error));
1611
1689
  throw finalization.error;
@@ -1613,22 +1691,18 @@ var AcpRuntimeManager = class {
1613
1691
  task.settleResult(terminalResult);
1614
1692
  }
1615
1693
  async runRuntimePrompt(task, turn, sessionId) {
1616
- try {
1617
- return await runPromptTurn({
1618
- client: turn.client,
1619
- sessionId,
1620
- prompt: task.promptInput,
1621
- timeoutMs: task.input.timeoutMs ?? this.options.timeoutMs,
1622
- conversation: turn.conversation,
1623
- promptMessageId: turn.promptMessageId,
1624
- onPromptRequestWritten: () => task.promptStarted.resolve(),
1625
- onElicitation: task.input.onElicitation,
1626
- onPermissionRequest: task.input.onPermissionRequest,
1627
- authority: task.input
1628
- });
1629
- } finally {
1630
- turn.client.endPromptElicitation?.(sessionId);
1631
- }
1694
+ return await runPromptTurn({
1695
+ client: turn.client,
1696
+ sessionId,
1697
+ prompt: task.promptInput,
1698
+ timeoutMs: task.input.timeoutMs ?? this.options.timeoutMs,
1699
+ conversation: turn.conversation,
1700
+ promptMessageId: turn.promptMessageId,
1701
+ onPromptRequestWritten: () => task.promptStarted.resolve(),
1702
+ onElicitation: task.input.onElicitation,
1703
+ onPermissionRequest: task.input.onPermissionRequest,
1704
+ authority: task.input
1705
+ });
1632
1706
  }
1633
1707
  async prepareRuntimeTurn(task) {
1634
1708
  const recordId = task.input.handle.acpxRecordId ?? task.input.handle.sessionKey;
@@ -1688,13 +1762,19 @@ var AcpRuntimeManager = class {
1688
1762
  let record = await this.requireRecord(recordId);
1689
1763
  const retainedOwner = await this.readRetainedSessionOwner(record, { consume: false });
1690
1764
  if (!retainedOwner) return { record };
1691
- const projection = retainedOwner.projection;
1692
- if (projection) record = structuredClone(projection.record);
1693
1765
  retainedOwner.bufferSessionUpdates = true;
1694
- return {
1695
- record,
1696
- retainedOwner
1697
- };
1766
+ try {
1767
+ await retainedOwner.checkpoint.flush();
1768
+ const projection = retainedOwner.projection;
1769
+ if (projection) record = structuredClone(projection.record);
1770
+ return {
1771
+ record,
1772
+ retainedOwner
1773
+ };
1774
+ } catch (error) {
1775
+ this.restoreBufferedSessionOwner(retainedOwner);
1776
+ throw error;
1777
+ }
1698
1778
  }
1699
1779
  restoreBufferedSessionOwner(owner) {
1700
1780
  if (!owner) return;
@@ -1710,18 +1790,16 @@ var AcpRuntimeManager = class {
1710
1790
  }
1711
1791
  createRunningRuntimeTurn(input) {
1712
1792
  const { record, conversation, acpxState, client, owner, connected, promptMessageId } = input;
1713
- const turn = {
1793
+ return {
1714
1794
  record,
1715
1795
  conversation,
1716
1796
  acpxState,
1717
- liveCheckpoint: this.createRuntimeTurnCheckpoint(record, conversation, () => turn.acpxState),
1718
1797
  client,
1719
1798
  owner,
1720
1799
  connected,
1721
1800
  promptMessageId,
1722
1801
  activeSessionId: record.acpSessionId
1723
1802
  };
1724
- return turn;
1725
1803
  }
1726
1804
  activateRuntimeTurn(task, turn) {
1727
1805
  const { owner, record } = turn;
@@ -1750,6 +1828,7 @@ var AcpRuntimeManager = class {
1750
1828
  ...record,
1751
1829
  sessionKey: record.name ?? record.acpxRecordId
1752
1830
  }),
1831
+ ...this.resolveClientCapabilities(),
1753
1832
  elicitationModes: this.options.elicitationModes,
1754
1833
  processLifecycle: this.options.processLifecycle,
1755
1834
  processLaunchScope: {
@@ -1760,15 +1839,6 @@ var AcpRuntimeManager = class {
1760
1839
  sessionOptions: sessionOptionsFromRecord(record)
1761
1840
  });
1762
1841
  }
1763
- createRuntimeTurnCheckpoint(record, conversation, readAcpxState) {
1764
- return new LiveSessionCheckpoint({ save: async () => {
1765
- record.lastUsedAt = isoNow();
1766
- record.acpx = readAcpxState();
1767
- applyConversation(record, conversation);
1768
- await this.refreshClosedState(record);
1769
- await this.options.sessionStore.save(record);
1770
- } });
1771
- }
1772
1842
  buildRuntimeTurnController(task, turn) {
1773
1843
  return {
1774
1844
  hasActivePrompt: () => turn.client.hasActivePrompt(),
@@ -1779,12 +1849,14 @@ var AcpRuntimeManager = class {
1779
1849
  const nextState = cloneSessionAcpxState(turn.acpxState) ?? {};
1780
1850
  nextState.desired_mode_id = modeId;
1781
1851
  turn.acpxState = nextState;
1852
+ await turn.owner.checkpoint.checkpoint();
1782
1853
  },
1783
1854
  setSessionModel: async (modelId, authority) => {
1784
1855
  await this.waitForRuntimeControlSession(task, turn);
1785
1856
  const models = advertisedModelState(turn.acpxState);
1786
1857
  const response = await turn.client.setSessionModel(turn.activeSessionId, modelId, models, authority);
1787
1858
  turn.acpxState = applyModelSelection(turn.acpxState, modelId, response);
1859
+ await turn.owner.checkpoint.checkpoint();
1788
1860
  return response;
1789
1861
  },
1790
1862
  setSessionConfigOption: async (configId, value, authority) => {
@@ -1809,9 +1881,10 @@ var AcpRuntimeManager = class {
1809
1881
  ...turn.record,
1810
1882
  acpx: turn.acpxState ?? void 0
1811
1883
  }, configId);
1812
- const modelConfigId = advertisedModelState(turn.acpxState)?.configId;
1813
- const response = await turn.client.setSessionConfigOption(turn.activeSessionId, resolvedConfigId, value, authority);
1814
- turn.acpxState = applyConfigOptionSelection(turn.acpxState, resolvedConfigId, value, response, modelConfigId);
1884
+ const models = advertisedModelState(turn.acpxState);
1885
+ const response = await turn.client.setSessionConfigOption(turn.activeSessionId, resolvedConfigId, value, models, authority);
1886
+ turn.acpxState = applyConfigOptionSelection(turn.acpxState, resolvedConfigId, value, response, models?.configId);
1887
+ await turn.owner.checkpoint.checkpoint();
1815
1888
  return {
1816
1889
  configId: resolvedConfigId,
1817
1890
  response
@@ -1860,7 +1933,7 @@ var AcpRuntimeManager = class {
1860
1933
  turn.record.closed = false;
1861
1934
  turn.record.closedAt = void 0;
1862
1935
  turn.record.lastUsedAt = isoNow();
1863
- await turn.liveCheckpoint.checkpoint();
1936
+ await turn.owner.checkpoint.checkpoint();
1864
1937
  }
1865
1938
  cancelRuntimeTurnBeforePrompt(task) {
1866
1939
  if (!task.state.pendingCancel && !task.input.signal?.aborted) return false;
@@ -1868,15 +1941,11 @@ var AcpRuntimeManager = class {
1868
1941
  task.promptStarted.reject(/* @__PURE__ */ new Error("ACP turn cancelled before prompt submission."));
1869
1942
  return true;
1870
1943
  }
1871
- async saveCompletedRuntimeTurn(turn) {
1944
+ updateCompletedRuntimeTurn(turn) {
1872
1945
  turn.record.acpSessionId = turn.activeSessionId;
1873
1946
  reconcileAgentSessionId(turn.record, turn.record.agentSessionId);
1874
1947
  turn.record.protocolVersion = turn.client.initializeResult?.protocolVersion;
1875
1948
  turn.record.agentCapabilities = turn.client.initializeResult?.agentCapabilities;
1876
- turn.record.acpx = turn.acpxState;
1877
- applyConversation(turn.record, turn.conversation);
1878
- applyLifecycleSnapshotToRecord(turn.record, turn.client.getAgentLifecycleSnapshot());
1879
- await this.options.sessionStore.save(turn.record);
1880
1949
  }
1881
1950
  handleRuntimeTurnFailure(task, error) {
1882
1951
  if (!task.input.signal?.aborted || error !== task.input.signal.reason) return this.failRuntimeTurn(task, error);
@@ -1919,13 +1988,24 @@ var AcpRuntimeManager = class {
1919
1988
  discardRetainedRuntimeTurnOwner(turn) {
1920
1989
  if (turn) {
1921
1990
  this.removeRetainedSessionOwner(turn.owner);
1922
- turn.owner.activeTurn = void 0;
1991
+ this.attachFinalRuntimeProjection(turn);
1923
1992
  }
1924
1993
  }
1994
+ attachFinalRuntimeProjection(turn) {
1995
+ const current = turn.connected ? turn.acpxState : turn.record.acpx;
1996
+ const state = turn.record.acpx?.reset_on_next_ensure === true ? {
1997
+ ...current,
1998
+ reset_on_next_ensure: true
1999
+ } : current;
2000
+ this.attachIdleProjection(turn.owner, turn.record, turn.conversation, state);
2001
+ }
1925
2002
  async closeRuntimeTurnClient(turn, pooled) {
1926
2003
  if (!turn || pooled) return;
1927
- turn.owner.activeTurn = void 0;
1928
- const failure = firstFailedAttempt([await settleAttempt(() => turn.client.clearEventHandlers()), await settleAttempt(async () => this.closeClient(turn.client))]);
2004
+ const failure = firstFailedAttempt([
2005
+ await settleAttempt(() => turn.client.clearEventHandlers()),
2006
+ await settleAttempt(async () => this.closeClient(turn.client)),
2007
+ await settleAttempt(async () => this.flushSessionOwner(turn.owner))
2008
+ ]);
1929
2009
  if (failure) {
1930
2010
  this.retiringSessionOwners.add(turn.owner);
1931
2011
  throw failure.error;
@@ -1940,19 +2020,12 @@ var AcpRuntimeManager = class {
1940
2020
  }
1941
2021
  async finalizeRuntimeTurnRecord(turn) {
1942
2022
  if (!turn.connected) turn.acpxState = cloneSessionAcpxState(turn.record.acpx);
1943
- applyLifecycleSnapshotToRecord(turn.record, turn.client.getAgentLifecycleSnapshot());
1944
- turn.record.acpx = turn.acpxState;
1945
- applyConversation(turn.record, turn.conversation);
1946
- turn.record.lastUsedAt = isoNow();
1947
- await turn.liveCheckpoint.flush();
1948
- const closed = await this.refreshClosedState(turn.record);
1949
- await this.options.sessionStore.save(turn.record);
1950
- if (closed || !turn.connected) return false;
2023
+ await turn.owner.checkpoint.checkpoint();
2024
+ this.attachFinalRuntimeProjection(turn);
2025
+ if (turn.record.closed || !turn.connected) return false;
1951
2026
  return await this.retainPersistentSessionOwnerAfterTurn({
1952
2027
  record: turn.record,
1953
- owner: turn.owner,
1954
- conversation: turn.conversation,
1955
- acpxState: turn.acpxState
2028
+ owner: turn.owner
1956
2029
  });
1957
2030
  }
1958
2031
  async *runTurn(input) {
@@ -1978,13 +2051,14 @@ var AcpRuntimeManager = class {
1978
2051
  const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
1979
2052
  assertControlAuthority(authority);
1980
2053
  const controller = this.activeControllers.get(record.acpxRecordId);
1981
- let targetRecord = record;
1982
- if (controller) await controller.setSessionMode(mode, authority);
1983
- else targetRecord = (await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId }) => {
2054
+ if (controller) {
2055
+ await controller.setSessionMode(mode, authority);
2056
+ return;
2057
+ }
2058
+ await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
1984
2059
  await client.setSessionMode(sessionId, mode, authority);
1985
- }, void 0, authority)).record;
1986
- setDesiredModeId(targetRecord, mode);
1987
- await this.options.sessionStore.save(targetRecord);
2060
+ setDesiredModeId(connectedRecord, mode);
2061
+ }, { replacingMode: true }, authority);
1988
2062
  }
1989
2063
  async setModel(handle, model, sessionMode = "persistent", authority) {
1990
2064
  const recordId = handle.acpxRecordId ?? handle.sessionKey;
@@ -1995,16 +2069,13 @@ var AcpRuntimeManager = class {
1995
2069
  assertControlAuthority(authority);
1996
2070
  const controller = this.activeControllers.get(record.acpxRecordId);
1997
2071
  if (controller) {
1998
- const response = await controller.setSessionModel(model, authority);
1999
- record.acpx = applyModelSelection(record.acpx, model, response);
2000
- await this.options.sessionStore.save(record);
2072
+ await controller.setSessionModel(model, authority);
2001
2073
  return;
2002
2074
  }
2003
- const result = await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
2075
+ await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
2004
2076
  const response = await client.setSessionModel(sessionId, model, advertisedModelState(connectedRecord.acpx), authority);
2005
2077
  connectedRecord.acpx = applyModelSelection(connectedRecord.acpx, model, response);
2006
- }, { key: "model" }, authority);
2007
- await this.options.sessionStore.save(result.record);
2078
+ }, { replacingConfigOption: { key: "model" } }, authority);
2008
2079
  }
2009
2080
  async setConfigOption(handle, key, value, sessionMode = "persistent", authority) {
2010
2081
  const recordId = handle.acpxRecordId ?? handle.sessionKey;
@@ -2015,23 +2086,19 @@ var AcpRuntimeManager = class {
2015
2086
  assertControlAuthority(authority);
2016
2087
  const controller = this.activeControllers.get(record.acpxRecordId);
2017
2088
  if (controller) {
2018
- const { configId, response } = await controller.setResolvedSessionConfigOption(key, value, authority);
2019
- record.acpx = applyConfigOptionSelection(record.acpx, configId, value, response);
2020
- await this.options.sessionStore.save(record);
2089
+ const { response } = await controller.setResolvedSessionConfigOption(key, value, authority);
2021
2090
  return response;
2022
2091
  }
2023
- const result = await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
2092
+ return await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
2024
2093
  const configId = resolveSupportedConfigOptionId(connectedRecord, key);
2025
- const modelConfigId = advertisedModelState(connectedRecord.acpx)?.configId;
2026
- const response = await client.setSessionConfigOption(sessionId, configId, value, authority);
2027
- connectedRecord.acpx = applyConfigOptionSelection(connectedRecord.acpx, configId, value, response, modelConfigId);
2094
+ const models = advertisedModelState(connectedRecord.acpx);
2095
+ const response = await client.setSessionConfigOption(sessionId, configId, value, models, authority);
2096
+ connectedRecord.acpx = applyConfigOptionSelection(connectedRecord.acpx, configId, value, response, models?.configId);
2028
2097
  return response;
2029
- }, {
2098
+ }, { replacingConfigOption: {
2030
2099
  key,
2031
2100
  resolve: (connectedRecord) => resolveSupportedConfigOptionId(connectedRecord, key)
2032
- }, authority);
2033
- await this.options.sessionStore.save(result.record);
2034
- return result.value;
2101
+ } }, authority);
2035
2102
  }
2036
2103
  async cancel(handle) {
2037
2104
  await this.activeControllers.get(handle.acpxRecordId ?? handle.sessionKey)?.requestCancelActivePrompt();
@@ -2104,7 +2171,8 @@ var AcpRuntimeManager = class {
2104
2171
  if (connection.owner) await this.flushSessionOwner(connection.owner);
2105
2172
  }),
2106
2173
  await settleAttempt(() => connection.owner?.client.clearEventHandlers()),
2107
- await settleAttempt(async () => this.closeClient(connection.client))
2174
+ await settleAttempt(async () => this.closeClient(connection.client)),
2175
+ await settleAttempt(async () => connection.owner?.checkpoint.flush())
2108
2176
  ]);
2109
2177
  if (failure) throw failure.error;
2110
2178
  }
@@ -2169,7 +2237,8 @@ var FileSessionStore = class {
2169
2237
  } catch {
2170
2238
  return;
2171
2239
  }
2172
- return parseSessionRecord(parsed) ?? void 0;
2240
+ const record = parseSessionRecord(parsed);
2241
+ return record?.acpxRecordId === sessionId ? record : void 0;
2173
2242
  }
2174
2243
  async save(record) {
2175
2244
  const persisted = serializeSessionRecordForDisk(record);
@@ -2239,6 +2308,16 @@ function sharedRecordId(handle) {
2239
2308
  if (handle.backend !== SHARED_BACKEND || !handle.acpxRecordId) return invalidOption("A shared runtime requires a handle from a shared session.");
2240
2309
  return handle.acpxRecordId;
2241
2310
  }
2311
+ /**
2312
+ * Shared controls are owner-only. Falling back to a direct connection would
2313
+ * start a second adapter connection for a session another process owns, and
2314
+ * that path may create a fresh provider session when the saved one no longer
2315
+ * loads. Shared turns already refuse that trade; controls refuse it too.
2316
+ */
2317
+ function requireOwnerDispatch(result, control) {
2318
+ if (result === void 0) throw new AcpRuntimeError("ACP_BACKEND_UNAVAILABLE", `No running owner holds this shared session, so ${control} was not sent. Submit a turn to start an owner, or apply the change with the equivalent acpx command, which connects directly instead of starting one.`);
2319
+ return result;
2320
+ }
2242
2321
  function turnFailure(error) {
2243
2322
  const normalized = normalizeOutputError(error, {
2244
2323
  defaultCode: "RUNTIME",
@@ -2264,6 +2343,7 @@ var SharedAcpRuntime = class {
2264
2343
  options;
2265
2344
  disconnect = new AbortController();
2266
2345
  pending = /* @__PURE__ */ new Set();
2346
+ controlChains = /* @__PURE__ */ new Map();
2267
2347
  constructor(options) {
2268
2348
  for (const name of [
2269
2349
  "sessionStore",
@@ -2272,7 +2352,9 @@ var SharedAcpRuntime = class {
2272
2352
  "sessionPermissions",
2273
2353
  "processLifecycle",
2274
2354
  "onPermissionRequest",
2275
- "elicitationModes"
2355
+ "elicitationModes",
2356
+ "fs",
2357
+ "terminal"
2276
2358
  ]) if (Object.hasOwn(options, name)) invalidOption(`${name} belongs to the in-process runtime and is not supported by shared sessions.`);
2277
2359
  this.options = {
2278
2360
  ...options,
@@ -2414,6 +2496,72 @@ var SharedAcpRuntime = class {
2414
2496
  this.assertOpen();
2415
2497
  return runtimeStatusFromRecord(await resolveSessionRecord(sharedRecordId(input.handle)));
2416
2498
  }
2499
+ async getCapabilities(input) {
2500
+ this.assertOpen();
2501
+ if (!input?.handle) return capabilitiesFromRecord(void 0);
2502
+ return capabilitiesFromRecord(await readSessionRecord(sharedRecordId(input.handle)));
2503
+ }
2504
+ /**
2505
+ * Preserves this client's control order, including caller-side persistence
2506
+ * when talking to older owners. Updated owners checkpoint accepted controls
2507
+ * themselves; serialization here cannot coordinate other legacy callers.
2508
+ */
2509
+ chainControl(sessionId, run) {
2510
+ const result = (this.controlChains.get(sessionId) ?? Promise.resolve()).then(run, run);
2511
+ const chained = result.then(() => void 0, () => void 0);
2512
+ this.controlChains.set(sessionId, chained);
2513
+ chained.then(() => {
2514
+ if (this.controlChains.get(sessionId) === chained) this.controlChains.delete(sessionId);
2515
+ });
2516
+ return result;
2517
+ }
2518
+ /**
2519
+ * Checks this client's authority locally, then hands the control to the
2520
+ * running owner. The check gates this client's dispatch only; it cannot gate
2521
+ * controls other clients of the same owner send, and it cannot recall a
2522
+ * control the owner already accepted.
2523
+ */
2524
+ async dispatchControl(input, control, send) {
2525
+ this.assertOpen();
2526
+ const sessionId = sharedRecordId(input.handle);
2527
+ const assertAuthority = () => {
2528
+ this.assertOpen();
2529
+ assertControlAuthority(input);
2530
+ };
2531
+ return await this.track(this.chainControl(sessionId, async () => {
2532
+ assertAuthority();
2533
+ return requireOwnerDispatch(await send(sessionId, assertAuthority), control);
2534
+ }));
2535
+ }
2536
+ async setMode(input) {
2537
+ await this.dispatchControl(input, "session/set_mode", async (sessionId, assertAuthority) => setSessionModeOnOwner({
2538
+ sessionId,
2539
+ modeId: input.mode,
2540
+ timeoutMs: this.options.timeoutMs,
2541
+ assertDispatch: assertAuthority
2542
+ }));
2543
+ }
2544
+ async setModel(input) {
2545
+ await this.dispatchControl(input, "session/set_model", async (sessionId, assertAuthority) => setSessionModelOnOwner({
2546
+ sessionId,
2547
+ modelId: input.model,
2548
+ timeoutMs: this.options.timeoutMs,
2549
+ assertDispatch: assertAuthority
2550
+ }));
2551
+ }
2552
+ async setConfigOption(input) {
2553
+ return (await this.dispatchControl(input, "session/set_config_option", async (sessionId, assertAuthority) => {
2554
+ const configId = resolveSupportedConfigOptionId(await resolveSessionRecord(sessionId), input.key);
2555
+ assertAuthority();
2556
+ return await setSessionConfigOptionOnOwner({
2557
+ sessionId,
2558
+ configId,
2559
+ value: input.value,
2560
+ timeoutMs: this.options.timeoutMs,
2561
+ assertDispatch: assertAuthority
2562
+ });
2563
+ })).response;
2564
+ }
2417
2565
  watchSession(input) {
2418
2566
  this.assertOpen();
2419
2567
  const signal = input.signal ? AbortSignal.any([input.signal, this.disconnect.signal]) : this.disconnect.signal;
@@ -2448,12 +2596,6 @@ function createSharedAcpRuntime(options) {
2448
2596
  //#endregion
2449
2597
  //#region src/runtime.ts
2450
2598
  const ACPX_BACKEND_ID = "acpx";
2451
- const ACPX_CAPABILITIES = { controls: [
2452
- "session/set_mode",
2453
- "session/set_model",
2454
- "session/set_config_option",
2455
- "session/status"
2456
- ] };
2457
2599
  var AcpxRuntime = class {
2458
2600
  options;
2459
2601
  testOptions;
@@ -2557,6 +2699,7 @@ var AcpxRuntime = class {
2557
2699
  onElicitation: input.onElicitation,
2558
2700
  onPermissionRequest: input.onPermissionRequest
2559
2701
  }));
2702
+ turnPromise.catch(() => {});
2560
2703
  return {
2561
2704
  requestId: input.requestId,
2562
2705
  get promptStarted() {
@@ -2593,15 +2736,9 @@ var AcpxRuntime = class {
2593
2736
  });
2594
2737
  }
2595
2738
  async getCapabilities(input) {
2596
- if (!input?.handle) return ACPX_CAPABILITIES;
2739
+ if (!input?.handle) return capabilitiesFromRecord(void 0);
2597
2740
  const { handle } = this.resolveManagerHandle(input.handle);
2598
- const record = await this.options.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey);
2599
- if (!record?.acpx?.config_options) return ACPX_CAPABILITIES;
2600
- const configOptionKeys = Array.from(new Set(record.acpx.config_options.map((option) => option.id).filter((id) => typeof id === "string" && id.trim().length > 0)));
2601
- return {
2602
- ...ACPX_CAPABILITIES,
2603
- ...configOptionKeys.length > 0 ? { configOptionKeys } : {}
2604
- };
2741
+ return capabilitiesFromRecord(await this.options.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey));
2605
2742
  }
2606
2743
  async getStatus(input) {
2607
2744
  const { handle } = this.resolveManagerHandle(input.handle);