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