@oh-my-pi/pi-coding-agent 13.19.0 → 14.0.3
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/CHANGELOG.md +277 -2
- package/package.json +86 -20
- package/scripts/format-prompts.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +24 -0
- package/src/autoresearch/contract.ts +0 -44
- package/src/autoresearch/dashboard.ts +1 -2
- package/src/autoresearch/git.ts +91 -0
- package/src/autoresearch/helpers.ts +49 -0
- package/src/autoresearch/index.ts +28 -187
- package/src/autoresearch/prompt.md +26 -9
- package/src/autoresearch/state.ts +0 -6
- package/src/autoresearch/tools/init-experiment.ts +202 -117
- package/src/autoresearch/tools/log-experiment.ts +83 -125
- package/src/autoresearch/tools/run-experiment.ts +48 -10
- package/src/autoresearch/types.ts +2 -2
- package/src/capability/index.ts +4 -2
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/grep-cli.ts +8 -8
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/read-cli.ts +67 -0
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/update-cli.ts +3 -3
- package/src/cli.ts +2 -0
- package/src/commands/grep.ts +6 -1
- package/src/commands/grievances.ts +20 -0
- package/src/commands/read.ts +33 -0
- package/src/commit/agentic/agent.ts +5 -5
- package/src/commit/agentic/index.ts +3 -4
- package/src/commit/agentic/tools/analyze-file.ts +3 -3
- package/src/commit/agentic/validation.ts +1 -1
- package/src/commit/analysis/conventional.ts +4 -4
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +4 -4
- package/src/commit/map-reduce/map-phase.ts +4 -4
- package/src/commit/map-reduce/reduce-phase.ts +4 -4
- package/src/commit/pipeline.ts +3 -4
- package/src/config/model-registry.ts +17 -3
- package/src/config/prompt-templates.ts +44 -226
- package/src/config/resolve-config-value.ts +4 -2
- package/src/config/settings-schema.ts +54 -2
- package/src/config/settings.ts +25 -26
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/log-viewer.ts +3 -2
- package/src/discovery/builtin.ts +1 -2
- package/src/discovery/codex.ts +2 -2
- package/src/discovery/github.ts +2 -1
- package/src/discovery/helpers.ts +2 -2
- package/src/discovery/opencode.ts +2 -2
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +309 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +454 -0
- package/src/{patch → edit/modes}/hashline.ts +741 -361
- package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
- package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
- package/src/{patch → edit}/normalize.ts +97 -76
- package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
- package/src/exec/bash-executor.ts +4 -2
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +5 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +2 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +36 -15
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +34 -11
- package/src/extensibility/extensions/loader.ts +9 -4
- package/src/extensibility/extensions/runner.ts +24 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/loader.ts +5 -6
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +2 -1
- package/src/extensibility/slash-commands.ts +3 -7
- package/src/index.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/ipy/executor.ts +58 -17
- package/src/ipy/gateway-coordinator.ts +6 -4
- package/src/ipy/kernel.ts +45 -22
- package/src/ipy/runtime.ts +2 -2
- package/src/lsp/client.ts +7 -4
- package/src/lsp/clients/lsp-linter-client.ts +4 -4
- package/src/lsp/config.ts +20 -4
- package/src/lsp/defaults.json +688 -154
- package/src/lsp/index.ts +234 -45
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +12 -1
- package/src/lsp/utils.ts +8 -1
- package/src/main.ts +102 -46
- package/src/memories/index.ts +4 -5
- package/src/modes/acp/acp-agent.ts +563 -163
- package/src/modes/acp/acp-event-mapper.ts +9 -1
- package/src/modes/acp/acp-mode.ts +4 -2
- package/src/modes/components/agent-dashboard.ts +3 -4
- package/src/modes/components/diff.ts +6 -7
- package/src/modes/components/read-tool-group.ts +6 -12
- package/src/modes/components/session-observer-overlay.ts +21 -12
- package/src/modes/components/settings-defs.ts +5 -0
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/btw-controller.ts +2 -2
- package/src/modes/controllers/command-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +12 -8
- package/src/modes/index.ts +20 -2
- package/src/modes/interactive-mode.ts +94 -37
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +178 -13
- package/src/modes/rpc/rpc-mode.ts +73 -3
- package/src/modes/rpc/rpc-types.ts +53 -1
- package/src/modes/theme/theme.ts +80 -8
- package/src/modes/types.ts +2 -2
- package/src/prompts/review-request.md +6 -0
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/chunk-edit.md +223 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/grep.md +3 -0
- package/src/prompts/tools/lsp.md +5 -5
- package/src/prompts/tools/read-chunk.md +17 -0
- package/src/prompts/tools/read.md +19 -5
- package/src/sdk.ts +190 -154
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +306 -256
- package/src/session/agent-storage.ts +12 -12
- package/src/session/compaction/branch-summarization.ts +3 -3
- package/src/session/compaction/compaction.ts +5 -6
- package/src/session/compaction/utils.ts +3 -3
- package/src/session/history-storage.ts +62 -19
- package/src/session/messages.ts +3 -3
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-storage.ts +4 -2
- package/src/session/streaming-output.ts +1 -1
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +56 -8
- package/src/ssh/connection-manager.ts +2 -2
- package/src/ssh/sshfs-mount.ts +5 -5
- package/src/stt/downloader.ts +4 -4
- package/src/stt/recorder.ts +4 -4
- package/src/stt/transcriber.ts +2 -2
- package/src/system-prompt.ts +21 -13
- package/src/task/agents.ts +5 -6
- package/src/task/commands.ts +2 -5
- package/src/task/executor.ts +4 -4
- package/src/task/index.ts +3 -4
- package/src/task/template.ts +2 -2
- package/src/task/worktree.ts +4 -4
- package/src/tools/ask.ts +2 -3
- package/src/tools/ast-edit.ts +7 -7
- package/src/tools/ast-grep.ts +7 -7
- package/src/tools/auto-generated-guard.ts +36 -41
- package/src/tools/await-tool.ts +2 -2
- package/src/tools/bash.ts +5 -23
- package/src/tools/browser.ts +4 -5
- package/src/tools/calculator.ts +2 -3
- package/src/tools/cancel-job.ts +2 -2
- package/src/tools/checkpoint.ts +3 -3
- package/src/tools/debug.ts +1007 -0
- package/src/tools/exit-plan-mode.ts +2 -3
- package/src/tools/fetch.ts +67 -3
- package/src/tools/find.ts +4 -5
- package/src/tools/fs-cache-invalidation.ts +5 -0
- package/src/tools/gemini-image.ts +13 -5
- package/src/tools/gh.ts +10 -11
- package/src/tools/grep.ts +57 -9
- package/src/tools/index.ts +44 -22
- package/src/tools/inspect-image.ts +4 -4
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/python.ts +19 -6
- package/src/tools/read.ts +198 -67
- package/src/tools/render-mermaid.ts +2 -3
- package/src/tools/render-utils.ts +20 -6
- package/src/tools/renderers.ts +3 -1
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +70 -39
- package/src/tools/search-tool-bm25.ts +2 -2
- package/src/tools/ssh.ts +2 -2
- package/src/tools/todo-write.ts +2 -2
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/write.ts +5 -6
- package/src/tui/tree-list.ts +3 -1
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/commit-message-generator.ts +2 -3
- package/src/utils/edit-mode.ts +49 -0
- package/src/utils/file-display-mode.ts +6 -5
- package/src/utils/file-mentions.ts +8 -7
- package/src/utils/git.ts +4 -4
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/title-generator.ts +2 -3
- package/src/utils/tools-manager.ts +6 -6
- package/src/web/scrapers/choosealicense.ts +1 -1
- package/src/web/search/index.ts +3 -3
- package/src/autoresearch/command-initialize.md +0 -34
- package/src/patch/diff.ts +0 -433
- package/src/patch/index.ts +0 -888
- package/src/patch/parser.ts +0 -532
- package/src/patch/types.ts +0 -292
- package/src/prompts/agents/oracle.md +0 -77
- package/src/tools/pending-action.ts +0 -49
- package/src/utils/child-process.ts +0 -88
- package/src/utils/frontmatter.ts +0 -117
- package/src/utils/image-input.ts +0 -274
- package/src/utils/mime.ts +0 -53
- package/src/utils/prompt-format.ts +0 -170
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
type AuthenticateRequest,
|
|
6
6
|
type AuthenticateResponse,
|
|
7
7
|
type AvailableCommand,
|
|
8
|
+
type CloseSessionRequest,
|
|
9
|
+
type CloseSessionResponse,
|
|
10
|
+
type ForkSessionRequest,
|
|
11
|
+
type ForkSessionResponse,
|
|
8
12
|
type InitializeRequest,
|
|
9
13
|
type InitializeResponse,
|
|
10
14
|
type ListSessionsRequest,
|
|
@@ -17,15 +21,21 @@ import {
|
|
|
17
21
|
PROTOCOL_VERSION,
|
|
18
22
|
type PromptRequest,
|
|
19
23
|
type PromptResponse,
|
|
24
|
+
type ResumeSessionRequest,
|
|
25
|
+
type ResumeSessionResponse,
|
|
20
26
|
type SessionConfigOption,
|
|
21
27
|
type SessionInfo,
|
|
28
|
+
type SessionModelState,
|
|
22
29
|
type SessionModeState,
|
|
23
30
|
type SessionNotification,
|
|
24
31
|
type SessionUpdate,
|
|
25
32
|
type SetSessionConfigOptionRequest,
|
|
26
33
|
type SetSessionConfigOptionResponse,
|
|
34
|
+
type SetSessionModelRequest,
|
|
35
|
+
type SetSessionModelResponse,
|
|
27
36
|
type SetSessionModeRequest,
|
|
28
37
|
type SetSessionModeResponse,
|
|
38
|
+
type Usage,
|
|
29
39
|
} from "@agentclientprotocol/sdk";
|
|
30
40
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
31
41
|
import { logger, VERSION } from "@oh-my-pi/pi-utils";
|
|
@@ -35,7 +45,11 @@ import { MCPManager } from "../../mcp/manager";
|
|
|
35
45
|
import type { MCPServerConfig } from "../../mcp/types";
|
|
36
46
|
import { theme } from "../../modes/theme/theme";
|
|
37
47
|
import type { AgentSession, AgentSessionEvent } from "../../session/agent-session";
|
|
38
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
SessionManager,
|
|
50
|
+
type SessionInfo as StoredSessionInfo,
|
|
51
|
+
type UsageStatistics,
|
|
52
|
+
} from "../../session/session-manager";
|
|
39
53
|
import { parseThinkingLevel } from "../../thinking";
|
|
40
54
|
import { mapAgentSessionEventToAcpSessionUpdates, mapToolKind } from "./acp-event-mapper";
|
|
41
55
|
|
|
@@ -53,14 +67,23 @@ type AgentImageContent = {
|
|
|
53
67
|
};
|
|
54
68
|
|
|
55
69
|
type PromptTurnState = {
|
|
56
|
-
|
|
70
|
+
userMessageId: string;
|
|
57
71
|
cancelRequested: boolean;
|
|
58
72
|
settled: boolean;
|
|
73
|
+
usageBaseline: UsageStatistics;
|
|
59
74
|
unsubscribe: (() => void) | undefined;
|
|
60
75
|
resolve: (value: PromptResponse) => void;
|
|
61
76
|
reject: (reason?: unknown) => void;
|
|
62
77
|
};
|
|
63
78
|
|
|
79
|
+
type ManagedSessionRecord = {
|
|
80
|
+
session: AgentSession;
|
|
81
|
+
mcpManager: MCPManager | undefined;
|
|
82
|
+
promptTurn: PromptTurnState | undefined;
|
|
83
|
+
liveMessageIds: WeakMap<object, string>;
|
|
84
|
+
extensionsConfigured: boolean;
|
|
85
|
+
};
|
|
86
|
+
|
|
64
87
|
type ReplayableMessage = {
|
|
65
88
|
role: string;
|
|
66
89
|
content?: unknown;
|
|
@@ -86,6 +109,8 @@ type MCPSourceMap = {
|
|
|
86
109
|
[name: string]: MCPSource;
|
|
87
110
|
};
|
|
88
111
|
|
|
112
|
+
type CreateAcpSession = (cwd: string) => Promise<AgentSession>;
|
|
113
|
+
|
|
89
114
|
const acpExtensionUiContext: ExtensionUIContext = {
|
|
90
115
|
select: async () => undefined,
|
|
91
116
|
confirm: async () => false,
|
|
@@ -118,17 +143,20 @@ const acpExtensionUiContext: ExtensionUIContext = {
|
|
|
118
143
|
|
|
119
144
|
export class AcpAgent implements Agent {
|
|
120
145
|
#connection: AgentSideConnection;
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
146
|
+
#initialSession: AgentSession | undefined;
|
|
147
|
+
#createSession: CreateAcpSession;
|
|
148
|
+
#sessions = new Map<string, ManagedSessionRecord>();
|
|
149
|
+
#disposePromise: Promise<void> | undefined;
|
|
150
|
+
#cleanupRegistered = false;
|
|
125
151
|
|
|
126
|
-
constructor(connection: AgentSideConnection,
|
|
152
|
+
constructor(connection: AgentSideConnection, initialSession: AgentSession, createSession: CreateAcpSession) {
|
|
127
153
|
this.#connection = connection;
|
|
128
|
-
this.#
|
|
154
|
+
this.#initialSession = initialSession;
|
|
155
|
+
this.#createSession = createSession;
|
|
129
156
|
}
|
|
130
157
|
|
|
131
158
|
async initialize(_params: InitializeRequest): Promise<InitializeResponse> {
|
|
159
|
+
this.#registerConnectionCleanup();
|
|
132
160
|
return {
|
|
133
161
|
protocolVersion: PROTOCOL_VERSION,
|
|
134
162
|
agentInfo: {
|
|
@@ -155,6 +183,9 @@ export class AcpAgent implements Agent {
|
|
|
155
183
|
},
|
|
156
184
|
sessionCapabilities: {
|
|
157
185
|
list: {},
|
|
186
|
+
fork: {},
|
|
187
|
+
resume: {},
|
|
188
|
+
close: {},
|
|
158
189
|
},
|
|
159
190
|
},
|
|
160
191
|
};
|
|
@@ -166,50 +197,27 @@ export class AcpAgent implements Agent {
|
|
|
166
197
|
|
|
167
198
|
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
|
|
168
199
|
this.#assertAbsoluteCwd(params.cwd);
|
|
169
|
-
await this.#
|
|
170
|
-
await this.#session.sessionManager.moveTo(params.cwd);
|
|
171
|
-
if (this.#hasOpenedSession) {
|
|
172
|
-
const success = await this.#session.newSession();
|
|
173
|
-
if (!success) {
|
|
174
|
-
throw new Error("ACP session creation was cancelled");
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
this.#hasOpenedSession = true;
|
|
178
|
-
await this.#session.sessionManager.ensureOnDisk();
|
|
179
|
-
await this.#configureExtensions();
|
|
180
|
-
await this.#configureMcpServers(params.mcpServers);
|
|
200
|
+
const record = await this.#createNewSessionRecord(params.cwd, params.mcpServers);
|
|
181
201
|
const response: NewSessionResponse = {
|
|
182
|
-
sessionId:
|
|
183
|
-
configOptions: this.#buildConfigOptions(),
|
|
202
|
+
sessionId: record.session.sessionId,
|
|
203
|
+
configOptions: this.#buildConfigOptions(record.session),
|
|
204
|
+
models: this.#buildModelState(record.session),
|
|
184
205
|
modes: this.#buildModeState(),
|
|
185
206
|
};
|
|
186
|
-
this.#scheduleBootstrapUpdates(
|
|
207
|
+
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
187
208
|
return response;
|
|
188
209
|
}
|
|
189
210
|
|
|
190
211
|
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
|
191
212
|
this.#assertAbsoluteCwd(params.cwd);
|
|
192
|
-
await this.#
|
|
193
|
-
|
|
194
|
-
if (!storedSession) {
|
|
195
|
-
throw new Error(`ACP session not found: ${params.sessionId}`);
|
|
196
|
-
}
|
|
197
|
-
const currentSessionFile = this.#session.sessionManager.getSessionFile();
|
|
198
|
-
if (currentSessionFile !== storedSession.path) {
|
|
199
|
-
const success = await this.#session.switchSession(storedSession.path);
|
|
200
|
-
if (!success) {
|
|
201
|
-
throw new Error(`ACP session load was cancelled: ${params.sessionId}`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
this.#hasOpenedSession = true;
|
|
205
|
-
await this.#configureExtensions();
|
|
206
|
-
await this.#configureMcpServers(params.mcpServers);
|
|
207
|
-
await this.#replaySessionHistory();
|
|
213
|
+
const record = await this.#loadManagedSession(params.sessionId, params.cwd, params.mcpServers);
|
|
214
|
+
await this.#replaySessionHistory(record);
|
|
208
215
|
const response: LoadSessionResponse = {
|
|
209
|
-
configOptions: this.#buildConfigOptions(),
|
|
216
|
+
configOptions: this.#buildConfigOptions(record.session),
|
|
217
|
+
models: this.#buildModelState(record.session),
|
|
210
218
|
modes: this.#buildModeState(),
|
|
211
219
|
};
|
|
212
|
-
this.#scheduleBootstrapUpdates(
|
|
220
|
+
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
213
221
|
return response;
|
|
214
222
|
}
|
|
215
223
|
|
|
@@ -217,7 +225,9 @@ export class AcpAgent implements Agent {
|
|
|
217
225
|
if (params.cwd) {
|
|
218
226
|
this.#assertAbsoluteCwd(params.cwd);
|
|
219
227
|
}
|
|
220
|
-
|
|
228
|
+
for (const record of this.#sessions.values()) {
|
|
229
|
+
await record.session.sessionManager.flush();
|
|
230
|
+
}
|
|
221
231
|
const sessions = await this.#listStoredSessions(params.cwd ?? undefined);
|
|
222
232
|
const offset = this.#parseCursor(params.cursor ?? undefined);
|
|
223
233
|
const paged = sessions.slice(offset, offset + SESSION_PAGE_SIZE);
|
|
@@ -228,20 +238,54 @@ export class AcpAgent implements Agent {
|
|
|
228
238
|
};
|
|
229
239
|
}
|
|
230
240
|
|
|
241
|
+
async unstable_resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
|
|
242
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
243
|
+
const record = await this.#resumeManagedSession(params.sessionId, params.cwd, params.mcpServers ?? []);
|
|
244
|
+
const response: ResumeSessionResponse = {
|
|
245
|
+
configOptions: this.#buildConfigOptions(record.session),
|
|
246
|
+
models: this.#buildModelState(record.session),
|
|
247
|
+
modes: this.#buildModeState(),
|
|
248
|
+
};
|
|
249
|
+
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
250
|
+
return response;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
|
|
254
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
255
|
+
const record = await this.#forkManagedSession(params);
|
|
256
|
+
const response: ForkSessionResponse = {
|
|
257
|
+
sessionId: record.session.sessionId,
|
|
258
|
+
configOptions: this.#buildConfigOptions(record.session),
|
|
259
|
+
models: this.#buildModelState(record.session),
|
|
260
|
+
modes: this.#buildModeState(),
|
|
261
|
+
};
|
|
262
|
+
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
263
|
+
return response;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async unstable_closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
|
|
267
|
+
const record = this.#sessions.get(params.sessionId);
|
|
268
|
+
if (!record) {
|
|
269
|
+
return {};
|
|
270
|
+
}
|
|
271
|
+
await this.#closeManagedSession(params.sessionId, record);
|
|
272
|
+
return {};
|
|
273
|
+
}
|
|
274
|
+
|
|
231
275
|
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse> {
|
|
232
|
-
this.#
|
|
276
|
+
const record = this.#getSessionRecord(params.sessionId);
|
|
233
277
|
if (params.modeId !== ACP_MODE_ID) {
|
|
234
278
|
throw new Error(`Unsupported ACP mode: ${params.modeId}`);
|
|
235
279
|
}
|
|
236
280
|
await this.#connection.sessionUpdate({
|
|
237
|
-
sessionId:
|
|
281
|
+
sessionId: record.session.sessionId,
|
|
238
282
|
update: this.#buildCurrentModeUpdate(),
|
|
239
283
|
});
|
|
240
284
|
return {};
|
|
241
285
|
}
|
|
242
286
|
|
|
243
287
|
async setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse> {
|
|
244
|
-
this.#
|
|
288
|
+
const record = this.#getSessionRecord(params.sessionId);
|
|
245
289
|
if (typeof params.value === "boolean") {
|
|
246
290
|
throw new Error(`Unsupported boolean ACP config option: ${params.configId}`);
|
|
247
291
|
}
|
|
@@ -253,18 +297,18 @@ export class AcpAgent implements Agent {
|
|
|
253
297
|
}
|
|
254
298
|
break;
|
|
255
299
|
case MODEL_CONFIG_ID:
|
|
256
|
-
await this.#setModelById(params.value);
|
|
300
|
+
await this.#setModelById(record.session, params.value);
|
|
257
301
|
break;
|
|
258
302
|
case THINKING_CONFIG_ID:
|
|
259
|
-
this.#setThinkingLevelById(params.value);
|
|
303
|
+
this.#setThinkingLevelById(record.session, params.value);
|
|
260
304
|
break;
|
|
261
305
|
default:
|
|
262
306
|
throw new Error(`Unknown ACP config option: ${params.configId}`);
|
|
263
307
|
}
|
|
264
308
|
|
|
265
|
-
const configOptions = this.#buildConfigOptions();
|
|
309
|
+
const configOptions = this.#buildConfigOptions(record.session);
|
|
266
310
|
await this.#connection.sessionUpdate({
|
|
267
|
-
sessionId:
|
|
311
|
+
sessionId: record.session.sessionId,
|
|
268
312
|
update: {
|
|
269
313
|
sessionUpdate: "config_option_update",
|
|
270
314
|
configOptions,
|
|
@@ -273,49 +317,64 @@ export class AcpAgent implements Agent {
|
|
|
273
317
|
return { configOptions };
|
|
274
318
|
}
|
|
275
319
|
|
|
320
|
+
async unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse> {
|
|
321
|
+
const record = this.#getSessionRecord(params.sessionId);
|
|
322
|
+
await this.#setModelById(record.session, params.modelId);
|
|
323
|
+
await this.#connection.sessionUpdate({
|
|
324
|
+
sessionId: record.session.sessionId,
|
|
325
|
+
update: {
|
|
326
|
+
sessionUpdate: "config_option_update",
|
|
327
|
+
configOptions: this.#buildConfigOptions(record.session),
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
return {};
|
|
331
|
+
}
|
|
332
|
+
|
|
276
333
|
async prompt(params: PromptRequest): Promise<PromptResponse> {
|
|
277
|
-
this.#
|
|
278
|
-
if (
|
|
334
|
+
const record = this.#getSessionRecord(params.sessionId);
|
|
335
|
+
if (record.promptTurn && !record.promptTurn.settled) {
|
|
279
336
|
throw new Error("ACP prompt already in progress for this session");
|
|
280
337
|
}
|
|
281
338
|
|
|
282
339
|
const converted = this.#convertPromptBlocks(params.prompt);
|
|
283
340
|
const pendingPrompt = Promise.withResolvers<PromptResponse>();
|
|
284
|
-
|
|
285
|
-
|
|
341
|
+
record.promptTurn = {
|
|
342
|
+
userMessageId: params.messageId ?? crypto.randomUUID(),
|
|
286
343
|
cancelRequested: false,
|
|
287
344
|
settled: false,
|
|
345
|
+
usageBaseline: this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
|
|
288
346
|
unsubscribe: undefined,
|
|
289
347
|
resolve: pendingPrompt.resolve,
|
|
290
348
|
reject: pendingPrompt.reject,
|
|
291
349
|
};
|
|
292
350
|
|
|
293
|
-
|
|
294
|
-
void this.#handlePromptEvent(event);
|
|
351
|
+
record.promptTurn.unsubscribe = record.session.subscribe(event => {
|
|
352
|
+
void this.#handlePromptEvent(record, event);
|
|
295
353
|
});
|
|
296
354
|
|
|
297
|
-
|
|
298
|
-
this.#finishPrompt(undefined, error);
|
|
355
|
+
record.session.prompt(converted.text, { images: converted.images }).catch((error: unknown) => {
|
|
356
|
+
this.#finishPrompt(record, undefined, error);
|
|
299
357
|
});
|
|
300
358
|
|
|
301
359
|
return await pendingPrompt.promise;
|
|
302
360
|
}
|
|
303
361
|
|
|
304
362
|
async cancel(params: { sessionId: string }): Promise<void> {
|
|
305
|
-
this.#
|
|
306
|
-
const promptTurn =
|
|
363
|
+
const record = this.#getSessionRecord(params.sessionId);
|
|
364
|
+
const promptTurn = record.promptTurn;
|
|
307
365
|
if (!promptTurn || promptTurn.settled) {
|
|
308
366
|
return;
|
|
309
367
|
}
|
|
310
368
|
promptTurn.cancelRequested = true;
|
|
311
369
|
try {
|
|
312
|
-
await
|
|
313
|
-
this.#finishPrompt({
|
|
370
|
+
await record.session.abort();
|
|
371
|
+
this.#finishPrompt(record, {
|
|
314
372
|
stopReason: "cancelled",
|
|
315
|
-
|
|
373
|
+
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
374
|
+
userMessageId: promptTurn.userMessageId,
|
|
316
375
|
});
|
|
317
376
|
} catch (error: unknown) {
|
|
318
|
-
this.#finishPrompt(undefined, error);
|
|
377
|
+
this.#finishPrompt(record, undefined, error);
|
|
319
378
|
}
|
|
320
379
|
}
|
|
321
380
|
|
|
@@ -333,37 +392,203 @@ export class AcpAgent implements Agent {
|
|
|
333
392
|
return this.#connection.closed;
|
|
334
393
|
}
|
|
335
394
|
|
|
336
|
-
|
|
337
|
-
|
|
395
|
+
#registerConnectionCleanup(): void {
|
|
396
|
+
if (this.#cleanupRegistered) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
this.#cleanupRegistered = true;
|
|
400
|
+
this.#connection.signal.addEventListener(
|
|
401
|
+
"abort",
|
|
402
|
+
() => {
|
|
403
|
+
void this.#disposeAllSessions();
|
|
404
|
+
},
|
|
405
|
+
{ once: true },
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async #createNewSessionRecord(cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
|
|
410
|
+
const session = await this.#createSession(path.resolve(cwd));
|
|
411
|
+
try {
|
|
412
|
+
await session.sessionManager.ensureOnDisk();
|
|
413
|
+
} catch (error) {
|
|
414
|
+
await this.#disposeStandaloneSession(session);
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
return await this.#registerPreparedSession(session, mcpServers);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async #loadManagedSession(sessionId: string, cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
|
|
421
|
+
const existing = this.#sessions.get(sessionId);
|
|
422
|
+
if (existing) {
|
|
423
|
+
this.#assertMatchingCwd(existing.session, cwd);
|
|
424
|
+
await this.#configureMcpServers(existing, mcpServers);
|
|
425
|
+
return existing;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const storedSession = await this.#findStoredSession(sessionId, cwd);
|
|
429
|
+
if (!storedSession) {
|
|
430
|
+
throw new Error(`ACP session not found: ${sessionId}`);
|
|
431
|
+
}
|
|
432
|
+
return await this.#openStoredSession(storedSession.path, cwd, mcpServers, sessionId);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async #resumeManagedSession(sessionId: string, cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
|
|
436
|
+
const existing = this.#sessions.get(sessionId);
|
|
437
|
+
if (existing) {
|
|
438
|
+
this.#assertMatchingCwd(existing.session, cwd);
|
|
439
|
+
await this.#configureMcpServers(existing, mcpServers);
|
|
440
|
+
return existing;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const storedSession = await this.#findStoredSession(sessionId, cwd);
|
|
444
|
+
if (!storedSession) {
|
|
445
|
+
throw new Error(`ACP session not found: ${sessionId}`);
|
|
446
|
+
}
|
|
447
|
+
return await this.#openStoredSession(storedSession.path, cwd, mcpServers, sessionId);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async #forkManagedSession(params: ForkSessionRequest): Promise<ManagedSessionRecord> {
|
|
451
|
+
const sourcePath = await this.#resolveForkSourceSessionPath(params.sessionId);
|
|
452
|
+
const session = await this.#createSession(path.resolve(params.cwd));
|
|
453
|
+
try {
|
|
454
|
+
const success = await session.switchSession(sourcePath);
|
|
455
|
+
if (!success) {
|
|
456
|
+
throw new Error(`ACP session fork was cancelled: ${params.sessionId}`);
|
|
457
|
+
}
|
|
458
|
+
const forked = await session.fork();
|
|
459
|
+
if (!forked) {
|
|
460
|
+
throw new Error(`ACP session fork failed: ${params.sessionId}`);
|
|
461
|
+
}
|
|
462
|
+
} catch (error) {
|
|
463
|
+
await this.#disposeStandaloneSession(session);
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
466
|
+
return await this.#registerPreparedSession(session, params.mcpServers ?? []);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async #openStoredSession(
|
|
470
|
+
sessionPath: string,
|
|
471
|
+
cwd: string,
|
|
472
|
+
mcpServers: McpServer[],
|
|
473
|
+
sessionId: string,
|
|
474
|
+
): Promise<ManagedSessionRecord> {
|
|
475
|
+
const session = await this.#createSession(path.resolve(cwd));
|
|
476
|
+
try {
|
|
477
|
+
const success = await session.switchSession(sessionPath);
|
|
478
|
+
if (!success) {
|
|
479
|
+
throw new Error(`ACP session load was cancelled: ${sessionId}`);
|
|
480
|
+
}
|
|
481
|
+
} catch (error) {
|
|
482
|
+
await this.#disposeStandaloneSession(session);
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
return await this.#registerPreparedSession(session, mcpServers);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async #registerPreparedSession(session: AgentSession, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
|
|
489
|
+
const record = this.#createManagedSessionRecord(session);
|
|
490
|
+
try {
|
|
491
|
+
await this.#configureExtensions(record);
|
|
492
|
+
await this.#configureMcpServers(record, mcpServers);
|
|
493
|
+
this.#sessions.set(session.sessionId, record);
|
|
494
|
+
return record;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
await this.#disposeSessionRecord(record);
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
#createManagedSessionRecord(session: AgentSession): ManagedSessionRecord {
|
|
502
|
+
return {
|
|
503
|
+
session,
|
|
504
|
+
mcpManager: undefined,
|
|
505
|
+
promptTurn: undefined,
|
|
506
|
+
liveMessageIds: new WeakMap<object, string>(),
|
|
507
|
+
extensionsConfigured: false,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
#getSessionRecord(sessionId: string): ManagedSessionRecord {
|
|
512
|
+
const record = this.#sessions.get(sessionId);
|
|
513
|
+
if (!record) {
|
|
514
|
+
throw new Error(`Unsupported ACP session: ${sessionId}`);
|
|
515
|
+
}
|
|
516
|
+
return record;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#assertMatchingCwd(session: AgentSession, cwd: string): void {
|
|
520
|
+
const expected = path.resolve(cwd);
|
|
521
|
+
const actual = path.resolve(session.sessionManager.getCwd());
|
|
522
|
+
if (actual !== expected) {
|
|
523
|
+
throw new Error(`ACP session ${session.sessionId} is already loaded for ${actual}, not ${expected}`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async #resolveForkSourceSessionPath(sessionId: string): Promise<string> {
|
|
528
|
+
const loaded = this.#sessions.get(sessionId);
|
|
529
|
+
if (loaded) {
|
|
530
|
+
const promptTurn = loaded.promptTurn;
|
|
531
|
+
if (promptTurn && !promptTurn.settled) {
|
|
532
|
+
throw new Error(`ACP session fork is unavailable while a prompt is in progress: ${sessionId}`);
|
|
533
|
+
}
|
|
534
|
+
await loaded.session.sessionManager.flush();
|
|
535
|
+
const sessionPath = loaded.session.sessionManager.getSessionFile();
|
|
536
|
+
if (!sessionPath) {
|
|
537
|
+
throw new Error(`ACP session cannot be forked before it is persisted: ${sessionId}`);
|
|
538
|
+
}
|
|
539
|
+
return sessionPath;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const storedSession = await this.#findStoredSessionById(sessionId);
|
|
543
|
+
if (!storedSession) {
|
|
544
|
+
throw new Error(`ACP session not found: ${sessionId}`);
|
|
545
|
+
}
|
|
546
|
+
return storedSession.path;
|
|
338
547
|
}
|
|
339
548
|
|
|
340
|
-
async #handlePromptEvent(event: AgentSessionEvent): Promise<void> {
|
|
341
|
-
const promptTurn =
|
|
549
|
+
async #handlePromptEvent(record: ManagedSessionRecord, event: AgentSessionEvent): Promise<void> {
|
|
550
|
+
const promptTurn = record.promptTurn;
|
|
342
551
|
if (!promptTurn || promptTurn.settled) {
|
|
343
552
|
return;
|
|
344
553
|
}
|
|
345
554
|
|
|
346
|
-
for (const notification of mapAgentSessionEventToAcpSessionUpdates(event,
|
|
555
|
+
for (const notification of mapAgentSessionEventToAcpSessionUpdates(event, record.session.sessionId, {
|
|
556
|
+
getMessageId: message => this.#getLiveMessageId(record, message),
|
|
557
|
+
})) {
|
|
347
558
|
await this.#connection.sessionUpdate(notification);
|
|
348
559
|
}
|
|
349
560
|
|
|
350
561
|
if (event.type === "agent_end") {
|
|
351
|
-
await this.#emitEndOfTurnUpdates();
|
|
352
|
-
this.#finishPrompt({
|
|
562
|
+
await this.#emitEndOfTurnUpdates(record);
|
|
563
|
+
this.#finishPrompt(record, {
|
|
353
564
|
stopReason: promptTurn.cancelRequested ? "cancelled" : "end_turn",
|
|
354
|
-
|
|
565
|
+
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
566
|
+
userMessageId: promptTurn.userMessageId,
|
|
355
567
|
});
|
|
356
568
|
}
|
|
357
569
|
}
|
|
358
570
|
|
|
359
|
-
#
|
|
360
|
-
|
|
571
|
+
#getLiveMessageId(record: ManagedSessionRecord, message: unknown): string | undefined {
|
|
572
|
+
if (typeof message !== "object" || message === null) {
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
const existing = record.liveMessageIds.get(message);
|
|
576
|
+
if (existing) {
|
|
577
|
+
return existing;
|
|
578
|
+
}
|
|
579
|
+
const nextMessageId = crypto.randomUUID();
|
|
580
|
+
record.liveMessageIds.set(message, nextMessageId);
|
|
581
|
+
return nextMessageId;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
#finishPrompt(record: ManagedSessionRecord, response?: PromptResponse, error?: unknown): void {
|
|
585
|
+
const promptTurn = record.promptTurn;
|
|
361
586
|
if (!promptTurn || promptTurn.settled) {
|
|
362
587
|
return;
|
|
363
588
|
}
|
|
364
589
|
promptTurn.settled = true;
|
|
365
590
|
promptTurn.unsubscribe?.();
|
|
366
|
-
|
|
591
|
+
record.promptTurn = undefined;
|
|
367
592
|
if (error !== undefined) {
|
|
368
593
|
promptTurn.reject(error);
|
|
369
594
|
return;
|
|
@@ -371,12 +596,6 @@ export class AcpAgent implements Agent {
|
|
|
371
596
|
promptTurn.resolve(response ?? { stopReason: "end_turn" });
|
|
372
597
|
}
|
|
373
598
|
|
|
374
|
-
#assertSameSession(sessionId: string): void {
|
|
375
|
-
if (sessionId !== this.#sessionId) {
|
|
376
|
-
throw new Error(`Unsupported ACP session: ${sessionId}`);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
599
|
#assertAbsoluteCwd(cwd: string): void {
|
|
381
600
|
if (!path.isAbsolute(cwd)) {
|
|
382
601
|
throw new Error(`ACP cwd must be absolute: ${cwd}`);
|
|
@@ -415,7 +634,7 @@ export class AcpAgent implements Agent {
|
|
|
415
634
|
};
|
|
416
635
|
}
|
|
417
636
|
|
|
418
|
-
#buildConfigOptions(): SessionConfigOption[] {
|
|
637
|
+
#buildConfigOptions(session: AgentSession): SessionConfigOption[] {
|
|
419
638
|
const configOptions: SessionConfigOption[] = [
|
|
420
639
|
{
|
|
421
640
|
id: MODE_CONFIG_ID,
|
|
@@ -427,8 +646,8 @@ export class AcpAgent implements Agent {
|
|
|
427
646
|
},
|
|
428
647
|
];
|
|
429
648
|
|
|
430
|
-
const models =
|
|
431
|
-
const currentModel =
|
|
649
|
+
const models = session.getAvailableModels();
|
|
650
|
+
const currentModel = session.model;
|
|
432
651
|
if (models.length > 0) {
|
|
433
652
|
configOptions.push({
|
|
434
653
|
id: MODEL_CONFIG_ID,
|
|
@@ -449,16 +668,38 @@ export class AcpAgent implements Agent {
|
|
|
449
668
|
name: "Thinking",
|
|
450
669
|
category: "thought_level",
|
|
451
670
|
type: "select",
|
|
452
|
-
currentValue: this.#toThinkingConfigValue(
|
|
453
|
-
options: this.#buildThinkingOptions(),
|
|
671
|
+
currentValue: this.#toThinkingConfigValue(session.thinkingLevel),
|
|
672
|
+
options: this.#buildThinkingOptions(session),
|
|
454
673
|
});
|
|
455
674
|
return configOptions;
|
|
456
675
|
}
|
|
457
676
|
|
|
458
|
-
#
|
|
677
|
+
#buildModelState(session: AgentSession): SessionModelState | undefined {
|
|
678
|
+
const models = session.getAvailableModels();
|
|
679
|
+
if (models.length === 0) {
|
|
680
|
+
return undefined;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const availableModels = models.map(model => ({
|
|
684
|
+
modelId: this.#toModelId(model),
|
|
685
|
+
name: model.name,
|
|
686
|
+
description: `${model.provider}/${model.id}`,
|
|
687
|
+
}));
|
|
688
|
+
const currentModelId = session.model ? this.#toModelId(session.model) : availableModels[0]?.modelId;
|
|
689
|
+
if (!currentModelId) {
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return {
|
|
694
|
+
availableModels,
|
|
695
|
+
currentModelId,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
#buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> {
|
|
459
700
|
return [
|
|
460
701
|
{ value: THINKING_OFF, name: "Off" },
|
|
461
|
-
...
|
|
702
|
+
...session.getAvailableThinkingLevels().map(level => ({
|
|
462
703
|
value: level,
|
|
463
704
|
name: level,
|
|
464
705
|
})),
|
|
@@ -469,20 +710,20 @@ export class AcpAgent implements Agent {
|
|
|
469
710
|
return value && value !== "inherit" ? value : THINKING_OFF;
|
|
470
711
|
}
|
|
471
712
|
|
|
472
|
-
async #setModelById(modelId: string): Promise<void> {
|
|
473
|
-
const model =
|
|
713
|
+
async #setModelById(session: AgentSession, modelId: string): Promise<void> {
|
|
714
|
+
const model = session.getAvailableModels().find(candidate => this.#toModelId(candidate) === modelId);
|
|
474
715
|
if (!model) {
|
|
475
716
|
throw new Error(`Unknown ACP model: ${modelId}`);
|
|
476
717
|
}
|
|
477
|
-
await
|
|
718
|
+
await session.setModel(model);
|
|
478
719
|
}
|
|
479
720
|
|
|
480
|
-
#setThinkingLevelById(value: string): void {
|
|
721
|
+
#setThinkingLevelById(session: AgentSession, value: string): void {
|
|
481
722
|
const thinkingLevel = parseThinkingLevel(value);
|
|
482
723
|
if (!thinkingLevel) {
|
|
483
724
|
throw new Error(`Unknown ACP thinking level: ${value}`);
|
|
484
725
|
}
|
|
485
|
-
|
|
726
|
+
session.setThinkingLevel(thinkingLevel);
|
|
486
727
|
}
|
|
487
728
|
|
|
488
729
|
#toModelId(model: Model): string {
|
|
@@ -503,7 +744,7 @@ export class AcpAgent implements Agent {
|
|
|
503
744
|
};
|
|
504
745
|
}
|
|
505
746
|
|
|
506
|
-
async #buildAvailableCommands(): Promise<AvailableCommand[]> {
|
|
747
|
+
async #buildAvailableCommands(session: AgentSession): Promise<AvailableCommand[]> {
|
|
507
748
|
const commands: AvailableCommand[] = [];
|
|
508
749
|
const seenNames = new Set<string>();
|
|
509
750
|
const appendCommand = (command: AvailableCommand): void => {
|
|
@@ -514,7 +755,7 @@ export class AcpAgent implements Agent {
|
|
|
514
755
|
commands.push(command);
|
|
515
756
|
};
|
|
516
757
|
|
|
517
|
-
for (const command of
|
|
758
|
+
for (const command of session.customCommands) {
|
|
518
759
|
appendCommand({
|
|
519
760
|
name: command.command.name,
|
|
520
761
|
description: command.command.description,
|
|
@@ -522,7 +763,7 @@ export class AcpAgent implements Agent {
|
|
|
522
763
|
});
|
|
523
764
|
}
|
|
524
765
|
|
|
525
|
-
for (const command of await loadSlashCommands({ cwd:
|
|
766
|
+
for (const command of await loadSlashCommands({ cwd: session.sessionManager.getCwd() })) {
|
|
526
767
|
appendCommand({
|
|
527
768
|
name: command.name,
|
|
528
769
|
description: command.description,
|
|
@@ -543,41 +784,44 @@ export class AcpAgent implements Agent {
|
|
|
543
784
|
|
|
544
785
|
#scheduleBootstrapUpdates(sessionId: string): void {
|
|
545
786
|
setTimeout(() => {
|
|
546
|
-
if (
|
|
787
|
+
if (this.#connection.signal.aborted) {
|
|
547
788
|
return;
|
|
548
789
|
}
|
|
549
|
-
|
|
790
|
+
const record = this.#sessions.get(sessionId);
|
|
791
|
+
if (!record) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
void this.#emitBootstrapUpdates(sessionId, record);
|
|
550
795
|
}, 0);
|
|
551
796
|
}
|
|
552
797
|
|
|
553
|
-
async #emitBootstrapUpdates(sessionId: string): Promise<void> {
|
|
554
|
-
if (sessionId !==
|
|
798
|
+
async #emitBootstrapUpdates(sessionId: string, record: ManagedSessionRecord): Promise<void> {
|
|
799
|
+
if (this.#sessions.get(sessionId) !== record) {
|
|
555
800
|
return;
|
|
556
801
|
}
|
|
557
802
|
await this.#connection.sessionUpdate({
|
|
558
803
|
sessionId,
|
|
559
804
|
update: {
|
|
560
805
|
sessionUpdate: "available_commands_update",
|
|
561
|
-
availableCommands: await this.#buildAvailableCommands(),
|
|
806
|
+
availableCommands: await this.#buildAvailableCommands(record.session),
|
|
562
807
|
},
|
|
563
808
|
});
|
|
564
809
|
await this.#connection.sessionUpdate({
|
|
565
810
|
sessionId,
|
|
566
811
|
update: {
|
|
567
812
|
sessionUpdate: "session_info_update",
|
|
568
|
-
title:
|
|
569
|
-
updatedAt:
|
|
813
|
+
title: record.session.sessionName,
|
|
814
|
+
updatedAt: record.session.sessionManager.getHeader()?.timestamp,
|
|
570
815
|
},
|
|
571
816
|
});
|
|
572
817
|
}
|
|
573
818
|
|
|
574
|
-
async #emitEndOfTurnUpdates(): Promise<void> {
|
|
575
|
-
const sessionId =
|
|
819
|
+
async #emitEndOfTurnUpdates(record: ManagedSessionRecord): Promise<void> {
|
|
820
|
+
const sessionId = record.session.sessionId;
|
|
576
821
|
|
|
577
|
-
|
|
578
|
-
const contextUsage = this.#session.getContextUsage();
|
|
822
|
+
const contextUsage = record.session.getContextUsage();
|
|
579
823
|
if (contextUsage) {
|
|
580
|
-
const usageStats =
|
|
824
|
+
const usageStats = record.session.sessionManager.getUsageStatistics();
|
|
581
825
|
await this.#connection.sessionUpdate({
|
|
582
826
|
sessionId,
|
|
583
827
|
update: {
|
|
@@ -589,17 +833,52 @@ export class AcpAgent implements Agent {
|
|
|
589
833
|
});
|
|
590
834
|
}
|
|
591
835
|
|
|
592
|
-
// Push latest session title
|
|
593
836
|
await this.#connection.sessionUpdate({
|
|
594
837
|
sessionId,
|
|
595
838
|
update: {
|
|
596
839
|
sessionUpdate: "session_info_update",
|
|
597
|
-
title:
|
|
840
|
+
title: record.session.sessionName,
|
|
598
841
|
updatedAt: new Date().toISOString(),
|
|
599
842
|
},
|
|
600
843
|
});
|
|
601
844
|
}
|
|
602
845
|
|
|
846
|
+
#cloneUsageStatistics(usage: UsageStatistics): UsageStatistics {
|
|
847
|
+
return {
|
|
848
|
+
input: usage.input,
|
|
849
|
+
output: usage.output,
|
|
850
|
+
cacheRead: usage.cacheRead,
|
|
851
|
+
cacheWrite: usage.cacheWrite,
|
|
852
|
+
premiumRequests: usage.premiumRequests,
|
|
853
|
+
cost: usage.cost,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
#buildTurnUsage(previous: UsageStatistics, current: UsageStatistics): Usage | undefined {
|
|
858
|
+
const inputTokens = Math.max(0, current.input - previous.input);
|
|
859
|
+
const outputTokens = Math.max(0, current.output - previous.output);
|
|
860
|
+
const cachedReadTokens = Math.max(0, current.cacheRead - previous.cacheRead);
|
|
861
|
+
const cachedWriteTokens = Math.max(0, current.cacheWrite - previous.cacheWrite);
|
|
862
|
+
const totalTokens = inputTokens + outputTokens + cachedReadTokens + cachedWriteTokens;
|
|
863
|
+
|
|
864
|
+
if (totalTokens === 0) {
|
|
865
|
+
return undefined;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const usage: Usage = {
|
|
869
|
+
inputTokens,
|
|
870
|
+
outputTokens,
|
|
871
|
+
totalTokens,
|
|
872
|
+
};
|
|
873
|
+
if (cachedReadTokens > 0) {
|
|
874
|
+
usage.cachedReadTokens = cachedReadTokens;
|
|
875
|
+
}
|
|
876
|
+
if (cachedWriteTokens > 0) {
|
|
877
|
+
usage.cachedWriteTokens = cachedWriteTokens;
|
|
878
|
+
}
|
|
879
|
+
return usage;
|
|
880
|
+
}
|
|
881
|
+
|
|
603
882
|
async #listStoredSessions(cwd?: string): Promise<StoredSessionInfo[]> {
|
|
604
883
|
const sessions = cwd ? await SessionManager.list(cwd) : await SessionManager.listAll();
|
|
605
884
|
return sessions.sort((left, right) => right.modified.getTime() - left.modified.getTime());
|
|
@@ -610,6 +889,11 @@ export class AcpAgent implements Agent {
|
|
|
610
889
|
return sessions.find(session => session.id === sessionId);
|
|
611
890
|
}
|
|
612
891
|
|
|
892
|
+
async #findStoredSessionById(sessionId: string): Promise<StoredSessionInfo | undefined> {
|
|
893
|
+
const sessions = await this.#listStoredSessions();
|
|
894
|
+
return sessions.find(session => session.id === sessionId);
|
|
895
|
+
}
|
|
896
|
+
|
|
613
897
|
#parseCursor(cursor: string | undefined): number {
|
|
614
898
|
if (!cursor) {
|
|
615
899
|
return 0;
|
|
@@ -621,17 +905,17 @@ export class AcpAgent implements Agent {
|
|
|
621
905
|
return parsed;
|
|
622
906
|
}
|
|
623
907
|
|
|
624
|
-
async #replaySessionHistory(): Promise<void> {
|
|
625
|
-
for (const message of
|
|
626
|
-
for (const notification of this.#messageToReplayNotifications(message)) {
|
|
908
|
+
async #replaySessionHistory(record: ManagedSessionRecord): Promise<void> {
|
|
909
|
+
for (const message of record.session.sessionManager.buildSessionContext().messages as ReplayableMessage[]) {
|
|
910
|
+
for (const notification of this.#messageToReplayNotifications(record.session.sessionId, message)) {
|
|
627
911
|
await this.#connection.sessionUpdate(notification);
|
|
628
912
|
}
|
|
629
913
|
}
|
|
630
914
|
}
|
|
631
915
|
|
|
632
|
-
#messageToReplayNotifications(message: ReplayableMessage): SessionNotification[] {
|
|
916
|
+
#messageToReplayNotifications(sessionId: string, message: ReplayableMessage): SessionNotification[] {
|
|
633
917
|
if (message.role === "assistant") {
|
|
634
|
-
return this.#replayAssistantMessage(message);
|
|
918
|
+
return this.#replayAssistantMessage(sessionId, message);
|
|
635
919
|
}
|
|
636
920
|
if (
|
|
637
921
|
message.role === "user" ||
|
|
@@ -639,28 +923,42 @@ export class AcpAgent implements Agent {
|
|
|
639
923
|
message.role === "custom" ||
|
|
640
924
|
message.role === "hookMessage"
|
|
641
925
|
) {
|
|
642
|
-
return this.#wrapReplayContent(
|
|
926
|
+
return this.#wrapReplayContent(
|
|
927
|
+
sessionId,
|
|
928
|
+
this.#extractReplayContent(message.content, undefined),
|
|
929
|
+
"user_message_chunk",
|
|
930
|
+
crypto.randomUUID(),
|
|
931
|
+
);
|
|
643
932
|
}
|
|
644
933
|
if (
|
|
645
934
|
message.role === "toolResult" &&
|
|
646
935
|
typeof message.toolCallId === "string" &&
|
|
647
936
|
typeof message.toolName === "string"
|
|
648
937
|
) {
|
|
649
|
-
return this.#replayToolResult(
|
|
938
|
+
return this.#replayToolResult(sessionId, {
|
|
939
|
+
...message,
|
|
940
|
+
toolCallId: message.toolCallId,
|
|
941
|
+
toolName: message.toolName,
|
|
942
|
+
});
|
|
650
943
|
}
|
|
651
944
|
if (
|
|
652
945
|
message.role === "bashExecution" ||
|
|
653
946
|
message.role === "pythonExecution" ||
|
|
654
947
|
message.role === "compactionSummary"
|
|
655
948
|
) {
|
|
656
|
-
return this.#wrapReplayContent(
|
|
949
|
+
return this.#wrapReplayContent(
|
|
950
|
+
sessionId,
|
|
951
|
+
this.#extractReplayContent(message.content, undefined),
|
|
952
|
+
"user_message_chunk",
|
|
953
|
+
crypto.randomUUID(),
|
|
954
|
+
);
|
|
657
955
|
}
|
|
658
956
|
return [];
|
|
659
957
|
}
|
|
660
958
|
|
|
661
|
-
#replayAssistantMessage(message: ReplayableMessage): SessionNotification[] {
|
|
959
|
+
#replayAssistantMessage(sessionId: string, message: ReplayableMessage): SessionNotification[] {
|
|
662
960
|
const notifications: SessionNotification[] = [];
|
|
663
|
-
const
|
|
961
|
+
const messageId = crypto.randomUUID();
|
|
664
962
|
if (Array.isArray(message.content)) {
|
|
665
963
|
for (const item of message.content) {
|
|
666
964
|
if (typeof item !== "object" || item === null || !("type" in item)) {
|
|
@@ -669,7 +967,11 @@ export class AcpAgent implements Agent {
|
|
|
669
967
|
if (item.type === "text" && "text" in item && typeof item.text === "string" && item.text.length > 0) {
|
|
670
968
|
notifications.push({
|
|
671
969
|
sessionId,
|
|
672
|
-
update: {
|
|
970
|
+
update: {
|
|
971
|
+
sessionUpdate: "agent_message_chunk",
|
|
972
|
+
content: { type: "text", text: item.text },
|
|
973
|
+
messageId,
|
|
974
|
+
},
|
|
673
975
|
});
|
|
674
976
|
continue;
|
|
675
977
|
}
|
|
@@ -681,7 +983,11 @@ export class AcpAgent implements Agent {
|
|
|
681
983
|
) {
|
|
682
984
|
notifications.push({
|
|
683
985
|
sessionId,
|
|
684
|
-
update: {
|
|
986
|
+
update: {
|
|
987
|
+
sessionUpdate: "agent_thought_chunk",
|
|
988
|
+
content: { type: "text", text: item.thinking },
|
|
989
|
+
messageId,
|
|
990
|
+
},
|
|
685
991
|
});
|
|
686
992
|
continue;
|
|
687
993
|
}
|
|
@@ -709,13 +1015,18 @@ export class AcpAgent implements Agent {
|
|
|
709
1015
|
if (notifications.length === 0 && message.errorMessage) {
|
|
710
1016
|
notifications.push({
|
|
711
1017
|
sessionId,
|
|
712
|
-
update: {
|
|
1018
|
+
update: {
|
|
1019
|
+
sessionUpdate: "agent_message_chunk",
|
|
1020
|
+
content: { type: "text", text: message.errorMessage },
|
|
1021
|
+
messageId,
|
|
1022
|
+
},
|
|
713
1023
|
});
|
|
714
1024
|
}
|
|
715
1025
|
return notifications;
|
|
716
1026
|
}
|
|
717
1027
|
|
|
718
1028
|
#replayToolResult(
|
|
1029
|
+
sessionId: string,
|
|
719
1030
|
message: Required<Pick<ReplayableMessage, "toolCallId" | "toolName">> & ReplayableMessage,
|
|
720
1031
|
): SessionNotification[] {
|
|
721
1032
|
const args = this.#buildReplayToolArgs(message.details);
|
|
@@ -737,8 +1048,8 @@ export class AcpAgent implements Agent {
|
|
|
737
1048
|
},
|
|
738
1049
|
};
|
|
739
1050
|
return [
|
|
740
|
-
...mapAgentSessionEventToAcpSessionUpdates(startEvent,
|
|
741
|
-
...mapAgentSessionEventToAcpSessionUpdates(endEvent,
|
|
1051
|
+
...mapAgentSessionEventToAcpSessionUpdates(startEvent, sessionId),
|
|
1052
|
+
...mapAgentSessionEventToAcpSessionUpdates(endEvent, sessionId),
|
|
742
1053
|
];
|
|
743
1054
|
}
|
|
744
1055
|
|
|
@@ -751,14 +1062,17 @@ export class AcpAgent implements Agent {
|
|
|
751
1062
|
}
|
|
752
1063
|
|
|
753
1064
|
#wrapReplayContent(
|
|
1065
|
+
sessionId: string,
|
|
754
1066
|
content: PromptRequest["prompt"],
|
|
755
1067
|
kind: "agent_message_chunk" | "user_message_chunk",
|
|
1068
|
+
messageId: string,
|
|
756
1069
|
): SessionNotification[] {
|
|
757
1070
|
return content.map(block => ({
|
|
758
|
-
sessionId
|
|
1071
|
+
sessionId,
|
|
759
1072
|
update: {
|
|
760
1073
|
sessionUpdate: kind,
|
|
761
1074
|
content: block,
|
|
1075
|
+
messageId,
|
|
762
1076
|
},
|
|
763
1077
|
}));
|
|
764
1078
|
}
|
|
@@ -791,89 +1105,94 @@ export class AcpAgent implements Agent {
|
|
|
791
1105
|
return replay;
|
|
792
1106
|
}
|
|
793
1107
|
|
|
794
|
-
async #configureExtensions(): Promise<void> {
|
|
795
|
-
|
|
1108
|
+
async #configureExtensions(record: ManagedSessionRecord): Promise<void> {
|
|
1109
|
+
if (record.extensionsConfigured) {
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const extensionRunner = record.session.extensionRunner;
|
|
796
1114
|
if (!extensionRunner) {
|
|
1115
|
+
record.extensionsConfigured = true;
|
|
797
1116
|
return;
|
|
798
1117
|
}
|
|
799
1118
|
|
|
800
1119
|
extensionRunner.initialize(
|
|
801
1120
|
{
|
|
802
1121
|
sendMessage: (message, options) => {
|
|
803
|
-
|
|
1122
|
+
record.session.sendCustomMessage(message, options).catch((error: unknown) => {
|
|
804
1123
|
logger.warn("ACP extension sendMessage failed", { error });
|
|
805
1124
|
});
|
|
806
1125
|
},
|
|
807
1126
|
sendUserMessage: (content, options) => {
|
|
808
|
-
|
|
1127
|
+
record.session.sendUserMessage(content, options).catch((error: unknown) => {
|
|
809
1128
|
logger.warn("ACP extension sendUserMessage failed", { error });
|
|
810
1129
|
});
|
|
811
1130
|
},
|
|
812
1131
|
appendEntry: (customType, data) => {
|
|
813
|
-
|
|
1132
|
+
record.session.sessionManager.appendCustomEntry(customType, data);
|
|
814
1133
|
},
|
|
815
1134
|
setLabel: (targetId, label) => {
|
|
816
|
-
|
|
1135
|
+
record.session.sessionManager.appendLabelChange(targetId, label);
|
|
817
1136
|
},
|
|
818
|
-
getActiveTools: () =>
|
|
819
|
-
getAllTools: () =>
|
|
820
|
-
setActiveTools: toolNames =>
|
|
1137
|
+
getActiveTools: () => record.session.getActiveToolNames(),
|
|
1138
|
+
getAllTools: () => record.session.getAllToolNames(),
|
|
1139
|
+
setActiveTools: toolNames => record.session.setActiveToolsByName(toolNames),
|
|
821
1140
|
getCommands: () => [],
|
|
822
1141
|
setModel: async model => {
|
|
823
|
-
const apiKey = await
|
|
1142
|
+
const apiKey = await record.session.modelRegistry.getApiKey(model);
|
|
824
1143
|
if (!apiKey) {
|
|
825
1144
|
return false;
|
|
826
1145
|
}
|
|
827
|
-
await
|
|
1146
|
+
await record.session.setModel(model);
|
|
828
1147
|
return true;
|
|
829
1148
|
},
|
|
830
|
-
getThinkingLevel: () =>
|
|
831
|
-
setThinkingLevel: level =>
|
|
1149
|
+
getThinkingLevel: () => record.session.thinkingLevel,
|
|
1150
|
+
setThinkingLevel: level => record.session.setThinkingLevel(level),
|
|
832
1151
|
},
|
|
833
1152
|
{
|
|
834
|
-
getModel: () =>
|
|
835
|
-
getSearchDb: () =>
|
|
836
|
-
isIdle: () => !
|
|
1153
|
+
getModel: () => record.session.model,
|
|
1154
|
+
getSearchDb: () => record.session.searchDb,
|
|
1155
|
+
isIdle: () => !record.session.isStreaming,
|
|
837
1156
|
abort: () => {
|
|
838
|
-
void
|
|
1157
|
+
void record.session.abort();
|
|
839
1158
|
},
|
|
840
|
-
hasPendingMessages: () =>
|
|
1159
|
+
hasPendingMessages: () => record.session.queuedMessageCount > 0,
|
|
841
1160
|
shutdown: () => {},
|
|
842
|
-
getContextUsage: () =>
|
|
843
|
-
getSystemPrompt: () =>
|
|
1161
|
+
getContextUsage: () => record.session.getContextUsage(),
|
|
1162
|
+
getSystemPrompt: () => record.session.systemPrompt,
|
|
844
1163
|
compact: async instructionsOrOptions => {
|
|
845
1164
|
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
846
1165
|
const options =
|
|
847
1166
|
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
848
1167
|
? instructionsOrOptions
|
|
849
1168
|
: undefined;
|
|
850
|
-
await
|
|
1169
|
+
await record.session.compact(instructions, options);
|
|
851
1170
|
},
|
|
852
1171
|
},
|
|
853
1172
|
{
|
|
854
|
-
getContextUsage: () =>
|
|
855
|
-
waitForIdle: () =>
|
|
1173
|
+
getContextUsage: () => record.session.getContextUsage(),
|
|
1174
|
+
waitForIdle: () => record.session.agent.waitForIdle(),
|
|
856
1175
|
newSession: async options => {
|
|
857
|
-
const success = await
|
|
1176
|
+
const success = await record.session.newSession({ parentSession: options?.parentSession });
|
|
858
1177
|
if (success && options?.setup) {
|
|
859
|
-
await options.setup(
|
|
1178
|
+
await options.setup(record.session.sessionManager);
|
|
860
1179
|
}
|
|
861
1180
|
return { cancelled: !success };
|
|
862
1181
|
},
|
|
863
1182
|
branch: async entryId => {
|
|
864
|
-
const result = await
|
|
1183
|
+
const result = await record.session.branch(entryId);
|
|
865
1184
|
return { cancelled: result.cancelled };
|
|
866
1185
|
},
|
|
867
1186
|
navigateTree: async (targetId, options) => {
|
|
868
|
-
const result = await
|
|
1187
|
+
const result = await record.session.navigateTree(targetId, { summarize: options?.summarize });
|
|
869
1188
|
return { cancelled: result.cancelled };
|
|
870
1189
|
},
|
|
871
1190
|
switchSession: async sessionPath => {
|
|
872
|
-
const success = await
|
|
1191
|
+
const success = await record.session.switchSession(sessionPath);
|
|
873
1192
|
return { cancelled: !success };
|
|
874
1193
|
},
|
|
875
1194
|
reload: async () => {
|
|
876
|
-
await
|
|
1195
|
+
await record.session.reload();
|
|
877
1196
|
},
|
|
878
1197
|
compact: async instructionsOrOptions => {
|
|
879
1198
|
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
@@ -881,25 +1200,26 @@ export class AcpAgent implements Agent {
|
|
|
881
1200
|
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
882
1201
|
? instructionsOrOptions
|
|
883
1202
|
: undefined;
|
|
884
|
-
await
|
|
1203
|
+
await record.session.compact(instructions, options);
|
|
885
1204
|
},
|
|
886
1205
|
},
|
|
887
1206
|
acpExtensionUiContext,
|
|
888
1207
|
);
|
|
889
1208
|
await extensionRunner.emit({ type: "session_start" });
|
|
1209
|
+
record.extensionsConfigured = true;
|
|
890
1210
|
}
|
|
891
1211
|
|
|
892
|
-
async #configureMcpServers(servers: McpServer[]): Promise<void> {
|
|
893
|
-
if (
|
|
894
|
-
await
|
|
1212
|
+
async #configureMcpServers(record: ManagedSessionRecord, servers: McpServer[]): Promise<void> {
|
|
1213
|
+
if (record.mcpManager) {
|
|
1214
|
+
await record.mcpManager.disconnectAll();
|
|
895
1215
|
}
|
|
896
1216
|
if (servers.length === 0) {
|
|
897
|
-
|
|
898
|
-
await
|
|
1217
|
+
record.mcpManager = undefined;
|
|
1218
|
+
await record.session.refreshMCPTools([]);
|
|
899
1219
|
return;
|
|
900
1220
|
}
|
|
901
1221
|
|
|
902
|
-
const manager = new MCPManager(
|
|
1222
|
+
const manager = new MCPManager(record.session.sessionManager.getCwd());
|
|
903
1223
|
const configs: MCPConfigMap = {};
|
|
904
1224
|
const sources: MCPSourceMap = {};
|
|
905
1225
|
for (const server of servers) {
|
|
@@ -921,8 +1241,8 @@ export class AcpAgent implements Agent {
|
|
|
921
1241
|
);
|
|
922
1242
|
}
|
|
923
1243
|
|
|
924
|
-
|
|
925
|
-
await
|
|
1244
|
+
record.mcpManager = manager;
|
|
1245
|
+
await record.session.refreshMCPTools(result.tools);
|
|
926
1246
|
}
|
|
927
1247
|
|
|
928
1248
|
#toMcpConfig(server: McpServer): MCPServerConfig {
|
|
@@ -955,4 +1275,84 @@ export class AcpAgent implements Agent {
|
|
|
955
1275
|
}
|
|
956
1276
|
return mapped;
|
|
957
1277
|
}
|
|
1278
|
+
|
|
1279
|
+
async #closeManagedSession(sessionId: string, record: ManagedSessionRecord): Promise<void> {
|
|
1280
|
+
this.#sessions.delete(sessionId);
|
|
1281
|
+
await this.#cancelPromptForClose(record);
|
|
1282
|
+
await this.#disposeSessionRecord(record);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
async #cancelPromptForClose(record: ManagedSessionRecord): Promise<void> {
|
|
1286
|
+
const promptTurn = record.promptTurn;
|
|
1287
|
+
if (!promptTurn || promptTurn.settled) {
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
promptTurn.cancelRequested = true;
|
|
1292
|
+
promptTurn.unsubscribe?.();
|
|
1293
|
+
try {
|
|
1294
|
+
await record.session.abort();
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
logger.warn("Failed to abort ACP prompt during session close", { error });
|
|
1297
|
+
}
|
|
1298
|
+
this.#finishPrompt(record, {
|
|
1299
|
+
stopReason: "cancelled",
|
|
1300
|
+
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
1301
|
+
userMessageId: promptTurn.userMessageId,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
async #disposeSessionRecord(record: ManagedSessionRecord): Promise<void> {
|
|
1306
|
+
if (record.mcpManager) {
|
|
1307
|
+
try {
|
|
1308
|
+
await record.mcpManager.disconnectAll();
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
logger.warn("Failed to disconnect ACP MCP servers", { error });
|
|
1311
|
+
}
|
|
1312
|
+
record.mcpManager = undefined;
|
|
1313
|
+
}
|
|
1314
|
+
try {
|
|
1315
|
+
await record.session.dispose();
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
logger.warn("Failed to dispose ACP session", { error });
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
async #disposeStandaloneSession(session: AgentSession): Promise<void> {
|
|
1322
|
+
try {
|
|
1323
|
+
await session.dispose();
|
|
1324
|
+
} catch (error) {
|
|
1325
|
+
logger.warn("Failed to dispose ACP session", { error });
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
async #disposeAllSessions(): Promise<void> {
|
|
1330
|
+
if (this.#disposePromise) {
|
|
1331
|
+
await this.#disposePromise;
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
this.#disposePromise = (async () => {
|
|
1336
|
+
const records = Array.from(this.#sessions.entries());
|
|
1337
|
+
this.#sessions.clear();
|
|
1338
|
+
await Promise.all(
|
|
1339
|
+
records.map(async ([sessionId, record]) => {
|
|
1340
|
+
try {
|
|
1341
|
+
await this.#cancelPromptForClose(record);
|
|
1342
|
+
await this.#disposeSessionRecord(record);
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
logger.warn("Failed to clean up ACP session", { sessionId, error });
|
|
1345
|
+
}
|
|
1346
|
+
}),
|
|
1347
|
+
);
|
|
1348
|
+
|
|
1349
|
+
const initialSession = this.#initialSession;
|
|
1350
|
+
this.#initialSession = undefined;
|
|
1351
|
+
if (initialSession) {
|
|
1352
|
+
await this.#disposeStandaloneSession(initialSession);
|
|
1353
|
+
}
|
|
1354
|
+
})();
|
|
1355
|
+
|
|
1356
|
+
await this.#disposePromise;
|
|
1357
|
+
}
|
|
958
1358
|
}
|