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