acpx 0.17.1 → 0.18.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 (36) hide show
  1. package/dist/{agent-registry-evUoJINz.js → agent-registry-DdKB-zia.js} +16 -17
  2. package/dist/{agent-registry-evUoJINz.js.map → agent-registry-DdKB-zia.js.map} +1 -1
  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-8mVHeUXh.js} +5 -5
  6. package/dist/{cli-B2ycKzaO.js.map → cli-8mVHeUXh.js.map} +1 -1
  7. package/dist/cli.js +122 -94
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{client-CkbBoSGd.d.ts → client-DeNKZZcu.d.ts} +14 -12
  10. package/dist/client-DeNKZZcu.d.ts.map +1 -0
  11. package/dist/{flows-xGyhmoHO.js → flows-WOQNzCMC.js} +5 -5
  12. package/dist/{flows-xGyhmoHO.js.map → flows-WOQNzCMC.js.map} +1 -1
  13. package/dist/flows.d.ts +1 -1
  14. package/dist/flows.js +1 -1
  15. package/dist/{invocation-options-eZEBh4vd.js → invocation-options-B6d7TI-y.js} +7 -6
  16. package/dist/{invocation-options-eZEBh4vd.js.map → invocation-options-B6d7TI-y.js.map} +1 -1
  17. package/dist/{ipc-D2rstexT.js → ipc-BJrvfNXr.js} +305 -88
  18. package/dist/ipc-BJrvfNXr.js.map +1 -0
  19. package/dist/{output-D2xL6wdp.js → output-CL18YmsG.js} +9 -6
  20. package/dist/output-CL18YmsG.js.map +1 -0
  21. package/dist/{queue-owner-runtime-Dgv3e2Tp.js → queue-owner-runtime-Cr0dWJuL.js} +952 -433
  22. package/dist/queue-owner-runtime-Cr0dWJuL.js.map +1 -0
  23. package/dist/runtime.d.ts +39 -3
  24. package/dist/runtime.d.ts.map +1 -1
  25. package/dist/runtime.js +272 -154
  26. package/dist/runtime.js.map +1 -1
  27. package/dist/{session-watch-CTe9Bnbc.js → session-watch-B8Q_TsGf.js} +4 -4
  28. package/dist/{session-watch-CTe9Bnbc.js.map → session-watch-B8Q_TsGf.js.map} +1 -1
  29. package/dist/{watch-DxuXEc8n.js → watch-Dp73ph8h.js} +2 -2
  30. package/dist/{watch-DxuXEc8n.js.map → watch-Dp73ph8h.js.map} +1 -1
  31. package/package.json +1 -1
  32. package/skills/acpx/SKILL.md +26 -7
  33. package/dist/client-CkbBoSGd.d.ts.map +0 -1
  34. package/dist/ipc-D2rstexT.js.map +0 -1
  35. package/dist/output-D2xL6wdp.js.map +0 -1
  36. package/dist/queue-owner-runtime-Dgv3e2Tp.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 { B as serializeSessionRecordForDisk, F as readSessionRecord, I as resolveSessionRecord, O as findSession, R as assertPersistedKeyPolicy, bt as isAcpResourceNotFoundError, ht as writePrivateJsonFile, t as tryCancelOnRunningOwner, vt as extractAcpError, w as textPrompt, z as parseSessionRecord } from "./ipc-BJrvfNXr.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-Cr0dWJuL.js";
3
+ import { D as normalizeAgentCommandInput, a as createAgentRegistry, i as DEFAULT_AGENT_NAME } from "./agent-registry-DdKB-zia.js";
4
+ import { t as watchSession } from "./watch-Dp73ph8h.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,38 @@ function isAcpRuntimeError(value) {
22
22
  return value instanceof AcpRuntimeError;
23
23
  }
24
24
  //#endregion
25
+ //#region src/runtime/engine/controls.ts
26
+ /** Controls acpx implements for every backend it exposes. */
27
+ const ACPX_CAPABILITIES = { controls: [
28
+ "session/set_mode",
29
+ "session/set_model",
30
+ "session/set_config_option",
31
+ "session/status"
32
+ ] };
33
+ function advertisedConfigOptionIds(record) {
34
+ const configOptions = record?.acpx?.config_options;
35
+ if (!configOptions) return;
36
+ return new Set(configOptions.map((option) => option.id).filter((id) => typeof id === "string" && id.trim().length > 0));
37
+ }
38
+ /** Reports the config option keys the session last advertised, if any. */
39
+ function capabilitiesFromRecord(record) {
40
+ const advertisedIds = advertisedConfigOptionIds(record);
41
+ if (!advertisedIds || advertisedIds.size === 0) return ACPX_CAPABILITIES;
42
+ return {
43
+ ...ACPX_CAPABILITIES,
44
+ configOptionKeys: [...advertisedIds]
45
+ };
46
+ }
47
+ function resolveSupportedConfigOptionId(record, configId) {
48
+ const advertisedIds = advertisedConfigOptionIds(record);
49
+ if (!advertisedIds) return configId;
50
+ if (advertisedIds.has(configId)) return configId;
51
+ if (configId === "thinking" && advertisedIds.has("effort")) return "effort";
52
+ const supported = [...advertisedIds].toSorted();
53
+ const supportedText = supported.length > 0 ? supported.join(", ") : "none";
54
+ throw new AcpRuntimeError("ACP_BACKEND_UNSUPPORTED_CONTROL", `ACP session ${record.acpxRecordId} does not advertise config option '${configId}'. Supported config options: ${supportedText}.`);
55
+ }
56
+ //#endregion
25
57
  //#region src/runtime/public/shared.ts
26
58
  function isRecord(value) {
27
59
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -918,20 +950,6 @@ function createRecordId(sessionKey, mode) {
918
950
  function resumePolicyForSessionMode(mode) {
919
951
  return mode === "persistent" ? "same-session-only" : "allow-new";
920
952
  }
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
953
  async function startRuntimeClient(client, agent, timeoutMs) {
936
954
  try {
937
955
  await withTimeout(client.start(), timeoutMs);
@@ -969,6 +987,7 @@ var AcpRuntimeManager = class {
969
987
  options;
970
988
  deps;
971
989
  activeControllers = /* @__PURE__ */ new Map();
990
+ controlSessionRecords = /* @__PURE__ */ new Map();
972
991
  retainedSessionOwners = /* @__PURE__ */ new Map();
973
992
  retiringSessionOwners = /* @__PURE__ */ new Set();
974
993
  pendingOneShotRecordIds = /* @__PURE__ */ new Map();
@@ -1085,6 +1104,7 @@ var AcpRuntimeManager = class {
1085
1104
  createSessionOwner(input) {
1086
1105
  const owner = {
1087
1106
  ...input,
1107
+ checkpoint: new LiveSessionCheckpoint({ save: async () => this.saveSessionOwner(owner) }),
1088
1108
  bufferSessionUpdates: false,
1089
1109
  pendingSessionUpdates: []
1090
1110
  };
@@ -1094,13 +1114,26 @@ var AcpRuntimeManager = class {
1094
1114
  });
1095
1115
  return owner;
1096
1116
  }
1117
+ async saveSessionOwner(owner) {
1118
+ if (!owner.recordId) return;
1119
+ const active = owner.activeTurn?.turn;
1120
+ const target = active ?? owner.projection;
1121
+ if (!target) return;
1122
+ const { record, conversation } = target;
1123
+ if (active?.connected) record.acpx = active.acpxState;
1124
+ record.lastUsedAt = isoNow();
1125
+ applyConversation(record, conversation);
1126
+ applyLifecycleSnapshotToRecord(record, owner.client.getAgentLifecycleSnapshot());
1127
+ await this.refreshClosedState(record);
1128
+ await this.options.sessionStore.save(record);
1129
+ }
1097
1130
  routeOwnedSessionUpdate(owner, notification) {
1098
1131
  const active = owner.activeTurn;
1099
1132
  if (active) {
1100
1133
  const { task, turn } = active;
1101
1134
  if (turn.connected) {
1102
1135
  turn.acpxState = recordSessionUpdate(turn.conversation, turn.acpxState, notification);
1103
- turn.liveCheckpoint.request();
1136
+ owner.checkpoint.request();
1104
1137
  } else turn.record.acpx = recordSessionUpdate(turn.conversation, turn.record.acpx, notification);
1105
1138
  trimConversationForRuntime(turn.conversation);
1106
1139
  this.emitRuntimeTurnEvent(task, {
@@ -1121,7 +1154,7 @@ var AcpRuntimeManager = class {
1121
1154
  }
1122
1155
  projection.record.acpx = recordSessionUpdate(projection.conversation, projection.record.acpx, notification);
1123
1156
  trimConversationForRuntime(projection.conversation);
1124
- projection.checkpoint.request();
1157
+ owner.checkpoint.request();
1125
1158
  }
1126
1159
  routeOwnedClientOperation(owner, operation) {
1127
1160
  const active = owner.activeTurn;
@@ -1129,7 +1162,7 @@ var AcpRuntimeManager = class {
1129
1162
  const { task, turn } = active;
1130
1163
  if (turn.connected) {
1131
1164
  turn.acpxState = recordClientOperation(turn.conversation, turn.acpxState, operation);
1132
- turn.liveCheckpoint.request();
1165
+ owner.checkpoint.request();
1133
1166
  } else turn.record.acpx = recordClientOperation(turn.conversation, turn.record.acpx, operation);
1134
1167
  trimConversationForRuntime(turn.conversation);
1135
1168
  this.emitRuntimeTurnEvent(task, {
@@ -1141,15 +1174,7 @@ var AcpRuntimeManager = class {
1141
1174
  record.acpx = acpxState;
1142
1175
  owner.projection = {
1143
1176
  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
- } })
1177
+ conversation
1153
1178
  };
1154
1179
  owner.activeTurn = void 0;
1155
1180
  this.drainPendingSessionUpdates(owner);
@@ -1159,7 +1184,7 @@ var AcpRuntimeManager = class {
1159
1184
  }
1160
1185
  async flushSessionOwner(owner) {
1161
1186
  await owner.client.waitForSessionUpdatesIdle?.().catch(() => {});
1162
- await owner.projection?.checkpoint.flush();
1187
+ await owner.checkpoint.flush();
1163
1188
  }
1164
1189
  removeRetainedSessionOwner(owner) {
1165
1190
  if (owner.recordId && this.retainedSessionOwners.get(owner.recordId) === owner) this.retainedSessionOwners.delete(owner.recordId);
@@ -1190,7 +1215,7 @@ var AcpRuntimeManager = class {
1190
1215
  await this.closeClient(owner.client).catch(() => {
1191
1216
  this.retiringSessionOwners.add(owner);
1192
1217
  });
1193
- await owner.projection?.checkpoint.flush().catch(() => {});
1218
+ await owner.checkpoint.flush().catch(() => {});
1194
1219
  try {
1195
1220
  owner.client.clearEventHandlers();
1196
1221
  } catch {}
@@ -1213,19 +1238,15 @@ var AcpRuntimeManager = class {
1213
1238
  const latest = await this.options.sessionStore.load(record.acpxRecordId).catch(() => void 0);
1214
1239
  record.closed = true;
1215
1240
  record.closedAt = latest?.closedAt ?? record.closedAt ?? isoNow();
1216
- if (latest?.acpx) record.acpx = {
1241
+ if (latest?.acpx?.reset_on_next_ensure === true) record.acpx = {
1217
1242
  ...record.acpx,
1218
- ...latest.acpx
1243
+ reset_on_next_ensure: true
1219
1244
  };
1220
1245
  return true;
1221
1246
  }
1222
1247
  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);
1248
+ const { record, owner } = input;
1249
+ if (!this.canRetainPersistentSessionOwner(owner, record)) return false;
1229
1250
  const previousOwner = this.retainedSessionOwners.get(record.acpxRecordId);
1230
1251
  this.retainedSessionOwners.set(record.acpxRecordId, owner);
1231
1252
  if (previousOwner && previousOwner !== owner) {
@@ -1276,20 +1297,27 @@ var AcpRuntimeManager = class {
1276
1297
  record: ownedRecord
1277
1298
  });
1278
1299
  this.refreshOwnedRecordLifecycle(owner, ownedRecord);
1279
- return {
1280
- value,
1281
- record: ownedRecord
1282
- };
1300
+ await owner.checkpoint.checkpoint();
1301
+ return value;
1283
1302
  } finally {
1284
1303
  await this.flushSessionOwner(owner);
1285
1304
  }
1286
1305
  }
1287
1306
  let controlClient;
1307
+ let controlledRecord = record;
1308
+ const checkpoint = new LiveSessionCheckpoint({ save: async () => this.options.sessionStore.save(controlledRecord) });
1288
1309
  try {
1289
- const result = await withConnectedSession({
1310
+ const result = await settleAttempt(async () => withConnectedSession({
1290
1311
  sessionRecordId: record.acpxRecordId,
1291
- loadRecord: async (sessionRecordId) => await this.requireRecord(sessionRecordId),
1292
- saveRecord: async (connectedRecord) => await this.options.sessionStore.save(connectedRecord),
1312
+ loadRecord: async (sessionRecordId) => {
1313
+ controlledRecord = await this.requireRecord(sessionRecordId);
1314
+ this.controlSessionRecords.set(sessionRecordId, {
1315
+ record: controlledRecord,
1316
+ checkpoint
1317
+ });
1318
+ return controlledRecord;
1319
+ },
1320
+ saveRecord: async () => checkpoint.checkpoint(),
1293
1321
  createClient: (options) => controlClient = this.createClient({
1294
1322
  ...options,
1295
1323
  processLifecycle: this.options.processLifecycle,
@@ -1313,12 +1341,14 @@ var AcpRuntimeManager = class {
1313
1341
  replacingConfigOption,
1314
1342
  authority,
1315
1343
  run
1316
- });
1317
- return {
1318
- value: result.value,
1319
- record: result.record
1320
- };
1344
+ }));
1345
+ this.controlSessionRecords.delete(record.acpxRecordId);
1346
+ const flushed = await settleAttempt(async () => checkpoint.flush());
1347
+ if (!result.ok) throw result.error;
1348
+ if (!flushed.ok) throw flushed.error;
1349
+ return result.value.value;
1321
1350
  } finally {
1351
+ this.controlSessionRecords.delete(record.acpxRecordId);
1322
1352
  if (controlClient) this.liveClients.delete(controlClient);
1323
1353
  }
1324
1354
  }
@@ -1340,6 +1370,13 @@ var AcpRuntimeManager = class {
1340
1370
  async withManagerLock(locks, key, run, authority) {
1341
1371
  this.assertOpen();
1342
1372
  assertControlAuthority(authority);
1373
+ return await this.serializeManagerOperation(locks, key, async () => {
1374
+ this.assertOpen();
1375
+ assertControlAuthority(authority);
1376
+ return await run();
1377
+ });
1378
+ }
1379
+ async serializeManagerOperation(locks, key, run) {
1343
1380
  const previous = locks.get(key) ?? Promise.resolve();
1344
1381
  let release;
1345
1382
  const gate = new Promise((resolve) => {
@@ -1349,8 +1386,6 @@ var AcpRuntimeManager = class {
1349
1386
  locks.set(key, tail);
1350
1387
  await previous;
1351
1388
  try {
1352
- this.assertOpen();
1353
- assertControlAuthority(authority);
1354
1389
  return await run();
1355
1390
  } finally {
1356
1391
  release();
@@ -1365,8 +1400,26 @@ var AcpRuntimeManager = class {
1365
1400
  agentCommand,
1366
1401
  agentArgv
1367
1402
  };
1403
+ if (input.mode !== "persistent") return await this.acquireRuntimeSession(input, agent);
1404
+ const compatible = await this.reuseCompatiblePersistentSession(input, agent);
1405
+ if (compatible) return compatible;
1406
+ this.assertRuntimeSessionIdle(input.sessionKey);
1407
+ return await this.withManagerLock(this.runtimeOperationLocks, input.sessionKey, async () => this.acquireRuntimeSession(input, agent));
1408
+ }
1409
+ async reuseCompatiblePersistentSession(input, agent) {
1410
+ const controlled = this.controlSessionRecords.get(input.sessionKey);
1411
+ const record = controlled ? controlled.record : await this.findSession(input.sessionKey);
1412
+ if (!record || this.closingActiveRecords.has(input.sessionKey) || !shouldReuseExistingRecord(record, {
1413
+ ...agent,
1414
+ resumeSessionId: input.resumeSessionId
1415
+ })) return;
1416
+ if (!record.closed) return record;
1417
+ if (!controlled || this.sessionTasks.has(input.sessionKey)) return;
1418
+ return await this.reuseRuntimeSession({ record }, controlled.checkpoint);
1419
+ }
1420
+ async acquireRuntimeSession(input, agent) {
1368
1421
  const existing = await this.loadExistingRuntimeSession(input);
1369
- if (existing && this.canReuseRuntimeSession(input, agent, existing)) return await this.reuseRuntimeSession(existing.record);
1422
+ if (existing && this.canReuseRuntimeSession(input, agent, existing)) return await this.reuseRuntimeSession(existing);
1370
1423
  await this.closeConflictingPersistentSession(input, existing?.owner);
1371
1424
  return await this.createOwnedRuntimeSession(input, agent);
1372
1425
  }
@@ -1397,17 +1450,26 @@ var AcpRuntimeManager = class {
1397
1450
  resumeSessionId: input.resumeSessionId
1398
1451
  })) return false;
1399
1452
  if (input.mode === "persistent") return true;
1400
- return Boolean(existing.owner && isDeepStrictEqual(sessionOptionsFromRecord(existing.record), input.sessionOptions));
1453
+ 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));
1454
+ }
1455
+ assertRuntimeSessionIdle(recordId) {
1456
+ 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
1457
  }
1402
- async reuseRuntimeSession(record) {
1458
+ async reuseRuntimeSession({ record, owner }, checkpoint = owner?.checkpoint) {
1459
+ if (!record.closed && !this.closingActiveRecords.has(record.acpxRecordId)) return record;
1403
1460
  record.closed = false;
1404
1461
  record.closedAt = void 0;
1405
1462
  this.closingActiveRecords.delete(record.acpxRecordId);
1406
- await this.options.sessionStore.save(record);
1463
+ if (checkpoint) await checkpoint.checkpoint();
1464
+ else await this.options.sessionStore.save(record);
1407
1465
  return record;
1408
1466
  }
1409
1467
  async closeConflictingPersistentSession(input, owner) {
1410
- if (input.mode === "persistent" && owner?.recordId) await this.closeRetainedSessionOwner(owner.recordId);
1468
+ if (input.mode === "persistent" && owner?.recordId) {
1469
+ this.removeRetainedSessionOwner(owner);
1470
+ this.retiringSessionOwners.add(owner);
1471
+ await this.retrySessionCleanup(owner.recordId);
1472
+ }
1411
1473
  }
1412
1474
  async createOwnedRuntimeSession(input, agent) {
1413
1475
  const { cwd, agentCommand, agentArgv } = agent;
@@ -1493,7 +1555,7 @@ var AcpRuntimeManager = class {
1493
1555
  }
1494
1556
  async retainInitializedSessionOwner(owner, record) {
1495
1557
  owner.recordId = record.acpxRecordId;
1496
- await owner.projection?.checkpoint.checkpoint();
1558
+ await owner.checkpoint.checkpoint();
1497
1559
  this.assertOpen();
1498
1560
  const previousOwner = this.retainedSessionOwners.get(record.acpxRecordId);
1499
1561
  this.retainedSessionOwners.set(record.acpxRecordId, owner);
@@ -1571,7 +1633,9 @@ var AcpRuntimeManager = class {
1571
1633
  }
1572
1634
  async closeRetainedOneShotHandle(handle) {
1573
1635
  const recordId = handle.acpxRecordId ?? handle.sessionKey;
1574
- if (this.retainedSessionOwners.get(recordId)?.mode === "oneshot") await this.closeRetainedSessionOwner(recordId);
1636
+ await this.serializeManagerOperation(this.runtimeOperationLocks, recordId, async () => {
1637
+ if (this.retainedSessionOwners.get(recordId)?.mode === "oneshot") await this.closeRetainedSessionOwner(recordId);
1638
+ });
1575
1639
  }
1576
1640
  async runRuntimeTurnTask(task) {
1577
1641
  let turn;
@@ -1594,7 +1658,7 @@ var AcpRuntimeManager = class {
1594
1658
  };
1595
1659
  else {
1596
1660
  const response = await this.runRuntimePrompt(task, turn, sessionId);
1597
- await this.saveCompletedRuntimeTurn(turn);
1661
+ this.updateCompletedRuntimeTurn(turn);
1598
1662
  terminalResult = {
1599
1663
  status: response.stopReason === "cancelled" ? "cancelled" : "completed",
1600
1664
  ...response.stopReason ? { stopReason: response.stopReason } : {},
@@ -1605,7 +1669,7 @@ var AcpRuntimeManager = class {
1605
1669
  } catch (error) {
1606
1670
  terminalResult = this.handleRuntimeTurnFailure(task, error);
1607
1671
  }
1608
- const finalization = await settleAttempt(async () => this.finalizeRuntimeTurn(task, turn));
1672
+ const finalization = await settleAttempt(async () => this.serializeManagerOperation(this.runtimeOperationLocks, task.input.handle.acpxRecordId ?? task.input.handle.sessionKey, async () => this.finalizeRuntimeTurn(task, turn)));
1609
1673
  if (!finalization.ok) {
1610
1674
  task.settleResult(this.failRuntimeTurn(task, finalization.error));
1611
1675
  throw finalization.error;
@@ -1613,22 +1677,18 @@ var AcpRuntimeManager = class {
1613
1677
  task.settleResult(terminalResult);
1614
1678
  }
1615
1679
  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
- }
1680
+ return await runPromptTurn({
1681
+ client: turn.client,
1682
+ sessionId,
1683
+ prompt: task.promptInput,
1684
+ timeoutMs: task.input.timeoutMs ?? this.options.timeoutMs,
1685
+ conversation: turn.conversation,
1686
+ promptMessageId: turn.promptMessageId,
1687
+ onPromptRequestWritten: () => task.promptStarted.resolve(),
1688
+ onElicitation: task.input.onElicitation,
1689
+ onPermissionRequest: task.input.onPermissionRequest,
1690
+ authority: task.input
1691
+ });
1632
1692
  }
1633
1693
  async prepareRuntimeTurn(task) {
1634
1694
  const recordId = task.input.handle.acpxRecordId ?? task.input.handle.sessionKey;
@@ -1688,13 +1748,19 @@ var AcpRuntimeManager = class {
1688
1748
  let record = await this.requireRecord(recordId);
1689
1749
  const retainedOwner = await this.readRetainedSessionOwner(record, { consume: false });
1690
1750
  if (!retainedOwner) return { record };
1691
- const projection = retainedOwner.projection;
1692
- if (projection) record = structuredClone(projection.record);
1693
1751
  retainedOwner.bufferSessionUpdates = true;
1694
- return {
1695
- record,
1696
- retainedOwner
1697
- };
1752
+ try {
1753
+ await retainedOwner.checkpoint.flush();
1754
+ const projection = retainedOwner.projection;
1755
+ if (projection) record = structuredClone(projection.record);
1756
+ return {
1757
+ record,
1758
+ retainedOwner
1759
+ };
1760
+ } catch (error) {
1761
+ this.restoreBufferedSessionOwner(retainedOwner);
1762
+ throw error;
1763
+ }
1698
1764
  }
1699
1765
  restoreBufferedSessionOwner(owner) {
1700
1766
  if (!owner) return;
@@ -1710,18 +1776,16 @@ var AcpRuntimeManager = class {
1710
1776
  }
1711
1777
  createRunningRuntimeTurn(input) {
1712
1778
  const { record, conversation, acpxState, client, owner, connected, promptMessageId } = input;
1713
- const turn = {
1779
+ return {
1714
1780
  record,
1715
1781
  conversation,
1716
1782
  acpxState,
1717
- liveCheckpoint: this.createRuntimeTurnCheckpoint(record, conversation, () => turn.acpxState),
1718
1783
  client,
1719
1784
  owner,
1720
1785
  connected,
1721
1786
  promptMessageId,
1722
1787
  activeSessionId: record.acpSessionId
1723
1788
  };
1724
- return turn;
1725
1789
  }
1726
1790
  activateRuntimeTurn(task, turn) {
1727
1791
  const { owner, record } = turn;
@@ -1760,15 +1824,6 @@ var AcpRuntimeManager = class {
1760
1824
  sessionOptions: sessionOptionsFromRecord(record)
1761
1825
  });
1762
1826
  }
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
1827
  buildRuntimeTurnController(task, turn) {
1773
1828
  return {
1774
1829
  hasActivePrompt: () => turn.client.hasActivePrompt(),
@@ -1779,12 +1834,14 @@ var AcpRuntimeManager = class {
1779
1834
  const nextState = cloneSessionAcpxState(turn.acpxState) ?? {};
1780
1835
  nextState.desired_mode_id = modeId;
1781
1836
  turn.acpxState = nextState;
1837
+ await turn.owner.checkpoint.checkpoint();
1782
1838
  },
1783
1839
  setSessionModel: async (modelId, authority) => {
1784
1840
  await this.waitForRuntimeControlSession(task, turn);
1785
1841
  const models = advertisedModelState(turn.acpxState);
1786
1842
  const response = await turn.client.setSessionModel(turn.activeSessionId, modelId, models, authority);
1787
1843
  turn.acpxState = applyModelSelection(turn.acpxState, modelId, response);
1844
+ await turn.owner.checkpoint.checkpoint();
1788
1845
  return response;
1789
1846
  },
1790
1847
  setSessionConfigOption: async (configId, value, authority) => {
@@ -1809,9 +1866,10 @@ var AcpRuntimeManager = class {
1809
1866
  ...turn.record,
1810
1867
  acpx: turn.acpxState ?? void 0
1811
1868
  }, 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);
1869
+ const models = advertisedModelState(turn.acpxState);
1870
+ const response = await turn.client.setSessionConfigOption(turn.activeSessionId, resolvedConfigId, value, models, authority);
1871
+ turn.acpxState = applyConfigOptionSelection(turn.acpxState, resolvedConfigId, value, response, models?.configId);
1872
+ await turn.owner.checkpoint.checkpoint();
1815
1873
  return {
1816
1874
  configId: resolvedConfigId,
1817
1875
  response
@@ -1860,7 +1918,7 @@ var AcpRuntimeManager = class {
1860
1918
  turn.record.closed = false;
1861
1919
  turn.record.closedAt = void 0;
1862
1920
  turn.record.lastUsedAt = isoNow();
1863
- await turn.liveCheckpoint.checkpoint();
1921
+ await turn.owner.checkpoint.checkpoint();
1864
1922
  }
1865
1923
  cancelRuntimeTurnBeforePrompt(task) {
1866
1924
  if (!task.state.pendingCancel && !task.input.signal?.aborted) return false;
@@ -1868,15 +1926,11 @@ var AcpRuntimeManager = class {
1868
1926
  task.promptStarted.reject(/* @__PURE__ */ new Error("ACP turn cancelled before prompt submission."));
1869
1927
  return true;
1870
1928
  }
1871
- async saveCompletedRuntimeTurn(turn) {
1929
+ updateCompletedRuntimeTurn(turn) {
1872
1930
  turn.record.acpSessionId = turn.activeSessionId;
1873
1931
  reconcileAgentSessionId(turn.record, turn.record.agentSessionId);
1874
1932
  turn.record.protocolVersion = turn.client.initializeResult?.protocolVersion;
1875
1933
  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
1934
  }
1881
1935
  handleRuntimeTurnFailure(task, error) {
1882
1936
  if (!task.input.signal?.aborted || error !== task.input.signal.reason) return this.failRuntimeTurn(task, error);
@@ -1919,13 +1973,24 @@ var AcpRuntimeManager = class {
1919
1973
  discardRetainedRuntimeTurnOwner(turn) {
1920
1974
  if (turn) {
1921
1975
  this.removeRetainedSessionOwner(turn.owner);
1922
- turn.owner.activeTurn = void 0;
1976
+ this.attachFinalRuntimeProjection(turn);
1923
1977
  }
1924
1978
  }
1979
+ attachFinalRuntimeProjection(turn) {
1980
+ const current = turn.connected ? turn.acpxState : turn.record.acpx;
1981
+ const state = turn.record.acpx?.reset_on_next_ensure === true ? {
1982
+ ...current,
1983
+ reset_on_next_ensure: true
1984
+ } : current;
1985
+ this.attachIdleProjection(turn.owner, turn.record, turn.conversation, state);
1986
+ }
1925
1987
  async closeRuntimeTurnClient(turn, pooled) {
1926
1988
  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))]);
1989
+ const failure = firstFailedAttempt([
1990
+ await settleAttempt(() => turn.client.clearEventHandlers()),
1991
+ await settleAttempt(async () => this.closeClient(turn.client)),
1992
+ await settleAttempt(async () => this.flushSessionOwner(turn.owner))
1993
+ ]);
1929
1994
  if (failure) {
1930
1995
  this.retiringSessionOwners.add(turn.owner);
1931
1996
  throw failure.error;
@@ -1940,19 +2005,12 @@ var AcpRuntimeManager = class {
1940
2005
  }
1941
2006
  async finalizeRuntimeTurnRecord(turn) {
1942
2007
  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;
2008
+ await turn.owner.checkpoint.checkpoint();
2009
+ this.attachFinalRuntimeProjection(turn);
2010
+ if (turn.record.closed || !turn.connected) return false;
1951
2011
  return await this.retainPersistentSessionOwnerAfterTurn({
1952
2012
  record: turn.record,
1953
- owner: turn.owner,
1954
- conversation: turn.conversation,
1955
- acpxState: turn.acpxState
2013
+ owner: turn.owner
1956
2014
  });
1957
2015
  }
1958
2016
  async *runTurn(input) {
@@ -1978,13 +2036,14 @@ var AcpRuntimeManager = class {
1978
2036
  const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
1979
2037
  assertControlAuthority(authority);
1980
2038
  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 }) => {
2039
+ if (controller) {
2040
+ await controller.setSessionMode(mode, authority);
2041
+ return;
2042
+ }
2043
+ await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
1984
2044
  await client.setSessionMode(sessionId, mode, authority);
1985
- }, void 0, authority)).record;
1986
- setDesiredModeId(targetRecord, mode);
1987
- await this.options.sessionStore.save(targetRecord);
2045
+ setDesiredModeId(connectedRecord, mode);
2046
+ }, void 0, authority);
1988
2047
  }
1989
2048
  async setModel(handle, model, sessionMode = "persistent", authority) {
1990
2049
  const recordId = handle.acpxRecordId ?? handle.sessionKey;
@@ -1995,16 +2054,13 @@ var AcpRuntimeManager = class {
1995
2054
  assertControlAuthority(authority);
1996
2055
  const controller = this.activeControllers.get(record.acpxRecordId);
1997
2056
  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);
2057
+ await controller.setSessionModel(model, authority);
2001
2058
  return;
2002
2059
  }
2003
- const result = await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
2060
+ await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
2004
2061
  const response = await client.setSessionModel(sessionId, model, advertisedModelState(connectedRecord.acpx), authority);
2005
2062
  connectedRecord.acpx = applyModelSelection(connectedRecord.acpx, model, response);
2006
2063
  }, { key: "model" }, authority);
2007
- await this.options.sessionStore.save(result.record);
2008
2064
  }
2009
2065
  async setConfigOption(handle, key, value, sessionMode = "persistent", authority) {
2010
2066
  const recordId = handle.acpxRecordId ?? handle.sessionKey;
@@ -2015,23 +2071,19 @@ var AcpRuntimeManager = class {
2015
2071
  assertControlAuthority(authority);
2016
2072
  const controller = this.activeControllers.get(record.acpxRecordId);
2017
2073
  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);
2074
+ const { response } = await controller.setResolvedSessionConfigOption(key, value, authority);
2021
2075
  return response;
2022
2076
  }
2023
- const result = await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
2077
+ return await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
2024
2078
  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);
2079
+ const models = advertisedModelState(connectedRecord.acpx);
2080
+ const response = await client.setSessionConfigOption(sessionId, configId, value, models, authority);
2081
+ connectedRecord.acpx = applyConfigOptionSelection(connectedRecord.acpx, configId, value, response, models?.configId);
2028
2082
  return response;
2029
2083
  }, {
2030
2084
  key,
2031
2085
  resolve: (connectedRecord) => resolveSupportedConfigOptionId(connectedRecord, key)
2032
2086
  }, authority);
2033
- await this.options.sessionStore.save(result.record);
2034
- return result.value;
2035
2087
  }
2036
2088
  async cancel(handle) {
2037
2089
  await this.activeControllers.get(handle.acpxRecordId ?? handle.sessionKey)?.requestCancelActivePrompt();
@@ -2104,7 +2156,8 @@ var AcpRuntimeManager = class {
2104
2156
  if (connection.owner) await this.flushSessionOwner(connection.owner);
2105
2157
  }),
2106
2158
  await settleAttempt(() => connection.owner?.client.clearEventHandlers()),
2107
- await settleAttempt(async () => this.closeClient(connection.client))
2159
+ await settleAttempt(async () => this.closeClient(connection.client)),
2160
+ await settleAttempt(async () => connection.owner?.checkpoint.flush())
2108
2161
  ]);
2109
2162
  if (failure) throw failure.error;
2110
2163
  }
@@ -2239,6 +2292,16 @@ function sharedRecordId(handle) {
2239
2292
  if (handle.backend !== SHARED_BACKEND || !handle.acpxRecordId) return invalidOption("A shared runtime requires a handle from a shared session.");
2240
2293
  return handle.acpxRecordId;
2241
2294
  }
2295
+ /**
2296
+ * Shared controls are owner-only. Falling back to a direct connection would
2297
+ * start a second adapter connection for a session another process owns, and
2298
+ * that path may create a fresh provider session when the saved one no longer
2299
+ * loads. Shared turns already refuse that trade; controls refuse it too.
2300
+ */
2301
+ function requireOwnerDispatch(result, control) {
2302
+ 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.`);
2303
+ return result;
2304
+ }
2242
2305
  function turnFailure(error) {
2243
2306
  const normalized = normalizeOutputError(error, {
2244
2307
  defaultCode: "RUNTIME",
@@ -2264,6 +2327,7 @@ var SharedAcpRuntime = class {
2264
2327
  options;
2265
2328
  disconnect = new AbortController();
2266
2329
  pending = /* @__PURE__ */ new Set();
2330
+ controlChains = /* @__PURE__ */ new Map();
2267
2331
  constructor(options) {
2268
2332
  for (const name of [
2269
2333
  "sessionStore",
@@ -2414,6 +2478,72 @@ var SharedAcpRuntime = class {
2414
2478
  this.assertOpen();
2415
2479
  return runtimeStatusFromRecord(await resolveSessionRecord(sharedRecordId(input.handle)));
2416
2480
  }
2481
+ async getCapabilities(input) {
2482
+ this.assertOpen();
2483
+ if (!input?.handle) return capabilitiesFromRecord(void 0);
2484
+ return capabilitiesFromRecord(await readSessionRecord(sharedRecordId(input.handle)));
2485
+ }
2486
+ /**
2487
+ * Preserves this client's control order, including caller-side persistence
2488
+ * when talking to older owners. Updated owners checkpoint accepted controls
2489
+ * themselves; serialization here cannot coordinate other legacy callers.
2490
+ */
2491
+ chainControl(sessionId, run) {
2492
+ const result = (this.controlChains.get(sessionId) ?? Promise.resolve()).then(run, run);
2493
+ const chained = result.then(() => void 0, () => void 0);
2494
+ this.controlChains.set(sessionId, chained);
2495
+ chained.then(() => {
2496
+ if (this.controlChains.get(sessionId) === chained) this.controlChains.delete(sessionId);
2497
+ });
2498
+ return result;
2499
+ }
2500
+ /**
2501
+ * Checks this client's authority locally, then hands the control to the
2502
+ * running owner. The check gates this client's dispatch only; it cannot gate
2503
+ * controls other clients of the same owner send, and it cannot recall a
2504
+ * control the owner already accepted.
2505
+ */
2506
+ async dispatchControl(input, control, send) {
2507
+ this.assertOpen();
2508
+ const sessionId = sharedRecordId(input.handle);
2509
+ const assertAuthority = () => {
2510
+ this.assertOpen();
2511
+ assertControlAuthority(input);
2512
+ };
2513
+ return await this.track(this.chainControl(sessionId, async () => {
2514
+ assertAuthority();
2515
+ return requireOwnerDispatch(await send(sessionId, assertAuthority), control);
2516
+ }));
2517
+ }
2518
+ async setMode(input) {
2519
+ await this.dispatchControl(input, "session/set_mode", async (sessionId, assertAuthority) => setSessionModeOnOwner({
2520
+ sessionId,
2521
+ modeId: input.mode,
2522
+ timeoutMs: this.options.timeoutMs,
2523
+ assertDispatch: assertAuthority
2524
+ }));
2525
+ }
2526
+ async setModel(input) {
2527
+ await this.dispatchControl(input, "session/set_model", async (sessionId, assertAuthority) => setSessionModelOnOwner({
2528
+ sessionId,
2529
+ modelId: input.model,
2530
+ timeoutMs: this.options.timeoutMs,
2531
+ assertDispatch: assertAuthority
2532
+ }));
2533
+ }
2534
+ async setConfigOption(input) {
2535
+ return (await this.dispatchControl(input, "session/set_config_option", async (sessionId, assertAuthority) => {
2536
+ const configId = resolveSupportedConfigOptionId(await resolveSessionRecord(sessionId), input.key);
2537
+ assertAuthority();
2538
+ return await setSessionConfigOptionOnOwner({
2539
+ sessionId,
2540
+ configId,
2541
+ value: input.value,
2542
+ timeoutMs: this.options.timeoutMs,
2543
+ assertDispatch: assertAuthority
2544
+ });
2545
+ })).response;
2546
+ }
2417
2547
  watchSession(input) {
2418
2548
  this.assertOpen();
2419
2549
  const signal = input.signal ? AbortSignal.any([input.signal, this.disconnect.signal]) : this.disconnect.signal;
@@ -2448,12 +2578,6 @@ function createSharedAcpRuntime(options) {
2448
2578
  //#endregion
2449
2579
  //#region src/runtime.ts
2450
2580
  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
2581
  var AcpxRuntime = class {
2458
2582
  options;
2459
2583
  testOptions;
@@ -2595,13 +2719,7 @@ var AcpxRuntime = class {
2595
2719
  async getCapabilities(input) {
2596
2720
  if (!input?.handle) return ACPX_CAPABILITIES;
2597
2721
  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
- };
2722
+ return capabilitiesFromRecord(await this.options.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey));
2605
2723
  }
2606
2724
  async getStatus(input) {
2607
2725
  const { handle } = this.resolveManagerHandle(input.handle);