@axhub/genie 0.2.11 → 0.2.12
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/api-docs.html +2 -2
- package/dist/assets/App-Clb2COtW.js +274 -0
- package/dist/assets/ImagePlaygroundPage-DqhMSbM8.js +106 -0
- package/dist/assets/ImagePlaygroundPage-MEn3NN80.css +1 -0
- package/dist/assets/ReviewApp-CDcLYe-u.js +1 -0
- package/dist/assets/{_basePickBy-BDnj7-0Z.js → _basePickBy-jUZsM51q.js} +1 -1
- package/dist/assets/{_baseUniq-Bl0JKOyl.js → _baseUniq-BXglE6_v.js} +1 -1
- package/dist/assets/{arc-DY-4Kev3.js → arc-D-oFCFBv.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-qw7crNVd.js → architectureDiagram-2XIMDMQ5-DC8bAnQt.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-B9xg7ep3.js → blockDiagram-WCTKOSBZ-C4semIRc.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-H9xp3ytb.js → c4Diagram-IC4MRINW-FHj1QO3y.js} +1 -1
- package/dist/assets/channel-BF4woPXX.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-B3EVDUxI.js → chunk-4BX2VUAB-D-LjsQ_s.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CGv945ef.js → chunk-55IACEB6-DI3j_d7A.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-uAT4CKWM.js → chunk-FMBD7UC4-BEVnaLFN.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-Cbvlpkf7.js → chunk-JSJVCQXG-CSxpcErk.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-CcqIuGat.js → chunk-KX2RTZJC-BbuhDN4h.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-CgrcsRuX.js → chunk-NQ4KR5QH-C3x61XQa.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Cx0APOoV.js → chunk-QZHKN3VN-DxWOFtPh.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-BbZirvBk.js → chunk-WL4C6EOR-Bt2OauD2.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-D2kHlnQ7.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-D2kHlnQ7.js +1 -0
- package/dist/assets/clone-CqBvwCJW.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-CrvmGFLD.js → cose-bilkent-S5V4N54A-Dexadrue.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-C-W6VPjS.js → dagre-KLK3FWXG-F9U4X2xC.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-IP2q3bL0.js → diagram-E7M64L7V-B3V17aH3.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-CQaL-XyV.js → diagram-IFDJBPK2-CdHAmLL1.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-BxBLThfv.js → diagram-P4PSJMXO-CrTNfk8K.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-Dyl7bJTt.js → erDiagram-INFDFZHY-vDh9SWK9.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-B7NFMgFK.js → flowDiagram-PKNHOUZH-DpltMg7L.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-hReWSDu2.js → ganttDiagram-A5KZAMGK-COTk2xur.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-gVgcr0ST.js → gitGraphDiagram-K3NZZRJ6-BNV7bvvj.js} +1 -1
- package/dist/assets/{graph-DNDiJhTn.js → graph-Dkeg9oys.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-DclLmTou.js → highlighted-body-TPN3WLV5-DaiQEBwR.js} +1 -1
- package/dist/assets/index-DgGmiqsP.css +1 -0
- package/dist/assets/index-DvA901Vs.js +2 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-CqQOOzDA.js → infoDiagram-LFFYTUFH-CZioW3Gt.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-CZ0iLiHg.js → ishikawaDiagram-PHBUUO56-BbqR3i1B.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-DdfYKfNh.js → journeyDiagram-4ABVD52K-wfb-WHzl.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-C5Vf32u6.js → kanban-definition-K7BYSVSG-B3c4y3VN.js} +1 -1
- package/dist/assets/{layout-rvTEu2KS.js → layout-Xr9Z2VGF.js} +1 -1
- package/dist/assets/{linear-CD9SiYze.js → linear-JBmzAJtl.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-OZ8qWWwa.js → mermaid-O7DHMXV3-fDuyNLKe.js} +230 -222
- package/dist/assets/{mindmap-definition-YRQLILUH-CQxrLNVc.js → mindmap-definition-YRQLILUH-B5NTN_jD.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-XgAUByWg.js → pieDiagram-SKSYHLDU-CuO98GVu.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-CH16ls7G.js → quadrantDiagram-337W2JSQ-LL3f4vLf.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-B_kQO06L.js → requirementDiagram-Z7DCOOCP-Di-2O6LH.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-ofe78CyS.js → sankeyDiagram-WA2Y5GQK-9lHqrXqR.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-Ckbxwny6.js → sequenceDiagram-2WXFIKYE-BQu-SoGr.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-DNtzCk14.js → stateDiagram-RAJIS63D-BUxvd2BC.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-CDVexTiR.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-zT6CklKt.js → timeline-definition-YZTLITO2-oP47UEU6.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-y0U2c3xG.js → treemap-KZPCXAKY-BRjDo2aE.js} +1 -1
- package/dist/assets/{vendor-codemirror-CMHSJ_9p.js → vendor-codemirror-BiCeS-y4.js} +1 -1
- package/dist/assets/{vendor-react-xmA_f8ig.js → vendor-react-DVlYPmi3.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-xKj3SjYG.js → vennDiagram-LZ73GAT5-DrRqcDqo.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-Da_qyEoX.js → xychartDiagram-JWTSCODW-DUXrymAi.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +25 -6
- package/scripts/refresh-acp-default-capabilities.mjs +160 -0
- package/server/acp-runtime/client.js +1137 -181
- package/server/acp-runtime/command-overrides.js +48 -0
- package/server/acp-runtime/index.js +576 -16
- package/server/acp-runtime/registry.js +6 -4
- package/server/acp-runtime/session-store.js +235 -92
- package/server/database/db.js +12 -3
- package/server/external-agent/ws.js +212 -11
- package/server/index.js +145 -52
- package/server/projects-watcher-config.js +4 -0
- package/server/projects.js +466 -125
- package/server/routes/cc-connect.js +5 -4
- package/server/routes/codex.js +24 -0
- package/server/routes/commands.js +144 -1
- package/server/routes/runs.js +641 -0
- package/server/routes/session-core.js +357 -109
- package/server/session-core/eventStore.js +0 -121
- package/server/session-core/providerAdapters.js +644 -163
- package/server/session-core/providerDiscovery.js +66 -38
- package/server/session-core/runRegistry.js +244 -0
- package/server/session-core/runtimeState.js +75 -3
- package/server/session-core/runtimeWriter.js +132 -10
- package/server/utils/codexImagePlayground.js +479 -0
- package/server/utils/localTerminal.js +56 -0
- package/server/utils/shellCommand.js +70 -0
- package/shared/acpCapabilities.js +393 -0
- package/shared/acpDefaultCapabilities.generated.json +141 -0
- package/shared/conversationEvents.js +425 -121
- package/dist/assets/App-VH1wNUHs.js +0 -259
- package/dist/assets/ReviewApp-D_9EN4TM.js +0 -1
- package/dist/assets/channel-CyNUnRfc.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-DxBtyz2A.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-DxBtyz2A.js +0 -1
- package/dist/assets/clone-C341l3d0.js +0 -1
- package/dist/assets/index-DBkz_W_P.css +0 -1
- package/dist/assets/index-DdRyoXKh.js +0 -2
- package/dist/assets/stateDiagram-v2-FVOUBMTO-B3VPhiE1.js +0 -1
|
@@ -13,8 +13,16 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
CONVERSATION_EVENT_KINDS,
|
|
15
15
|
createConversationEvent,
|
|
16
|
+
createUserMessageConversationEvent,
|
|
16
17
|
stripAssistantProtocolTags
|
|
17
18
|
} from '../../shared/conversationEvents.js';
|
|
19
|
+
import {
|
|
20
|
+
buildAcpCapabilities,
|
|
21
|
+
normalizeAcpAvailableCommands,
|
|
22
|
+
normalizeAcpConfigOptions,
|
|
23
|
+
normalizeAcpModeState,
|
|
24
|
+
normalizeAcpTokenUsage
|
|
25
|
+
} from '../../shared/acpCapabilities.js';
|
|
18
26
|
import { parseDataUrl } from '../utils/agentImages.js';
|
|
19
27
|
import { spawnCommand } from '../utils/spawnCommand.js';
|
|
20
28
|
import { resolveCommandPath } from '../utils/resolveCommandPath.js';
|
|
@@ -22,10 +30,30 @@ import { resolveAgentCommand } from './registry.js';
|
|
|
22
30
|
|
|
23
31
|
const DEFAULT_TERMINAL_OUTPUT_LIMIT = 256 * 1024;
|
|
24
32
|
const DEFAULT_CLOSE_GRACE_MS = 1000;
|
|
33
|
+
const DEFAULT_CANCEL_GRACE_MS = normalizePromptTimeoutMs(process.env.ACP_CANCEL_GRACE_MS, 3000);
|
|
25
34
|
const DEFAULT_TERMINAL_MAX_LIFETIME_MS = parseInt(process.env.ACP_TERMINAL_MAX_LIFETIME_MS, 10) || (5 * 60 * 1000);
|
|
35
|
+
const DEFAULT_START_TIMEOUT_MS = normalizePromptTimeoutMs(
|
|
36
|
+
process.env.ACP_START_TIMEOUT_MS ?? process.env.AXHUB_GENIE_ACP_START_TIMEOUT_MS,
|
|
37
|
+
45 * 1000
|
|
38
|
+
);
|
|
39
|
+
const DEFAULT_SESSION_TIMEOUT_MS = normalizePromptTimeoutMs(
|
|
40
|
+
process.env.ACP_SESSION_TIMEOUT_MS ?? process.env.AXHUB_GENIE_ACP_SESSION_TIMEOUT_MS,
|
|
41
|
+
45 * 1000
|
|
42
|
+
);
|
|
43
|
+
const DEFAULT_PROMPT_TIMEOUT_MS = normalizePromptTimeoutMs(
|
|
44
|
+
process.env.ACP_PROMPT_INACTIVITY_TIMEOUT_MS
|
|
45
|
+
?? process.env.AXHUB_GENIE_ACP_PROMPT_INACTIVITY_TIMEOUT_MS
|
|
46
|
+
?? process.env.ACP_PROMPT_TIMEOUT_MS
|
|
47
|
+
?? process.env.AXHUB_GENIE_ACP_PROMPT_TIMEOUT_MS,
|
|
48
|
+
600 * 1000
|
|
49
|
+
);
|
|
50
|
+
const START_TIMEOUT_ERROR_CODE = 'ACP_START_TIMEOUT';
|
|
51
|
+
const SESSION_TIMEOUT_ERROR_CODE = 'ACP_SESSION_TIMEOUT';
|
|
52
|
+
const PROMPT_TIMEOUT_ERROR_CODE = 'ACP_PROMPT_INACTIVITY_TIMEOUT';
|
|
26
53
|
const FILTERED_CHILD_PROCESS_ENV_KEYS = new Set(['NODE_OPTIONS']);
|
|
27
54
|
|
|
28
55
|
const pendingPermissionRequests = new Map();
|
|
56
|
+
const pendingElicitationRequests = new Map();
|
|
29
57
|
const loggedMalformedUsageUpdates = new Set();
|
|
30
58
|
|
|
31
59
|
function getDisabledClaudeAcpMcpServers() {
|
|
@@ -42,9 +70,60 @@ function getDisabledClaudeAcpMcpServers() {
|
|
|
42
70
|
}
|
|
43
71
|
|
|
44
72
|
function cloneJsonValue(value) {
|
|
73
|
+
if (value === undefined) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
45
77
|
return JSON.parse(JSON.stringify(value));
|
|
46
78
|
}
|
|
47
79
|
|
|
80
|
+
function normalizePromptTimeoutMs(value, fallback) {
|
|
81
|
+
const parsed = Number(value);
|
|
82
|
+
return Number.isFinite(parsed) && parsed >= 0
|
|
83
|
+
? Math.floor(parsed)
|
|
84
|
+
: fallback;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createPromptTimeoutError(agentKey, timeoutMs, details = {}) {
|
|
88
|
+
const error = new Error(`${agentKey || 'ACP'} ACP prompt inactivity timed out after ${timeoutMs}ms`);
|
|
89
|
+
error.code = PROMPT_TIMEOUT_ERROR_CODE;
|
|
90
|
+
error.details = {
|
|
91
|
+
timeoutMs,
|
|
92
|
+
...details
|
|
93
|
+
};
|
|
94
|
+
return error;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function createLifecycleTimeoutError(agentKey, operation, timeoutMs, code) {
|
|
98
|
+
const error = new Error(`${agentKey || 'ACP'} ACP ${operation} timed out after ${timeoutMs}ms`);
|
|
99
|
+
error.code = code;
|
|
100
|
+
return error;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function withAcpOperationTimeout(promise, {
|
|
104
|
+
agentKey = 'ACP',
|
|
105
|
+
operation = 'operation',
|
|
106
|
+
timeoutMs = 0,
|
|
107
|
+
code = START_TIMEOUT_ERROR_CODE
|
|
108
|
+
} = {}) {
|
|
109
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
110
|
+
return promise;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let timeoutId = null;
|
|
114
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
115
|
+
timeoutId = setTimeout(() => {
|
|
116
|
+
reject(createLifecycleTimeoutError(agentKey, operation, timeoutMs, code));
|
|
117
|
+
}, timeoutMs);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
121
|
+
if (timeoutId) {
|
|
122
|
+
clearTimeout(timeoutId);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
48
127
|
function logMalformedUsageUpdate(agentKey, sessionId, used, size) {
|
|
49
128
|
const cacheKey = `${agentKey || 'unknown'}:${sessionId || 'unknown'}:${String(used)}:${String(size)}`;
|
|
50
129
|
if (loggedMalformedUsageUpdates.has(cacheKey)) {
|
|
@@ -205,6 +284,10 @@ function normalizeText(value) {
|
|
|
205
284
|
return typeof value === 'string' ? value : String(value ?? '');
|
|
206
285
|
}
|
|
207
286
|
|
|
287
|
+
function normalizePromptEchoText(value) {
|
|
288
|
+
return String(value || '').replace(/\r\n/g, '\n').trim();
|
|
289
|
+
}
|
|
290
|
+
|
|
208
291
|
function normalizeComparableValue(value) {
|
|
209
292
|
try {
|
|
210
293
|
return JSON.stringify(value ?? null);
|
|
@@ -389,6 +472,26 @@ function choosePermissionResponse(request, decision = {}) {
|
|
|
389
472
|
return rejectMatch ? createSelectedPermissionResponse(rejectMatch.optionId) : createCancelledPermissionResponse();
|
|
390
473
|
}
|
|
391
474
|
|
|
475
|
+
function normalizeElicitationResponse(decision = {}) {
|
|
476
|
+
const action = String(decision?.action || '').trim().toLowerCase();
|
|
477
|
+
|
|
478
|
+
if (action === 'accept') {
|
|
479
|
+
const content = decision?.content && typeof decision.content === 'object' && !Array.isArray(decision.content)
|
|
480
|
+
? cloneJsonValue(decision.content)
|
|
481
|
+
: {};
|
|
482
|
+
return {
|
|
483
|
+
action: 'accept',
|
|
484
|
+
content
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (action === 'decline') {
|
|
489
|
+
return { action: 'decline' };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return { action: 'cancel' };
|
|
493
|
+
}
|
|
494
|
+
|
|
392
495
|
function readLines(content, line = null, limit = null) {
|
|
393
496
|
if (line == null && limit == null) {
|
|
394
497
|
return content;
|
|
@@ -512,68 +615,9 @@ function buildToolCallConversationPayload(update = {}, toolName = 'Tool') {
|
|
|
512
615
|
return payload;
|
|
513
616
|
}
|
|
514
617
|
|
|
515
|
-
function normalizeSessionModeState(modes) {
|
|
516
|
-
if (!modes || typeof modes !== 'object') {
|
|
517
|
-
return null;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const availableModes = Array.isArray(modes.availableModes)
|
|
521
|
-
? modes.availableModes
|
|
522
|
-
.map((mode) => {
|
|
523
|
-
if (!mode || typeof mode !== 'object') {
|
|
524
|
-
return null;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
const id = String(mode.id || '').trim();
|
|
528
|
-
if (!id) {
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
return {
|
|
533
|
-
id,
|
|
534
|
-
name: String(mode.name || id),
|
|
535
|
-
description: mode.description == null ? null : String(mode.description)
|
|
536
|
-
};
|
|
537
|
-
})
|
|
538
|
-
.filter(Boolean)
|
|
539
|
-
: [];
|
|
540
|
-
|
|
541
|
-
const currentModeId = String(modes.currentModeId || '').trim();
|
|
542
|
-
|
|
543
|
-
if (!currentModeId && availableModes.length === 0) {
|
|
544
|
-
return null;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return {
|
|
548
|
-
availableModes,
|
|
549
|
-
currentModeId: currentModeId || null
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
|
|
553
618
|
function normalizeAvailableCommandsUpdate(update = {}) {
|
|
554
|
-
const availableCommands = Array.isArray(update.availableCommands)
|
|
555
|
-
? update.availableCommands
|
|
556
|
-
.map((command) => {
|
|
557
|
-
if (!command || typeof command !== 'object') {
|
|
558
|
-
return null;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const name = String(command.name || '').trim();
|
|
562
|
-
if (!name) {
|
|
563
|
-
return null;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
return {
|
|
567
|
-
name,
|
|
568
|
-
description: command.description == null ? '' : String(command.description),
|
|
569
|
-
input: command.input ? cloneJsonValue(command.input) : null
|
|
570
|
-
};
|
|
571
|
-
})
|
|
572
|
-
.filter(Boolean)
|
|
573
|
-
: [];
|
|
574
|
-
|
|
575
619
|
return {
|
|
576
|
-
availableCommands
|
|
620
|
+
availableCommands: normalizeAcpAvailableCommands(update)
|
|
577
621
|
};
|
|
578
622
|
}
|
|
579
623
|
|
|
@@ -775,7 +819,7 @@ async function resolveNpxFallbackCommand(commandLine, agentKey) {
|
|
|
775
819
|
|
|
776
820
|
const npmCheck = await resolveCommandPath('npm');
|
|
777
821
|
if (npmCheck.found) {
|
|
778
|
-
return
|
|
822
|
+
return convertNpxCommandToNpmExec(commandLine);
|
|
779
823
|
}
|
|
780
824
|
|
|
781
825
|
throw new Error(
|
|
@@ -783,6 +827,142 @@ async function resolveNpxFallbackCommand(commandLine, agentKey) {
|
|
|
783
827
|
);
|
|
784
828
|
}
|
|
785
829
|
|
|
830
|
+
async function resolveOpencodeCommand(commandLine) {
|
|
831
|
+
const parsed = splitCommandLine(commandLine);
|
|
832
|
+
if (parsed.command !== 'opencode') {
|
|
833
|
+
return commandLine;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const opencodeCheck = await resolveCommandPath('opencode');
|
|
837
|
+
if (opencodeCheck.found) {
|
|
838
|
+
return commandLine;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
return resolveNpxFallbackCommand('npx -y opencode-ai acp', 'opencode');
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function buildCommandLineFromAdapterArgs(command, args = []) {
|
|
845
|
+
const parts = [command, ...args].map((part) => String(part || '').trim()).filter(Boolean);
|
|
846
|
+
return parts.join(' ');
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function convertNpxCommandToNpmExec(commandLine) {
|
|
850
|
+
const parsed = splitCommandLine(commandLine);
|
|
851
|
+
if (parsed.command !== 'npx') {
|
|
852
|
+
return commandLine;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const normalizedArgs = [...parsed.args];
|
|
856
|
+
while (normalizedArgs[0] === '-y' || normalizedArgs[0] === '--yes') {
|
|
857
|
+
normalizedArgs.shift();
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return buildCommandLineFromAdapterArgs('npm exec --yes --', normalizedArgs);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function isSpawnEnoentError(error) {
|
|
864
|
+
return Boolean(error && typeof error === 'object' && error.code === 'ENOENT');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function waitForChildProcessSpawn(childProcess) {
|
|
868
|
+
return new Promise((resolve, reject) => {
|
|
869
|
+
const cleanup = () => {
|
|
870
|
+
childProcess.off?.('spawn', onSpawn);
|
|
871
|
+
childProcess.off?.('error', onError);
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const onSpawn = () => {
|
|
875
|
+
cleanup();
|
|
876
|
+
resolve(childProcess);
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
const onError = (error) => {
|
|
880
|
+
cleanup();
|
|
881
|
+
reject(error);
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
childProcess.once('spawn', onSpawn);
|
|
885
|
+
childProcess.once('error', onError);
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function waitForChildProcessClose(childProcess, graceMs = DEFAULT_CLOSE_GRACE_MS) {
|
|
890
|
+
return new Promise((resolve) => {
|
|
891
|
+
if (!childProcess || childProcess.exitCode != null || childProcess.signalCode != null) {
|
|
892
|
+
resolve(true);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
let settled = false;
|
|
897
|
+
const finalize = (value) => {
|
|
898
|
+
if (settled) {
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
settled = true;
|
|
902
|
+
resolve(value);
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
const timer = setTimeout(() => finalize(false), graceMs);
|
|
906
|
+
childProcess.once?.('close', () => {
|
|
907
|
+
clearTimeout(timer);
|
|
908
|
+
finalize(true);
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function terminateChildProcessTree(childProcess, signal = 'SIGTERM') {
|
|
914
|
+
if (!childProcess || childProcess.killed) {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (process.platform !== 'win32' && Number.isInteger(childProcess.pid)) {
|
|
919
|
+
try {
|
|
920
|
+
process.kill(-childProcess.pid, signal);
|
|
921
|
+
return true;
|
|
922
|
+
} catch (error) {
|
|
923
|
+
if (error?.code !== 'ESRCH' && error?.code !== 'EINVAL') {
|
|
924
|
+
console.warn(`[ACP] Failed to terminate child process group ${childProcess.pid}: ${error.message}`);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
try {
|
|
930
|
+
childProcess.kill(signal);
|
|
931
|
+
return true;
|
|
932
|
+
} catch {
|
|
933
|
+
return false;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
export async function startAcpChildProcess({ commandLine, projectPath, env, spawnImpl = spawnCommand }) {
|
|
938
|
+
const spawnAttempt = (attemptCommandLine) => {
|
|
939
|
+
const { command, args } = splitCommandLine(attemptCommandLine);
|
|
940
|
+
const childProcess = spawnImpl(command, args, {
|
|
941
|
+
cwd: projectPath,
|
|
942
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
943
|
+
windowsHide: true,
|
|
944
|
+
detached: process.platform !== 'win32',
|
|
945
|
+
env
|
|
946
|
+
});
|
|
947
|
+
return waitForChildProcessSpawn(childProcess);
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
try {
|
|
951
|
+
return await spawnAttempt(commandLine);
|
|
952
|
+
} catch (error) {
|
|
953
|
+
if (!isSpawnEnoentError(error)) {
|
|
954
|
+
throw error;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const fallbackCommandLine = convertNpxCommandToNpmExec(commandLine);
|
|
958
|
+
if (fallbackCommandLine === commandLine) {
|
|
959
|
+
throw error;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
return spawnAttempt(fallbackCommandLine);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
786
966
|
export async function resolveLaunchCommand(agentKey, overrides = {}) {
|
|
787
967
|
const commandLine = resolveAgentCommand(agentKey, overrides);
|
|
788
968
|
if (!commandLine) {
|
|
@@ -796,6 +976,10 @@ export async function resolveLaunchCommand(agentKey, overrides = {}) {
|
|
|
796
976
|
return resolveGeminiCommand(resolvedCommandLine);
|
|
797
977
|
}
|
|
798
978
|
|
|
979
|
+
if (normalizedAgentKey === 'opencode') {
|
|
980
|
+
return resolveOpencodeCommand(resolvedCommandLine);
|
|
981
|
+
}
|
|
982
|
+
|
|
799
983
|
return resolvedCommandLine;
|
|
800
984
|
}
|
|
801
985
|
|
|
@@ -943,7 +1127,14 @@ export class AcpClient {
|
|
|
943
1127
|
projectPath,
|
|
944
1128
|
agentCommandOverrides = {},
|
|
945
1129
|
model = null,
|
|
946
|
-
permissionMode = 'default'
|
|
1130
|
+
permissionMode = 'default',
|
|
1131
|
+
modeId = null,
|
|
1132
|
+
thoughtLevel = null,
|
|
1133
|
+
promptTimeoutMs = DEFAULT_PROMPT_TIMEOUT_MS,
|
|
1134
|
+
startTimeoutMs = DEFAULT_START_TIMEOUT_MS,
|
|
1135
|
+
sessionTimeoutMs = DEFAULT_SESSION_TIMEOUT_MS,
|
|
1136
|
+
startChildProcess = startAcpChildProcess,
|
|
1137
|
+
createConnection = (handlers, stream) => new ClientSideConnection(handlers, stream)
|
|
947
1138
|
}) {
|
|
948
1139
|
this.agentKey = String(agentKey || '').trim().toLowerCase();
|
|
949
1140
|
this.writer = writer;
|
|
@@ -951,26 +1142,54 @@ export class AcpClient {
|
|
|
951
1142
|
this.agentCommandOverrides = agentCommandOverrides || {};
|
|
952
1143
|
this.model = model || null;
|
|
953
1144
|
this.permissionMode = permissionMode || 'default';
|
|
1145
|
+
this.modeId = typeof modeId === 'string' && modeId.trim() ? modeId.trim() : null;
|
|
1146
|
+
this.thoughtLevel = typeof thoughtLevel === 'string' && thoughtLevel.trim() ? thoughtLevel.trim() : null;
|
|
1147
|
+
this.promptTimeoutMs = normalizePromptTimeoutMs(promptTimeoutMs, DEFAULT_PROMPT_TIMEOUT_MS);
|
|
1148
|
+
this.startTimeoutMs = normalizePromptTimeoutMs(startTimeoutMs, DEFAULT_START_TIMEOUT_MS);
|
|
1149
|
+
this.sessionTimeoutMs = normalizePromptTimeoutMs(sessionTimeoutMs, DEFAULT_SESSION_TIMEOUT_MS);
|
|
1150
|
+
this.startChildProcess = typeof startChildProcess === 'function' ? startChildProcess : startAcpChildProcess;
|
|
1151
|
+
this.createConnection = typeof createConnection === 'function'
|
|
1152
|
+
? createConnection
|
|
1153
|
+
: ((handlers, stream) => new ClientSideConnection(handlers, stream));
|
|
1154
|
+
this.configOptionIdsByCategory = new Map();
|
|
1155
|
+
this.configOptionIdsByKey = new Map();
|
|
954
1156
|
|
|
955
1157
|
this.childProcess = null;
|
|
956
1158
|
this.connection = null;
|
|
957
1159
|
this.sessionId = null;
|
|
958
1160
|
this.initializeResult = null;
|
|
959
1161
|
this.suppressSessionUpdates = false;
|
|
1162
|
+
this.isLoadingSessionReplay = false;
|
|
960
1163
|
this.pendingPermissionIds = new Set();
|
|
1164
|
+
this.pendingElicitationIds = new Set();
|
|
961
1165
|
this.terminalProcesses = new Map();
|
|
962
1166
|
this.spawnCleanup = null;
|
|
963
1167
|
this.turnQueue = Promise.resolve();
|
|
964
1168
|
this.isPromptInFlight = false;
|
|
965
1169
|
this.pendingPromptDiagnostic = null;
|
|
1170
|
+
this.pendingPromptTurnCompletion = null;
|
|
1171
|
+
this.activePromptClientRequestId = null;
|
|
1172
|
+
this.activePromptText = null;
|
|
1173
|
+
this.isReplayingPromptHistory = false;
|
|
1174
|
+
this.hasObservedActivePromptEcho = false;
|
|
966
1175
|
this.hasReportedPromptFatalError = false;
|
|
967
1176
|
this.agentDiagnosticBuffer = '';
|
|
1177
|
+
this.promptActivity = {
|
|
1178
|
+
timer: null,
|
|
1179
|
+
reject: null,
|
|
1180
|
+
timeoutMs: this.promptTimeoutMs,
|
|
1181
|
+
startedAt: null,
|
|
1182
|
+
lastActivityAt: null,
|
|
1183
|
+
lastActivityKind: null,
|
|
1184
|
+
pauseCount: 0
|
|
1185
|
+
};
|
|
968
1186
|
this.streamState = {
|
|
969
1187
|
turnId: null,
|
|
970
1188
|
assistantSegmentIndex: 0,
|
|
971
1189
|
assistantMessageId: null,
|
|
972
1190
|
assistantTextStarted: false,
|
|
973
1191
|
reasoningMessageId: null,
|
|
1192
|
+
textStreamsClosedForTurn: false,
|
|
974
1193
|
toolCalls: new Map()
|
|
975
1194
|
};
|
|
976
1195
|
}
|
|
@@ -997,7 +1216,38 @@ export class AcpClient {
|
|
|
997
1216
|
}
|
|
998
1217
|
}
|
|
999
1218
|
|
|
1000
|
-
|
|
1219
|
+
noteConfigOptions(configOptions = []) {
|
|
1220
|
+
const normalizedConfigOptions = normalizeAcpConfigOptions(configOptions);
|
|
1221
|
+
for (const option of normalizedConfigOptions) {
|
|
1222
|
+
const configId = String(option.id || option.key || option.category || '').trim();
|
|
1223
|
+
if (!configId) {
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (option.key) {
|
|
1228
|
+
this.configOptionIdsByKey.set(option.key, configId);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (option.category) {
|
|
1232
|
+
this.configOptionIdsByCategory.set(option.category, configId);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
return normalizedConfigOptions;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
resolveConfigOptionId(canonicalKey) {
|
|
1240
|
+
const normalizedKey = String(canonicalKey || '').trim();
|
|
1241
|
+
if (!normalizedKey) {
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
return this.configOptionIdsByCategory.get(normalizedKey) ||
|
|
1246
|
+
this.configOptionIdsByKey.get(normalizedKey) ||
|
|
1247
|
+
normalizedKey;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
async configureForTurn({ writer, permissionMode = null, model = null, modeId = null, thoughtLevel = null } = {}) {
|
|
1001
1251
|
if (writer !== undefined) {
|
|
1002
1252
|
this.attachWriter(writer);
|
|
1003
1253
|
}
|
|
@@ -1013,9 +1263,35 @@ export class AcpClient {
|
|
|
1013
1263
|
await this.setModel(model);
|
|
1014
1264
|
}
|
|
1015
1265
|
}
|
|
1266
|
+
|
|
1267
|
+
const normalizedModeId = typeof modeId === 'string' && modeId.trim() ? modeId.trim() : null;
|
|
1268
|
+
if (normalizedModeId) {
|
|
1269
|
+
const shouldUpdateMode = normalizedModeId !== this.modeId;
|
|
1270
|
+
this.modeId = normalizedModeId;
|
|
1271
|
+
if (shouldUpdateMode && this.connection && this.sessionId) {
|
|
1272
|
+
await this.setMode(normalizedModeId);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
const normalizedThoughtLevel = typeof thoughtLevel === 'string' && thoughtLevel.trim()
|
|
1277
|
+
? thoughtLevel.trim()
|
|
1278
|
+
: null;
|
|
1279
|
+
if (normalizedThoughtLevel) {
|
|
1280
|
+
const shouldUpdateThoughtLevel = normalizedThoughtLevel !== this.thoughtLevel;
|
|
1281
|
+
this.thoughtLevel = normalizedThoughtLevel;
|
|
1282
|
+
if (shouldUpdateThoughtLevel && this.connection && this.sessionId) {
|
|
1283
|
+
await this.setConfigOption(this.resolveConfigOptionId('thought_level'), normalizedThoughtLevel);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1016
1286
|
}
|
|
1017
1287
|
|
|
1018
1288
|
emitConversationEvent(kind, payload = {}, options = {}) {
|
|
1289
|
+
const replayExtensions = this.isLoadingSessionReplay
|
|
1290
|
+
? { runtimeReplay: true }
|
|
1291
|
+
: {};
|
|
1292
|
+
const replaySourceType = this.isLoadingSessionReplay
|
|
1293
|
+
? 'acp-session-loaded-replay'
|
|
1294
|
+
: null;
|
|
1019
1295
|
const event = createConversationEvent({
|
|
1020
1296
|
kind,
|
|
1021
1297
|
provider: this.agentKey,
|
|
@@ -1024,11 +1300,12 @@ export class AcpClient {
|
|
|
1024
1300
|
payload,
|
|
1025
1301
|
extensions: {
|
|
1026
1302
|
runtimeSource: 'acp',
|
|
1303
|
+
...replayExtensions,
|
|
1027
1304
|
...(options.extensions || {})
|
|
1028
1305
|
},
|
|
1029
1306
|
rawRef: {
|
|
1030
1307
|
runtime: 'acp',
|
|
1031
|
-
sourceType: options.sourceType || 'acp-session-update'
|
|
1308
|
+
sourceType: replaySourceType || options.sourceType || 'acp-session-update'
|
|
1032
1309
|
}
|
|
1033
1310
|
});
|
|
1034
1311
|
|
|
@@ -1060,13 +1337,19 @@ export class AcpClient {
|
|
|
1060
1337
|
|
|
1061
1338
|
this.hasReportedPromptFatalError = true;
|
|
1062
1339
|
const errorMessage = error?.message || 'Agent prompt failed.';
|
|
1340
|
+
const errorCode = error?.code || null;
|
|
1341
|
+
const errorDetails = error?.details && typeof error.details === 'object'
|
|
1342
|
+
? cloneJsonValue(error.details)
|
|
1343
|
+
: null;
|
|
1063
1344
|
|
|
1064
1345
|
const failedAt = nowIso();
|
|
1065
1346
|
this.flushOpenTextStreams(failedAt);
|
|
1066
1347
|
this.emitConversationEvent(
|
|
1067
1348
|
CONVERSATION_EVENT_KINDS.ERROR,
|
|
1068
1349
|
{
|
|
1069
|
-
message: errorMessage
|
|
1350
|
+
message: errorMessage,
|
|
1351
|
+
...(errorCode ? { code: errorCode } : {}),
|
|
1352
|
+
...(errorDetails ? { details: errorDetails } : {})
|
|
1070
1353
|
},
|
|
1071
1354
|
{
|
|
1072
1355
|
timestamp: failedAt,
|
|
@@ -1077,7 +1360,9 @@ export class AcpClient {
|
|
|
1077
1360
|
CONVERSATION_EVENT_KINDS.SESSION_STATE_CHANGED,
|
|
1078
1361
|
{
|
|
1079
1362
|
state: 'errored',
|
|
1080
|
-
message: errorMessage
|
|
1363
|
+
message: errorMessage,
|
|
1364
|
+
...(errorCode ? { code: errorCode } : {}),
|
|
1365
|
+
...(errorDetails ? { details: errorDetails } : {})
|
|
1081
1366
|
},
|
|
1082
1367
|
{
|
|
1083
1368
|
timestamp: failedAt,
|
|
@@ -1096,6 +1381,8 @@ export class AcpClient {
|
|
|
1096
1381
|
return;
|
|
1097
1382
|
}
|
|
1098
1383
|
|
|
1384
|
+
this.notePromptActivity('stderr_diagnostic');
|
|
1385
|
+
|
|
1099
1386
|
const nextBuffer = this.agentDiagnosticBuffer
|
|
1100
1387
|
? `${this.agentDiagnosticBuffer}\n${diagnosticText}`
|
|
1101
1388
|
: diagnosticText;
|
|
@@ -1126,6 +1413,114 @@ export class AcpClient {
|
|
|
1126
1413
|
}
|
|
1127
1414
|
}
|
|
1128
1415
|
|
|
1416
|
+
createPromptTimeoutDetails() {
|
|
1417
|
+
const now = Date.now();
|
|
1418
|
+
const startedAt = this.promptActivity.startedAt;
|
|
1419
|
+
const lastActivityAt = this.promptActivity.lastActivityAt;
|
|
1420
|
+
return {
|
|
1421
|
+
timeoutMs: this.promptActivity.timeoutMs,
|
|
1422
|
+
lastActivityAt: lastActivityAt ? new Date(lastActivityAt).toISOString() : null,
|
|
1423
|
+
lastActivityKind: this.promptActivity.lastActivityKind || null,
|
|
1424
|
+
promptAgeMs: startedAt ? Math.max(0, now - startedAt) : null,
|
|
1425
|
+
pendingPermissionCount: this.pendingPermissionIds.size,
|
|
1426
|
+
pendingElicitationCount: this.pendingElicitationIds.size,
|
|
1427
|
+
stderrTail: this.agentDiagnosticBuffer || ''
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
clearPromptActivityTimer() {
|
|
1432
|
+
if (this.promptActivity.timer) {
|
|
1433
|
+
clearTimeout(this.promptActivity.timer);
|
|
1434
|
+
this.promptActivity.timer = null;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
schedulePromptActivityTimer() {
|
|
1439
|
+
this.clearPromptActivityTimer();
|
|
1440
|
+
const timeoutMs = this.promptActivity.timeoutMs;
|
|
1441
|
+
if (
|
|
1442
|
+
!this.isPromptInFlight ||
|
|
1443
|
+
!this.promptActivity.reject ||
|
|
1444
|
+
!Number.isFinite(timeoutMs) ||
|
|
1445
|
+
timeoutMs <= 0 ||
|
|
1446
|
+
this.promptActivity.pauseCount > 0
|
|
1447
|
+
) {
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
this.promptActivity.timer = setTimeout(() => {
|
|
1452
|
+
this.promptActivity.timer = null;
|
|
1453
|
+
const reject = this.promptActivity.reject;
|
|
1454
|
+
if (reject) {
|
|
1455
|
+
reject(createPromptTimeoutError(this.agentKey, timeoutMs, this.createPromptTimeoutDetails()));
|
|
1456
|
+
}
|
|
1457
|
+
}, timeoutMs);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
startPromptActivityWatchdog(reject, timeoutMs) {
|
|
1461
|
+
const now = Date.now();
|
|
1462
|
+
this.promptActivity = {
|
|
1463
|
+
timer: null,
|
|
1464
|
+
reject,
|
|
1465
|
+
timeoutMs,
|
|
1466
|
+
startedAt: now,
|
|
1467
|
+
lastActivityAt: now,
|
|
1468
|
+
lastActivityKind: 'prompt_sent',
|
|
1469
|
+
pauseCount: 0
|
|
1470
|
+
};
|
|
1471
|
+
this.schedulePromptActivityTimer();
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
stopPromptActivityWatchdog() {
|
|
1475
|
+
this.clearPromptActivityTimer();
|
|
1476
|
+
this.promptActivity.reject = null;
|
|
1477
|
+
this.promptActivity.pauseCount = 0;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
resolvePromptTurnCompletion(stopReason = 'end_turn') {
|
|
1481
|
+
if (!this.isPromptInFlight || !this.pendingPromptTurnCompletion) {
|
|
1482
|
+
return false;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
if (this.isLoadingSessionReplay || this.isReplayingPromptHistory) {
|
|
1486
|
+
return false;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
const resolve = this.pendingPromptTurnCompletion;
|
|
1490
|
+
this.pendingPromptTurnCompletion = null;
|
|
1491
|
+
resolve({ stopReason });
|
|
1492
|
+
return true;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
notePromptActivity(kind) {
|
|
1496
|
+
if (!this.isPromptInFlight) {
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
this.promptActivity.lastActivityAt = Date.now();
|
|
1500
|
+
this.promptActivity.lastActivityKind = kind || 'activity';
|
|
1501
|
+
this.schedulePromptActivityTimer();
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
pausePromptActivityWatchdog(kind = 'paused') {
|
|
1505
|
+
if (!this.isPromptInFlight) {
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
this.promptActivity.pauseCount += 1;
|
|
1509
|
+
this.promptActivity.lastActivityAt = Date.now();
|
|
1510
|
+
this.promptActivity.lastActivityKind = kind;
|
|
1511
|
+
this.clearPromptActivityTimer();
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
resumePromptActivityWatchdog(kind = 'resumed') {
|
|
1515
|
+
if (!this.isPromptInFlight) {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
this.promptActivity.pauseCount = Math.max(0, this.promptActivity.pauseCount - 1);
|
|
1519
|
+
this.promptActivity.lastActivityAt = Date.now();
|
|
1520
|
+
this.promptActivity.lastActivityKind = kind;
|
|
1521
|
+
this.schedulePromptActivityTimer();
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1129
1524
|
async runExclusive(operation) {
|
|
1130
1525
|
const previousTurn = this.turnQueue.catch(() => {});
|
|
1131
1526
|
let releaseTurn;
|
|
@@ -1160,6 +1555,39 @@ export class AcpClient {
|
|
|
1160
1555
|
);
|
|
1161
1556
|
}
|
|
1162
1557
|
|
|
1558
|
+
normalizeAcpMessageId(messageId, prefix = 'assistant_text') {
|
|
1559
|
+
const normalizedMessageId = typeof messageId === 'string' && messageId.trim()
|
|
1560
|
+
? messageId.trim()
|
|
1561
|
+
: null;
|
|
1562
|
+
if (!normalizedMessageId) {
|
|
1563
|
+
return this.createAssistantMessageId(prefix);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
const turnId = this.ensureTurnId();
|
|
1567
|
+
return createEventMessageId(
|
|
1568
|
+
prefix,
|
|
1569
|
+
this.agentKey,
|
|
1570
|
+
this.sessionId,
|
|
1571
|
+
`${turnId}:segment-${this.streamState.assistantSegmentIndex}:${normalizedMessageId}`
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
prepareAssistantMessageBoundary(messageId, timestamp, sourceType) {
|
|
1576
|
+
const nextMessageId = this.normalizeAcpMessageId(messageId);
|
|
1577
|
+
if (
|
|
1578
|
+
this.streamState.assistantMessageId &&
|
|
1579
|
+
this.streamState.assistantMessageId !== nextMessageId
|
|
1580
|
+
) {
|
|
1581
|
+
this.closeAssistantMessageBoundary(timestamp, sourceType);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
if (!this.streamState.assistantMessageId) {
|
|
1585
|
+
this.streamState.assistantMessageId = nextMessageId;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
return this.streamState.assistantMessageId;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1163
1591
|
closeAssistantMessageBoundary(timestamp = nowIso(), sourceType = 'acp-message-boundary') {
|
|
1164
1592
|
const hadAssistantBoundary = Boolean(
|
|
1165
1593
|
this.streamState.assistantMessageId || this.streamState.assistantTextStarted
|
|
@@ -1197,6 +1625,8 @@ export class AcpClient {
|
|
|
1197
1625
|
);
|
|
1198
1626
|
this.streamState.reasoningMessageId = null;
|
|
1199
1627
|
}
|
|
1628
|
+
|
|
1629
|
+
this.streamState.textStreamsClosedForTurn = true;
|
|
1200
1630
|
}
|
|
1201
1631
|
|
|
1202
1632
|
resetTurnState({ nextTurnId = randomUUID() } = {}) {
|
|
@@ -1204,6 +1634,72 @@ export class AcpClient {
|
|
|
1204
1634
|
this.streamState.toolCalls.clear();
|
|
1205
1635
|
this.streamState.turnId = nextTurnId;
|
|
1206
1636
|
this.streamState.assistantSegmentIndex = 0;
|
|
1637
|
+
this.streamState.textStreamsClosedForTurn = false;
|
|
1638
|
+
this.isReplayingPromptHistory = false;
|
|
1639
|
+
this.hasObservedActivePromptEcho = false;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
classifyPromptUserChunk(text) {
|
|
1643
|
+
if (!this.isPromptInFlight) {
|
|
1644
|
+
return {
|
|
1645
|
+
isActivePromptEcho: false,
|
|
1646
|
+
isPromptHistoryReplay: false
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const activePromptText = normalizePromptEchoText(this.activePromptText);
|
|
1651
|
+
const chunkText = normalizePromptEchoText(text);
|
|
1652
|
+
const isActivePromptEcho = Boolean(activePromptText && chunkText && activePromptText === chunkText);
|
|
1653
|
+
|
|
1654
|
+
if (isActivePromptEcho) {
|
|
1655
|
+
this.hasObservedActivePromptEcho = true;
|
|
1656
|
+
this.isReplayingPromptHistory = false;
|
|
1657
|
+
return {
|
|
1658
|
+
isActivePromptEcho: true,
|
|
1659
|
+
isPromptHistoryReplay: false
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
this.isReplayingPromptHistory = true;
|
|
1664
|
+
return {
|
|
1665
|
+
isActivePromptEcho: false,
|
|
1666
|
+
isPromptHistoryReplay: true
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
buildSessionUpdateEventOptions(sourceType, timestamp) {
|
|
1671
|
+
const replayingPromptHistory = this.isReplayingPromptHistory;
|
|
1672
|
+
const isTextStreamSource = sourceType === 'acp-agent-message' ||
|
|
1673
|
+
sourceType === 'acp-agent-message-boundary' ||
|
|
1674
|
+
sourceType === 'acp-agent-thought';
|
|
1675
|
+
if (this.isLoadingSessionReplay) {
|
|
1676
|
+
return {
|
|
1677
|
+
timestamp,
|
|
1678
|
+
sourceType: 'acp-session-loaded-replay',
|
|
1679
|
+
extensions: { runtimeReplay: true }
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
if (replayingPromptHistory) {
|
|
1684
|
+
return {
|
|
1685
|
+
timestamp,
|
|
1686
|
+
sourceType: 'acp-prompt-history-replay',
|
|
1687
|
+
extensions: { runtimeReplay: true }
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
if (isTextStreamSource && this.streamState.textStreamsClosedForTurn) {
|
|
1692
|
+
return {
|
|
1693
|
+
timestamp,
|
|
1694
|
+
sourceType: 'acp-post-turn-replay',
|
|
1695
|
+
extensions: { runtimeReplay: true }
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
return {
|
|
1700
|
+
timestamp,
|
|
1701
|
+
sourceType
|
|
1702
|
+
};
|
|
1207
1703
|
}
|
|
1208
1704
|
|
|
1209
1705
|
async handleRequestPermission(request) {
|
|
@@ -1235,6 +1731,7 @@ export class AcpClient {
|
|
|
1235
1731
|
const timestamp = nowIso();
|
|
1236
1732
|
|
|
1237
1733
|
this.pendingPermissionIds.add(requestId);
|
|
1734
|
+
this.pausePromptActivityWatchdog('approval_request');
|
|
1238
1735
|
|
|
1239
1736
|
this.emitConversationEvent(
|
|
1240
1737
|
CONVERSATION_EVENT_KINDS.APPROVAL_REQUEST,
|
|
@@ -1294,39 +1791,154 @@ export class AcpClient {
|
|
|
1294
1791
|
}
|
|
1295
1792
|
);
|
|
1296
1793
|
|
|
1794
|
+
this.resumePromptActivityWatchdog('approval_resolved');
|
|
1795
|
+
|
|
1297
1796
|
return response;
|
|
1298
1797
|
}
|
|
1299
1798
|
|
|
1300
|
-
async
|
|
1301
|
-
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
const update = notification?.update;
|
|
1799
|
+
async handleCreateElicitation(request) {
|
|
1800
|
+
const requestId = `acp-elicitation:${randomUUID()}`;
|
|
1306
1801
|
const timestamp = nowIso();
|
|
1307
|
-
|
|
1308
|
-
return;
|
|
1309
|
-
}
|
|
1802
|
+
const mode = String(request?.mode || '').trim().toLowerCase() || 'form';
|
|
1310
1803
|
|
|
1311
|
-
|
|
1804
|
+
this.pendingElicitationIds.add(requestId);
|
|
1805
|
+
this.pausePromptActivityWatchdog('elicitation_request');
|
|
1806
|
+
|
|
1807
|
+
this.emitConversationEvent(
|
|
1808
|
+
CONVERSATION_EVENT_KINDS.ELICITATION_REQUEST,
|
|
1809
|
+
{
|
|
1810
|
+
requestId,
|
|
1811
|
+
mode,
|
|
1812
|
+
message: String(request?.message || ''),
|
|
1813
|
+
requestedSchema: request?.requestedSchema && typeof request.requestedSchema === 'object'
|
|
1814
|
+
? cloneJsonValue(request.requestedSchema)
|
|
1815
|
+
: null,
|
|
1816
|
+
url: typeof request?.url === 'string' ? request.url : null,
|
|
1817
|
+
elicitationId: typeof request?.elicitationId === 'string' ? request.elicitationId : null,
|
|
1818
|
+
scope: {
|
|
1819
|
+
sessionId: request?.sessionId || this.sessionId || null,
|
|
1820
|
+
toolCallId: request?.toolCallId || null,
|
|
1821
|
+
requestId: request?.requestId || null
|
|
1822
|
+
},
|
|
1823
|
+
sessionId: request?.sessionId || this.sessionId || null
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
timestamp,
|
|
1827
|
+
extensions: {
|
|
1828
|
+
requestId
|
|
1829
|
+
},
|
|
1830
|
+
sourceType: 'acp-elicitation-request'
|
|
1831
|
+
}
|
|
1832
|
+
);
|
|
1833
|
+
|
|
1834
|
+
const response = await new Promise((resolve) => {
|
|
1835
|
+
pendingElicitationRequests.set(requestId, {
|
|
1836
|
+
request,
|
|
1837
|
+
sessionId: this.sessionId,
|
|
1838
|
+
provider: this.agentKey,
|
|
1839
|
+
resolve
|
|
1840
|
+
});
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
this.pendingElicitationIds.delete(requestId);
|
|
1844
|
+
pendingElicitationRequests.delete(requestId);
|
|
1845
|
+
|
|
1846
|
+
this.emitConversationEvent(
|
|
1847
|
+
CONVERSATION_EVENT_KINDS.ELICITATION_RESOLVED,
|
|
1848
|
+
{
|
|
1849
|
+
requestId,
|
|
1850
|
+
action: response.action,
|
|
1851
|
+
content: response.action === 'accept' ? cloneJsonValue(response.content || {}) : null,
|
|
1852
|
+
message: response.action === 'accept'
|
|
1853
|
+
? 'User submitted input'
|
|
1854
|
+
: response.action === 'decline'
|
|
1855
|
+
? 'User declined input'
|
|
1856
|
+
: 'User cancelled input'
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
timestamp: nowIso(),
|
|
1860
|
+
extensions: {
|
|
1861
|
+
requestId
|
|
1862
|
+
},
|
|
1863
|
+
sourceType: 'acp-elicitation-resolution'
|
|
1864
|
+
}
|
|
1865
|
+
);
|
|
1866
|
+
|
|
1867
|
+
this.resumePromptActivityWatchdog('elicitation_resolved');
|
|
1868
|
+
|
|
1869
|
+
return response;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
async handleSessionUpdate(notification) {
|
|
1873
|
+
if (this.suppressSessionUpdates) {
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
const update = notification?.update;
|
|
1878
|
+
const timestamp = nowIso();
|
|
1879
|
+
if (!update || typeof update !== 'object') {
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
const updateKind = String(update.sessionUpdate || '').trim() || 'unknown';
|
|
1883
|
+
this.notePromptActivity(`session_update:${updateKind}`);
|
|
1884
|
+
|
|
1885
|
+
if (update.sessionUpdate === 'user_message_chunk') {
|
|
1312
1886
|
if (update.content?.type === 'text' && typeof update.content.text === 'string') {
|
|
1313
|
-
const
|
|
1314
|
-
if (!
|
|
1887
|
+
const text = update.content.text;
|
|
1888
|
+
if (!text) {
|
|
1315
1889
|
return;
|
|
1316
1890
|
}
|
|
1891
|
+
const promptChunk = this.classifyPromptUserChunk(text);
|
|
1892
|
+
const replayExtensions = (
|
|
1893
|
+
this.isLoadingSessionReplay ||
|
|
1894
|
+
promptChunk.isPromptHistoryReplay
|
|
1895
|
+
)
|
|
1896
|
+
? { runtimeReplay: true }
|
|
1897
|
+
: {};
|
|
1898
|
+
const sourceType = this.isLoadingSessionReplay
|
|
1899
|
+
? 'acp-session-loaded-replay'
|
|
1900
|
+
: promptChunk.isPromptHistoryReplay
|
|
1901
|
+
? 'acp-prompt-history-replay'
|
|
1902
|
+
: 'acp-user-message-chunk';
|
|
1903
|
+
|
|
1904
|
+
this.emitPayload({
|
|
1905
|
+
type: 'conversation-event',
|
|
1906
|
+
event: createUserMessageConversationEvent({
|
|
1907
|
+
provider: this.agentKey,
|
|
1908
|
+
sessionId: notification?.sessionId || this.sessionId,
|
|
1909
|
+
timestamp,
|
|
1910
|
+
text,
|
|
1911
|
+
contentBlocks: [],
|
|
1912
|
+
rawRef: {
|
|
1913
|
+
runtime: 'acp',
|
|
1914
|
+
sourceType
|
|
1915
|
+
},
|
|
1916
|
+
clientRequestId: null,
|
|
1917
|
+
extensions: replayExtensions
|
|
1918
|
+
})
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1317
1923
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
if (!
|
|
1322
|
-
|
|
1924
|
+
if (update.sessionUpdate === 'agent_message_chunk') {
|
|
1925
|
+
if (update.content?.type === 'text' && typeof update.content.text === 'string') {
|
|
1926
|
+
const cleanText = stripAssistantProtocolTags(update.content.text, { trim: false });
|
|
1927
|
+
if (!cleanText) {
|
|
1928
|
+
return;
|
|
1323
1929
|
}
|
|
1324
1930
|
|
|
1931
|
+
const messageId = this.prepareAssistantMessageBoundary(
|
|
1932
|
+
update.messageId,
|
|
1933
|
+
timestamp,
|
|
1934
|
+
'acp-agent-message-boundary'
|
|
1935
|
+
);
|
|
1936
|
+
|
|
1325
1937
|
if (!this.streamState.assistantTextStarted) {
|
|
1326
1938
|
this.emitConversationEvent(
|
|
1327
1939
|
CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START,
|
|
1328
1940
|
{ messageId },
|
|
1329
|
-
|
|
1941
|
+
this.buildSessionUpdateEventOptions('acp-agent-message', timestamp)
|
|
1330
1942
|
);
|
|
1331
1943
|
this.streamState.assistantTextStarted = true;
|
|
1332
1944
|
}
|
|
@@ -1337,22 +1949,18 @@ export class AcpClient {
|
|
|
1337
1949
|
messageId,
|
|
1338
1950
|
text: cleanText
|
|
1339
1951
|
},
|
|
1340
|
-
|
|
1341
|
-
timestamp,
|
|
1342
|
-
sourceType: 'acp-agent-message'
|
|
1343
|
-
}
|
|
1952
|
+
this.buildSessionUpdateEventOptions('acp-agent-message', timestamp)
|
|
1344
1953
|
);
|
|
1345
1954
|
return;
|
|
1346
1955
|
}
|
|
1347
1956
|
|
|
1348
1957
|
const contentBlock = normalizeAssistantContentBlock(update.content);
|
|
1349
1958
|
if (contentBlock) {
|
|
1350
|
-
const messageId = this.
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
}
|
|
1959
|
+
const messageId = this.prepareAssistantMessageBoundary(
|
|
1960
|
+
update.messageId,
|
|
1961
|
+
timestamp,
|
|
1962
|
+
'acp-agent-message-boundary'
|
|
1963
|
+
);
|
|
1356
1964
|
|
|
1357
1965
|
this.emitConversationEvent(
|
|
1358
1966
|
CONVERSATION_EVENT_KINDS.ASSISTANT_CONTENT_BLOCK,
|
|
@@ -1360,10 +1968,7 @@ export class AcpClient {
|
|
|
1360
1968
|
messageId,
|
|
1361
1969
|
contentBlock
|
|
1362
1970
|
},
|
|
1363
|
-
|
|
1364
|
-
timestamp,
|
|
1365
|
-
sourceType: 'acp-agent-message'
|
|
1366
|
-
}
|
|
1971
|
+
this.buildSessionUpdateEventOptions('acp-agent-message', timestamp)
|
|
1367
1972
|
);
|
|
1368
1973
|
}
|
|
1369
1974
|
return;
|
|
@@ -1378,7 +1983,7 @@ export class AcpClient {
|
|
|
1378
1983
|
this.emitConversationEvent(
|
|
1379
1984
|
CONVERSATION_EVENT_KINDS.REASONING_START,
|
|
1380
1985
|
{ messageId },
|
|
1381
|
-
|
|
1986
|
+
this.buildSessionUpdateEventOptions('acp-agent-thought', timestamp)
|
|
1382
1987
|
);
|
|
1383
1988
|
this.streamState.reasoningMessageId = messageId;
|
|
1384
1989
|
}
|
|
@@ -1389,10 +1994,7 @@ export class AcpClient {
|
|
|
1389
1994
|
messageId,
|
|
1390
1995
|
text: update.content.text
|
|
1391
1996
|
},
|
|
1392
|
-
|
|
1393
|
-
timestamp,
|
|
1394
|
-
sourceType: 'acp-agent-thought'
|
|
1395
|
-
}
|
|
1997
|
+
this.buildSessionUpdateEventOptions('acp-agent-thought', timestamp)
|
|
1396
1998
|
);
|
|
1397
1999
|
}
|
|
1398
2000
|
return;
|
|
@@ -1400,6 +2002,7 @@ export class AcpClient {
|
|
|
1400
2002
|
|
|
1401
2003
|
if (update.sessionUpdate === 'end_turn') {
|
|
1402
2004
|
this.flushOpenTextStreams(timestamp);
|
|
2005
|
+
this.resolvePromptTurnCompletion('end_turn');
|
|
1403
2006
|
return;
|
|
1404
2007
|
}
|
|
1405
2008
|
|
|
@@ -1447,10 +2050,11 @@ export class AcpClient {
|
|
|
1447
2050
|
}
|
|
1448
2051
|
|
|
1449
2052
|
if (update.sessionUpdate === 'config_option_update') {
|
|
2053
|
+
const normalizedConfigOptions = this.noteConfigOptions(update.configOptions || []);
|
|
1450
2054
|
this.emitConversationEvent(
|
|
1451
2055
|
CONVERSATION_EVENT_KINDS.CONFIG_OPTION_UPDATE,
|
|
1452
2056
|
{
|
|
1453
|
-
configOptions: cloneJsonValue(
|
|
2057
|
+
configOptions: cloneJsonValue(normalizedConfigOptions)
|
|
1454
2058
|
},
|
|
1455
2059
|
{
|
|
1456
2060
|
timestamp,
|
|
@@ -1557,15 +2161,20 @@ export class AcpClient {
|
|
|
1557
2161
|
});
|
|
1558
2162
|
|
|
1559
2163
|
if (resultSignature !== existing.resultSignature) {
|
|
2164
|
+
const resultPayload = {
|
|
2165
|
+
...toolPayload,
|
|
2166
|
+
content: resultText || '',
|
|
2167
|
+
isError: update.status === 'failed',
|
|
2168
|
+
contentBlocks: Array.isArray(update.content) ? cloneJsonValue(update.content) : []
|
|
2169
|
+
};
|
|
2170
|
+
|
|
2171
|
+
if (update.rawOutput !== undefined) {
|
|
2172
|
+
resultPayload.rawOutput = cloneJsonValue(update.rawOutput);
|
|
2173
|
+
}
|
|
2174
|
+
|
|
1560
2175
|
this.emitConversationEvent(
|
|
1561
2176
|
CONVERSATION_EVENT_KINDS.TOOL_RESULT,
|
|
1562
|
-
|
|
1563
|
-
...toolPayload,
|
|
1564
|
-
content: resultText || '',
|
|
1565
|
-
isError: update.status === 'failed',
|
|
1566
|
-
rawOutput: cloneJsonValue(update.rawOutput),
|
|
1567
|
-
contentBlocks: Array.isArray(update.content) ? cloneJsonValue(update.content) : []
|
|
1568
|
-
},
|
|
2177
|
+
resultPayload,
|
|
1569
2178
|
{
|
|
1570
2179
|
timestamp,
|
|
1571
2180
|
sourceType: 'acp-tool-call'
|
|
@@ -1588,14 +2197,11 @@ export class AcpClient {
|
|
|
1588
2197
|
}
|
|
1589
2198
|
|
|
1590
2199
|
const launchCommand = await resolveLaunchCommand(this.agentKey, this.agentCommandOverrides);
|
|
1591
|
-
const { command, args } = splitCommandLine(launchCommand);
|
|
1592
2200
|
const spawnEnvironment = await prepareSpawnEnvironment(this.agentKey);
|
|
1593
2201
|
this.spawnCleanup = spawnEnvironment.cleanup;
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1598
|
-
windowsHide: true,
|
|
2202
|
+
this.childProcess = await this.startChildProcess({
|
|
2203
|
+
commandLine: launchCommand,
|
|
2204
|
+
projectPath: this.projectPath,
|
|
1599
2205
|
env: spawnEnvironment.env
|
|
1600
2206
|
});
|
|
1601
2207
|
|
|
@@ -1613,10 +2219,12 @@ export class AcpClient {
|
|
|
1613
2219
|
this.agentKey
|
|
1614
2220
|
);
|
|
1615
2221
|
|
|
1616
|
-
this.connection =
|
|
2222
|
+
this.connection = this.createConnection(() => ({
|
|
1617
2223
|
requestPermission: (params) => this.handleRequestPermission(params),
|
|
2224
|
+
unstable_createElicitation: (params) => this.handleCreateElicitation(params),
|
|
1618
2225
|
sessionUpdate: (params) => this.handleSessionUpdate(params),
|
|
1619
2226
|
readTextFile: async (params) => {
|
|
2227
|
+
this.notePromptActivity('host_tool:readTextFile');
|
|
1620
2228
|
const resolvedPath = path.isAbsolute(params.path)
|
|
1621
2229
|
? params.path
|
|
1622
2230
|
: path.resolve(this.projectPath, params.path);
|
|
@@ -1626,6 +2234,7 @@ export class AcpClient {
|
|
|
1626
2234
|
};
|
|
1627
2235
|
},
|
|
1628
2236
|
writeTextFile: async (params) => {
|
|
2237
|
+
this.notePromptActivity('host_tool:writeTextFile');
|
|
1629
2238
|
const resolvedPath = path.isAbsolute(params.path)
|
|
1630
2239
|
? params.path
|
|
1631
2240
|
: path.resolve(this.projectPath, params.path);
|
|
@@ -1634,6 +2243,7 @@ export class AcpClient {
|
|
|
1634
2243
|
return {};
|
|
1635
2244
|
},
|
|
1636
2245
|
createTerminal: async (params) => {
|
|
2246
|
+
this.notePromptActivity('host_tool:createTerminal');
|
|
1637
2247
|
const terminalId = `terminal:${randomUUID()}`;
|
|
1638
2248
|
const terminal = new LocalTerminalProcess({
|
|
1639
2249
|
sessionId: params.sessionId,
|
|
@@ -1647,12 +2257,15 @@ export class AcpClient {
|
|
|
1647
2257
|
return { terminalId };
|
|
1648
2258
|
},
|
|
1649
2259
|
terminalOutput: async (params) => {
|
|
2260
|
+
this.notePromptActivity('host_tool:terminalOutput');
|
|
1650
2261
|
return this.terminalProcesses.get(params.terminalId)?.currentOutput() || { output: '' };
|
|
1651
2262
|
},
|
|
1652
2263
|
waitForTerminalExit: async (params) => {
|
|
2264
|
+
this.notePromptActivity('host_tool:waitForTerminalExit');
|
|
1653
2265
|
return this.terminalProcesses.get(params.terminalId)?.waitForExit() || {};
|
|
1654
2266
|
},
|
|
1655
2267
|
killTerminal: async (params) => {
|
|
2268
|
+
this.notePromptActivity('host_tool:killTerminal');
|
|
1656
2269
|
const terminal = this.terminalProcesses.get(params.terminalId);
|
|
1657
2270
|
if (terminal) {
|
|
1658
2271
|
await terminal.kill();
|
|
@@ -1660,6 +2273,7 @@ export class AcpClient {
|
|
|
1660
2273
|
return {};
|
|
1661
2274
|
},
|
|
1662
2275
|
releaseTerminal: async (params) => {
|
|
2276
|
+
this.notePromptActivity('host_tool:releaseTerminal');
|
|
1663
2277
|
const terminal = this.terminalProcesses.get(params.terminalId);
|
|
1664
2278
|
if (terminal) {
|
|
1665
2279
|
await terminal.release();
|
|
@@ -1669,37 +2283,75 @@ export class AcpClient {
|
|
|
1669
2283
|
}
|
|
1670
2284
|
}), stream);
|
|
1671
2285
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
2286
|
+
try {
|
|
2287
|
+
this.initializeResult = await withAcpOperationTimeout(
|
|
2288
|
+
this.connection.initialize({
|
|
2289
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2290
|
+
clientCapabilities: {
|
|
2291
|
+
fs: {
|
|
2292
|
+
readTextFile: true,
|
|
2293
|
+
writeTextFile: true
|
|
2294
|
+
},
|
|
2295
|
+
elicitation: {
|
|
2296
|
+
form: {}
|
|
2297
|
+
},
|
|
2298
|
+
terminal: true
|
|
2299
|
+
},
|
|
2300
|
+
clientInfo: {
|
|
2301
|
+
name: '@axhub/genie',
|
|
2302
|
+
title: 'Axhub Genie',
|
|
2303
|
+
version: process.env.APP_VERSION || 'unknown'
|
|
2304
|
+
}
|
|
2305
|
+
}),
|
|
2306
|
+
{
|
|
2307
|
+
agentKey: this.agentKey,
|
|
2308
|
+
operation: 'initialize',
|
|
2309
|
+
timeoutMs: this.startTimeoutMs,
|
|
2310
|
+
code: START_TIMEOUT_ERROR_CODE
|
|
1678
2311
|
},
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
version: process.env.APP_VERSION || 'unknown'
|
|
1685
|
-
}
|
|
1686
|
-
});
|
|
2312
|
+
);
|
|
2313
|
+
} catch (error) {
|
|
2314
|
+
await this.dispose().catch(() => {});
|
|
2315
|
+
throw error;
|
|
2316
|
+
}
|
|
1687
2317
|
|
|
1688
2318
|
return this.initializeResult;
|
|
1689
2319
|
}
|
|
1690
2320
|
|
|
1691
2321
|
async createSession() {
|
|
1692
|
-
const response = await
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
2322
|
+
const response = await withAcpOperationTimeout(
|
|
2323
|
+
this.connection.newSession({
|
|
2324
|
+
cwd: this.projectPath,
|
|
2325
|
+
mcpServers: []
|
|
2326
|
+
}),
|
|
2327
|
+
{
|
|
2328
|
+
agentKey: this.agentKey,
|
|
2329
|
+
operation: 'session/new',
|
|
2330
|
+
timeoutMs: this.sessionTimeoutMs,
|
|
2331
|
+
code: SESSION_TIMEOUT_ERROR_CODE
|
|
2332
|
+
}
|
|
2333
|
+
);
|
|
1696
2334
|
|
|
1697
2335
|
this.sessionId = response.sessionId;
|
|
1698
2336
|
this.attachWriter(this.writer);
|
|
1699
|
-
const normalizedModes =
|
|
2337
|
+
const normalizedModes = normalizeAcpModeState(response?.modes);
|
|
2338
|
+
const normalizedConfigOptions = this.noteConfigOptions(response?.configOptions || []);
|
|
2339
|
+
const normalizedTokenUsage = normalizeAcpTokenUsage(response?.tokenUsage || null);
|
|
2340
|
+
const normalizedAvailableCommands = normalizeAcpAvailableCommands(response?.availableCommands || []);
|
|
1700
2341
|
this.emitPayload({
|
|
1701
2342
|
type: 'session-created',
|
|
1702
|
-
modes: normalizedModes
|
|
2343
|
+
modes: normalizedModes,
|
|
2344
|
+
configOptions: normalizedConfigOptions,
|
|
2345
|
+
tokenUsage: normalizedTokenUsage,
|
|
2346
|
+
availableCommands: normalizedAvailableCommands,
|
|
2347
|
+
capabilitySnapshot: buildAcpCapabilities({
|
|
2348
|
+
configOptions: normalizedConfigOptions,
|
|
2349
|
+
modeState: normalizedModes,
|
|
2350
|
+
tokenUsage: normalizedTokenUsage,
|
|
2351
|
+
availableCommands: normalizedAvailableCommands,
|
|
2352
|
+
provider: this.agentKey,
|
|
2353
|
+
source: 'session-created'
|
|
2354
|
+
})
|
|
1703
2355
|
});
|
|
1704
2356
|
|
|
1705
2357
|
if (normalizedModes) {
|
|
@@ -1713,10 +2365,176 @@ export class AcpClient {
|
|
|
1713
2365
|
);
|
|
1714
2366
|
}
|
|
1715
2367
|
|
|
2368
|
+
if (normalizedConfigOptions.length > 0) {
|
|
2369
|
+
this.emitConversationEvent(
|
|
2370
|
+
CONVERSATION_EVENT_KINDS.CONFIG_OPTION_UPDATE,
|
|
2371
|
+
{ configOptions: normalizedConfigOptions },
|
|
2372
|
+
{
|
|
2373
|
+
timestamp: nowIso(),
|
|
2374
|
+
sourceType: 'acp-session-created-config-options'
|
|
2375
|
+
}
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
if (normalizedAvailableCommands.length > 0) {
|
|
2380
|
+
this.emitConversationEvent(
|
|
2381
|
+
CONVERSATION_EVENT_KINDS.AVAILABLE_COMMANDS_UPDATE,
|
|
2382
|
+
{ availableCommands: normalizedAvailableCommands },
|
|
2383
|
+
{
|
|
2384
|
+
timestamp: nowIso(),
|
|
2385
|
+
sourceType: 'acp-session-created-available-commands'
|
|
2386
|
+
}
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
if (normalizedTokenUsage) {
|
|
2391
|
+
this.emitConversationEvent(
|
|
2392
|
+
CONVERSATION_EVENT_KINDS.USAGE_UPDATE,
|
|
2393
|
+
normalizedTokenUsage,
|
|
2394
|
+
{
|
|
2395
|
+
timestamp: nowIso(),
|
|
2396
|
+
sourceType: 'acp-session-created-usage'
|
|
2397
|
+
}
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
if (this.model) {
|
|
2402
|
+
await this.setModel(this.model);
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
if (this.modeId) {
|
|
2406
|
+
await this.setMode(this.modeId);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
if (this.thoughtLevel) {
|
|
2410
|
+
await this.setConfigOption(this.resolveConfigOptionId('thought_level'), this.thoughtLevel);
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
return response;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
canResumeSession() {
|
|
2417
|
+
const resumeCapability = this.initializeResult?.agentCapabilities?.sessionCapabilities?.resume;
|
|
2418
|
+
return resumeCapability != null && resumeCapability !== false;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
canListSessions() {
|
|
2422
|
+
const listCapability = this.initializeResult?.agentCapabilities?.sessionCapabilities?.list;
|
|
2423
|
+
return listCapability != null && listCapability !== false;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
canLoadSession() {
|
|
2427
|
+
return this.initializeResult?.agentCapabilities?.loadSession === true;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
canCloseSession() {
|
|
2431
|
+
const closeCapability = this.initializeResult?.agentCapabilities?.sessionCapabilities?.close;
|
|
2432
|
+
return closeCapability != null && closeCapability !== false;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
emitRestoredSessionState(response, sourceLabel) {
|
|
2436
|
+
const normalizedModes = normalizeAcpModeState(response?.modes);
|
|
2437
|
+
const normalizedConfigOptions = this.noteConfigOptions(response?.configOptions || []);
|
|
2438
|
+
const normalizedTokenUsage = normalizeAcpTokenUsage(response?.tokenUsage || null);
|
|
2439
|
+
const normalizedAvailableCommands = normalizeAcpAvailableCommands(response?.availableCommands || []);
|
|
2440
|
+
|
|
2441
|
+
if (normalizedModes) {
|
|
2442
|
+
this.emitConversationEvent(
|
|
2443
|
+
CONVERSATION_EVENT_KINDS.MODE_UPDATE,
|
|
2444
|
+
normalizedModes,
|
|
2445
|
+
{
|
|
2446
|
+
timestamp: nowIso(),
|
|
2447
|
+
sourceType: `acp-session-${sourceLabel}-modes`
|
|
2448
|
+
}
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
if (normalizedConfigOptions.length > 0) {
|
|
2453
|
+
this.emitConversationEvent(
|
|
2454
|
+
CONVERSATION_EVENT_KINDS.CONFIG_OPTION_UPDATE,
|
|
2455
|
+
{ configOptions: normalizedConfigOptions },
|
|
2456
|
+
{
|
|
2457
|
+
timestamp: nowIso(),
|
|
2458
|
+
sourceType: `acp-session-${sourceLabel}-config-options`
|
|
2459
|
+
}
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
if (normalizedAvailableCommands.length > 0) {
|
|
2464
|
+
this.emitConversationEvent(
|
|
2465
|
+
CONVERSATION_EVENT_KINDS.AVAILABLE_COMMANDS_UPDATE,
|
|
2466
|
+
{ availableCommands: normalizedAvailableCommands },
|
|
2467
|
+
{
|
|
2468
|
+
timestamp: nowIso(),
|
|
2469
|
+
sourceType: `acp-session-${sourceLabel}-available-commands`
|
|
2470
|
+
}
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
if (normalizedTokenUsage) {
|
|
2475
|
+
this.emitConversationEvent(
|
|
2476
|
+
CONVERSATION_EVENT_KINDS.USAGE_UPDATE,
|
|
2477
|
+
normalizedTokenUsage,
|
|
2478
|
+
{
|
|
2479
|
+
timestamp: nowIso(),
|
|
2480
|
+
sourceType: `acp-session-${sourceLabel}-usage`
|
|
2481
|
+
}
|
|
2482
|
+
);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
emitLoadedSessionIdleState() {
|
|
2487
|
+
if (this.pendingPermissionIds.size > 0 || this.pendingElicitationIds.size > 0) {
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
this.flushOpenTextStreams(nowIso());
|
|
2492
|
+
this.emitConversationEvent(
|
|
2493
|
+
CONVERSATION_EVENT_KINDS.SESSION_STATE_CHANGED,
|
|
2494
|
+
{ state: 'idle' },
|
|
2495
|
+
{
|
|
2496
|
+
timestamp: nowIso(),
|
|
2497
|
+
sourceType: 'acp-session-loaded-idle'
|
|
2498
|
+
}
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
async applyPostSessionRestoreConfiguration() {
|
|
1716
2503
|
if (this.model) {
|
|
1717
2504
|
await this.setModel(this.model);
|
|
1718
2505
|
}
|
|
1719
2506
|
|
|
2507
|
+
if (this.modeId) {
|
|
2508
|
+
await this.setMode(this.modeId);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
if (this.thoughtLevel) {
|
|
2512
|
+
await this.setConfigOption(this.resolveConfigOptionId('thought_level'), this.thoughtLevel);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
async resumeSession(sessionId) {
|
|
2517
|
+
this.sessionId = String(sessionId || '').trim();
|
|
2518
|
+
this.attachWriter(this.writer);
|
|
2519
|
+
|
|
2520
|
+
if (typeof this.connection?.resumeSession !== 'function') {
|
|
2521
|
+
throw new Error('ACP connection does not support session/resume');
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
const response = await this.connection.resumeSession({
|
|
2525
|
+
cwd: this.projectPath,
|
|
2526
|
+
mcpServers: [],
|
|
2527
|
+
sessionId: this.sessionId
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2530
|
+
if (typeof response?.sessionId === 'string' && response.sessionId.trim()) {
|
|
2531
|
+
this.sessionId = response.sessionId.trim();
|
|
2532
|
+
this.attachWriter(this.writer);
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
this.emitRestoredSessionState(response, 'resumed');
|
|
2536
|
+
await this.applyPostSessionRestoreConfiguration();
|
|
2537
|
+
|
|
1720
2538
|
return response;
|
|
1721
2539
|
}
|
|
1722
2540
|
|
|
@@ -1724,6 +2542,7 @@ export class AcpClient {
|
|
|
1724
2542
|
this.sessionId = String(sessionId || '').trim();
|
|
1725
2543
|
this.attachWriter(this.writer);
|
|
1726
2544
|
this.suppressSessionUpdates = Boolean(suppressReplayUpdates);
|
|
2545
|
+
this.isLoadingSessionReplay = true;
|
|
1727
2546
|
try {
|
|
1728
2547
|
const response = await this.connection.loadSession({
|
|
1729
2548
|
cwd: this.projectPath,
|
|
@@ -1731,28 +2550,35 @@ export class AcpClient {
|
|
|
1731
2550
|
sessionId: this.sessionId
|
|
1732
2551
|
});
|
|
1733
2552
|
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
{
|
|
1740
|
-
timestamp: nowIso(),
|
|
1741
|
-
sourceType: 'acp-session-loaded-modes'
|
|
1742
|
-
}
|
|
1743
|
-
);
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
if (this.model) {
|
|
1747
|
-
await this.setModel(this.model);
|
|
1748
|
-
}
|
|
2553
|
+
this.flushOpenTextStreams(nowIso());
|
|
2554
|
+
this.isLoadingSessionReplay = false;
|
|
2555
|
+
this.emitRestoredSessionState(response, 'loaded');
|
|
2556
|
+
this.emitLoadedSessionIdleState();
|
|
2557
|
+
await this.applyPostSessionRestoreConfiguration();
|
|
1749
2558
|
|
|
1750
2559
|
return response;
|
|
1751
2560
|
} finally {
|
|
1752
2561
|
this.suppressSessionUpdates = false;
|
|
2562
|
+
this.isLoadingSessionReplay = false;
|
|
1753
2563
|
}
|
|
1754
2564
|
}
|
|
1755
2565
|
|
|
2566
|
+
async listSessions({ cursor = null, projectPath = this.projectPath } = {}) {
|
|
2567
|
+
if (typeof this.connection?.listSessions !== 'function') {
|
|
2568
|
+
return { sessions: [], nextCursor: null };
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
const response = await this.connection.listSessions({
|
|
2572
|
+
cwd: projectPath || this.projectPath,
|
|
2573
|
+
cursor
|
|
2574
|
+
});
|
|
2575
|
+
|
|
2576
|
+
return {
|
|
2577
|
+
sessions: Array.isArray(response?.sessions) ? response.sessions : [],
|
|
2578
|
+
nextCursor: response?.nextCursor || null
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
|
|
1756
2582
|
async setModel(modelId) {
|
|
1757
2583
|
if (!modelId || !this.sessionId || !this.connection) {
|
|
1758
2584
|
return;
|
|
@@ -1794,6 +2620,25 @@ export class AcpClient {
|
|
|
1794
2620
|
}
|
|
1795
2621
|
}
|
|
1796
2622
|
|
|
2623
|
+
async setConfigOption(configId, value) {
|
|
2624
|
+
const normalizedConfigId = String(configId || '').trim();
|
|
2625
|
+
if (!normalizedConfigId || value == null || !this.sessionId || !this.connection) {
|
|
2626
|
+
return false;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
if (typeof this.connection.setSessionConfigOption !== 'function') {
|
|
2630
|
+
return false;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
await this.connection.setSessionConfigOption({
|
|
2634
|
+
sessionId: this.sessionId,
|
|
2635
|
+
configId: normalizedConfigId,
|
|
2636
|
+
value
|
|
2637
|
+
});
|
|
2638
|
+
|
|
2639
|
+
return true;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
1797
2642
|
async setMode(modeId) {
|
|
1798
2643
|
const normalizedModeId = String(modeId || '').trim();
|
|
1799
2644
|
if (!normalizedModeId || !this.sessionId || !this.connection?.setSessionMode) {
|
|
@@ -1807,6 +2652,35 @@ export class AcpClient {
|
|
|
1807
2652
|
return true;
|
|
1808
2653
|
}
|
|
1809
2654
|
|
|
2655
|
+
async requestPromptCancellation() {
|
|
2656
|
+
const normalizedSessionId = String(this.sessionId || '').trim();
|
|
2657
|
+
if (!normalizedSessionId || typeof this.connection?.cancel !== 'function') {
|
|
2658
|
+
return false;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
for (const requestId of this.pendingPermissionIds) {
|
|
2662
|
+
AcpClient.resolvePermissionRequest(requestId, { cancelled: true });
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
for (const requestId of this.pendingElicitationIds) {
|
|
2666
|
+
AcpClient.resolveElicitationRequest(requestId, { action: 'cancel' });
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
let timeoutId = null;
|
|
2670
|
+
try {
|
|
2671
|
+
return await Promise.race([
|
|
2672
|
+
this.connection.cancel({ sessionId: normalizedSessionId }).then(() => true, () => false),
|
|
2673
|
+
new Promise((resolve) => {
|
|
2674
|
+
timeoutId = setTimeout(() => resolve(false), DEFAULT_CLOSE_GRACE_MS);
|
|
2675
|
+
})
|
|
2676
|
+
]);
|
|
2677
|
+
} finally {
|
|
2678
|
+
if (timeoutId) {
|
|
2679
|
+
clearTimeout(timeoutId);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
|
|
1810
2684
|
async sendPrompt(promptText, attachments = {}) {
|
|
1811
2685
|
this.resetTurnState();
|
|
1812
2686
|
this.hasReportedPromptFatalError = false;
|
|
@@ -1815,6 +2689,10 @@ export class AcpClient {
|
|
|
1815
2689
|
const clientRequestId = typeof attachments?.clientRequestId === 'string' && attachments.clientRequestId.trim()
|
|
1816
2690
|
? attachments.clientRequestId.trim()
|
|
1817
2691
|
: null;
|
|
2692
|
+
this.activePromptClientRequestId = clientRequestId;
|
|
2693
|
+
this.activePromptText = promptPayload.text;
|
|
2694
|
+
this.isReplayingPromptHistory = false;
|
|
2695
|
+
this.hasObservedActivePromptEcho = false;
|
|
1818
2696
|
|
|
1819
2697
|
this.emitConversationEvent(
|
|
1820
2698
|
CONVERSATION_EVENT_KINDS.USER_MESSAGE,
|
|
@@ -1854,6 +2732,28 @@ export class AcpClient {
|
|
|
1854
2732
|
};
|
|
1855
2733
|
this.pendingPromptDiagnostic = diagnosticGuard;
|
|
1856
2734
|
});
|
|
2735
|
+
let turnCompletionResolve = null;
|
|
2736
|
+
const turnCompletionPromise = new Promise((resolve) => {
|
|
2737
|
+
turnCompletionResolve = resolve;
|
|
2738
|
+
this.pendingPromptTurnCompletion = resolve;
|
|
2739
|
+
});
|
|
2740
|
+
let timeoutGuard = null;
|
|
2741
|
+
const promptTimeoutMs = this.promptTimeoutMs;
|
|
2742
|
+
const timeoutPromise = promptTimeoutMs > 0
|
|
2743
|
+
? new Promise((_, reject) => {
|
|
2744
|
+
timeoutGuard = {
|
|
2745
|
+
handled: false,
|
|
2746
|
+
reject
|
|
2747
|
+
};
|
|
2748
|
+
this.startPromptActivityWatchdog((error) => {
|
|
2749
|
+
if (timeoutGuard.handled) {
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
2752
|
+
timeoutGuard.handled = true;
|
|
2753
|
+
reject(error);
|
|
2754
|
+
}, promptTimeoutMs);
|
|
2755
|
+
})
|
|
2756
|
+
: null;
|
|
1857
2757
|
|
|
1858
2758
|
const promptPromise = this.connection.prompt({
|
|
1859
2759
|
sessionId: this.sessionId,
|
|
@@ -1862,7 +2762,10 @@ export class AcpClient {
|
|
|
1862
2762
|
promptPromise.catch(() => {});
|
|
1863
2763
|
|
|
1864
2764
|
try {
|
|
1865
|
-
const
|
|
2765
|
+
const racedPromises = timeoutPromise
|
|
2766
|
+
? [promptPromise, diagnosticPromise, turnCompletionPromise, timeoutPromise]
|
|
2767
|
+
: [promptPromise, diagnosticPromise, turnCompletionPromise];
|
|
2768
|
+
const response = await Promise.race(racedPromises);
|
|
1866
2769
|
const finishedAt = nowIso();
|
|
1867
2770
|
this.flushOpenTextStreams(finishedAt);
|
|
1868
2771
|
this.emitConversationEvent(
|
|
@@ -1884,17 +2787,28 @@ export class AcpClient {
|
|
|
1884
2787
|
|
|
1885
2788
|
return response;
|
|
1886
2789
|
} catch (error) {
|
|
2790
|
+
if (error?.code === PROMPT_TIMEOUT_ERROR_CODE) {
|
|
2791
|
+
await this.requestPromptCancellation();
|
|
2792
|
+
}
|
|
1887
2793
|
this.reportPromptFailure(error);
|
|
1888
2794
|
throw error;
|
|
1889
2795
|
} finally {
|
|
2796
|
+
this.stopPromptActivityWatchdog();
|
|
1890
2797
|
this.isPromptInFlight = false;
|
|
2798
|
+
this.activePromptClientRequestId = null;
|
|
2799
|
+
this.activePromptText = null;
|
|
2800
|
+
this.isReplayingPromptHistory = false;
|
|
2801
|
+
this.hasObservedActivePromptEcho = false;
|
|
1891
2802
|
if (this.pendingPromptDiagnostic === diagnosticGuard) {
|
|
1892
2803
|
this.pendingPromptDiagnostic = null;
|
|
1893
2804
|
}
|
|
2805
|
+
if (this.pendingPromptTurnCompletion === turnCompletionResolve) {
|
|
2806
|
+
this.pendingPromptTurnCompletion = null;
|
|
2807
|
+
}
|
|
1894
2808
|
}
|
|
1895
2809
|
}
|
|
1896
2810
|
|
|
1897
|
-
async cancel(sessionId = this.sessionId) {
|
|
2811
|
+
async cancel(sessionId = this.sessionId, { disposeOnTimeout = false, cancelGraceMs = DEFAULT_CANCEL_GRACE_MS } = {}) {
|
|
1898
2812
|
const normalizedSessionId = String(sessionId || this.sessionId || '').trim();
|
|
1899
2813
|
if (!normalizedSessionId) {
|
|
1900
2814
|
return false;
|
|
@@ -1904,25 +2818,66 @@ export class AcpClient {
|
|
|
1904
2818
|
AcpClient.resolvePermissionRequest(requestId, { cancelled: true });
|
|
1905
2819
|
}
|
|
1906
2820
|
|
|
2821
|
+
for (const requestId of this.pendingElicitationIds) {
|
|
2822
|
+
AcpClient.resolveElicitationRequest(requestId, { action: 'cancel' });
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
let timeoutId = null;
|
|
1907
2826
|
try {
|
|
1908
|
-
await
|
|
1909
|
-
|
|
1910
|
-
|
|
2827
|
+
const cancelled = await Promise.race([
|
|
2828
|
+
this.connection.cancel({
|
|
2829
|
+
sessionId: normalizedSessionId
|
|
2830
|
+
}).then(() => true, () => false),
|
|
2831
|
+
new Promise((resolve) => {
|
|
2832
|
+
timeoutId = setTimeout(() => resolve(false), normalizePromptTimeoutMs(cancelGraceMs, DEFAULT_CANCEL_GRACE_MS));
|
|
2833
|
+
})
|
|
2834
|
+
]);
|
|
2835
|
+
if (!cancelled && disposeOnTimeout) {
|
|
2836
|
+
await this.dispose().catch(() => {});
|
|
2837
|
+
}
|
|
1911
2838
|
this.emitPayload({
|
|
1912
2839
|
type: 'session-aborted',
|
|
1913
|
-
success:
|
|
2840
|
+
success: cancelled
|
|
1914
2841
|
});
|
|
1915
|
-
return
|
|
2842
|
+
return cancelled;
|
|
1916
2843
|
} catch {
|
|
2844
|
+
if (disposeOnTimeout) {
|
|
2845
|
+
await this.dispose().catch(() => {});
|
|
2846
|
+
}
|
|
1917
2847
|
return false;
|
|
2848
|
+
} finally {
|
|
2849
|
+
if (timeoutId) {
|
|
2850
|
+
clearTimeout(timeoutId);
|
|
2851
|
+
}
|
|
1918
2852
|
}
|
|
1919
2853
|
}
|
|
1920
2854
|
|
|
1921
|
-
async
|
|
2855
|
+
async closeSession(sessionId = this.sessionId) {
|
|
2856
|
+
const normalizedSessionId = String(sessionId || this.sessionId || '').trim();
|
|
2857
|
+
if (!normalizedSessionId || !this.canCloseSession() || typeof this.connection?.closeSession !== 'function') {
|
|
2858
|
+
return false;
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
try {
|
|
2862
|
+
await this.connection.closeSession({
|
|
2863
|
+
sessionId: normalizedSessionId
|
|
2864
|
+
});
|
|
2865
|
+
return true;
|
|
2866
|
+
} catch (error) {
|
|
2867
|
+
console.warn(`[ACP:${this.agentKey}] Failed to close session ${normalizedSessionId}: ${error.message}`);
|
|
2868
|
+
return false;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
async dispose() {
|
|
1922
2873
|
for (const requestId of this.pendingPermissionIds) {
|
|
1923
2874
|
AcpClient.resolvePermissionRequest(requestId, { cancelled: true });
|
|
1924
2875
|
}
|
|
1925
2876
|
|
|
2877
|
+
for (const requestId of this.pendingElicitationIds) {
|
|
2878
|
+
AcpClient.resolveElicitationRequest(requestId, { action: 'cancel' });
|
|
2879
|
+
}
|
|
2880
|
+
|
|
1926
2881
|
for (const [terminalId, terminal] of this.terminalProcesses.entries()) {
|
|
1927
2882
|
try {
|
|
1928
2883
|
await terminal.release();
|
|
@@ -1935,27 +2890,13 @@ export class AcpClient {
|
|
|
1935
2890
|
this.childProcess.stdin.end();
|
|
1936
2891
|
}
|
|
1937
2892
|
|
|
1938
|
-
|
|
1939
|
-
let settled = false;
|
|
1940
|
-
const finalize = (value) => {
|
|
1941
|
-
if (settled) {
|
|
1942
|
-
return;
|
|
1943
|
-
}
|
|
1944
|
-
settled = true;
|
|
1945
|
-
resolve(value);
|
|
1946
|
-
};
|
|
1947
|
-
|
|
1948
|
-
const timer = setTimeout(() => finalize(false), DEFAULT_CLOSE_GRACE_MS);
|
|
1949
|
-
this.childProcess.once('close', () => {
|
|
1950
|
-
clearTimeout(timer);
|
|
1951
|
-
finalize(true);
|
|
1952
|
-
});
|
|
1953
|
-
});
|
|
1954
|
-
|
|
2893
|
+
let closed = await waitForChildProcessClose(this.childProcess);
|
|
1955
2894
|
if (!closed) {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2895
|
+
terminateChildProcessTree(this.childProcess, 'SIGTERM');
|
|
2896
|
+
closed = await waitForChildProcessClose(this.childProcess);
|
|
2897
|
+
}
|
|
2898
|
+
if (!closed) {
|
|
2899
|
+
terminateChildProcessTree(this.childProcess, 'SIGKILL');
|
|
1959
2900
|
}
|
|
1960
2901
|
}
|
|
1961
2902
|
|
|
@@ -1971,6 +2912,11 @@ export class AcpClient {
|
|
|
1971
2912
|
this.initializeResult = null;
|
|
1972
2913
|
}
|
|
1973
2914
|
|
|
2915
|
+
async close() {
|
|
2916
|
+
await this.closeSession().catch(() => false);
|
|
2917
|
+
await this.dispose();
|
|
2918
|
+
}
|
|
2919
|
+
|
|
1974
2920
|
static resolvePermissionRequest(requestId, decision = {}) {
|
|
1975
2921
|
const pending = pendingPermissionRequests.get(requestId);
|
|
1976
2922
|
if (!pending) {
|
|
@@ -1980,6 +2926,16 @@ export class AcpClient {
|
|
|
1980
2926
|
pending.resolve(choosePermissionResponse(pending.request, decision));
|
|
1981
2927
|
return true;
|
|
1982
2928
|
}
|
|
2929
|
+
|
|
2930
|
+
static resolveElicitationRequest(requestId, decision = {}) {
|
|
2931
|
+
const pending = pendingElicitationRequests.get(requestId);
|
|
2932
|
+
if (!pending) {
|
|
2933
|
+
return false;
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
pending.resolve(normalizeElicitationResponse(decision));
|
|
2937
|
+
return true;
|
|
2938
|
+
}
|
|
1983
2939
|
}
|
|
1984
2940
|
|
|
1985
2941
|
export { normalizeTerminalCommandForSpawn };
|