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.
- package/dist/{agent-registry-evUoJINz.js → agent-registry-DdKB-zia.js} +16 -17
- package/dist/{agent-registry-evUoJINz.js.map → agent-registry-DdKB-zia.js.map} +1 -1
- package/dist/agent-registry.d.ts.map +1 -1
- package/dist/agent-registry.js +1 -1
- package/dist/{cli-B2ycKzaO.js → cli-8mVHeUXh.js} +5 -5
- package/dist/{cli-B2ycKzaO.js.map → cli-8mVHeUXh.js.map} +1 -1
- package/dist/cli.js +122 -94
- package/dist/cli.js.map +1 -1
- package/dist/{client-CkbBoSGd.d.ts → client-DeNKZZcu.d.ts} +14 -12
- package/dist/client-DeNKZZcu.d.ts.map +1 -0
- package/dist/{flows-xGyhmoHO.js → flows-WOQNzCMC.js} +5 -5
- package/dist/{flows-xGyhmoHO.js.map → flows-WOQNzCMC.js.map} +1 -1
- package/dist/flows.d.ts +1 -1
- package/dist/flows.js +1 -1
- package/dist/{invocation-options-eZEBh4vd.js → invocation-options-B6d7TI-y.js} +7 -6
- package/dist/{invocation-options-eZEBh4vd.js.map → invocation-options-B6d7TI-y.js.map} +1 -1
- package/dist/{ipc-D2rstexT.js → ipc-BJrvfNXr.js} +305 -88
- package/dist/ipc-BJrvfNXr.js.map +1 -0
- package/dist/{output-D2xL6wdp.js → output-CL18YmsG.js} +9 -6
- package/dist/output-CL18YmsG.js.map +1 -0
- package/dist/{queue-owner-runtime-Dgv3e2Tp.js → queue-owner-runtime-Cr0dWJuL.js} +952 -433
- package/dist/queue-owner-runtime-Cr0dWJuL.js.map +1 -0
- package/dist/runtime.d.ts +39 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +272 -154
- package/dist/runtime.js.map +1 -1
- package/dist/{session-watch-CTe9Bnbc.js → session-watch-B8Q_TsGf.js} +4 -4
- package/dist/{session-watch-CTe9Bnbc.js.map → session-watch-B8Q_TsGf.js.map} +1 -1
- package/dist/{watch-DxuXEc8n.js → watch-Dp73ph8h.js} +2 -2
- package/dist/{watch-DxuXEc8n.js.map → watch-Dp73ph8h.js.map} +1 -1
- package/package.json +1 -1
- package/skills/acpx/SKILL.md +26 -7
- package/dist/client-CkbBoSGd.d.ts.map +0 -1
- package/dist/ipc-D2rstexT.js.map +0 -1
- package/dist/output-D2xL6wdp.js.map +0 -1
- package/dist/queue-owner-runtime-Dgv3e2Tp.js.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as
|
|
3
|
-
import { D as normalizeAgentCommandInput, a as createAgentRegistry, i as DEFAULT_AGENT_NAME } from "./agent-registry-
|
|
4
|
-
import { t as watchSession } from "./watch-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1243
|
+
reset_on_next_ensure: true
|
|
1219
1244
|
};
|
|
1220
1245
|
return true;
|
|
1221
1246
|
}
|
|
1222
1247
|
async retainPersistentSessionOwnerAfterTurn(input) {
|
|
1223
|
-
const { record, owner
|
|
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
|
-
|
|
1280
|
-
|
|
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) =>
|
|
1292
|
-
|
|
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
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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
|
|
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
|
|
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)
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
1695
|
-
|
|
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
|
-
|
|
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
|
|
1813
|
-
const response = await turn.client.setSessionConfigOption(turn.activeSessionId, resolvedConfigId, value, authority);
|
|
1814
|
-
turn.acpxState = applyConfigOptionSelection(turn.acpxState, resolvedConfigId, value, response,
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1928
|
-
|
|
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
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
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
|
-
|
|
1986
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
2077
|
+
return await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
|
|
2024
2078
|
const configId = resolveSupportedConfigOptionId(connectedRecord, key);
|
|
2025
|
-
const
|
|
2026
|
-
const response = await client.setSessionConfigOption(sessionId, configId, value, authority);
|
|
2027
|
-
connectedRecord.acpx = applyConfigOptionSelection(connectedRecord.acpx, configId, value, response,
|
|
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
|
-
|
|
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);
|