acpx 0.17.0 → 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-DU6mhZBL.js → agent-registry-DdKB-zia.js} +20 -17
- package/dist/agent-registry-DdKB-zia.js.map +1 -0
- package/dist/agent-registry.d.ts.map +1 -1
- package/dist/agent-registry.js +1 -1
- package/dist/{cli-CiyfEC7L.js → cli-8mVHeUXh.js} +5 -5
- package/dist/{cli-CiyfEC7L.js.map → cli-8mVHeUXh.js.map} +1 -1
- package/dist/cli.js +122 -94
- package/dist/cli.js.map +1 -1
- package/dist/{client-C6NUFqr-.d.ts → client-DeNKZZcu.d.ts} +19 -13
- package/dist/client-DeNKZZcu.d.ts.map +1 -0
- package/dist/{flows-CTOdIk-m.js → flows-WOQNzCMC.js} +5 -5
- package/dist/{flows-CTOdIk-m.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-DbCcOAzM.js → invocation-options-B6d7TI-y.js} +7 -6
- package/dist/{invocation-options-DbCcOAzM.js.map → invocation-options-B6d7TI-y.js.map} +1 -1
- package/dist/{ipc-D3akHMEq.js → ipc-BJrvfNXr.js} +307 -90
- package/dist/ipc-BJrvfNXr.js.map +1 -0
- package/dist/{output-BPPjvJTI.js → output-CL18YmsG.js} +9 -6
- package/dist/output-CL18YmsG.js.map +1 -0
- package/dist/{queue-owner-runtime--IbbndGz.js → queue-owner-runtime-Cr0dWJuL.js} +1008 -463
- package/dist/queue-owner-runtime-Cr0dWJuL.js.map +1 -0
- package/dist/runtime.d.ts +129 -39
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +378 -176
- package/dist/runtime.js.map +1 -1
- package/dist/{session-watch--UX5dh00.js → session-watch-B8Q_TsGf.js} +4 -4
- package/dist/{session-watch--UX5dh00.js.map → session-watch-B8Q_TsGf.js.map} +1 -1
- package/dist/{watch-BbBxO-kb.js → watch-Dp73ph8h.js} +2 -2
- package/dist/{watch-BbBxO-kb.js.map → watch-Dp73ph8h.js.map} +1 -1
- package/package.json +3 -3
- package/skills/acpx/SKILL.md +27 -7
- package/dist/agent-registry-DU6mhZBL.js.map +0 -1
- package/dist/client-C6NUFqr-.d.ts.map +0 -1
- package/dist/ipc-D3akHMEq.js.map +0 -1
- package/dist/output-BPPjvJTI.js.map +0 -1
- package/dist/queue-owner-runtime--IbbndGz.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);
|
|
@@ -547,6 +579,61 @@ function updateStatusEvent(payload, tag) {
|
|
|
547
579
|
}
|
|
548
580
|
//#endregion
|
|
549
581
|
//#region src/runtime/public/probe.ts
|
|
582
|
+
/**
|
|
583
|
+
* Inspect session model metadata without a runtime store or prompt. The agent
|
|
584
|
+
* may create native session history; only its owned transport is closed.
|
|
585
|
+
*/
|
|
586
|
+
async function inspectAgentModels(options) {
|
|
587
|
+
options.signal?.throwIfAborted();
|
|
588
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
589
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) throw new RangeError("Model inspection timeoutMs must be positive and at most 2147483647");
|
|
590
|
+
const deadline = new AbortController();
|
|
591
|
+
const signal = options.signal ? AbortSignal.any([options.signal, deadline.signal]) : deadline.signal;
|
|
592
|
+
const assertActive = () => signal.throwIfAborted();
|
|
593
|
+
const client = new AcpClient({
|
|
594
|
+
...normalizeAgentCommandInput(options.agentCommand),
|
|
595
|
+
cwd: options.cwd,
|
|
596
|
+
agentProcessEnv: options.agentProcessEnv,
|
|
597
|
+
permissionMode: "deny-all",
|
|
598
|
+
nonInteractivePermissions: "deny",
|
|
599
|
+
fs: false,
|
|
600
|
+
terminal: false,
|
|
601
|
+
processLifecycle: {
|
|
602
|
+
onBeforeSpawn: assertActive,
|
|
603
|
+
onSpawned: assertActive
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
const timer = setTimeout(() => deadline.abort(new TimeoutError(timeoutMs)), timeoutMs);
|
|
607
|
+
let onAbort;
|
|
608
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
609
|
+
onAbort = () => reject(signal.reason);
|
|
610
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
611
|
+
});
|
|
612
|
+
const inspection = (async () => {
|
|
613
|
+
assertActive();
|
|
614
|
+
await client.start();
|
|
615
|
+
assertActive();
|
|
616
|
+
const { models } = await client.createSession();
|
|
617
|
+
assertActive();
|
|
618
|
+
return models ? {
|
|
619
|
+
currentModelId: models.currentModelId,
|
|
620
|
+
availableModelIds: models.availableModels.map((model) => model.modelId),
|
|
621
|
+
availableModels: models.availableModels
|
|
622
|
+
} : void 0;
|
|
623
|
+
})();
|
|
624
|
+
try {
|
|
625
|
+
return await Promise.race([inspection, aborted]);
|
|
626
|
+
} finally {
|
|
627
|
+
clearTimeout(timer);
|
|
628
|
+
signal.removeEventListener("abort", onAbort);
|
|
629
|
+
try {
|
|
630
|
+
if (signal.aborted) await client.close();
|
|
631
|
+
} finally {
|
|
632
|
+
await inspection.catch(() => {});
|
|
633
|
+
await client.close();
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
550
637
|
function isPrimitiveDetail(value) {
|
|
551
638
|
return value == null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol";
|
|
552
639
|
}
|
|
@@ -863,19 +950,13 @@ function createRecordId(sessionKey, mode) {
|
|
|
863
950
|
function resumePolicyForSessionMode(mode) {
|
|
864
951
|
return mode === "persistent" ? "same-session-only" : "allow-new";
|
|
865
952
|
}
|
|
866
|
-
function
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
if (!advertisedIds) return configId;
|
|
874
|
-
if (advertisedIds.has(configId)) return configId;
|
|
875
|
-
if (configId === "thinking" && advertisedIds.has("effort")) return "effort";
|
|
876
|
-
const supported = [...advertisedIds].toSorted();
|
|
877
|
-
const supportedText = supported.length > 0 ? supported.join(", ") : "none";
|
|
878
|
-
throw new AcpRuntimeError("ACP_BACKEND_UNSUPPORTED_CONTROL", `ACP session ${record.acpxRecordId} does not advertise config option '${configId}'. Supported config options: ${supportedText}.`);
|
|
953
|
+
async function startRuntimeClient(client, agent, timeoutMs) {
|
|
954
|
+
try {
|
|
955
|
+
await withTimeout(client.start(), timeoutMs);
|
|
956
|
+
} catch (error) {
|
|
957
|
+
if (!(error instanceof TimeoutError)) throw error;
|
|
958
|
+
throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", `ACP agent ${path.basename(agent.agentArgv?.[0] ?? agent.agentCommand.split(/\s+/)[0])} did not complete ACP initialization within ${timeoutMs}ms. Check that the configured command starts an ACP server.`, { cause: error });
|
|
959
|
+
}
|
|
879
960
|
}
|
|
880
961
|
async function createOrLoadRuntimeSession(client, resumeSessionId, cwd) {
|
|
881
962
|
if (resumeSessionId) {
|
|
@@ -906,6 +987,7 @@ var AcpRuntimeManager = class {
|
|
|
906
987
|
options;
|
|
907
988
|
deps;
|
|
908
989
|
activeControllers = /* @__PURE__ */ new Map();
|
|
990
|
+
controlSessionRecords = /* @__PURE__ */ new Map();
|
|
909
991
|
retainedSessionOwners = /* @__PURE__ */ new Map();
|
|
910
992
|
retiringSessionOwners = /* @__PURE__ */ new Set();
|
|
911
993
|
pendingOneShotRecordIds = /* @__PURE__ */ new Map();
|
|
@@ -1022,6 +1104,7 @@ var AcpRuntimeManager = class {
|
|
|
1022
1104
|
createSessionOwner(input) {
|
|
1023
1105
|
const owner = {
|
|
1024
1106
|
...input,
|
|
1107
|
+
checkpoint: new LiveSessionCheckpoint({ save: async () => this.saveSessionOwner(owner) }),
|
|
1025
1108
|
bufferSessionUpdates: false,
|
|
1026
1109
|
pendingSessionUpdates: []
|
|
1027
1110
|
};
|
|
@@ -1031,13 +1114,26 @@ var AcpRuntimeManager = class {
|
|
|
1031
1114
|
});
|
|
1032
1115
|
return owner;
|
|
1033
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
|
+
}
|
|
1034
1130
|
routeOwnedSessionUpdate(owner, notification) {
|
|
1035
1131
|
const active = owner.activeTurn;
|
|
1036
1132
|
if (active) {
|
|
1037
1133
|
const { task, turn } = active;
|
|
1038
1134
|
if (turn.connected) {
|
|
1039
1135
|
turn.acpxState = recordSessionUpdate(turn.conversation, turn.acpxState, notification);
|
|
1040
|
-
|
|
1136
|
+
owner.checkpoint.request();
|
|
1041
1137
|
} else turn.record.acpx = recordSessionUpdate(turn.conversation, turn.record.acpx, notification);
|
|
1042
1138
|
trimConversationForRuntime(turn.conversation);
|
|
1043
1139
|
this.emitRuntimeTurnEvent(task, {
|
|
@@ -1058,7 +1154,7 @@ var AcpRuntimeManager = class {
|
|
|
1058
1154
|
}
|
|
1059
1155
|
projection.record.acpx = recordSessionUpdate(projection.conversation, projection.record.acpx, notification);
|
|
1060
1156
|
trimConversationForRuntime(projection.conversation);
|
|
1061
|
-
|
|
1157
|
+
owner.checkpoint.request();
|
|
1062
1158
|
}
|
|
1063
1159
|
routeOwnedClientOperation(owner, operation) {
|
|
1064
1160
|
const active = owner.activeTurn;
|
|
@@ -1066,7 +1162,7 @@ var AcpRuntimeManager = class {
|
|
|
1066
1162
|
const { task, turn } = active;
|
|
1067
1163
|
if (turn.connected) {
|
|
1068
1164
|
turn.acpxState = recordClientOperation(turn.conversation, turn.acpxState, operation);
|
|
1069
|
-
|
|
1165
|
+
owner.checkpoint.request();
|
|
1070
1166
|
} else turn.record.acpx = recordClientOperation(turn.conversation, turn.record.acpx, operation);
|
|
1071
1167
|
trimConversationForRuntime(turn.conversation);
|
|
1072
1168
|
this.emitRuntimeTurnEvent(task, {
|
|
@@ -1078,15 +1174,7 @@ var AcpRuntimeManager = class {
|
|
|
1078
1174
|
record.acpx = acpxState;
|
|
1079
1175
|
owner.projection = {
|
|
1080
1176
|
record,
|
|
1081
|
-
conversation
|
|
1082
|
-
checkpoint: new LiveSessionCheckpoint({ save: async () => {
|
|
1083
|
-
if (!owner.recordId) return;
|
|
1084
|
-
record.lastUsedAt = isoNow();
|
|
1085
|
-
applyConversation(record, conversation);
|
|
1086
|
-
applyLifecycleSnapshotToRecord(record, owner.client.getAgentLifecycleSnapshot());
|
|
1087
|
-
await this.refreshClosedState(record);
|
|
1088
|
-
await this.options.sessionStore.save(record);
|
|
1089
|
-
} })
|
|
1177
|
+
conversation
|
|
1090
1178
|
};
|
|
1091
1179
|
owner.activeTurn = void 0;
|
|
1092
1180
|
this.drainPendingSessionUpdates(owner);
|
|
@@ -1096,7 +1184,7 @@ var AcpRuntimeManager = class {
|
|
|
1096
1184
|
}
|
|
1097
1185
|
async flushSessionOwner(owner) {
|
|
1098
1186
|
await owner.client.waitForSessionUpdatesIdle?.().catch(() => {});
|
|
1099
|
-
await owner.
|
|
1187
|
+
await owner.checkpoint.flush();
|
|
1100
1188
|
}
|
|
1101
1189
|
removeRetainedSessionOwner(owner) {
|
|
1102
1190
|
if (owner.recordId && this.retainedSessionOwners.get(owner.recordId) === owner) this.retainedSessionOwners.delete(owner.recordId);
|
|
@@ -1127,7 +1215,7 @@ var AcpRuntimeManager = class {
|
|
|
1127
1215
|
await this.closeClient(owner.client).catch(() => {
|
|
1128
1216
|
this.retiringSessionOwners.add(owner);
|
|
1129
1217
|
});
|
|
1130
|
-
await owner.
|
|
1218
|
+
await owner.checkpoint.flush().catch(() => {});
|
|
1131
1219
|
try {
|
|
1132
1220
|
owner.client.clearEventHandlers();
|
|
1133
1221
|
} catch {}
|
|
@@ -1150,19 +1238,15 @@ var AcpRuntimeManager = class {
|
|
|
1150
1238
|
const latest = await this.options.sessionStore.load(record.acpxRecordId).catch(() => void 0);
|
|
1151
1239
|
record.closed = true;
|
|
1152
1240
|
record.closedAt = latest?.closedAt ?? record.closedAt ?? isoNow();
|
|
1153
|
-
if (latest?.acpx) record.acpx = {
|
|
1241
|
+
if (latest?.acpx?.reset_on_next_ensure === true) record.acpx = {
|
|
1154
1242
|
...record.acpx,
|
|
1155
|
-
|
|
1243
|
+
reset_on_next_ensure: true
|
|
1156
1244
|
};
|
|
1157
1245
|
return true;
|
|
1158
1246
|
}
|
|
1159
1247
|
async retainPersistentSessionOwnerAfterTurn(input) {
|
|
1160
|
-
const { record, owner
|
|
1161
|
-
if (!this.canRetainPersistentSessionOwner(owner, record))
|
|
1162
|
-
owner.activeTurn = void 0;
|
|
1163
|
-
return false;
|
|
1164
|
-
}
|
|
1165
|
-
this.attachIdleProjection(owner, record, conversation, acpxState);
|
|
1248
|
+
const { record, owner } = input;
|
|
1249
|
+
if (!this.canRetainPersistentSessionOwner(owner, record)) return false;
|
|
1166
1250
|
const previousOwner = this.retainedSessionOwners.get(record.acpxRecordId);
|
|
1167
1251
|
this.retainedSessionOwners.set(record.acpxRecordId, owner);
|
|
1168
1252
|
if (previousOwner && previousOwner !== owner) {
|
|
@@ -1184,6 +1268,21 @@ var AcpRuntimeManager = class {
|
|
|
1184
1268
|
agentArgv: agentArgv ? [...agentArgv] : void 0
|
|
1185
1269
|
}) : servers ?? []];
|
|
1186
1270
|
}
|
|
1271
|
+
resolveSessionPermissions(session) {
|
|
1272
|
+
const { sessionKey, cwd, agentCommand, agentArgv } = session;
|
|
1273
|
+
const { permissionMode = this.options.permissionMode, nonInteractivePermissions = this.options.nonInteractivePermissions, permissionPolicy = this.options.permissionPolicy, onPermissionRequest = this.options.onPermissionRequest } = this.options.sessionPermissions?.({
|
|
1274
|
+
sessionKey,
|
|
1275
|
+
cwd,
|
|
1276
|
+
agentCommand,
|
|
1277
|
+
agentArgv: agentArgv ? [...agentArgv] : void 0
|
|
1278
|
+
}) ?? {};
|
|
1279
|
+
return {
|
|
1280
|
+
permissionMode,
|
|
1281
|
+
nonInteractivePermissions,
|
|
1282
|
+
permissionPolicy,
|
|
1283
|
+
onPermissionRequest
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1187
1286
|
async withRuntimeControlSession(record, sessionMode, run, replacingConfigOption, authority) {
|
|
1188
1287
|
await this.retrySessionCleanup(record.acpxRecordId);
|
|
1189
1288
|
assertControlAuthority(authority);
|
|
@@ -1198,20 +1297,27 @@ var AcpRuntimeManager = class {
|
|
|
1198
1297
|
record: ownedRecord
|
|
1199
1298
|
});
|
|
1200
1299
|
this.refreshOwnedRecordLifecycle(owner, ownedRecord);
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
record: ownedRecord
|
|
1204
|
-
};
|
|
1300
|
+
await owner.checkpoint.checkpoint();
|
|
1301
|
+
return value;
|
|
1205
1302
|
} finally {
|
|
1206
1303
|
await this.flushSessionOwner(owner);
|
|
1207
1304
|
}
|
|
1208
1305
|
}
|
|
1209
1306
|
let controlClient;
|
|
1307
|
+
let controlledRecord = record;
|
|
1308
|
+
const checkpoint = new LiveSessionCheckpoint({ save: async () => this.options.sessionStore.save(controlledRecord) });
|
|
1210
1309
|
try {
|
|
1211
|
-
const result = await withConnectedSession({
|
|
1310
|
+
const result = await settleAttempt(async () => withConnectedSession({
|
|
1212
1311
|
sessionRecordId: record.acpxRecordId,
|
|
1213
|
-
loadRecord: async (sessionRecordId) =>
|
|
1214
|
-
|
|
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(),
|
|
1215
1321
|
createClient: (options) => controlClient = this.createClient({
|
|
1216
1322
|
...options,
|
|
1217
1323
|
processLifecycle: this.options.processLifecycle,
|
|
@@ -1224,10 +1330,10 @@ var AcpRuntimeManager = class {
|
|
|
1224
1330
|
...record,
|
|
1225
1331
|
sessionKey: record.name ?? record.acpxRecordId
|
|
1226
1332
|
}),
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1333
|
+
...this.resolveSessionPermissions({
|
|
1334
|
+
...record,
|
|
1335
|
+
sessionKey: record.name ?? record.acpxRecordId
|
|
1336
|
+
}),
|
|
1231
1337
|
elicitationModes: this.options.elicitationModes,
|
|
1232
1338
|
verbose: this.options.verbose,
|
|
1233
1339
|
timeoutMs: this.options.timeoutMs,
|
|
@@ -1235,12 +1341,14 @@ var AcpRuntimeManager = class {
|
|
|
1235
1341
|
replacingConfigOption,
|
|
1236
1342
|
authority,
|
|
1237
1343
|
run
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
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;
|
|
1243
1350
|
} finally {
|
|
1351
|
+
this.controlSessionRecords.delete(record.acpxRecordId);
|
|
1244
1352
|
if (controlClient) this.liveClients.delete(controlClient);
|
|
1245
1353
|
}
|
|
1246
1354
|
}
|
|
@@ -1262,6 +1370,13 @@ var AcpRuntimeManager = class {
|
|
|
1262
1370
|
async withManagerLock(locks, key, run, authority) {
|
|
1263
1371
|
this.assertOpen();
|
|
1264
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) {
|
|
1265
1380
|
const previous = locks.get(key) ?? Promise.resolve();
|
|
1266
1381
|
let release;
|
|
1267
1382
|
const gate = new Promise((resolve) => {
|
|
@@ -1271,8 +1386,6 @@ var AcpRuntimeManager = class {
|
|
|
1271
1386
|
locks.set(key, tail);
|
|
1272
1387
|
await previous;
|
|
1273
1388
|
try {
|
|
1274
|
-
this.assertOpen();
|
|
1275
|
-
assertControlAuthority(authority);
|
|
1276
1389
|
return await run();
|
|
1277
1390
|
} finally {
|
|
1278
1391
|
release();
|
|
@@ -1287,8 +1400,26 @@ var AcpRuntimeManager = class {
|
|
|
1287
1400
|
agentCommand,
|
|
1288
1401
|
agentArgv
|
|
1289
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) {
|
|
1290
1421
|
const existing = await this.loadExistingRuntimeSession(input);
|
|
1291
|
-
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);
|
|
1292
1423
|
await this.closeConflictingPersistentSession(input, existing?.owner);
|
|
1293
1424
|
return await this.createOwnedRuntimeSession(input, agent);
|
|
1294
1425
|
}
|
|
@@ -1319,17 +1450,26 @@ var AcpRuntimeManager = class {
|
|
|
1319
1450
|
resumeSessionId: input.resumeSessionId
|
|
1320
1451
|
})) return false;
|
|
1321
1452
|
if (input.mode === "persistent") return true;
|
|
1322
|
-
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.`);
|
|
1323
1457
|
}
|
|
1324
|
-
async reuseRuntimeSession(record) {
|
|
1458
|
+
async reuseRuntimeSession({ record, owner }, checkpoint = owner?.checkpoint) {
|
|
1459
|
+
if (!record.closed && !this.closingActiveRecords.has(record.acpxRecordId)) return record;
|
|
1325
1460
|
record.closed = false;
|
|
1326
1461
|
record.closedAt = void 0;
|
|
1327
1462
|
this.closingActiveRecords.delete(record.acpxRecordId);
|
|
1328
|
-
await
|
|
1463
|
+
if (checkpoint) await checkpoint.checkpoint();
|
|
1464
|
+
else await this.options.sessionStore.save(record);
|
|
1329
1465
|
return record;
|
|
1330
1466
|
}
|
|
1331
1467
|
async closeConflictingPersistentSession(input, owner) {
|
|
1332
|
-
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
|
+
}
|
|
1333
1473
|
}
|
|
1334
1474
|
async createOwnedRuntimeSession(input, agent) {
|
|
1335
1475
|
const { cwd, agentCommand, agentArgv } = agent;
|
|
@@ -1341,10 +1481,10 @@ var AcpRuntimeManager = class {
|
|
|
1341
1481
|
...agent,
|
|
1342
1482
|
sessionKey: input.sessionKey
|
|
1343
1483
|
}),
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1484
|
+
...this.resolveSessionPermissions({
|
|
1485
|
+
...agent,
|
|
1486
|
+
sessionKey: input.sessionKey
|
|
1487
|
+
}),
|
|
1348
1488
|
elicitationModes: this.options.elicitationModes,
|
|
1349
1489
|
processLifecycle: this.options.processLifecycle,
|
|
1350
1490
|
processLaunchScope: {
|
|
@@ -1361,7 +1501,7 @@ var AcpRuntimeManager = class {
|
|
|
1361
1501
|
});
|
|
1362
1502
|
let retained = false;
|
|
1363
1503
|
try {
|
|
1364
|
-
await client.
|
|
1504
|
+
await startRuntimeClient(client, agent, this.options.timeoutMs);
|
|
1365
1505
|
this.assertOpen();
|
|
1366
1506
|
const session = await createOrLoadRuntimeSession(client, input.resumeSessionId, cwd);
|
|
1367
1507
|
const record = await this.prepareInitialRuntimeRecord({
|
|
@@ -1415,7 +1555,7 @@ var AcpRuntimeManager = class {
|
|
|
1415
1555
|
}
|
|
1416
1556
|
async retainInitializedSessionOwner(owner, record) {
|
|
1417
1557
|
owner.recordId = record.acpxRecordId;
|
|
1418
|
-
await owner.
|
|
1558
|
+
await owner.checkpoint.checkpoint();
|
|
1419
1559
|
this.assertOpen();
|
|
1420
1560
|
const previousOwner = this.retainedSessionOwners.get(record.acpxRecordId);
|
|
1421
1561
|
this.retainedSessionOwners.set(record.acpxRecordId, owner);
|
|
@@ -1465,7 +1605,7 @@ var AcpRuntimeManager = class {
|
|
|
1465
1605
|
return true;
|
|
1466
1606
|
};
|
|
1467
1607
|
const abortHandler = () => {
|
|
1468
|
-
requestCancel();
|
|
1608
|
+
requestCancel().catch(() => {});
|
|
1469
1609
|
};
|
|
1470
1610
|
if (input.signal && !input.signal.aborted) input.signal.addEventListener("abort", abortHandler, { once: true });
|
|
1471
1611
|
this.queueSessionTask(input.handle.acpxRecordId ?? input.handle.sessionKey, async () => this.runRuntimeTurnTask({
|
|
@@ -1493,7 +1633,9 @@ var AcpRuntimeManager = class {
|
|
|
1493
1633
|
}
|
|
1494
1634
|
async closeRetainedOneShotHandle(handle) {
|
|
1495
1635
|
const recordId = handle.acpxRecordId ?? handle.sessionKey;
|
|
1496
|
-
|
|
1636
|
+
await this.serializeManagerOperation(this.runtimeOperationLocks, recordId, async () => {
|
|
1637
|
+
if (this.retainedSessionOwners.get(recordId)?.mode === "oneshot") await this.closeRetainedSessionOwner(recordId);
|
|
1638
|
+
});
|
|
1497
1639
|
}
|
|
1498
1640
|
async runRuntimeTurnTask(task) {
|
|
1499
1641
|
let turn;
|
|
@@ -1515,9 +1657,8 @@ var AcpRuntimeManager = class {
|
|
|
1515
1657
|
stopReason: "cancelled"
|
|
1516
1658
|
};
|
|
1517
1659
|
else {
|
|
1518
|
-
await this.applyPendingRuntimeTurnCancel(task, turn);
|
|
1519
1660
|
const response = await this.runRuntimePrompt(task, turn, sessionId);
|
|
1520
|
-
|
|
1661
|
+
this.updateCompletedRuntimeTurn(turn);
|
|
1521
1662
|
terminalResult = {
|
|
1522
1663
|
status: response.stopReason === "cancelled" ? "cancelled" : "completed",
|
|
1523
1664
|
...response.stopReason ? { stopReason: response.stopReason } : {},
|
|
@@ -1526,9 +1667,9 @@ var AcpRuntimeManager = class {
|
|
|
1526
1667
|
}
|
|
1527
1668
|
}
|
|
1528
1669
|
} catch (error) {
|
|
1529
|
-
terminalResult = this.
|
|
1670
|
+
terminalResult = this.handleRuntimeTurnFailure(task, error);
|
|
1530
1671
|
}
|
|
1531
|
-
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)));
|
|
1532
1673
|
if (!finalization.ok) {
|
|
1533
1674
|
task.settleResult(this.failRuntimeTurn(task, finalization.error));
|
|
1534
1675
|
throw finalization.error;
|
|
@@ -1536,21 +1677,18 @@ var AcpRuntimeManager = class {
|
|
|
1536
1677
|
task.settleResult(terminalResult);
|
|
1537
1678
|
}
|
|
1538
1679
|
async runRuntimePrompt(task, turn, sessionId) {
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
} finally {
|
|
1552
|
-
turn.client.endPromptElicitation?.(sessionId);
|
|
1553
|
-
}
|
|
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
|
+
});
|
|
1554
1692
|
}
|
|
1555
1693
|
async prepareRuntimeTurn(task) {
|
|
1556
1694
|
const recordId = task.input.handle.acpxRecordId ?? task.input.handle.sessionKey;
|
|
@@ -1610,13 +1748,19 @@ var AcpRuntimeManager = class {
|
|
|
1610
1748
|
let record = await this.requireRecord(recordId);
|
|
1611
1749
|
const retainedOwner = await this.readRetainedSessionOwner(record, { consume: false });
|
|
1612
1750
|
if (!retainedOwner) return { record };
|
|
1613
|
-
const projection = retainedOwner.projection;
|
|
1614
|
-
if (projection) record = structuredClone(projection.record);
|
|
1615
1751
|
retainedOwner.bufferSessionUpdates = true;
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
retainedOwner
|
|
1619
|
-
|
|
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
|
+
}
|
|
1620
1764
|
}
|
|
1621
1765
|
restoreBufferedSessionOwner(owner) {
|
|
1622
1766
|
if (!owner) return;
|
|
@@ -1632,18 +1776,16 @@ var AcpRuntimeManager = class {
|
|
|
1632
1776
|
}
|
|
1633
1777
|
createRunningRuntimeTurn(input) {
|
|
1634
1778
|
const { record, conversation, acpxState, client, owner, connected, promptMessageId } = input;
|
|
1635
|
-
|
|
1779
|
+
return {
|
|
1636
1780
|
record,
|
|
1637
1781
|
conversation,
|
|
1638
1782
|
acpxState,
|
|
1639
|
-
liveCheckpoint: this.createRuntimeTurnCheckpoint(record, conversation, () => turn.acpxState),
|
|
1640
1783
|
client,
|
|
1641
1784
|
owner,
|
|
1642
1785
|
connected,
|
|
1643
1786
|
promptMessageId,
|
|
1644
1787
|
activeSessionId: record.acpSessionId
|
|
1645
1788
|
};
|
|
1646
|
-
return turn;
|
|
1647
1789
|
}
|
|
1648
1790
|
activateRuntimeTurn(task, turn) {
|
|
1649
1791
|
const { owner, record } = turn;
|
|
@@ -1668,10 +1810,10 @@ var AcpRuntimeManager = class {
|
|
|
1668
1810
|
...record,
|
|
1669
1811
|
sessionKey: record.name ?? record.acpxRecordId
|
|
1670
1812
|
}),
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1813
|
+
...this.resolveSessionPermissions({
|
|
1814
|
+
...record,
|
|
1815
|
+
sessionKey: record.name ?? record.acpxRecordId
|
|
1816
|
+
}),
|
|
1675
1817
|
elicitationModes: this.options.elicitationModes,
|
|
1676
1818
|
processLifecycle: this.options.processLifecycle,
|
|
1677
1819
|
processLaunchScope: {
|
|
@@ -1682,15 +1824,6 @@ var AcpRuntimeManager = class {
|
|
|
1682
1824
|
sessionOptions: sessionOptionsFromRecord(record)
|
|
1683
1825
|
});
|
|
1684
1826
|
}
|
|
1685
|
-
createRuntimeTurnCheckpoint(record, conversation, readAcpxState) {
|
|
1686
|
-
return new LiveSessionCheckpoint({ save: async () => {
|
|
1687
|
-
record.lastUsedAt = isoNow();
|
|
1688
|
-
record.acpx = readAcpxState();
|
|
1689
|
-
applyConversation(record, conversation);
|
|
1690
|
-
await this.refreshClosedState(record);
|
|
1691
|
-
await this.options.sessionStore.save(record);
|
|
1692
|
-
} });
|
|
1693
|
-
}
|
|
1694
1827
|
buildRuntimeTurnController(task, turn) {
|
|
1695
1828
|
return {
|
|
1696
1829
|
hasActivePrompt: () => turn.client.hasActivePrompt(),
|
|
@@ -1701,12 +1834,14 @@ var AcpRuntimeManager = class {
|
|
|
1701
1834
|
const nextState = cloneSessionAcpxState(turn.acpxState) ?? {};
|
|
1702
1835
|
nextState.desired_mode_id = modeId;
|
|
1703
1836
|
turn.acpxState = nextState;
|
|
1837
|
+
await turn.owner.checkpoint.checkpoint();
|
|
1704
1838
|
},
|
|
1705
1839
|
setSessionModel: async (modelId, authority) => {
|
|
1706
1840
|
await this.waitForRuntimeControlSession(task, turn);
|
|
1707
1841
|
const models = advertisedModelState(turn.acpxState);
|
|
1708
1842
|
const response = await turn.client.setSessionModel(turn.activeSessionId, modelId, models, authority);
|
|
1709
1843
|
turn.acpxState = applyModelSelection(turn.acpxState, modelId, response);
|
|
1844
|
+
await turn.owner.checkpoint.checkpoint();
|
|
1710
1845
|
return response;
|
|
1711
1846
|
},
|
|
1712
1847
|
setSessionConfigOption: async (configId, value, authority) => {
|
|
@@ -1731,9 +1866,10 @@ var AcpRuntimeManager = class {
|
|
|
1731
1866
|
...turn.record,
|
|
1732
1867
|
acpx: turn.acpxState ?? void 0
|
|
1733
1868
|
}, configId);
|
|
1734
|
-
const
|
|
1735
|
-
const response = await turn.client.setSessionConfigOption(turn.activeSessionId, resolvedConfigId, value, authority);
|
|
1736
|
-
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();
|
|
1737
1873
|
return {
|
|
1738
1874
|
configId: resolvedConfigId,
|
|
1739
1875
|
response
|
|
@@ -1782,7 +1918,7 @@ var AcpRuntimeManager = class {
|
|
|
1782
1918
|
turn.record.closed = false;
|
|
1783
1919
|
turn.record.closedAt = void 0;
|
|
1784
1920
|
turn.record.lastUsedAt = isoNow();
|
|
1785
|
-
await turn.
|
|
1921
|
+
await turn.owner.checkpoint.checkpoint();
|
|
1786
1922
|
}
|
|
1787
1923
|
cancelRuntimeTurnBeforePrompt(task) {
|
|
1788
1924
|
if (!task.state.pendingCancel && !task.input.signal?.aborted) return false;
|
|
@@ -1790,21 +1926,20 @@ var AcpRuntimeManager = class {
|
|
|
1790
1926
|
task.promptStarted.reject(/* @__PURE__ */ new Error("ACP turn cancelled before prompt submission."));
|
|
1791
1927
|
return true;
|
|
1792
1928
|
}
|
|
1793
|
-
|
|
1794
|
-
if (!task.state.pendingCancel || !turn.client.hasActivePrompt()) return false;
|
|
1795
|
-
const cancelled = await turn.client.requestCancelActivePrompt();
|
|
1796
|
-
if (cancelled) task.state.pendingCancel = false;
|
|
1797
|
-
return cancelled;
|
|
1798
|
-
}
|
|
1799
|
-
async saveCompletedRuntimeTurn(turn) {
|
|
1929
|
+
updateCompletedRuntimeTurn(turn) {
|
|
1800
1930
|
turn.record.acpSessionId = turn.activeSessionId;
|
|
1801
1931
|
reconcileAgentSessionId(turn.record, turn.record.agentSessionId);
|
|
1802
1932
|
turn.record.protocolVersion = turn.client.initializeResult?.protocolVersion;
|
|
1803
1933
|
turn.record.agentCapabilities = turn.client.initializeResult?.agentCapabilities;
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1934
|
+
}
|
|
1935
|
+
handleRuntimeTurnFailure(task, error) {
|
|
1936
|
+
if (!task.input.signal?.aborted || error !== task.input.signal.reason) return this.failRuntimeTurn(task, error);
|
|
1937
|
+
task.promptStarted.reject(error);
|
|
1938
|
+
task.sessionReady.reject(error);
|
|
1939
|
+
return {
|
|
1940
|
+
status: "cancelled",
|
|
1941
|
+
stopReason: "cancelled"
|
|
1942
|
+
};
|
|
1808
1943
|
}
|
|
1809
1944
|
failRuntimeTurn(task, error) {
|
|
1810
1945
|
task.promptStarted.reject(error);
|
|
@@ -1838,13 +1973,24 @@ var AcpRuntimeManager = class {
|
|
|
1838
1973
|
discardRetainedRuntimeTurnOwner(turn) {
|
|
1839
1974
|
if (turn) {
|
|
1840
1975
|
this.removeRetainedSessionOwner(turn.owner);
|
|
1841
|
-
turn
|
|
1976
|
+
this.attachFinalRuntimeProjection(turn);
|
|
1842
1977
|
}
|
|
1843
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
|
+
}
|
|
1844
1987
|
async closeRuntimeTurnClient(turn, pooled) {
|
|
1845
1988
|
if (!turn || pooled) return;
|
|
1846
|
-
|
|
1847
|
-
|
|
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
|
+
]);
|
|
1848
1994
|
if (failure) {
|
|
1849
1995
|
this.retiringSessionOwners.add(turn.owner);
|
|
1850
1996
|
throw failure.error;
|
|
@@ -1859,19 +2005,12 @@ var AcpRuntimeManager = class {
|
|
|
1859
2005
|
}
|
|
1860
2006
|
async finalizeRuntimeTurnRecord(turn) {
|
|
1861
2007
|
if (!turn.connected) turn.acpxState = cloneSessionAcpxState(turn.record.acpx);
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
turn.record.lastUsedAt = isoNow();
|
|
1866
|
-
await turn.liveCheckpoint.flush();
|
|
1867
|
-
const closed = await this.refreshClosedState(turn.record);
|
|
1868
|
-
await this.options.sessionStore.save(turn.record);
|
|
1869
|
-
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;
|
|
1870
2011
|
return await this.retainPersistentSessionOwnerAfterTurn({
|
|
1871
2012
|
record: turn.record,
|
|
1872
|
-
owner: turn.owner
|
|
1873
|
-
conversation: turn.conversation,
|
|
1874
|
-
acpxState: turn.acpxState
|
|
2013
|
+
owner: turn.owner
|
|
1875
2014
|
});
|
|
1876
2015
|
}
|
|
1877
2016
|
async *runTurn(input) {
|
|
@@ -1897,13 +2036,14 @@ var AcpRuntimeManager = class {
|
|
|
1897
2036
|
const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
|
|
1898
2037
|
assertControlAuthority(authority);
|
|
1899
2038
|
const controller = this.activeControllers.get(record.acpxRecordId);
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
2039
|
+
if (controller) {
|
|
2040
|
+
await controller.setSessionMode(mode, authority);
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
|
|
1903
2044
|
await client.setSessionMode(sessionId, mode, authority);
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
await this.options.sessionStore.save(targetRecord);
|
|
2045
|
+
setDesiredModeId(connectedRecord, mode);
|
|
2046
|
+
}, void 0, authority);
|
|
1907
2047
|
}
|
|
1908
2048
|
async setModel(handle, model, sessionMode = "persistent", authority) {
|
|
1909
2049
|
const recordId = handle.acpxRecordId ?? handle.sessionKey;
|
|
@@ -1914,16 +2054,13 @@ var AcpRuntimeManager = class {
|
|
|
1914
2054
|
assertControlAuthority(authority);
|
|
1915
2055
|
const controller = this.activeControllers.get(record.acpxRecordId);
|
|
1916
2056
|
if (controller) {
|
|
1917
|
-
|
|
1918
|
-
record.acpx = applyModelSelection(record.acpx, model, response);
|
|
1919
|
-
await this.options.sessionStore.save(record);
|
|
2057
|
+
await controller.setSessionModel(model, authority);
|
|
1920
2058
|
return;
|
|
1921
2059
|
}
|
|
1922
|
-
|
|
2060
|
+
await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
|
|
1923
2061
|
const response = await client.setSessionModel(sessionId, model, advertisedModelState(connectedRecord.acpx), authority);
|
|
1924
2062
|
connectedRecord.acpx = applyModelSelection(connectedRecord.acpx, model, response);
|
|
1925
2063
|
}, { key: "model" }, authority);
|
|
1926
|
-
await this.options.sessionStore.save(result.record);
|
|
1927
2064
|
}
|
|
1928
2065
|
async setConfigOption(handle, key, value, sessionMode = "persistent", authority) {
|
|
1929
2066
|
const recordId = handle.acpxRecordId ?? handle.sessionKey;
|
|
@@ -1934,23 +2071,19 @@ var AcpRuntimeManager = class {
|
|
|
1934
2071
|
assertControlAuthority(authority);
|
|
1935
2072
|
const controller = this.activeControllers.get(record.acpxRecordId);
|
|
1936
2073
|
if (controller) {
|
|
1937
|
-
const {
|
|
1938
|
-
record.acpx = applyConfigOptionSelection(record.acpx, configId, value, response);
|
|
1939
|
-
await this.options.sessionStore.save(record);
|
|
2074
|
+
const { response } = await controller.setResolvedSessionConfigOption(key, value, authority);
|
|
1940
2075
|
return response;
|
|
1941
2076
|
}
|
|
1942
|
-
|
|
2077
|
+
return await this.withRuntimeControlSession(record, sessionMode, async ({ client, sessionId, record: connectedRecord }) => {
|
|
1943
2078
|
const configId = resolveSupportedConfigOptionId(connectedRecord, key);
|
|
1944
|
-
const
|
|
1945
|
-
const response = await client.setSessionConfigOption(sessionId, configId, value, authority);
|
|
1946
|
-
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);
|
|
1947
2082
|
return response;
|
|
1948
2083
|
}, {
|
|
1949
2084
|
key,
|
|
1950
2085
|
resolve: (connectedRecord) => resolveSupportedConfigOptionId(connectedRecord, key)
|
|
1951
2086
|
}, authority);
|
|
1952
|
-
await this.options.sessionStore.save(result.record);
|
|
1953
|
-
return result.value;
|
|
1954
2087
|
}
|
|
1955
2088
|
async cancel(handle) {
|
|
1956
2089
|
await this.activeControllers.get(handle.acpxRecordId ?? handle.sessionKey)?.requestCancelActivePrompt();
|
|
@@ -2023,7 +2156,8 @@ var AcpRuntimeManager = class {
|
|
|
2023
2156
|
if (connection.owner) await this.flushSessionOwner(connection.owner);
|
|
2024
2157
|
}),
|
|
2025
2158
|
await settleAttempt(() => connection.owner?.client.clearEventHandlers()),
|
|
2026
|
-
await settleAttempt(async () => this.closeClient(connection.client))
|
|
2159
|
+
await settleAttempt(async () => this.closeClient(connection.client)),
|
|
2160
|
+
await settleAttempt(async () => connection.owner?.checkpoint.flush())
|
|
2027
2161
|
]);
|
|
2028
2162
|
if (failure) throw failure.error;
|
|
2029
2163
|
}
|
|
@@ -2158,6 +2292,16 @@ function sharedRecordId(handle) {
|
|
|
2158
2292
|
if (handle.backend !== SHARED_BACKEND || !handle.acpxRecordId) return invalidOption("A shared runtime requires a handle from a shared session.");
|
|
2159
2293
|
return handle.acpxRecordId;
|
|
2160
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
|
+
}
|
|
2161
2305
|
function turnFailure(error) {
|
|
2162
2306
|
const normalized = normalizeOutputError(error, {
|
|
2163
2307
|
defaultCode: "RUNTIME",
|
|
@@ -2183,11 +2327,13 @@ var SharedAcpRuntime = class {
|
|
|
2183
2327
|
options;
|
|
2184
2328
|
disconnect = new AbortController();
|
|
2185
2329
|
pending = /* @__PURE__ */ new Set();
|
|
2330
|
+
controlChains = /* @__PURE__ */ new Map();
|
|
2186
2331
|
constructor(options) {
|
|
2187
2332
|
for (const name of [
|
|
2188
2333
|
"sessionStore",
|
|
2189
2334
|
"agentProcessEnv",
|
|
2190
2335
|
"mcpServers",
|
|
2336
|
+
"sessionPermissions",
|
|
2191
2337
|
"processLifecycle",
|
|
2192
2338
|
"onPermissionRequest",
|
|
2193
2339
|
"elicitationModes"
|
|
@@ -2239,7 +2385,7 @@ var SharedAcpRuntime = class {
|
|
|
2239
2385
|
}
|
|
2240
2386
|
startTurn(input) {
|
|
2241
2387
|
this.assertOpen();
|
|
2242
|
-
if (input.mode !== "prompt" || input.onPermissionRequest || input.onElicitation) invalidOption("Shared turns support prompt mode and the client's static permission policy; in-process callbacks and steering are unsupported.");
|
|
2388
|
+
if (input.mode !== "prompt" || input.assertActive || input.onPermissionRequest || input.onElicitation) invalidOption("Shared turns support prompt mode and the client's static permission policy; in-process callbacks and steering are unsupported.");
|
|
2243
2389
|
if (!input.requestId.trim()) invalidOption("A shared turn requires a requestId.");
|
|
2244
2390
|
const sessionId = sharedRecordId(input.handle);
|
|
2245
2391
|
const prompt = toPromptInput(input.text, input.attachments);
|
|
@@ -2332,6 +2478,72 @@ var SharedAcpRuntime = class {
|
|
|
2332
2478
|
this.assertOpen();
|
|
2333
2479
|
return runtimeStatusFromRecord(await resolveSessionRecord(sharedRecordId(input.handle)));
|
|
2334
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
|
+
}
|
|
2335
2547
|
watchSession(input) {
|
|
2336
2548
|
this.assertOpen();
|
|
2337
2549
|
const signal = input.signal ? AbortSignal.any([input.signal, this.disconnect.signal]) : this.disconnect.signal;
|
|
@@ -2366,12 +2578,6 @@ function createSharedAcpRuntime(options) {
|
|
|
2366
2578
|
//#endregion
|
|
2367
2579
|
//#region src/runtime.ts
|
|
2368
2580
|
const ACPX_BACKEND_ID = "acpx";
|
|
2369
|
-
const ACPX_CAPABILITIES = { controls: [
|
|
2370
|
-
"session/set_mode",
|
|
2371
|
-
"session/set_model",
|
|
2372
|
-
"session/set_config_option",
|
|
2373
|
-
"session/status"
|
|
2374
|
-
] };
|
|
2375
2581
|
var AcpxRuntime = class {
|
|
2376
2582
|
options;
|
|
2377
2583
|
testOptions;
|
|
@@ -2471,6 +2677,7 @@ var AcpxRuntime = class {
|
|
|
2471
2677
|
requestId: input.requestId,
|
|
2472
2678
|
timeoutMs: input.timeoutMs,
|
|
2473
2679
|
signal: input.signal,
|
|
2680
|
+
assertActive: input.assertActive,
|
|
2474
2681
|
onElicitation: input.onElicitation,
|
|
2475
2682
|
onPermissionRequest: input.onPermissionRequest
|
|
2476
2683
|
}));
|
|
@@ -2504,6 +2711,7 @@ var AcpxRuntime = class {
|
|
|
2504
2711
|
requestId: input.requestId,
|
|
2505
2712
|
timeoutMs: input.timeoutMs,
|
|
2506
2713
|
signal: input.signal,
|
|
2714
|
+
assertActive: input.assertActive,
|
|
2507
2715
|
onElicitation: input.onElicitation,
|
|
2508
2716
|
onPermissionRequest: input.onPermissionRequest
|
|
2509
2717
|
});
|
|
@@ -2511,13 +2719,7 @@ var AcpxRuntime = class {
|
|
|
2511
2719
|
async getCapabilities(input) {
|
|
2512
2720
|
if (!input?.handle) return ACPX_CAPABILITIES;
|
|
2513
2721
|
const { handle } = this.resolveManagerHandle(input.handle);
|
|
2514
|
-
|
|
2515
|
-
if (!record?.acpx?.config_options) return ACPX_CAPABILITIES;
|
|
2516
|
-
const configOptionKeys = Array.from(new Set(record.acpx.config_options.map((option) => option.id).filter((id) => typeof id === "string" && id.trim().length > 0)));
|
|
2517
|
-
return {
|
|
2518
|
-
...ACPX_CAPABILITIES,
|
|
2519
|
-
...configOptionKeys.length > 0 ? { configOptionKeys } : {}
|
|
2520
|
-
};
|
|
2722
|
+
return capabilitiesFromRecord(await this.options.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey));
|
|
2521
2723
|
}
|
|
2522
2724
|
async getStatus(input) {
|
|
2523
2725
|
const { handle } = this.resolveManagerHandle(input.handle);
|
|
@@ -2616,6 +2818,6 @@ function createRuntimeStore(options) {
|
|
|
2616
2818
|
return createFileSessionStore(options);
|
|
2617
2819
|
}
|
|
2618
2820
|
//#endregion
|
|
2619
|
-
export { ACPX_BACKEND_ID, AcpRuntimeError, AcpxRuntime, DEFAULT_AGENT_NAME, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE, REQUESTED_MODEL_UNSUPPORTED_REASONS, RequestedModelUnsupportedError, SharedAcpRuntime, createAcpRuntime, createAgentRegistry, createFileSessionStore, createRuntimeStore, createSharedAcpRuntime, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState, isAcpRuntimeError, isRequestedModelUnsupportedError };
|
|
2821
|
+
export { ACPX_BACKEND_ID, AcpRuntimeError, AcpxRuntime, DEFAULT_AGENT_NAME, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE, REQUESTED_MODEL_UNSUPPORTED_REASONS, RequestedModelUnsupportedError, SharedAcpRuntime, createAcpRuntime, createAgentRegistry, createFileSessionStore, createRuntimeStore, createSharedAcpRuntime, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState, inspectAgentModels, isAcpRuntimeError, isRequestedModelUnsupportedError };
|
|
2620
2822
|
|
|
2621
2823
|
//# sourceMappingURL=runtime.js.map
|