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.
- package/dist/{agent-registry-evUoJINz.js → agent-registry-Ct2yWPW7.js} +18 -18
- package/dist/agent-registry-Ct2yWPW7.js.map +1 -0
- package/dist/agent-registry.d.ts.map +1 -1
- package/dist/agent-registry.js +1 -1
- package/dist/{cli-B2ycKzaO.js → cli-Bmki9cj5.js} +5 -5
- package/dist/{cli-B2ycKzaO.js.map → cli-Bmki9cj5.js.map} +1 -1
- package/dist/cli.js +167 -131
- package/dist/cli.js.map +1 -1
- package/dist/{client-CkbBoSGd.d.ts → client-DzaUKazo.d.ts} +23 -15
- package/dist/client-DzaUKazo.d.ts.map +1 -0
- package/dist/{flows-xGyhmoHO.js → flows--Ewxn_fQ.js} +665 -460
- package/dist/flows--Ewxn_fQ.js.map +1 -0
- package/dist/flows.d.ts +19 -7
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{invocation-options-eZEBh4vd.js → invocation-options-DRzxHZmr.js} +34 -12
- package/dist/invocation-options-DRzxHZmr.js.map +1 -0
- package/dist/{ipc-D2rstexT.js → ipc-fMgj5vrO.js} +818 -302
- package/dist/ipc-fMgj5vrO.js.map +1 -0
- package/dist/{output-D2xL6wdp.js → output-D6kDs8BN.js} +30 -14
- package/dist/output-D6kDs8BN.js.map +1 -0
- package/dist/{queue-owner-runtime-Dgv3e2Tp.js → queue-owner-runtime-CLwjMYNL.js} +1448 -717
- package/dist/queue-owner-runtime-CLwjMYNL.js.map +1 -0
- package/dist/runtime.d.ts +57 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +303 -166
- package/dist/runtime.js.map +1 -1
- package/dist/session-options-Dp6mzNSy.d.ts.map +1 -1
- package/dist/{session-watch-CTe9Bnbc.js → session-watch-BfdIo-n4.js} +5 -5
- package/dist/session-watch-BfdIo-n4.js.map +1 -0
- package/dist/{watch-DxuXEc8n.js → watch-DH-zXQH2.js} +2 -2
- package/dist/{watch-DxuXEc8n.js.map → watch-DH-zXQH2.js.map} +1 -1
- package/package.json +1 -1
- package/skills/acpx/SKILL.md +69 -30
- package/dist/agent-registry-evUoJINz.js.map +0 -1
- package/dist/client-CkbBoSGd.d.ts.map +0 -1
- package/dist/flows-xGyhmoHO.js.map +0 -1
- package/dist/invocation-options-eZEBh4vd.js.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/session-watch-CTe9Bnbc.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 { 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
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1248
|
+
reset_on_next_ensure: true
|
|
1219
1249
|
};
|
|
1220
1250
|
return true;
|
|
1221
1251
|
}
|
|
1222
1252
|
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);
|
|
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
|
-
|
|
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
|
-
|
|
1280
|
-
|
|
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) =>
|
|
1292
|
-
|
|
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
|
-
|
|
1353
|
+
replacingMode: replayOptions?.replacingMode,
|
|
1354
|
+
replacingConfigOption: replayOptions?.replacingConfigOption,
|
|
1314
1355
|
authority,
|
|
1315
1356
|
run
|
|
1316
|
-
});
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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
|
|
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
|
|
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)
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
1695
|
-
|
|
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
|
-
|
|
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
|
|
1813
|
-
const response = await turn.client.setSessionConfigOption(turn.activeSessionId, resolvedConfigId, value, authority);
|
|
1814
|
-
turn.acpxState = applyConfigOptionSelection(turn.acpxState, resolvedConfigId, value, response,
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1928
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
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
|
-
|
|
1986
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
2092
|
+
return await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
|
|
2024
2093
|
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,
|
|
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
|
-
|
|
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
|
|
2739
|
+
if (!input?.handle) return capabilitiesFromRecord(void 0);
|
|
2597
2740
|
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
|
-
};
|
|
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);
|