acpx 0.1.9 → 0.1.11

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 (2) hide show
  1. package/dist/cli.js +2248 -1651
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { Command, CommanderError, InvalidArgumentError } from "commander";
4
+ import { Command, CommanderError, InvalidArgumentError as InvalidArgumentError3 } from "commander";
5
5
  import { realpathSync } from "fs";
6
6
  import fs6 from "fs/promises";
7
7
  import path7 from "path";
@@ -11,9 +11,9 @@ import { findSkillsRoot, maybeHandleSkillflag } from "skillflag";
11
11
  // src/agent-registry.ts
12
12
  var AGENT_REGISTRY = {
13
13
  codex: "npx @zed-industries/codex-acp",
14
- claude: "npx @zed-industries/claude-agent-acp",
14
+ claude: "npx -y @zed-industries/claude-agent-acp",
15
15
  gemini: "gemini",
16
- opencode: "npx opencode-ai",
16
+ opencode: "npx -y opencode-ai acp",
17
17
  pi: "npx pi-acp"
18
18
  };
19
19
  var DEFAULT_AGENT_NAME = "codex";
@@ -43,6 +43,223 @@ function listBuiltInAgents(overrides) {
43
43
  return Object.keys(mergeAgentRegistry(overrides));
44
44
  }
45
45
 
46
+ // src/cli-internal-owner.ts
47
+ import { InvalidArgumentError } from "commander";
48
+
49
+ // src/types.ts
50
+ var EXIT_CODES = {
51
+ SUCCESS: 0,
52
+ ERROR: 1,
53
+ USAGE: 2,
54
+ TIMEOUT: 3,
55
+ NO_SESSION: 4,
56
+ PERMISSION_DENIED: 5,
57
+ INTERRUPTED: 130
58
+ };
59
+ var OUTPUT_FORMATS = ["text", "json", "quiet"];
60
+ var PERMISSION_MODES = ["approve-all", "approve-reads", "deny-all"];
61
+ var AUTH_POLICIES = ["skip", "fail"];
62
+ var NON_INTERACTIVE_PERMISSION_POLICIES = ["deny", "fail"];
63
+ var OUTPUT_ERROR_CODES = [
64
+ "NO_SESSION",
65
+ "TIMEOUT",
66
+ "PERMISSION_DENIED",
67
+ "PERMISSION_PROMPT_UNAVAILABLE",
68
+ "RUNTIME",
69
+ "USAGE"
70
+ ];
71
+ var OUTPUT_ERROR_ORIGINS = ["cli", "runtime", "queue", "acp"];
72
+
73
+ // src/cli-internal-owner.ts
74
+ function parseNonEmptyValue(label, value) {
75
+ const trimmed = value.trim();
76
+ if (trimmed.length === 0) {
77
+ throw new InvalidArgumentError(`${label} must not be empty`);
78
+ }
79
+ return trimmed;
80
+ }
81
+ function parseNonNegativeMilliseconds(value) {
82
+ const parsed = Number(value);
83
+ if (!Number.isFinite(parsed) || parsed < 0) {
84
+ throw new InvalidArgumentError("TTL must be a non-negative number of milliseconds");
85
+ }
86
+ return Math.round(parsed);
87
+ }
88
+ function parseTimeoutMilliseconds(value) {
89
+ const parsed = Number(value);
90
+ if (!Number.isFinite(parsed) || parsed <= 0) {
91
+ throw new InvalidArgumentError("Timeout must be a positive number of milliseconds");
92
+ }
93
+ return Math.round(parsed);
94
+ }
95
+ function parsePermissionMode(value) {
96
+ if (!PERMISSION_MODES.includes(value)) {
97
+ throw new InvalidArgumentError(
98
+ `Invalid permission mode "${value}". Expected one of: ${PERMISSION_MODES.join(", ")}`
99
+ );
100
+ }
101
+ return value;
102
+ }
103
+ function parseAuthPolicy(value) {
104
+ if (!AUTH_POLICIES.includes(value)) {
105
+ throw new InvalidArgumentError(
106
+ `Invalid auth policy "${value}". Expected one of: ${AUTH_POLICIES.join(", ")}`
107
+ );
108
+ }
109
+ return value;
110
+ }
111
+ function parseNonInteractivePermissionPolicy(value) {
112
+ if (!NON_INTERACTIVE_PERMISSION_POLICIES.includes(
113
+ value
114
+ )) {
115
+ throw new InvalidArgumentError(
116
+ `Invalid non-interactive permission policy "${value}". Expected one of: ${NON_INTERACTIVE_PERMISSION_POLICIES.join(", ")}`
117
+ );
118
+ }
119
+ return value;
120
+ }
121
+ function parseQueueOwnerFlags(argv, defaultTtlMs) {
122
+ if (argv[0] !== "__queue-owner") {
123
+ return void 0;
124
+ }
125
+ const flags = {
126
+ ttlMs: defaultTtlMs
127
+ };
128
+ const consumeValue = (index, token) => {
129
+ if (token.includes("=")) {
130
+ return {
131
+ value: token.slice(token.indexOf("=") + 1),
132
+ next: index
133
+ };
134
+ }
135
+ const value = argv[index + 1];
136
+ if (!value || value.startsWith("-")) {
137
+ throw new InvalidArgumentError(`${token} requires a value`);
138
+ }
139
+ return {
140
+ value,
141
+ next: index + 1
142
+ };
143
+ };
144
+ for (let index = 1; index < argv.length; index += 1) {
145
+ const token = argv[index];
146
+ if (token === "--session-id" || token.startsWith("--session-id=")) {
147
+ const consumed = consumeValue(index, token);
148
+ flags.sessionId = parseNonEmptyValue("Session id", consumed.value);
149
+ index = consumed.next;
150
+ continue;
151
+ }
152
+ if (token === "--ttl-ms" || token.startsWith("--ttl-ms=")) {
153
+ const consumed = consumeValue(index, token);
154
+ flags.ttlMs = parseNonNegativeMilliseconds(consumed.value);
155
+ index = consumed.next;
156
+ continue;
157
+ }
158
+ if (token === "--permission-mode" || token.startsWith("--permission-mode=")) {
159
+ const consumed = consumeValue(index, token);
160
+ flags.permissionMode = parsePermissionMode(consumed.value);
161
+ index = consumed.next;
162
+ continue;
163
+ }
164
+ if (token === "--non-interactive-permissions" || token.startsWith("--non-interactive-permissions=")) {
165
+ const consumed = consumeValue(index, token);
166
+ flags.nonInteractivePermissions = parseNonInteractivePermissionPolicy(
167
+ consumed.value
168
+ );
169
+ index = consumed.next;
170
+ continue;
171
+ }
172
+ if (token === "--auth-policy" || token.startsWith("--auth-policy=")) {
173
+ const consumed = consumeValue(index, token);
174
+ flags.authPolicy = parseAuthPolicy(consumed.value);
175
+ index = consumed.next;
176
+ continue;
177
+ }
178
+ if (token === "--timeout-ms" || token.startsWith("--timeout-ms=")) {
179
+ const consumed = consumeValue(index, token);
180
+ flags.timeoutMs = parseTimeoutMilliseconds(consumed.value);
181
+ index = consumed.next;
182
+ continue;
183
+ }
184
+ if (token === "--verbose") {
185
+ flags.verbose = true;
186
+ continue;
187
+ }
188
+ if (token === "--suppress-sdk-console-errors") {
189
+ flags.suppressSdkConsoleErrors = true;
190
+ continue;
191
+ }
192
+ throw new InvalidArgumentError(`Unknown __queue-owner option: ${token}`);
193
+ }
194
+ if (!flags.sessionId) {
195
+ throw new InvalidArgumentError("__queue-owner requires --session-id");
196
+ }
197
+ if (!flags.permissionMode) {
198
+ throw new InvalidArgumentError("__queue-owner requires --permission-mode");
199
+ }
200
+ return {
201
+ sessionId: flags.sessionId,
202
+ ttlMs: flags.ttlMs ?? defaultTtlMs,
203
+ permissionMode: flags.permissionMode,
204
+ nonInteractivePermissions: flags.nonInteractivePermissions,
205
+ authPolicy: flags.authPolicy,
206
+ timeoutMs: flags.timeoutMs,
207
+ verbose: flags.verbose,
208
+ suppressSdkConsoleErrors: flags.suppressSdkConsoleErrors
209
+ };
210
+ }
211
+
212
+ // src/cli-public.ts
213
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
214
+ function configurePublicCli(options) {
215
+ const builtInAgents = options.listBuiltInAgents(options.config.agents);
216
+ for (const agentName of builtInAgents) {
217
+ options.registerAgentCommand(options.program, agentName, options.config);
218
+ }
219
+ options.registerDefaultCommands(options.program, options.config);
220
+ const scan = options.detectAgentToken(options.argv);
221
+ if (!scan.hasAgentOverride && scan.token && !options.topLevelVerbs.has(scan.token) && !builtInAgents.includes(scan.token)) {
222
+ options.registerAgentCommand(options.program, scan.token, options.config);
223
+ }
224
+ options.program.argument("[prompt...]", "Prompt text").action(async function(promptParts) {
225
+ if (promptParts.length === 0 && process.stdin.isTTY) {
226
+ if (options.requestedJsonStrict) {
227
+ throw new InvalidArgumentError2(
228
+ "Prompt is required (pass as argument, --file, or pipe via stdin)"
229
+ );
230
+ }
231
+ this.outputHelp();
232
+ return;
233
+ }
234
+ await options.handlePromptAction(this, promptParts);
235
+ });
236
+ options.program.addHelpText(
237
+ "after",
238
+ `
239
+ Examples:
240
+ acpx codex sessions new
241
+ acpx codex "fix the tests"
242
+ acpx codex prompt "fix the tests"
243
+ acpx codex --no-wait "queue follow-up task"
244
+ acpx codex exec "what does this repo do"
245
+ acpx codex cancel
246
+ acpx codex set-mode plan
247
+ acpx codex set approval_policy conservative
248
+ acpx codex -s backend "fix the API"
249
+ acpx codex sessions
250
+ acpx codex sessions new --name backend
251
+ acpx codex sessions ensure --name backend
252
+ acpx codex sessions close backend
253
+ acpx codex status
254
+ acpx config show
255
+ acpx config init
256
+ acpx --ttl 30 codex "investigate flaky tests"
257
+ acpx claude "refactor auth"
258
+ acpx gemini "add logging"
259
+ acpx --agent ./my-custom-server "do something"`
260
+ );
261
+ }
262
+
46
263
  // src/config.ts
47
264
  import fs from "fs/promises";
48
265
  import os from "os";
@@ -92,7 +309,7 @@ function parseTimeoutMs(value, sourcePath) {
92
309
  }
93
310
  return Math.round(value * 1e3);
94
311
  }
95
- function parsePermissionMode(value, sourcePath) {
312
+ function parsePermissionMode2(value, sourcePath) {
96
313
  if (value == null) {
97
314
  return void 0;
98
315
  }
@@ -103,7 +320,7 @@ function parsePermissionMode(value, sourcePath) {
103
320
  }
104
321
  return value;
105
322
  }
106
- function parseNonInteractivePermissionPolicy(value, sourcePath) {
323
+ function parseNonInteractivePermissionPolicy2(value, sourcePath) {
107
324
  if (value == null) {
108
325
  return void 0;
109
326
  }
@@ -116,7 +333,7 @@ function parseNonInteractivePermissionPolicy(value, sourcePath) {
116
333
  }
117
334
  return value;
118
335
  }
119
- function parseAuthPolicy(value, sourcePath) {
336
+ function parseAuthPolicy2(value, sourcePath) {
120
337
  if (value == null) {
121
338
  return void 0;
122
339
  }
@@ -239,15 +456,15 @@ async function loadResolvedConfig(cwd) {
239
456
  const globalConfig = globalResult.config;
240
457
  const projectConfig = projectResult.config;
241
458
  const defaultAgent = parseDefaultAgent(projectConfig?.defaultAgent, projectPath) ?? parseDefaultAgent(globalConfig?.defaultAgent, globalPath) ?? DEFAULT_AGENT_NAME;
242
- const defaultPermissions = parsePermissionMode(projectConfig?.defaultPermissions, projectPath) ?? parsePermissionMode(globalConfig?.defaultPermissions, globalPath) ?? DEFAULT_PERMISSION_MODE;
243
- const nonInteractivePermissions = parseNonInteractivePermissionPolicy(
459
+ const defaultPermissions = parsePermissionMode2(projectConfig?.defaultPermissions, projectPath) ?? parsePermissionMode2(globalConfig?.defaultPermissions, globalPath) ?? DEFAULT_PERMISSION_MODE;
460
+ const nonInteractivePermissions = parseNonInteractivePermissionPolicy2(
244
461
  projectConfig?.nonInteractivePermissions,
245
462
  projectPath
246
- ) ?? parseNonInteractivePermissionPolicy(
463
+ ) ?? parseNonInteractivePermissionPolicy2(
247
464
  globalConfig?.nonInteractivePermissions,
248
465
  globalPath
249
466
  ) ?? DEFAULT_NON_INTERACTIVE_PERMISSION_POLICY;
250
- const authPolicy = parseAuthPolicy(projectConfig?.authPolicy, projectPath) ?? parseAuthPolicy(globalConfig?.authPolicy, globalPath) ?? DEFAULT_AUTH_POLICY;
467
+ const authPolicy = parseAuthPolicy2(projectConfig?.authPolicy, projectPath) ?? parseAuthPolicy2(globalConfig?.authPolicy, globalPath) ?? DEFAULT_AUTH_POLICY;
251
468
  const ttlMs = parseTtlMs(projectConfig?.ttl, projectPath) ?? parseTtlMs(globalConfig?.ttl, globalPath) ?? DEFAULT_TTL_MS;
252
469
  const timeoutConfiguredInProject = projectConfig != null && Object.prototype.hasOwnProperty.call(projectConfig, "timeout");
253
470
  const timeoutConfiguredInGlobal = globalConfig != null && Object.prototype.hasOwnProperty.call(globalConfig, "timeout");
@@ -388,96 +605,14 @@ var PermissionPromptUnavailableError = class extends AcpxOperationalError {
388
605
  }
389
606
  };
390
607
 
391
- // src/types.ts
392
- var EXIT_CODES = {
393
- SUCCESS: 0,
394
- ERROR: 1,
395
- USAGE: 2,
396
- TIMEOUT: 3,
397
- NO_SESSION: 4,
398
- PERMISSION_DENIED: 5,
399
- INTERRUPTED: 130
400
- };
401
- var OUTPUT_FORMATS = ["text", "json", "quiet"];
402
- var AUTH_POLICIES = ["skip", "fail"];
403
- var NON_INTERACTIVE_PERMISSION_POLICIES = ["deny", "fail"];
404
- var OUTPUT_ERROR_CODES = [
405
- "NO_SESSION",
406
- "TIMEOUT",
407
- "PERMISSION_DENIED",
408
- "PERMISSION_PROMPT_UNAVAILABLE",
409
- "RUNTIME",
410
- "USAGE"
411
- ];
412
- var OUTPUT_ERROR_ORIGINS = ["cli", "runtime", "queue", "acp"];
413
-
414
- // src/error-normalization.ts
608
+ // src/acp-error-shapes.ts
415
609
  var RESOURCE_NOT_FOUND_ACP_CODES = /* @__PURE__ */ new Set([-32001, -32002]);
416
- var AUTH_REQUIRED_ACP_CODES = /* @__PURE__ */ new Set([-32e3]);
417
610
  function asRecord(value) {
418
611
  if (!value || typeof value !== "object" || Array.isArray(value)) {
419
612
  return void 0;
420
613
  }
421
614
  return value;
422
615
  }
423
- function isAuthRequiredMessage(value) {
424
- if (!value) {
425
- return false;
426
- }
427
- const normalized = value.toLowerCase();
428
- 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");
429
- }
430
- function isAcpAuthRequiredPayload(acp) {
431
- if (!acp) {
432
- return false;
433
- }
434
- if (!AUTH_REQUIRED_ACP_CODES.has(acp.code)) {
435
- return false;
436
- }
437
- if (isAuthRequiredMessage(acp.message)) {
438
- return true;
439
- }
440
- const data = asRecord(acp.data);
441
- if (!data) {
442
- return false;
443
- }
444
- if (data.authRequired === true) {
445
- return true;
446
- }
447
- const methodId = data.methodId;
448
- if (typeof methodId === "string" && methodId.trim().length > 0) {
449
- return true;
450
- }
451
- const methods = data.methods;
452
- if (Array.isArray(methods) && methods.length > 0) {
453
- return true;
454
- }
455
- return false;
456
- }
457
- function isOutputErrorCode(value) {
458
- return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
459
- }
460
- function isOutputErrorOrigin(value) {
461
- return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
462
- }
463
- function readOutputErrorMeta(error) {
464
- const record = asRecord(error);
465
- if (!record) {
466
- return {};
467
- }
468
- const outputCode = isOutputErrorCode(record.outputCode) ? record.outputCode : void 0;
469
- const detailCode = typeof record.detailCode === "string" && record.detailCode.trim().length > 0 ? record.detailCode : void 0;
470
- const origin = isOutputErrorOrigin(record.origin) ? record.origin : void 0;
471
- const retryable = typeof record.retryable === "boolean" ? record.retryable : void 0;
472
- const acp = toAcpErrorPayload(record.acp);
473
- return {
474
- outputCode,
475
- detailCode,
476
- origin,
477
- retryable,
478
- acp
479
- };
480
- }
481
616
  function toAcpErrorPayload(value) {
482
617
  const record = asRecord(value);
483
618
  if (!record) {
@@ -521,19 +656,7 @@ function extractAcpErrorInternal(value, depth) {
521
656
  }
522
657
  return void 0;
523
658
  }
524
- function isTimeoutLike(error) {
525
- return error instanceof Error && error.name === "TimeoutError";
526
- }
527
- function isNoSessionLike(error) {
528
- return error instanceof Error && error.name === "NoSessionError";
529
- }
530
- function isUsageLike(error) {
531
- if (!(error instanceof Error)) {
532
- return false;
533
- }
534
- return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord(error)?.code === "commander.invalidArgument";
535
- }
536
- function formatErrorMessage(error) {
659
+ function formatUnknownErrorMessage(error) {
537
660
  if (error instanceof Error) {
538
661
  return error.message;
539
662
  }
@@ -549,6 +672,31 @@ function formatErrorMessage(error) {
549
672
  }
550
673
  return String(error);
551
674
  }
675
+ function isSessionNotFoundText(value) {
676
+ if (typeof value !== "string") {
677
+ return false;
678
+ }
679
+ const normalized = value.toLowerCase();
680
+ return normalized.includes("resource_not_found") || normalized.includes("resource not found") || normalized.includes("session not found") || normalized.includes("unknown session");
681
+ }
682
+ function hasSessionNotFoundHint(value, depth = 0) {
683
+ if (depth > 4) {
684
+ return false;
685
+ }
686
+ if (isSessionNotFoundText(value)) {
687
+ return true;
688
+ }
689
+ if (Array.isArray(value)) {
690
+ return value.some((entry) => hasSessionNotFoundHint(entry, depth + 1));
691
+ }
692
+ const record = asRecord(value);
693
+ if (!record) {
694
+ return false;
695
+ }
696
+ return Object.values(record).some(
697
+ (entry) => hasSessionNotFoundHint(entry, depth + 1)
698
+ );
699
+ }
552
700
  function extractAcpError(error) {
553
701
  return extractAcpErrorInternal(error, 0);
554
702
  }
@@ -557,12 +705,131 @@ function isAcpResourceNotFoundError(error) {
557
705
  if (acp && RESOURCE_NOT_FOUND_ACP_CODES.has(acp.code)) {
558
706
  return true;
559
707
  }
560
- const message = formatErrorMessage(error).toLowerCase();
561
- return message.includes("resource_not_found") || message.includes("resource not found") || message.includes("session not found") || message.includes("unknown session");
708
+ if (acp) {
709
+ if (isSessionNotFoundText(acp.message)) {
710
+ return true;
711
+ }
712
+ if (hasSessionNotFoundHint(acp.data)) {
713
+ return true;
714
+ }
715
+ }
716
+ return isSessionNotFoundText(formatUnknownErrorMessage(error));
562
717
  }
563
- function mapErrorCode(error) {
564
- if (error instanceof PermissionPromptUnavailableError) {
565
- return "PERMISSION_PROMPT_UNAVAILABLE";
718
+
719
+ // src/error-normalization.ts
720
+ var AUTH_REQUIRED_ACP_CODES = /* @__PURE__ */ new Set([-32e3]);
721
+ function asRecord2(value) {
722
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
723
+ return void 0;
724
+ }
725
+ return value;
726
+ }
727
+ function isAuthRequiredMessage(value) {
728
+ if (!value) {
729
+ return false;
730
+ }
731
+ const normalized = value.toLowerCase();
732
+ 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");
733
+ }
734
+ function isAcpAuthRequiredPayload(acp) {
735
+ if (!acp) {
736
+ return false;
737
+ }
738
+ if (!AUTH_REQUIRED_ACP_CODES.has(acp.code)) {
739
+ return false;
740
+ }
741
+ if (isAuthRequiredMessage(acp.message)) {
742
+ return true;
743
+ }
744
+ const data = asRecord2(acp.data);
745
+ if (!data) {
746
+ return false;
747
+ }
748
+ if (data.authRequired === true) {
749
+ return true;
750
+ }
751
+ const methodId = data.methodId;
752
+ if (typeof methodId === "string" && methodId.trim().length > 0) {
753
+ return true;
754
+ }
755
+ const methods = data.methods;
756
+ if (Array.isArray(methods) && methods.length > 0) {
757
+ return true;
758
+ }
759
+ return false;
760
+ }
761
+ function isOutputErrorCode(value) {
762
+ return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
763
+ }
764
+ function isOutputErrorOrigin(value) {
765
+ return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
766
+ }
767
+ function readOutputErrorMeta(error) {
768
+ const record = asRecord2(error);
769
+ if (!record) {
770
+ return {};
771
+ }
772
+ const outputCode = isOutputErrorCode(record.outputCode) ? record.outputCode : void 0;
773
+ const detailCode = typeof record.detailCode === "string" && record.detailCode.trim().length > 0 ? record.detailCode : void 0;
774
+ const origin = isOutputErrorOrigin(record.origin) ? record.origin : void 0;
775
+ const retryable = typeof record.retryable === "boolean" ? record.retryable : void 0;
776
+ const acp = toAcpErrorPayload2(record.acp);
777
+ return {
778
+ outputCode,
779
+ detailCode,
780
+ origin,
781
+ retryable,
782
+ acp
783
+ };
784
+ }
785
+ function toAcpErrorPayload2(value) {
786
+ const record = asRecord2(value);
787
+ if (!record) {
788
+ return void 0;
789
+ }
790
+ if (typeof record.code !== "number" || !Number.isFinite(record.code)) {
791
+ return void 0;
792
+ }
793
+ if (typeof record.message !== "string" || record.message.length === 0) {
794
+ return void 0;
795
+ }
796
+ return {
797
+ code: record.code,
798
+ message: record.message,
799
+ data: record.data
800
+ };
801
+ }
802
+ function isTimeoutLike(error) {
803
+ return error instanceof Error && error.name === "TimeoutError";
804
+ }
805
+ function isNoSessionLike(error) {
806
+ return error instanceof Error && error.name === "NoSessionError";
807
+ }
808
+ function isUsageLike(error) {
809
+ if (!(error instanceof Error)) {
810
+ return false;
811
+ }
812
+ return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord2(error)?.code === "commander.invalidArgument";
813
+ }
814
+ function formatErrorMessage(error) {
815
+ if (error instanceof Error) {
816
+ return error.message;
817
+ }
818
+ if (error && typeof error === "object") {
819
+ const maybeMessage = error.message;
820
+ if (typeof maybeMessage === "string" && maybeMessage.length > 0) {
821
+ return maybeMessage;
822
+ }
823
+ try {
824
+ return JSON.stringify(error);
825
+ } catch {
826
+ }
827
+ }
828
+ return String(error);
829
+ }
830
+ function mapErrorCode(error) {
831
+ if (error instanceof PermissionPromptUnavailableError) {
832
+ return "PERMISSION_PROMPT_UNAVAILABLE";
566
833
  }
567
834
  if (error instanceof PermissionDeniedError) {
568
835
  return "PERMISSION_DENIED";
@@ -658,7 +925,7 @@ function toStatusLabel(status) {
658
925
  return "running";
659
926
  }
660
927
  }
661
- function asRecord2(value) {
928
+ function asRecord3(value) {
662
929
  if (!value || typeof value !== "object" || Array.isArray(value)) {
663
930
  return void 0;
664
931
  }
@@ -752,7 +1019,7 @@ function summarizeToolInput(rawInput) {
752
1019
  if (typeof rawInput === "string" || typeof rawInput === "number" || typeof rawInput === "boolean") {
753
1020
  return toInline(String(rawInput));
754
1021
  }
755
- const record = asRecord2(rawInput);
1022
+ const record = asRecord3(rawInput);
756
1023
  if (record) {
757
1024
  const command = readFirstString(record, ["command", "cmd", "program"]);
758
1025
  const args = readFirstStringArray(record, ["args", "arguments"]);
@@ -886,7 +1153,7 @@ function extractOutputText(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
886
1153
  }
887
1154
  return dedupeStrings(parts).join("\n");
888
1155
  }
889
- const record = asRecord2(value);
1156
+ const record = asRecord3(value);
890
1157
  if (!record) {
891
1158
  return void 0;
892
1159
  }
@@ -2086,6 +2353,9 @@ var TerminalManager = class {
2086
2353
  var REPLAY_IDLE_MS = 80;
2087
2354
  var REPLAY_DRAIN_TIMEOUT_MS = 5e3;
2088
2355
  var DRAIN_POLL_INTERVAL_MS = 20;
2356
+ var AGENT_CLOSE_AFTER_STDIN_END_MS = 100;
2357
+ var AGENT_CLOSE_TERM_GRACE_MS = 1500;
2358
+ var AGENT_CLOSE_KILL_GRACE_MS = 1e3;
2089
2359
  function shouldSuppressSdkConsoleError(args) {
2090
2360
  if (args.length === 0) {
2091
2361
  return false;
@@ -2121,6 +2391,38 @@ function waitForSpawn2(child) {
2121
2391
  child.once("error", onError);
2122
2392
  });
2123
2393
  }
2394
+ function isChildProcessRunning(child) {
2395
+ return child.exitCode == null && child.signalCode == null;
2396
+ }
2397
+ function waitForChildExit(child, timeoutMs) {
2398
+ if (!isChildProcessRunning(child)) {
2399
+ return Promise.resolve(true);
2400
+ }
2401
+ return new Promise((resolve) => {
2402
+ let settled = false;
2403
+ const timer = setTimeout(
2404
+ () => {
2405
+ finish(false);
2406
+ },
2407
+ Math.max(0, timeoutMs)
2408
+ );
2409
+ const finish = (value) => {
2410
+ if (settled) {
2411
+ return;
2412
+ }
2413
+ settled = true;
2414
+ child.off("close", onExitLike);
2415
+ child.off("exit", onExitLike);
2416
+ clearTimeout(timer);
2417
+ resolve(value);
2418
+ };
2419
+ const onExitLike = () => {
2420
+ finish(true);
2421
+ };
2422
+ child.once("close", onExitLike);
2423
+ child.once("exit", onExitLike);
2424
+ });
2425
+ }
2124
2426
  function splitCommandLine(value) {
2125
2427
  const parts = [];
2126
2428
  let current = "";
@@ -2542,8 +2844,8 @@ var AcpClient = class {
2542
2844
  async close() {
2543
2845
  this.closing = true;
2544
2846
  await this.terminalManager.shutdown();
2545
- if (this.agent && !this.agent.killed) {
2546
- this.agent.kill();
2847
+ if (this.agent) {
2848
+ await this.terminateAgentProcess(this.agent);
2547
2849
  }
2548
2850
  this.sessionUpdateChain = Promise.resolve();
2549
2851
  this.observedSessionUpdates = 0;
@@ -2555,6 +2857,44 @@ var AcpClient = class {
2555
2857
  this.connection = void 0;
2556
2858
  this.agent = void 0;
2557
2859
  }
2860
+ async terminateAgentProcess(child) {
2861
+ if (!child.stdin.destroyed) {
2862
+ try {
2863
+ child.stdin.end();
2864
+ } catch {
2865
+ }
2866
+ }
2867
+ let exited = await waitForChildExit(child, AGENT_CLOSE_AFTER_STDIN_END_MS);
2868
+ if (!exited && isChildProcessRunning(child)) {
2869
+ try {
2870
+ child.kill("SIGTERM");
2871
+ } catch {
2872
+ }
2873
+ exited = await waitForChildExit(child, AGENT_CLOSE_TERM_GRACE_MS);
2874
+ }
2875
+ if (!exited && isChildProcessRunning(child)) {
2876
+ this.log(
2877
+ `agent did not exit after ${AGENT_CLOSE_TERM_GRACE_MS}ms; forcing SIGKILL`
2878
+ );
2879
+ try {
2880
+ child.kill("SIGKILL");
2881
+ } catch {
2882
+ }
2883
+ exited = await waitForChildExit(child, AGENT_CLOSE_KILL_GRACE_MS);
2884
+ }
2885
+ if (!child.stdin.destroyed) {
2886
+ child.stdin.destroy();
2887
+ }
2888
+ if (!child.stdout.destroyed) {
2889
+ child.stdout.destroy();
2890
+ }
2891
+ if (!child.stderr.destroyed) {
2892
+ child.stderr.destroy();
2893
+ }
2894
+ if (!exited) {
2895
+ child.unref();
2896
+ }
2897
+ }
2558
2898
  getConnection() {
2559
2899
  if (!this.connection) {
2560
2900
  throw new Error("ACP client not started");
@@ -2768,118 +3108,273 @@ var AcpClient = class {
2768
3108
  }
2769
3109
  };
2770
3110
 
2771
- // src/queue-owner-turn-controller.ts
2772
- var QueueOwnerTurnController = class {
2773
- options;
2774
- state = "idle";
2775
- pendingCancel = false;
2776
- activeController;
2777
- constructor(options) {
2778
- this.options = options;
2779
- }
2780
- get lifecycleState() {
2781
- return this.state;
2782
- }
2783
- get hasPendingCancel() {
2784
- return this.pendingCancel;
2785
- }
2786
- beginTurn() {
2787
- this.state = "starting";
2788
- this.pendingCancel = false;
2789
- }
2790
- markPromptActive() {
2791
- if (this.state === "starting" || this.state === "active") {
2792
- this.state = "active";
2793
- }
3111
+ // src/queue-lease-store.ts
3112
+ import { createHash } from "crypto";
3113
+ import fs3 from "fs/promises";
3114
+ import os2 from "os";
3115
+ import path4 from "path";
3116
+ var PROCESS_EXIT_GRACE_MS = 1500;
3117
+ var PROCESS_POLL_MS = 50;
3118
+ var QUEUE_OWNER_STALE_HEARTBEAT_MS = 15e3;
3119
+ function queueBaseDir() {
3120
+ return path4.join(os2.homedir(), ".acpx", "queues");
3121
+ }
3122
+ function queueKeyForSession(sessionId) {
3123
+ return createHash("sha256").update(sessionId).digest("hex").slice(0, 24);
3124
+ }
3125
+ function queueLockFilePath(sessionId) {
3126
+ return path4.join(queueBaseDir(), `${queueKeyForSession(sessionId)}.lock`);
3127
+ }
3128
+ function queueSocketPath(sessionId) {
3129
+ const key = queueKeyForSession(sessionId);
3130
+ if (process.platform === "win32") {
3131
+ return `\\\\.\\pipe\\acpx-${key}`;
2794
3132
  }
2795
- endTurn() {
2796
- this.state = "idle";
2797
- this.pendingCancel = false;
3133
+ return path4.join(queueBaseDir(), `${key}.sock`);
3134
+ }
3135
+ function parseQueueOwnerRecord(raw) {
3136
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
3137
+ return null;
2798
3138
  }
2799
- beginClosing() {
2800
- this.state = "closing";
2801
- this.pendingCancel = false;
2802
- this.activeController = void 0;
3139
+ const record = raw;
3140
+ 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) {
3141
+ return null;
2803
3142
  }
2804
- setActiveController(controller) {
2805
- this.activeController = controller;
3143
+ return {
3144
+ pid: record.pid,
3145
+ sessionId: record.sessionId,
3146
+ socketPath: record.socketPath,
3147
+ createdAt: record.createdAt,
3148
+ heartbeatAt: record.heartbeatAt,
3149
+ ownerGeneration: record.ownerGeneration,
3150
+ queueDepth: record.queueDepth
3151
+ };
3152
+ }
3153
+ function createOwnerGeneration() {
3154
+ return Date.now() * 1e3 + Math.floor(Math.random() * 1e3);
3155
+ }
3156
+ function nowIso4() {
3157
+ return (/* @__PURE__ */ new Date()).toISOString();
3158
+ }
3159
+ function isQueueOwnerHeartbeatStale(owner) {
3160
+ const heartbeatMs = Date.parse(owner.heartbeatAt);
3161
+ if (!Number.isFinite(heartbeatMs)) {
3162
+ return true;
2806
3163
  }
2807
- clearActiveController() {
2808
- this.activeController = void 0;
3164
+ return Date.now() - heartbeatMs > QUEUE_OWNER_STALE_HEARTBEAT_MS;
3165
+ }
3166
+ async function ensureQueueDir() {
3167
+ await fs3.mkdir(queueBaseDir(), { recursive: true });
3168
+ }
3169
+ async function removeSocketFile(socketPath) {
3170
+ if (process.platform === "win32") {
3171
+ return;
2809
3172
  }
2810
- assertCanHandleControlRequest() {
2811
- if (this.state === "closing") {
2812
- throw new QueueConnectionError("Queue owner is closing", {
2813
- detailCode: "QUEUE_OWNER_SHUTTING_DOWN",
2814
- origin: "queue",
2815
- retryable: true
2816
- });
3173
+ try {
3174
+ await fs3.unlink(socketPath);
3175
+ } catch (error) {
3176
+ if (error.code !== "ENOENT") {
3177
+ throw error;
2817
3178
  }
2818
3179
  }
2819
- async requestCancel() {
2820
- const activeController = this.activeController;
2821
- if (activeController?.hasActivePrompt()) {
2822
- const cancelled2 = await activeController.requestCancelActivePrompt();
2823
- if (cancelled2) {
2824
- this.pendingCancel = false;
2825
- }
2826
- return cancelled2;
2827
- }
2828
- if (this.state === "starting" || this.state === "active") {
2829
- this.pendingCancel = true;
3180
+ }
3181
+ async function waitForProcessExit(pid, timeoutMs) {
3182
+ const deadline = Date.now() + Math.max(0, timeoutMs);
3183
+ while (Date.now() <= deadline) {
3184
+ if (!isProcessAlive(pid)) {
2830
3185
  return true;
2831
3186
  }
2832
- return false;
3187
+ await waitMs2(PROCESS_POLL_MS);
2833
3188
  }
2834
- async applyPendingCancel() {
2835
- const activeController = this.activeController;
2836
- if (!this.pendingCancel || !activeController || !activeController.hasActivePrompt()) {
2837
- return false;
2838
- }
2839
- const cancelled2 = await activeController.requestCancelActivePrompt();
2840
- if (cancelled2) {
2841
- this.pendingCancel = false;
3189
+ return !isProcessAlive(pid);
3190
+ }
3191
+ async function cleanupStaleQueueOwner(sessionId, owner) {
3192
+ const lockPath = queueLockFilePath(sessionId);
3193
+ const socketPath = owner?.socketPath ?? queueSocketPath(sessionId);
3194
+ await removeSocketFile(socketPath).catch(() => {
3195
+ });
3196
+ await fs3.unlink(lockPath).catch((error) => {
3197
+ if (error.code !== "ENOENT") {
3198
+ throw error;
2842
3199
  }
2843
- return cancelled2;
3200
+ });
3201
+ }
3202
+ async function readQueueOwnerRecord(sessionId) {
3203
+ const lockPath = queueLockFilePath(sessionId);
3204
+ try {
3205
+ const payload = await fs3.readFile(lockPath, "utf8");
3206
+ const parsed = parseQueueOwnerRecord(JSON.parse(payload));
3207
+ return parsed ?? void 0;
3208
+ } catch {
3209
+ return void 0;
2844
3210
  }
2845
- async setSessionMode(modeId, timeoutMs) {
2846
- this.assertCanHandleControlRequest();
2847
- const activeController = this.activeController;
2848
- if (activeController) {
2849
- await this.options.withTimeout(
2850
- async () => await activeController.setSessionMode(modeId),
2851
- timeoutMs
2852
- );
2853
- return;
3211
+ }
3212
+ function isProcessAlive(pid) {
3213
+ if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) {
3214
+ return false;
3215
+ }
3216
+ try {
3217
+ process.kill(pid, 0);
3218
+ return true;
3219
+ } catch {
3220
+ return false;
3221
+ }
3222
+ }
3223
+ async function terminateProcess(pid) {
3224
+ if (!isProcessAlive(pid)) {
3225
+ return false;
3226
+ }
3227
+ try {
3228
+ process.kill(pid, "SIGTERM");
3229
+ } catch {
3230
+ return false;
3231
+ }
3232
+ if (await waitForProcessExit(pid, PROCESS_EXIT_GRACE_MS)) {
3233
+ return true;
3234
+ }
3235
+ try {
3236
+ process.kill(pid, "SIGKILL");
3237
+ } catch {
3238
+ return false;
3239
+ }
3240
+ await waitForProcessExit(pid, PROCESS_EXIT_GRACE_MS);
3241
+ return true;
3242
+ }
3243
+ async function ensureOwnerIsUsable(sessionId, owner) {
3244
+ const alive = isProcessAlive(owner.pid);
3245
+ const stale = isQueueOwnerHeartbeatStale(owner);
3246
+ if (alive && !stale) {
3247
+ return true;
3248
+ }
3249
+ if (alive) {
3250
+ await terminateProcess(owner.pid).catch(() => {
3251
+ });
3252
+ }
3253
+ await cleanupStaleQueueOwner(sessionId, owner);
3254
+ return false;
3255
+ }
3256
+ async function readQueueOwnerStatus(sessionId) {
3257
+ const owner = await readQueueOwnerRecord(sessionId);
3258
+ if (!owner) {
3259
+ return void 0;
3260
+ }
3261
+ const alive = await ensureOwnerIsUsable(sessionId, owner);
3262
+ if (!alive) {
3263
+ return void 0;
3264
+ }
3265
+ return {
3266
+ pid: owner.pid,
3267
+ socketPath: owner.socketPath,
3268
+ heartbeatAt: owner.heartbeatAt,
3269
+ ownerGeneration: owner.ownerGeneration,
3270
+ queueDepth: owner.queueDepth,
3271
+ alive,
3272
+ stale: isQueueOwnerHeartbeatStale(owner)
3273
+ };
3274
+ }
3275
+ async function tryAcquireQueueOwnerLease(sessionId, nowIsoFactory = nowIso4) {
3276
+ await ensureQueueDir();
3277
+ const lockPath = queueLockFilePath(sessionId);
3278
+ const socketPath = queueSocketPath(sessionId);
3279
+ const createdAt = nowIsoFactory();
3280
+ const ownerGeneration = createOwnerGeneration();
3281
+ const payload = JSON.stringify(
3282
+ {
3283
+ pid: process.pid,
3284
+ sessionId,
3285
+ socketPath,
3286
+ createdAt,
3287
+ heartbeatAt: createdAt,
3288
+ ownerGeneration,
3289
+ queueDepth: 0
3290
+ },
3291
+ null,
3292
+ 2
3293
+ );
3294
+ try {
3295
+ await fs3.writeFile(lockPath, `${payload}
3296
+ `, {
3297
+ encoding: "utf8",
3298
+ flag: "wx"
3299
+ });
3300
+ await removeSocketFile(socketPath).catch(() => {
3301
+ });
3302
+ return {
3303
+ sessionId,
3304
+ lockPath,
3305
+ socketPath,
3306
+ createdAt,
3307
+ ownerGeneration
3308
+ };
3309
+ } catch (error) {
3310
+ if (error.code !== "EEXIST") {
3311
+ throw error;
2854
3312
  }
2855
- await this.options.setSessionModeFallback(modeId, timeoutMs);
3313
+ const owner = await readQueueOwnerRecord(sessionId);
3314
+ if (!owner) {
3315
+ await cleanupStaleQueueOwner(sessionId, owner);
3316
+ return void 0;
3317
+ }
3318
+ if (!isProcessAlive(owner.pid) || isQueueOwnerHeartbeatStale(owner)) {
3319
+ if (isProcessAlive(owner.pid)) {
3320
+ await terminateProcess(owner.pid).catch(() => {
3321
+ });
3322
+ }
3323
+ await cleanupStaleQueueOwner(sessionId, owner);
3324
+ }
3325
+ return void 0;
2856
3326
  }
2857
- async setSessionConfigOption(configId, value, timeoutMs) {
2858
- this.assertCanHandleControlRequest();
2859
- const activeController = this.activeController;
2860
- if (activeController) {
2861
- return await this.options.withTimeout(
2862
- async () => await activeController.setSessionConfigOption(configId, value),
2863
- timeoutMs
2864
- );
3327
+ }
3328
+ async function refreshQueueOwnerLease(lease, options, nowIsoFactory = nowIso4) {
3329
+ const payload = JSON.stringify(
3330
+ {
3331
+ pid: process.pid,
3332
+ sessionId: lease.sessionId,
3333
+ socketPath: lease.socketPath,
3334
+ createdAt: lease.createdAt,
3335
+ heartbeatAt: nowIsoFactory(),
3336
+ ownerGeneration: lease.ownerGeneration,
3337
+ queueDepth: Math.max(0, Math.round(options.queueDepth))
3338
+ },
3339
+ null,
3340
+ 2
3341
+ );
3342
+ await fs3.writeFile(lease.lockPath, `${payload}
3343
+ `, {
3344
+ encoding: "utf8"
3345
+ });
3346
+ }
3347
+ async function releaseQueueOwnerLease(lease) {
3348
+ await removeSocketFile(lease.socketPath).catch(() => {
3349
+ });
3350
+ await fs3.unlink(lease.lockPath).catch((error) => {
3351
+ if (error.code !== "ENOENT") {
3352
+ throw error;
2865
3353
  }
2866
- return await this.options.setSessionConfigOptionFallback(
2867
- configId,
2868
- value,
2869
- timeoutMs
2870
- );
3354
+ });
3355
+ }
3356
+ async function terminateQueueOwnerForSession(sessionId) {
3357
+ const owner = await readQueueOwnerRecord(sessionId);
3358
+ if (!owner) {
3359
+ return;
2871
3360
  }
2872
- };
3361
+ if (isProcessAlive(owner.pid)) {
3362
+ await terminateProcess(owner.pid);
3363
+ }
3364
+ await cleanupStaleQueueOwner(sessionId, owner);
3365
+ }
3366
+ async function waitMs2(ms) {
3367
+ await new Promise((resolve) => {
3368
+ setTimeout(resolve, ms);
3369
+ });
3370
+ }
2873
3371
 
2874
- // src/queue-ipc.ts
2875
- import { createHash, randomUUID as randomUUID2 } from "crypto";
2876
- import fs3 from "fs/promises";
3372
+ // src/queue-ipc-client.ts
3373
+ import { randomUUID as randomUUID2 } from "crypto";
2877
3374
  import net from "net";
2878
- import os2 from "os";
2879
- import path4 from "path";
2880
3375
 
2881
3376
  // src/queue-messages.ts
2882
- function asRecord3(value) {
3377
+ function asRecord4(value) {
2883
3378
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2884
3379
  return void 0;
2885
3380
  }
@@ -2898,7 +3393,7 @@ function isOutputErrorOrigin2(value) {
2898
3393
  return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
2899
3394
  }
2900
3395
  function parseAcpError(value) {
2901
- const record = asRecord3(value);
3396
+ const record = asRecord4(value);
2902
3397
  if (!record) {
2903
3398
  return void 0;
2904
3399
  }
@@ -2915,7 +3410,7 @@ function parseAcpError(value) {
2915
3410
  };
2916
3411
  }
2917
3412
  function parseQueueRequest(raw) {
2918
- const request = asRecord3(raw);
3413
+ const request = asRecord4(raw);
2919
3414
  if (!request) {
2920
3415
  return null;
2921
3416
  }
@@ -2973,15 +3468,15 @@ function parseQueueRequest(raw) {
2973
3468
  return null;
2974
3469
  }
2975
3470
  function parseSessionSendResult(raw) {
2976
- const result = asRecord3(raw);
3471
+ const result = asRecord4(raw);
2977
3472
  if (!result) {
2978
3473
  return null;
2979
3474
  }
2980
3475
  if (typeof result.stopReason !== "string" || typeof result.sessionId !== "string" || typeof result.resumed !== "boolean") {
2981
3476
  return null;
2982
3477
  }
2983
- const permissionStats = asRecord3(result.permissionStats);
2984
- const record = asRecord3(result.record);
3478
+ const permissionStats = asRecord4(result.permissionStats);
3479
+ const record = asRecord4(result.record);
2985
3480
  if (!permissionStats || !record) {
2986
3481
  return null;
2987
3482
  }
@@ -2996,7 +3491,7 @@ function parseSessionSendResult(raw) {
2996
3491
  return result;
2997
3492
  }
2998
3493
  function parseQueueOwnerMessage(raw) {
2999
- const message = asRecord3(raw);
3494
+ const message = asRecord4(raw);
3000
3495
  if (!message || typeof message.type !== "string") {
3001
3496
  return null;
3002
3497
  }
@@ -3021,7 +3516,7 @@ function parseQueueOwnerMessage(raw) {
3021
3516
  };
3022
3517
  }
3023
3518
  if (message.type === "client_operation") {
3024
- const operation = asRecord3(message.operation);
3519
+ const operation = asRecord4(message.operation);
3025
3520
  if (!operation || typeof operation.method !== "string" || typeof operation.status !== "string" || typeof operation.summary !== "string" || typeof operation.timestamp !== "string") {
3026
3521
  return null;
3027
3522
  }
@@ -3076,7 +3571,7 @@ function parseQueueOwnerMessage(raw) {
3076
3571
  };
3077
3572
  }
3078
3573
  if (message.type === "set_config_option_result") {
3079
- const response = asRecord3(message.response);
3574
+ const response = asRecord4(message.response);
3080
3575
  if (!response || !Array.isArray(response.configOptions)) {
3081
3576
  return null;
3082
3577
  }
@@ -3109,504 +3604,354 @@ function parseQueueOwnerMessage(raw) {
3109
3604
  return null;
3110
3605
  }
3111
3606
 
3112
- // src/queue-ipc.ts
3113
- var PROCESS_EXIT_GRACE_MS = 1500;
3114
- var PROCESS_POLL_MS = 50;
3607
+ // src/queue-ipc-client.ts
3115
3608
  var QUEUE_CONNECT_ATTEMPTS = 40;
3116
3609
  var QUEUE_CONNECT_RETRY_MS = 50;
3117
- function queueBaseDir() {
3118
- return path4.join(os2.homedir(), ".acpx", "queues");
3119
- }
3120
- function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
3121
- return {
3122
- type: "error",
3123
- requestId,
3124
- code: "RUNTIME",
3125
- detailCode,
3126
- origin: "queue",
3127
- retryable: options.retryable,
3128
- message
3129
- };
3610
+ function shouldRetryQueueConnect(error) {
3611
+ const code = error.code;
3612
+ return code === "ENOENT" || code === "ECONNREFUSED";
3130
3613
  }
3131
- function makeQueueOwnerErrorFromUnknown(requestId, error, detailCode, options = {}) {
3132
- const normalized = normalizeOutputError(error, {
3133
- defaultCode: "RUNTIME",
3134
- origin: "queue",
3135
- detailCode,
3136
- retryable: options.retryable
3614
+ async function connectToSocket(socketPath) {
3615
+ return await new Promise((resolve, reject) => {
3616
+ const socket = net.createConnection(socketPath);
3617
+ const onConnect = () => {
3618
+ socket.off("error", onError);
3619
+ resolve(socket);
3620
+ };
3621
+ const onError = (error) => {
3622
+ socket.off("connect", onConnect);
3623
+ reject(error);
3624
+ };
3625
+ socket.once("connect", onConnect);
3626
+ socket.once("error", onError);
3137
3627
  });
3138
- return {
3139
- type: "error",
3140
- requestId,
3141
- code: normalized.code,
3142
- detailCode: normalized.detailCode,
3143
- origin: normalized.origin,
3144
- message: normalized.message,
3145
- retryable: normalized.retryable,
3146
- acp: normalized.acp
3147
- };
3148
3628
  }
3149
- function isProcessAlive(pid) {
3150
- if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) {
3151
- return false;
3629
+ async function connectToQueueOwner(owner) {
3630
+ let lastError;
3631
+ for (let attempt = 0; attempt < QUEUE_CONNECT_ATTEMPTS; attempt += 1) {
3632
+ try {
3633
+ return await connectToSocket(owner.socketPath);
3634
+ } catch (error) {
3635
+ lastError = error;
3636
+ if (!shouldRetryQueueConnect(error)) {
3637
+ throw error;
3638
+ }
3639
+ if (!isProcessAlive(owner.pid)) {
3640
+ return void 0;
3641
+ }
3642
+ await waitMs2(QUEUE_CONNECT_RETRY_MS);
3643
+ }
3152
3644
  }
3153
- try {
3154
- process.kill(pid, 0);
3155
- return true;
3156
- } catch {
3157
- return false;
3645
+ if (lastError && !shouldRetryQueueConnect(lastError)) {
3646
+ throw lastError;
3158
3647
  }
3648
+ return void 0;
3159
3649
  }
3160
- async function waitForProcessExit(pid, timeoutMs) {
3161
- const deadline = Date.now() + Math.max(0, timeoutMs);
3162
- while (Date.now() <= deadline) {
3163
- if (!isProcessAlive(pid)) {
3164
- return true;
3165
- }
3166
- await new Promise((resolve) => {
3167
- setTimeout(resolve, PROCESS_POLL_MS);
3168
- });
3650
+ async function submitToQueueOwner(owner, options) {
3651
+ const socket = await connectToQueueOwner(owner);
3652
+ if (!socket) {
3653
+ return void 0;
3169
3654
  }
3170
- return !isProcessAlive(pid);
3171
- }
3172
- async function terminateProcess(pid) {
3173
- if (!isProcessAlive(pid)) {
3174
- return false;
3175
- }
3176
- try {
3177
- process.kill(pid, "SIGTERM");
3178
- } catch {
3179
- return false;
3180
- }
3181
- if (await waitForProcessExit(pid, PROCESS_EXIT_GRACE_MS)) {
3182
- return true;
3183
- }
3184
- try {
3185
- process.kill(pid, "SIGKILL");
3186
- } catch {
3187
- return false;
3188
- }
3189
- await waitForProcessExit(pid, PROCESS_EXIT_GRACE_MS);
3190
- return true;
3191
- }
3192
- function parseQueueOwnerRecord(raw) {
3193
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
3194
- return null;
3195
- }
3196
- const record = raw;
3197
- if (!Number.isInteger(record.pid) || record.pid <= 0 || typeof record.sessionId !== "string" || typeof record.socketPath !== "string") {
3198
- return null;
3199
- }
3200
- return {
3201
- pid: record.pid,
3202
- sessionId: record.sessionId,
3203
- socketPath: record.socketPath
3655
+ socket.setEncoding("utf8");
3656
+ const requestId = randomUUID2();
3657
+ const request = {
3658
+ type: "submit_prompt",
3659
+ requestId,
3660
+ message: options.message,
3661
+ permissionMode: options.permissionMode,
3662
+ nonInteractivePermissions: options.nonInteractivePermissions,
3663
+ timeoutMs: options.timeoutMs,
3664
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
3665
+ waitForCompletion: options.waitForCompletion
3204
3666
  };
3205
- }
3206
- function queueKeyForSession(sessionId) {
3207
- return createHash("sha256").update(sessionId).digest("hex").slice(0, 24);
3208
- }
3209
- function queueLockFilePath(sessionId) {
3210
- return path4.join(queueBaseDir(), `${queueKeyForSession(sessionId)}.lock`);
3211
- }
3212
- function queueSocketPath(sessionId) {
3213
- const key = queueKeyForSession(sessionId);
3214
- if (process.platform === "win32") {
3215
- return `\\\\.\\pipe\\acpx-${key}`;
3216
- }
3217
- return path4.join(queueBaseDir(), `${key}.sock`);
3218
- }
3219
- async function ensureQueueDir() {
3220
- await fs3.mkdir(queueBaseDir(), { recursive: true });
3221
- }
3222
- async function removeSocketFile(socketPath) {
3223
- if (process.platform === "win32") {
3224
- return;
3225
- }
3226
- try {
3227
- await fs3.unlink(socketPath);
3228
- } catch (error) {
3229
- if (error.code !== "ENOENT") {
3230
- throw error;
3231
- }
3232
- }
3233
- }
3234
- async function readQueueOwnerRecord(sessionId) {
3235
- const lockPath = queueLockFilePath(sessionId);
3236
- try {
3237
- const payload = await fs3.readFile(lockPath, "utf8");
3238
- const parsed = parseQueueOwnerRecord(JSON.parse(payload));
3239
- return parsed ?? void 0;
3240
- } catch {
3241
- return void 0;
3242
- }
3243
- }
3244
- async function cleanupStaleQueueOwner(sessionId, owner) {
3245
- const lockPath = queueLockFilePath(sessionId);
3246
- const socketPath = owner?.socketPath ?? queueSocketPath(sessionId);
3247
- await removeSocketFile(socketPath).catch(() => {
3248
- });
3249
- await fs3.unlink(lockPath).catch((error) => {
3250
- if (error.code !== "ENOENT") {
3251
- throw error;
3252
- }
3253
- });
3254
- }
3255
- async function tryAcquireQueueOwnerLease(sessionId, nowIso4 = () => (/* @__PURE__ */ new Date()).toISOString()) {
3256
- await ensureQueueDir();
3257
- const lockPath = queueLockFilePath(sessionId);
3258
- const socketPath = queueSocketPath(sessionId);
3259
- const payload = JSON.stringify(
3260
- {
3261
- pid: process.pid,
3262
- sessionId,
3263
- socketPath,
3264
- createdAt: nowIso4()
3265
- },
3266
- null,
3267
- 2
3268
- );
3269
- try {
3270
- await fs3.writeFile(lockPath, `${payload}
3271
- `, {
3272
- encoding: "utf8",
3273
- flag: "wx"
3274
- });
3275
- await removeSocketFile(socketPath).catch(() => {
3276
- });
3277
- return { lockPath, socketPath };
3278
- } catch (error) {
3279
- if (error.code !== "EEXIST") {
3280
- throw error;
3281
- }
3282
- const owner = await readQueueOwnerRecord(sessionId);
3283
- if (!owner || !isProcessAlive(owner.pid)) {
3284
- await cleanupStaleQueueOwner(sessionId, owner);
3285
- }
3286
- return void 0;
3287
- }
3288
- }
3289
- async function releaseQueueOwnerLease(lease) {
3290
- await removeSocketFile(lease.socketPath).catch(() => {
3291
- });
3292
- await fs3.unlink(lease.lockPath).catch((error) => {
3293
- if (error.code !== "ENOENT") {
3294
- throw error;
3295
- }
3296
- });
3297
- }
3298
- function shouldRetryQueueConnect(error) {
3299
- const code = error.code;
3300
- return code === "ENOENT" || code === "ECONNREFUSED";
3301
- }
3302
- async function waitMs2(ms) {
3303
- await new Promise((resolve) => {
3304
- setTimeout(resolve, ms);
3667
+ options.outputFormatter.setContext({
3668
+ sessionId: options.sessionId,
3669
+ requestId,
3670
+ stream: "prompt"
3305
3671
  });
3306
- }
3307
- async function connectToSocket(socketPath) {
3308
3672
  return await new Promise((resolve, reject) => {
3309
- const socket = net.createConnection(socketPath);
3310
- const onConnect = () => {
3311
- socket.off("error", onError);
3312
- resolve(socket);
3673
+ let settled = false;
3674
+ let acknowledged = false;
3675
+ let buffer = "";
3676
+ let sawDone = false;
3677
+ const finishResolve = (result) => {
3678
+ if (settled) {
3679
+ return;
3680
+ }
3681
+ settled = true;
3682
+ socket.removeAllListeners();
3683
+ if (!socket.destroyed) {
3684
+ socket.end();
3685
+ }
3686
+ resolve(result);
3313
3687
  };
3314
- const onError = (error) => {
3315
- socket.off("connect", onConnect);
3688
+ const finishReject = (error) => {
3689
+ if (settled) {
3690
+ return;
3691
+ }
3692
+ settled = true;
3693
+ socket.removeAllListeners();
3694
+ if (!socket.destroyed) {
3695
+ socket.destroy();
3696
+ }
3316
3697
  reject(error);
3317
3698
  };
3318
- socket.once("connect", onConnect);
3319
- socket.once("error", onError);
3320
- });
3321
- }
3322
- async function connectToQueueOwner(owner) {
3323
- let lastError;
3324
- for (let attempt = 0; attempt < QUEUE_CONNECT_ATTEMPTS; attempt += 1) {
3325
- try {
3326
- return await connectToSocket(owner.socketPath);
3327
- } catch (error) {
3328
- lastError = error;
3329
- if (!shouldRetryQueueConnect(error)) {
3330
- throw error;
3699
+ const processLine = (line) => {
3700
+ let parsed;
3701
+ try {
3702
+ parsed = JSON.parse(line);
3703
+ } catch {
3704
+ finishReject(
3705
+ new QueueProtocolError("Queue owner sent invalid JSON payload", {
3706
+ detailCode: "QUEUE_PROTOCOL_INVALID_JSON",
3707
+ origin: "queue",
3708
+ retryable: true
3709
+ })
3710
+ );
3711
+ return;
3331
3712
  }
3332
- if (!isProcessAlive(owner.pid)) {
3333
- return void 0;
3713
+ const message = parseQueueOwnerMessage(parsed);
3714
+ if (!message || message.requestId !== requestId) {
3715
+ finishReject(
3716
+ new QueueProtocolError("Queue owner sent malformed message", {
3717
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
3718
+ origin: "queue",
3719
+ retryable: true
3720
+ })
3721
+ );
3722
+ return;
3334
3723
  }
3335
- await waitMs2(QUEUE_CONNECT_RETRY_MS);
3336
- }
3337
- }
3338
- if (lastError && !shouldRetryQueueConnect(lastError)) {
3339
- throw lastError;
3340
- }
3341
- return void 0;
3342
- }
3343
- function writeQueueMessage(socket, message) {
3344
- if (socket.destroyed || !socket.writable) {
3345
- return;
3346
- }
3347
- socket.write(`${JSON.stringify(message)}
3348
- `);
3349
- }
3350
- var SessionQueueOwner = class _SessionQueueOwner {
3351
- server;
3352
- controlHandlers;
3353
- pending = [];
3354
- waiters = [];
3355
- closed = false;
3356
- constructor(server, controlHandlers) {
3357
- this.server = server;
3358
- this.controlHandlers = controlHandlers;
3359
- }
3360
- static async start(lease, controlHandlers) {
3361
- const ownerRef = { current: void 0 };
3362
- const server = net.createServer((socket) => {
3363
- ownerRef.current?.handleConnection(socket);
3364
- });
3365
- ownerRef.current = new _SessionQueueOwner(server, controlHandlers);
3366
- await new Promise((resolve, reject) => {
3367
- const onListening = () => {
3368
- server.off("error", onError);
3369
- resolve();
3370
- };
3371
- const onError = (error) => {
3372
- server.off("listening", onListening);
3373
- reject(error);
3374
- };
3375
- server.once("listening", onListening);
3376
- server.once("error", onError);
3377
- server.listen(lease.socketPath);
3378
- });
3379
- return ownerRef.current;
3380
- }
3381
- async close() {
3382
- if (this.closed) {
3383
- return;
3384
- }
3385
- this.closed = true;
3386
- for (const waiter of this.waiters.splice(0)) {
3387
- waiter(void 0);
3388
- }
3389
- for (const task of this.pending.splice(0)) {
3390
- if (task.waitForCompletion) {
3391
- task.send(
3392
- makeQueueOwnerError(
3393
- task.requestId,
3394
- "Queue owner shutting down before prompt execution",
3395
- "QUEUE_OWNER_SHUTTING_DOWN",
3396
- {
3397
- retryable: true
3398
- }
3399
- )
3724
+ if (message.type === "accepted") {
3725
+ acknowledged = true;
3726
+ options.outputFormatter.setContext({
3727
+ sessionId: options.sessionId,
3728
+ requestId: message.requestId,
3729
+ stream: "prompt"
3730
+ });
3731
+ if (!options.waitForCompletion) {
3732
+ const queued = {
3733
+ queued: true,
3734
+ sessionId: options.sessionId,
3735
+ requestId
3736
+ };
3737
+ finishResolve(queued);
3738
+ }
3739
+ return;
3740
+ }
3741
+ if (message.type === "error") {
3742
+ options.outputFormatter.setContext({
3743
+ sessionId: options.sessionId,
3744
+ requestId: message.requestId,
3745
+ stream: "prompt"
3746
+ });
3747
+ options.outputFormatter.onError({
3748
+ code: message.code ?? "RUNTIME",
3749
+ detailCode: message.detailCode,
3750
+ origin: message.origin ?? "queue",
3751
+ message: message.message,
3752
+ retryable: message.retryable,
3753
+ acp: message.acp
3754
+ });
3755
+ options.outputFormatter.flush();
3756
+ const queueErrorAlreadyEmitted = options.errorEmissionPolicy?.queueErrorAlreadyEmitted ?? true;
3757
+ finishReject(
3758
+ new QueueConnectionError(message.message, {
3759
+ outputCode: message.code,
3760
+ detailCode: message.detailCode,
3761
+ origin: message.origin ?? "queue",
3762
+ retryable: message.retryable,
3763
+ acp: message.acp,
3764
+ ...queueErrorAlreadyEmitted ? { outputAlreadyEmitted: true } : {}
3765
+ })
3400
3766
  );
3767
+ return;
3401
3768
  }
3402
- task.close();
3403
- }
3404
- await new Promise((resolve) => {
3405
- this.server.close(() => resolve());
3406
- });
3407
- }
3408
- async nextTask(timeoutMs) {
3409
- if (this.pending.length > 0) {
3410
- return this.pending.shift();
3411
- }
3412
- if (this.closed) {
3413
- return void 0;
3414
- }
3415
- return await new Promise((resolve) => {
3416
- const shouldTimeout = timeoutMs != null;
3417
- const timer = shouldTimeout && setTimeout(
3418
- () => {
3419
- const index = this.waiters.indexOf(waiter);
3420
- if (index >= 0) {
3421
- this.waiters.splice(index, 1);
3422
- }
3423
- resolve(void 0);
3424
- },
3425
- Math.max(0, timeoutMs)
3769
+ if (!acknowledged) {
3770
+ finishReject(
3771
+ new QueueConnectionError("Queue owner did not acknowledge request", {
3772
+ detailCode: "QUEUE_ACK_MISSING",
3773
+ origin: "queue",
3774
+ retryable: true
3775
+ })
3776
+ );
3777
+ return;
3778
+ }
3779
+ if (message.type === "session_update") {
3780
+ options.outputFormatter.onSessionUpdate(message.notification);
3781
+ return;
3782
+ }
3783
+ if (message.type === "client_operation") {
3784
+ options.outputFormatter.onClientOperation(message.operation);
3785
+ return;
3786
+ }
3787
+ if (message.type === "done") {
3788
+ options.outputFormatter.onDone(message.stopReason);
3789
+ sawDone = true;
3790
+ return;
3791
+ }
3792
+ if (message.type === "result") {
3793
+ if (!sawDone) {
3794
+ options.outputFormatter.onDone(message.result.stopReason);
3795
+ }
3796
+ options.outputFormatter.flush();
3797
+ finishResolve(message.result);
3798
+ return;
3799
+ }
3800
+ finishReject(
3801
+ new QueueProtocolError("Queue owner returned unexpected response", {
3802
+ detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
3803
+ origin: "queue",
3804
+ retryable: true
3805
+ })
3426
3806
  );
3427
- const waiter = (task) => {
3428
- if (timer) {
3429
- clearTimeout(timer);
3807
+ };
3808
+ socket.on("data", (chunk) => {
3809
+ buffer += chunk;
3810
+ let index = buffer.indexOf("\n");
3811
+ while (index >= 0) {
3812
+ const line = buffer.slice(0, index).trim();
3813
+ buffer = buffer.slice(index + 1);
3814
+ if (line.length > 0) {
3815
+ processLine(line);
3430
3816
  }
3431
- resolve(task);
3432
- };
3433
- this.waiters.push(waiter);
3817
+ index = buffer.indexOf("\n");
3818
+ }
3434
3819
  });
3435
- }
3436
- enqueue(task) {
3437
- if (this.closed) {
3438
- if (task.waitForCompletion) {
3439
- task.send(
3440
- makeQueueOwnerError(
3441
- task.requestId,
3442
- "Queue owner is shutting down",
3443
- "QUEUE_OWNER_SHUTTING_DOWN",
3820
+ socket.once("error", (error) => {
3821
+ finishReject(error);
3822
+ });
3823
+ socket.once("close", () => {
3824
+ if (settled) {
3825
+ return;
3826
+ }
3827
+ if (!acknowledged) {
3828
+ finishReject(
3829
+ new QueueConnectionError(
3830
+ "Queue owner disconnected before acknowledging request",
3444
3831
  {
3832
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_ACK",
3833
+ origin: "queue",
3445
3834
  retryable: true
3446
3835
  }
3447
3836
  )
3448
3837
  );
3838
+ return;
3449
3839
  }
3450
- task.close();
3451
- return;
3452
- }
3453
- const waiter = this.waiters.shift();
3454
- if (waiter) {
3455
- waiter(task);
3456
- return;
3457
- }
3458
- this.pending.push(task);
3459
- }
3460
- handleConnection(socket) {
3461
- socket.setEncoding("utf8");
3462
- if (this.closed) {
3463
- writeQueueMessage(
3464
- socket,
3465
- makeQueueOwnerError("unknown", "Queue owner is closed", "QUEUE_OWNER_CLOSED", {
3840
+ if (!options.waitForCompletion) {
3841
+ const queued = {
3842
+ queued: true,
3843
+ sessionId: options.sessionId,
3844
+ requestId
3845
+ };
3846
+ finishResolve(queued);
3847
+ return;
3848
+ }
3849
+ finishReject(
3850
+ new QueueConnectionError("Queue owner disconnected before prompt completion", {
3851
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_COMPLETION",
3852
+ origin: "queue",
3466
3853
  retryable: true
3467
3854
  })
3468
3855
  );
3469
- socket.end();
3470
- return;
3471
- }
3856
+ });
3857
+ socket.write(`${JSON.stringify(request)}
3858
+ `);
3859
+ });
3860
+ }
3861
+ async function submitControlToQueueOwner(owner, request, isExpectedResponse) {
3862
+ const socket = await connectToQueueOwner(owner);
3863
+ if (!socket) {
3864
+ return void 0;
3865
+ }
3866
+ socket.setEncoding("utf8");
3867
+ return await new Promise((resolve, reject) => {
3868
+ let settled = false;
3869
+ let acknowledged = false;
3472
3870
  let buffer = "";
3473
- let handled = false;
3474
- const fail = (requestId, message, detailCode) => {
3475
- writeQueueMessage(
3476
- socket,
3477
- makeQueueOwnerError(requestId, message, detailCode, {
3478
- retryable: false
3479
- })
3480
- );
3481
- socket.end();
3871
+ const finishResolve = (result) => {
3872
+ if (settled) {
3873
+ return;
3874
+ }
3875
+ settled = true;
3876
+ socket.removeAllListeners();
3877
+ if (!socket.destroyed) {
3878
+ socket.end();
3879
+ }
3880
+ resolve(result);
3482
3881
  };
3483
- const processLine = (line) => {
3484
- if (handled) {
3882
+ const finishReject = (error) => {
3883
+ if (settled) {
3485
3884
  return;
3486
3885
  }
3487
- handled = true;
3886
+ settled = true;
3887
+ socket.removeAllListeners();
3888
+ if (!socket.destroyed) {
3889
+ socket.destroy();
3890
+ }
3891
+ reject(error);
3892
+ };
3893
+ const processLine = (line) => {
3488
3894
  let parsed;
3489
3895
  try {
3490
3896
  parsed = JSON.parse(line);
3491
3897
  } catch {
3492
- fail(
3493
- "unknown",
3494
- "Invalid queue request payload",
3495
- "QUEUE_REQUEST_PAYLOAD_INVALID_JSON"
3898
+ finishReject(
3899
+ new QueueProtocolError("Queue owner sent invalid JSON payload", {
3900
+ detailCode: "QUEUE_PROTOCOL_INVALID_JSON",
3901
+ origin: "queue",
3902
+ retryable: true
3903
+ })
3496
3904
  );
3497
3905
  return;
3498
3906
  }
3499
- const request = parseQueueRequest(parsed);
3500
- if (!request) {
3501
- fail("unknown", "Invalid queue request", "QUEUE_REQUEST_INVALID");
3502
- return;
3503
- }
3504
- if (request.type === "cancel_prompt") {
3505
- writeQueueMessage(socket, {
3506
- type: "accepted",
3507
- requestId: request.requestId
3508
- });
3509
- void this.controlHandlers.cancelPrompt().then((cancelled2) => {
3510
- writeQueueMessage(socket, {
3511
- type: "cancel_result",
3512
- requestId: request.requestId,
3513
- cancelled: cancelled2
3514
- });
3515
- }).catch((error) => {
3516
- writeQueueMessage(
3517
- socket,
3518
- makeQueueOwnerErrorFromUnknown(
3519
- request.requestId,
3520
- error,
3521
- "QUEUE_CONTROL_REQUEST_FAILED"
3522
- )
3523
- );
3524
- }).finally(() => {
3525
- if (!socket.destroyed) {
3526
- socket.end();
3527
- }
3528
- });
3907
+ const message = parseQueueOwnerMessage(parsed);
3908
+ if (!message || message.requestId !== request.requestId) {
3909
+ finishReject(
3910
+ new QueueProtocolError("Queue owner sent malformed message", {
3911
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
3912
+ origin: "queue",
3913
+ retryable: true
3914
+ })
3915
+ );
3529
3916
  return;
3530
3917
  }
3531
- if (request.type === "set_mode") {
3532
- writeQueueMessage(socket, {
3533
- type: "accepted",
3534
- requestId: request.requestId
3535
- });
3536
- void this.controlHandlers.setSessionMode(request.modeId, request.timeoutMs).then(() => {
3537
- writeQueueMessage(socket, {
3538
- type: "set_mode_result",
3539
- requestId: request.requestId,
3540
- modeId: request.modeId
3541
- });
3542
- }).catch((error) => {
3543
- writeQueueMessage(
3544
- socket,
3545
- makeQueueOwnerErrorFromUnknown(
3546
- request.requestId,
3547
- error,
3548
- "QUEUE_CONTROL_REQUEST_FAILED"
3549
- )
3550
- );
3551
- }).finally(() => {
3552
- if (!socket.destroyed) {
3553
- socket.end();
3554
- }
3555
- });
3918
+ if (message.type === "accepted") {
3919
+ acknowledged = true;
3556
3920
  return;
3557
3921
  }
3558
- if (request.type === "set_config_option") {
3559
- writeQueueMessage(socket, {
3560
- type: "accepted",
3561
- requestId: request.requestId
3562
- });
3563
- void this.controlHandlers.setSessionConfigOption(request.configId, request.value, request.timeoutMs).then((response) => {
3564
- writeQueueMessage(socket, {
3565
- type: "set_config_option_result",
3566
- requestId: request.requestId,
3567
- response
3568
- });
3569
- }).catch((error) => {
3570
- writeQueueMessage(
3571
- socket,
3572
- makeQueueOwnerErrorFromUnknown(
3573
- request.requestId,
3574
- error,
3575
- "QUEUE_CONTROL_REQUEST_FAILED"
3576
- )
3577
- );
3578
- }).finally(() => {
3579
- if (!socket.destroyed) {
3580
- socket.end();
3581
- }
3582
- });
3922
+ if (message.type === "error") {
3923
+ finishReject(
3924
+ new QueueConnectionError(message.message, {
3925
+ outputCode: message.code,
3926
+ detailCode: message.detailCode,
3927
+ origin: message.origin ?? "queue",
3928
+ retryable: message.retryable,
3929
+ acp: message.acp
3930
+ })
3931
+ );
3583
3932
  return;
3584
3933
  }
3585
- const task = {
3586
- requestId: request.requestId,
3587
- message: request.message,
3588
- permissionMode: request.permissionMode,
3589
- nonInteractivePermissions: request.nonInteractivePermissions,
3590
- timeoutMs: request.timeoutMs,
3591
- suppressSdkConsoleErrors: request.suppressSdkConsoleErrors,
3592
- waitForCompletion: request.waitForCompletion,
3593
- send: (message) => {
3594
- writeQueueMessage(socket, message);
3595
- },
3596
- close: () => {
3597
- if (!socket.destroyed) {
3598
- socket.end();
3599
- }
3600
- }
3601
- };
3602
- writeQueueMessage(socket, {
3603
- type: "accepted",
3604
- requestId: request.requestId
3605
- });
3606
- if (!request.waitForCompletion) {
3607
- task.close();
3934
+ if (!acknowledged) {
3935
+ finishReject(
3936
+ new QueueConnectionError("Queue owner did not acknowledge request", {
3937
+ detailCode: "QUEUE_ACK_MISSING",
3938
+ origin: "queue",
3939
+ retryable: true
3940
+ })
3941
+ );
3942
+ return;
3608
3943
  }
3609
- this.enqueue(task);
3944
+ if (!isExpectedResponse(message)) {
3945
+ finishReject(
3946
+ new QueueProtocolError("Queue owner returned unexpected response", {
3947
+ detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
3948
+ origin: "queue",
3949
+ retryable: true
3950
+ })
3951
+ );
3952
+ return;
3953
+ }
3954
+ finishResolve(message);
3610
3955
  };
3611
3956
  socket.on("data", (chunk) => {
3612
3957
  buffer += chunk;
@@ -3620,315 +3965,539 @@ var SessionQueueOwner = class _SessionQueueOwner {
3620
3965
  index = buffer.indexOf("\n");
3621
3966
  }
3622
3967
  });
3623
- socket.on("error", () => {
3968
+ socket.once("error", (error) => {
3969
+ finishReject(error);
3970
+ });
3971
+ socket.once("close", () => {
3972
+ if (settled) {
3973
+ return;
3974
+ }
3975
+ if (!acknowledged) {
3976
+ finishReject(
3977
+ new QueueConnectionError(
3978
+ "Queue owner disconnected before acknowledging request",
3979
+ {
3980
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_ACK",
3981
+ origin: "queue",
3982
+ retryable: true
3983
+ }
3984
+ )
3985
+ );
3986
+ return;
3987
+ }
3988
+ finishReject(
3989
+ new QueueConnectionError("Queue owner disconnected before responding", {
3990
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_COMPLETION",
3991
+ origin: "queue",
3992
+ retryable: true
3993
+ })
3994
+ );
3995
+ });
3996
+ socket.write(`${JSON.stringify(request)}
3997
+ `);
3998
+ });
3999
+ }
4000
+ async function submitCancelToQueueOwner(owner) {
4001
+ const request = {
4002
+ type: "cancel_prompt",
4003
+ requestId: randomUUID2()
4004
+ };
4005
+ const response = await submitControlToQueueOwner(
4006
+ owner,
4007
+ request,
4008
+ (message) => message.type === "cancel_result"
4009
+ );
4010
+ if (!response) {
4011
+ return void 0;
4012
+ }
4013
+ if (response.requestId !== request.requestId) {
4014
+ throw new QueueProtocolError("Queue owner returned mismatched cancel response", {
4015
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
4016
+ origin: "queue",
4017
+ retryable: true
4018
+ });
4019
+ }
4020
+ return response.cancelled;
4021
+ }
4022
+ async function submitSetModeToQueueOwner(owner, modeId, timeoutMs) {
4023
+ const request = {
4024
+ type: "set_mode",
4025
+ requestId: randomUUID2(),
4026
+ modeId,
4027
+ timeoutMs
4028
+ };
4029
+ const response = await submitControlToQueueOwner(
4030
+ owner,
4031
+ request,
4032
+ (message) => message.type === "set_mode_result"
4033
+ );
4034
+ if (!response) {
4035
+ return void 0;
4036
+ }
4037
+ if (response.requestId !== request.requestId) {
4038
+ throw new QueueProtocolError("Queue owner returned mismatched set_mode response", {
4039
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
4040
+ origin: "queue",
4041
+ retryable: true
4042
+ });
4043
+ }
4044
+ return true;
4045
+ }
4046
+ async function submitSetConfigOptionToQueueOwner(owner, configId, value, timeoutMs) {
4047
+ const request = {
4048
+ type: "set_config_option",
4049
+ requestId: randomUUID2(),
4050
+ configId,
4051
+ value,
4052
+ timeoutMs
4053
+ };
4054
+ const response = await submitControlToQueueOwner(
4055
+ owner,
4056
+ request,
4057
+ (message) => message.type === "set_config_option_result"
4058
+ );
4059
+ if (!response) {
4060
+ return void 0;
4061
+ }
4062
+ if (response.requestId !== request.requestId) {
4063
+ throw new QueueProtocolError(
4064
+ "Queue owner returned mismatched set_config_option response",
4065
+ {
4066
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
4067
+ origin: "queue",
4068
+ retryable: true
4069
+ }
4070
+ );
4071
+ }
4072
+ return response.response;
4073
+ }
4074
+ async function trySubmitToRunningOwner(options) {
4075
+ const owner = await readQueueOwnerRecord(options.sessionId);
4076
+ if (!owner) {
4077
+ return void 0;
4078
+ }
4079
+ if (!await ensureOwnerIsUsable(options.sessionId, owner)) {
4080
+ return void 0;
4081
+ }
4082
+ const submitted = await submitToQueueOwner(owner, options);
4083
+ if (submitted) {
4084
+ if (options.verbose) {
4085
+ process.stderr.write(
4086
+ `[acpx] queued prompt on active owner pid ${owner.pid} for session ${options.sessionId}
4087
+ `
4088
+ );
4089
+ }
4090
+ return submitted;
4091
+ }
4092
+ if (!await ensureOwnerIsUsable(options.sessionId, owner)) {
4093
+ return void 0;
4094
+ }
4095
+ throw new QueueConnectionError(
4096
+ "Session queue owner is running but not accepting queue requests",
4097
+ {
4098
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4099
+ origin: "queue",
4100
+ retryable: true
4101
+ }
4102
+ );
4103
+ }
4104
+ async function tryCancelOnRunningOwner(options) {
4105
+ const owner = await readQueueOwnerRecord(options.sessionId);
4106
+ if (!owner) {
4107
+ return void 0;
4108
+ }
4109
+ if (!await ensureOwnerIsUsable(options.sessionId, owner)) {
4110
+ return void 0;
4111
+ }
4112
+ const cancelled2 = await submitCancelToQueueOwner(owner);
4113
+ if (cancelled2 !== void 0) {
4114
+ if (options.verbose) {
4115
+ process.stderr.write(
4116
+ `[acpx] requested cancel on active owner pid ${owner.pid} for session ${options.sessionId}
4117
+ `
4118
+ );
4119
+ }
4120
+ return cancelled2;
4121
+ }
4122
+ if (!await ensureOwnerIsUsable(options.sessionId, owner)) {
4123
+ return void 0;
4124
+ }
4125
+ throw new QueueConnectionError(
4126
+ "Session queue owner is running but not accepting cancel requests",
4127
+ {
4128
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4129
+ origin: "queue",
4130
+ retryable: true
4131
+ }
4132
+ );
4133
+ }
4134
+ async function trySetModeOnRunningOwner(sessionId, modeId, timeoutMs, verbose) {
4135
+ const owner = await readQueueOwnerRecord(sessionId);
4136
+ if (!owner) {
4137
+ return void 0;
4138
+ }
4139
+ if (!await ensureOwnerIsUsable(sessionId, owner)) {
4140
+ return void 0;
4141
+ }
4142
+ const submitted = await submitSetModeToQueueOwner(owner, modeId, timeoutMs);
4143
+ if (submitted) {
4144
+ if (verbose) {
4145
+ process.stderr.write(
4146
+ `[acpx] requested session/set_mode on owner pid ${owner.pid} for session ${sessionId}
4147
+ `
4148
+ );
4149
+ }
4150
+ return true;
4151
+ }
4152
+ if (!await ensureOwnerIsUsable(sessionId, owner)) {
4153
+ return void 0;
4154
+ }
4155
+ throw new QueueConnectionError(
4156
+ "Session queue owner is running but not accepting set_mode requests",
4157
+ {
4158
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4159
+ origin: "queue",
4160
+ retryable: true
4161
+ }
4162
+ );
4163
+ }
4164
+ async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, timeoutMs, verbose) {
4165
+ const owner = await readQueueOwnerRecord(sessionId);
4166
+ if (!owner) {
4167
+ return void 0;
4168
+ }
4169
+ if (!await ensureOwnerIsUsable(sessionId, owner)) {
4170
+ return void 0;
4171
+ }
4172
+ const response = await submitSetConfigOptionToQueueOwner(
4173
+ owner,
4174
+ configId,
4175
+ value,
4176
+ timeoutMs
4177
+ );
4178
+ if (response) {
4179
+ if (verbose) {
4180
+ process.stderr.write(
4181
+ `[acpx] requested session/set_config_option on owner pid ${owner.pid} for session ${sessionId}
4182
+ `
4183
+ );
4184
+ }
4185
+ return response;
4186
+ }
4187
+ if (!await ensureOwnerIsUsable(sessionId, owner)) {
4188
+ return void 0;
4189
+ }
4190
+ throw new QueueConnectionError(
4191
+ "Session queue owner is running but not accepting set_config_option requests",
4192
+ {
4193
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4194
+ origin: "queue",
4195
+ retryable: true
4196
+ }
4197
+ );
4198
+ }
4199
+
4200
+ // src/queue-ipc-server.ts
4201
+ import net2 from "net";
4202
+ function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
4203
+ return {
4204
+ type: "error",
4205
+ requestId,
4206
+ code: "RUNTIME",
4207
+ detailCode,
4208
+ origin: "queue",
4209
+ retryable: options.retryable,
4210
+ message
4211
+ };
4212
+ }
4213
+ function makeQueueOwnerErrorFromUnknown(requestId, error, detailCode, options = {}) {
4214
+ const normalized = normalizeOutputError(error, {
4215
+ defaultCode: "RUNTIME",
4216
+ origin: "queue",
4217
+ detailCode,
4218
+ retryable: options.retryable
4219
+ });
4220
+ return {
4221
+ type: "error",
4222
+ requestId,
4223
+ code: normalized.code,
4224
+ detailCode: normalized.detailCode,
4225
+ origin: normalized.origin,
4226
+ message: normalized.message,
4227
+ retryable: normalized.retryable,
4228
+ acp: normalized.acp
4229
+ };
4230
+ }
4231
+ function writeQueueMessage(socket, message) {
4232
+ if (socket.destroyed || !socket.writable) {
4233
+ return;
4234
+ }
4235
+ socket.write(`${JSON.stringify(message)}
4236
+ `);
4237
+ }
4238
+ var SessionQueueOwner = class _SessionQueueOwner {
4239
+ server;
4240
+ controlHandlers;
4241
+ pending = [];
4242
+ waiters = [];
4243
+ closed = false;
4244
+ constructor(server, controlHandlers) {
4245
+ this.server = server;
4246
+ this.controlHandlers = controlHandlers;
4247
+ }
4248
+ static async start(lease, controlHandlers) {
4249
+ const ownerRef = { current: void 0 };
4250
+ const server = net2.createServer((socket) => {
4251
+ ownerRef.current?.handleConnection(socket);
4252
+ });
4253
+ ownerRef.current = new _SessionQueueOwner(server, controlHandlers);
4254
+ await new Promise((resolve, reject) => {
4255
+ const onListening = () => {
4256
+ server.off("error", onError);
4257
+ resolve();
4258
+ };
4259
+ const onError = (error) => {
4260
+ server.off("listening", onListening);
4261
+ reject(error);
4262
+ };
4263
+ server.once("listening", onListening);
4264
+ server.once("error", onError);
4265
+ server.listen(lease.socketPath);
3624
4266
  });
4267
+ return ownerRef.current;
3625
4268
  }
3626
- };
3627
- async function submitToQueueOwner(owner, options) {
3628
- const socket = await connectToQueueOwner(owner);
3629
- if (!socket) {
3630
- return void 0;
3631
- }
3632
- socket.setEncoding("utf8");
3633
- const requestId = randomUUID2();
3634
- const request = {
3635
- type: "submit_prompt",
3636
- requestId,
3637
- message: options.message,
3638
- permissionMode: options.permissionMode,
3639
- nonInteractivePermissions: options.nonInteractivePermissions,
3640
- timeoutMs: options.timeoutMs,
3641
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
3642
- waitForCompletion: options.waitForCompletion
3643
- };
3644
- options.outputFormatter.setContext({
3645
- sessionId: options.sessionId,
3646
- requestId,
3647
- stream: "prompt"
3648
- });
3649
- return await new Promise((resolve, reject) => {
3650
- let settled = false;
3651
- let acknowledged = false;
3652
- let buffer = "";
3653
- let sawDone = false;
3654
- const finishResolve = (result) => {
3655
- if (settled) {
3656
- return;
3657
- }
3658
- settled = true;
3659
- socket.removeAllListeners();
3660
- if (!socket.destroyed) {
3661
- socket.end();
3662
- }
3663
- resolve(result);
3664
- };
3665
- const finishReject = (error) => {
3666
- if (settled) {
3667
- return;
3668
- }
3669
- settled = true;
3670
- socket.removeAllListeners();
3671
- if (!socket.destroyed) {
3672
- socket.destroy();
3673
- }
3674
- reject(error);
3675
- };
3676
- const processLine = (line) => {
3677
- let parsed;
3678
- try {
3679
- parsed = JSON.parse(line);
3680
- } catch {
3681
- finishReject(
3682
- new QueueProtocolError("Queue owner sent invalid JSON payload", {
3683
- detailCode: "QUEUE_PROTOCOL_INVALID_JSON",
3684
- origin: "queue",
3685
- retryable: true
3686
- })
3687
- );
3688
- return;
3689
- }
3690
- const message = parseQueueOwnerMessage(parsed);
3691
- if (!message || message.requestId !== requestId) {
3692
- finishReject(
3693
- new QueueProtocolError("Queue owner sent malformed message", {
3694
- detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
3695
- origin: "queue",
3696
- retryable: true
3697
- })
3698
- );
3699
- return;
3700
- }
3701
- if (message.type === "accepted") {
3702
- acknowledged = true;
3703
- options.outputFormatter.setContext({
3704
- sessionId: options.sessionId,
3705
- requestId: message.requestId,
3706
- stream: "prompt"
3707
- });
3708
- if (!options.waitForCompletion) {
3709
- const queued = {
3710
- queued: true,
3711
- sessionId: options.sessionId,
3712
- requestId
3713
- };
3714
- finishResolve(queued);
3715
- }
3716
- return;
3717
- }
3718
- if (message.type === "error") {
3719
- options.outputFormatter.setContext({
3720
- sessionId: options.sessionId,
3721
- requestId: message.requestId,
3722
- stream: "prompt"
3723
- });
3724
- options.outputFormatter.onError({
3725
- code: message.code ?? "RUNTIME",
3726
- detailCode: message.detailCode,
3727
- origin: message.origin ?? "queue",
3728
- message: message.message,
3729
- retryable: message.retryable,
3730
- acp: message.acp
3731
- });
3732
- options.outputFormatter.flush();
3733
- const queueErrorAlreadyEmitted = options.errorEmissionPolicy?.queueErrorAlreadyEmitted ?? true;
3734
- finishReject(
3735
- new QueueConnectionError(message.message, {
3736
- outputCode: message.code,
3737
- detailCode: message.detailCode,
3738
- origin: message.origin ?? "queue",
3739
- retryable: message.retryable,
3740
- acp: message.acp,
3741
- ...queueErrorAlreadyEmitted ? { outputAlreadyEmitted: true } : {}
3742
- })
3743
- );
3744
- return;
3745
- }
3746
- if (!acknowledged) {
3747
- finishReject(
3748
- new QueueConnectionError("Queue owner did not acknowledge request", {
3749
- detailCode: "QUEUE_ACK_MISSING",
3750
- origin: "queue",
3751
- retryable: true
3752
- })
4269
+ async close() {
4270
+ if (this.closed) {
4271
+ return;
4272
+ }
4273
+ this.closed = true;
4274
+ for (const waiter of this.waiters.splice(0)) {
4275
+ waiter(void 0);
4276
+ }
4277
+ for (const task of this.pending.splice(0)) {
4278
+ if (task.waitForCompletion) {
4279
+ task.send(
4280
+ makeQueueOwnerError(
4281
+ task.requestId,
4282
+ "Queue owner shutting down before prompt execution",
4283
+ "QUEUE_OWNER_SHUTTING_DOWN",
4284
+ {
4285
+ retryable: true
4286
+ }
4287
+ )
3753
4288
  );
3754
- return;
3755
- }
3756
- if (message.type === "session_update") {
3757
- options.outputFormatter.onSessionUpdate(message.notification);
3758
- return;
3759
- }
3760
- if (message.type === "client_operation") {
3761
- options.outputFormatter.onClientOperation(message.operation);
3762
- return;
3763
- }
3764
- if (message.type === "done") {
3765
- options.outputFormatter.onDone(message.stopReason);
3766
- sawDone = true;
3767
- return;
3768
- }
3769
- if (message.type === "result") {
3770
- if (!sawDone) {
3771
- options.outputFormatter.onDone(message.result.stopReason);
3772
- }
3773
- options.outputFormatter.flush();
3774
- finishResolve(message.result);
3775
- return;
3776
4289
  }
3777
- finishReject(
3778
- new QueueProtocolError("Queue owner returned unexpected response", {
3779
- detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
3780
- origin: "queue",
3781
- retryable: true
3782
- })
4290
+ task.close();
4291
+ }
4292
+ await new Promise((resolve) => {
4293
+ this.server.close(() => resolve());
4294
+ });
4295
+ }
4296
+ async nextTask(timeoutMs) {
4297
+ if (this.pending.length > 0) {
4298
+ return this.pending.shift();
4299
+ }
4300
+ if (this.closed) {
4301
+ return void 0;
4302
+ }
4303
+ return await new Promise((resolve) => {
4304
+ const shouldTimeout = timeoutMs != null;
4305
+ const timer = shouldTimeout && setTimeout(
4306
+ () => {
4307
+ const index = this.waiters.indexOf(waiter);
4308
+ if (index >= 0) {
4309
+ this.waiters.splice(index, 1);
4310
+ }
4311
+ resolve(void 0);
4312
+ },
4313
+ Math.max(0, timeoutMs)
3783
4314
  );
3784
- };
3785
- socket.on("data", (chunk) => {
3786
- buffer += chunk;
3787
- let index = buffer.indexOf("\n");
3788
- while (index >= 0) {
3789
- const line = buffer.slice(0, index).trim();
3790
- buffer = buffer.slice(index + 1);
3791
- if (line.length > 0) {
3792
- processLine(line);
4315
+ const waiter = (task) => {
4316
+ if (timer) {
4317
+ clearTimeout(timer);
3793
4318
  }
3794
- index = buffer.indexOf("\n");
3795
- }
3796
- });
3797
- socket.once("error", (error) => {
3798
- finishReject(error);
4319
+ resolve(task);
4320
+ };
4321
+ this.waiters.push(waiter);
3799
4322
  });
3800
- socket.once("close", () => {
3801
- if (settled) {
3802
- return;
3803
- }
3804
- if (!acknowledged) {
3805
- finishReject(
3806
- new QueueConnectionError(
3807
- "Queue owner disconnected before acknowledging request",
3808
- {
3809
- detailCode: "QUEUE_DISCONNECTED_BEFORE_ACK",
3810
- origin: "queue",
4323
+ }
4324
+ queueDepth() {
4325
+ return this.pending.length;
4326
+ }
4327
+ enqueue(task) {
4328
+ if (this.closed) {
4329
+ if (task.waitForCompletion) {
4330
+ task.send(
4331
+ makeQueueOwnerError(
4332
+ task.requestId,
4333
+ "Queue owner is shutting down",
4334
+ "QUEUE_OWNER_SHUTTING_DOWN",
4335
+ {
3811
4336
  retryable: true
3812
4337
  }
3813
4338
  )
3814
4339
  );
3815
- return;
3816
- }
3817
- if (!options.waitForCompletion) {
3818
- const queued = {
3819
- queued: true,
3820
- sessionId: options.sessionId,
3821
- requestId
3822
- };
3823
- finishResolve(queued);
3824
- return;
3825
4340
  }
3826
- finishReject(
3827
- new QueueConnectionError("Queue owner disconnected before prompt completion", {
3828
- detailCode: "QUEUE_DISCONNECTED_BEFORE_COMPLETION",
3829
- origin: "queue",
4341
+ task.close();
4342
+ return;
4343
+ }
4344
+ const waiter = this.waiters.shift();
4345
+ if (waiter) {
4346
+ waiter(task);
4347
+ return;
4348
+ }
4349
+ this.pending.push(task);
4350
+ }
4351
+ handleConnection(socket) {
4352
+ socket.setEncoding("utf8");
4353
+ if (this.closed) {
4354
+ writeQueueMessage(
4355
+ socket,
4356
+ makeQueueOwnerError("unknown", "Queue owner is closed", "QUEUE_OWNER_CLOSED", {
3830
4357
  retryable: true
3831
4358
  })
3832
4359
  );
3833
- });
3834
- socket.write(`${JSON.stringify(request)}
3835
- `);
3836
- });
3837
- }
3838
- async function submitControlToQueueOwner(owner, request, isExpectedResponse) {
3839
- const socket = await connectToQueueOwner(owner);
3840
- if (!socket) {
3841
- return void 0;
3842
- }
3843
- socket.setEncoding("utf8");
3844
- return await new Promise((resolve, reject) => {
3845
- let settled = false;
3846
- let acknowledged = false;
4360
+ socket.end();
4361
+ return;
4362
+ }
3847
4363
  let buffer = "";
3848
- const finishResolve = (result) => {
3849
- if (settled) {
3850
- return;
3851
- }
3852
- settled = true;
3853
- socket.removeAllListeners();
3854
- if (!socket.destroyed) {
3855
- socket.end();
3856
- }
3857
- resolve(result);
4364
+ let handled = false;
4365
+ const fail = (requestId, message, detailCode) => {
4366
+ writeQueueMessage(
4367
+ socket,
4368
+ makeQueueOwnerError(requestId, message, detailCode, {
4369
+ retryable: false
4370
+ })
4371
+ );
4372
+ socket.end();
3858
4373
  };
3859
- const finishReject = (error) => {
3860
- if (settled) {
4374
+ const processLine = (line) => {
4375
+ if (handled) {
3861
4376
  return;
3862
4377
  }
3863
- settled = true;
3864
- socket.removeAllListeners();
3865
- if (!socket.destroyed) {
3866
- socket.destroy();
3867
- }
3868
- reject(error);
3869
- };
3870
- const processLine = (line) => {
4378
+ handled = true;
3871
4379
  let parsed;
3872
4380
  try {
3873
4381
  parsed = JSON.parse(line);
3874
4382
  } catch {
3875
- finishReject(
3876
- new QueueProtocolError("Queue owner sent invalid JSON payload", {
3877
- detailCode: "QUEUE_PROTOCOL_INVALID_JSON",
3878
- origin: "queue",
3879
- retryable: true
3880
- })
4383
+ fail(
4384
+ "unknown",
4385
+ "Invalid queue request payload",
4386
+ "QUEUE_REQUEST_PAYLOAD_INVALID_JSON"
3881
4387
  );
3882
4388
  return;
3883
4389
  }
3884
- const message = parseQueueOwnerMessage(parsed);
3885
- if (!message || message.requestId !== request.requestId) {
3886
- finishReject(
3887
- new QueueProtocolError("Queue owner sent malformed message", {
3888
- detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
3889
- origin: "queue",
3890
- retryable: true
3891
- })
3892
- );
4390
+ const request = parseQueueRequest(parsed);
4391
+ if (!request) {
4392
+ fail("unknown", "Invalid queue request", "QUEUE_REQUEST_INVALID");
3893
4393
  return;
3894
4394
  }
3895
- if (message.type === "accepted") {
3896
- acknowledged = true;
4395
+ if (request.type === "cancel_prompt") {
4396
+ writeQueueMessage(socket, {
4397
+ type: "accepted",
4398
+ requestId: request.requestId
4399
+ });
4400
+ void this.controlHandlers.cancelPrompt().then((cancelled2) => {
4401
+ writeQueueMessage(socket, {
4402
+ type: "cancel_result",
4403
+ requestId: request.requestId,
4404
+ cancelled: cancelled2
4405
+ });
4406
+ }).catch((error) => {
4407
+ writeQueueMessage(
4408
+ socket,
4409
+ makeQueueOwnerErrorFromUnknown(
4410
+ request.requestId,
4411
+ error,
4412
+ "QUEUE_CONTROL_REQUEST_FAILED"
4413
+ )
4414
+ );
4415
+ }).finally(() => {
4416
+ if (!socket.destroyed) {
4417
+ socket.end();
4418
+ }
4419
+ });
3897
4420
  return;
3898
4421
  }
3899
- if (message.type === "error") {
3900
- finishReject(
3901
- new QueueConnectionError(message.message, {
3902
- outputCode: message.code,
3903
- detailCode: message.detailCode,
3904
- origin: message.origin ?? "queue",
3905
- retryable: message.retryable,
3906
- acp: message.acp
3907
- })
3908
- );
4422
+ if (request.type === "set_mode") {
4423
+ writeQueueMessage(socket, {
4424
+ type: "accepted",
4425
+ requestId: request.requestId
4426
+ });
4427
+ void this.controlHandlers.setSessionMode(request.modeId, request.timeoutMs).then(() => {
4428
+ writeQueueMessage(socket, {
4429
+ type: "set_mode_result",
4430
+ requestId: request.requestId,
4431
+ modeId: request.modeId
4432
+ });
4433
+ }).catch((error) => {
4434
+ writeQueueMessage(
4435
+ socket,
4436
+ makeQueueOwnerErrorFromUnknown(
4437
+ request.requestId,
4438
+ error,
4439
+ "QUEUE_CONTROL_REQUEST_FAILED"
4440
+ )
4441
+ );
4442
+ }).finally(() => {
4443
+ if (!socket.destroyed) {
4444
+ socket.end();
4445
+ }
4446
+ });
3909
4447
  return;
3910
4448
  }
3911
- if (!acknowledged) {
3912
- finishReject(
3913
- new QueueConnectionError("Queue owner did not acknowledge request", {
3914
- detailCode: "QUEUE_ACK_MISSING",
3915
- origin: "queue",
3916
- retryable: true
3917
- })
3918
- );
4449
+ if (request.type === "set_config_option") {
4450
+ writeQueueMessage(socket, {
4451
+ type: "accepted",
4452
+ requestId: request.requestId
4453
+ });
4454
+ void this.controlHandlers.setSessionConfigOption(request.configId, request.value, request.timeoutMs).then((response) => {
4455
+ writeQueueMessage(socket, {
4456
+ type: "set_config_option_result",
4457
+ requestId: request.requestId,
4458
+ response
4459
+ });
4460
+ }).catch((error) => {
4461
+ writeQueueMessage(
4462
+ socket,
4463
+ makeQueueOwnerErrorFromUnknown(
4464
+ request.requestId,
4465
+ error,
4466
+ "QUEUE_CONTROL_REQUEST_FAILED"
4467
+ )
4468
+ );
4469
+ }).finally(() => {
4470
+ if (!socket.destroyed) {
4471
+ socket.end();
4472
+ }
4473
+ });
3919
4474
  return;
3920
4475
  }
3921
- if (!isExpectedResponse(message)) {
3922
- finishReject(
3923
- new QueueProtocolError("Queue owner returned unexpected response", {
3924
- detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
3925
- origin: "queue",
3926
- retryable: true
3927
- })
3928
- );
3929
- return;
4476
+ const task = {
4477
+ requestId: request.requestId,
4478
+ message: request.message,
4479
+ permissionMode: request.permissionMode,
4480
+ nonInteractivePermissions: request.nonInteractivePermissions,
4481
+ timeoutMs: request.timeoutMs,
4482
+ suppressSdkConsoleErrors: request.suppressSdkConsoleErrors,
4483
+ waitForCompletion: request.waitForCompletion,
4484
+ send: (message) => {
4485
+ writeQueueMessage(socket, message);
4486
+ },
4487
+ close: () => {
4488
+ if (!socket.destroyed) {
4489
+ socket.end();
4490
+ }
4491
+ }
4492
+ };
4493
+ writeQueueMessage(socket, {
4494
+ type: "accepted",
4495
+ requestId: request.requestId
4496
+ });
4497
+ if (!request.waitForCompletion) {
4498
+ task.close();
3930
4499
  }
3931
- finishResolve(message);
4500
+ this.enqueue(task);
3932
4501
  };
3933
4502
  socket.on("data", (chunk) => {
3934
4503
  buffer += chunk;
@@ -3936,262 +4505,270 @@ async function submitControlToQueueOwner(owner, request, isExpectedResponse) {
3936
4505
  while (index >= 0) {
3937
4506
  const line = buffer.slice(0, index).trim();
3938
4507
  buffer = buffer.slice(index + 1);
3939
- if (line.length > 0) {
3940
- processLine(line);
3941
- }
3942
- index = buffer.indexOf("\n");
3943
- }
3944
- });
3945
- socket.once("error", (error) => {
3946
- finishReject(error);
3947
- });
3948
- socket.once("close", () => {
3949
- if (settled) {
3950
- return;
3951
- }
3952
- if (!acknowledged) {
3953
- finishReject(
3954
- new QueueConnectionError(
3955
- "Queue owner disconnected before acknowledging request",
3956
- {
3957
- detailCode: "QUEUE_DISCONNECTED_BEFORE_ACK",
3958
- origin: "queue",
3959
- retryable: true
3960
- }
3961
- )
3962
- );
3963
- return;
4508
+ if (line.length > 0) {
4509
+ processLine(line);
4510
+ }
4511
+ index = buffer.indexOf("\n");
3964
4512
  }
3965
- finishReject(
3966
- new QueueConnectionError("Queue owner disconnected before responding", {
3967
- detailCode: "QUEUE_DISCONNECTED_BEFORE_COMPLETION",
3968
- origin: "queue",
3969
- retryable: true
3970
- })
3971
- );
3972
4513
  });
3973
- socket.write(`${JSON.stringify(request)}
3974
- `);
3975
- });
3976
- }
3977
- async function submitCancelToQueueOwner(owner) {
3978
- const request = {
3979
- type: "cancel_prompt",
3980
- requestId: randomUUID2()
3981
- };
3982
- const response = await submitControlToQueueOwner(
3983
- owner,
3984
- request,
3985
- (message) => message.type === "cancel_result"
3986
- );
3987
- if (!response) {
3988
- return void 0;
3989
- }
3990
- if (response.requestId !== request.requestId) {
3991
- throw new QueueProtocolError("Queue owner returned mismatched cancel response", {
3992
- detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
3993
- origin: "queue",
3994
- retryable: true
4514
+ socket.on("error", () => {
3995
4515
  });
3996
4516
  }
3997
- return response.cancelled;
3998
- }
3999
- async function submitSetModeToQueueOwner(owner, modeId, timeoutMs) {
4000
- const request = {
4001
- type: "set_mode",
4002
- requestId: randomUUID2(),
4003
- modeId,
4004
- timeoutMs
4005
- };
4006
- const response = await submitControlToQueueOwner(
4007
- owner,
4008
- request,
4009
- (message) => message.type === "set_mode_result"
4010
- );
4011
- if (!response) {
4012
- return void 0;
4517
+ };
4518
+
4519
+ // src/queue-owner-turn-controller.ts
4520
+ var QueueOwnerTurnController = class {
4521
+ options;
4522
+ state = "idle";
4523
+ pendingCancel = false;
4524
+ activeController;
4525
+ constructor(options) {
4526
+ this.options = options;
4013
4527
  }
4014
- if (response.requestId !== request.requestId) {
4015
- throw new QueueProtocolError("Queue owner returned mismatched set_mode response", {
4016
- detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
4017
- origin: "queue",
4018
- retryable: true
4019
- });
4528
+ get lifecycleState() {
4529
+ return this.state;
4020
4530
  }
4021
- return true;
4022
- }
4023
- async function submitSetConfigOptionToQueueOwner(owner, configId, value, timeoutMs) {
4024
- const request = {
4025
- type: "set_config_option",
4026
- requestId: randomUUID2(),
4027
- configId,
4028
- value,
4029
- timeoutMs
4030
- };
4031
- const response = await submitControlToQueueOwner(
4032
- owner,
4033
- request,
4034
- (message) => message.type === "set_config_option_result"
4035
- );
4036
- if (!response) {
4037
- return void 0;
4531
+ get hasPendingCancel() {
4532
+ return this.pendingCancel;
4038
4533
  }
4039
- if (response.requestId !== request.requestId) {
4040
- throw new QueueProtocolError(
4041
- "Queue owner returned mismatched set_config_option response",
4042
- {
4043
- detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
4044
- origin: "queue",
4045
- retryable: true
4046
- }
4047
- );
4534
+ beginTurn() {
4535
+ this.state = "starting";
4536
+ this.pendingCancel = false;
4048
4537
  }
4049
- return response.response;
4050
- }
4051
- async function trySubmitToRunningOwner(options) {
4052
- const owner = await readQueueOwnerRecord(options.sessionId);
4053
- if (!owner) {
4054
- return void 0;
4538
+ markPromptActive() {
4539
+ if (this.state === "starting" || this.state === "active") {
4540
+ this.state = "active";
4541
+ }
4055
4542
  }
4056
- if (!isProcessAlive(owner.pid)) {
4057
- await cleanupStaleQueueOwner(options.sessionId, owner);
4058
- return void 0;
4543
+ endTurn() {
4544
+ this.state = "idle";
4545
+ this.pendingCancel = false;
4059
4546
  }
4060
- const submitted = await submitToQueueOwner(owner, options);
4061
- if (submitted) {
4062
- if (options.verbose) {
4063
- process.stderr.write(
4064
- `[acpx] queued prompt on active owner pid ${owner.pid} for session ${options.sessionId}
4065
- `
4066
- );
4067
- }
4068
- return submitted;
4547
+ beginClosing() {
4548
+ this.state = "closing";
4549
+ this.pendingCancel = false;
4550
+ this.activeController = void 0;
4069
4551
  }
4070
- if (!isProcessAlive(owner.pid)) {
4071
- await cleanupStaleQueueOwner(options.sessionId, owner);
4072
- return void 0;
4552
+ setActiveController(controller) {
4553
+ this.activeController = controller;
4073
4554
  }
4074
- throw new QueueConnectionError(
4075
- "Session queue owner is running but not accepting queue requests",
4076
- {
4077
- detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4078
- origin: "queue",
4079
- retryable: true
4555
+ clearActiveController() {
4556
+ this.activeController = void 0;
4557
+ }
4558
+ assertCanHandleControlRequest() {
4559
+ if (this.state === "closing") {
4560
+ throw new QueueConnectionError("Queue owner is closing", {
4561
+ detailCode: "QUEUE_OWNER_SHUTTING_DOWN",
4562
+ origin: "queue",
4563
+ retryable: true
4564
+ });
4080
4565
  }
4081
- );
4082
- }
4083
- async function tryCancelOnRunningOwner(options) {
4084
- const owner = await readQueueOwnerRecord(options.sessionId);
4085
- if (!owner) {
4086
- return void 0;
4087
4566
  }
4088
- if (!isProcessAlive(owner.pid)) {
4089
- await cleanupStaleQueueOwner(options.sessionId, owner);
4090
- return void 0;
4567
+ async requestCancel() {
4568
+ const activeController = this.activeController;
4569
+ if (activeController?.hasActivePrompt()) {
4570
+ const cancelled2 = await activeController.requestCancelActivePrompt();
4571
+ if (cancelled2) {
4572
+ this.pendingCancel = false;
4573
+ }
4574
+ return cancelled2;
4575
+ }
4576
+ if (this.state === "starting" || this.state === "active") {
4577
+ this.pendingCancel = true;
4578
+ return true;
4579
+ }
4580
+ return false;
4091
4581
  }
4092
- const cancelled2 = await submitCancelToQueueOwner(owner);
4093
- if (cancelled2 !== void 0) {
4094
- if (options.verbose) {
4095
- process.stderr.write(
4096
- `[acpx] requested cancel on active owner pid ${owner.pid} for session ${options.sessionId}
4097
- `
4098
- );
4582
+ async applyPendingCancel() {
4583
+ const activeController = this.activeController;
4584
+ if (!this.pendingCancel || !activeController || !activeController.hasActivePrompt()) {
4585
+ return false;
4586
+ }
4587
+ const cancelled2 = await activeController.requestCancelActivePrompt();
4588
+ if (cancelled2) {
4589
+ this.pendingCancel = false;
4099
4590
  }
4100
4591
  return cancelled2;
4101
4592
  }
4102
- if (!isProcessAlive(owner.pid)) {
4103
- await cleanupStaleQueueOwner(options.sessionId, owner);
4104
- return void 0;
4593
+ async setSessionMode(modeId, timeoutMs) {
4594
+ this.assertCanHandleControlRequest();
4595
+ const activeController = this.activeController;
4596
+ if (activeController) {
4597
+ await this.options.withTimeout(
4598
+ async () => await activeController.setSessionMode(modeId),
4599
+ timeoutMs
4600
+ );
4601
+ return;
4602
+ }
4603
+ await this.options.setSessionModeFallback(modeId, timeoutMs);
4105
4604
  }
4106
- throw new QueueConnectionError(
4107
- "Session queue owner is running but not accepting cancel requests",
4108
- {
4109
- detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4110
- origin: "queue",
4111
- retryable: true
4605
+ async setSessionConfigOption(configId, value, timeoutMs) {
4606
+ this.assertCanHandleControlRequest();
4607
+ const activeController = this.activeController;
4608
+ if (activeController) {
4609
+ return await this.options.withTimeout(
4610
+ async () => await activeController.setSessionConfigOption(configId, value),
4611
+ timeoutMs
4612
+ );
4112
4613
  }
4113
- );
4114
- }
4115
- async function trySetModeOnRunningOwner(sessionId, modeId, timeoutMs, verbose) {
4116
- const owner = await readQueueOwnerRecord(sessionId);
4117
- if (!owner) {
4118
- return void 0;
4614
+ return await this.options.setSessionConfigOptionFallback(
4615
+ configId,
4616
+ value,
4617
+ timeoutMs
4618
+ );
4119
4619
  }
4120
- if (!isProcessAlive(owner.pid)) {
4121
- await cleanupStaleQueueOwner(sessionId, owner);
4122
- return void 0;
4620
+ };
4621
+
4622
+ // src/session-owner-runtime.ts
4623
+ var DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
4624
+ var QUEUE_OWNER_HEARTBEAT_INTERVAL_MS = 2e3;
4625
+ function normalizeQueueOwnerTtlMs(ttlMs) {
4626
+ if (ttlMs == null) {
4627
+ return DEFAULT_QUEUE_OWNER_TTL_MS;
4123
4628
  }
4124
- const submitted = await submitSetModeToQueueOwner(owner, modeId, timeoutMs);
4125
- if (submitted) {
4126
- if (verbose) {
4629
+ if (!Number.isFinite(ttlMs) || ttlMs < 0) {
4630
+ return DEFAULT_QUEUE_OWNER_TTL_MS;
4631
+ }
4632
+ return Math.round(ttlMs);
4633
+ }
4634
+ function createQueueOwnerTurnRuntime(options, deps) {
4635
+ const turnController = new QueueOwnerTurnController({
4636
+ withTimeout: async (run, timeoutMs) => await deps.withTimeout(run, timeoutMs),
4637
+ setSessionModeFallback: deps.setSessionModeFallback,
4638
+ setSessionConfigOptionFallback: deps.setSessionConfigOptionFallback
4639
+ });
4640
+ const applyPendingCancel = async () => {
4641
+ return await turnController.applyPendingCancel();
4642
+ };
4643
+ const scheduleApplyPendingCancel = () => {
4644
+ void applyPendingCancel().catch((error) => {
4645
+ if (options.verbose) {
4646
+ process.stderr.write(
4647
+ `[acpx] failed to apply deferred cancel: ${formatErrorMessage(error)}
4648
+ `
4649
+ );
4650
+ }
4651
+ });
4652
+ };
4653
+ return {
4654
+ beginClosing: () => {
4655
+ turnController.beginClosing();
4656
+ },
4657
+ onClientAvailable: (controller) => {
4658
+ turnController.setActiveController(controller);
4659
+ scheduleApplyPendingCancel();
4660
+ },
4661
+ onClientClosed: () => {
4662
+ turnController.clearActiveController();
4663
+ },
4664
+ onPromptActive: async () => {
4665
+ turnController.markPromptActive();
4666
+ await applyPendingCancel();
4667
+ },
4668
+ runPromptTurn: async (run) => {
4669
+ turnController.beginTurn();
4670
+ try {
4671
+ return await run();
4672
+ } finally {
4673
+ turnController.endTurn();
4674
+ }
4675
+ },
4676
+ controlHandlers: {
4677
+ cancelPrompt: async () => {
4678
+ const accepted = await turnController.requestCancel();
4679
+ if (!accepted) {
4680
+ return false;
4681
+ }
4682
+ await applyPendingCancel();
4683
+ return true;
4684
+ },
4685
+ setSessionMode: async (modeId, timeoutMs) => {
4686
+ await turnController.setSessionMode(modeId, timeoutMs);
4687
+ },
4688
+ setSessionConfigOption: async (configId, value, timeoutMs) => {
4689
+ return await turnController.setSessionConfigOption(configId, value, timeoutMs);
4690
+ }
4691
+ }
4692
+ };
4693
+ }
4694
+ async function runQueueOwnerProcess(options, deps) {
4695
+ const queueOwnerTtlMs = normalizeQueueOwnerTtlMs(options.ttlMs);
4696
+ const lease = await tryAcquireQueueOwnerLease(options.sessionId);
4697
+ if (!lease) {
4698
+ if (options.verbose) {
4127
4699
  process.stderr.write(
4128
- `[acpx] requested session/set_mode on owner pid ${owner.pid} for session ${sessionId}
4700
+ `[acpx] queue owner already active for session ${options.sessionId}; skipping spawn
4129
4701
  `
4130
4702
  );
4131
4703
  }
4132
- return true;
4133
- }
4134
- if (!isProcessAlive(owner.pid)) {
4135
- await cleanupStaleQueueOwner(sessionId, owner);
4136
- return void 0;
4704
+ return;
4137
4705
  }
4138
- throw new QueueConnectionError(
4139
- "Session queue owner is running but not accepting set_mode requests",
4140
- {
4141
- detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4142
- origin: "queue",
4143
- retryable: true
4706
+ const runtime = createQueueOwnerTurnRuntime(options, deps);
4707
+ let owner;
4708
+ let heartbeatTimer;
4709
+ const refreshHeartbeat = async () => {
4710
+ if (!owner) {
4711
+ return;
4144
4712
  }
4145
- );
4146
- }
4147
- async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, timeoutMs, verbose) {
4148
- const owner = await readQueueOwnerRecord(sessionId);
4149
- if (!owner) {
4150
- return void 0;
4151
- }
4152
- if (!isProcessAlive(owner.pid)) {
4153
- await cleanupStaleQueueOwner(sessionId, owner);
4154
- return void 0;
4155
- }
4156
- const response = await submitSetConfigOptionToQueueOwner(
4157
- owner,
4158
- configId,
4159
- value,
4160
- timeoutMs
4161
- );
4162
- if (response) {
4163
- if (verbose) {
4164
- process.stderr.write(
4165
- `[acpx] requested session/set_config_option on owner pid ${owner.pid} for session ${sessionId}
4713
+ await refreshQueueOwnerLease(lease, {
4714
+ queueDepth: owner.queueDepth()
4715
+ }).catch((error) => {
4716
+ if (options.verbose) {
4717
+ process.stderr.write(
4718
+ `[acpx] queue owner heartbeat update failed: ${formatErrorMessage(error)}
4166
4719
  `
4167
- );
4720
+ );
4721
+ }
4722
+ });
4723
+ };
4724
+ try {
4725
+ owner = await SessionQueueOwner.start(lease, runtime.controlHandlers);
4726
+ await refreshHeartbeat();
4727
+ heartbeatTimer = setInterval(() => {
4728
+ void refreshHeartbeat();
4729
+ }, QUEUE_OWNER_HEARTBEAT_INTERVAL_MS);
4730
+ heartbeatTimer.unref();
4731
+ const idleWaitMs = queueOwnerTtlMs === 0 ? void 0 : Math.max(0, queueOwnerTtlMs);
4732
+ while (true) {
4733
+ const task = await owner.nextTask(idleWaitMs);
4734
+ if (!task) {
4735
+ if (queueOwnerTtlMs > 0 && options.verbose) {
4736
+ process.stderr.write(
4737
+ `[acpx] queue owner TTL expired after ${Math.round(queueOwnerTtlMs / 1e3)}s for session ${options.sessionId}; shutting down
4738
+ `
4739
+ );
4740
+ }
4741
+ break;
4742
+ }
4743
+ await runtime.runPromptTurn(async () => {
4744
+ await deps.runQueuedTask(options.sessionId, task, {
4745
+ verbose: options.verbose,
4746
+ nonInteractivePermissions: options.nonInteractivePermissions,
4747
+ authCredentials: options.authCredentials,
4748
+ authPolicy: options.authPolicy,
4749
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
4750
+ onClientAvailable: runtime.onClientAvailable,
4751
+ onClientClosed: runtime.onClientClosed,
4752
+ onPromptActive: runtime.onPromptActive
4753
+ });
4754
+ });
4755
+ await refreshHeartbeat();
4168
4756
  }
4169
- return response;
4170
- }
4171
- if (!isProcessAlive(owner.pid)) {
4172
- await cleanupStaleQueueOwner(sessionId, owner);
4173
- return void 0;
4174
- }
4175
- throw new QueueConnectionError(
4176
- "Session queue owner is running but not accepting set_config_option requests",
4177
- {
4178
- detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4179
- origin: "queue",
4180
- retryable: true
4757
+ } finally {
4758
+ if (heartbeatTimer) {
4759
+ clearInterval(heartbeatTimer);
4181
4760
  }
4182
- );
4183
- }
4184
- async function terminateQueueOwnerForSession(sessionId) {
4185
- const owner = await readQueueOwnerRecord(sessionId);
4186
- if (!owner) {
4187
- return;
4188
- }
4189
- if (isProcessAlive(owner.pid)) {
4190
- await terminateProcess(owner.pid);
4761
+ runtime.beginClosing();
4762
+ if (owner) {
4763
+ await owner.close();
4764
+ }
4765
+ await releaseQueueOwnerLease(lease);
4191
4766
  }
4192
- await cleanupStaleQueueOwner(sessionId, owner);
4193
4767
  }
4194
4768
 
4769
+ // src/session-owner-spawn.ts
4770
+ import { spawn as spawn3 } from "child_process";
4771
+
4195
4772
  // src/session-persistence.ts
4196
4773
  import { statSync } from "fs";
4197
4774
  import fs4 from "fs/promises";
@@ -4363,88 +4940,390 @@ function normalizeName(value) {
4363
4940
  function isoNow2() {
4364
4941
  return (/* @__PURE__ */ new Date()).toISOString();
4365
4942
  }
4366
- async function listSessions() {
4367
- await ensureSessionDir();
4368
- const entries = await fs4.readdir(sessionBaseDir(), { withFileTypes: true });
4369
- const records = [];
4943
+ async function listSessions() {
4944
+ await ensureSessionDir();
4945
+ const entries = await fs4.readdir(sessionBaseDir(), { withFileTypes: true });
4946
+ const records = [];
4947
+ for (const entry of entries) {
4948
+ if (!entry.isFile() || !entry.name.endsWith(".json")) {
4949
+ continue;
4950
+ }
4951
+ const fullPath = path5.join(sessionBaseDir(), entry.name);
4952
+ try {
4953
+ const payload = await fs4.readFile(fullPath, "utf8");
4954
+ const parsed = parseSessionRecord(JSON.parse(payload));
4955
+ if (parsed) {
4956
+ records.push(parsed);
4957
+ }
4958
+ } catch {
4959
+ }
4960
+ }
4961
+ records.sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
4962
+ return records;
4963
+ }
4964
+ async function listSessionsForAgent(agentCommand) {
4965
+ const sessions = await listSessions();
4966
+ return sessions.filter((session) => session.agentCommand === agentCommand);
4967
+ }
4968
+ async function findSession(options) {
4969
+ const normalizedCwd = absolutePath(options.cwd);
4970
+ const normalizedName = normalizeName(options.name);
4971
+ const sessions = await listSessionsForAgent(options.agentCommand);
4972
+ return sessions.find((session) => {
4973
+ if (session.cwd !== normalizedCwd) {
4974
+ return false;
4975
+ }
4976
+ if (!options.includeClosed && session.closed) {
4977
+ return false;
4978
+ }
4979
+ if (normalizedName == null) {
4980
+ return session.name == null;
4981
+ }
4982
+ return session.name === normalizedName;
4983
+ });
4984
+ }
4985
+ async function findSessionByDirectoryWalk(options) {
4986
+ const normalizedName = normalizeName(options.name);
4987
+ const normalizedStart = absolutePath(options.cwd);
4988
+ const normalizedBoundary = absolutePath(options.boundary ?? normalizedStart);
4989
+ const walkBoundary = isWithinBoundary(normalizedBoundary, normalizedStart) ? normalizedBoundary : normalizedStart;
4990
+ const sessions = await listSessionsForAgent(options.agentCommand);
4991
+ const matchesScope = (session, dir2) => {
4992
+ if (session.cwd !== dir2) {
4993
+ return false;
4994
+ }
4995
+ if (session.closed) {
4996
+ return false;
4997
+ }
4998
+ if (normalizedName == null) {
4999
+ return session.name == null;
5000
+ }
5001
+ return session.name === normalizedName;
5002
+ };
5003
+ let dir = normalizedStart;
5004
+ for (; ; ) {
5005
+ const match = sessions.find((session) => matchesScope(session, dir));
5006
+ if (match) {
5007
+ return match;
5008
+ }
5009
+ if (dir === walkBoundary) {
5010
+ return void 0;
5011
+ }
5012
+ const parent = path5.dirname(dir);
5013
+ if (parent === dir) {
5014
+ return void 0;
5015
+ }
5016
+ dir = parent;
5017
+ }
5018
+ }
5019
+
5020
+ // src/session-owner-spawn.ts
5021
+ var QUEUE_OWNER_STARTUP_TIMEOUT_MS = 1e4;
5022
+ var QUEUE_OWNER_RESPAWN_BACKOFF_MS = 250;
5023
+ function isQueueNotAcceptingError(error) {
5024
+ return error instanceof QueueConnectionError && error.detailCode === "QUEUE_NOT_ACCEPTING_REQUESTS";
5025
+ }
5026
+ function spawnDetachedQueueOwner(ownerSpawn) {
5027
+ const child = spawn3(ownerSpawn.command, ownerSpawn.args, {
5028
+ cwd: ownerSpawn.cwd,
5029
+ env: ownerSpawn.env,
5030
+ stdio: "ignore",
5031
+ detached: true
5032
+ });
5033
+ child.unref();
5034
+ }
5035
+ async function buildDefaultQueueOwnerSpawn(options, queueOwnerTtlMs) {
5036
+ const entrypoint = process.argv[1];
5037
+ if (!entrypoint) {
5038
+ throw new Error("Cannot spawn queue owner process: CLI entrypoint is missing");
5039
+ }
5040
+ const record = await resolveSessionRecord(options.sessionId);
5041
+ const args = [
5042
+ entrypoint,
5043
+ "__queue-owner",
5044
+ "--session-id",
5045
+ options.sessionId,
5046
+ "--ttl-ms",
5047
+ String(queueOwnerTtlMs),
5048
+ "--permission-mode",
5049
+ options.permissionMode
5050
+ ];
5051
+ if (options.nonInteractivePermissions) {
5052
+ args.push("--non-interactive-permissions", options.nonInteractivePermissions);
5053
+ }
5054
+ if (options.authPolicy) {
5055
+ args.push("--auth-policy", options.authPolicy);
5056
+ }
5057
+ if (options.timeoutMs != null && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0) {
5058
+ args.push("--timeout-ms", String(Math.round(options.timeoutMs)));
5059
+ }
5060
+ if (options.verbose) {
5061
+ args.push("--verbose");
5062
+ }
5063
+ if (options.suppressSdkConsoleErrors) {
5064
+ args.push("--suppress-sdk-console-errors");
5065
+ }
5066
+ return {
5067
+ command: process.execPath,
5068
+ args,
5069
+ cwd: absolutePath(record.cwd)
5070
+ };
5071
+ }
5072
+ async function sendViaDetachedQueueOwner(options) {
5073
+ const waitForCompletion = options.waitForCompletion !== false;
5074
+ const queueOwnerTtlMs = normalizeQueueOwnerTtlMs(options.ttlMs);
5075
+ const ownerSpawn = options.queueOwnerSpawn ?? await buildDefaultQueueOwnerSpawn(options, queueOwnerTtlMs);
5076
+ const startupDeadline = Date.now() + QUEUE_OWNER_STARTUP_TIMEOUT_MS;
5077
+ let lastSpawnAttemptAt = 0;
5078
+ for (; ; ) {
5079
+ try {
5080
+ const queuedToOwner = await trySubmitToRunningOwner({
5081
+ sessionId: options.sessionId,
5082
+ message: options.message,
5083
+ permissionMode: options.permissionMode,
5084
+ nonInteractivePermissions: options.nonInteractivePermissions,
5085
+ outputFormatter: options.outputFormatter,
5086
+ errorEmissionPolicy: options.errorEmissionPolicy,
5087
+ timeoutMs: options.timeoutMs,
5088
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
5089
+ waitForCompletion,
5090
+ verbose: options.verbose
5091
+ });
5092
+ if (queuedToOwner) {
5093
+ return queuedToOwner;
5094
+ }
5095
+ } catch (error) {
5096
+ if (!isQueueNotAcceptingError(error)) {
5097
+ throw error;
5098
+ }
5099
+ if (Date.now() >= startupDeadline) {
5100
+ throw new QueueConnectionError(
5101
+ "Timed out waiting for detached queue owner to accept prompt requests",
5102
+ {
5103
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
5104
+ origin: "queue",
5105
+ retryable: true,
5106
+ cause: error instanceof Error ? error : void 0
5107
+ }
5108
+ );
5109
+ }
5110
+ await waitMs2(QUEUE_CONNECT_RETRY_MS);
5111
+ continue;
5112
+ }
5113
+ const now = Date.now();
5114
+ if (now >= startupDeadline) {
5115
+ throw new QueueConnectionError(
5116
+ "Timed out waiting for detached queue owner to start",
5117
+ {
5118
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
5119
+ origin: "queue",
5120
+ retryable: true
5121
+ }
5122
+ );
5123
+ }
5124
+ if (now - lastSpawnAttemptAt >= QUEUE_OWNER_RESPAWN_BACKOFF_MS) {
5125
+ spawnDetachedQueueOwner(ownerSpawn);
5126
+ lastSpawnAttemptAt = now;
5127
+ if (options.verbose) {
5128
+ process.stderr.write(
5129
+ `[acpx] starting detached queue owner for session ${options.sessionId}
5130
+ `
5131
+ );
5132
+ }
5133
+ }
5134
+ await waitMs2(QUEUE_CONNECT_RETRY_MS);
5135
+ }
5136
+ }
5137
+
5138
+ // src/session-runtime-history.ts
5139
+ var SESSION_HISTORY_MAX_ENTRIES = 500;
5140
+ var SESSION_HISTORY_PREVIEW_CHARS = 220;
5141
+ function collapseWhitespace2(value) {
5142
+ return value.replace(/\s+/g, " ").trim();
5143
+ }
5144
+ function toPreviewText(value) {
5145
+ const collapsed = collapseWhitespace2(value);
5146
+ if (collapsed.length <= SESSION_HISTORY_PREVIEW_CHARS) {
5147
+ return collapsed;
5148
+ }
5149
+ if (SESSION_HISTORY_PREVIEW_CHARS <= 3) {
5150
+ return collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS);
5151
+ }
5152
+ return `${collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS - 3)}...`;
5153
+ }
5154
+ function textFromContent(content) {
5155
+ if (content.type === "text") {
5156
+ return content.text;
5157
+ }
5158
+ if (content.type === "resource_link") {
5159
+ return content.title ?? content.name ?? content.uri;
5160
+ }
5161
+ if (content.type === "resource") {
5162
+ if ("text" in content.resource && typeof content.resource.text === "string") {
5163
+ return content.resource.text;
5164
+ }
5165
+ return content.resource.uri;
5166
+ }
5167
+ return void 0;
5168
+ }
5169
+ function toHistoryEntryFromUpdate(notification) {
5170
+ const update = notification.update;
5171
+ if (update.sessionUpdate !== "user_message_chunk" && update.sessionUpdate !== "agent_message_chunk") {
5172
+ return void 0;
5173
+ }
5174
+ const text = textFromContent(update.content);
5175
+ if (!text) {
5176
+ return void 0;
5177
+ }
5178
+ const textPreview = toPreviewText(text);
5179
+ if (!textPreview) {
5180
+ return void 0;
5181
+ }
5182
+ return {
5183
+ role: update.sessionUpdate === "user_message_chunk" ? "user" : "assistant",
5184
+ timestamp: isoNow2(),
5185
+ textPreview
5186
+ };
5187
+ }
5188
+ function appendHistoryEntries(current, entries) {
5189
+ const base = current ? [...current] : [];
4370
5190
  for (const entry of entries) {
4371
- if (!entry.isFile() || !entry.name.endsWith(".json")) {
5191
+ if (!entry.textPreview.trim()) {
4372
5192
  continue;
4373
5193
  }
4374
- const fullPath = path5.join(sessionBaseDir(), entry.name);
4375
- try {
4376
- const payload = await fs4.readFile(fullPath, "utf8");
4377
- const parsed = parseSessionRecord(JSON.parse(payload));
4378
- if (parsed) {
4379
- records.push(parsed);
4380
- }
4381
- } catch {
4382
- }
5194
+ base.push(entry);
4383
5195
  }
4384
- records.sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
4385
- return records;
5196
+ if (base.length <= SESSION_HISTORY_MAX_ENTRIES) {
5197
+ return base;
5198
+ }
5199
+ return base.slice(base.length - SESSION_HISTORY_MAX_ENTRIES);
4386
5200
  }
4387
- async function listSessionsForAgent(agentCommand) {
4388
- const sessions = await listSessions();
4389
- return sessions.filter((session) => session.agentCommand === agentCommand);
5201
+
5202
+ // src/session-runtime-lifecycle.ts
5203
+ function applyLifecycleSnapshotToRecord(record, snapshot) {
5204
+ record.pid = snapshot.pid;
5205
+ record.agentStartedAt = snapshot.startedAt;
5206
+ if (snapshot.lastExit) {
5207
+ record.lastAgentExitCode = snapshot.lastExit.exitCode;
5208
+ record.lastAgentExitSignal = snapshot.lastExit.signal;
5209
+ record.lastAgentExitAt = snapshot.lastExit.exitedAt;
5210
+ record.lastAgentDisconnectReason = snapshot.lastExit.reason;
5211
+ return;
5212
+ }
5213
+ record.lastAgentExitCode = void 0;
5214
+ record.lastAgentExitSignal = void 0;
5215
+ record.lastAgentExitAt = void 0;
5216
+ record.lastAgentDisconnectReason = void 0;
4390
5217
  }
4391
- async function findSession(options) {
4392
- const normalizedCwd = absolutePath(options.cwd);
4393
- const normalizedName = normalizeName(options.name);
4394
- const sessions = await listSessionsForAgent(options.agentCommand);
4395
- return sessions.find((session) => {
4396
- if (session.cwd !== normalizedCwd) {
4397
- return false;
4398
- }
4399
- if (!options.includeClosed && session.closed) {
4400
- return false;
4401
- }
4402
- if (normalizedName == null) {
4403
- return session.name == null;
4404
- }
4405
- return session.name === normalizedName;
4406
- });
5218
+ function reconcileAgentSessionId(record, agentSessionId) {
5219
+ const normalized = normalizeAgentSessionId(agentSessionId);
5220
+ if (!normalized) {
5221
+ return;
5222
+ }
5223
+ record.agentSessionId = normalized;
4407
5224
  }
4408
- async function findSessionByDirectoryWalk(options) {
4409
- const normalizedName = normalizeName(options.name);
4410
- const normalizedStart = absolutePath(options.cwd);
4411
- const normalizedBoundary = absolutePath(options.boundary ?? normalizedStart);
4412
- const walkBoundary = isWithinBoundary(normalizedBoundary, normalizedStart) ? normalizedBoundary : normalizedStart;
4413
- const sessions = await listSessionsForAgent(options.agentCommand);
4414
- const matchesScope = (session, dir2) => {
4415
- if (session.cwd !== dir2) {
4416
- return false;
4417
- }
4418
- if (session.closed) {
4419
- return false;
4420
- }
4421
- if (normalizedName == null) {
4422
- return session.name == null;
5225
+
5226
+ // src/session-runtime-reconnect.ts
5227
+ function loadSessionCandidates(record) {
5228
+ const candidates = [normalizeAgentSessionId(record.agentSessionId), record.sessionId];
5229
+ const unique = [];
5230
+ for (const candidate of candidates) {
5231
+ if (!candidate || unique.includes(candidate)) {
5232
+ continue;
4423
5233
  }
4424
- return session.name === normalizedName;
4425
- };
4426
- let dir = normalizedStart;
4427
- for (; ; ) {
4428
- const match = sessions.find((session) => matchesScope(session, dir));
4429
- if (match) {
4430
- return match;
5234
+ unique.push(candidate);
5235
+ }
5236
+ return unique;
5237
+ }
5238
+ async function connectAndLoadSession(options) {
5239
+ const record = options.record;
5240
+ const client = options.client;
5241
+ const storedProcessAlive = isProcessAlive(record.pid);
5242
+ const shouldReconnect = Boolean(record.pid) && !storedProcessAlive;
5243
+ if (options.verbose) {
5244
+ if (storedProcessAlive) {
5245
+ process.stderr.write(
5246
+ `[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession
5247
+ `
5248
+ );
5249
+ } else if (shouldReconnect) {
5250
+ process.stderr.write(
5251
+ `[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session/load
5252
+ `
5253
+ );
4431
5254
  }
4432
- if (dir === walkBoundary) {
4433
- return void 0;
5255
+ }
5256
+ await options.withTimeout(client.start(), options.timeoutMs);
5257
+ options.onClientAvailable?.(options.activeController);
5258
+ applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
5259
+ record.closed = false;
5260
+ record.closedAt = void 0;
5261
+ options.onConnectedRecord?.(record);
5262
+ await writeSessionRecord(record);
5263
+ let resumed = false;
5264
+ let loadError;
5265
+ let sessionId = record.sessionId;
5266
+ if (client.supportsLoadSession()) {
5267
+ const candidates = loadSessionCandidates(record);
5268
+ for (const candidate of candidates) {
5269
+ if (options.verbose && candidates.length > 1) {
5270
+ process.stderr.write(`[acpx] attempting session/load with ${candidate}
5271
+ `);
5272
+ }
5273
+ try {
5274
+ const loadResult = await options.withTimeout(
5275
+ client.loadSessionWithOptions(candidate, record.cwd, {
5276
+ suppressReplayUpdates: true
5277
+ }),
5278
+ options.timeoutMs
5279
+ );
5280
+ reconcileAgentSessionId(record, loadResult.agentSessionId);
5281
+ resumed = true;
5282
+ sessionId = candidate;
5283
+ loadError = void 0;
5284
+ break;
5285
+ } catch (error) {
5286
+ loadError = formatErrorMessage(error);
5287
+ if (!options.shouldFallbackToNewSession(error)) {
5288
+ throw error;
5289
+ }
5290
+ if (options.verbose) {
5291
+ process.stderr.write(
5292
+ `[acpx] session/load failed for ${candidate}: ${loadError}
5293
+ `
5294
+ );
5295
+ }
5296
+ }
4434
5297
  }
4435
- const parent = path5.dirname(dir);
4436
- if (parent === dir) {
4437
- return void 0;
5298
+ if (!resumed) {
5299
+ const createdSession = await options.withTimeout(
5300
+ client.createSession(record.cwd),
5301
+ options.timeoutMs
5302
+ );
5303
+ sessionId = createdSession.sessionId;
5304
+ record.sessionId = sessionId;
5305
+ reconcileAgentSessionId(record, createdSession.agentSessionId);
4438
5306
  }
4439
- dir = parent;
5307
+ } else {
5308
+ const createdSession = await options.withTimeout(
5309
+ client.createSession(record.cwd),
5310
+ options.timeoutMs
5311
+ );
5312
+ sessionId = createdSession.sessionId;
5313
+ record.sessionId = sessionId;
5314
+ reconcileAgentSessionId(record, createdSession.agentSessionId);
4440
5315
  }
5316
+ options.onSessionIdResolved?.(sessionId);
5317
+ return {
5318
+ sessionId,
5319
+ agentSessionId: record.agentSessionId,
5320
+ resumed,
5321
+ loadError
5322
+ };
4441
5323
  }
4442
5324
 
4443
5325
  // src/session-runtime.ts
4444
- var DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
4445
5326
  var INTERRUPT_CANCEL_WAIT_MS = 2500;
4446
- var SESSION_HISTORY_MAX_ENTRIES = 500;
4447
- var SESSION_HISTORY_PREVIEW_CHARS = 220;
4448
5327
  var TimeoutError = class extends Error {
4449
5328
  constructor(timeoutMs) {
4450
5329
  super(`Timed out after ${timeoutMs}ms`);
@@ -4539,203 +5418,44 @@ var QueueTaskOutputFormatter = class {
4539
5418
  this.send({
4540
5419
  type: "done",
4541
5420
  requestId: this.requestId,
4542
- stopReason
4543
- });
4544
- }
4545
- onError(params) {
4546
- this.send({
4547
- type: "error",
4548
- requestId: this.requestId,
4549
- code: params.code,
4550
- detailCode: params.detailCode,
4551
- origin: params.origin,
4552
- message: params.message,
4553
- retryable: params.retryable,
4554
- acp: params.acp
4555
- });
4556
- }
4557
- flush() {
4558
- }
4559
- };
4560
- var DISCARD_OUTPUT_FORMATTER = {
4561
- setContext() {
4562
- },
4563
- onSessionUpdate() {
4564
- },
4565
- onClientOperation() {
4566
- },
4567
- onDone() {
4568
- },
4569
- onError() {
4570
- },
4571
- flush() {
4572
- }
4573
- };
4574
- function normalizeQueueOwnerTtlMs(ttlMs) {
4575
- if (ttlMs == null) {
4576
- return DEFAULT_QUEUE_OWNER_TTL_MS;
4577
- }
4578
- if (!Number.isFinite(ttlMs) || ttlMs < 0) {
4579
- return DEFAULT_QUEUE_OWNER_TTL_MS;
4580
- }
4581
- return Math.round(ttlMs);
4582
- }
4583
- function collapseWhitespace2(value) {
4584
- return value.replace(/\s+/g, " ").trim();
4585
- }
4586
- function toPreviewText(value) {
4587
- const collapsed = collapseWhitespace2(value);
4588
- if (collapsed.length <= SESSION_HISTORY_PREVIEW_CHARS) {
4589
- return collapsed;
4590
- }
4591
- if (SESSION_HISTORY_PREVIEW_CHARS <= 3) {
4592
- return collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS);
4593
- }
4594
- return `${collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS - 3)}...`;
4595
- }
4596
- function textFromContent(content) {
4597
- if (content.type === "text") {
4598
- return content.text;
4599
- }
4600
- if (content.type === "resource_link") {
4601
- return content.title ?? content.name ?? content.uri;
4602
- }
4603
- if (content.type === "resource") {
4604
- if ("text" in content.resource && typeof content.resource.text === "string") {
4605
- return content.resource.text;
4606
- }
4607
- return content.resource.uri;
4608
- }
4609
- return void 0;
4610
- }
4611
- function toHistoryEntryFromUpdate(notification) {
4612
- const update = notification.update;
4613
- if (update.sessionUpdate !== "user_message_chunk" && update.sessionUpdate !== "agent_message_chunk") {
4614
- return void 0;
4615
- }
4616
- const text = textFromContent(update.content);
4617
- if (!text) {
4618
- return void 0;
4619
- }
4620
- const textPreview = toPreviewText(text);
4621
- if (!textPreview) {
4622
- return void 0;
4623
- }
4624
- return {
4625
- role: update.sessionUpdate === "user_message_chunk" ? "user" : "assistant",
4626
- timestamp: isoNow2(),
4627
- textPreview
4628
- };
4629
- }
4630
- function appendHistoryEntries(current, entries) {
4631
- const base = current ? [...current] : [];
4632
- for (const entry of entries) {
4633
- if (!entry.textPreview.trim()) {
4634
- continue;
4635
- }
4636
- base.push(entry);
5421
+ stopReason
5422
+ });
4637
5423
  }
4638
- if (base.length <= SESSION_HISTORY_MAX_ENTRIES) {
4639
- return base;
5424
+ onError(params) {
5425
+ this.send({
5426
+ type: "error",
5427
+ requestId: this.requestId,
5428
+ code: params.code,
5429
+ detailCode: params.detailCode,
5430
+ origin: params.origin,
5431
+ message: params.message,
5432
+ retryable: params.retryable,
5433
+ acp: params.acp
5434
+ });
4640
5435
  }
4641
- return base.slice(base.length - SESSION_HISTORY_MAX_ENTRIES);
4642
- }
4643
- function applyLifecycleSnapshotToRecord(record, snapshot) {
4644
- record.pid = snapshot.pid;
4645
- record.agentStartedAt = snapshot.startedAt;
4646
- if (snapshot.lastExit) {
4647
- record.lastAgentExitCode = snapshot.lastExit.exitCode;
4648
- record.lastAgentExitSignal = snapshot.lastExit.signal;
4649
- record.lastAgentExitAt = snapshot.lastExit.exitedAt;
4650
- record.lastAgentDisconnectReason = snapshot.lastExit.reason;
4651
- return;
5436
+ flush() {
4652
5437
  }
4653
- record.lastAgentExitCode = void 0;
4654
- record.lastAgentExitSignal = void 0;
4655
- record.lastAgentExitAt = void 0;
4656
- record.lastAgentDisconnectReason = void 0;
4657
- }
4658
- function reconcileAgentSessionId(record, agentSessionId) {
4659
- const normalized = normalizeAgentSessionId(agentSessionId);
4660
- if (!normalized) {
4661
- return;
5438
+ };
5439
+ var DISCARD_OUTPUT_FORMATTER = {
5440
+ setContext() {
5441
+ },
5442
+ onSessionUpdate() {
5443
+ },
5444
+ onClientOperation() {
5445
+ },
5446
+ onDone() {
5447
+ },
5448
+ onError() {
5449
+ },
5450
+ flush() {
4662
5451
  }
4663
- record.agentSessionId = normalized;
4664
- }
5452
+ };
4665
5453
  function shouldFallbackToNewSession(error) {
4666
5454
  if (error instanceof TimeoutError || error instanceof InterruptedError) {
4667
5455
  return false;
4668
5456
  }
4669
5457
  return isAcpResourceNotFoundError(error);
4670
5458
  }
4671
- async function connectAndLoadSession(options) {
4672
- const record = options.record;
4673
- const client = options.client;
4674
- const storedProcessAlive = isProcessAlive(record.pid);
4675
- const shouldReconnect = Boolean(record.pid) && !storedProcessAlive;
4676
- if (options.verbose) {
4677
- if (storedProcessAlive) {
4678
- process.stderr.write(
4679
- `[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession
4680
- `
4681
- );
4682
- } else if (shouldReconnect) {
4683
- process.stderr.write(
4684
- `[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session/load
4685
- `
4686
- );
4687
- }
4688
- }
4689
- await withTimeout(client.start(), options.timeoutMs);
4690
- options.onClientAvailable?.(options.activeController);
4691
- applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
4692
- record.closed = false;
4693
- record.closedAt = void 0;
4694
- options.onConnectedRecord?.(record);
4695
- await writeSessionRecord(record);
4696
- let resumed = false;
4697
- let loadError;
4698
- let sessionId = record.sessionId;
4699
- if (client.supportsLoadSession()) {
4700
- try {
4701
- const loadResult = await withTimeout(
4702
- client.loadSessionWithOptions(record.sessionId, record.cwd, {
4703
- suppressReplayUpdates: true
4704
- }),
4705
- options.timeoutMs
4706
- );
4707
- reconcileAgentSessionId(record, loadResult.agentSessionId);
4708
- resumed = true;
4709
- } catch (error) {
4710
- loadError = formatErrorMessage(error);
4711
- if (!shouldFallbackToNewSession(error)) {
4712
- throw error;
4713
- }
4714
- const createdSession = await withTimeout(
4715
- client.createSession(record.cwd),
4716
- options.timeoutMs
4717
- );
4718
- sessionId = createdSession.sessionId;
4719
- record.sessionId = sessionId;
4720
- reconcileAgentSessionId(record, createdSession.agentSessionId);
4721
- }
4722
- } else {
4723
- const createdSession = await withTimeout(
4724
- client.createSession(record.cwd),
4725
- options.timeoutMs
4726
- );
4727
- sessionId = createdSession.sessionId;
4728
- record.sessionId = sessionId;
4729
- reconcileAgentSessionId(record, createdSession.agentSessionId);
4730
- }
4731
- options.onSessionIdResolved?.(sessionId);
4732
- return {
4733
- sessionId,
4734
- agentSessionId: record.agentSessionId,
4735
- resumed,
4736
- loadError
4737
- };
4738
- }
4739
5459
  async function runQueuedTask(sessionRecordId, task, options) {
4740
5460
  const outputFormatter = task.waitForCompletion ? new QueueTaskOutputFormatter(task) : DISCARD_OUTPUT_FORMATTER;
4741
5461
  try {
@@ -4842,6 +5562,8 @@ async function runSessionPrompt(options) {
4842
5562
  timeoutMs: options.timeoutMs,
4843
5563
  verbose: options.verbose,
4844
5564
  activeController,
5565
+ withTimeout,
5566
+ shouldFallbackToNewSession,
4845
5567
  onClientAvailable: (controller) => {
4846
5568
  options.onClientAvailable?.(controller);
4847
5569
  notifiedClientAvailable = true;
@@ -4976,6 +5698,8 @@ async function withConnectedSession(options) {
4976
5698
  timeoutMs: options.timeoutMs,
4977
5699
  verbose: options.verbose,
4978
5700
  activeController,
5701
+ withTimeout,
5702
+ shouldFallbackToNewSession,
4979
5703
  onClientAvailable: (controller) => {
4980
5704
  options.onClientAvailable?.(controller);
4981
5705
  notifiedClientAvailable = true;
@@ -5191,179 +5915,41 @@ async function ensureSession(options) {
5191
5915
  created: true
5192
5916
  };
5193
5917
  }
5194
- async function sendSession(options) {
5195
- const waitForCompletion = options.waitForCompletion !== false;
5196
- const queueOwnerTtlMs = normalizeQueueOwnerTtlMs(options.ttlMs);
5197
- const queuedToOwner = await trySubmitToRunningOwner({
5198
- sessionId: options.sessionId,
5199
- message: options.message,
5200
- permissionMode: options.permissionMode,
5201
- nonInteractivePermissions: options.nonInteractivePermissions,
5202
- outputFormatter: options.outputFormatter,
5203
- errorEmissionPolicy: options.errorEmissionPolicy,
5204
- timeoutMs: options.timeoutMs,
5205
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
5206
- waitForCompletion,
5207
- verbose: options.verbose
5208
- });
5209
- if (queuedToOwner) {
5210
- return queuedToOwner;
5211
- }
5212
- for (; ; ) {
5213
- const lease = await tryAcquireQueueOwnerLease(options.sessionId);
5214
- if (!lease) {
5215
- const retryQueued = await trySubmitToRunningOwner({
5216
- sessionId: options.sessionId,
5217
- message: options.message,
5218
- permissionMode: options.permissionMode,
5918
+ async function runQueueOwnerProcess2(options) {
5919
+ await runQueueOwnerProcess(options, {
5920
+ runQueuedTask,
5921
+ withTimeout: async (run, timeoutMs) => await withTimeout(run(), timeoutMs),
5922
+ setSessionModeFallback: async (modeId, timeoutMs) => {
5923
+ await runSessionSetModeDirect({
5924
+ sessionRecordId: options.sessionId,
5925
+ modeId,
5219
5926
  nonInteractivePermissions: options.nonInteractivePermissions,
5220
- outputFormatter: options.outputFormatter,
5221
- errorEmissionPolicy: options.errorEmissionPolicy,
5222
- timeoutMs: options.timeoutMs,
5223
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
5224
- waitForCompletion,
5927
+ authCredentials: options.authCredentials,
5928
+ authPolicy: options.authPolicy,
5929
+ timeoutMs,
5225
5930
  verbose: options.verbose
5226
5931
  });
5227
- if (retryQueued) {
5228
- return retryQueued;
5229
- }
5230
- await waitMs2(QUEUE_CONNECT_RETRY_MS);
5231
- continue;
5232
- }
5233
- let owner;
5234
- const turnController = new QueueOwnerTurnController({
5235
- withTimeout: async (run, timeoutMs) => await withTimeout(run(), timeoutMs),
5236
- setSessionModeFallback: async (modeId, timeoutMs) => {
5237
- await runSessionSetModeDirect({
5238
- sessionRecordId: options.sessionId,
5239
- modeId,
5240
- nonInteractivePermissions: options.nonInteractivePermissions,
5241
- authCredentials: options.authCredentials,
5242
- authPolicy: options.authPolicy,
5243
- timeoutMs,
5244
- verbose: options.verbose
5245
- });
5246
- },
5247
- setSessionConfigOptionFallback: async (configId, value, timeoutMs) => {
5248
- const result = await runSessionSetConfigOptionDirect({
5249
- sessionRecordId: options.sessionId,
5250
- configId,
5251
- value,
5252
- nonInteractivePermissions: options.nonInteractivePermissions,
5253
- authCredentials: options.authCredentials,
5254
- authPolicy: options.authPolicy,
5255
- timeoutMs,
5256
- verbose: options.verbose
5257
- });
5258
- return result.response;
5259
- }
5260
- });
5261
- const applyPendingCancel = async () => {
5262
- return await turnController.applyPendingCancel();
5263
- };
5264
- const scheduleApplyPendingCancel = () => {
5265
- void applyPendingCancel().catch((error) => {
5266
- if (options.verbose) {
5267
- process.stderr.write(
5268
- `[acpx] failed to apply deferred cancel: ${formatErrorMessage(error)}
5269
- `
5270
- );
5271
- }
5272
- });
5273
- };
5274
- const setActiveController = (controller) => {
5275
- turnController.setActiveController(controller);
5276
- scheduleApplyPendingCancel();
5277
- };
5278
- const clearActiveController = () => {
5279
- turnController.clearActiveController();
5280
- };
5281
- const runPromptTurn = async (run) => {
5282
- turnController.beginTurn();
5283
- try {
5284
- return await run();
5285
- } finally {
5286
- turnController.endTurn();
5287
- }
5288
- };
5289
- try {
5290
- owner = await SessionQueueOwner.start(lease, {
5291
- cancelPrompt: async () => {
5292
- const accepted = await turnController.requestCancel();
5293
- if (!accepted) {
5294
- return false;
5295
- }
5296
- await applyPendingCancel();
5297
- return true;
5298
- },
5299
- setSessionMode: async (modeId, timeoutMs) => {
5300
- await turnController.setSessionMode(modeId, timeoutMs);
5301
- },
5302
- setSessionConfigOption: async (configId, value, timeoutMs) => {
5303
- return await turnController.setSessionConfigOption(
5304
- configId,
5305
- value,
5306
- timeoutMs
5307
- );
5308
- }
5309
- });
5310
- const localResult = await runPromptTurn(async () => {
5311
- return await runSessionPrompt({
5312
- sessionRecordId: options.sessionId,
5313
- message: options.message,
5314
- permissionMode: options.permissionMode,
5315
- nonInteractivePermissions: options.nonInteractivePermissions,
5316
- authCredentials: options.authCredentials,
5317
- authPolicy: options.authPolicy,
5318
- outputFormatter: options.outputFormatter,
5319
- timeoutMs: options.timeoutMs,
5320
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
5321
- verbose: options.verbose,
5322
- onClientAvailable: setActiveController,
5323
- onClientClosed: clearActiveController,
5324
- onPromptActive: async () => {
5325
- turnController.markPromptActive();
5326
- await applyPendingCancel();
5327
- }
5328
- });
5932
+ },
5933
+ setSessionConfigOptionFallback: async (configId, value, timeoutMs) => {
5934
+ const result = await runSessionSetConfigOptionDirect({
5935
+ sessionRecordId: options.sessionId,
5936
+ configId,
5937
+ value,
5938
+ nonInteractivePermissions: options.nonInteractivePermissions,
5939
+ authCredentials: options.authCredentials,
5940
+ authPolicy: options.authPolicy,
5941
+ timeoutMs,
5942
+ verbose: options.verbose
5329
5943
  });
5330
- const idleWaitMs = queueOwnerTtlMs === 0 ? void 0 : Math.max(0, queueOwnerTtlMs);
5331
- while (true) {
5332
- const task = await owner.nextTask(idleWaitMs);
5333
- if (!task) {
5334
- if (queueOwnerTtlMs > 0 && options.verbose) {
5335
- process.stderr.write(
5336
- `[acpx] queue owner TTL expired after ${Math.round(queueOwnerTtlMs / 1e3)}s for session ${options.sessionId}; shutting down
5337
- `
5338
- );
5339
- }
5340
- break;
5341
- }
5342
- await runPromptTurn(async () => {
5343
- await runQueuedTask(options.sessionId, task, {
5344
- verbose: options.verbose,
5345
- nonInteractivePermissions: options.nonInteractivePermissions,
5346
- authCredentials: options.authCredentials,
5347
- authPolicy: options.authPolicy,
5348
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
5349
- onClientAvailable: setActiveController,
5350
- onClientClosed: clearActiveController,
5351
- onPromptActive: async () => {
5352
- turnController.markPromptActive();
5353
- await applyPendingCancel();
5354
- }
5355
- });
5356
- });
5357
- }
5358
- return localResult;
5359
- } finally {
5360
- turnController.beginClosing();
5361
- if (owner) {
5362
- await owner.close();
5363
- }
5364
- await releaseQueueOwnerLease(lease);
5944
+ return result.response;
5365
5945
  }
5366
- }
5946
+ });
5947
+ }
5948
+ async function sendSession(options) {
5949
+ return await sendViaDetachedQueueOwner(options);
5950
+ }
5951
+ async function readSessionQueueOwnerStatus(sessionId) {
5952
+ return await readQueueOwnerStatus(sessionId);
5367
5953
  }
5368
5954
  async function cancelSessionPrompt(options) {
5369
5955
  const cancelled2 = await tryCancelOnRunningOwner(options);
@@ -5469,6 +6055,7 @@ var NoSessionError = class extends Error {
5469
6055
  }
5470
6056
  };
5471
6057
  var TOP_LEVEL_VERBS = /* @__PURE__ */ new Set([
6058
+ "__queue-owner",
5472
6059
  "prompt",
5473
6060
  "exec",
5474
6061
  "cancel",
@@ -5481,25 +6068,25 @@ var TOP_LEVEL_VERBS = /* @__PURE__ */ new Set([
5481
6068
  ]);
5482
6069
  function parseOutputFormat2(value) {
5483
6070
  if (!OUTPUT_FORMATS.includes(value)) {
5484
- throw new InvalidArgumentError(
6071
+ throw new InvalidArgumentError3(
5485
6072
  `Invalid format "${value}". Expected one of: ${OUTPUT_FORMATS.join(", ")}`
5486
6073
  );
5487
6074
  }
5488
6075
  return value;
5489
6076
  }
5490
- function parseAuthPolicy2(value) {
6077
+ function parseAuthPolicy3(value) {
5491
6078
  if (!AUTH_POLICIES.includes(value)) {
5492
- throw new InvalidArgumentError(
6079
+ throw new InvalidArgumentError3(
5493
6080
  `Invalid auth policy "${value}". Expected one of: ${AUTH_POLICIES.join(", ")}`
5494
6081
  );
5495
6082
  }
5496
6083
  return value;
5497
6084
  }
5498
- function parseNonInteractivePermissionPolicy2(value) {
6085
+ function parseNonInteractivePermissionPolicy3(value) {
5499
6086
  if (!NON_INTERACTIVE_PERMISSION_POLICIES.includes(
5500
6087
  value
5501
6088
  )) {
5502
- throw new InvalidArgumentError(
6089
+ throw new InvalidArgumentError3(
5503
6090
  `Invalid non-interactive permission policy "${value}". Expected one of: ${NON_INTERACTIVE_PERMISSION_POLICIES.join(", ")}`
5504
6091
  );
5505
6092
  }
@@ -5508,35 +6095,35 @@ function parseNonInteractivePermissionPolicy2(value) {
5508
6095
  function parseTimeoutSeconds(value) {
5509
6096
  const parsed = Number(value);
5510
6097
  if (!Number.isFinite(parsed) || parsed <= 0) {
5511
- throw new InvalidArgumentError("Timeout must be a positive number of seconds");
6098
+ throw new InvalidArgumentError3("Timeout must be a positive number of seconds");
5512
6099
  }
5513
6100
  return Math.round(parsed * 1e3);
5514
6101
  }
5515
6102
  function parseTtlSeconds(value) {
5516
6103
  const parsed = Number(value);
5517
6104
  if (!Number.isFinite(parsed) || parsed < 0) {
5518
- throw new InvalidArgumentError("TTL must be a non-negative number of seconds");
6105
+ throw new InvalidArgumentError3("TTL must be a non-negative number of seconds");
5519
6106
  }
5520
6107
  return Math.round(parsed * 1e3);
5521
6108
  }
5522
6109
  function parseSessionName(value) {
5523
6110
  const trimmed = value.trim();
5524
6111
  if (trimmed.length === 0) {
5525
- throw new InvalidArgumentError("Session name must not be empty");
6112
+ throw new InvalidArgumentError3("Session name must not be empty");
5526
6113
  }
5527
6114
  return trimmed;
5528
6115
  }
5529
- function parseNonEmptyValue(label, value) {
6116
+ function parseNonEmptyValue2(label, value) {
5530
6117
  const trimmed = value.trim();
5531
6118
  if (trimmed.length === 0) {
5532
- throw new InvalidArgumentError(`${label} must not be empty`);
6119
+ throw new InvalidArgumentError3(`${label} must not be empty`);
5533
6120
  }
5534
6121
  return trimmed;
5535
6122
  }
5536
6123
  function parseHistoryLimit(value) {
5537
6124
  const parsed = Number(value);
5538
6125
  if (!Number.isInteger(parsed) || parsed <= 0) {
5539
- throw new InvalidArgumentError("Limit must be a positive integer");
6126
+ throw new InvalidArgumentError3("Limit must be a positive integer");
5540
6127
  }
5541
6128
  return parsed;
5542
6129
  }
@@ -5545,7 +6132,7 @@ function resolvePermissionMode(flags, defaultMode) {
5545
6132
  Boolean
5546
6133
  ).length;
5547
6134
  if (selected2 > 1) {
5548
- throw new InvalidArgumentError(
6135
+ throw new InvalidArgumentError3(
5549
6136
  "Use only one permission mode: --approve-all, --approve-reads, or --deny-all"
5550
6137
  );
5551
6138
  }
@@ -5572,7 +6159,7 @@ async function readPrompt(promptParts, filePath, cwd) {
5572
6159
  );
5573
6160
  const prompt2 = pieces.join("\n\n").trim();
5574
6161
  if (!prompt2) {
5575
- throw new InvalidArgumentError("Prompt from --file is empty");
6162
+ throw new InvalidArgumentError3("Prompt from --file is empty");
5576
6163
  }
5577
6164
  return prompt2;
5578
6165
  }
@@ -5581,13 +6168,13 @@ async function readPrompt(promptParts, filePath, cwd) {
5581
6168
  return joined;
5582
6169
  }
5583
6170
  if (process.stdin.isTTY) {
5584
- throw new InvalidArgumentError(
6171
+ throw new InvalidArgumentError3(
5585
6172
  "Prompt is required (pass as argument, --file, or pipe via stdin)"
5586
6173
  );
5587
6174
  }
5588
6175
  const prompt = (await readPromptInputFromStdin()).trim();
5589
6176
  if (!prompt) {
5590
- throw new InvalidArgumentError("Prompt from stdin is empty");
6177
+ throw new InvalidArgumentError3("Prompt from stdin is empty");
5591
6178
  }
5592
6179
  return prompt;
5593
6180
  }
@@ -5602,14 +6189,14 @@ function addGlobalFlags(command) {
5602
6189
  return command.option("--agent <command>", "Raw ACP agent command (escape hatch)").option("--cwd <dir>", "Working directory", process.cwd()).option(
5603
6190
  "--auth-policy <policy>",
5604
6191
  "Authentication policy: skip or fail when auth is required",
5605
- parseAuthPolicy2
6192
+ parseAuthPolicy3
5606
6193
  ).option("--approve-all", "Auto-approve all permission requests").option(
5607
6194
  "--approve-reads",
5608
6195
  "Auto-approve read/search requests and prompt for writes"
5609
6196
  ).option("--deny-all", "Deny all permission requests").option(
5610
6197
  "--non-interactive-permissions <policy>",
5611
6198
  "When prompting is unavailable: deny or fail",
5612
- parseNonInteractivePermissionPolicy2
6199
+ parseNonInteractivePermissionPolicy3
5613
6200
  ).option("--format <fmt>", "Output format: text, json, quiet", parseOutputFormat2).option(
5614
6201
  "--json-strict",
5615
6202
  "Strict JSON mode: requires --format json and suppresses non-JSON stderr output"
@@ -5666,10 +6253,10 @@ function resolveGlobalFlags(command, config) {
5666
6253
  const jsonStrict = opts.jsonStrict === true;
5667
6254
  const verbose = opts.verbose === true;
5668
6255
  if (jsonStrict && format !== "json") {
5669
- throw new InvalidArgumentError("--json-strict requires --format json");
6256
+ throw new InvalidArgumentError3("--json-strict requires --format json");
5670
6257
  }
5671
6258
  if (jsonStrict && verbose) {
5672
- throw new InvalidArgumentError("--json-strict cannot be combined with --verbose");
6259
+ throw new InvalidArgumentError3("--json-strict cannot be combined with --verbose");
5673
6260
  }
5674
6261
  return {
5675
6262
  agent: opts.agent,
@@ -5698,7 +6285,7 @@ function resolveOutputPolicy(format, jsonStrict) {
5698
6285
  function resolveAgentInvocation(explicitAgentName, globalFlags, config) {
5699
6286
  const override = globalFlags.agent?.trim();
5700
6287
  if (override && explicitAgentName) {
5701
- throw new InvalidArgumentError(
6288
+ throw new InvalidArgumentError3(
5702
6289
  "Do not combine positional agent with --agent override"
5703
6290
  );
5704
6291
  }
@@ -6376,7 +6963,12 @@ async function handleStatus(explicitAgentName, flags, command, config) {
6376
6963
  uptime: null,
6377
6964
  lastPromptTime: null,
6378
6965
  exitCode: null,
6379
- signal: null
6966
+ signal: null,
6967
+ ownerPid: null,
6968
+ ownerStatus: null,
6969
+ ownerGeneration: null,
6970
+ ownerHeartbeatAt: null,
6971
+ ownerQueueDepth: null
6380
6972
  };
6381
6973
  if (globalFlags.format === "json") {
6382
6974
  process.stdout.write(`${JSON.stringify(payload2)}
@@ -6398,10 +6990,14 @@ async function handleStatus(explicitAgentName, flags, command, config) {
6398
6990
  process.stdout.write(`uptime: -
6399
6991
  `);
6400
6992
  process.stdout.write(`lastPromptTime: -
6993
+ `);
6994
+ process.stdout.write(`ownerStatus: -
6401
6995
  `);
6402
6996
  return;
6403
6997
  }
6404
6998
  const running = isProcessAlive(record.pid);
6999
+ const owner = await readSessionQueueOwnerStatus(record.id);
7000
+ const ownerStatus = owner ? owner.stale ? "stale" : "active" : "inactive";
6405
7001
  const payload = {
6406
7002
  ...canonicalSessionIdentity(record),
6407
7003
  agentCommand: record.agentCommand,
@@ -6410,7 +7006,12 @@ async function handleStatus(explicitAgentName, flags, command, config) {
6410
7006
  uptime: running ? formatUptime(record.agentStartedAt) ?? null : null,
6411
7007
  lastPromptTime: record.lastPromptAt ?? null,
6412
7008
  exitCode: running ? null : record.lastAgentExitCode ?? null,
6413
- signal: running ? null : record.lastAgentExitSignal ?? null
7009
+ signal: running ? null : record.lastAgentExitSignal ?? null,
7010
+ ownerPid: owner?.pid ?? null,
7011
+ ownerStatus,
7012
+ ownerGeneration: owner?.ownerGeneration ?? null,
7013
+ ownerHeartbeatAt: owner?.heartbeatAt ?? null,
7014
+ ownerQueueDepth: owner?.queueDepth ?? null
6414
7015
  };
6415
7016
  if (globalFlags.format === "json") {
6416
7017
  process.stdout.write(`${JSON.stringify(payload)}
@@ -6437,6 +7038,16 @@ async function handleStatus(explicitAgentName, flags, command, config) {
6437
7038
  process.stdout.write(`uptime: ${payload.uptime ?? "-"}
6438
7039
  `);
6439
7040
  process.stdout.write(`lastPromptTime: ${payload.lastPromptTime ?? "-"}
7041
+ `);
7042
+ process.stdout.write(`ownerStatus: ${payload.ownerStatus}
7043
+ `);
7044
+ process.stdout.write(`ownerPid: ${payload.ownerPid ?? "-"}
7045
+ `);
7046
+ process.stdout.write(`ownerGeneration: ${payload.ownerGeneration ?? "-"}
7047
+ `);
7048
+ process.stdout.write(`ownerHeartbeatAt: ${payload.ownerHeartbeatAt ?? "-"}
7049
+ `);
7050
+ process.stdout.write(`ownerQueueDepth: ${payload.ownerQueueDepth ?? "-"}
6440
7051
  `);
6441
7052
  if (payload.status === "dead") {
6442
7053
  process.stdout.write(`exitCode: ${payload.exitCode ?? "-"}
@@ -6541,7 +7152,7 @@ function registerSharedAgentSubcommands(parent, explicitAgentName, config, descr
6541
7152
  const setModeCommand = parent.command("set-mode").description(descriptions.setMode).argument(
6542
7153
  "<mode>",
6543
7154
  "Mode id",
6544
- (value) => parseNonEmptyValue("Mode", value)
7155
+ (value) => parseNonEmptyValue2("Mode", value)
6545
7156
  );
6546
7157
  addSessionNameOption(setModeCommand);
6547
7158
  setModeCommand.action(async function(modeId, flags) {
@@ -6550,11 +7161,11 @@ function registerSharedAgentSubcommands(parent, explicitAgentName, config, descr
6550
7161
  const setConfigCommand = parent.command("set").description(descriptions.setConfig).argument(
6551
7162
  "<key>",
6552
7163
  "Config option id",
6553
- (value) => parseNonEmptyValue("Config option key", value)
7164
+ (value) => parseNonEmptyValue2("Config option key", value)
6554
7165
  ).argument(
6555
7166
  "<value>",
6556
7167
  "Config option value",
6557
- (value) => parseNonEmptyValue("Config option value", value)
7168
+ (value) => parseNonEmptyValue2("Config option value", value)
6558
7169
  );
6559
7170
  addSessionNameOption(setConfigCommand);
6560
7171
  setConfigCommand.action(async function(key, value, flags) {
@@ -6749,7 +7360,10 @@ async function main(argv = process.argv) {
6749
7360
  requestedOutputFormat,
6750
7361
  requestedJsonStrict
6751
7362
  );
6752
- const builtInAgents = listBuiltInAgents(config.agents);
7363
+ const internalQueueOwnerFlags = parseQueueOwnerFlags(
7364
+ argv.slice(2),
7365
+ DEFAULT_QUEUE_OWNER_TTL_MS
7366
+ );
6753
7367
  const program = new Command();
6754
7368
  program.name("acpx").description("Headless CLI client for the Agent Client Protocol").enablePositionalOptions().showHelpAfterError();
6755
7369
  if (requestedJsonStrict) {
@@ -6761,56 +7375,39 @@ async function main(argv = process.argv) {
6761
7375
  });
6762
7376
  }
6763
7377
  addGlobalFlags(program);
6764
- for (const agentName of builtInAgents) {
6765
- registerAgentCommand(program, agentName, config);
6766
- }
6767
- registerDefaultCommands(program, config);
6768
- const scan = detectAgentToken(argv.slice(2));
6769
- if (!scan.hasAgentOverride && scan.token && !TOP_LEVEL_VERBS.has(scan.token) && !builtInAgents.includes(scan.token)) {
6770
- registerAgentCommand(program, scan.token, config);
6771
- }
6772
- program.argument("[prompt...]", "Prompt text").action(async function(promptParts) {
6773
- if (promptParts.length === 0 && process.stdin.isTTY) {
6774
- if (requestedJsonStrict) {
6775
- throw new InvalidArgumentError(
6776
- "Prompt is required (pass as argument, --file, or pipe via stdin)"
6777
- );
6778
- }
6779
- this.outputHelp();
6780
- return;
7378
+ configurePublicCli({
7379
+ program,
7380
+ argv: argv.slice(2),
7381
+ config,
7382
+ requestedJsonStrict,
7383
+ topLevelVerbs: TOP_LEVEL_VERBS,
7384
+ listBuiltInAgents,
7385
+ detectAgentToken,
7386
+ registerAgentCommand,
7387
+ registerDefaultCommands,
7388
+ handlePromptAction: async (command, promptParts) => {
7389
+ await handlePrompt(void 0, promptParts, {}, command, config);
6781
7390
  }
6782
- await handlePrompt(void 0, promptParts, {}, this, config);
6783
7391
  });
6784
- program.addHelpText(
6785
- "after",
6786
- `
6787
- Examples:
6788
- acpx codex sessions new
6789
- acpx codex "fix the tests"
6790
- acpx codex prompt "fix the tests"
6791
- acpx codex --no-wait "queue follow-up task"
6792
- acpx codex exec "what does this repo do"
6793
- acpx codex cancel
6794
- acpx codex set-mode plan
6795
- acpx codex set approval_policy conservative
6796
- acpx codex -s backend "fix the API"
6797
- acpx codex sessions
6798
- acpx codex sessions new --name backend
6799
- acpx codex sessions ensure --name backend
6800
- acpx codex sessions close backend
6801
- acpx codex status
6802
- acpx config show
6803
- acpx config init
6804
- acpx --ttl 30 codex "investigate flaky tests"
6805
- acpx claude "refactor auth"
6806
- acpx gemini "add logging"
6807
- acpx --agent ./my-custom-server "do something"`
6808
- );
6809
7392
  program.exitOverride((error) => {
6810
7393
  throw error;
6811
7394
  });
6812
7395
  await runWithOutputPolicy(requestedOutputPolicy, async () => {
6813
7396
  try {
7397
+ if (internalQueueOwnerFlags) {
7398
+ await runQueueOwnerProcess2({
7399
+ sessionId: internalQueueOwnerFlags.sessionId,
7400
+ ttlMs: internalQueueOwnerFlags.ttlMs,
7401
+ permissionMode: internalQueueOwnerFlags.permissionMode,
7402
+ nonInteractivePermissions: internalQueueOwnerFlags.nonInteractivePermissions,
7403
+ authCredentials: config.auth,
7404
+ authPolicy: internalQueueOwnerFlags.authPolicy,
7405
+ timeoutMs: internalQueueOwnerFlags.timeoutMs,
7406
+ suppressSdkConsoleErrors: internalQueueOwnerFlags.suppressSdkConsoleErrors,
7407
+ verbose: internalQueueOwnerFlags.verbose
7408
+ });
7409
+ return;
7410
+ }
6814
7411
  await program.parseAsync(argv);
6815
7412
  } catch (error) {
6816
7413
  if (error instanceof CommanderError) {