acpx 0.1.15 → 0.2.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.
@@ -0,0 +1,1571 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-CjeV3_4I.js";
2
+ import { t as isAcpJsonRpcMessage } from "./acp-jsonrpc-CGT_1Mel.js";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { createHash, randomUUID } from "node:crypto";
6
+ import os from "node:os";
7
+ import net from "node:net";
8
+
9
+ //#region src/errors.ts
10
+ var AcpxOperationalError = class extends Error {
11
+ outputCode;
12
+ detailCode;
13
+ origin;
14
+ retryable;
15
+ acp;
16
+ outputAlreadyEmitted;
17
+ constructor(message, options) {
18
+ super(message, options);
19
+ this.name = new.target.name;
20
+ this.outputCode = options?.outputCode;
21
+ this.detailCode = options?.detailCode;
22
+ this.origin = options?.origin;
23
+ this.retryable = options?.retryable;
24
+ this.acp = options?.acp;
25
+ this.outputAlreadyEmitted = options?.outputAlreadyEmitted;
26
+ }
27
+ };
28
+ var SessionNotFoundError = class extends AcpxOperationalError {
29
+ sessionId;
30
+ constructor(sessionId) {
31
+ super(`Session not found: ${sessionId}`);
32
+ this.sessionId = sessionId;
33
+ }
34
+ };
35
+ var SessionResolutionError = class extends AcpxOperationalError {};
36
+ var AgentSpawnError = class extends AcpxOperationalError {
37
+ agentCommand;
38
+ constructor(agentCommand, cause) {
39
+ super(`Failed to spawn agent command: ${agentCommand}`, { cause: cause instanceof Error ? cause : void 0 });
40
+ this.agentCommand = agentCommand;
41
+ }
42
+ };
43
+ var GeminiAcpStartupTimeoutError = class extends AcpxOperationalError {
44
+ constructor(message, options) {
45
+ super(message, {
46
+ outputCode: "TIMEOUT",
47
+ detailCode: "GEMINI_ACP_STARTUP_TIMEOUT",
48
+ origin: "acp",
49
+ ...options
50
+ });
51
+ }
52
+ };
53
+ var SessionModeReplayError = class extends AcpxOperationalError {
54
+ constructor(message, options) {
55
+ super(message, {
56
+ outputCode: "RUNTIME",
57
+ detailCode: "SESSION_MODE_REPLAY_FAILED",
58
+ origin: "acp",
59
+ ...options
60
+ });
61
+ }
62
+ };
63
+ var ClaudeAcpSessionCreateTimeoutError = class extends AcpxOperationalError {
64
+ constructor(message, options) {
65
+ super(message, {
66
+ outputCode: "TIMEOUT",
67
+ detailCode: "CLAUDE_ACP_SESSION_CREATE_TIMEOUT",
68
+ origin: "acp",
69
+ ...options
70
+ });
71
+ }
72
+ };
73
+ var CopilotAcpUnsupportedError = class extends AcpxOperationalError {
74
+ constructor(message, options) {
75
+ super(message, {
76
+ outputCode: "RUNTIME",
77
+ detailCode: "COPILOT_ACP_UNSUPPORTED",
78
+ origin: "acp",
79
+ ...options
80
+ });
81
+ }
82
+ };
83
+ var AuthPolicyError = class extends AcpxOperationalError {
84
+ constructor(message, options) {
85
+ super(message, {
86
+ outputCode: "RUNTIME",
87
+ detailCode: "AUTH_REQUIRED",
88
+ origin: "acp",
89
+ ...options
90
+ });
91
+ }
92
+ };
93
+ var QueueConnectionError = class extends AcpxOperationalError {};
94
+ var QueueProtocolError = class extends AcpxOperationalError {};
95
+ var PermissionDeniedError = class extends AcpxOperationalError {};
96
+ var PermissionPromptUnavailableError = class extends AcpxOperationalError {
97
+ constructor() {
98
+ super("Permission prompt unavailable in non-interactive mode");
99
+ }
100
+ };
101
+
102
+ //#endregion
103
+ //#region src/prompt-content.ts
104
+ function asRecord$3(value) {
105
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
106
+ return value;
107
+ }
108
+ function isTextBlock(value) {
109
+ const record = asRecord$3(value);
110
+ return record?.type === "text" && typeof record.text === "string";
111
+ }
112
+ function isImageBlock(value) {
113
+ const record = asRecord$3(value);
114
+ return record?.type === "image" && typeof record.mimeType === "string" && typeof record.data === "string";
115
+ }
116
+ function isResourceLinkBlock(value) {
117
+ const record = asRecord$3(value);
118
+ return record?.type === "resource_link" && typeof record.uri === "string" && (record.title === void 0 || typeof record.title === "string") && (record.name === void 0 || typeof record.name === "string");
119
+ }
120
+ function isResourcePayload(value) {
121
+ const record = asRecord$3(value);
122
+ if (!record || typeof record.uri !== "string") return false;
123
+ return record.text === void 0 || typeof record.text === "string";
124
+ }
125
+ function isResourceBlock(value) {
126
+ const record = asRecord$3(value);
127
+ return record?.type === "resource" && isResourcePayload(record.resource);
128
+ }
129
+ function isContentBlock(value) {
130
+ return isTextBlock(value) || isImageBlock(value) || isResourceLinkBlock(value) || isResourceBlock(value);
131
+ }
132
+ function isPromptInput(value) {
133
+ return Array.isArray(value) && value.every((entry) => isContentBlock(entry));
134
+ }
135
+ function textPrompt(text) {
136
+ return [{
137
+ type: "text",
138
+ text
139
+ }];
140
+ }
141
+ function parseStructuredPrompt(source) {
142
+ if (!source.startsWith("[")) return;
143
+ try {
144
+ const parsed = JSON.parse(source);
145
+ return isPromptInput(parsed) ? parsed : void 0;
146
+ } catch {
147
+ return;
148
+ }
149
+ }
150
+ function parsePromptSource(source) {
151
+ const trimmed = source.trim();
152
+ const structured = parseStructuredPrompt(trimmed);
153
+ if (structured) return structured;
154
+ if (!trimmed) return [];
155
+ return textPrompt(trimmed);
156
+ }
157
+ function mergePromptSourceWithText(source, suffixText) {
158
+ const prompt = parsePromptSource(source);
159
+ const appended = suffixText.trim();
160
+ if (!appended) return prompt;
161
+ if (prompt.length === 0) return textPrompt(appended);
162
+ return [...prompt, ...textPrompt(appended)];
163
+ }
164
+ function promptToDisplayText(prompt) {
165
+ return prompt.map((block) => {
166
+ switch (block.type) {
167
+ case "text": return block.text;
168
+ case "resource_link": return block.title ?? block.name ?? block.uri;
169
+ case "resource": return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
170
+ case "image": return `[image] ${block.mimeType}`;
171
+ default: return "";
172
+ }
173
+ }).filter((entry) => entry.trim().length > 0).join("\n\n").trim();
174
+ }
175
+
176
+ //#endregion
177
+ //#region src/acp-error-shapes.ts
178
+ const RESOURCE_NOT_FOUND_ACP_CODES = new Set([-32001, -32002]);
179
+ function asRecord$2(value) {
180
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
181
+ return value;
182
+ }
183
+ function toAcpErrorPayload(value) {
184
+ const record = asRecord$2(value);
185
+ if (!record) return;
186
+ if (typeof record.code !== "number" || !Number.isFinite(record.code)) return;
187
+ if (typeof record.message !== "string" || record.message.length === 0) return;
188
+ return {
189
+ code: record.code,
190
+ message: record.message,
191
+ data: record.data
192
+ };
193
+ }
194
+ function extractAcpErrorInternal(value, depth) {
195
+ if (depth > 5) return;
196
+ const direct = toAcpErrorPayload(value);
197
+ if (direct) return direct;
198
+ const record = asRecord$2(value);
199
+ if (!record) return;
200
+ if ("error" in record) {
201
+ const nested = extractAcpErrorInternal(record.error, depth + 1);
202
+ if (nested) return nested;
203
+ }
204
+ if ("cause" in record) {
205
+ const nested = extractAcpErrorInternal(record.cause, depth + 1);
206
+ if (nested) return nested;
207
+ }
208
+ }
209
+ function formatUnknownErrorMessage(error) {
210
+ if (error instanceof Error) return error.message;
211
+ if (error && typeof error === "object") {
212
+ const maybeMessage = error.message;
213
+ if (typeof maybeMessage === "string" && maybeMessage.length > 0) return maybeMessage;
214
+ try {
215
+ return JSON.stringify(error);
216
+ } catch {}
217
+ }
218
+ return String(error);
219
+ }
220
+ function isSessionNotFoundText(value) {
221
+ if (typeof value !== "string") return false;
222
+ const normalized = value.toLowerCase();
223
+ return normalized.includes("resource_not_found") || normalized.includes("resource not found") || normalized.includes("session not found") || normalized.includes("unknown session") || normalized.includes("invalid session identifier");
224
+ }
225
+ function hasSessionNotFoundHint(value, depth = 0) {
226
+ if (depth > 4) return false;
227
+ if (isSessionNotFoundText(value)) return true;
228
+ if (Array.isArray(value)) return value.some((entry) => hasSessionNotFoundHint(entry, depth + 1));
229
+ const record = asRecord$2(value);
230
+ if (!record) return false;
231
+ return Object.values(record).some((entry) => hasSessionNotFoundHint(entry, depth + 1));
232
+ }
233
+ function extractAcpError(error) {
234
+ return extractAcpErrorInternal(error, 0);
235
+ }
236
+ function isAcpResourceNotFoundError(error) {
237
+ const acp = extractAcpError(error);
238
+ if (acp && RESOURCE_NOT_FOUND_ACP_CODES.has(acp.code)) return true;
239
+ if (acp) {
240
+ if (isSessionNotFoundText(acp.message)) return true;
241
+ if (hasSessionNotFoundHint(acp.data)) return true;
242
+ }
243
+ return isSessionNotFoundText(formatUnknownErrorMessage(error));
244
+ }
245
+
246
+ //#endregion
247
+ //#region src/types.ts
248
+ const EXIT_CODES = {
249
+ SUCCESS: 0,
250
+ ERROR: 1,
251
+ USAGE: 2,
252
+ TIMEOUT: 3,
253
+ NO_SESSION: 4,
254
+ PERMISSION_DENIED: 5,
255
+ INTERRUPTED: 130
256
+ };
257
+ const OUTPUT_FORMATS = [
258
+ "text",
259
+ "json",
260
+ "quiet"
261
+ ];
262
+ const AUTH_POLICIES = ["skip", "fail"];
263
+ const NON_INTERACTIVE_PERMISSION_POLICIES = ["deny", "fail"];
264
+ const OUTPUT_ERROR_CODES = [
265
+ "NO_SESSION",
266
+ "TIMEOUT",
267
+ "PERMISSION_DENIED",
268
+ "PERMISSION_PROMPT_UNAVAILABLE",
269
+ "RUNTIME",
270
+ "USAGE"
271
+ ];
272
+ const OUTPUT_ERROR_ORIGINS = [
273
+ "cli",
274
+ "runtime",
275
+ "queue",
276
+ "acp"
277
+ ];
278
+ const SESSION_RECORD_SCHEMA = "acpx.session.v1";
279
+
280
+ //#endregion
281
+ //#region src/error-normalization.ts
282
+ const AUTH_REQUIRED_ACP_CODES = new Set([-32e3]);
283
+ const QUERY_CLOSED_BEFORE_RESPONSE_DETAIL = "query closed before response received";
284
+ function asRecord$1(value) {
285
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
286
+ return value;
287
+ }
288
+ function isAuthRequiredMessage(value) {
289
+ if (!value) return false;
290
+ const normalized = value.toLowerCase();
291
+ return normalized.includes("auth required") || normalized.includes("authentication required") || normalized.includes("authorization required") || normalized.includes("credential required") || normalized.includes("credentials required") || normalized.includes("token required") || normalized.includes("login required");
292
+ }
293
+ function isAcpAuthRequiredPayload(acp) {
294
+ if (!acp) return false;
295
+ if (!AUTH_REQUIRED_ACP_CODES.has(acp.code)) return false;
296
+ if (isAuthRequiredMessage(acp.message)) return true;
297
+ const data = asRecord$1(acp.data);
298
+ if (!data) return false;
299
+ if (data.authRequired === true) return true;
300
+ const methodId = data.methodId;
301
+ if (typeof methodId === "string" && methodId.trim().length > 0) return true;
302
+ const methods = data.methods;
303
+ if (Array.isArray(methods) && methods.length > 0) return true;
304
+ return false;
305
+ }
306
+ function isOutputErrorCode$1(value) {
307
+ return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
308
+ }
309
+ function isOutputErrorOrigin$1(value) {
310
+ return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
311
+ }
312
+ function readOutputErrorMeta(error) {
313
+ const record = asRecord$1(error);
314
+ if (!record) return {};
315
+ return {
316
+ outputCode: isOutputErrorCode$1(record.outputCode) ? record.outputCode : void 0,
317
+ detailCode: typeof record.detailCode === "string" && record.detailCode.trim().length > 0 ? record.detailCode : void 0,
318
+ origin: isOutputErrorOrigin$1(record.origin) ? record.origin : void 0,
319
+ retryable: typeof record.retryable === "boolean" ? record.retryable : void 0,
320
+ acp: extractAcpError(record.acp)
321
+ };
322
+ }
323
+ function isTimeoutLike(error) {
324
+ return error instanceof Error && error.name === "TimeoutError";
325
+ }
326
+ function isNoSessionLike(error) {
327
+ return error instanceof Error && error.name === "NoSessionError";
328
+ }
329
+ function isUsageLike(error) {
330
+ if (!(error instanceof Error)) return false;
331
+ return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord$1(error)?.code === "commander.invalidArgument";
332
+ }
333
+ function formatErrorMessage(error) {
334
+ return formatUnknownErrorMessage(error);
335
+ }
336
+ function isAcpQueryClosedBeforeResponseError(error) {
337
+ const acp = extractAcpError(error);
338
+ if (!acp || acp.code !== -32603) return false;
339
+ const details = asRecord$1(acp.data)?.details;
340
+ if (typeof details !== "string") return false;
341
+ return details.toLowerCase().includes(QUERY_CLOSED_BEFORE_RESPONSE_DETAIL);
342
+ }
343
+ function mapErrorCode(error) {
344
+ if (error instanceof PermissionPromptUnavailableError) return "PERMISSION_PROMPT_UNAVAILABLE";
345
+ if (error instanceof PermissionDeniedError) return "PERMISSION_DENIED";
346
+ if (isTimeoutLike(error)) return "TIMEOUT";
347
+ if (isNoSessionLike(error) || isAcpResourceNotFoundError(error)) return "NO_SESSION";
348
+ if (isUsageLike(error)) return "USAGE";
349
+ }
350
+ function normalizeOutputError(error, options = {}) {
351
+ const meta = readOutputErrorMeta(error);
352
+ let code = mapErrorCode(error) ?? options.defaultCode ?? "RUNTIME";
353
+ if (meta.outputCode) code = meta.outputCode;
354
+ if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) code = "NO_SESSION";
355
+ const acp = options.acp ?? meta.acp ?? extractAcpError(error);
356
+ const detailCode = meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
357
+ return {
358
+ code,
359
+ message: formatErrorMessage(error),
360
+ detailCode,
361
+ origin: meta.origin ?? options.origin,
362
+ retryable: meta.retryable ?? options.retryable,
363
+ acp
364
+ };
365
+ }
366
+ function exitCodeForOutputErrorCode(code) {
367
+ switch (code) {
368
+ case "USAGE": return EXIT_CODES.USAGE;
369
+ case "TIMEOUT": return EXIT_CODES.TIMEOUT;
370
+ case "NO_SESSION": return EXIT_CODES.NO_SESSION;
371
+ case "PERMISSION_DENIED":
372
+ case "PERMISSION_PROMPT_UNAVAILABLE": return EXIT_CODES.PERMISSION_DENIED;
373
+ default: return EXIT_CODES.ERROR;
374
+ }
375
+ }
376
+
377
+ //#endregion
378
+ //#region src/perf-metrics.ts
379
+ const counters = /* @__PURE__ */ new Map();
380
+ const gauges = /* @__PURE__ */ new Map();
381
+ const timings = /* @__PURE__ */ new Map();
382
+ function hrNow() {
383
+ return process.hrtime.bigint();
384
+ }
385
+ function durationMs(start) {
386
+ return Number(process.hrtime.bigint() - start) / 1e6;
387
+ }
388
+ function roundMetric(value) {
389
+ return Number(value.toFixed(3));
390
+ }
391
+ function incrementPerfCounter(name, delta = 1) {
392
+ counters.set(name, (counters.get(name) ?? 0) + delta);
393
+ }
394
+ function setPerfGauge(name, value) {
395
+ gauges.set(name, value);
396
+ }
397
+ function recordPerfDuration(name, durationMsValue) {
398
+ const next = timings.get(name) ?? {
399
+ count: 0,
400
+ totalMs: 0,
401
+ maxMs: 0
402
+ };
403
+ next.count += 1;
404
+ next.totalMs += durationMsValue;
405
+ next.maxMs = Math.max(next.maxMs, durationMsValue);
406
+ timings.set(name, next);
407
+ }
408
+ async function measurePerf(name, run) {
409
+ const startedAt = hrNow();
410
+ try {
411
+ return await run();
412
+ } finally {
413
+ recordPerfDuration(name, durationMs(startedAt));
414
+ }
415
+ }
416
+ function startPerfTimer(name) {
417
+ const startedAt = hrNow();
418
+ return () => {
419
+ const elapsedMs = durationMs(startedAt);
420
+ recordPerfDuration(name, elapsedMs);
421
+ return elapsedMs;
422
+ };
423
+ }
424
+ function getPerfMetricsSnapshot() {
425
+ return {
426
+ counters: Object.fromEntries(counters.entries()),
427
+ gauges: Object.fromEntries(gauges.entries()),
428
+ timings: Object.fromEntries([...timings.entries()].map(([name, bucket]) => [name, {
429
+ count: bucket.count,
430
+ totalMs: roundMetric(bucket.totalMs),
431
+ maxMs: roundMetric(bucket.maxMs)
432
+ }]))
433
+ };
434
+ }
435
+ function resetPerfMetrics() {
436
+ counters.clear();
437
+ gauges.clear();
438
+ timings.clear();
439
+ }
440
+ function formatPerfMetric(name, durationMsValue) {
441
+ return `${name}=${roundMetric(durationMsValue)}ms`;
442
+ }
443
+
444
+ //#endregion
445
+ //#region src/queue-paths.ts
446
+ function shortHash(value, length) {
447
+ return createHash("sha256").update(value).digest("hex").slice(0, length);
448
+ }
449
+ function queueKeyForSession(sessionId) {
450
+ return shortHash(sessionId, 24);
451
+ }
452
+ function queueBaseDir(homeDir = os.homedir()) {
453
+ return path.join(homeDir, ".acpx", "queues");
454
+ }
455
+ function queueSocketBaseDir(homeDir = os.homedir()) {
456
+ if (process.platform === "win32") return;
457
+ return path.join("/tmp", `acpx-${shortHash(homeDir, 10)}`);
458
+ }
459
+ function queueLockFilePath(sessionId, homeDir = os.homedir()) {
460
+ return path.join(queueBaseDir(homeDir), `${queueKeyForSession(sessionId)}.lock`);
461
+ }
462
+ function queueSocketPath(sessionId, homeDir = os.homedir()) {
463
+ const key = queueKeyForSession(sessionId);
464
+ if (process.platform === "win32") return `\\\\.\\pipe\\acpx-${key}`;
465
+ return path.join(queueSocketBaseDir(homeDir) ?? "/tmp", `${key}.sock`);
466
+ }
467
+
468
+ //#endregion
469
+ //#region src/queue-lease-store.ts
470
+ const PROCESS_EXIT_GRACE_MS = 1500;
471
+ const PROCESS_POLL_MS = 50;
472
+ const QUEUE_OWNER_STALE_HEARTBEAT_MS = 15e3;
473
+ function parseQueueOwnerRecord(raw) {
474
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
475
+ const record = raw;
476
+ if (!Number.isInteger(record.pid) || record.pid <= 0 || typeof record.sessionId !== "string" || typeof record.socketPath !== "string" || typeof record.createdAt !== "string" || typeof record.heartbeatAt !== "string" || !Number.isInteger(record.ownerGeneration) || record.ownerGeneration <= 0 || !Number.isInteger(record.queueDepth) || record.queueDepth < 0) return null;
477
+ return {
478
+ pid: record.pid,
479
+ sessionId: record.sessionId,
480
+ socketPath: record.socketPath,
481
+ createdAt: record.createdAt,
482
+ heartbeatAt: record.heartbeatAt,
483
+ ownerGeneration: record.ownerGeneration,
484
+ queueDepth: record.queueDepth
485
+ };
486
+ }
487
+ function createOwnerGeneration() {
488
+ return Date.now() * 1e3 + Math.floor(Math.random() * 1e3);
489
+ }
490
+ function nowIso() {
491
+ return (/* @__PURE__ */ new Date()).toISOString();
492
+ }
493
+ function isQueueOwnerHeartbeatStale(owner) {
494
+ const heartbeatMs = Date.parse(owner.heartbeatAt);
495
+ if (!Number.isFinite(heartbeatMs)) return true;
496
+ return Date.now() - heartbeatMs > QUEUE_OWNER_STALE_HEARTBEAT_MS;
497
+ }
498
+ async function ensureQueueDir() {
499
+ await fs.mkdir(queueBaseDir(), { recursive: true });
500
+ const socketDir = queueSocketBaseDir();
501
+ if (socketDir) await fs.mkdir(socketDir, { recursive: true });
502
+ }
503
+ async function removeSocketFile(socketPath) {
504
+ if (process.platform === "win32") return;
505
+ try {
506
+ await fs.unlink(socketPath);
507
+ } catch (error) {
508
+ if (error.code !== "ENOENT") throw error;
509
+ }
510
+ }
511
+ async function waitForProcessExit(pid, timeoutMs) {
512
+ const deadline = Date.now() + Math.max(0, timeoutMs);
513
+ while (Date.now() <= deadline) {
514
+ if (!isProcessAlive(pid)) return true;
515
+ await waitMs(PROCESS_POLL_MS);
516
+ }
517
+ return !isProcessAlive(pid);
518
+ }
519
+ async function cleanupStaleQueueOwner(sessionId, owner) {
520
+ const lockPath = queueLockFilePath(sessionId);
521
+ await removeSocketFile(owner?.socketPath ?? queueSocketPath(sessionId)).catch(() => {});
522
+ await fs.unlink(lockPath).catch((error) => {
523
+ if (error.code !== "ENOENT") throw error;
524
+ });
525
+ }
526
+ async function readQueueOwnerRecord(sessionId) {
527
+ const lockPath = queueLockFilePath(sessionId);
528
+ try {
529
+ const payload = await fs.readFile(lockPath, "utf8");
530
+ return parseQueueOwnerRecord(JSON.parse(payload)) ?? void 0;
531
+ } catch {
532
+ return;
533
+ }
534
+ }
535
+ function isProcessAlive(pid) {
536
+ if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
537
+ try {
538
+ process.kill(pid, 0);
539
+ return true;
540
+ } catch {
541
+ return false;
542
+ }
543
+ }
544
+ async function terminateProcess(pid) {
545
+ if (!isProcessAlive(pid)) return false;
546
+ try {
547
+ process.kill(pid, "SIGTERM");
548
+ } catch {
549
+ return false;
550
+ }
551
+ if (await waitForProcessExit(pid, PROCESS_EXIT_GRACE_MS)) return true;
552
+ try {
553
+ process.kill(pid, "SIGKILL");
554
+ } catch {
555
+ return false;
556
+ }
557
+ await waitForProcessExit(pid, PROCESS_EXIT_GRACE_MS);
558
+ return true;
559
+ }
560
+ async function ensureOwnerIsUsable(sessionId, owner) {
561
+ const alive = isProcessAlive(owner.pid);
562
+ const stale = isQueueOwnerHeartbeatStale(owner);
563
+ if (alive && !stale) return true;
564
+ if (alive) await terminateProcess(owner.pid).catch(() => {});
565
+ await cleanupStaleQueueOwner(sessionId, owner);
566
+ return false;
567
+ }
568
+ async function readQueueOwnerStatus(sessionId) {
569
+ const owner = await readQueueOwnerRecord(sessionId);
570
+ if (!owner) return;
571
+ const alive = await ensureOwnerIsUsable(sessionId, owner);
572
+ if (!alive) return;
573
+ return {
574
+ pid: owner.pid,
575
+ socketPath: owner.socketPath,
576
+ heartbeatAt: owner.heartbeatAt,
577
+ ownerGeneration: owner.ownerGeneration,
578
+ queueDepth: owner.queueDepth,
579
+ alive,
580
+ stale: isQueueOwnerHeartbeatStale(owner)
581
+ };
582
+ }
583
+ async function tryAcquireQueueOwnerLease(sessionId, nowIsoFactory = nowIso) {
584
+ await ensureQueueDir();
585
+ const lockPath = queueLockFilePath(sessionId);
586
+ const socketPath = queueSocketPath(sessionId);
587
+ const createdAt = nowIsoFactory();
588
+ const ownerGeneration = createOwnerGeneration();
589
+ const payload = JSON.stringify({
590
+ pid: process.pid,
591
+ sessionId,
592
+ socketPath,
593
+ createdAt,
594
+ heartbeatAt: createdAt,
595
+ ownerGeneration,
596
+ queueDepth: 0
597
+ }, null, 2);
598
+ try {
599
+ await fs.writeFile(lockPath, `${payload}\n`, {
600
+ encoding: "utf8",
601
+ flag: "wx"
602
+ });
603
+ await removeSocketFile(socketPath).catch(() => {});
604
+ return {
605
+ sessionId,
606
+ lockPath,
607
+ socketPath,
608
+ createdAt,
609
+ ownerGeneration
610
+ };
611
+ } catch (error) {
612
+ if (error.code !== "EEXIST") throw error;
613
+ const owner = await readQueueOwnerRecord(sessionId);
614
+ if (!owner) {
615
+ await cleanupStaleQueueOwner(sessionId, owner);
616
+ return;
617
+ }
618
+ if (!isProcessAlive(owner.pid) || isQueueOwnerHeartbeatStale(owner)) {
619
+ if (isProcessAlive(owner.pid)) await terminateProcess(owner.pid).catch(() => {});
620
+ await cleanupStaleQueueOwner(sessionId, owner);
621
+ }
622
+ return;
623
+ }
624
+ }
625
+ async function refreshQueueOwnerLease(lease, options, nowIsoFactory = nowIso) {
626
+ const payload = JSON.stringify({
627
+ pid: process.pid,
628
+ sessionId: lease.sessionId,
629
+ socketPath: lease.socketPath,
630
+ createdAt: lease.createdAt,
631
+ heartbeatAt: nowIsoFactory(),
632
+ ownerGeneration: lease.ownerGeneration,
633
+ queueDepth: Math.max(0, Math.round(options.queueDepth))
634
+ }, null, 2);
635
+ await fs.writeFile(lease.lockPath, `${payload}\n`, { encoding: "utf8" });
636
+ }
637
+ async function releaseQueueOwnerLease(lease) {
638
+ await removeSocketFile(lease.socketPath).catch(() => {});
639
+ await fs.unlink(lease.lockPath).catch((error) => {
640
+ if (error.code !== "ENOENT") throw error;
641
+ });
642
+ }
643
+ async function terminateQueueOwnerForSession(sessionId) {
644
+ const owner = await readQueueOwnerRecord(sessionId);
645
+ if (!owner) return;
646
+ if (isProcessAlive(owner.pid)) await terminateProcess(owner.pid);
647
+ await cleanupStaleQueueOwner(sessionId, owner);
648
+ }
649
+ async function waitMs(ms) {
650
+ await new Promise((resolve) => {
651
+ setTimeout(resolve, ms);
652
+ });
653
+ }
654
+
655
+ //#endregion
656
+ //#region src/queue-messages.ts
657
+ function asRecord(value) {
658
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
659
+ return value;
660
+ }
661
+ function isPermissionMode(value) {
662
+ return value === "approve-all" || value === "approve-reads" || value === "deny-all";
663
+ }
664
+ function isNonInteractivePermissionPolicy(value) {
665
+ return value === "deny" || value === "fail";
666
+ }
667
+ function isOutputErrorCode(value) {
668
+ return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
669
+ }
670
+ function isOutputErrorOrigin(value) {
671
+ return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
672
+ }
673
+ function parseAcpError(value) {
674
+ const record = asRecord(value);
675
+ if (!record) return;
676
+ if (typeof record.code !== "number" || !Number.isFinite(record.code)) return;
677
+ if (typeof record.message !== "string" || record.message.length === 0) return;
678
+ return {
679
+ code: record.code,
680
+ message: record.message,
681
+ data: record.data
682
+ };
683
+ }
684
+ function parseOwnerGeneration(value) {
685
+ if (value == null) return;
686
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return null;
687
+ return value;
688
+ }
689
+ function parseQueueRequest(raw) {
690
+ const request = asRecord(raw);
691
+ if (!request) return null;
692
+ if (typeof request.type !== "string" || typeof request.requestId !== "string") return null;
693
+ const ownerGeneration = parseOwnerGeneration(request.ownerGeneration);
694
+ if (ownerGeneration === null) return null;
695
+ const timeoutRaw = request.timeoutMs;
696
+ const timeoutMs = typeof timeoutRaw === "number" && Number.isFinite(timeoutRaw) && timeoutRaw > 0 ? Math.round(timeoutRaw) : void 0;
697
+ if (request.type === "submit_prompt") {
698
+ const nonInteractivePermissions = request.nonInteractivePermissions == null ? void 0 : isNonInteractivePermissionPolicy(request.nonInteractivePermissions) ? request.nonInteractivePermissions : null;
699
+ const suppressSdkConsoleErrors = request.suppressSdkConsoleErrors == null ? void 0 : typeof request.suppressSdkConsoleErrors === "boolean" ? request.suppressSdkConsoleErrors : null;
700
+ const prompt = request.prompt == null ? void 0 : isPromptInput(request.prompt) ? request.prompt : null;
701
+ if (typeof request.message !== "string" || !isPermissionMode(request.permissionMode) || prompt === null || nonInteractivePermissions === null || suppressSdkConsoleErrors === null || typeof request.waitForCompletion !== "boolean") return null;
702
+ return {
703
+ type: "submit_prompt",
704
+ requestId: request.requestId,
705
+ ownerGeneration,
706
+ message: request.message,
707
+ prompt: prompt ?? textPrompt(request.message),
708
+ permissionMode: request.permissionMode,
709
+ nonInteractivePermissions,
710
+ timeoutMs,
711
+ ...suppressSdkConsoleErrors !== void 0 ? { suppressSdkConsoleErrors } : {},
712
+ waitForCompletion: request.waitForCompletion
713
+ };
714
+ }
715
+ if (request.type === "cancel_prompt") return {
716
+ type: "cancel_prompt",
717
+ requestId: request.requestId,
718
+ ownerGeneration
719
+ };
720
+ if (request.type === "set_mode") {
721
+ if (typeof request.modeId !== "string" || request.modeId.trim().length === 0) return null;
722
+ return {
723
+ type: "set_mode",
724
+ requestId: request.requestId,
725
+ ownerGeneration,
726
+ modeId: request.modeId,
727
+ timeoutMs
728
+ };
729
+ }
730
+ if (request.type === "set_config_option") {
731
+ if (typeof request.configId !== "string" || request.configId.trim().length === 0 || typeof request.value !== "string" || request.value.trim().length === 0) return null;
732
+ return {
733
+ type: "set_config_option",
734
+ requestId: request.requestId,
735
+ ownerGeneration,
736
+ configId: request.configId,
737
+ value: request.value,
738
+ timeoutMs
739
+ };
740
+ }
741
+ return null;
742
+ }
743
+ function parseSessionSendResult(raw) {
744
+ const result = asRecord(raw);
745
+ if (!result) return null;
746
+ if (typeof result.stopReason !== "string" || typeof result.sessionId !== "string" || typeof result.resumed !== "boolean") return null;
747
+ const permissionStats = asRecord(result.permissionStats);
748
+ const record = asRecord(result.record);
749
+ if (!permissionStats || !record) return null;
750
+ if (!(typeof permissionStats.requested === "number" && typeof permissionStats.approved === "number" && typeof permissionStats.denied === "number" && typeof permissionStats.cancelled === "number")) return null;
751
+ if (!(typeof record.acpxRecordId === "string" && typeof record.acpSessionId === "string" && typeof record.agentCommand === "string" && typeof record.cwd === "string" && typeof record.createdAt === "string" && typeof record.lastUsedAt === "string" && Array.isArray(record.messages) && typeof record.updated_at === "string" && typeof record.lastSeq === "number" && Number.isInteger(record.lastSeq) && !!record.eventLog && typeof record.eventLog === "object")) return null;
752
+ return result;
753
+ }
754
+ function parseQueueOwnerMessage(raw) {
755
+ const message = asRecord(raw);
756
+ if (!message || typeof message.type !== "string") return null;
757
+ if (typeof message.requestId !== "string") return null;
758
+ const ownerGeneration = parseOwnerGeneration(message.ownerGeneration);
759
+ if (ownerGeneration === null) return null;
760
+ if (message.type === "accepted") return {
761
+ type: "accepted",
762
+ requestId: message.requestId,
763
+ ownerGeneration
764
+ };
765
+ if (message.type === "event") {
766
+ if (!isAcpJsonRpcMessage(message.message)) return null;
767
+ return {
768
+ type: "event",
769
+ requestId: message.requestId,
770
+ ownerGeneration,
771
+ message: message.message
772
+ };
773
+ }
774
+ if (message.type === "result") {
775
+ const parsedResult = parseSessionSendResult(message.result);
776
+ if (!parsedResult) return null;
777
+ return {
778
+ type: "result",
779
+ requestId: message.requestId,
780
+ ownerGeneration,
781
+ result: parsedResult
782
+ };
783
+ }
784
+ if (message.type === "cancel_result") {
785
+ if (typeof message.cancelled !== "boolean") return null;
786
+ return {
787
+ type: "cancel_result",
788
+ requestId: message.requestId,
789
+ ownerGeneration,
790
+ cancelled: message.cancelled
791
+ };
792
+ }
793
+ if (message.type === "set_mode_result") {
794
+ if (typeof message.modeId !== "string") return null;
795
+ return {
796
+ type: "set_mode_result",
797
+ requestId: message.requestId,
798
+ ownerGeneration,
799
+ modeId: message.modeId
800
+ };
801
+ }
802
+ if (message.type === "set_config_option_result") {
803
+ const response = asRecord(message.response);
804
+ if (!response || !Array.isArray(response.configOptions)) return null;
805
+ return {
806
+ type: "set_config_option_result",
807
+ requestId: message.requestId,
808
+ ownerGeneration,
809
+ response
810
+ };
811
+ }
812
+ if (message.type === "error") {
813
+ if (typeof message.message !== "string" || !isOutputErrorCode(message.code) || !isOutputErrorOrigin(message.origin)) return null;
814
+ const detailCode = typeof message.detailCode === "string" && message.detailCode.trim().length > 0 ? message.detailCode : void 0;
815
+ const retryable = typeof message.retryable === "boolean" ? message.retryable : void 0;
816
+ const acp = parseAcpError(message.acp);
817
+ const outputAlreadyEmitted = typeof message.outputAlreadyEmitted === "boolean" ? message.outputAlreadyEmitted : void 0;
818
+ return {
819
+ type: "error",
820
+ requestId: message.requestId,
821
+ ownerGeneration,
822
+ code: message.code,
823
+ detailCode,
824
+ origin: message.origin,
825
+ message: message.message,
826
+ retryable,
827
+ acp,
828
+ ...outputAlreadyEmitted === void 0 ? {} : { outputAlreadyEmitted }
829
+ };
830
+ }
831
+ return null;
832
+ }
833
+
834
+ //#endregion
835
+ //#region src/queue-ipc-server.ts
836
+ function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
837
+ return {
838
+ type: "error",
839
+ requestId,
840
+ ownerGeneration: void 0,
841
+ code: "RUNTIME",
842
+ detailCode,
843
+ origin: "queue",
844
+ retryable: options.retryable,
845
+ message
846
+ };
847
+ }
848
+ function makeQueueOwnerErrorFromUnknown(requestId, error, detailCode, options = {}) {
849
+ const normalized = normalizeOutputError(error, {
850
+ defaultCode: "RUNTIME",
851
+ origin: "queue",
852
+ detailCode,
853
+ retryable: options.retryable
854
+ });
855
+ return {
856
+ type: "error",
857
+ requestId,
858
+ code: normalized.code,
859
+ detailCode: normalized.detailCode,
860
+ origin: normalized.origin,
861
+ message: normalized.message,
862
+ retryable: normalized.retryable,
863
+ acp: normalized.acp
864
+ };
865
+ }
866
+ function writeQueueMessage(socket, message) {
867
+ if (socket.destroyed || !socket.writable) return;
868
+ socket.write(`${JSON.stringify(message)}\n`);
869
+ }
870
+ var SessionQueueOwner = class SessionQueueOwner {
871
+ server;
872
+ controlHandlers;
873
+ ownerGeneration;
874
+ maxQueueDepth;
875
+ onQueueDepthChanged;
876
+ pending = [];
877
+ waiters = [];
878
+ closed = false;
879
+ constructor(server, controlHandlers, lease, options) {
880
+ this.server = server;
881
+ this.controlHandlers = controlHandlers;
882
+ this.ownerGeneration = lease.ownerGeneration;
883
+ this.maxQueueDepth = Math.max(1, Math.round(options.maxQueueDepth));
884
+ this.onQueueDepthChanged = options.onQueueDepthChanged;
885
+ }
886
+ static async start(lease, controlHandlers, options = { maxQueueDepth: 16 }) {
887
+ const ownerRef = { current: void 0 };
888
+ const server = net.createServer((socket) => {
889
+ ownerRef.current?.handleConnection(socket);
890
+ });
891
+ ownerRef.current = new SessionQueueOwner(server, controlHandlers, lease, options);
892
+ await new Promise((resolve, reject) => {
893
+ const onListening = () => {
894
+ server.off("error", onError);
895
+ resolve();
896
+ };
897
+ const onError = (error) => {
898
+ server.off("listening", onListening);
899
+ reject(error);
900
+ };
901
+ server.once("listening", onListening);
902
+ server.once("error", onError);
903
+ server.listen(lease.socketPath);
904
+ });
905
+ return ownerRef.current;
906
+ }
907
+ async close() {
908
+ if (this.closed) return;
909
+ this.closed = true;
910
+ for (const waiter of this.waiters.splice(0)) waiter(void 0);
911
+ for (const task of this.pending.splice(0)) {
912
+ if (task.waitForCompletion) task.send(makeQueueOwnerError(task.requestId, "Queue owner shutting down before prompt execution", "QUEUE_OWNER_SHUTTING_DOWN", { retryable: true }));
913
+ task.close();
914
+ }
915
+ this.emitQueueDepth();
916
+ await new Promise((resolve) => {
917
+ this.server.close(() => resolve());
918
+ });
919
+ }
920
+ async nextTask(timeoutMs) {
921
+ if (this.pending.length > 0) {
922
+ const task = this.pending.shift();
923
+ this.emitQueueDepth();
924
+ if (task) recordPerfDuration("queue.owner.wait_ms", Date.now() - task.enqueuedAt);
925
+ return task;
926
+ }
927
+ if (this.closed) return;
928
+ return await new Promise((resolve) => {
929
+ const timer = timeoutMs != null && setTimeout(() => {
930
+ const index = this.waiters.indexOf(waiter);
931
+ if (index >= 0) this.waiters.splice(index, 1);
932
+ resolve(void 0);
933
+ }, Math.max(0, timeoutMs));
934
+ const waiter = (task) => {
935
+ if (timer) clearTimeout(timer);
936
+ resolve(task);
937
+ };
938
+ this.waiters.push(waiter);
939
+ });
940
+ }
941
+ queueDepth() {
942
+ return this.pending.length;
943
+ }
944
+ emitQueueDepth() {
945
+ this.onQueueDepthChanged?.(this.pending.length);
946
+ }
947
+ enqueue(task) {
948
+ if (this.closed) {
949
+ if (task.waitForCompletion) task.send(makeQueueOwnerError(task.requestId, "Queue owner is shutting down", "QUEUE_OWNER_SHUTTING_DOWN", { retryable: true }));
950
+ task.close();
951
+ return;
952
+ }
953
+ const waiter = this.waiters.shift();
954
+ if (waiter) {
955
+ waiter(task);
956
+ return;
957
+ }
958
+ if (this.pending.length >= this.maxQueueDepth) {
959
+ if (task.waitForCompletion) task.send({
960
+ ...makeQueueOwnerError(task.requestId, `Queue owner is overloaded (${this.pending.length}/${this.maxQueueDepth} queued)`, "QUEUE_OWNER_OVERLOADED", { retryable: true }),
961
+ ownerGeneration: this.ownerGeneration
962
+ });
963
+ task.close();
964
+ return;
965
+ }
966
+ this.pending.push(task);
967
+ this.emitQueueDepth();
968
+ }
969
+ handleControlRequest(options) {
970
+ writeQueueMessage(options.socket, {
971
+ type: "accepted",
972
+ requestId: options.requestId,
973
+ ownerGeneration: this.ownerGeneration
974
+ });
975
+ options.run().then((message) => {
976
+ writeQueueMessage(options.socket, {
977
+ ...message,
978
+ ownerGeneration: this.ownerGeneration
979
+ });
980
+ }).catch((error) => {
981
+ writeQueueMessage(options.socket, {
982
+ ...makeQueueOwnerErrorFromUnknown(options.requestId, error, "QUEUE_CONTROL_REQUEST_FAILED"),
983
+ ownerGeneration: this.ownerGeneration
984
+ });
985
+ }).finally(() => {
986
+ if (!options.socket.destroyed) options.socket.end();
987
+ });
988
+ }
989
+ handleConnection(socket) {
990
+ socket.setEncoding("utf8");
991
+ if (this.closed) {
992
+ writeQueueMessage(socket, makeQueueOwnerError("unknown", "Queue owner is closed", "QUEUE_OWNER_CLOSED", { retryable: true }));
993
+ socket.end();
994
+ return;
995
+ }
996
+ let buffer = "";
997
+ let handled = false;
998
+ const fail = (requestId, message, detailCode) => {
999
+ writeQueueMessage(socket, {
1000
+ ...makeQueueOwnerError(requestId, message, detailCode, { retryable: false }),
1001
+ ownerGeneration: this.ownerGeneration
1002
+ });
1003
+ socket.end();
1004
+ };
1005
+ const processLine = (line) => {
1006
+ if (handled) return;
1007
+ handled = true;
1008
+ let parsed;
1009
+ try {
1010
+ parsed = JSON.parse(line);
1011
+ } catch {
1012
+ fail("unknown", "Invalid queue request payload", "QUEUE_REQUEST_PAYLOAD_INVALID_JSON");
1013
+ return;
1014
+ }
1015
+ const request = parseQueueRequest(parsed);
1016
+ if (!request) {
1017
+ fail("unknown", "Invalid queue request", "QUEUE_REQUEST_INVALID");
1018
+ return;
1019
+ }
1020
+ if (request.ownerGeneration !== void 0 && this.ownerGeneration !== void 0 && request.ownerGeneration !== this.ownerGeneration) {
1021
+ fail(request.requestId, "Queue request targeted a stale queue owner generation", "QUEUE_OWNER_GENERATION_MISMATCH");
1022
+ return;
1023
+ }
1024
+ if (request.type === "cancel_prompt") {
1025
+ this.handleControlRequest({
1026
+ socket,
1027
+ requestId: request.requestId,
1028
+ run: async () => ({
1029
+ type: "cancel_result",
1030
+ requestId: request.requestId,
1031
+ cancelled: await this.controlHandlers.cancelPrompt()
1032
+ })
1033
+ });
1034
+ return;
1035
+ }
1036
+ if (request.type === "set_mode") {
1037
+ this.handleControlRequest({
1038
+ socket,
1039
+ requestId: request.requestId,
1040
+ run: async () => {
1041
+ await this.controlHandlers.setSessionMode(request.modeId, request.timeoutMs);
1042
+ return {
1043
+ type: "set_mode_result",
1044
+ requestId: request.requestId,
1045
+ modeId: request.modeId
1046
+ };
1047
+ }
1048
+ });
1049
+ return;
1050
+ }
1051
+ if (request.type === "set_config_option") {
1052
+ this.handleControlRequest({
1053
+ socket,
1054
+ requestId: request.requestId,
1055
+ run: async () => ({
1056
+ type: "set_config_option_result",
1057
+ requestId: request.requestId,
1058
+ response: await this.controlHandlers.setSessionConfigOption(request.configId, request.value, request.timeoutMs)
1059
+ })
1060
+ });
1061
+ return;
1062
+ }
1063
+ const task = {
1064
+ requestId: request.requestId,
1065
+ message: request.message,
1066
+ prompt: request.prompt ?? textPrompt(request.message),
1067
+ permissionMode: request.permissionMode,
1068
+ nonInteractivePermissions: request.nonInteractivePermissions,
1069
+ timeoutMs: request.timeoutMs,
1070
+ suppressSdkConsoleErrors: request.suppressSdkConsoleErrors,
1071
+ waitForCompletion: request.waitForCompletion,
1072
+ enqueuedAt: Date.now(),
1073
+ send: (message) => {
1074
+ writeQueueMessage(socket, {
1075
+ ...message,
1076
+ ownerGeneration: this.ownerGeneration
1077
+ });
1078
+ },
1079
+ close: () => {
1080
+ if (!socket.destroyed) socket.end();
1081
+ }
1082
+ };
1083
+ writeQueueMessage(socket, {
1084
+ type: "accepted",
1085
+ requestId: request.requestId,
1086
+ ownerGeneration: this.ownerGeneration
1087
+ });
1088
+ if (!request.waitForCompletion) task.close();
1089
+ this.enqueue(task);
1090
+ };
1091
+ socket.on("data", (chunk) => {
1092
+ buffer += chunk;
1093
+ let index = buffer.indexOf("\n");
1094
+ while (index >= 0) {
1095
+ const line = buffer.slice(0, index).trim();
1096
+ buffer = buffer.slice(index + 1);
1097
+ if (line.length > 0) processLine(line);
1098
+ index = buffer.indexOf("\n");
1099
+ }
1100
+ });
1101
+ socket.on("error", () => {});
1102
+ }
1103
+ };
1104
+
1105
+ //#endregion
1106
+ //#region src/queue-ipc.ts
1107
+ var queue_ipc_exports = /* @__PURE__ */ __exportAll({
1108
+ QUEUE_CONNECT_RETRY_MS: () => QUEUE_CONNECT_RETRY_MS,
1109
+ SessionQueueOwner: () => SessionQueueOwner,
1110
+ isProcessAlive: () => isProcessAlive,
1111
+ probeQueueOwnerHealth: () => probeQueueOwnerHealth,
1112
+ releaseQueueOwnerLease: () => releaseQueueOwnerLease,
1113
+ terminateProcess: () => terminateProcess,
1114
+ terminateQueueOwnerForSession: () => terminateQueueOwnerForSession,
1115
+ tryAcquireQueueOwnerLease: () => tryAcquireQueueOwnerLease,
1116
+ tryCancelOnRunningOwner: () => tryCancelOnRunningOwner,
1117
+ trySetConfigOptionOnRunningOwner: () => trySetConfigOptionOnRunningOwner,
1118
+ trySetModeOnRunningOwner: () => trySetModeOnRunningOwner,
1119
+ trySubmitToRunningOwner: () => trySubmitToRunningOwner,
1120
+ waitMs: () => waitMs
1121
+ });
1122
+ const QUEUE_CONNECT_ATTEMPTS = 40;
1123
+ const QUEUE_CONNECT_RETRY_MS = 50;
1124
+ const STALE_OWNER_PROTOCOL_DETAIL_CODES = new Set(["QUEUE_PROTOCOL_MALFORMED_MESSAGE", "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE"]);
1125
+ async function maybeRecoverStaleOwnerAfterProtocolMismatch(params) {
1126
+ if (!(params.error instanceof QueueProtocolError)) return false;
1127
+ const detailCode = params.error.detailCode;
1128
+ if (!detailCode || !STALE_OWNER_PROTOCOL_DETAIL_CODES.has(detailCode)) return false;
1129
+ await terminateQueueOwnerForSession(params.sessionId).catch(() => {});
1130
+ incrementPerfCounter("queue.owner.stale_recovered");
1131
+ if (params.verbose) process.stderr.write(`[acpx] dropped stale queue owner metadata after protocol mismatch for session ${params.sessionId} (${detailCode})\n`);
1132
+ return true;
1133
+ }
1134
+ function shouldRetryQueueConnect(error) {
1135
+ const code = error.code;
1136
+ return code === "ENOENT" || code === "ECONNREFUSED";
1137
+ }
1138
+ async function connectToSocket(socketPath) {
1139
+ return await new Promise((resolve, reject) => {
1140
+ const socket = net.createConnection(socketPath);
1141
+ const onConnect = () => {
1142
+ socket.off("error", onError);
1143
+ resolve(socket);
1144
+ };
1145
+ const onError = (error) => {
1146
+ socket.off("connect", onConnect);
1147
+ reject(error);
1148
+ };
1149
+ socket.once("connect", onConnect);
1150
+ socket.once("error", onError);
1151
+ });
1152
+ }
1153
+ async function connectToQueueOwner(owner, maxAttempts = QUEUE_CONNECT_ATTEMPTS) {
1154
+ let lastError;
1155
+ const attempts = Math.max(1, Math.trunc(maxAttempts));
1156
+ for (let attempt = 0; attempt < attempts; attempt += 1) try {
1157
+ return await measurePerf("queue.connect", async () => await connectToSocket(owner.socketPath));
1158
+ } catch (error) {
1159
+ lastError = error;
1160
+ if (!shouldRetryQueueConnect(error)) throw error;
1161
+ await waitMs(QUEUE_CONNECT_RETRY_MS);
1162
+ }
1163
+ if (lastError && !shouldRetryQueueConnect(lastError)) throw lastError;
1164
+ }
1165
+ async function probeQueueOwnerHealth(sessionId) {
1166
+ const ownerRecord = await readQueueOwnerRecord(sessionId);
1167
+ if (!ownerRecord) return {
1168
+ sessionId,
1169
+ hasLease: false,
1170
+ healthy: false,
1171
+ socketReachable: false,
1172
+ pidAlive: false
1173
+ };
1174
+ const owner = await readQueueOwnerStatus(sessionId);
1175
+ if (!owner) return {
1176
+ sessionId,
1177
+ hasLease: false,
1178
+ healthy: false,
1179
+ socketReachable: false,
1180
+ pidAlive: false
1181
+ };
1182
+ const pidAlive = owner.alive;
1183
+ let socketReachable = false;
1184
+ try {
1185
+ const socket = await connectToQueueOwner(ownerRecord, 2);
1186
+ if (socket) {
1187
+ socketReachable = true;
1188
+ if (!socket.destroyed) socket.end();
1189
+ }
1190
+ } catch {
1191
+ socketReachable = false;
1192
+ }
1193
+ return {
1194
+ sessionId,
1195
+ hasLease: true,
1196
+ healthy: socketReachable,
1197
+ socketReachable,
1198
+ pidAlive,
1199
+ pid: owner.pid,
1200
+ socketPath: owner.socketPath,
1201
+ ownerGeneration: owner.ownerGeneration,
1202
+ queueDepth: owner.queueDepth
1203
+ };
1204
+ }
1205
+ function assertOwnerGeneration(owner, message) {
1206
+ if (owner.ownerGeneration !== void 0 && message.ownerGeneration !== void 0 && message.ownerGeneration !== owner.ownerGeneration) throw new QueueProtocolError("Queue owner returned mismatched generation", {
1207
+ detailCode: "QUEUE_OWNER_GENERATION_MISMATCH",
1208
+ origin: "queue",
1209
+ retryable: true
1210
+ });
1211
+ return message;
1212
+ }
1213
+ function makeMalformedQueueMessageError() {
1214
+ return new QueueProtocolError("Queue owner sent malformed message", {
1215
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
1216
+ origin: "queue",
1217
+ retryable: true
1218
+ });
1219
+ }
1220
+ function parseQueueOwnerResponseLine(owner, requestId, line) {
1221
+ let parsed;
1222
+ try {
1223
+ parsed = JSON.parse(line);
1224
+ } catch {
1225
+ throw new QueueProtocolError("Queue owner sent invalid JSON payload", {
1226
+ detailCode: "QUEUE_PROTOCOL_INVALID_JSON",
1227
+ origin: "queue",
1228
+ retryable: true
1229
+ });
1230
+ }
1231
+ const parsedMessage = parseQueueOwnerMessage(parsed);
1232
+ if (!parsedMessage) throw makeMalformedQueueMessageError();
1233
+ const message = assertOwnerGeneration(owner, parsedMessage);
1234
+ if (message.requestId !== requestId) throw makeMalformedQueueMessageError();
1235
+ return message;
1236
+ }
1237
+ async function runQueueOwnerRequest(options) {
1238
+ const socket = await connectToQueueOwner(options.owner);
1239
+ if (!socket) return;
1240
+ socket.setEncoding("utf8");
1241
+ return await new Promise((resolve, reject) => {
1242
+ let settled = false;
1243
+ let buffer = "";
1244
+ const state = { acknowledged: false };
1245
+ const finishResolve = (result) => {
1246
+ if (settled) return;
1247
+ settled = true;
1248
+ socket.removeAllListeners();
1249
+ if (!socket.destroyed) socket.end();
1250
+ resolve(result);
1251
+ };
1252
+ const finishReject = (error) => {
1253
+ if (settled) return;
1254
+ settled = true;
1255
+ socket.removeAllListeners();
1256
+ if (!socket.destroyed) socket.destroy();
1257
+ reject(error);
1258
+ };
1259
+ const controls = {
1260
+ state,
1261
+ resolve: finishResolve,
1262
+ reject: finishReject
1263
+ };
1264
+ const processLine = (line) => {
1265
+ let message;
1266
+ try {
1267
+ message = parseQueueOwnerResponseLine(options.owner, options.request.requestId, line);
1268
+ } catch (error) {
1269
+ finishReject(error);
1270
+ return;
1271
+ }
1272
+ if (message.type === "accepted") {
1273
+ state.acknowledged = true;
1274
+ options.onAccepted?.(controls);
1275
+ return;
1276
+ }
1277
+ options.onMessage(message, controls);
1278
+ };
1279
+ socket.on("data", (chunk) => {
1280
+ buffer += chunk;
1281
+ let index = buffer.indexOf("\n");
1282
+ while (index >= 0) {
1283
+ const line = buffer.slice(0, index).trim();
1284
+ buffer = buffer.slice(index + 1);
1285
+ if (line.length > 0) processLine(line);
1286
+ index = buffer.indexOf("\n");
1287
+ }
1288
+ });
1289
+ socket.once("error", (error) => {
1290
+ finishReject(error);
1291
+ });
1292
+ socket.once("close", () => {
1293
+ if (settled) return;
1294
+ options.onClose(controls);
1295
+ });
1296
+ socket.write(`${JSON.stringify(options.request)}\n`);
1297
+ });
1298
+ }
1299
+ async function submitToQueueOwner(owner, options) {
1300
+ const requestId = randomUUID();
1301
+ const request = {
1302
+ type: "submit_prompt",
1303
+ requestId,
1304
+ ownerGeneration: owner.ownerGeneration,
1305
+ message: options.message,
1306
+ prompt: options.prompt,
1307
+ permissionMode: options.permissionMode,
1308
+ nonInteractivePermissions: options.nonInteractivePermissions,
1309
+ timeoutMs: options.timeoutMs,
1310
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
1311
+ waitForCompletion: options.waitForCompletion
1312
+ };
1313
+ options.outputFormatter.setContext({ sessionId: options.sessionId });
1314
+ return await runQueueOwnerRequest({
1315
+ owner,
1316
+ request,
1317
+ onAccepted: ({ resolve }) => {
1318
+ options.outputFormatter.setContext({ sessionId: options.sessionId });
1319
+ if (!options.waitForCompletion) resolve({
1320
+ queued: true,
1321
+ sessionId: options.sessionId,
1322
+ requestId
1323
+ });
1324
+ },
1325
+ onMessage: (message, { state, resolve, reject }) => {
1326
+ if (message.type === "error") {
1327
+ options.outputFormatter.setContext({ sessionId: options.sessionId });
1328
+ const queueErrorAlreadyEmitted = options.errorEmissionPolicy?.queueErrorAlreadyEmitted ?? true;
1329
+ if (!(message.outputAlreadyEmitted === true) || !queueErrorAlreadyEmitted) {
1330
+ options.outputFormatter.onError({
1331
+ code: message.code ?? "RUNTIME",
1332
+ detailCode: message.detailCode,
1333
+ origin: message.origin ?? "queue",
1334
+ message: message.message,
1335
+ retryable: message.retryable,
1336
+ acp: message.acp
1337
+ });
1338
+ options.outputFormatter.flush();
1339
+ }
1340
+ reject(new QueueConnectionError(message.message, {
1341
+ outputCode: message.code,
1342
+ detailCode: message.detailCode,
1343
+ origin: message.origin ?? "queue",
1344
+ retryable: message.retryable,
1345
+ acp: message.acp,
1346
+ ...queueErrorAlreadyEmitted ? { outputAlreadyEmitted: true } : {}
1347
+ }));
1348
+ return;
1349
+ }
1350
+ if (!state.acknowledged) {
1351
+ reject(new QueueConnectionError("Queue owner did not acknowledge request", {
1352
+ detailCode: "QUEUE_ACK_MISSING",
1353
+ origin: "queue",
1354
+ retryable: true
1355
+ }));
1356
+ return;
1357
+ }
1358
+ if (message.type === "event") {
1359
+ options.outputFormatter.onAcpMessage(message.message);
1360
+ return;
1361
+ }
1362
+ if (message.type === "result") {
1363
+ options.outputFormatter.flush();
1364
+ resolve(message.result);
1365
+ return;
1366
+ }
1367
+ reject(new QueueProtocolError("Queue owner returned unexpected response", {
1368
+ detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
1369
+ origin: "queue",
1370
+ retryable: true
1371
+ }));
1372
+ },
1373
+ onClose: ({ state, resolve, reject }) => {
1374
+ if (!state.acknowledged) {
1375
+ reject(new QueueConnectionError("Queue owner disconnected before acknowledging request", {
1376
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_ACK",
1377
+ origin: "queue",
1378
+ retryable: true
1379
+ }));
1380
+ return;
1381
+ }
1382
+ if (!options.waitForCompletion) {
1383
+ resolve({
1384
+ queued: true,
1385
+ sessionId: options.sessionId,
1386
+ requestId
1387
+ });
1388
+ return;
1389
+ }
1390
+ reject(new QueueConnectionError("Queue owner disconnected before prompt completion", {
1391
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_COMPLETION",
1392
+ origin: "queue",
1393
+ retryable: true
1394
+ }));
1395
+ }
1396
+ });
1397
+ }
1398
+ async function submitControlToQueueOwner(owner, request, isExpectedResponse) {
1399
+ return await runQueueOwnerRequest({
1400
+ owner,
1401
+ request,
1402
+ onMessage: (message, { state, resolve, reject }) => {
1403
+ if (message.type === "error") {
1404
+ reject(new QueueConnectionError(message.message, {
1405
+ outputCode: message.code,
1406
+ detailCode: message.detailCode,
1407
+ origin: message.origin ?? "queue",
1408
+ retryable: message.retryable,
1409
+ acp: message.acp
1410
+ }));
1411
+ return;
1412
+ }
1413
+ if (!state.acknowledged) {
1414
+ reject(new QueueConnectionError("Queue owner did not acknowledge request", {
1415
+ detailCode: "QUEUE_ACK_MISSING",
1416
+ origin: "queue",
1417
+ retryable: true
1418
+ }));
1419
+ return;
1420
+ }
1421
+ if (!isExpectedResponse(message)) {
1422
+ reject(new QueueProtocolError("Queue owner returned unexpected response", {
1423
+ detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
1424
+ origin: "queue",
1425
+ retryable: true
1426
+ }));
1427
+ return;
1428
+ }
1429
+ resolve(message);
1430
+ },
1431
+ onClose: ({ state, reject }) => {
1432
+ if (!state.acknowledged) {
1433
+ reject(new QueueConnectionError("Queue owner disconnected before acknowledging request", {
1434
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_ACK",
1435
+ origin: "queue",
1436
+ retryable: true
1437
+ }));
1438
+ return;
1439
+ }
1440
+ reject(new QueueConnectionError("Queue owner disconnected before responding", {
1441
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_COMPLETION",
1442
+ origin: "queue",
1443
+ retryable: true
1444
+ }));
1445
+ }
1446
+ });
1447
+ }
1448
+ async function submitCancelToQueueOwner(owner) {
1449
+ const request = {
1450
+ type: "cancel_prompt",
1451
+ requestId: randomUUID(),
1452
+ ownerGeneration: owner.ownerGeneration
1453
+ };
1454
+ const response = await submitControlToQueueOwner(owner, request, (message) => message.type === "cancel_result");
1455
+ if (!response) return;
1456
+ if (response.requestId !== request.requestId) throw new QueueProtocolError("Queue owner returned mismatched cancel response", {
1457
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
1458
+ origin: "queue",
1459
+ retryable: true
1460
+ });
1461
+ return response.cancelled;
1462
+ }
1463
+ async function submitSetModeToQueueOwner(owner, modeId, timeoutMs) {
1464
+ const request = {
1465
+ type: "set_mode",
1466
+ requestId: randomUUID(),
1467
+ ownerGeneration: owner.ownerGeneration,
1468
+ modeId,
1469
+ timeoutMs
1470
+ };
1471
+ const response = await submitControlToQueueOwner(owner, request, (message) => message.type === "set_mode_result");
1472
+ if (!response) return;
1473
+ if (response.requestId !== request.requestId) throw new QueueProtocolError("Queue owner returned mismatched set_mode response", {
1474
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
1475
+ origin: "queue",
1476
+ retryable: true
1477
+ });
1478
+ return true;
1479
+ }
1480
+ async function submitSetConfigOptionToQueueOwner(owner, configId, value, timeoutMs) {
1481
+ const request = {
1482
+ type: "set_config_option",
1483
+ requestId: randomUUID(),
1484
+ ownerGeneration: owner.ownerGeneration,
1485
+ configId,
1486
+ value,
1487
+ timeoutMs
1488
+ };
1489
+ const response = await submitControlToQueueOwner(owner, request, (message) => message.type === "set_config_option_result");
1490
+ if (!response) return;
1491
+ if (response.requestId !== request.requestId) throw new QueueProtocolError("Queue owner returned mismatched set_config_option response", {
1492
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
1493
+ origin: "queue",
1494
+ retryable: true
1495
+ });
1496
+ return response.response;
1497
+ }
1498
+ async function trySubmitToRunningOwner(options) {
1499
+ const owner = await readQueueOwnerRecord(options.sessionId);
1500
+ if (!owner) return;
1501
+ let submitted;
1502
+ try {
1503
+ submitted = await submitToQueueOwner(owner, options);
1504
+ } catch (error) {
1505
+ if (await maybeRecoverStaleOwnerAfterProtocolMismatch({
1506
+ sessionId: options.sessionId,
1507
+ owner,
1508
+ error,
1509
+ verbose: options.verbose
1510
+ })) return;
1511
+ throw error;
1512
+ }
1513
+ if (submitted) {
1514
+ if (options.verbose) process.stderr.write(`[acpx] queued prompt on active owner pid ${owner.pid} for session ${options.sessionId}\n`);
1515
+ return submitted;
1516
+ }
1517
+ if (!(await probeQueueOwnerHealth(options.sessionId)).hasLease) return;
1518
+ throw new QueueConnectionError("Session queue owner is running but not accepting queue requests", {
1519
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
1520
+ origin: "queue",
1521
+ retryable: true
1522
+ });
1523
+ }
1524
+ async function tryCancelOnRunningOwner(options) {
1525
+ const owner = await readQueueOwnerRecord(options.sessionId);
1526
+ if (!owner) return;
1527
+ const cancelled = await submitCancelToQueueOwner(owner);
1528
+ if (cancelled !== void 0) {
1529
+ if (options.verbose) process.stderr.write(`[acpx] requested cancel on active owner pid ${owner.pid} for session ${options.sessionId}\n`);
1530
+ return cancelled;
1531
+ }
1532
+ if (!(await probeQueueOwnerHealth(options.sessionId)).hasLease) return;
1533
+ throw new QueueConnectionError("Session queue owner is running but not accepting cancel requests", {
1534
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
1535
+ origin: "queue",
1536
+ retryable: true
1537
+ });
1538
+ }
1539
+ async function trySetModeOnRunningOwner(sessionId, modeId, timeoutMs, verbose) {
1540
+ const owner = await readQueueOwnerRecord(sessionId);
1541
+ if (!owner) return;
1542
+ if (await submitSetModeToQueueOwner(owner, modeId, timeoutMs)) {
1543
+ if (verbose) process.stderr.write(`[acpx] requested session/set_mode on owner pid ${owner.pid} for session ${sessionId}\n`);
1544
+ return true;
1545
+ }
1546
+ if (!(await probeQueueOwnerHealth(sessionId)).hasLease) return;
1547
+ throw new QueueConnectionError("Session queue owner is running but not accepting set_mode requests", {
1548
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
1549
+ origin: "queue",
1550
+ retryable: true
1551
+ });
1552
+ }
1553
+ async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, timeoutMs, verbose) {
1554
+ const owner = await readQueueOwnerRecord(sessionId);
1555
+ if (!owner) return;
1556
+ const response = await submitSetConfigOptionToQueueOwner(owner, configId, value, timeoutMs);
1557
+ if (response) {
1558
+ if (verbose) process.stderr.write(`[acpx] requested session/set_config_option on owner pid ${owner.pid} for session ${sessionId}\n`);
1559
+ return response;
1560
+ }
1561
+ if (!(await probeQueueOwnerHealth(sessionId)).hasLease) return;
1562
+ throw new QueueConnectionError("Session queue owner is running but not accepting set_config_option requests", {
1563
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
1564
+ origin: "queue",
1565
+ retryable: true
1566
+ });
1567
+ }
1568
+
1569
+ //#endregion
1570
+ export { OUTPUT_FORMATS as A, ClaudeAcpSessionCreateTimeoutError as B, exitCodeForOutputErrorCode as C, AUTH_POLICIES as D, normalizeOutputError as E, parsePromptSource as F, QueueConnectionError as G, GeminiAcpStartupTimeoutError as H, promptToDisplayText as I, SessionResolutionError as J, SessionModeReplayError as K, textPrompt as L, extractAcpError as M, isAcpResourceNotFoundError as N, EXIT_CODES as O, mergePromptSourceWithText as P, AgentSpawnError as R, startPerfTimer as S, isAcpQueryClosedBeforeResponseError as T, PermissionDeniedError as U, CopilotAcpUnsupportedError as V, PermissionPromptUnavailableError as W, getPerfMetricsSnapshot as _, trySetConfigOptionOnRunningOwner as a, resetPerfMetrics as b, SessionQueueOwner as c, releaseQueueOwnerLease as d, terminateProcess as f, formatPerfMetric as g, waitMs as h, tryCancelOnRunningOwner as i, SESSION_RECORD_SCHEMA as j, NON_INTERACTIVE_PERMISSION_POLICIES as k, isProcessAlive as l, tryAcquireQueueOwnerLease as m, probeQueueOwnerHealth as n, trySetModeOnRunningOwner as o, terminateQueueOwnerForSession as p, SessionNotFoundError as q, queue_ipc_exports as r, trySubmitToRunningOwner as s, QUEUE_CONNECT_RETRY_MS as t, refreshQueueOwnerLease as u, incrementPerfCounter as v, formatErrorMessage as w, setPerfGauge as x, measurePerf as y, AuthPolicyError as z };
1571
+ //# sourceMappingURL=queue-ipc-C8StWiZt.js.map