acpx 0.13.0 → 0.13.1
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/README.md +64 -382
- package/dist/{cli-D52eGvxU.js → cli-_7Sei6dD.js} +4 -4
- package/dist/{cli-D52eGvxU.js.map → cli-_7Sei6dD.js.map} +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -6
- package/dist/cli.js.map +1 -1
- package/dist/{client-BeKvumq9.d.ts → client-CxNllqui.d.ts} +15 -3
- package/dist/client-CxNllqui.d.ts.map +1 -0
- package/dist/{flags---9OR06b.js → flags-Ds6FzmHG.js} +3 -3
- package/dist/{flags---9OR06b.js.map → flags-Ds6FzmHG.js.map} +1 -1
- package/dist/{flows-CYfjwbn2.js → flows-fzH0GKYF.js} +3 -3
- package/dist/{flows-CYfjwbn2.js.map → flows-fzH0GKYF.js.map} +1 -1
- package/dist/flows.d.ts +2 -2
- package/dist/flows.js +1 -1
- package/dist/{live-checkpoint-CBecfnSH.js → live-checkpoint-BSIrfgVo.js} +345 -186
- package/dist/live-checkpoint-BSIrfgVo.js.map +1 -0
- package/dist/{output-CzN8vLdE.js → output-BZQE0gI1.js} +50 -11
- package/dist/output-BZQE0gI1.js.map +1 -0
- package/dist/runtime.d.ts +104 -17
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +600 -149
- package/dist/runtime.js.map +1 -1
- package/dist/{session-options-CTdZvr68.d.ts → session-options-DwRDODlr.d.ts} +25 -3
- package/dist/session-options-DwRDODlr.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/client-BeKvumq9.d.ts.map +0 -1
- package/dist/live-checkpoint-CBecfnSH.js.map +0 -1
- package/dist/output-CzN8vLdE.js.map +0 -1
- package/dist/session-options-CTdZvr68.d.ts.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE, D as applyLifecycleSnapshotToRecord, E as applyConversation, F as modelStateFromConfigOptions, Ft as normalizeAgentName, Ht as normalizeOutputError, I as normalizeAgentCommandInput, It as resolveAgentArgv, Lt as resolveAgentCommand, M as RequestedModelUnsupportedError, Mt as withTimeout, Nt as DEFAULT_AGENT_NAME, O as reconcileAgentSessionId, Ot as textPrompt, P as isRequestedModelUnsupportedError, Pt as listBuiltInAgents, Rt as resolveCanonicalAgentName, S as advertisedModelState, T as sessionOptionsFromRecord, Ut as extractAcpError, Wt as isAcpResourceNotFoundError, _ as createSessionConversation, a as applyRequestedModelIfAdvertised, b as recordSessionUpdate, c as setCurrentModelId, d as setDesiredModelId, dt as serializeSessionRecordForDisk, et as createAtomicWriteTempPath, f as syncAdvertisedModelState, g as cloneSessionConversation, h as cloneSessionAcpxState, i as connectAndLoadSession, j as REQUESTED_MODEL_UNSUPPORTED_REASONS, k as AcpClient, l as setDesiredConfigOption, m as applyConfigOptionsToState, mt as defaultSessionEventLog, n as runPromptTurn, o as currentModelIdFromSetModelResponse, p as applyConfigOptionsToRecord, r as withConnectedSession, s as clearDesiredConfigOption, t as LiveSessionCheckpoint, tt as assertPersistedKeyPolicy, u as setDesiredModeId, ut as parseSessionRecord, v as recordClientOperation, w as persistSessionOptions, x as trimConversationForRuntime, y as recordPromptSubmission } from "./live-checkpoint-BSIrfgVo.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { isDeepStrictEqual } from "node:util";
|
|
5
6
|
//#region src/runtime/public/errors.ts
|
|
6
7
|
var AcpRuntimeError = class extends Error {
|
|
7
8
|
code;
|
|
@@ -107,7 +108,46 @@ function planStatusText(payload) {
|
|
|
107
108
|
const content = asTrimmedString((Array.isArray(payload.entries) ? payload.entries : []).find((entry) => isRecord(entry))?.content);
|
|
108
109
|
return content ? `plan: ${content}` : null;
|
|
109
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Documented allowlist for text_delta.meta origin fields.
|
|
113
|
+
* Only these keys may be copied from ACP update `_meta`.
|
|
114
|
+
* Unknown keys (including secret-like producer-controlled names) are dropped.
|
|
115
|
+
*/
|
|
116
|
+
const ORIGIN_META_KEYS = [
|
|
117
|
+
"origin",
|
|
118
|
+
"kind",
|
|
119
|
+
"source"
|
|
120
|
+
];
|
|
121
|
+
/**
|
|
122
|
+
* Preserve a fail-closed subset of ACP update origin fields for text_delta
|
|
123
|
+
* consumers. Empty or unusable values are omitted rather than inferred.
|
|
124
|
+
*/
|
|
125
|
+
function extractTextDeltaOrigin(payload) {
|
|
126
|
+
const messageId = asOptionalString(payload.messageId);
|
|
127
|
+
const meta = sanitizeOriginMeta(payload._meta);
|
|
128
|
+
return {
|
|
129
|
+
...messageId ? { messageId } : {},
|
|
130
|
+
...meta ? { meta } : {}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Copy only allowlisted string origin keys from wire `_meta`.
|
|
135
|
+
* Nested objects, arrays, non-strings, and unknown keys are dropped.
|
|
136
|
+
*/
|
|
137
|
+
function sanitizeOriginMeta(value) {
|
|
138
|
+
if (!isRecord(value)) return;
|
|
139
|
+
const out = {};
|
|
140
|
+
for (const key of ORIGIN_META_KEYS) {
|
|
141
|
+
const entry = value[key];
|
|
142
|
+
if (typeof entry !== "string") continue;
|
|
143
|
+
const trimmed = entry.trim();
|
|
144
|
+
if (!trimmed) continue;
|
|
145
|
+
out[key] = trimmed;
|
|
146
|
+
}
|
|
147
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
148
|
+
}
|
|
110
149
|
function resolveTextChunk(params) {
|
|
150
|
+
const origin = extractTextDeltaOrigin(params.payload);
|
|
111
151
|
const contentRaw = params.payload.content;
|
|
112
152
|
if (isRecord(contentRaw)) {
|
|
113
153
|
const contentType = asTrimmedString(contentRaw.type);
|
|
@@ -117,7 +157,8 @@ function resolveTextChunk(params) {
|
|
|
117
157
|
type: "text_delta",
|
|
118
158
|
text,
|
|
119
159
|
stream: params.stream,
|
|
120
|
-
tag: params.tag
|
|
160
|
+
tag: params.tag,
|
|
161
|
+
...origin
|
|
121
162
|
};
|
|
122
163
|
}
|
|
123
164
|
const text = asString(params.payload.text);
|
|
@@ -126,7 +167,8 @@ function resolveTextChunk(params) {
|
|
|
126
167
|
type: "text_delta",
|
|
127
168
|
text,
|
|
128
169
|
stream: params.stream,
|
|
129
|
-
tag: params.tag
|
|
170
|
+
tag: params.tag,
|
|
171
|
+
...origin
|
|
130
172
|
};
|
|
131
173
|
}
|
|
132
174
|
function createTextDeltaEvent(params) {
|
|
@@ -461,9 +503,15 @@ function shouldReuseExistingRecord(record, params) {
|
|
|
461
503
|
if (record.acpx?.reset_on_next_ensure === true) return false;
|
|
462
504
|
if (path.resolve(record.cwd) !== path.resolve(params.cwd)) return false;
|
|
463
505
|
if (record.agentCommand !== params.agentCommand) return false;
|
|
506
|
+
if (!sameArgv(record.agentArgv, params.agentArgv)) return false;
|
|
464
507
|
if (params.resumeSessionId && record.acpSessionId !== params.resumeSessionId) return false;
|
|
465
508
|
return true;
|
|
466
509
|
}
|
|
510
|
+
function sameArgv(left, right) {
|
|
511
|
+
const leftValues = left ?? [];
|
|
512
|
+
const rightValues = right ?? [];
|
|
513
|
+
return leftValues.length === rightValues.length && leftValues.every((value, index) => value === rightValues[index]);
|
|
514
|
+
}
|
|
467
515
|
//#endregion
|
|
468
516
|
//#region src/runtime/engine/manager.ts
|
|
469
517
|
function createDeferred() {
|
|
@@ -478,6 +526,22 @@ function createDeferred() {
|
|
|
478
526
|
reject
|
|
479
527
|
};
|
|
480
528
|
}
|
|
529
|
+
async function settleAttempt(run) {
|
|
530
|
+
try {
|
|
531
|
+
return {
|
|
532
|
+
ok: true,
|
|
533
|
+
value: await run()
|
|
534
|
+
};
|
|
535
|
+
} catch (error) {
|
|
536
|
+
return {
|
|
537
|
+
ok: false,
|
|
538
|
+
error
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function firstFailedAttempt(attempts) {
|
|
543
|
+
return attempts.find((attempt) => !attempt.ok);
|
|
544
|
+
}
|
|
481
545
|
var AsyncEventQueue = class {
|
|
482
546
|
items = [];
|
|
483
547
|
waits = [];
|
|
@@ -738,7 +802,10 @@ var AcpRuntimeManager = class {
|
|
|
738
802
|
options;
|
|
739
803
|
deps;
|
|
740
804
|
activeControllers = /* @__PURE__ */ new Map();
|
|
741
|
-
|
|
805
|
+
retainedSessionOwners = /* @__PURE__ */ new Map();
|
|
806
|
+
pendingOneShotRecordIds = /* @__PURE__ */ new Map();
|
|
807
|
+
ensureSessionLocks = /* @__PURE__ */ new Map();
|
|
808
|
+
runtimeOperationLocks = /* @__PURE__ */ new Map();
|
|
742
809
|
closingActiveRecords = /* @__PURE__ */ new Set();
|
|
743
810
|
constructor(options, deps = {}) {
|
|
744
811
|
this.options = options;
|
|
@@ -747,22 +814,123 @@ var AcpRuntimeManager = class {
|
|
|
747
814
|
createClient(options) {
|
|
748
815
|
return this.deps.clientFactory?.(options) ?? new AcpClient(options);
|
|
749
816
|
}
|
|
750
|
-
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
817
|
+
createSessionOwner(input) {
|
|
818
|
+
const owner = {
|
|
819
|
+
...input,
|
|
820
|
+
bufferSessionUpdates: false,
|
|
821
|
+
pendingSessionUpdates: []
|
|
822
|
+
};
|
|
823
|
+
input.client.setEventHandlers({
|
|
824
|
+
onSessionUpdate: (notification) => this.routeOwnedSessionUpdate(owner, notification),
|
|
825
|
+
onClientOperation: (operation) => this.routeOwnedClientOperation(owner, operation)
|
|
826
|
+
});
|
|
827
|
+
return owner;
|
|
828
|
+
}
|
|
829
|
+
routeOwnedSessionUpdate(owner, notification) {
|
|
830
|
+
const active = owner.activeTurn;
|
|
831
|
+
if (active) {
|
|
832
|
+
const { task, turn } = active;
|
|
833
|
+
if (turn.connectionUpdates) {
|
|
834
|
+
turn.connectionUpdates.push(notification);
|
|
835
|
+
this.emitRuntimeTurnEvent(task, {
|
|
836
|
+
jsonrpc: "2.0",
|
|
837
|
+
method: "session/update",
|
|
838
|
+
params: notification
|
|
839
|
+
});
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
turn.acpxState = recordSessionUpdate(turn.conversation, turn.acpxState, notification);
|
|
843
|
+
trimConversationForRuntime(turn.conversation);
|
|
844
|
+
turn.liveCheckpoint.request();
|
|
845
|
+
this.emitRuntimeTurnEvent(task, {
|
|
846
|
+
jsonrpc: "2.0",
|
|
847
|
+
method: "session/update",
|
|
848
|
+
params: notification
|
|
849
|
+
});
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (owner.bufferSessionUpdates) {
|
|
853
|
+
owner.pendingSessionUpdates.push(notification);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const projection = owner.projection;
|
|
857
|
+
if (!projection) {
|
|
858
|
+
owner.pendingSessionUpdates.push(notification);
|
|
756
859
|
return;
|
|
757
860
|
}
|
|
758
|
-
|
|
759
|
-
|
|
861
|
+
projection.acpxState = recordSessionUpdate(projection.conversation, projection.acpxState, notification);
|
|
862
|
+
trimConversationForRuntime(projection.conversation);
|
|
863
|
+
projection.checkpoint.request();
|
|
864
|
+
}
|
|
865
|
+
routeOwnedClientOperation(owner, operation) {
|
|
866
|
+
const active = owner.activeTurn;
|
|
867
|
+
if (!active) return;
|
|
868
|
+
const { task, turn } = active;
|
|
869
|
+
turn.acpxState = recordClientOperation(turn.conversation, turn.acpxState, operation);
|
|
870
|
+
trimConversationForRuntime(turn.conversation);
|
|
871
|
+
turn.liveCheckpoint.request();
|
|
872
|
+
this.emitRuntimeTurnEvent(task, {
|
|
873
|
+
type: "client_operation",
|
|
874
|
+
...operation
|
|
875
|
+
});
|
|
760
876
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
877
|
+
attachIdleProjection(owner, record, conversation = cloneSessionConversation(record), acpxState = cloneSessionAcpxState(record.acpx)) {
|
|
878
|
+
let projection;
|
|
879
|
+
projection = {
|
|
880
|
+
record,
|
|
881
|
+
conversation,
|
|
882
|
+
acpxState,
|
|
883
|
+
checkpoint: new LiveSessionCheckpoint({ save: async () => {
|
|
884
|
+
record.lastUsedAt = isoNow();
|
|
885
|
+
record.acpx = projection.acpxState;
|
|
886
|
+
applyConversation(record, projection.conversation);
|
|
887
|
+
applyLifecycleSnapshotToRecord(record, owner.client.getAgentLifecycleSnapshot());
|
|
888
|
+
await this.refreshClosedState(record);
|
|
889
|
+
await this.options.sessionStore.save(record);
|
|
890
|
+
} })
|
|
891
|
+
};
|
|
892
|
+
owner.projection = projection;
|
|
893
|
+
owner.activeTurn = void 0;
|
|
894
|
+
this.drainPendingSessionUpdates(owner);
|
|
895
|
+
}
|
|
896
|
+
drainPendingSessionUpdates(owner) {
|
|
897
|
+
for (const notification of owner.pendingSessionUpdates.splice(0)) this.routeOwnedSessionUpdate(owner, notification);
|
|
898
|
+
}
|
|
899
|
+
async flushSessionOwner(owner) {
|
|
900
|
+
await owner.client.waitForSessionUpdatesIdle?.().catch(() => {});
|
|
901
|
+
await owner.projection?.checkpoint.flush();
|
|
902
|
+
}
|
|
903
|
+
removeRetainedSessionOwner(owner) {
|
|
904
|
+
if (owner.recordId && this.retainedSessionOwners.get(owner.recordId) === owner) this.retainedSessionOwners.delete(owner.recordId);
|
|
905
|
+
if (owner.mode === "oneshot" && owner.recordId && this.pendingOneShotRecordIds.get(owner.sessionKey) === owner.recordId) this.pendingOneShotRecordIds.delete(owner.sessionKey);
|
|
906
|
+
}
|
|
907
|
+
async readRetainedSessionOwner(record, options) {
|
|
908
|
+
const owner = this.retainedSessionOwners.get(record.acpxRecordId);
|
|
909
|
+
if (!owner) return;
|
|
910
|
+
await this.flushSessionOwner(owner);
|
|
911
|
+
const projectedRecord = owner.projection?.record;
|
|
912
|
+
if (projectedRecord && projectedRecord !== record) Object.assign(record, structuredClone(projectedRecord));
|
|
913
|
+
if (!owner.client.hasReusableSession(record.acpSessionId)) {
|
|
914
|
+
this.removeRetainedSessionOwner(owner);
|
|
915
|
+
await this.stopSessionOwner(owner);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
if (options.consume) this.removeRetainedSessionOwner(owner);
|
|
919
|
+
return owner;
|
|
920
|
+
}
|
|
921
|
+
async closeRetainedSessionOwner(recordId) {
|
|
922
|
+
const owner = this.retainedSessionOwners.get(recordId);
|
|
923
|
+
if (!owner) return;
|
|
924
|
+
this.removeRetainedSessionOwner(owner);
|
|
925
|
+
await this.stopSessionOwner(owner);
|
|
926
|
+
}
|
|
927
|
+
async stopSessionOwner(owner) {
|
|
928
|
+
await this.flushSessionOwner(owner).catch(() => {});
|
|
929
|
+
await owner.client.close().catch(() => {});
|
|
930
|
+
await owner.projection?.checkpoint.flush().catch(() => {});
|
|
931
|
+
try {
|
|
932
|
+
owner.client.clearEventHandlers();
|
|
933
|
+
} catch {}
|
|
766
934
|
}
|
|
767
935
|
async refreshClosedState(record) {
|
|
768
936
|
if (!this.closingActiveRecords.has(record.acpxRecordId)) return record.closed === true;
|
|
@@ -775,32 +943,43 @@ var AcpRuntimeManager = class {
|
|
|
775
943
|
};
|
|
776
944
|
return true;
|
|
777
945
|
}
|
|
778
|
-
async
|
|
779
|
-
const { record,
|
|
780
|
-
if (
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
946
|
+
async retainPersistentSessionOwnerAfterTurn(input) {
|
|
947
|
+
const { record, owner, conversation, acpxState } = input;
|
|
948
|
+
if (!this.canRetainPersistentSessionOwner(owner, record)) {
|
|
949
|
+
owner.activeTurn = void 0;
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
this.attachIdleProjection(owner, record, conversation, acpxState);
|
|
953
|
+
const previousOwner = this.retainedSessionOwners.get(record.acpxRecordId);
|
|
954
|
+
this.retainedSessionOwners.set(record.acpxRecordId, owner);
|
|
955
|
+
if (previousOwner && previousOwner !== owner) {
|
|
956
|
+
this.removeRetainedSessionOwner(previousOwner);
|
|
957
|
+
await this.stopSessionOwner(previousOwner);
|
|
958
|
+
}
|
|
784
959
|
return true;
|
|
785
960
|
}
|
|
961
|
+
canRetainPersistentSessionOwner(owner, record) {
|
|
962
|
+
return owner.mode === "persistent" && !record.closed && !(owner.client.hasUnresolvedPrompt?.() ?? false) && owner.client.hasReusableSession(record.acpSessionId);
|
|
963
|
+
}
|
|
786
964
|
async withRuntimeControlSession(record, sessionMode, run) {
|
|
787
|
-
const
|
|
788
|
-
if (
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
965
|
+
const owner = await this.readRetainedSessionOwner(record, { consume: false });
|
|
966
|
+
if (owner) {
|
|
967
|
+
const ownedRecord = owner.projection?.record ?? record;
|
|
968
|
+
owner.bufferSessionUpdates = true;
|
|
969
|
+
try {
|
|
970
|
+
const value = await run({
|
|
971
|
+
client: owner.client,
|
|
972
|
+
sessionId: ownedRecord.acpSessionId,
|
|
973
|
+
record: ownedRecord
|
|
974
|
+
});
|
|
975
|
+
this.refreshOwnedRecordLifecycle(owner, ownedRecord);
|
|
976
|
+
return {
|
|
977
|
+
value,
|
|
978
|
+
record: ownedRecord
|
|
979
|
+
};
|
|
980
|
+
} finally {
|
|
981
|
+
await this.finishBufferedOwnerControl(owner, ownedRecord);
|
|
982
|
+
}
|
|
804
983
|
}
|
|
805
984
|
const result = await withConnectedSession({
|
|
806
985
|
sessionRecordId: record.acpxRecordId,
|
|
@@ -810,7 +989,9 @@ var AcpRuntimeManager = class {
|
|
|
810
989
|
mcpServers: [...this.options.mcpServers ?? []],
|
|
811
990
|
permissionMode: this.options.permissionMode,
|
|
812
991
|
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
992
|
+
permissionPolicy: this.options.permissionPolicy,
|
|
813
993
|
onPermissionRequest: this.options.onPermissionRequest,
|
|
994
|
+
elicitationModes: this.options.elicitationModes,
|
|
814
995
|
verbose: this.options.verbose,
|
|
815
996
|
timeoutMs: this.options.timeoutMs,
|
|
816
997
|
resumePolicy: resumePolicyForSessionMode(sessionMode),
|
|
@@ -821,21 +1002,98 @@ var AcpRuntimeManager = class {
|
|
|
821
1002
|
record: result.record
|
|
822
1003
|
};
|
|
823
1004
|
}
|
|
1005
|
+
refreshOwnedRecordLifecycle(owner, record) {
|
|
1006
|
+
record.lastUsedAt = isoNow();
|
|
1007
|
+
record.closed = false;
|
|
1008
|
+
record.closedAt = void 0;
|
|
1009
|
+
record.protocolVersion = owner.client.initializeResult?.protocolVersion;
|
|
1010
|
+
record.agentCapabilities = owner.client.initializeResult?.agentCapabilities;
|
|
1011
|
+
applyLifecycleSnapshotToRecord(record, owner.client.getAgentLifecycleSnapshot());
|
|
1012
|
+
}
|
|
1013
|
+
async finishBufferedOwnerControl(owner, record) {
|
|
1014
|
+
const projection = owner.projection;
|
|
1015
|
+
if (projection) projection.acpxState = cloneSessionAcpxState(record.acpx);
|
|
1016
|
+
owner.bufferSessionUpdates = false;
|
|
1017
|
+
this.drainPendingSessionUpdates(owner);
|
|
1018
|
+
if (projection) {
|
|
1019
|
+
record.acpx = projection.acpxState;
|
|
1020
|
+
applyConversation(record, projection.conversation);
|
|
1021
|
+
await projection.checkpoint.flush();
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
824
1024
|
async ensureSession(input) {
|
|
1025
|
+
return await this.withEnsureSessionLock(input, async () => this.ensureSessionWithOwnership(input));
|
|
1026
|
+
}
|
|
1027
|
+
async withEnsureSessionLock(input, run) {
|
|
1028
|
+
const key = `${input.mode}\0${input.sessionKey}`;
|
|
1029
|
+
return await this.withManagerLock(this.ensureSessionLocks, key, run);
|
|
1030
|
+
}
|
|
1031
|
+
async withManagerLock(locks, key, run) {
|
|
1032
|
+
const previous = locks.get(key) ?? Promise.resolve();
|
|
1033
|
+
let release;
|
|
1034
|
+
const gate = new Promise((resolve) => {
|
|
1035
|
+
release = resolve;
|
|
1036
|
+
});
|
|
1037
|
+
const tail = previous.then(() => gate);
|
|
1038
|
+
locks.set(key, tail);
|
|
1039
|
+
await previous;
|
|
1040
|
+
try {
|
|
1041
|
+
return await run();
|
|
1042
|
+
} finally {
|
|
1043
|
+
release();
|
|
1044
|
+
if (locks.get(key) === tail) locks.delete(key);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
async ensureSessionWithOwnership(input) {
|
|
825
1048
|
const cwd = path.resolve(input.cwd?.trim() || this.options.cwd);
|
|
826
1049
|
const { agentCommand, agentArgv } = normalizeAgentCommandInput(this.options.agentRegistry.resolve(input.agent));
|
|
827
|
-
const
|
|
828
|
-
if (input.mode === "persistent" && existing && shouldReuseExistingRecord(existing, {
|
|
1050
|
+
const agent = {
|
|
829
1051
|
cwd,
|
|
830
1052
|
agentCommand,
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1053
|
+
agentArgv
|
|
1054
|
+
};
|
|
1055
|
+
const existing = await this.loadExistingRuntimeSession(input);
|
|
1056
|
+
if (existing && this.canReuseRuntimeSession(input, agent, existing)) return await this.reuseRuntimeSession(existing.record);
|
|
1057
|
+
await this.closeConflictingPersistentSession(input, existing?.owner);
|
|
1058
|
+
return await this.createOwnedRuntimeSession(input, agent);
|
|
1059
|
+
}
|
|
1060
|
+
async loadExistingRuntimeSession(input) {
|
|
1061
|
+
const existingRecordId = input.mode === "persistent" ? input.sessionKey : this.pendingOneShotRecordIds.get(input.sessionKey);
|
|
1062
|
+
if (!existingRecordId) return;
|
|
1063
|
+
let record = await this.options.sessionStore.load(existingRecordId);
|
|
1064
|
+
if (!record) return;
|
|
1065
|
+
const owner = this.retainedSessionOwners.get(record.acpxRecordId);
|
|
1066
|
+
if (owner) {
|
|
1067
|
+
await this.flushSessionOwner(owner);
|
|
1068
|
+
record = owner.projection?.record ?? record;
|
|
838
1069
|
}
|
|
1070
|
+
return {
|
|
1071
|
+
record,
|
|
1072
|
+
owner
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
canReuseRuntimeSession(input, agent, existing) {
|
|
1076
|
+
if (!shouldReuseExistingRecord(existing.record, {
|
|
1077
|
+
cwd: agent.cwd,
|
|
1078
|
+
agentCommand: agent.agentCommand,
|
|
1079
|
+
agentArgv: agent.agentArgv,
|
|
1080
|
+
resumeSessionId: input.resumeSessionId
|
|
1081
|
+
})) return false;
|
|
1082
|
+
if (input.mode === "persistent") return true;
|
|
1083
|
+
return Boolean(existing.owner && isDeepStrictEqual(sessionOptionsFromRecord(existing.record), input.sessionOptions));
|
|
1084
|
+
}
|
|
1085
|
+
async reuseRuntimeSession(record) {
|
|
1086
|
+
record.closed = false;
|
|
1087
|
+
record.closedAt = void 0;
|
|
1088
|
+
this.closingActiveRecords.delete(record.acpxRecordId);
|
|
1089
|
+
await this.options.sessionStore.save(record);
|
|
1090
|
+
return record;
|
|
1091
|
+
}
|
|
1092
|
+
async closeConflictingPersistentSession(input, owner) {
|
|
1093
|
+
if (input.mode === "persistent" && owner?.recordId) await this.closeRetainedSessionOwner(owner.recordId);
|
|
1094
|
+
}
|
|
1095
|
+
async createOwnedRuntimeSession(input, agent) {
|
|
1096
|
+
const { cwd, agentCommand, agentArgv } = agent;
|
|
839
1097
|
const client = this.createClient({
|
|
840
1098
|
agentCommand,
|
|
841
1099
|
agentArgv,
|
|
@@ -843,11 +1101,18 @@ var AcpRuntimeManager = class {
|
|
|
843
1101
|
mcpServers: [...this.options.mcpServers ?? []],
|
|
844
1102
|
permissionMode: this.options.permissionMode,
|
|
845
1103
|
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
1104
|
+
permissionPolicy: this.options.permissionPolicy,
|
|
846
1105
|
onPermissionRequest: this.options.onPermissionRequest,
|
|
1106
|
+
elicitationModes: this.options.elicitationModes,
|
|
847
1107
|
verbose: this.options.verbose,
|
|
848
1108
|
sessionOptions: input.sessionOptions
|
|
849
1109
|
});
|
|
850
|
-
|
|
1110
|
+
const owner = this.createSessionOwner({
|
|
1111
|
+
client,
|
|
1112
|
+
sessionKey: input.sessionKey,
|
|
1113
|
+
mode: input.mode
|
|
1114
|
+
});
|
|
1115
|
+
let retained = false;
|
|
851
1116
|
try {
|
|
852
1117
|
await client.start();
|
|
853
1118
|
const session = await createOrLoadRuntimeSession(client, input.resumeSessionId, cwd);
|
|
@@ -859,10 +1124,15 @@ var AcpRuntimeManager = class {
|
|
|
859
1124
|
cwd,
|
|
860
1125
|
session
|
|
861
1126
|
});
|
|
862
|
-
|
|
1127
|
+
this.attachIdleProjection(owner, record);
|
|
1128
|
+
await this.retainInitializedSessionOwner(owner, record);
|
|
1129
|
+
retained = true;
|
|
863
1130
|
return record;
|
|
864
1131
|
} finally {
|
|
865
|
-
if (!
|
|
1132
|
+
if (!retained) {
|
|
1133
|
+
client.clearEventHandlers();
|
|
1134
|
+
await client.close();
|
|
1135
|
+
}
|
|
866
1136
|
}
|
|
867
1137
|
}
|
|
868
1138
|
async createAndSaveRuntimeRecord(params) {
|
|
@@ -896,17 +1166,28 @@ var AcpRuntimeManager = class {
|
|
|
896
1166
|
await this.options.sessionStore.save(record);
|
|
897
1167
|
return record;
|
|
898
1168
|
}
|
|
899
|
-
async
|
|
900
|
-
|
|
901
|
-
const
|
|
902
|
-
this.
|
|
903
|
-
|
|
904
|
-
|
|
1169
|
+
async retainInitializedSessionOwner(owner, record) {
|
|
1170
|
+
owner.recordId = record.acpxRecordId;
|
|
1171
|
+
const previousOwner = this.retainedSessionOwners.get(record.acpxRecordId);
|
|
1172
|
+
this.retainedSessionOwners.set(record.acpxRecordId, owner);
|
|
1173
|
+
if (owner.mode === "oneshot") this.pendingOneShotRecordIds.set(owner.sessionKey, record.acpxRecordId);
|
|
1174
|
+
if (previousOwner && previousOwner !== owner) {
|
|
1175
|
+
this.removeRetainedSessionOwner(previousOwner);
|
|
1176
|
+
await this.stopSessionOwner(previousOwner);
|
|
1177
|
+
}
|
|
905
1178
|
}
|
|
906
1179
|
startTurn(input) {
|
|
907
|
-
|
|
1180
|
+
let promptInput;
|
|
1181
|
+
try {
|
|
1182
|
+
promptInput = toPromptInput(input.text, input.attachments);
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
this.closeRetainedOneShotHandle(input.handle).catch(() => {});
|
|
1185
|
+
throw error;
|
|
1186
|
+
}
|
|
908
1187
|
const queue = new AsyncEventQueue();
|
|
909
1188
|
const result = createDeferred();
|
|
1189
|
+
const promptStarted = createDeferred();
|
|
1190
|
+
promptStarted.promise.catch(() => {});
|
|
910
1191
|
const sessionReady = createDeferred();
|
|
911
1192
|
sessionReady.promise.catch(() => {});
|
|
912
1193
|
let resultSettled = false;
|
|
@@ -938,13 +1219,17 @@ var AcpRuntimeManager = class {
|
|
|
938
1219
|
};
|
|
939
1220
|
if (input.signal) {
|
|
940
1221
|
if (input.signal.aborted) {
|
|
1222
|
+
promptStarted.reject(/* @__PURE__ */ new Error("ACP turn cancelled before prompt submission."));
|
|
941
1223
|
closeStream();
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1224
|
+
this.closeRetainedOneShotHandle(input.handle).catch(() => {}).then(() => {
|
|
1225
|
+
settleResult({
|
|
1226
|
+
status: "cancelled",
|
|
1227
|
+
stopReason: "cancelled"
|
|
1228
|
+
});
|
|
945
1229
|
});
|
|
946
1230
|
return {
|
|
947
1231
|
requestId: input.requestId,
|
|
1232
|
+
promptStarted: promptStarted.promise,
|
|
948
1233
|
events: queue.iterate(),
|
|
949
1234
|
result: result.promise,
|
|
950
1235
|
cancel: async () => {},
|
|
@@ -957,6 +1242,7 @@ var AcpRuntimeManager = class {
|
|
|
957
1242
|
input,
|
|
958
1243
|
promptInput,
|
|
959
1244
|
queue,
|
|
1245
|
+
promptStarted,
|
|
960
1246
|
sessionReady,
|
|
961
1247
|
state,
|
|
962
1248
|
settleResult,
|
|
@@ -964,6 +1250,7 @@ var AcpRuntimeManager = class {
|
|
|
964
1250
|
});
|
|
965
1251
|
return {
|
|
966
1252
|
requestId: input.requestId,
|
|
1253
|
+
promptStarted: promptStarted.promise,
|
|
967
1254
|
events: queue.iterate(),
|
|
968
1255
|
result: result.promise,
|
|
969
1256
|
cancel: async () => {
|
|
@@ -974,61 +1261,160 @@ var AcpRuntimeManager = class {
|
|
|
974
1261
|
}
|
|
975
1262
|
};
|
|
976
1263
|
}
|
|
1264
|
+
async closeRetainedOneShotHandle(handle) {
|
|
1265
|
+
const recordId = handle.acpxRecordId ?? handle.sessionKey;
|
|
1266
|
+
if (this.retainedSessionOwners.get(recordId)?.mode === "oneshot") await this.closeRetainedSessionOwner(recordId);
|
|
1267
|
+
}
|
|
977
1268
|
async runRuntimeTurnTask(task) {
|
|
978
1269
|
let turn;
|
|
1270
|
+
let terminalResult;
|
|
979
1271
|
try {
|
|
980
1272
|
turn = await this.prepareRuntimeTurn(task);
|
|
981
1273
|
const { sessionId, resumed, loadError } = await this.connectRuntimeTurn(task, turn);
|
|
982
1274
|
await this.resolveRuntimeTurnReady(task, turn, resumed, loadError);
|
|
983
|
-
if (this.cancelRuntimeTurnBeforePrompt(task))
|
|
984
|
-
|
|
985
|
-
|
|
1275
|
+
if (this.cancelRuntimeTurnBeforePrompt(task)) terminalResult = {
|
|
1276
|
+
status: "cancelled",
|
|
1277
|
+
stopReason: "cancelled"
|
|
1278
|
+
};
|
|
1279
|
+
else {
|
|
1280
|
+
await this.applyPendingRuntimeTurnCancel(task, turn);
|
|
1281
|
+
const response = await this.runRuntimePrompt(task, turn, sessionId);
|
|
1282
|
+
await this.saveCompletedRuntimeTurn(turn, response.stopReason);
|
|
1283
|
+
terminalResult = {
|
|
1284
|
+
status: response.stopReason === "cancelled" ? "cancelled" : "completed",
|
|
1285
|
+
...response.stopReason ? { stopReason: response.stopReason } : {}
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
terminalResult = this.failRuntimeTurn(task, error);
|
|
1290
|
+
}
|
|
1291
|
+
try {
|
|
1292
|
+
await this.finalizeRuntimeTurn(task, turn);
|
|
1293
|
+
} catch (error) {
|
|
1294
|
+
terminalResult = this.failRuntimeTurn(task, error);
|
|
1295
|
+
}
|
|
1296
|
+
task.settleResult(terminalResult);
|
|
1297
|
+
}
|
|
1298
|
+
async runRuntimePrompt(task, turn, sessionId) {
|
|
1299
|
+
try {
|
|
1300
|
+
return await runPromptTurn({
|
|
986
1301
|
client: turn.client,
|
|
987
1302
|
sessionId,
|
|
988
1303
|
prompt: task.promptInput,
|
|
989
1304
|
timeoutMs: task.input.timeoutMs ?? this.options.timeoutMs,
|
|
990
1305
|
conversation: turn.conversation,
|
|
991
|
-
promptMessageId: turn.promptMessageId
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
task.settleResult({
|
|
995
|
-
status: response.stopReason === "cancelled" ? "cancelled" : "completed",
|
|
996
|
-
...response.stopReason ? { stopReason: response.stopReason } : {}
|
|
1306
|
+
promptMessageId: turn.promptMessageId,
|
|
1307
|
+
onPromptRequestStarted: () => task.promptStarted.resolve(),
|
|
1308
|
+
onElicitation: task.input.onElicitation
|
|
997
1309
|
});
|
|
998
|
-
} catch (error) {
|
|
999
|
-
this.failRuntimeTurn(task, error);
|
|
1000
1310
|
} finally {
|
|
1001
|
-
|
|
1311
|
+
turn.client.endPromptElicitation?.(sessionId);
|
|
1002
1312
|
}
|
|
1003
1313
|
}
|
|
1004
1314
|
async prepareRuntimeTurn(task) {
|
|
1005
|
-
const
|
|
1315
|
+
const recordId = task.input.handle.acpxRecordId ?? task.input.handle.sessionKey;
|
|
1316
|
+
return await this.withManagerLock(this.runtimeOperationLocks, recordId, async () => this.prepareRuntimeTurnWithOwnership(task));
|
|
1317
|
+
}
|
|
1318
|
+
async prepareRuntimeTurnWithOwnership(task) {
|
|
1319
|
+
const { record, retainedOwner, conversation, acpxState, promptMessageId } = await this.prepareRuntimeTurnState(task);
|
|
1320
|
+
try {
|
|
1321
|
+
const client = retainedOwner?.client ?? this.createTurnClient(record);
|
|
1322
|
+
const owner = this.resolveRuntimeTurnOwner(task, record, client, retainedOwner);
|
|
1323
|
+
const turn = this.createRunningRuntimeTurn({
|
|
1324
|
+
record,
|
|
1325
|
+
conversation,
|
|
1326
|
+
acpxState,
|
|
1327
|
+
client,
|
|
1328
|
+
owner,
|
|
1329
|
+
pendingClient: retainedOwner?.client,
|
|
1330
|
+
promptMessageId
|
|
1331
|
+
});
|
|
1332
|
+
this.activateRuntimeTurn(task, turn);
|
|
1333
|
+
return turn;
|
|
1334
|
+
} catch (error) {
|
|
1335
|
+
this.restoreBufferedSessionOwner(retainedOwner);
|
|
1336
|
+
throw error;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
async prepareRuntimeTurnState(task) {
|
|
1340
|
+
const { record, retainedOwner } = await this.acquireRuntimeTurnState(task);
|
|
1006
1341
|
const conversation = cloneSessionConversation(record);
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1342
|
+
const acpxState = cloneSessionAcpxState(record.acpx);
|
|
1343
|
+
try {
|
|
1344
|
+
const promptStartedAt = isoNow();
|
|
1345
|
+
const promptMessageId = recordPromptSubmission(conversation, task.promptInput, promptStartedAt);
|
|
1346
|
+
trimConversationForRuntime(conversation);
|
|
1347
|
+
record.lastPromptAt = promptStartedAt;
|
|
1348
|
+
record.lastUsedAt = promptStartedAt;
|
|
1349
|
+
record.acpx = acpxState;
|
|
1350
|
+
applyConversation(record, conversation);
|
|
1351
|
+
await this.options.sessionStore.save(record);
|
|
1352
|
+
return {
|
|
1353
|
+
record,
|
|
1354
|
+
retainedOwner,
|
|
1355
|
+
conversation,
|
|
1356
|
+
acpxState,
|
|
1357
|
+
promptMessageId
|
|
1358
|
+
};
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
this.restoreBufferedSessionOwner(retainedOwner);
|
|
1361
|
+
throw error;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
async acquireRuntimeTurnState(task) {
|
|
1365
|
+
const recordId = task.input.handle.acpxRecordId ?? task.input.handle.sessionKey;
|
|
1366
|
+
let record = await this.requireRecord(recordId);
|
|
1367
|
+
const retainedOwner = await this.readRetainedSessionOwner(record, { consume: false });
|
|
1368
|
+
if (!retainedOwner) return { record };
|
|
1369
|
+
const projection = retainedOwner.projection;
|
|
1370
|
+
if (projection) record = structuredClone(projection.record);
|
|
1371
|
+
retainedOwner.bufferSessionUpdates = true;
|
|
1372
|
+
return {
|
|
1373
|
+
record,
|
|
1374
|
+
retainedOwner
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
restoreBufferedSessionOwner(owner) {
|
|
1378
|
+
if (!owner) return;
|
|
1379
|
+
owner.bufferSessionUpdates = false;
|
|
1380
|
+
this.drainPendingSessionUpdates(owner);
|
|
1381
|
+
}
|
|
1382
|
+
resolveRuntimeTurnOwner(task, record, client, retainedOwner) {
|
|
1383
|
+
return retainedOwner ?? this.createSessionOwner({
|
|
1384
|
+
client,
|
|
1385
|
+
sessionKey: record.name ?? task.input.handle.sessionKey,
|
|
1386
|
+
mode: task.input.sessionMode
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
createRunningRuntimeTurn(input) {
|
|
1390
|
+
const { record, conversation, acpxState, client, owner, pendingClient, promptMessageId } = input;
|
|
1018
1391
|
const turn = {
|
|
1019
1392
|
record,
|
|
1020
1393
|
conversation,
|
|
1021
1394
|
acpxState,
|
|
1022
1395
|
liveCheckpoint: this.createRuntimeTurnCheckpoint(record, conversation, () => turn.acpxState),
|
|
1023
1396
|
client,
|
|
1397
|
+
owner,
|
|
1024
1398
|
pendingClient,
|
|
1399
|
+
connectionUpdates: pendingClient ? void 0 : [],
|
|
1025
1400
|
promptMessageId,
|
|
1026
1401
|
activeSessionId: record.acpSessionId
|
|
1027
1402
|
};
|
|
1403
|
+
return turn;
|
|
1404
|
+
}
|
|
1405
|
+
activateRuntimeTurn(task, turn) {
|
|
1406
|
+
const { owner, record } = turn;
|
|
1407
|
+
this.removeRetainedSessionOwner(owner);
|
|
1408
|
+
owner.recordId = record.acpxRecordId;
|
|
1028
1409
|
task.state.activeController = this.buildRuntimeTurnController(task, turn);
|
|
1029
1410
|
this.activeControllers.set(record.acpxRecordId, task.state.activeController);
|
|
1030
|
-
|
|
1031
|
-
|
|
1411
|
+
owner.projection = void 0;
|
|
1412
|
+
owner.activeTurn = {
|
|
1413
|
+
task,
|
|
1414
|
+
turn
|
|
1415
|
+
};
|
|
1416
|
+
owner.bufferSessionUpdates = false;
|
|
1417
|
+
this.drainPendingSessionUpdates(owner);
|
|
1032
1418
|
}
|
|
1033
1419
|
createTurnClient(record) {
|
|
1034
1420
|
return this.createClient({
|
|
@@ -1038,7 +1424,9 @@ var AcpRuntimeManager = class {
|
|
|
1038
1424
|
mcpServers: [...this.options.mcpServers ?? []],
|
|
1039
1425
|
permissionMode: this.options.permissionMode,
|
|
1040
1426
|
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
1427
|
+
permissionPolicy: this.options.permissionPolicy,
|
|
1041
1428
|
onPermissionRequest: this.options.onPermissionRequest,
|
|
1429
|
+
elicitationModes: this.options.elicitationModes,
|
|
1042
1430
|
verbose: this.options.verbose,
|
|
1043
1431
|
sessionOptions: sessionOptionsFromRecord(record)
|
|
1044
1432
|
});
|
|
@@ -1111,29 +1499,6 @@ var AcpRuntimeManager = class {
|
|
|
1111
1499
|
applyConfigOptionResponseToTurn(turn, response);
|
|
1112
1500
|
applyDesiredConfigOptionToTurn(turn, configId, value);
|
|
1113
1501
|
}
|
|
1114
|
-
installRuntimeTurnEventHandlers(task, turn) {
|
|
1115
|
-
turn.client.setEventHandlers({
|
|
1116
|
-
onSessionUpdate: (notification) => {
|
|
1117
|
-
turn.acpxState = recordSessionUpdate(turn.conversation, turn.acpxState, notification);
|
|
1118
|
-
trimConversationForRuntime(turn.conversation);
|
|
1119
|
-
turn.liveCheckpoint.request();
|
|
1120
|
-
this.emitRuntimeTurnEvent(task, {
|
|
1121
|
-
jsonrpc: "2.0",
|
|
1122
|
-
method: "session/update",
|
|
1123
|
-
params: notification
|
|
1124
|
-
});
|
|
1125
|
-
},
|
|
1126
|
-
onClientOperation: (operation) => {
|
|
1127
|
-
turn.acpxState = recordClientOperation(turn.conversation, turn.acpxState, operation);
|
|
1128
|
-
trimConversationForRuntime(turn.conversation);
|
|
1129
|
-
turn.liveCheckpoint.request();
|
|
1130
|
-
this.emitRuntimeTurnEvent(task, {
|
|
1131
|
-
type: "client_operation",
|
|
1132
|
-
...operation
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
1502
|
emitRuntimeTurnEvent(task, payload) {
|
|
1138
1503
|
const parsed = parsePromptEventLine(JSON.stringify(payload));
|
|
1139
1504
|
if (!parsed) return;
|
|
@@ -1145,9 +1510,17 @@ var AcpRuntimeManager = class {
|
|
|
1145
1510
|
resumed: false,
|
|
1146
1511
|
loadError: void 0
|
|
1147
1512
|
} : await this.connectRuntimeTurnClient(task, turn);
|
|
1148
|
-
|
|
1513
|
+
this.drainRuntimeConnectionUpdates(turn);
|
|
1149
1514
|
return loaded;
|
|
1150
1515
|
}
|
|
1516
|
+
drainRuntimeConnectionUpdates(turn) {
|
|
1517
|
+
if (!turn.connectionUpdates) return;
|
|
1518
|
+
turn.acpxState = cloneSessionAcpxState(turn.record.acpx);
|
|
1519
|
+
for (const notification of turn.connectionUpdates) turn.acpxState = recordSessionUpdate(turn.conversation, turn.acpxState, notification);
|
|
1520
|
+
turn.connectionUpdates = void 0;
|
|
1521
|
+
trimConversationForRuntime(turn.conversation);
|
|
1522
|
+
turn.liveCheckpoint.request();
|
|
1523
|
+
}
|
|
1151
1524
|
async connectRuntimeTurnClient(task, turn) {
|
|
1152
1525
|
return await connectAndLoadSession({
|
|
1153
1526
|
client: turn.client,
|
|
@@ -1188,10 +1561,7 @@ var AcpRuntimeManager = class {
|
|
|
1188
1561
|
cancelRuntimeTurnBeforePrompt(task) {
|
|
1189
1562
|
if (!task.state.pendingCancel && !task.input.signal?.aborted) return false;
|
|
1190
1563
|
task.state.pendingCancel = false;
|
|
1191
|
-
task.
|
|
1192
|
-
status: "cancelled",
|
|
1193
|
-
stopReason: "cancelled"
|
|
1194
|
-
});
|
|
1564
|
+
task.promptStarted.reject(/* @__PURE__ */ new Error("ACP turn cancelled before prompt submission."));
|
|
1195
1565
|
return true;
|
|
1196
1566
|
}
|
|
1197
1567
|
async applyPendingRuntimeTurnCancel(task, turn) {
|
|
@@ -1211,9 +1581,10 @@ var AcpRuntimeManager = class {
|
|
|
1211
1581
|
await this.options.sessionStore.save(turn.record);
|
|
1212
1582
|
}
|
|
1213
1583
|
failRuntimeTurn(task, error) {
|
|
1584
|
+
task.promptStarted.reject(error);
|
|
1214
1585
|
task.sessionReady.reject(error);
|
|
1215
1586
|
const normalized = normalizeOutputError(error, { origin: "runtime" });
|
|
1216
|
-
|
|
1587
|
+
return {
|
|
1217
1588
|
status: "failed",
|
|
1218
1589
|
error: {
|
|
1219
1590
|
message: normalized.message,
|
|
@@ -1221,13 +1592,36 @@ var AcpRuntimeManager = class {
|
|
|
1221
1592
|
...normalized.detailCode ? { detailCode: normalized.detailCode } : {},
|
|
1222
1593
|
...normalized.retryable !== void 0 ? { retryable: normalized.retryable } : {}
|
|
1223
1594
|
}
|
|
1224
|
-
}
|
|
1595
|
+
};
|
|
1225
1596
|
}
|
|
1226
1597
|
async finalizeRuntimeTurn(task, turn) {
|
|
1227
1598
|
task.state.turnActive = false;
|
|
1228
|
-
task.input.signal?.removeEventListener("abort", task.abortHandler);
|
|
1229
|
-
turn
|
|
1230
|
-
|
|
1599
|
+
const abortHandlerAttempt = await settleAttempt(() => task.input.signal?.removeEventListener("abort", task.abortHandler));
|
|
1600
|
+
const recordAttempt = await settleAttempt(async () => turn ? await this.finalizeRuntimeTurnRecord(turn) : false);
|
|
1601
|
+
let failure = firstFailedAttempt([abortHandlerAttempt, recordAttempt]);
|
|
1602
|
+
let pooled = recordAttempt.ok ? recordAttempt.value : false;
|
|
1603
|
+
if (failure) {
|
|
1604
|
+
this.discardRetainedRuntimeTurnOwner(turn);
|
|
1605
|
+
pooled = false;
|
|
1606
|
+
}
|
|
1607
|
+
const closeAttempt = await settleAttempt(async () => this.closeRuntimeTurnClient(turn, pooled));
|
|
1608
|
+
this.cleanupRuntimeTurn(task, turn);
|
|
1609
|
+
failure ??= firstFailedAttempt([closeAttempt]);
|
|
1610
|
+
if (failure) throw failure.error;
|
|
1611
|
+
}
|
|
1612
|
+
discardRetainedRuntimeTurnOwner(turn) {
|
|
1613
|
+
if (turn) {
|
|
1614
|
+
this.removeRetainedSessionOwner(turn.owner);
|
|
1615
|
+
turn.owner.activeTurn = void 0;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
async closeRuntimeTurnClient(turn, pooled) {
|
|
1619
|
+
if (!turn || pooled) return;
|
|
1620
|
+
turn.owner.activeTurn = void 0;
|
|
1621
|
+
const failure = firstFailedAttempt([await settleAttempt(() => turn.client.clearEventHandlers()), await settleAttempt(async () => turn.client.close())]);
|
|
1622
|
+
if (failure) throw failure.error;
|
|
1623
|
+
}
|
|
1624
|
+
cleanupRuntimeTurn(task, turn) {
|
|
1231
1625
|
if (turn) {
|
|
1232
1626
|
this.activeControllers.delete(turn.record.acpxRecordId);
|
|
1233
1627
|
this.closingActiveRecords.delete(turn.record.acpxRecordId);
|
|
@@ -1235,17 +1629,20 @@ var AcpRuntimeManager = class {
|
|
|
1235
1629
|
task.queue.close();
|
|
1236
1630
|
}
|
|
1237
1631
|
async finalizeRuntimeTurnRecord(turn) {
|
|
1632
|
+
this.drainRuntimeConnectionUpdates(turn);
|
|
1238
1633
|
applyLifecycleSnapshotToRecord(turn.record, turn.client.getAgentLifecycleSnapshot());
|
|
1239
1634
|
turn.record.acpx = turn.acpxState;
|
|
1240
1635
|
applyConversation(turn.record, turn.conversation);
|
|
1241
1636
|
turn.record.lastUsedAt = isoNow();
|
|
1242
|
-
await turn.liveCheckpoint.flush()
|
|
1637
|
+
await turn.liveCheckpoint.flush();
|
|
1243
1638
|
const closed = await this.refreshClosedState(turn.record);
|
|
1244
|
-
await this.options.sessionStore.save(turn.record)
|
|
1639
|
+
await this.options.sessionStore.save(turn.record);
|
|
1245
1640
|
if (closed) return false;
|
|
1246
|
-
return await this.
|
|
1641
|
+
return await this.retainPersistentSessionOwnerAfterTurn({
|
|
1247
1642
|
record: turn.record,
|
|
1248
|
-
|
|
1643
|
+
owner: turn.owner,
|
|
1644
|
+
conversation: turn.conversation,
|
|
1645
|
+
acpxState: turn.acpxState
|
|
1249
1646
|
});
|
|
1250
1647
|
}
|
|
1251
1648
|
async *runTurn(input) {
|
|
@@ -1254,7 +1651,10 @@ var AcpRuntimeManager = class {
|
|
|
1254
1651
|
yield legacyTerminalEventFromTurnResult(await turn.result);
|
|
1255
1652
|
}
|
|
1256
1653
|
async getStatus(handle) {
|
|
1257
|
-
const
|
|
1654
|
+
const recordId = handle.acpxRecordId ?? handle.sessionKey;
|
|
1655
|
+
const owner = this.retainedSessionOwners.get(recordId);
|
|
1656
|
+
if (owner) await this.flushSessionOwner(owner);
|
|
1657
|
+
const record = await this.requireRecord(recordId);
|
|
1258
1658
|
return {
|
|
1259
1659
|
summary: statusSummary(record),
|
|
1260
1660
|
acpxRecordId: record.acpxRecordId,
|
|
@@ -1272,6 +1672,10 @@ var AcpRuntimeManager = class {
|
|
|
1272
1672
|
};
|
|
1273
1673
|
}
|
|
1274
1674
|
async setMode(handle, mode, sessionMode = "persistent") {
|
|
1675
|
+
const recordId = handle.acpxRecordId ?? handle.sessionKey;
|
|
1676
|
+
await this.withManagerLock(this.runtimeOperationLocks, recordId, async () => this.setModeWithOwnership(handle, mode, sessionMode));
|
|
1677
|
+
}
|
|
1678
|
+
async setModeWithOwnership(handle, mode, sessionMode) {
|
|
1275
1679
|
const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
|
|
1276
1680
|
const controller = this.activeControllers.get(record.acpxRecordId);
|
|
1277
1681
|
let targetRecord = record;
|
|
@@ -1283,6 +1687,10 @@ var AcpRuntimeManager = class {
|
|
|
1283
1687
|
await this.options.sessionStore.save(targetRecord);
|
|
1284
1688
|
}
|
|
1285
1689
|
async setConfigOption(handle, key, value, sessionMode = "persistent") {
|
|
1690
|
+
const recordId = handle.acpxRecordId ?? handle.sessionKey;
|
|
1691
|
+
await this.withManagerLock(this.runtimeOperationLocks, recordId, async () => this.setConfigOptionWithOwnership(handle, key, value, sessionMode));
|
|
1692
|
+
}
|
|
1693
|
+
async setConfigOptionWithOwnership(handle, key, value, sessionMode) {
|
|
1286
1694
|
const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
|
|
1287
1695
|
const controller = this.activeControllers.get(record.acpxRecordId);
|
|
1288
1696
|
if (controller) {
|
|
@@ -1303,43 +1711,80 @@ var AcpRuntimeManager = class {
|
|
|
1303
1711
|
await this.activeControllers.get(handle.acpxRecordId ?? handle.sessionKey)?.requestCancelActivePrompt();
|
|
1304
1712
|
}
|
|
1305
1713
|
async close(handle, options = {}) {
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1714
|
+
const recordId = handle.acpxRecordId ?? handle.sessionKey;
|
|
1715
|
+
const record = await this.resolveRuntimeRecordForClose(recordId);
|
|
1716
|
+
this.markActiveRuntimeRecordClosing(record);
|
|
1308
1717
|
await this.cancel(handle);
|
|
1309
|
-
|
|
1718
|
+
await this.closeRuntimeRecordOwnership(record, options.discardPersistentState === true);
|
|
1719
|
+
record.closed = true;
|
|
1720
|
+
record.closedAt = isoNow();
|
|
1721
|
+
await this.options.sessionStore.save(record);
|
|
1722
|
+
}
|
|
1723
|
+
async resolveRuntimeRecordForClose(recordId) {
|
|
1724
|
+
const retainedOwner = this.retainedSessionOwners.get(recordId);
|
|
1725
|
+
if (retainedOwner) await this.flushSessionOwner(retainedOwner);
|
|
1726
|
+
return retainedOwner?.projection?.record ?? await this.requireRecord(recordId);
|
|
1727
|
+
}
|
|
1728
|
+
markActiveRuntimeRecordClosing(record) {
|
|
1729
|
+
if (this.activeControllers.has(record.acpxRecordId)) this.closingActiveRecords.add(record.acpxRecordId);
|
|
1730
|
+
}
|
|
1731
|
+
async closeRuntimeRecordOwnership(record, discardPersistentState) {
|
|
1732
|
+
if (discardPersistentState) {
|
|
1310
1733
|
await this.closeBackendSession(record);
|
|
1311
1734
|
record.acpx = {
|
|
1312
1735
|
...record.acpx,
|
|
1313
1736
|
reset_on_next_ensure: true
|
|
1314
1737
|
};
|
|
1315
|
-
} else await this.
|
|
1316
|
-
record.closed = true;
|
|
1317
|
-
record.closedAt = isoNow();
|
|
1318
|
-
await this.options.sessionStore.save(record);
|
|
1738
|
+
} else await this.closeRetainedSessionOwner(record.acpxRecordId);
|
|
1319
1739
|
}
|
|
1320
1740
|
async closeBackendSession(record) {
|
|
1321
|
-
const
|
|
1322
|
-
|
|
1741
|
+
const connection = await this.acquireBackendCloseConnection(record);
|
|
1742
|
+
try {
|
|
1743
|
+
await this.requestBackendSessionClose(record, connection);
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
this.handleBackendSessionCloseError(record, error);
|
|
1746
|
+
} finally {
|
|
1747
|
+
await this.finalizeBackendCloseConnection(connection);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
async finalizeBackendCloseConnection(connection) {
|
|
1751
|
+
const failure = firstFailedAttempt([
|
|
1752
|
+
await settleAttempt(async () => {
|
|
1753
|
+
if (connection.owner) await this.flushSessionOwner(connection.owner);
|
|
1754
|
+
}),
|
|
1755
|
+
await settleAttempt(() => connection.owner?.client.clearEventHandlers()),
|
|
1756
|
+
await settleAttempt(async () => connection.client.close())
|
|
1757
|
+
]);
|
|
1758
|
+
if (failure) throw failure.error;
|
|
1759
|
+
}
|
|
1760
|
+
async acquireBackendCloseConnection(record) {
|
|
1761
|
+
const owner = await this.readRetainedSessionOwner(record, { consume: true });
|
|
1762
|
+
if (owner) return {
|
|
1763
|
+
client: owner.client,
|
|
1764
|
+
owner
|
|
1765
|
+
};
|
|
1766
|
+
return { client: this.createClient({
|
|
1323
1767
|
agentCommand: record.agentCommand,
|
|
1324
1768
|
agentArgv: record.agentArgv,
|
|
1325
1769
|
cwd: record.cwd,
|
|
1326
1770
|
mcpServers: [...this.options.mcpServers ?? []],
|
|
1327
1771
|
permissionMode: this.options.permissionMode,
|
|
1328
1772
|
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
1773
|
+
permissionPolicy: this.options.permissionPolicy,
|
|
1329
1774
|
onPermissionRequest: this.options.onPermissionRequest,
|
|
1775
|
+
elicitationModes: this.options.elicitationModes,
|
|
1330
1776
|
verbose: this.options.verbose
|
|
1331
|
-
});
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
}
|
|
1777
|
+
}) };
|
|
1778
|
+
}
|
|
1779
|
+
async requestBackendSessionClose(record, connection) {
|
|
1780
|
+
if (!connection.owner) await withTimeout(connection.client.start(), this.options.timeoutMs);
|
|
1781
|
+
if (!connection.client.supportsCloseSession()) throw new AcpRuntimeError("ACP_BACKEND_UNSUPPORTED_CONTROL", `Agent does not support session/close for ${record.acpxRecordId}.`);
|
|
1782
|
+
await withTimeout(connection.client.closeSession(record.acpSessionId), this.options.timeoutMs);
|
|
1783
|
+
}
|
|
1784
|
+
handleBackendSessionCloseError(record, error) {
|
|
1785
|
+
if (isAcpResourceNotFoundError(error)) return;
|
|
1786
|
+
if (isUnsupportedSessionCloseError(error)) throw new AcpRuntimeError("ACP_BACKEND_UNSUPPORTED_CONTROL", `Agent does not support session/close for ${record.acpxRecordId}.`, { cause: error });
|
|
1787
|
+
throw error;
|
|
1343
1788
|
}
|
|
1344
1789
|
async requireRecord(sessionId) {
|
|
1345
1790
|
const record = await this.options.sessionStore.load(sessionId);
|
|
@@ -1506,6 +1951,7 @@ function createProbeClient(options, agentCommand, deps) {
|
|
|
1506
1951
|
mcpServers: [...options.mcpServers ?? []],
|
|
1507
1952
|
permissionMode: options.permissionMode,
|
|
1508
1953
|
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
1954
|
+
permissionPolicy: options.permissionPolicy,
|
|
1509
1955
|
verbose: options.verbose
|
|
1510
1956
|
};
|
|
1511
1957
|
return deps.clientFactory?.(clientOptions) ?? new AcpClient(clientOptions);
|
|
@@ -1614,10 +2060,14 @@ var AcpxRuntime = class {
|
|
|
1614
2060
|
sessionMode: state.mode,
|
|
1615
2061
|
requestId: input.requestId,
|
|
1616
2062
|
timeoutMs: input.timeoutMs,
|
|
1617
|
-
signal: input.signal
|
|
2063
|
+
signal: input.signal,
|
|
2064
|
+
onElicitation: input.onElicitation
|
|
1618
2065
|
}));
|
|
1619
2066
|
return {
|
|
1620
2067
|
requestId: input.requestId,
|
|
2068
|
+
get promptStarted() {
|
|
2069
|
+
return turnPromise.then((turn) => turn.promptStarted);
|
|
2070
|
+
},
|
|
1621
2071
|
events: { async *[Symbol.asyncIterator]() {
|
|
1622
2072
|
yield* (await turnPromise).events;
|
|
1623
2073
|
} },
|
|
@@ -1642,7 +2092,8 @@ var AcpxRuntime = class {
|
|
|
1642
2092
|
sessionMode: state.mode,
|
|
1643
2093
|
requestId: input.requestId,
|
|
1644
2094
|
timeoutMs: input.timeoutMs,
|
|
1645
|
-
signal: input.signal
|
|
2095
|
+
signal: input.signal,
|
|
2096
|
+
onElicitation: input.onElicitation
|
|
1646
2097
|
});
|
|
1647
2098
|
}
|
|
1648
2099
|
async getCapabilities(input) {
|