acpx 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +14 -6
  2. package/dist/{cli-T-Z-9x6a.js → cli-Bf3yjqzE.js} +35 -10
  3. package/dist/cli-Bf3yjqzE.js.map +1 -0
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +724 -241
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-COPilhO_.d.ts → client-BssohYqM.d.ts} +35 -4
  9. package/dist/client-BssohYqM.d.ts.map +1 -0
  10. package/dist/flags-C-rwARqg.js +260 -0
  11. package/dist/flags-C-rwARqg.js.map +1 -0
  12. package/dist/{flows-CF8w1rPI.js → flows-WLs26_5Y.js} +405 -337
  13. package/dist/flows-WLs26_5Y.js.map +1 -0
  14. package/dist/flows.d.ts +23 -2
  15. package/dist/flows.d.ts.map +1 -1
  16. package/dist/flows.js +1 -1
  17. package/dist/{prompt-turn-CVPMWdj1.js → live-checkpoint-D5d-K9s1.js} +2487 -609
  18. package/dist/live-checkpoint-D5d-K9s1.js.map +1 -0
  19. package/dist/output-DPg20dvn.js +4146 -0
  20. package/dist/output-DPg20dvn.js.map +1 -0
  21. package/dist/runtime.d.ts +56 -4
  22. package/dist/runtime.d.ts.map +1 -1
  23. package/dist/runtime.js +676 -393
  24. package/dist/runtime.js.map +1 -1
  25. package/dist/{types-CVBeQyi3.d.ts → session-options-CFudjdkU.d.ts} +62 -3
  26. package/dist/session-options-CFudjdkU.d.ts.map +1 -0
  27. package/package.json +30 -25
  28. package/skills/acpx/SKILL.md +211 -13
  29. package/dist/cli-T-Z-9x6a.js.map +0 -1
  30. package/dist/client-COPilhO_.d.ts.map +0 -1
  31. package/dist/flags-Dj-IXgo9.js +0 -163
  32. package/dist/flags-Dj-IXgo9.js.map +0 -1
  33. package/dist/flows-CF8w1rPI.js.map +0 -1
  34. package/dist/ipc-ABXlXzGP.js +0 -1290
  35. package/dist/ipc-ABXlXzGP.js.map +0 -1
  36. package/dist/jsonrpc-DSxh2w5R.js +0 -68
  37. package/dist/jsonrpc-DSxh2w5R.js.map +0 -1
  38. package/dist/output-DmHvT8vm.js +0 -807
  39. package/dist/output-DmHvT8vm.js.map +0 -1
  40. package/dist/perf-metrics-C2pXfxvR.js +0 -598
  41. package/dist/perf-metrics-C2pXfxvR.js.map +0 -1
  42. package/dist/prompt-turn-CVPMWdj1.js.map +0 -1
  43. package/dist/render-N5YwotCy.js +0 -172
  44. package/dist/render-N5YwotCy.js.map +0 -1
  45. package/dist/rolldown-runtime-CiIaOW0V.js +0 -13
  46. package/dist/session-CDaQe6BH.js +0 -1538
  47. package/dist/session-CDaQe6BH.js.map +0 -1
  48. package/dist/session-options-pCbHn_n7.d.ts +0 -13
  49. package/dist/session-options-pCbHn_n7.d.ts.map +0 -1
  50. package/dist/types-CVBeQyi3.d.ts.map +0 -1
@@ -1,1538 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-CiIaOW0V.js";
2
- import { H as QueueConnectionError, b as isRetryablePromptError, c as startPerfTimer, g as textPrompt, h as promptToDisplayText, i as measurePerf, n as getPerfMetricsSnapshot, o as resetPerfMetrics, r as incrementPerfCounter, s as setPerfGauge, t as formatPerfMetric, u as normalizeRuntimeSessionId, v as formatErrorMessage, x as normalizeOutputError } from "./perf-metrics-C2pXfxvR.js";
3
- import { A as isoNow, B as sessionBaseDir, C as AcpClient, D as findGitRepositoryRoot, E as absolutePath, F as resolveSessionRecord, H as sessionEventLockPath, I as writeSessionRecord, J as withInterrupt, K as InterruptedError, M as listSessionsForAgent, N as normalizeName, O as findSession, P as pruneSessions, S as trimConversationForRuntime, U as sessionEventSegmentPath, V as sessionEventActivePath, Y as withTimeout, _ as cloneSessionConversation, a as connectAndLoadSession, b as recordPromptSubmission, d as setDesiredConfigOption, f as setDesiredModeId, g as cloneSessionAcpxState, h as applyConfigOptionsToRecord, i as sessionOptionsFromRecord, j as listSessions, k as findSessionByDirectoryWalk, l as assertRequestedModelSupported, m as syncAdvertisedModelState, n as withConnectedSession, o as applyConversation, p as setDesiredModelId, q as TimeoutError, r as mergeSessionOptions, s as applyLifecycleSnapshotToRecord, t as runPromptTurn, u as setCurrentModelId, v as createSessionConversation, x as recordSessionUpdate, y as recordClientOperation, z as defaultSessionEventLog } from "./prompt-turn-CVPMWdj1.js";
4
- import { n as isAcpJsonRpcMessage } from "./jsonrpc-DSxh2w5R.js";
5
- import { a as trySetModelOnRunningOwner, d as releaseQueueOwnerLease, f as terminateProcess, h as waitMs, i as trySetModeOnRunningOwner, l as isProcessAlive, m as tryAcquireQueueOwnerLease, n as tryCloseSessionOnRunningOwner, o as trySubmitToRunningOwner, p as terminateQueueOwnerForSession, r as trySetConfigOptionOnRunningOwner, s as SessionQueueOwner, t as tryCancelOnRunningOwner, u as refreshQueueOwnerLease } from "./ipc-ABXlXzGP.js";
6
- import fs, { realpathSync } from "node:fs";
7
- import path from "node:path";
8
- import fs$1 from "node:fs/promises";
9
- import { spawn } from "node:child_process";
10
- //#region src/cli/session/contracts.ts
11
- const DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
12
- function normalizeQueueOwnerTtlMs(ttlMs) {
13
- if (ttlMs == null) return DEFAULT_QUEUE_OWNER_TTL_MS;
14
- if (!Number.isFinite(ttlMs) || ttlMs < 0) return DEFAULT_QUEUE_OWNER_TTL_MS;
15
- return Math.round(ttlMs);
16
- }
17
- //#endregion
18
- //#region src/cli/session/model-helpers.ts
19
- async function applyRequestedModelIfAdvertised(params) {
20
- const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
21
- if (!requestedModel) return false;
22
- assertRequestedModelSupported({
23
- requestedModel,
24
- models: params.models,
25
- agentCommand: params.agentCommand,
26
- context: "apply"
27
- });
28
- if (!params.models) return false;
29
- if (params.models.currentModelId === requestedModel) return true;
30
- await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel), params.timeoutMs);
31
- return true;
32
- }
33
- //#endregion
34
- //#region src/cli/session/prompt-runner.ts
35
- function buildDirectConnectedSessionOptions(options, run) {
36
- return {
37
- sessionRecordId: options.sessionRecordId,
38
- loadRecord: resolveSessionRecord,
39
- saveRecord: writeSessionRecord,
40
- mcpServers: options.mcpServers,
41
- nonInteractivePermissions: options.nonInteractivePermissions,
42
- authCredentials: options.authCredentials,
43
- authPolicy: options.authPolicy,
44
- terminal: options.terminal,
45
- timeoutMs: options.timeoutMs,
46
- verbose: options.verbose,
47
- onClientAvailable: (controller) => {
48
- options.onClientAvailable?.(controller);
49
- },
50
- onClientClosed: options.onClientClosed,
51
- run
52
- };
53
- }
54
- function toSessionMutationResult(result) {
55
- return {
56
- record: result.record,
57
- resumed: result.resumed,
58
- loadError: result.loadError
59
- };
60
- }
61
- async function runSessionSetModeDirect(options) {
62
- return toSessionMutationResult(await withConnectedSession(buildDirectConnectedSessionOptions(options, async ({ client, sessionId, record }) => {
63
- await withTimeout(client.setSessionMode(sessionId, options.modeId), options.timeoutMs);
64
- setDesiredModeId(record, options.modeId);
65
- })));
66
- }
67
- async function runSessionSetModelDirect(options) {
68
- return toSessionMutationResult(await withConnectedSession(buildDirectConnectedSessionOptions(options, async ({ client, sessionId, record }) => {
69
- await withTimeout(client.setSessionModel(sessionId, options.modelId), options.timeoutMs);
70
- setDesiredModelId(record, options.modelId);
71
- setCurrentModelId(record, options.modelId);
72
- })));
73
- }
74
- async function runSessionSetConfigOptionDirect(options) {
75
- const result = await withConnectedSession(buildDirectConnectedSessionOptions(options, async ({ client, sessionId, record }) => {
76
- const response = await withTimeout(client.setSessionConfigOption(sessionId, options.configId, options.value), options.timeoutMs);
77
- if (options.configId === "mode") setDesiredModeId(record, options.value);
78
- else setDesiredConfigOption(record, options.configId, options.value);
79
- return response;
80
- }));
81
- return {
82
- record: result.record,
83
- response: result.value,
84
- resumed: result.resumed,
85
- loadError: result.loadError
86
- };
87
- }
88
- //#endregion
89
- //#region src/cli/session/session-control.ts
90
- async function cancelSessionPrompt(options) {
91
- const cancelled = await tryCancelOnRunningOwner(options);
92
- return {
93
- sessionId: options.sessionId,
94
- cancelled: cancelled === true
95
- };
96
- }
97
- async function setSessionMode(options) {
98
- if (await trySetModeOnRunningOwner(options.sessionId, options.modeId, options.timeoutMs, options.verbose)) {
99
- const record = await resolveSessionRecord(options.sessionId);
100
- setDesiredModeId(record, options.modeId);
101
- await writeSessionRecord(record);
102
- return {
103
- record,
104
- resumed: false
105
- };
106
- }
107
- return await runSessionSetModeDirect({
108
- sessionRecordId: options.sessionId,
109
- modeId: options.modeId,
110
- mcpServers: options.mcpServers,
111
- nonInteractivePermissions: options.nonInteractivePermissions,
112
- authCredentials: options.authCredentials,
113
- authPolicy: options.authPolicy,
114
- terminal: options.terminal,
115
- timeoutMs: options.timeoutMs,
116
- verbose: options.verbose
117
- });
118
- }
119
- async function setSessionModel(options) {
120
- if (await trySetModelOnRunningOwner(options.sessionId, options.modelId, options.timeoutMs, options.verbose)) {
121
- const record = await resolveSessionRecord(options.sessionId);
122
- setDesiredModelId(record, options.modelId);
123
- setCurrentModelId(record, options.modelId);
124
- await writeSessionRecord(record);
125
- return {
126
- record,
127
- resumed: false
128
- };
129
- }
130
- return await runSessionSetModelDirect({
131
- sessionRecordId: options.sessionId,
132
- modelId: options.modelId,
133
- mcpServers: options.mcpServers,
134
- nonInteractivePermissions: options.nonInteractivePermissions,
135
- authCredentials: options.authCredentials,
136
- authPolicy: options.authPolicy,
137
- terminal: options.terminal,
138
- timeoutMs: options.timeoutMs,
139
- verbose: options.verbose
140
- });
141
- }
142
- async function setSessionConfigOption(options) {
143
- const ownerResponse = await trySetConfigOptionOnRunningOwner(options.sessionId, options.configId, options.value, options.timeoutMs, options.verbose);
144
- if (ownerResponse) {
145
- const record = await resolveSessionRecord(options.sessionId);
146
- if (options.configId === "mode") setDesiredModeId(record, options.value);
147
- else setDesiredConfigOption(record, options.configId, options.value);
148
- await writeSessionRecord(record);
149
- return {
150
- record,
151
- response: ownerResponse,
152
- resumed: false
153
- };
154
- }
155
- return await runSessionSetConfigOptionDirect({
156
- sessionRecordId: options.sessionId,
157
- configId: options.configId,
158
- value: options.value,
159
- mcpServers: options.mcpServers,
160
- nonInteractivePermissions: options.nonInteractivePermissions,
161
- authCredentials: options.authCredentials,
162
- authPolicy: options.authPolicy,
163
- terminal: options.terminal,
164
- timeoutMs: options.timeoutMs,
165
- verbose: options.verbose
166
- });
167
- }
168
- function firstAgentCommandToken(command) {
169
- const trimmed = command.trim();
170
- if (!trimmed) return;
171
- const token = trimmed.split(/\s+/, 1)[0];
172
- return token.length > 0 ? token : void 0;
173
- }
174
- async function isLikelyMatchingProcess(pid, agentCommand) {
175
- const expectedToken = firstAgentCommandToken(agentCommand);
176
- if (!expectedToken) return false;
177
- const procCmdline = `/proc/${pid}/cmdline`;
178
- try {
179
- const argv = (await fs$1.readFile(procCmdline, "utf8")).split("\0").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
180
- if (argv.length === 0) return false;
181
- const executableBase = path.basename(argv[0]);
182
- const expectedBase = path.basename(expectedToken);
183
- return executableBase === expectedBase || argv.some((entry) => path.basename(entry) === expectedBase);
184
- } catch {
185
- return true;
186
- }
187
- }
188
- async function closeSession(sessionId) {
189
- const record = await resolveSessionRecord(sessionId);
190
- await tryCloseSessionOnRunningOwner({ sessionId: record.acpxRecordId }).catch(() => {});
191
- await terminateQueueOwnerForSession(record.acpxRecordId);
192
- if (record.pid != null && isProcessAlive(record.pid) && await isLikelyMatchingProcess(record.pid, record.agentCommand)) await terminateProcess(record.pid);
193
- record.pid = void 0;
194
- record.closed = true;
195
- record.closedAt = isoNow();
196
- await writeSessionRecord(record);
197
- return record;
198
- }
199
- //#endregion
200
- //#region src/cli/session/session-management.ts
201
- function persistSessionOptions(record, options) {
202
- const systemPromptOption = options?.systemPrompt;
203
- const normalizedSystemPrompt = typeof systemPromptOption === "string" && systemPromptOption.length > 0 ? systemPromptOption : systemPromptOption && typeof systemPromptOption === "object" && typeof systemPromptOption.append === "string" && systemPromptOption.append.length > 0 ? { append: systemPromptOption.append } : void 0;
204
- const next = options && {
205
- model: typeof options.model === "string" ? options.model : void 0,
206
- allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
207
- max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
208
- system_prompt: normalizedSystemPrompt
209
- };
210
- if (Boolean(next && (typeof next.model === "string" && next.model.trim().length > 0 || Array.isArray(next.allowed_tools) && next.allowed_tools.length > 0 || typeof next.max_turns === "number" || next.system_prompt !== void 0)) && next) {
211
- record.acpx = {
212
- ...record.acpx,
213
- session_options: next
214
- };
215
- return;
216
- }
217
- if (!record.acpx) return;
218
- delete record.acpx.session_options;
219
- }
220
- async function createSessionRecordWithClient(client, options) {
221
- const cwd = absolutePath(options.cwd);
222
- await withTimeout(client.start(), options.timeoutMs);
223
- let sessionId;
224
- let agentSessionId;
225
- let sessionResult;
226
- let sessionModels;
227
- let requestedModelApplied = false;
228
- if (options.resumeSessionId) {
229
- if (!client.supportsLoadSession()) throw new Error(`Agent command "${options.agentCommand}" does not support session/load; cannot resume session ${options.resumeSessionId}`);
230
- try {
231
- const loadedSession = await withTimeout(client.loadSession(options.resumeSessionId, cwd), options.timeoutMs);
232
- sessionId = options.resumeSessionId;
233
- agentSessionId = normalizeRuntimeSessionId(loadedSession.agentSessionId);
234
- sessionResult = loadedSession;
235
- sessionModels = loadedSession.models;
236
- requestedModelApplied = await applyRequestedModelIfAdvertised({
237
- client,
238
- sessionId,
239
- requestedModel: options.sessionOptions?.model,
240
- models: sessionModels,
241
- agentCommand: options.agentCommand,
242
- timeoutMs: options.timeoutMs
243
- });
244
- } catch (error) {
245
- throw new Error(`Failed to resume ACP session ${options.resumeSessionId}: ${formatErrorMessage(error)}`, { cause: error });
246
- }
247
- } else {
248
- const createdSession = await withTimeout(client.createSession(cwd), options.timeoutMs);
249
- sessionId = createdSession.sessionId;
250
- agentSessionId = normalizeRuntimeSessionId(createdSession.agentSessionId);
251
- sessionResult = createdSession;
252
- sessionModels = createdSession.models;
253
- requestedModelApplied = await applyRequestedModelIfAdvertised({
254
- client,
255
- sessionId,
256
- requestedModel: options.sessionOptions?.model,
257
- models: sessionModels,
258
- agentCommand: options.agentCommand,
259
- timeoutMs: options.timeoutMs
260
- });
261
- }
262
- const lifecycle = client.getAgentLifecycleSnapshot();
263
- const now = isoNow();
264
- const record = {
265
- schema: "acpx.session.v1",
266
- acpxRecordId: sessionId,
267
- acpSessionId: sessionId,
268
- agentSessionId,
269
- agentCommand: options.agentCommand,
270
- cwd,
271
- name: normalizeName(options.name),
272
- createdAt: now,
273
- lastUsedAt: now,
274
- lastSeq: 0,
275
- lastRequestId: void 0,
276
- eventLog: defaultSessionEventLog(sessionId),
277
- closed: false,
278
- closedAt: void 0,
279
- pid: lifecycle.pid,
280
- agentStartedAt: lifecycle.startedAt,
281
- protocolVersion: client.initializeResult?.protocolVersion,
282
- agentCapabilities: client.initializeResult?.agentCapabilities,
283
- ...createSessionConversation(now),
284
- acpx: {}
285
- };
286
- persistSessionOptions(record, options.sessionOptions);
287
- applyConfigOptionsToRecord(record, sessionResult);
288
- syncAdvertisedModelState(record, sessionModels);
289
- if (requestedModelApplied) setCurrentModelId(record, options.sessionOptions?.model);
290
- await writeSessionRecord(record);
291
- return record;
292
- }
293
- async function createSessionWithClient(options) {
294
- const client = new AcpClient({
295
- agentCommand: options.agentCommand,
296
- cwd: absolutePath(options.cwd),
297
- mcpServers: options.mcpServers,
298
- permissionMode: options.permissionMode,
299
- nonInteractivePermissions: options.nonInteractivePermissions,
300
- authCredentials: options.authCredentials,
301
- authPolicy: options.authPolicy,
302
- terminal: options.terminal,
303
- verbose: options.verbose,
304
- sessionOptions: options.sessionOptions
305
- });
306
- try {
307
- return {
308
- record: await withInterrupt(async () => await createSessionRecordWithClient(client, options), async () => {
309
- await client.close();
310
- }),
311
- client
312
- };
313
- } catch (error) {
314
- await client.close();
315
- throw error;
316
- }
317
- }
318
- async function createSession(options) {
319
- const { record, client } = await createSessionWithClient(options);
320
- try {
321
- return record;
322
- } finally {
323
- await client.close();
324
- }
325
- }
326
- async function ensureSession(options) {
327
- const cwd = absolutePath(options.cwd);
328
- const gitRoot = findGitRepositoryRoot(cwd);
329
- const walkBoundary = options.walkBoundary ?? gitRoot ?? cwd;
330
- const existing = await findSessionByDirectoryWalk({
331
- agentCommand: options.agentCommand,
332
- cwd,
333
- name: options.name,
334
- boundary: walkBoundary
335
- });
336
- if (existing) {
337
- const requestedModel = options.sessionOptions?.model;
338
- if (requestedModel) return {
339
- record: (await setSessionModel({
340
- sessionId: existing.acpxRecordId,
341
- modelId: requestedModel,
342
- mcpServers: options.mcpServers,
343
- nonInteractivePermissions: options.nonInteractivePermissions,
344
- authCredentials: options.authCredentials,
345
- authPolicy: options.authPolicy,
346
- terminal: options.terminal,
347
- timeoutMs: options.timeoutMs,
348
- verbose: options.verbose
349
- })).record,
350
- created: false
351
- };
352
- return {
353
- record: existing,
354
- created: false
355
- };
356
- }
357
- return {
358
- record: await createSession({
359
- agentCommand: options.agentCommand,
360
- cwd,
361
- name: options.name,
362
- resumeSessionId: options.resumeSessionId,
363
- mcpServers: options.mcpServers,
364
- permissionMode: options.permissionMode,
365
- nonInteractivePermissions: options.nonInteractivePermissions,
366
- authCredentials: options.authCredentials,
367
- authPolicy: options.authPolicy,
368
- terminal: options.terminal,
369
- timeoutMs: options.timeoutMs,
370
- verbose: options.verbose,
371
- sessionOptions: options.sessionOptions
372
- }),
373
- created: true
374
- };
375
- }
376
- //#endregion
377
- //#region src/perf-metrics-capture.ts
378
- const PERF_METRICS_FILE_ENV = "ACPX_PERF_METRICS_FILE";
379
- let installed = false;
380
- let flushed = false;
381
- let captureFilePath;
382
- let captureRole = "cli";
383
- let captureArgv = [];
384
- let captureSequence = 0;
385
- function shouldCapture() {
386
- return typeof captureFilePath === "string" && captureFilePath.trim().length > 0;
387
- }
388
- function buildPayload(reason) {
389
- return {
390
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
391
- pid: process.pid,
392
- ppid: process.ppid,
393
- role: captureRole,
394
- argv: captureArgv,
395
- cwd: process.cwd(),
396
- sequence: captureSequence,
397
- reason,
398
- metrics: getPerfMetricsSnapshot()
399
- };
400
- }
401
- function writePerfMetricsCapture(reason, resetAfterWrite) {
402
- if (!shouldCapture()) return false;
403
- const payload = buildPayload(reason);
404
- const metrics = payload.metrics;
405
- if (!(Object.keys(metrics.counters ?? {}).length > 0 || Object.keys(metrics.gauges ?? {}).length > 0 || Object.keys(metrics.timings ?? {}).length > 0)) return false;
406
- try {
407
- fs.mkdirSync(path.dirname(captureFilePath), { recursive: true });
408
- fs.appendFileSync(captureFilePath, `${JSON.stringify(payload)}\n`, "utf8");
409
- captureSequence += 1;
410
- if (resetAfterWrite) resetPerfMetrics();
411
- return true;
412
- } catch {
413
- return false;
414
- }
415
- }
416
- function checkpointPerfMetricsCapture() {
417
- flushed = false;
418
- writePerfMetricsCapture("checkpoint", true);
419
- }
420
- function flushPerfMetricsCapture(reason = "exit") {
421
- if (flushed || !shouldCapture()) return;
422
- flushed = true;
423
- writePerfMetricsCapture(reason, false);
424
- }
425
- function installPerfMetricsCapture(options = {}) {
426
- captureFilePath = options.filePath ?? process.env[PERF_METRICS_FILE_ENV];
427
- if (!shouldCapture()) return;
428
- resetPerfMetrics();
429
- captureRole = options.role ?? captureRole;
430
- captureArgv = options.argv ?? [];
431
- captureSequence = 0;
432
- flushed = false;
433
- if (installed) return;
434
- installed = true;
435
- process.once("exit", () => {
436
- flushPerfMetricsCapture("exit");
437
- });
438
- for (const signal of ["SIGINT", "SIGTERM"]) {
439
- const handler = () => {
440
- flushPerfMetricsCapture("signal");
441
- process.removeListener(signal, handler);
442
- process.kill(process.pid, signal);
443
- };
444
- process.once(signal, handler);
445
- }
446
- }
447
- //#endregion
448
- //#region src/cli/queue/owner-turn-controller.ts
449
- var QueueOwnerTurnController = class {
450
- options;
451
- state = "idle";
452
- pendingCancel = false;
453
- activeController;
454
- constructor(options) {
455
- this.options = options;
456
- }
457
- get lifecycleState() {
458
- return this.state;
459
- }
460
- get hasPendingCancel() {
461
- return this.pendingCancel;
462
- }
463
- beginTurn() {
464
- this.state = "starting";
465
- this.pendingCancel = false;
466
- }
467
- markPromptActive() {
468
- if (this.state === "starting" || this.state === "active") this.state = "active";
469
- }
470
- endTurn() {
471
- this.state = "idle";
472
- this.pendingCancel = false;
473
- }
474
- beginClosing() {
475
- this.state = "closing";
476
- this.pendingCancel = false;
477
- this.activeController = void 0;
478
- }
479
- setActiveController(controller) {
480
- this.activeController = controller;
481
- }
482
- clearActiveController() {
483
- this.activeController = void 0;
484
- }
485
- assertCanHandleControlRequest() {
486
- if (this.state === "closing") throw new QueueConnectionError("Queue owner is closing", {
487
- detailCode: "QUEUE_OWNER_SHUTTING_DOWN",
488
- origin: "queue",
489
- retryable: true
490
- });
491
- }
492
- async requestCancel() {
493
- const activeController = this.activeController;
494
- if (activeController?.hasActivePrompt()) {
495
- const cancelled = await activeController.requestCancelActivePrompt();
496
- if (cancelled) this.pendingCancel = false;
497
- return cancelled;
498
- }
499
- if (this.state === "starting" || this.state === "active") {
500
- this.pendingCancel = true;
501
- return true;
502
- }
503
- return false;
504
- }
505
- async applyPendingCancel() {
506
- const activeController = this.activeController;
507
- if (!this.pendingCancel || !activeController || !activeController.hasActivePrompt()) return false;
508
- const cancelled = await activeController.requestCancelActivePrompt();
509
- if (cancelled) this.pendingCancel = false;
510
- return cancelled;
511
- }
512
- async setSessionMode(modeId, timeoutMs) {
513
- this.assertCanHandleControlRequest();
514
- const activeController = this.activeController;
515
- if (activeController) {
516
- await this.options.withTimeout(async () => await activeController.setSessionMode(modeId), timeoutMs);
517
- return;
518
- }
519
- await this.options.setSessionModeFallback(modeId, timeoutMs);
520
- }
521
- async setSessionModel(modelId, timeoutMs) {
522
- this.assertCanHandleControlRequest();
523
- const activeController = this.activeController;
524
- if (activeController) {
525
- await this.options.withTimeout(async () => await activeController.setSessionModel(modelId), timeoutMs);
526
- return;
527
- }
528
- await this.options.setSessionModelFallback(modelId, timeoutMs);
529
- }
530
- async setSessionConfigOption(configId, value, timeoutMs) {
531
- this.assertCanHandleControlRequest();
532
- const activeController = this.activeController;
533
- if (activeController) return await this.options.withTimeout(async () => await activeController.setSessionConfigOption(configId, value), timeoutMs);
534
- return await this.options.setSessionConfigOptionFallback(configId, value, timeoutMs);
535
- }
536
- };
537
- //#endregion
538
- //#region src/cli/session/queue-owner-process.ts
539
- function sanitizeQueueOwnerExecArgv(execArgv = process.execArgv) {
540
- const sanitized = [];
541
- for (let index = 0; index < execArgv.length; index += 1) {
542
- const value = execArgv[index];
543
- if (value === "--experimental-test-coverage" || value === "--test") continue;
544
- if (value === "--test-name-pattern" || value === "--test-reporter" || value === "--test-reporter-destination") {
545
- index += 1;
546
- continue;
547
- }
548
- if (value.startsWith("--test-")) continue;
549
- if (value === "--inspect" || value === "--inspect-brk" || value === "--inspect-port" || value === "--inspect-publish-uid" || value.startsWith("--inspect=") || value.startsWith("--inspect-brk=") || value.startsWith("--inspect-port=") || value.startsWith("--inspect-publish-uid=") || value === "--debug-port" || value.startsWith("--debug-port=")) {
550
- if (value === "--inspect" || value === "--inspect-brk" || value === "--inspect-port" || value === "--inspect-publish-uid" || value === "--debug-port") index += 1;
551
- continue;
552
- }
553
- sanitized.push(value);
554
- }
555
- return sanitized;
556
- }
557
- function buildQueueOwnerArgOverride(entryPath, execArgv = process.execArgv) {
558
- const sanitized = sanitizeQueueOwnerExecArgv(execArgv);
559
- if (sanitized.length === 0) return null;
560
- return JSON.stringify([
561
- ...sanitized,
562
- entryPath,
563
- "__queue-owner"
564
- ]);
565
- }
566
- function resolveQueueOwnerSpawnArgs(argv = process.argv) {
567
- const override = process.env.ACPX_QUEUE_OWNER_ARGS;
568
- if (override) {
569
- const parsed = JSON.parse(override);
570
- if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((value) => typeof value === "string" && value.length > 0)) return [...parsed];
571
- throw new Error("acpx self-spawn failed: invalid ACPX_QUEUE_OWNER_ARGS");
572
- }
573
- const entry = argv[1];
574
- if (!entry || entry.trim().length === 0) throw new Error("acpx self-spawn failed: missing CLI entry path");
575
- return [realpathSync(entry), "__queue-owner"];
576
- }
577
- function queueOwnerRuntimeOptionsFromSend(options) {
578
- return {
579
- sessionId: options.sessionId,
580
- mcpServers: options.mcpServers,
581
- permissionMode: options.permissionMode,
582
- nonInteractivePermissions: options.nonInteractivePermissions,
583
- authCredentials: options.authCredentials,
584
- authPolicy: options.authPolicy,
585
- terminal: options.terminal,
586
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
587
- verbose: options.verbose,
588
- ttlMs: options.ttlMs,
589
- maxQueueDepth: options.maxQueueDepth,
590
- promptRetries: options.promptRetries,
591
- sessionOptions: options.sessionOptions
592
- };
593
- }
594
- function buildQueueOwnerSpawnOptions(payload) {
595
- return {
596
- detached: true,
597
- stdio: "ignore",
598
- env: {
599
- ...process.env,
600
- ACPX_QUEUE_OWNER_PAYLOAD: payload
601
- },
602
- windowsHide: true
603
- };
604
- }
605
- function spawnQueueOwnerProcess(options) {
606
- const payload = JSON.stringify(options);
607
- spawn(process.execPath, resolveQueueOwnerSpawnArgs(), buildQueueOwnerSpawnOptions(payload)).unref();
608
- }
609
- //#endregion
610
- //#region src/session/events.ts
611
- const LOCK_RETRY_MS = 15;
612
- const EVENT_LOCK_STALE_MS = 15e3;
613
- async function ensureSessionDir() {
614
- await fs$1.mkdir(sessionBaseDir(), { recursive: true });
615
- }
616
- async function pathExists(filePath) {
617
- try {
618
- await fs$1.access(filePath);
619
- return true;
620
- } catch {
621
- return false;
622
- }
623
- }
624
- async function statSize(filePath) {
625
- try {
626
- return (await fs$1.stat(filePath)).size;
627
- } catch {
628
- return 0;
629
- }
630
- }
631
- async function countExistingSegments(sessionId, maxSegments) {
632
- let count = 0;
633
- for (let segment = 1; segment <= maxSegments; segment += 1) if (await pathExists(sessionEventSegmentPath(sessionId, segment))) count += 1;
634
- if (await pathExists(sessionEventActivePath(sessionId))) count += 1;
635
- return count;
636
- }
637
- async function rotateSegments(sessionId, maxSegments) {
638
- const active = sessionEventActivePath(sessionId);
639
- const overflow = sessionEventSegmentPath(sessionId, maxSegments);
640
- await fs$1.unlink(overflow).catch((error) => {
641
- if (error.code !== "ENOENT") throw error;
642
- });
643
- for (let segment = maxSegments - 1; segment >= 1; segment -= 1) {
644
- const from = sessionEventSegmentPath(sessionId, segment);
645
- const to = sessionEventSegmentPath(sessionId, segment + 1);
646
- if (!await pathExists(from)) continue;
647
- await fs$1.rename(from, to);
648
- }
649
- if (await pathExists(active)) await fs$1.rename(active, sessionEventSegmentPath(sessionId, 1));
650
- }
651
- function parseEventLockPayload(raw) {
652
- try {
653
- const parsed = JSON.parse(raw);
654
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
655
- const record = parsed;
656
- return {
657
- pid: typeof record.pid === "number" ? record.pid : void 0,
658
- created_at: typeof record.created_at === "string" ? record.created_at : void 0
659
- };
660
- } catch {
661
- return {};
662
- }
663
- }
664
- async function removeStaleEventLock(lockPath) {
665
- try {
666
- const parsed = parseEventLockPayload(await fs$1.readFile(lockPath, "utf8"));
667
- const createdAtMs = parsed.created_at ? Date.parse(parsed.created_at) : NaN;
668
- const lockAgeMs = Number.isFinite(createdAtMs) ? Date.now() - createdAtMs : Number.POSITIVE_INFINITY;
669
- if (isProcessAlive(parsed.pid) && lockAgeMs <= EVENT_LOCK_STALE_MS) return false;
670
- await fs$1.unlink(lockPath);
671
- incrementPerfCounter("session.events.stale_lock_recovered");
672
- return true;
673
- } catch (error) {
674
- if (error.code === "ENOENT") return true;
675
- return false;
676
- }
677
- }
678
- async function acquireEventsLock(sessionId) {
679
- await ensureSessionDir();
680
- const lockPath = sessionEventLockPath(sessionId);
681
- const payload = JSON.stringify({
682
- pid: process.pid,
683
- created_at: (/* @__PURE__ */ new Date()).toISOString()
684
- }, null, 2);
685
- for (;;) try {
686
- await fs$1.writeFile(lockPath, `${payload}\n`, {
687
- encoding: "utf8",
688
- flag: "wx"
689
- });
690
- return { filePath: lockPath };
691
- } catch (error) {
692
- if (error.code !== "EEXIST") throw error;
693
- if (await removeStaleEventLock(lockPath)) continue;
694
- await new Promise((resolve) => {
695
- setTimeout(resolve, LOCK_RETRY_MS);
696
- });
697
- }
698
- }
699
- async function releaseEventsLock(lock) {
700
- await fs$1.unlink(lock.filePath).catch((error) => {
701
- if (error.code !== "ENOENT") throw error;
702
- });
703
- }
704
- var SessionEventWriter = class SessionEventWriter {
705
- record;
706
- lock;
707
- maxSegmentBytes;
708
- maxSegments;
709
- activePath;
710
- activeSizeBytes;
711
- segmentCount;
712
- closed = false;
713
- constructor(record, lock, options, state) {
714
- this.record = record;
715
- this.lock = lock;
716
- this.maxSegmentBytes = options.maxSegmentBytes;
717
- this.maxSegments = options.maxSegments;
718
- this.activePath = state.activePath;
719
- this.activeSizeBytes = state.activeSizeBytes;
720
- this.segmentCount = state.segmentCount;
721
- }
722
- static async open(record, options = {}) {
723
- const lock = await acquireEventsLock(record.acpxRecordId);
724
- const maxSegmentBytes = options.maxSegmentBytes ?? record.eventLog.max_segment_bytes ?? 67108864;
725
- const maxSegments = options.maxSegments ?? record.eventLog.max_segments ?? 5;
726
- const activePath = sessionEventActivePath(record.acpxRecordId);
727
- const activeSizeBytes = await statSize(activePath);
728
- const segmentCount = Number.isInteger(record.eventLog.segment_count) && record.eventLog.segment_count > 0 ? record.eventLog.segment_count : await countExistingSegments(record.acpxRecordId, maxSegments) || 1;
729
- return new SessionEventWriter(record, lock, {
730
- maxSegmentBytes,
731
- maxSegments
732
- }, {
733
- activePath,
734
- activeSizeBytes,
735
- segmentCount
736
- });
737
- }
738
- getRecord() {
739
- return this.record;
740
- }
741
- async appendMessage(message, options = {}) {
742
- await this.appendMessages([message], options);
743
- }
744
- async appendMessages(messages, options = {}) {
745
- if (this.closed) throw new Error("SessionEventWriter is closed");
746
- if (messages.length === 0) return;
747
- await ensureSessionDir();
748
- await measurePerf("session.events.append_batch", async () => {
749
- for (const message of messages) {
750
- if (!isAcpJsonRpcMessage(message)) throw new Error("Attempted to persist invalid ACP JSON-RPC payload");
751
- const line = `${JSON.stringify(message)}\n`;
752
- const lineBytes = Buffer.byteLength(line);
753
- if (this.activeSizeBytes > 0 && this.activeSizeBytes + lineBytes > this.maxSegmentBytes) {
754
- await rotateSegments(this.record.acpxRecordId, this.maxSegments);
755
- this.activePath = sessionEventActivePath(this.record.acpxRecordId);
756
- this.activeSizeBytes = 0;
757
- this.segmentCount = Math.min(this.segmentCount + 1, this.maxSegments);
758
- incrementPerfCounter("session.events.rotate");
759
- }
760
- await fs$1.appendFile(this.activePath, line, "utf8");
761
- this.activeSizeBytes += lineBytes;
762
- this.record.lastSeq += 1;
763
- if (Object.hasOwn(message, "id")) {
764
- const id = message.id;
765
- if (typeof id === "string" || typeof id === "number") this.record.lastRequestId = String(id);
766
- }
767
- const writeTs = (/* @__PURE__ */ new Date()).toISOString();
768
- this.record.lastUsedAt = writeTs;
769
- this.record.eventLog = {
770
- active_path: this.activePath,
771
- segment_count: this.segmentCount,
772
- max_segment_bytes: this.maxSegmentBytes,
773
- max_segments: this.maxSegments,
774
- last_write_at: writeTs,
775
- last_write_error: null
776
- };
777
- }
778
- });
779
- if (options.checkpoint === true) await writeSessionRecord(this.record);
780
- }
781
- async checkpoint() {
782
- if (this.closed) throw new Error("SessionEventWriter is closed");
783
- await writeSessionRecord(this.record);
784
- }
785
- async close(options = {}) {
786
- if (this.closed) return;
787
- try {
788
- if (options.checkpoint !== false) await writeSessionRecord(this.record);
789
- } finally {
790
- this.closed = true;
791
- await releaseEventsLock(this.lock);
792
- }
793
- }
794
- };
795
- //#endregion
796
- //#region src/cli/session/runtime.ts
797
- const INTERRUPT_CANCEL_WAIT_MS = 2500;
798
- var QueueTaskOutputFormatter = class {
799
- requestId;
800
- send;
801
- constructor(task) {
802
- this.requestId = task.requestId;
803
- this.send = task.send;
804
- }
805
- setContext(_context) {}
806
- onAcpMessage(message) {
807
- this.send({
808
- type: "event",
809
- requestId: this.requestId,
810
- message
811
- });
812
- }
813
- onError(params) {
814
- this.send({
815
- type: "error",
816
- requestId: this.requestId,
817
- code: params.code,
818
- detailCode: params.detailCode,
819
- origin: params.origin,
820
- message: params.message,
821
- retryable: params.retryable,
822
- acp: params.acp
823
- });
824
- }
825
- flush() {}
826
- };
827
- const DISCARD_OUTPUT_FORMATTER = {
828
- setContext() {},
829
- onAcpMessage() {},
830
- onError() {},
831
- flush() {}
832
- };
833
- function toPromptResult(stopReason, sessionId, client) {
834
- return {
835
- stopReason,
836
- sessionId,
837
- permissionStats: client.getPermissionStats()
838
- };
839
- }
840
- async function applyPromptModelIfAdvertised(params) {
841
- const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
842
- if (!requestedModel) return;
843
- const availableModels = params.record.acpx?.available_models;
844
- assertRequestedModelSupported({
845
- requestedModel,
846
- models: Array.isArray(availableModels) ? {
847
- currentModelId: params.record.acpx?.current_model_id ?? "",
848
- availableModels: availableModels.map((modelId) => ({
849
- modelId,
850
- name: modelId
851
- }))
852
- } : void 0,
853
- agentCommand: params.record.agentCommand,
854
- context: "apply"
855
- });
856
- if (!Array.isArray(availableModels)) return;
857
- if (params.record.acpx?.current_model_id === requestedModel) {
858
- setDesiredModelId(params.record, requestedModel);
859
- return;
860
- }
861
- await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel), params.timeoutMs);
862
- setDesiredModelId(params.record, requestedModel);
863
- setCurrentModelId(params.record, requestedModel);
864
- }
865
- function jsonRpcIdKey(value) {
866
- if (typeof value === "string") return `s:${value}`;
867
- if (typeof value === "number" && Number.isFinite(value)) return `n:${value}`;
868
- }
869
- function extractJsonRpcRequestInfo(message) {
870
- const candidate = message;
871
- if (typeof candidate.method !== "string") return;
872
- const idKey = jsonRpcIdKey(candidate.id);
873
- if (!idKey) return;
874
- return {
875
- idKey,
876
- method: candidate.method
877
- };
878
- }
879
- function extractJsonRpcResponseInfo(message) {
880
- const candidate = message;
881
- const idKey = jsonRpcIdKey(candidate.id);
882
- if (!idKey) return;
883
- const hasError = Object.hasOwn(candidate, "error");
884
- if (!hasError && !Object.hasOwn(candidate, "result")) return;
885
- return {
886
- idKey,
887
- hasError
888
- };
889
- }
890
- function filterRecoverableLoadFallbackOutput(messages) {
891
- const requestMethodById = /* @__PURE__ */ new Map();
892
- const failedLoadRequestIds = /* @__PURE__ */ new Set();
893
- for (const message of messages) {
894
- const request = extractJsonRpcRequestInfo(message);
895
- if (request) {
896
- requestMethodById.set(request.idKey, request.method);
897
- continue;
898
- }
899
- const response = extractJsonRpcResponseInfo(message);
900
- if (!response || !response.hasError) continue;
901
- if (requestMethodById.get(response.idKey) === "session/load") failedLoadRequestIds.add(response.idKey);
902
- }
903
- if (failedLoadRequestIds.size === 0) return messages;
904
- return messages.filter((message) => {
905
- const request = extractJsonRpcRequestInfo(message);
906
- if (request && request.method === "session/load" && failedLoadRequestIds.has(request.idKey)) return false;
907
- const response = extractJsonRpcResponseInfo(message);
908
- if (response && failedLoadRequestIds.has(response.idKey)) return false;
909
- return true;
910
- });
911
- }
912
- function emitPromptRetryNotice(params) {
913
- if (params.suppressSdkConsoleErrors) return;
914
- process.stderr.write(`[acpx] prompt failed (${formatErrorMessage(params.error)}), retrying in ${params.delayMs}ms (attempt ${params.attempt}/${params.maxRetries})\n`);
915
- }
916
- async function runQueuedTask(sessionRecordId, task, options) {
917
- const outputFormatter = task.waitForCompletion ? new QueueTaskOutputFormatter(task) : DISCARD_OUTPUT_FORMATTER;
918
- try {
919
- const result = await runSessionPrompt({
920
- sessionRecordId,
921
- mcpServers: options.mcpServers,
922
- prompt: task.prompt ?? textPrompt(task.message),
923
- permissionMode: task.permissionMode,
924
- resumePolicy: task.resumePolicy,
925
- nonInteractivePermissions: task.nonInteractivePermissions ?? options.nonInteractivePermissions,
926
- authCredentials: options.authCredentials,
927
- authPolicy: options.authPolicy,
928
- outputFormatter,
929
- timeoutMs: task.timeoutMs,
930
- suppressSdkConsoleErrors: task.suppressSdkConsoleErrors ?? options.suppressSdkConsoleErrors,
931
- verbose: options.verbose,
932
- promptRetries: options.promptRetries,
933
- sessionOptions: mergeSessionOptions(task.sessionOptions, options.sessionOptions),
934
- onClientAvailable: options.onClientAvailable,
935
- onClientClosed: options.onClientClosed,
936
- onPromptActive: options.onPromptActive,
937
- client: options.sharedClient
938
- });
939
- if (task.waitForCompletion) task.send({
940
- type: "result",
941
- requestId: task.requestId,
942
- result
943
- });
944
- } catch (error) {
945
- const normalizedError = normalizeOutputError(error, {
946
- origin: "runtime",
947
- detailCode: "QUEUE_RUNTIME_PROMPT_FAILED"
948
- });
949
- const alreadyEmitted = error.outputAlreadyEmitted === true;
950
- if (task.waitForCompletion) task.send({
951
- type: "error",
952
- requestId: task.requestId,
953
- code: normalizedError.code,
954
- detailCode: normalizedError.detailCode,
955
- origin: normalizedError.origin,
956
- message: normalizedError.message,
957
- retryable: normalizedError.retryable,
958
- acp: normalizedError.acp,
959
- outputAlreadyEmitted: alreadyEmitted
960
- });
961
- if (error instanceof InterruptedError) throw error;
962
- } finally {
963
- task.close();
964
- }
965
- }
966
- async function runSessionPrompt(options) {
967
- const stopTotalTimer = startPerfTimer("runtime.prompt.total");
968
- const output = options.outputFormatter;
969
- const record = await measurePerf("session.resolve_prompt_record", async () => {
970
- return await resolveSessionRecord(options.sessionRecordId);
971
- });
972
- const conversation = cloneSessionConversation(record);
973
- let acpxState = cloneSessionAcpxState(record.acpx);
974
- const promptStartedAt = isoNow();
975
- const promptMessageId = recordPromptSubmission(conversation, options.prompt, promptStartedAt);
976
- record.lastPromptAt = promptStartedAt;
977
- record.lastUsedAt = promptStartedAt;
978
- applyConversation(record, conversation);
979
- record.acpx = acpxState;
980
- await writeSessionRecord(record);
981
- output.setContext({ sessionId: record.acpxRecordId });
982
- const eventWriter = await measurePerf("session.events.open", async () => {
983
- return await SessionEventWriter.open(record);
984
- });
985
- const pendingMessages = [];
986
- const pendingConnectOutputMessages = [];
987
- const sessionOptions = mergeSessionOptions(options.sessionOptions, sessionOptionsFromRecord(record));
988
- let bufferingConnectOutput = true;
989
- let promptTurnActive = false;
990
- let promptTurnHadSideEffects = false;
991
- let sawAcpMessage = false;
992
- let eventWriterClosed = false;
993
- const closeEventWriter = async (checkpoint) => {
994
- if (eventWriterClosed) return;
995
- eventWriterClosed = true;
996
- await eventWriter.close({ checkpoint });
997
- };
998
- const flushPendingMessages = async (checkpoint = false) => {
999
- if (pendingMessages.length === 0) return;
1000
- const batch = pendingMessages.splice(0);
1001
- await measurePerf("session.events.flush_pending", async () => {
1002
- await eventWriter.appendMessages(batch, { checkpoint });
1003
- });
1004
- };
1005
- const ownClient = options.client == null;
1006
- const client = options.client ?? new AcpClient({
1007
- agentCommand: record.agentCommand,
1008
- cwd: absolutePath(record.cwd),
1009
- mcpServers: options.mcpServers,
1010
- permissionMode: options.permissionMode,
1011
- nonInteractivePermissions: options.nonInteractivePermissions,
1012
- authCredentials: options.authCredentials,
1013
- authPolicy: options.authPolicy,
1014
- terminal: options.terminal,
1015
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
1016
- verbose: options.verbose,
1017
- sessionOptions
1018
- });
1019
- client.updateRuntimeOptions({
1020
- permissionMode: options.permissionMode,
1021
- nonInteractivePermissions: options.nonInteractivePermissions,
1022
- terminal: options.terminal,
1023
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
1024
- verbose: options.verbose
1025
- });
1026
- client.setEventHandlers({
1027
- onAcpMessage: (direction, message) => {
1028
- sawAcpMessage = true;
1029
- pendingMessages.push(message);
1030
- options.onAcpMessage?.(direction, message);
1031
- },
1032
- onAcpOutputMessage: (_direction, message) => {
1033
- if (bufferingConnectOutput) {
1034
- pendingConnectOutputMessages.push(message);
1035
- return;
1036
- }
1037
- output.onAcpMessage(message);
1038
- },
1039
- onSessionUpdate: (notification) => {
1040
- if (promptTurnActive) promptTurnHadSideEffects = true;
1041
- acpxState = recordSessionUpdate(conversation, acpxState, notification);
1042
- trimConversationForRuntime(conversation);
1043
- options.onSessionUpdate?.(notification);
1044
- },
1045
- onClientOperation: (operation) => {
1046
- if (promptTurnActive) promptTurnHadSideEffects = true;
1047
- acpxState = recordClientOperation(conversation, acpxState, operation);
1048
- trimConversationForRuntime(conversation);
1049
- options.onClientOperation?.(operation);
1050
- }
1051
- });
1052
- let activeSessionIdForControl = record.acpSessionId;
1053
- let notifiedClientAvailable = false;
1054
- const activeController = {
1055
- hasActivePrompt: () => client.hasActivePrompt(),
1056
- requestCancelActivePrompt: async () => await client.requestCancelActivePrompt(),
1057
- setSessionMode: async (modeId) => {
1058
- await client.setSessionMode(activeSessionIdForControl, modeId);
1059
- },
1060
- setSessionModel: async (modelId) => {
1061
- await client.setSessionModel(activeSessionIdForControl, modelId);
1062
- },
1063
- setSessionConfigOption: async (configId, value) => {
1064
- return await client.setSessionConfigOption(activeSessionIdForControl, configId, value);
1065
- }
1066
- };
1067
- try {
1068
- return await withInterrupt(async () => {
1069
- const connectStartedAt = Date.now();
1070
- const { sessionId: activeSessionId, resumed, loadError } = await measurePerf("runtime.connect_and_load", async () => {
1071
- try {
1072
- return await connectAndLoadSession({
1073
- client,
1074
- record,
1075
- resumePolicy: options.resumePolicy,
1076
- timeoutMs: options.timeoutMs,
1077
- verbose: options.verbose,
1078
- activeController,
1079
- onClientAvailable: (controller) => {
1080
- options.onClientAvailable?.(controller);
1081
- notifiedClientAvailable = true;
1082
- },
1083
- onConnectedRecord: (connectedRecord) => {
1084
- connectedRecord.lastPromptAt = isoNow();
1085
- },
1086
- onSessionIdResolved: (sessionId) => {
1087
- activeSessionIdForControl = sessionId;
1088
- }
1089
- });
1090
- } catch (error) {
1091
- bufferingConnectOutput = false;
1092
- for (const message of pendingConnectOutputMessages) output.onAcpMessage(message);
1093
- pendingConnectOutputMessages.length = 0;
1094
- throw error;
1095
- }
1096
- });
1097
- bufferingConnectOutput = false;
1098
- const connectOutputMessages = loadError == null ? pendingConnectOutputMessages : filterRecoverableLoadFallbackOutput(pendingConnectOutputMessages);
1099
- for (const message of connectOutputMessages) output.onAcpMessage(message);
1100
- pendingConnectOutputMessages.length = 0;
1101
- if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.connect_and_load", Date.now() - connectStartedAt)}\n`);
1102
- await applyPromptModelIfAdvertised({
1103
- client,
1104
- sessionId: activeSessionId,
1105
- requestedModel: sessionOptions?.model,
1106
- record,
1107
- timeoutMs: options.timeoutMs
1108
- });
1109
- output.setContext({ sessionId: record.acpxRecordId });
1110
- await flushPendingMessages(false);
1111
- const maxRetries = options.promptRetries ?? 0;
1112
- let response;
1113
- promptTurnActive = true;
1114
- for (let attempt = 0;; attempt++) try {
1115
- const promptStartedAt = Date.now();
1116
- response = await measurePerf("runtime.prompt.agent_turn", async () => {
1117
- return await runPromptTurn({
1118
- client,
1119
- sessionId: activeSessionId,
1120
- prompt: options.prompt,
1121
- timeoutMs: options.timeoutMs,
1122
- conversation,
1123
- promptMessageId,
1124
- onPromptStarted: attempt === 0 && options.onPromptActive ? async () => {
1125
- try {
1126
- await options.onPromptActive?.();
1127
- } catch (error) {
1128
- if (options.verbose) process.stderr.write("[acpx] onPromptActive hook failed: " + formatErrorMessage(error) + "\n");
1129
- }
1130
- } : void 0
1131
- });
1132
- });
1133
- if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.agent_turn", Date.now() - promptStartedAt)}\n`);
1134
- break;
1135
- } catch (error) {
1136
- const snapshot = client.getAgentLifecycleSnapshot();
1137
- const agentCrashed = snapshot.lastExit?.unexpectedDuringPrompt === true;
1138
- if (attempt < maxRetries && !agentCrashed && !promptTurnHadSideEffects && isRetryablePromptError(error)) {
1139
- const delayMs = Math.min(1e3 * 2 ** attempt, 1e4);
1140
- emitPromptRetryNotice({
1141
- error,
1142
- delayMs,
1143
- attempt: attempt + 1,
1144
- maxRetries,
1145
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors
1146
- });
1147
- await waitMs(delayMs);
1148
- if (!promptTurnHadSideEffects) continue;
1149
- }
1150
- promptTurnActive = false;
1151
- applyLifecycleSnapshotToRecord(record, snapshot);
1152
- const lastExit = snapshot.lastExit;
1153
- if (lastExit?.unexpectedDuringPrompt && options.verbose) process.stderr.write("[acpx] agent disconnected during prompt (" + lastExit.reason + ", exit=" + lastExit.exitCode + ", signal=" + (lastExit.signal ?? "none") + ")\n");
1154
- const normalizedError = normalizeOutputError(error, { origin: "runtime" });
1155
- await flushPendingMessages(false).catch(() => {});
1156
- output.flush();
1157
- record.lastUsedAt = isoNow();
1158
- applyConversation(record, conversation);
1159
- record.acpx = acpxState;
1160
- const propagated = error instanceof Error ? error : new Error(formatErrorMessage(error));
1161
- propagated.outputAlreadyEmitted = sawAcpMessage;
1162
- propagated.normalizedOutputError = normalizedError;
1163
- throw propagated;
1164
- }
1165
- promptTurnActive = false;
1166
- await flushPendingMessages(false);
1167
- output.flush();
1168
- record.lastUsedAt = isoNow();
1169
- record.closed = false;
1170
- record.closedAt = void 0;
1171
- record.protocolVersion = client.initializeResult?.protocolVersion;
1172
- record.agentCapabilities = client.initializeResult?.agentCapabilities;
1173
- applyConversation(record, conversation);
1174
- record.acpx = acpxState;
1175
- applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
1176
- stopTotalTimer();
1177
- return {
1178
- ...toPromptResult(response.stopReason, record.acpxRecordId, client),
1179
- record,
1180
- resumed,
1181
- loadError
1182
- };
1183
- }, async () => {
1184
- await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
1185
- applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
1186
- record.lastUsedAt = isoNow();
1187
- applyConversation(record, conversation);
1188
- record.acpx = acpxState;
1189
- await flushPendingMessages(false).catch(() => {});
1190
- if (ownClient) await client.close();
1191
- });
1192
- } finally {
1193
- if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.total", stopTotalTimer())}\n`);
1194
- else stopTotalTimer();
1195
- if (notifiedClientAvailable) options.onClientClosed?.();
1196
- client.clearEventHandlers();
1197
- if (ownClient) await client.close();
1198
- applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
1199
- applyConversation(record, conversation);
1200
- record.acpx = acpxState;
1201
- await flushPendingMessages(false).catch(() => {});
1202
- await closeEventWriter(true).catch(() => {});
1203
- }
1204
- }
1205
- async function runOnce(options) {
1206
- const output = options.outputFormatter;
1207
- let promptTurnActive = false;
1208
- let promptTurnHadSideEffects = false;
1209
- const client = new AcpClient({
1210
- agentCommand: options.agentCommand,
1211
- cwd: absolutePath(options.cwd),
1212
- mcpServers: options.mcpServers,
1213
- permissionMode: options.permissionMode,
1214
- nonInteractivePermissions: options.nonInteractivePermissions,
1215
- authCredentials: options.authCredentials,
1216
- authPolicy: options.authPolicy,
1217
- terminal: options.terminal,
1218
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
1219
- verbose: options.verbose,
1220
- onAcpMessage: options.onAcpMessage,
1221
- onAcpOutputMessage: (_direction, message) => output.onAcpMessage(message),
1222
- onSessionUpdate: (notification) => {
1223
- if (promptTurnActive) promptTurnHadSideEffects = true;
1224
- options.onSessionUpdate?.(notification);
1225
- },
1226
- onClientOperation: (operation) => {
1227
- if (promptTurnActive) promptTurnHadSideEffects = true;
1228
- options.onClientOperation?.(operation);
1229
- },
1230
- sessionOptions: options.sessionOptions
1231
- });
1232
- try {
1233
- return await withInterrupt(async () => {
1234
- await measurePerf("runtime.exec.start", async () => {
1235
- await withTimeout(client.start(), options.timeoutMs);
1236
- });
1237
- const createdSession = await measurePerf("runtime.exec.create_session", async () => {
1238
- return await withTimeout(client.createSession(absolutePath(options.cwd)), options.timeoutMs);
1239
- });
1240
- const sessionId = createdSession.sessionId;
1241
- await applyRequestedModelIfAdvertised({
1242
- client,
1243
- sessionId,
1244
- requestedModel: options.sessionOptions?.model,
1245
- models: createdSession.models,
1246
- agentCommand: options.agentCommand,
1247
- timeoutMs: options.timeoutMs
1248
- });
1249
- output.setContext({ sessionId });
1250
- const maxRetries = options.promptRetries ?? 0;
1251
- let response;
1252
- promptTurnActive = true;
1253
- for (let attempt = 0;; attempt++) try {
1254
- response = await measurePerf("runtime.exec.prompt", async () => {
1255
- return await withTimeout(client.prompt(sessionId, options.prompt), options.timeoutMs);
1256
- });
1257
- break;
1258
- } catch (error) {
1259
- if (attempt < maxRetries && !promptTurnHadSideEffects && isRetryablePromptError(error)) {
1260
- const delayMs = Math.min(1e3 * 2 ** attempt, 1e4);
1261
- emitPromptRetryNotice({
1262
- error,
1263
- delayMs,
1264
- attempt: attempt + 1,
1265
- maxRetries,
1266
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors
1267
- });
1268
- await waitMs(delayMs);
1269
- if (!promptTurnHadSideEffects) continue;
1270
- }
1271
- promptTurnActive = false;
1272
- throw error;
1273
- }
1274
- promptTurnActive = false;
1275
- output.flush();
1276
- return toPromptResult(response.stopReason, sessionId, client);
1277
- }, async () => {
1278
- await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
1279
- await client.close();
1280
- });
1281
- } finally {
1282
- await client.close();
1283
- }
1284
- }
1285
- async function sendSessionDirect(options) {
1286
- return await runSessionPrompt({
1287
- sessionRecordId: options.sessionId,
1288
- prompt: options.prompt,
1289
- mcpServers: options.mcpServers,
1290
- permissionMode: options.permissionMode,
1291
- resumePolicy: options.resumePolicy,
1292
- nonInteractivePermissions: options.nonInteractivePermissions,
1293
- authCredentials: options.authCredentials,
1294
- authPolicy: options.authPolicy,
1295
- terminal: options.terminal,
1296
- outputFormatter: options.outputFormatter,
1297
- onAcpMessage: options.onAcpMessage,
1298
- onSessionUpdate: options.onSessionUpdate,
1299
- onClientOperation: options.onClientOperation,
1300
- timeoutMs: options.timeoutMs,
1301
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
1302
- verbose: options.verbose,
1303
- client: options.client
1304
- });
1305
- }
1306
- //#endregion
1307
- //#region src/cli/session/queue-owner-runtime.ts
1308
- const QUEUE_OWNER_STARTUP_MAX_ATTEMPTS = 120;
1309
- const QUEUE_OWNER_HEARTBEAT_INTERVAL_MS = 5e3;
1310
- async function submitToRunningOwner(options, waitForCompletion) {
1311
- return await trySubmitToRunningOwner({
1312
- sessionId: options.sessionId,
1313
- message: promptToDisplayText(options.prompt),
1314
- prompt: options.prompt,
1315
- permissionMode: options.permissionMode,
1316
- nonInteractivePermissions: options.nonInteractivePermissions,
1317
- outputFormatter: options.outputFormatter,
1318
- errorEmissionPolicy: options.errorEmissionPolicy,
1319
- timeoutMs: options.timeoutMs,
1320
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
1321
- waitForCompletion,
1322
- verbose: options.verbose,
1323
- sessionOptions: options.sessionOptions
1324
- });
1325
- }
1326
- async function runSessionQueueOwner(options) {
1327
- const lease = await tryAcquireQueueOwnerLease(options.sessionId);
1328
- if (!lease) return;
1329
- const sessionRecord = await resolveSessionRecord(options.sessionId);
1330
- let owner;
1331
- let heartbeatTimer;
1332
- const sharedClient = new AcpClient({
1333
- agentCommand: sessionRecord.agentCommand,
1334
- cwd: absolutePath(sessionRecord.cwd),
1335
- mcpServers: options.mcpServers,
1336
- permissionMode: options.permissionMode,
1337
- nonInteractivePermissions: options.nonInteractivePermissions,
1338
- authCredentials: options.authCredentials,
1339
- authPolicy: options.authPolicy,
1340
- terminal: options.terminal,
1341
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
1342
- verbose: options.verbose,
1343
- sessionOptions: mergeSessionOptions(options.sessionOptions, sessionOptionsFromRecord(sessionRecord))
1344
- });
1345
- const ttlMs = normalizeQueueOwnerTtlMs(options.ttlMs);
1346
- const maxQueueDepth = Math.max(1, Math.round(options.maxQueueDepth ?? 16));
1347
- const taskPollTimeoutMs = ttlMs === 0 ? void 0 : ttlMs;
1348
- const initialTaskPollTimeoutMs = taskPollTimeoutMs == null ? void 0 : Math.max(taskPollTimeoutMs, 1e3);
1349
- const turnController = new QueueOwnerTurnController({
1350
- withTimeout: async (run, timeoutMs) => await withTimeout(run(), timeoutMs),
1351
- setSessionModeFallback: async (modeId, timeoutMs) => {
1352
- await runSessionSetModeDirect({
1353
- sessionRecordId: options.sessionId,
1354
- modeId,
1355
- mcpServers: options.mcpServers,
1356
- nonInteractivePermissions: options.nonInteractivePermissions,
1357
- authCredentials: options.authCredentials,
1358
- authPolicy: options.authPolicy,
1359
- terminal: options.terminal,
1360
- timeoutMs,
1361
- verbose: options.verbose
1362
- });
1363
- },
1364
- setSessionModelFallback: async (modelId, timeoutMs) => {
1365
- await runSessionSetModelDirect({
1366
- sessionRecordId: options.sessionId,
1367
- modelId,
1368
- mcpServers: options.mcpServers,
1369
- nonInteractivePermissions: options.nonInteractivePermissions,
1370
- authCredentials: options.authCredentials,
1371
- authPolicy: options.authPolicy,
1372
- terminal: options.terminal,
1373
- timeoutMs,
1374
- verbose: options.verbose
1375
- });
1376
- },
1377
- setSessionConfigOptionFallback: async (configId, value, timeoutMs) => {
1378
- return (await runSessionSetConfigOptionDirect({
1379
- sessionRecordId: options.sessionId,
1380
- configId,
1381
- value,
1382
- mcpServers: options.mcpServers,
1383
- nonInteractivePermissions: options.nonInteractivePermissions,
1384
- authCredentials: options.authCredentials,
1385
- authPolicy: options.authPolicy,
1386
- terminal: options.terminal,
1387
- timeoutMs,
1388
- verbose: options.verbose
1389
- })).response;
1390
- }
1391
- });
1392
- const applyPendingCancel = async () => {
1393
- return await turnController.applyPendingCancel();
1394
- };
1395
- const scheduleApplyPendingCancel = () => {
1396
- applyPendingCancel().catch((error) => {
1397
- if (options.verbose) process.stderr.write(`[acpx] failed to apply deferred cancel: ${formatErrorMessage(error)}\n`);
1398
- });
1399
- };
1400
- const setActiveController = (controller) => {
1401
- turnController.setActiveController(controller);
1402
- scheduleApplyPendingCancel();
1403
- };
1404
- const clearActiveController = () => {
1405
- turnController.clearActiveController();
1406
- };
1407
- const closeActiveBackendSession = async (timeoutMs) => {
1408
- const latestRecord = await resolveSessionRecord(options.sessionId);
1409
- if (!sharedClient.supportsCloseSession()) return false;
1410
- await withTimeout(sharedClient.closeSession(latestRecord.acpSessionId), timeoutMs);
1411
- return true;
1412
- };
1413
- const runPromptTurn = async (run) => {
1414
- turnController.beginTurn();
1415
- try {
1416
- return await run();
1417
- } finally {
1418
- turnController.endTurn();
1419
- }
1420
- };
1421
- try {
1422
- owner = await SessionQueueOwner.start(lease, {
1423
- cancelPrompt: async () => {
1424
- if (!await turnController.requestCancel()) return false;
1425
- await applyPendingCancel();
1426
- return true;
1427
- },
1428
- closeSession: async (timeoutMs) => await closeActiveBackendSession(timeoutMs),
1429
- setSessionMode: async (modeId, timeoutMs) => {
1430
- await turnController.setSessionMode(modeId, timeoutMs);
1431
- },
1432
- setSessionModel: async (modelId, timeoutMs) => {
1433
- await turnController.setSessionModel(modelId, timeoutMs);
1434
- },
1435
- setSessionConfigOption: async (configId, value, timeoutMs) => {
1436
- return await turnController.setSessionConfigOption(configId, value, timeoutMs);
1437
- }
1438
- }, {
1439
- maxQueueDepth,
1440
- onQueueDepthChanged: (queueDepth) => {
1441
- setPerfGauge("queue.owner.depth", queueDepth);
1442
- refreshQueueOwnerLease(lease, { queueDepth }).catch(() => {});
1443
- }
1444
- });
1445
- if (options.verbose) process.stderr.write(`[acpx] queue owner ready for session ${options.sessionId} (ttlMs=${ttlMs}, maxQueueDepth=${maxQueueDepth})\n`);
1446
- await refreshQueueOwnerLease(lease, { queueDepth: owner.queueDepth() }).catch(() => {});
1447
- heartbeatTimer = setInterval(() => {
1448
- refreshQueueOwnerLease(lease, { queueDepth: owner?.queueDepth() ?? 0 }).catch(() => {});
1449
- }, QUEUE_OWNER_HEARTBEAT_INTERVAL_MS);
1450
- let isFirstTask = true;
1451
- while (true) {
1452
- const pollTimeoutMs = isFirstTask ? initialTaskPollTimeoutMs : taskPollTimeoutMs;
1453
- const task = await owner.nextTask(pollTimeoutMs);
1454
- if (!task) break;
1455
- isFirstTask = false;
1456
- await runPromptTurn(async () => {
1457
- try {
1458
- await runQueuedTask(options.sessionId, task, {
1459
- sharedClient,
1460
- verbose: options.verbose,
1461
- mcpServers: options.mcpServers,
1462
- nonInteractivePermissions: options.nonInteractivePermissions,
1463
- authCredentials: options.authCredentials,
1464
- authPolicy: options.authPolicy,
1465
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
1466
- promptRetries: options.promptRetries,
1467
- sessionOptions: options.sessionOptions,
1468
- onClientAvailable: setActiveController,
1469
- onClientClosed: clearActiveController,
1470
- onPromptActive: async () => {
1471
- turnController.markPromptActive();
1472
- await applyPendingCancel();
1473
- }
1474
- });
1475
- } finally {
1476
- checkpointPerfMetricsCapture();
1477
- }
1478
- });
1479
- }
1480
- } finally {
1481
- if (heartbeatTimer) clearInterval(heartbeatTimer);
1482
- turnController.beginClosing();
1483
- if (owner) await owner.close();
1484
- await sharedClient.close().catch(() => {});
1485
- try {
1486
- const record = await resolveSessionRecord(options.sessionId);
1487
- applyLifecycleSnapshotToRecord(record, sharedClient.getAgentLifecycleSnapshot());
1488
- await writeSessionRecord(record);
1489
- } catch {}
1490
- await releaseQueueOwnerLease(lease);
1491
- if (options.verbose) process.stderr.write(`[acpx] queue owner stopped for session ${options.sessionId}\n`);
1492
- }
1493
- }
1494
- async function sendSession(options) {
1495
- const waitForCompletion = options.waitForCompletion !== false;
1496
- const queuedToOwner = await submitToRunningOwner(options, waitForCompletion);
1497
- if (queuedToOwner) return queuedToOwner;
1498
- spawnQueueOwnerProcess(queueOwnerRuntimeOptionsFromSend(options));
1499
- for (let attempt = 0; attempt < QUEUE_OWNER_STARTUP_MAX_ATTEMPTS; attempt += 1) {
1500
- const queued = await submitToRunningOwner(options, waitForCompletion);
1501
- if (queued) return queued;
1502
- await waitMs(50);
1503
- }
1504
- throw new Error(`Session queue owner failed to start for session ${options.sessionId}`);
1505
- }
1506
- //#endregion
1507
- //#region src/session/session.ts
1508
- var session_exports = /* @__PURE__ */ __exportAll({
1509
- DEFAULT_HISTORY_LIMIT: () => 20,
1510
- DEFAULT_QUEUE_OWNER_TTL_MS: () => DEFAULT_QUEUE_OWNER_TTL_MS,
1511
- InterruptedError: () => InterruptedError,
1512
- TimeoutError: () => TimeoutError,
1513
- cancelSessionPrompt: () => cancelSessionPrompt,
1514
- closeSession: () => closeSession,
1515
- createSession: () => createSession,
1516
- createSessionWithClient: () => createSessionWithClient,
1517
- ensureSession: () => ensureSession,
1518
- findGitRepositoryRoot: () => findGitRepositoryRoot,
1519
- findSession: () => findSession,
1520
- findSessionByDirectoryWalk: () => findSessionByDirectoryWalk,
1521
- isProcessAlive: () => isProcessAlive,
1522
- listSessions: () => listSessions,
1523
- listSessionsForAgent: () => listSessionsForAgent,
1524
- normalizeQueueOwnerTtlMs: () => normalizeQueueOwnerTtlMs,
1525
- pruneSessions: () => pruneSessions,
1526
- runOnce: () => runOnce,
1527
- runQueuedTask: () => runQueuedTask,
1528
- runSessionQueueOwner: () => runSessionQueueOwner,
1529
- sendSession: () => sendSession,
1530
- sendSessionDirect: () => sendSessionDirect,
1531
- setSessionConfigOption: () => setSessionConfigOption,
1532
- setSessionMode: () => setSessionMode,
1533
- setSessionModel: () => setSessionModel
1534
- });
1535
- //#endregion
1536
- export { buildQueueOwnerArgOverride as a, createSessionWithClient as c, sendSessionDirect as i, cancelSessionPrompt as l, runSessionQueueOwner as n, flushPerfMetricsCapture as o, runOnce as r, installPerfMetricsCapture as s, session_exports as t, DEFAULT_QUEUE_OWNER_TTL_MS as u };
1537
-
1538
- //# sourceMappingURL=session-CDaQe6BH.js.map