acpx 0.1.5 → 0.1.7

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 (3) hide show
  1. package/README.md +40 -7
  2. package/dist/cli.js +1168 -231
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -50,6 +50,7 @@ import path from "path";
50
50
  var DEFAULT_TIMEOUT_MS = void 0;
51
51
  var DEFAULT_TTL_MS = 3e5;
52
52
  var DEFAULT_PERMISSION_MODE = "approve-reads";
53
+ var DEFAULT_NON_INTERACTIVE_PERMISSION_POLICY = "deny";
53
54
  var DEFAULT_AUTH_POLICY = "skip";
54
55
  var DEFAULT_OUTPUT_FORMAT = "text";
55
56
  var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set([
@@ -57,6 +58,7 @@ var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set([
57
58
  "approve-reads",
58
59
  "deny-all"
59
60
  ]);
61
+ var VALID_NON_INTERACTIVE_PERMISSION_POLICIES = /* @__PURE__ */ new Set(["deny", "fail"]);
60
62
  var VALID_AUTH_POLICIES = /* @__PURE__ */ new Set(["skip", "fail"]);
61
63
  var VALID_OUTPUT_FORMATS = /* @__PURE__ */ new Set(["text", "json", "quiet"]);
62
64
  function defaultGlobalConfigPath() {
@@ -101,6 +103,19 @@ function parsePermissionMode(value, sourcePath) {
101
103
  }
102
104
  return value;
103
105
  }
106
+ function parseNonInteractivePermissionPolicy(value, sourcePath) {
107
+ if (value == null) {
108
+ return void 0;
109
+ }
110
+ if (typeof value !== "string" || !VALID_NON_INTERACTIVE_PERMISSION_POLICIES.has(
111
+ value
112
+ )) {
113
+ throw new Error(
114
+ `Invalid config nonInteractivePermissions in ${sourcePath}: expected deny or fail`
115
+ );
116
+ }
117
+ return value;
118
+ }
104
119
  function parseAuthPolicy(value, sourcePath) {
105
120
  if (value == null) {
106
121
  return void 0;
@@ -225,6 +240,13 @@ async function loadResolvedConfig(cwd) {
225
240
  const projectConfig = projectResult.config;
226
241
  const defaultAgent = parseDefaultAgent(projectConfig?.defaultAgent, projectPath) ?? parseDefaultAgent(globalConfig?.defaultAgent, globalPath) ?? DEFAULT_AGENT_NAME;
227
242
  const defaultPermissions = parsePermissionMode(projectConfig?.defaultPermissions, projectPath) ?? parsePermissionMode(globalConfig?.defaultPermissions, globalPath) ?? DEFAULT_PERMISSION_MODE;
243
+ const nonInteractivePermissions = parseNonInteractivePermissionPolicy(
244
+ projectConfig?.nonInteractivePermissions,
245
+ projectPath
246
+ ) ?? parseNonInteractivePermissionPolicy(
247
+ globalConfig?.nonInteractivePermissions,
248
+ globalPath
249
+ ) ?? DEFAULT_NON_INTERACTIVE_PERMISSION_POLICY;
228
250
  const authPolicy = parseAuthPolicy(projectConfig?.authPolicy, projectPath) ?? parseAuthPolicy(globalConfig?.authPolicy, globalPath) ?? DEFAULT_AUTH_POLICY;
229
251
  const ttlMs = parseTtlMs(projectConfig?.ttl, projectPath) ?? parseTtlMs(globalConfig?.ttl, globalPath) ?? DEFAULT_TTL_MS;
230
252
  const timeoutConfiguredInProject = projectConfig != null && Object.prototype.hasOwnProperty.call(projectConfig, "timeout");
@@ -247,6 +269,7 @@ async function loadResolvedConfig(cwd) {
247
269
  return {
248
270
  defaultAgent,
249
271
  defaultPermissions,
272
+ nonInteractivePermissions,
250
273
  authPolicy,
251
274
  ttlMs,
252
275
  timeoutMs,
@@ -267,6 +290,7 @@ function toConfigDisplay(config) {
267
290
  return {
268
291
  defaultAgent: config.defaultAgent,
269
292
  defaultPermissions: config.defaultPermissions,
293
+ nonInteractivePermissions: config.nonInteractivePermissions,
270
294
  authPolicy: config.authPolicy,
271
295
  ttl: Math.round(config.ttlMs / 1e3),
272
296
  timeout: config.timeoutMs == null ? null : config.timeoutMs / 1e3,
@@ -289,6 +313,7 @@ async function initGlobalConfigFile() {
289
313
  const payload = {
290
314
  defaultAgent: DEFAULT_AGENT_NAME,
291
315
  defaultPermissions: "approve-all",
316
+ nonInteractivePermissions: "deny",
292
317
  authPolicy: "skip",
293
318
  ttl: 300,
294
319
  timeout: null,
@@ -304,6 +329,293 @@ async function initGlobalConfigFile() {
304
329
  };
305
330
  }
306
331
 
332
+ // src/errors.ts
333
+ var AcpxOperationalError = class extends Error {
334
+ outputCode;
335
+ detailCode;
336
+ origin;
337
+ retryable;
338
+ acp;
339
+ outputAlreadyEmitted;
340
+ constructor(message, options) {
341
+ super(message, options);
342
+ this.name = new.target.name;
343
+ this.outputCode = options?.outputCode;
344
+ this.detailCode = options?.detailCode;
345
+ this.origin = options?.origin;
346
+ this.retryable = options?.retryable;
347
+ this.acp = options?.acp;
348
+ this.outputAlreadyEmitted = options?.outputAlreadyEmitted;
349
+ }
350
+ };
351
+ var SessionNotFoundError = class extends AcpxOperationalError {
352
+ sessionId;
353
+ constructor(sessionId) {
354
+ super(`Session not found: ${sessionId}`);
355
+ this.sessionId = sessionId;
356
+ }
357
+ };
358
+ var SessionResolutionError = class extends AcpxOperationalError {
359
+ };
360
+ var AgentSpawnError = class extends AcpxOperationalError {
361
+ agentCommand;
362
+ constructor(agentCommand, cause) {
363
+ super(`Failed to spawn agent command: ${agentCommand}`, {
364
+ cause: cause instanceof Error ? cause : void 0
365
+ });
366
+ this.agentCommand = agentCommand;
367
+ }
368
+ };
369
+ var AuthPolicyError = class extends AcpxOperationalError {
370
+ constructor(message, options) {
371
+ super(message, {
372
+ outputCode: "RUNTIME",
373
+ detailCode: "AUTH_REQUIRED",
374
+ origin: "acp",
375
+ ...options
376
+ });
377
+ }
378
+ };
379
+ var QueueConnectionError = class extends AcpxOperationalError {
380
+ };
381
+ var QueueProtocolError = class extends AcpxOperationalError {
382
+ };
383
+ var PermissionDeniedError = class extends AcpxOperationalError {
384
+ };
385
+ var PermissionPromptUnavailableError = class extends AcpxOperationalError {
386
+ constructor() {
387
+ super("Permission prompt unavailable in non-interactive mode");
388
+ }
389
+ };
390
+
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
415
+ var RESOURCE_NOT_FOUND_ACP_CODES = /* @__PURE__ */ new Set([-32001, -32002]);
416
+ var AUTH_REQUIRED_ACP_CODES = /* @__PURE__ */ new Set([-32e3]);
417
+ function asRecord(value) {
418
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
419
+ return void 0;
420
+ }
421
+ return value;
422
+ }
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
+ function toAcpErrorPayload(value) {
482
+ const record = asRecord(value);
483
+ if (!record) {
484
+ return void 0;
485
+ }
486
+ if (typeof record.code !== "number" || !Number.isFinite(record.code)) {
487
+ return void 0;
488
+ }
489
+ if (typeof record.message !== "string" || record.message.length === 0) {
490
+ return void 0;
491
+ }
492
+ return {
493
+ code: record.code,
494
+ message: record.message,
495
+ data: record.data
496
+ };
497
+ }
498
+ function extractAcpErrorInternal(value, depth) {
499
+ if (depth > 5) {
500
+ return void 0;
501
+ }
502
+ const direct = toAcpErrorPayload(value);
503
+ if (direct) {
504
+ return direct;
505
+ }
506
+ const record = asRecord(value);
507
+ if (!record) {
508
+ return void 0;
509
+ }
510
+ if ("error" in record) {
511
+ const nested = extractAcpErrorInternal(record.error, depth + 1);
512
+ if (nested) {
513
+ return nested;
514
+ }
515
+ }
516
+ if ("cause" in record) {
517
+ const nested = extractAcpErrorInternal(record.cause, depth + 1);
518
+ if (nested) {
519
+ return nested;
520
+ }
521
+ }
522
+ return void 0;
523
+ }
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) {
537
+ if (error instanceof Error) {
538
+ return error.message;
539
+ }
540
+ if (error && typeof error === "object") {
541
+ const maybeMessage = error.message;
542
+ if (typeof maybeMessage === "string" && maybeMessage.length > 0) {
543
+ return maybeMessage;
544
+ }
545
+ try {
546
+ return JSON.stringify(error);
547
+ } catch {
548
+ }
549
+ }
550
+ return String(error);
551
+ }
552
+ function extractAcpError(error) {
553
+ return extractAcpErrorInternal(error, 0);
554
+ }
555
+ function isAcpResourceNotFoundError(error) {
556
+ const acp = extractAcpError(error);
557
+ if (acp && RESOURCE_NOT_FOUND_ACP_CODES.has(acp.code)) {
558
+ return true;
559
+ }
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");
562
+ }
563
+ function mapErrorCode(error) {
564
+ if (error instanceof PermissionPromptUnavailableError) {
565
+ return "PERMISSION_PROMPT_UNAVAILABLE";
566
+ }
567
+ if (error instanceof PermissionDeniedError) {
568
+ return "PERMISSION_DENIED";
569
+ }
570
+ if (isTimeoutLike(error)) {
571
+ return "TIMEOUT";
572
+ }
573
+ if (isNoSessionLike(error) || isAcpResourceNotFoundError(error)) {
574
+ return "NO_SESSION";
575
+ }
576
+ if (isUsageLike(error)) {
577
+ return "USAGE";
578
+ }
579
+ return void 0;
580
+ }
581
+ function normalizeOutputError(error, options = {}) {
582
+ const meta = readOutputErrorMeta(error);
583
+ const mapped = mapErrorCode(error);
584
+ let code = mapped ?? options.defaultCode ?? "RUNTIME";
585
+ if (meta.outputCode) {
586
+ code = meta.outputCode;
587
+ }
588
+ if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) {
589
+ code = "NO_SESSION";
590
+ }
591
+ const acp = options.acp ?? meta.acp ?? extractAcpError(error);
592
+ const detailCode = meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
593
+ return {
594
+ code,
595
+ message: formatErrorMessage(error),
596
+ detailCode,
597
+ origin: meta.origin ?? options.origin,
598
+ retryable: meta.retryable ?? options.retryable,
599
+ acp
600
+ };
601
+ }
602
+ function exitCodeForOutputErrorCode(code) {
603
+ switch (code) {
604
+ case "USAGE":
605
+ return EXIT_CODES.USAGE;
606
+ case "TIMEOUT":
607
+ return EXIT_CODES.TIMEOUT;
608
+ case "NO_SESSION":
609
+ return EXIT_CODES.NO_SESSION;
610
+ case "PERMISSION_DENIED":
611
+ case "PERMISSION_PROMPT_UNAVAILABLE":
612
+ return EXIT_CODES.PERMISSION_DENIED;
613
+ case "RUNTIME":
614
+ default:
615
+ return EXIT_CODES.ERROR;
616
+ }
617
+ }
618
+
307
619
  // src/output.ts
308
620
  var MAX_THOUGHT_CHARS = 900;
309
621
  var MAX_INLINE_CHARS = 220;
@@ -324,6 +636,8 @@ var OUTPUT_PRIORITY_KEYS = [
324
636
  function nowIso() {
325
637
  return (/* @__PURE__ */ new Date()).toISOString();
326
638
  }
639
+ var DEFAULT_JSON_SESSION_ID = "unknown";
640
+ var DEFAULT_JSON_STREAM = "prompt";
327
641
  function asStatus(status) {
328
642
  return status ?? "unknown";
329
643
  }
@@ -344,7 +658,7 @@ function toStatusLabel(status) {
344
658
  return "running";
345
659
  }
346
660
  }
347
- function asRecord(value) {
661
+ function asRecord2(value) {
348
662
  if (!value || typeof value !== "object" || Array.isArray(value)) {
349
663
  return void 0;
350
664
  }
@@ -438,7 +752,7 @@ function summarizeToolInput(rawInput) {
438
752
  if (typeof rawInput === "string" || typeof rawInput === "number" || typeof rawInput === "boolean") {
439
753
  return toInline(String(rawInput));
440
754
  }
441
- const record = asRecord(rawInput);
755
+ const record = asRecord2(rawInput);
442
756
  if (record) {
443
757
  const command = readFirstString(record, ["command", "cmd", "program"]);
444
758
  const args = readFirstStringArray(record, ["args", "arguments"]);
@@ -572,7 +886,7 @@ function extractOutputText(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
572
886
  }
573
887
  return dedupeStrings(parts).join("\n");
574
888
  }
575
- const record = asRecord(value);
889
+ const record = asRecord2(value);
576
890
  if (!record) {
577
891
  return void 0;
578
892
  }
@@ -641,6 +955,8 @@ var TextOutputFormatter = class {
641
955
  this.stdout = stdout;
642
956
  this.useColor = Boolean(stdout.isTTY);
643
957
  }
958
+ setContext(_context) {
959
+ }
644
960
  onSessionUpdate(notification) {
645
961
  const update = notification.update;
646
962
  if (update.sessionUpdate !== "agent_thought_chunk") {
@@ -684,6 +1000,11 @@ var TextOutputFormatter = class {
684
1000
  this.beginSection("done");
685
1001
  this.writeLine(this.dim(`[done] ${stopReason}`));
686
1002
  }
1003
+ onError(params) {
1004
+ this.flushThoughtBuffer();
1005
+ this.beginSection("done");
1006
+ this.writeLine(this.formatAnsi(`[error] ${params.code}: ${params.message}`, "31"));
1007
+ }
687
1008
  onClientOperation(operation) {
688
1009
  this.flushThoughtBuffer();
689
1010
  this.beginSection("client");
@@ -870,8 +1191,29 @@ var TextOutputFormatter = class {
870
1191
  };
871
1192
  var JsonOutputFormatter = class {
872
1193
  stdout;
873
- constructor(stdout) {
1194
+ sessionId;
1195
+ requestId;
1196
+ stream;
1197
+ sequence = 0;
1198
+ constructor(stdout, context) {
874
1199
  this.stdout = stdout;
1200
+ this.sessionId = context?.sessionId?.trim() || DEFAULT_JSON_SESSION_ID;
1201
+ this.requestId = context?.requestId?.trim() || void 0;
1202
+ this.stream = context?.stream ?? DEFAULT_JSON_STREAM;
1203
+ }
1204
+ setContext(context) {
1205
+ const nextSessionId = context.sessionId?.trim() || this.sessionId || DEFAULT_JSON_SESSION_ID;
1206
+ const nextRequestId = context.requestId?.trim() || void 0;
1207
+ const nextStream = context.stream ?? this.stream ?? DEFAULT_JSON_STREAM;
1208
+ const sessionChanged = nextSessionId !== this.sessionId;
1209
+ const requestChanged = nextRequestId !== this.requestId;
1210
+ const streamChanged = nextStream !== this.stream;
1211
+ this.sessionId = nextSessionId;
1212
+ this.requestId = nextRequestId;
1213
+ this.stream = nextStream;
1214
+ if (sessionChanged || requestChanged || streamChanged) {
1215
+ this.sequence = 0;
1216
+ }
875
1217
  }
876
1218
  onSessionUpdate(notification) {
877
1219
  const update = notification.update;
@@ -945,6 +1287,18 @@ var JsonOutputFormatter = class {
945
1287
  timestamp: nowIso()
946
1288
  });
947
1289
  }
1290
+ onError(params) {
1291
+ this.emit({
1292
+ type: "error",
1293
+ code: params.code,
1294
+ detailCode: params.detailCode,
1295
+ origin: params.origin,
1296
+ message: params.message,
1297
+ retryable: params.retryable,
1298
+ acp: params.acp,
1299
+ timestamp: params.timestamp ?? nowIso()
1300
+ });
1301
+ }
948
1302
  onClientOperation(operation) {
949
1303
  this.emit({
950
1304
  type: "client_operation",
@@ -958,7 +1312,15 @@ var JsonOutputFormatter = class {
958
1312
  flush() {
959
1313
  }
960
1314
  emit(event) {
961
- this.stdout.write(`${JSON.stringify(event)}
1315
+ const payload = {
1316
+ eventVersion: 1,
1317
+ sessionId: this.sessionId || DEFAULT_JSON_SESSION_ID,
1318
+ requestId: this.requestId,
1319
+ seq: this.sequence++,
1320
+ stream: this.stream ?? DEFAULT_JSON_STREAM,
1321
+ ...event
1322
+ };
1323
+ this.stdout.write(`${JSON.stringify(payload)}
962
1324
  `);
963
1325
  }
964
1326
  };
@@ -968,6 +1330,8 @@ var QuietOutputFormatter = class {
968
1330
  constructor(stdout) {
969
1331
  this.stdout = stdout;
970
1332
  }
1333
+ setContext(_context) {
1334
+ }
971
1335
  onSessionUpdate(notification) {
972
1336
  const update = notification.update;
973
1337
  if (update.sessionUpdate !== "agent_message_chunk") {
@@ -982,6 +1346,8 @@ var QuietOutputFormatter = class {
982
1346
  const text = this.chunks.join("");
983
1347
  this.stdout.write(text.endsWith("\n") ? text : `${text}
984
1348
  `);
1349
+ }
1350
+ onError(_params) {
985
1351
  }
986
1352
  onClientOperation(_operation) {
987
1353
  }
@@ -994,7 +1360,7 @@ function createOutputFormatter(format, options = {}) {
994
1360
  case "text":
995
1361
  return new TextOutputFormatter(stdout);
996
1362
  case "json":
997
- return new JsonOutputFormatter(stdout);
1363
+ return new JsonOutputFormatter(stdout, options.jsonContext);
998
1364
  case "quiet":
999
1365
  return new QuietOutputFormatter(stdout);
1000
1366
  default: {
@@ -1018,40 +1384,6 @@ import { spawn as spawn2 } from "child_process";
1018
1384
  import path3 from "path";
1019
1385
  import { Readable, Writable } from "stream";
1020
1386
 
1021
- // src/errors.ts
1022
- var AcpxOperationalError = class extends Error {
1023
- constructor(message, options) {
1024
- super(message, options);
1025
- this.name = new.target.name;
1026
- }
1027
- };
1028
- var SessionNotFoundError = class extends AcpxOperationalError {
1029
- sessionId;
1030
- constructor(sessionId) {
1031
- super(`Session not found: ${sessionId}`);
1032
- this.sessionId = sessionId;
1033
- }
1034
- };
1035
- var SessionResolutionError = class extends AcpxOperationalError {
1036
- };
1037
- var AgentSpawnError = class extends AcpxOperationalError {
1038
- agentCommand;
1039
- constructor(agentCommand, cause) {
1040
- super(`Failed to spawn agent command: ${agentCommand}`, {
1041
- cause: cause instanceof Error ? cause : void 0
1042
- });
1043
- this.agentCommand = agentCommand;
1044
- }
1045
- };
1046
- var AuthPolicyError = class extends AcpxOperationalError {
1047
- };
1048
- var QueueConnectionError = class extends AcpxOperationalError {
1049
- };
1050
- var QueueProtocolError = class extends AcpxOperationalError {
1051
- };
1052
- var PermissionDeniedError = class extends AcpxOperationalError {
1053
- };
1054
-
1055
1387
  // src/filesystem.ts
1056
1388
  import fs2 from "fs/promises";
1057
1389
  import path2 from "path";
@@ -1115,15 +1447,22 @@ async function defaultConfirmWrite(filePath, preview) {
1115
1447
  prompt: "Allow write? (y/N) "
1116
1448
  });
1117
1449
  }
1450
+ function canPromptForPermission() {
1451
+ return Boolean(process.stdin.isTTY && process.stderr.isTTY);
1452
+ }
1118
1453
  var FileSystemHandlers = class {
1119
1454
  rootDir;
1120
1455
  permissionMode;
1456
+ nonInteractivePermissions;
1121
1457
  onOperation;
1458
+ usesDefaultConfirmWrite;
1122
1459
  confirmWrite;
1123
1460
  constructor(options) {
1124
1461
  this.rootDir = path2.resolve(options.cwd);
1125
1462
  this.permissionMode = options.permissionMode;
1463
+ this.nonInteractivePermissions = options.nonInteractivePermissions ?? "deny";
1126
1464
  this.onOperation = options.onOperation;
1465
+ this.usesDefaultConfirmWrite = options.confirmWrite == null;
1127
1466
  this.confirmWrite = options.confirmWrite ?? defaultConfirmWrite;
1128
1467
  }
1129
1468
  async readTextFile(params) {
@@ -1208,6 +1547,9 @@ var FileSystemHandlers = class {
1208
1547
  if (this.permissionMode === "deny-all") {
1209
1548
  return false;
1210
1549
  }
1550
+ if (this.usesDefaultConfirmWrite && this.nonInteractivePermissions === "fail" && !canPromptForPermission()) {
1551
+ throw new PermissionPromptUnavailableError();
1552
+ }
1211
1553
  return await this.confirmWrite(filePath, preview);
1212
1554
  }
1213
1555
  resolvePathWithinRoot(rawPath) {
@@ -1312,7 +1654,10 @@ async function promptForToolPermission(params) {
1312
1654
  [permission] Allow ${toolName} [${toolKind}]? (y/N) `
1313
1655
  });
1314
1656
  }
1315
- async function resolvePermissionRequest(params, mode) {
1657
+ function canPromptForPermission2() {
1658
+ return Boolean(process.stdin.isTTY && process.stderr.isTTY);
1659
+ }
1660
+ async function resolvePermissionRequest(params, mode, nonInteractivePolicy = "deny") {
1316
1661
  const options = params.options ?? [];
1317
1662
  if (options.length === 0) {
1318
1663
  return cancelled();
@@ -1335,6 +1680,15 @@ async function resolvePermissionRequest(params, mode) {
1335
1680
  if (isAutoApprovedReadKind(kind) && allowOption) {
1336
1681
  return selected(allowOption.optionId);
1337
1682
  }
1683
+ if (!canPromptForPermission2()) {
1684
+ if (nonInteractivePolicy === "fail") {
1685
+ throw new PermissionPromptUnavailableError();
1686
+ }
1687
+ if (rejectOption) {
1688
+ return selected(rejectOption.optionId);
1689
+ }
1690
+ return cancelled();
1691
+ }
1338
1692
  const approved = await promptForToolPermission(params);
1339
1693
  if (approved && allowOption) {
1340
1694
  return selected(allowOption.optionId);
@@ -1419,6 +1773,9 @@ async function defaultConfirmExecute(commandLine) {
1419
1773
  [permission] Allow terminal command "${commandLine}"? (y/N) `
1420
1774
  });
1421
1775
  }
1776
+ function canPromptForPermission3() {
1777
+ return Boolean(process.stdin.isTTY && process.stderr.isTTY);
1778
+ }
1422
1779
  function waitMs(ms) {
1423
1780
  return new Promise((resolve) => {
1424
1781
  setTimeout(resolve, Math.max(0, ms));
@@ -1427,14 +1784,18 @@ function waitMs(ms) {
1427
1784
  var TerminalManager = class {
1428
1785
  cwd;
1429
1786
  permissionMode;
1787
+ nonInteractivePermissions;
1430
1788
  onOperation;
1789
+ usesDefaultConfirmExecute;
1431
1790
  confirmExecute;
1432
1791
  killGraceMs;
1433
1792
  terminals = /* @__PURE__ */ new Map();
1434
1793
  constructor(options) {
1435
1794
  this.cwd = options.cwd;
1436
1795
  this.permissionMode = options.permissionMode;
1796
+ this.nonInteractivePermissions = options.nonInteractivePermissions ?? "deny";
1437
1797
  this.onOperation = options.onOperation;
1798
+ this.usesDefaultConfirmExecute = options.confirmExecute == null;
1438
1799
  this.confirmExecute = options.confirmExecute ?? defaultConfirmExecute;
1439
1800
  this.killGraceMs = Math.max(
1440
1801
  0,
@@ -1656,6 +2017,9 @@ var TerminalManager = class {
1656
2017
  if (this.permissionMode === "deny-all") {
1657
2018
  return false;
1658
2019
  }
2020
+ if (this.usesDefaultConfirmExecute && this.nonInteractivePermissions === "fail" && !canPromptForPermission3()) {
2021
+ throw new PermissionPromptUnavailableError();
2022
+ }
1659
2023
  return await this.confirmExecute(commandLine);
1660
2024
  }
1661
2025
  isRunning(terminal) {
@@ -1693,6 +2057,24 @@ var TerminalManager = class {
1693
2057
  var REPLAY_IDLE_MS = 80;
1694
2058
  var REPLAY_DRAIN_TIMEOUT_MS = 5e3;
1695
2059
  var DRAIN_POLL_INTERVAL_MS = 20;
2060
+ function shouldSuppressSdkConsoleError(args) {
2061
+ if (args.length === 0) {
2062
+ return false;
2063
+ }
2064
+ return typeof args[0] === "string" && args[0] === "Error handling request";
2065
+ }
2066
+ function installSdkConsoleErrorSuppression() {
2067
+ const originalConsoleError = console.error;
2068
+ console.error = (...args) => {
2069
+ if (shouldSuppressSdkConsoleError(args)) {
2070
+ return;
2071
+ }
2072
+ originalConsoleError(...args);
2073
+ };
2074
+ return () => {
2075
+ console.error = originalConsoleError;
2076
+ };
2077
+ }
1696
2078
  function isoNow() {
1697
2079
  return (/* @__PURE__ */ new Date()).toISOString();
1698
2080
  }
@@ -1835,6 +2217,7 @@ var AcpClient = class {
1835
2217
  agentStartedAt;
1836
2218
  lastAgentExit;
1837
2219
  lastKnownPid;
2220
+ promptPermissionFailures = /* @__PURE__ */ new Map();
1838
2221
  constructor(options) {
1839
2222
  this.options = {
1840
2223
  ...options,
@@ -1845,11 +2228,13 @@ var AcpClient = class {
1845
2228
  this.filesystem = new FileSystemHandlers({
1846
2229
  cwd: this.options.cwd,
1847
2230
  permissionMode: this.options.permissionMode,
2231
+ nonInteractivePermissions: this.options.nonInteractivePermissions,
1848
2232
  onOperation: emitOperation
1849
2233
  });
1850
2234
  this.terminalManager = new TerminalManager({
1851
2235
  cwd: this.options.cwd,
1852
2236
  permissionMode: this.options.permissionMode,
2237
+ nonInteractivePermissions: this.options.nonInteractivePermissions,
1853
2238
  onOperation: emitOperation
1854
2239
  });
1855
2240
  }
@@ -2014,26 +2399,46 @@ var AcpClient = class {
2014
2399
  }
2015
2400
  async prompt(sessionId, text) {
2016
2401
  const connection = this.getConnection();
2017
- const promptPromise = connection.prompt({
2018
- sessionId,
2019
- prompt: [
2020
- {
2021
- type: "text",
2022
- text
2023
- }
2024
- ]
2025
- });
2402
+ const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
2403
+ let promptPromise;
2404
+ try {
2405
+ promptPromise = connection.prompt({
2406
+ sessionId,
2407
+ prompt: [
2408
+ {
2409
+ type: "text",
2410
+ text
2411
+ }
2412
+ ]
2413
+ });
2414
+ } catch (error) {
2415
+ restoreConsoleError?.();
2416
+ throw error;
2417
+ }
2026
2418
  this.activePrompt = {
2027
2419
  sessionId,
2028
2420
  promise: promptPromise
2029
2421
  };
2030
2422
  try {
2031
- return await promptPromise;
2423
+ const response = await promptPromise;
2424
+ const permissionFailure = this.consumePromptPermissionFailure(sessionId);
2425
+ if (permissionFailure) {
2426
+ throw permissionFailure;
2427
+ }
2428
+ return response;
2429
+ } catch (error) {
2430
+ const permissionFailure = this.consumePromptPermissionFailure(sessionId);
2431
+ if (permissionFailure) {
2432
+ throw permissionFailure;
2433
+ }
2434
+ throw error;
2032
2435
  } finally {
2436
+ restoreConsoleError?.();
2033
2437
  if (this.activePrompt?.promise === promptPromise) {
2034
2438
  this.activePrompt = void 0;
2035
2439
  }
2036
2440
  this.cancellingSessionIds.delete(sessionId);
2441
+ this.promptPermissionFailures.delete(sessionId);
2037
2442
  }
2038
2443
  }
2039
2444
  async setSessionMode(sessionId, modeId) {
@@ -2110,6 +2515,7 @@ var AcpClient = class {
2110
2515
  this.suppressSessionUpdates = false;
2111
2516
  this.activePrompt = void 0;
2112
2517
  this.cancellingSessionIds.clear();
2518
+ this.promptPermissionFailures.clear();
2113
2519
  this.connection = void 0;
2114
2520
  this.agent = void 0;
2115
2521
  }
@@ -2177,10 +2583,26 @@ var AcpClient = class {
2177
2583
  }
2178
2584
  };
2179
2585
  }
2180
- const response = await resolvePermissionRequest(
2181
- params,
2182
- this.options.permissionMode
2183
- );
2586
+ let response;
2587
+ try {
2588
+ response = await resolvePermissionRequest(
2589
+ params,
2590
+ this.options.permissionMode,
2591
+ this.options.nonInteractivePermissions ?? "deny"
2592
+ );
2593
+ } catch (error) {
2594
+ if (error instanceof PermissionPromptUnavailableError) {
2595
+ this.notePromptPermissionFailure(params.sessionId, error);
2596
+ this.permissionStats.requested += 1;
2597
+ this.permissionStats.cancelled += 1;
2598
+ return {
2599
+ outcome: {
2600
+ outcome: "cancelled"
2601
+ }
2602
+ };
2603
+ }
2604
+ throw error;
2605
+ }
2184
2606
  const decision = classifyPermissionDecision(params, response);
2185
2607
  this.permissionStats.requested += 1;
2186
2608
  if (decision === "approved") {
@@ -2219,14 +2641,40 @@ var AcpClient = class {
2219
2641
  unexpectedDuringPrompt: !this.closing && Boolean(this.activePrompt)
2220
2642
  };
2221
2643
  }
2644
+ notePromptPermissionFailure(sessionId, error) {
2645
+ if (!this.promptPermissionFailures.has(sessionId)) {
2646
+ this.promptPermissionFailures.set(sessionId, error);
2647
+ }
2648
+ }
2649
+ consumePromptPermissionFailure(sessionId) {
2650
+ const error = this.promptPermissionFailures.get(sessionId);
2651
+ if (error) {
2652
+ this.promptPermissionFailures.delete(sessionId);
2653
+ }
2654
+ return error;
2655
+ }
2222
2656
  async handleReadTextFile(params) {
2223
2657
  return await this.filesystem.readTextFile(params);
2224
2658
  }
2225
2659
  async handleWriteTextFile(params) {
2226
- return await this.filesystem.writeTextFile(params);
2660
+ try {
2661
+ return await this.filesystem.writeTextFile(params);
2662
+ } catch (error) {
2663
+ if (error instanceof PermissionPromptUnavailableError) {
2664
+ this.notePromptPermissionFailure(params.sessionId, error);
2665
+ }
2666
+ throw error;
2667
+ }
2227
2668
  }
2228
2669
  async handleCreateTerminal(params) {
2229
- return await this.terminalManager.createTerminal(params);
2670
+ try {
2671
+ return await this.terminalManager.createTerminal(params);
2672
+ } catch (error) {
2673
+ if (error instanceof PermissionPromptUnavailableError) {
2674
+ this.notePromptPermissionFailure(params.sessionId, error);
2675
+ }
2676
+ throw error;
2677
+ }
2230
2678
  }
2231
2679
  async handleTerminalOutput(params) {
2232
2680
  return await this.terminalManager.terminalOutput(params);
@@ -2325,7 +2773,11 @@ var QueueOwnerTurnController = class {
2325
2773
  }
2326
2774
  assertCanHandleControlRequest() {
2327
2775
  if (this.state === "closing") {
2328
- throw new QueueConnectionError("Queue owner is closing");
2776
+ throw new QueueConnectionError("Queue owner is closing", {
2777
+ detailCode: "QUEUE_OWNER_SHUTTING_DOWN",
2778
+ origin: "queue",
2779
+ retryable: true
2780
+ });
2329
2781
  }
2330
2782
  }
2331
2783
  async requestCancel() {
@@ -2391,7 +2843,7 @@ import os2 from "os";
2391
2843
  import path4 from "path";
2392
2844
 
2393
2845
  // src/queue-messages.ts
2394
- function asRecord2(value) {
2846
+ function asRecord3(value) {
2395
2847
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2396
2848
  return void 0;
2397
2849
  }
@@ -2400,8 +2852,34 @@ function asRecord2(value) {
2400
2852
  function isPermissionMode(value) {
2401
2853
  return value === "approve-all" || value === "approve-reads" || value === "deny-all";
2402
2854
  }
2855
+ function isNonInteractivePermissionPolicy(value) {
2856
+ return value === "deny" || value === "fail";
2857
+ }
2858
+ function isOutputErrorCode2(value) {
2859
+ return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
2860
+ }
2861
+ function isOutputErrorOrigin2(value) {
2862
+ return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
2863
+ }
2864
+ function parseAcpError(value) {
2865
+ const record = asRecord3(value);
2866
+ if (!record) {
2867
+ return void 0;
2868
+ }
2869
+ if (typeof record.code !== "number" || !Number.isFinite(record.code)) {
2870
+ return void 0;
2871
+ }
2872
+ if (typeof record.message !== "string" || record.message.length === 0) {
2873
+ return void 0;
2874
+ }
2875
+ return {
2876
+ code: record.code,
2877
+ message: record.message,
2878
+ data: record.data
2879
+ };
2880
+ }
2403
2881
  function parseQueueRequest(raw) {
2404
- const request = asRecord2(raw);
2882
+ const request = asRecord3(raw);
2405
2883
  if (!request) {
2406
2884
  return null;
2407
2885
  }
@@ -2411,7 +2889,9 @@ function parseQueueRequest(raw) {
2411
2889
  const timeoutRaw = request.timeoutMs;
2412
2890
  const timeoutMs = typeof timeoutRaw === "number" && Number.isFinite(timeoutRaw) && timeoutRaw > 0 ? Math.round(timeoutRaw) : void 0;
2413
2891
  if (request.type === "submit_prompt") {
2414
- if (typeof request.message !== "string" || !isPermissionMode(request.permissionMode) || typeof request.waitForCompletion !== "boolean") {
2892
+ const nonInteractivePermissions = request.nonInteractivePermissions == null ? void 0 : isNonInteractivePermissionPolicy(request.nonInteractivePermissions) ? request.nonInteractivePermissions : null;
2893
+ const suppressSdkConsoleErrors = request.suppressSdkConsoleErrors == null ? void 0 : typeof request.suppressSdkConsoleErrors === "boolean" ? request.suppressSdkConsoleErrors : null;
2894
+ if (typeof request.message !== "string" || !isPermissionMode(request.permissionMode) || nonInteractivePermissions === null || suppressSdkConsoleErrors === null || typeof request.waitForCompletion !== "boolean") {
2415
2895
  return null;
2416
2896
  }
2417
2897
  return {
@@ -2419,7 +2899,9 @@ function parseQueueRequest(raw) {
2419
2899
  requestId: request.requestId,
2420
2900
  message: request.message,
2421
2901
  permissionMode: request.permissionMode,
2902
+ nonInteractivePermissions,
2422
2903
  timeoutMs,
2904
+ ...suppressSdkConsoleErrors !== void 0 ? { suppressSdkConsoleErrors } : {},
2423
2905
  waitForCompletion: request.waitForCompletion
2424
2906
  };
2425
2907
  }
@@ -2455,15 +2937,15 @@ function parseQueueRequest(raw) {
2455
2937
  return null;
2456
2938
  }
2457
2939
  function parseSessionSendResult(raw) {
2458
- const result = asRecord2(raw);
2940
+ const result = asRecord3(raw);
2459
2941
  if (!result) {
2460
2942
  return null;
2461
2943
  }
2462
2944
  if (typeof result.stopReason !== "string" || typeof result.sessionId !== "string" || typeof result.resumed !== "boolean") {
2463
2945
  return null;
2464
2946
  }
2465
- const permissionStats = asRecord2(result.permissionStats);
2466
- const record = asRecord2(result.record);
2947
+ const permissionStats = asRecord3(result.permissionStats);
2948
+ const record = asRecord3(result.record);
2467
2949
  if (!permissionStats || !record) {
2468
2950
  return null;
2469
2951
  }
@@ -2478,7 +2960,7 @@ function parseSessionSendResult(raw) {
2478
2960
  return result;
2479
2961
  }
2480
2962
  function parseQueueOwnerMessage(raw) {
2481
- const message = asRecord2(raw);
2963
+ const message = asRecord3(raw);
2482
2964
  if (!message || typeof message.type !== "string") {
2483
2965
  return null;
2484
2966
  }
@@ -2503,7 +2985,7 @@ function parseQueueOwnerMessage(raw) {
2503
2985
  };
2504
2986
  }
2505
2987
  if (message.type === "client_operation") {
2506
- const operation = asRecord2(message.operation);
2988
+ const operation = asRecord3(message.operation);
2507
2989
  if (!operation || typeof operation.method !== "string" || typeof operation.status !== "string" || typeof operation.summary !== "string" || typeof operation.timestamp !== "string") {
2508
2990
  return null;
2509
2991
  }
@@ -2558,7 +3040,7 @@ function parseQueueOwnerMessage(raw) {
2558
3040
  };
2559
3041
  }
2560
3042
  if (message.type === "set_config_option_result") {
2561
- const response = asRecord2(message.response);
3043
+ const response = asRecord3(message.response);
2562
3044
  if (!response || !Array.isArray(response.configOptions)) {
2563
3045
  return null;
2564
3046
  }
@@ -2572,10 +3054,20 @@ function parseQueueOwnerMessage(raw) {
2572
3054
  if (typeof message.message !== "string") {
2573
3055
  return null;
2574
3056
  }
3057
+ const code = isOutputErrorCode2(message.code) ? message.code : void 0;
3058
+ const detailCode = typeof message.detailCode === "string" && message.detailCode.trim().length > 0 ? message.detailCode : void 0;
3059
+ const origin = isOutputErrorOrigin2(message.origin) ? message.origin : void 0;
3060
+ const retryable = typeof message.retryable === "boolean" ? message.retryable : void 0;
3061
+ const acp = parseAcpError(message.acp);
2575
3062
  return {
2576
3063
  type: "error",
2577
3064
  requestId: message.requestId,
2578
- message: message.message
3065
+ code,
3066
+ detailCode,
3067
+ origin,
3068
+ message: message.message,
3069
+ retryable,
3070
+ acp
2579
3071
  };
2580
3072
  }
2581
3073
  return null;
@@ -2589,21 +3081,34 @@ var QUEUE_CONNECT_RETRY_MS = 50;
2589
3081
  function queueBaseDir() {
2590
3082
  return path4.join(os2.homedir(), ".acpx", "queues");
2591
3083
  }
2592
- function formatError(error) {
2593
- if (error instanceof Error) {
2594
- return error.message;
2595
- }
2596
- if (error && typeof error === "object") {
2597
- const maybeMessage = error.message;
2598
- if (typeof maybeMessage === "string" && maybeMessage.length > 0) {
2599
- return maybeMessage;
2600
- }
2601
- try {
2602
- return JSON.stringify(error);
2603
- } catch {
2604
- }
2605
- }
2606
- return String(error);
3084
+ function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
3085
+ return {
3086
+ type: "error",
3087
+ requestId,
3088
+ code: "RUNTIME",
3089
+ detailCode,
3090
+ origin: "queue",
3091
+ retryable: options.retryable,
3092
+ message
3093
+ };
3094
+ }
3095
+ function makeQueueOwnerErrorFromUnknown(requestId, error, detailCode, options = {}) {
3096
+ const normalized = normalizeOutputError(error, {
3097
+ defaultCode: "RUNTIME",
3098
+ origin: "queue",
3099
+ detailCode,
3100
+ retryable: options.retryable
3101
+ });
3102
+ return {
3103
+ type: "error",
3104
+ requestId,
3105
+ code: normalized.code,
3106
+ detailCode: normalized.detailCode,
3107
+ origin: normalized.origin,
3108
+ message: normalized.message,
3109
+ retryable: normalized.retryable,
3110
+ acp: normalized.acp
3111
+ };
2607
3112
  }
2608
3113
  function isProcessAlive(pid) {
2609
3114
  if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) {
@@ -2847,11 +3352,16 @@ var SessionQueueOwner = class _SessionQueueOwner {
2847
3352
  }
2848
3353
  for (const task of this.pending.splice(0)) {
2849
3354
  if (task.waitForCompletion) {
2850
- task.send({
2851
- type: "error",
2852
- requestId: task.requestId,
2853
- message: "Queue owner shutting down before prompt execution"
2854
- });
3355
+ task.send(
3356
+ makeQueueOwnerError(
3357
+ task.requestId,
3358
+ "Queue owner shutting down before prompt execution",
3359
+ "QUEUE_OWNER_SHUTTING_DOWN",
3360
+ {
3361
+ retryable: true
3362
+ }
3363
+ )
3364
+ );
2855
3365
  }
2856
3366
  task.close();
2857
3367
  }
@@ -2890,11 +3400,16 @@ var SessionQueueOwner = class _SessionQueueOwner {
2890
3400
  enqueue(task) {
2891
3401
  if (this.closed) {
2892
3402
  if (task.waitForCompletion) {
2893
- task.send({
2894
- type: "error",
2895
- requestId: task.requestId,
2896
- message: "Queue owner is shutting down"
2897
- });
3403
+ task.send(
3404
+ makeQueueOwnerError(
3405
+ task.requestId,
3406
+ "Queue owner is shutting down",
3407
+ "QUEUE_OWNER_SHUTTING_DOWN",
3408
+ {
3409
+ retryable: true
3410
+ }
3411
+ )
3412
+ );
2898
3413
  }
2899
3414
  task.close();
2900
3415
  return;
@@ -2909,22 +3424,24 @@ var SessionQueueOwner = class _SessionQueueOwner {
2909
3424
  handleConnection(socket) {
2910
3425
  socket.setEncoding("utf8");
2911
3426
  if (this.closed) {
2912
- writeQueueMessage(socket, {
2913
- type: "error",
2914
- requestId: "unknown",
2915
- message: "Queue owner is closed"
2916
- });
3427
+ writeQueueMessage(
3428
+ socket,
3429
+ makeQueueOwnerError("unknown", "Queue owner is closed", "QUEUE_OWNER_CLOSED", {
3430
+ retryable: true
3431
+ })
3432
+ );
2917
3433
  socket.end();
2918
3434
  return;
2919
3435
  }
2920
3436
  let buffer = "";
2921
3437
  let handled = false;
2922
- const fail = (requestId, message) => {
2923
- writeQueueMessage(socket, {
2924
- type: "error",
2925
- requestId,
2926
- message
2927
- });
3438
+ const fail = (requestId, message, detailCode) => {
3439
+ writeQueueMessage(
3440
+ socket,
3441
+ makeQueueOwnerError(requestId, message, detailCode, {
3442
+ retryable: false
3443
+ })
3444
+ );
2928
3445
  socket.end();
2929
3446
  };
2930
3447
  const processLine = (line) => {
@@ -2936,12 +3453,16 @@ var SessionQueueOwner = class _SessionQueueOwner {
2936
3453
  try {
2937
3454
  parsed = JSON.parse(line);
2938
3455
  } catch {
2939
- fail("unknown", "Invalid queue request payload");
3456
+ fail(
3457
+ "unknown",
3458
+ "Invalid queue request payload",
3459
+ "QUEUE_REQUEST_PAYLOAD_INVALID_JSON"
3460
+ );
2940
3461
  return;
2941
3462
  }
2942
3463
  const request = parseQueueRequest(parsed);
2943
3464
  if (!request) {
2944
- fail("unknown", "Invalid queue request");
3465
+ fail("unknown", "Invalid queue request", "QUEUE_REQUEST_INVALID");
2945
3466
  return;
2946
3467
  }
2947
3468
  if (request.type === "cancel_prompt") {
@@ -2956,12 +3477,14 @@ var SessionQueueOwner = class _SessionQueueOwner {
2956
3477
  cancelled: cancelled2
2957
3478
  });
2958
3479
  }).catch((error) => {
2959
- const message = formatError(error);
2960
- writeQueueMessage(socket, {
2961
- type: "error",
2962
- requestId: request.requestId,
2963
- message
2964
- });
3480
+ writeQueueMessage(
3481
+ socket,
3482
+ makeQueueOwnerErrorFromUnknown(
3483
+ request.requestId,
3484
+ error,
3485
+ "QUEUE_CONTROL_REQUEST_FAILED"
3486
+ )
3487
+ );
2965
3488
  }).finally(() => {
2966
3489
  if (!socket.destroyed) {
2967
3490
  socket.end();
@@ -2981,12 +3504,14 @@ var SessionQueueOwner = class _SessionQueueOwner {
2981
3504
  modeId: request.modeId
2982
3505
  });
2983
3506
  }).catch((error) => {
2984
- const message = formatError(error);
2985
- writeQueueMessage(socket, {
2986
- type: "error",
2987
- requestId: request.requestId,
2988
- message
2989
- });
3507
+ writeQueueMessage(
3508
+ socket,
3509
+ makeQueueOwnerErrorFromUnknown(
3510
+ request.requestId,
3511
+ error,
3512
+ "QUEUE_CONTROL_REQUEST_FAILED"
3513
+ )
3514
+ );
2990
3515
  }).finally(() => {
2991
3516
  if (!socket.destroyed) {
2992
3517
  socket.end();
@@ -3006,12 +3531,14 @@ var SessionQueueOwner = class _SessionQueueOwner {
3006
3531
  response
3007
3532
  });
3008
3533
  }).catch((error) => {
3009
- const message = formatError(error);
3010
- writeQueueMessage(socket, {
3011
- type: "error",
3012
- requestId: request.requestId,
3013
- message
3014
- });
3534
+ writeQueueMessage(
3535
+ socket,
3536
+ makeQueueOwnerErrorFromUnknown(
3537
+ request.requestId,
3538
+ error,
3539
+ "QUEUE_CONTROL_REQUEST_FAILED"
3540
+ )
3541
+ );
3015
3542
  }).finally(() => {
3016
3543
  if (!socket.destroyed) {
3017
3544
  socket.end();
@@ -3023,7 +3550,9 @@ var SessionQueueOwner = class _SessionQueueOwner {
3023
3550
  requestId: request.requestId,
3024
3551
  message: request.message,
3025
3552
  permissionMode: request.permissionMode,
3553
+ nonInteractivePermissions: request.nonInteractivePermissions,
3026
3554
  timeoutMs: request.timeoutMs,
3555
+ suppressSdkConsoleErrors: request.suppressSdkConsoleErrors,
3027
3556
  waitForCompletion: request.waitForCompletion,
3028
3557
  send: (message) => {
3029
3558
  writeQueueMessage(socket, message);
@@ -3071,9 +3600,16 @@ async function submitToQueueOwner(owner, options) {
3071
3600
  requestId,
3072
3601
  message: options.message,
3073
3602
  permissionMode: options.permissionMode,
3603
+ nonInteractivePermissions: options.nonInteractivePermissions,
3074
3604
  timeoutMs: options.timeoutMs,
3605
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
3075
3606
  waitForCompletion: options.waitForCompletion
3076
3607
  };
3608
+ options.outputFormatter.setContext({
3609
+ sessionId: options.sessionId,
3610
+ requestId,
3611
+ stream: "prompt"
3612
+ });
3077
3613
  return await new Promise((resolve, reject) => {
3078
3614
  let settled = false;
3079
3615
  let acknowledged = false;
@@ -3106,16 +3642,33 @@ async function submitToQueueOwner(owner, options) {
3106
3642
  try {
3107
3643
  parsed = JSON.parse(line);
3108
3644
  } catch {
3109
- finishReject(new QueueProtocolError("Queue owner sent invalid JSON payload"));
3645
+ finishReject(
3646
+ new QueueProtocolError("Queue owner sent invalid JSON payload", {
3647
+ detailCode: "QUEUE_PROTOCOL_INVALID_JSON",
3648
+ origin: "queue",
3649
+ retryable: true
3650
+ })
3651
+ );
3110
3652
  return;
3111
3653
  }
3112
3654
  const message = parseQueueOwnerMessage(parsed);
3113
3655
  if (!message || message.requestId !== requestId) {
3114
- finishReject(new QueueProtocolError("Queue owner sent malformed message"));
3656
+ finishReject(
3657
+ new QueueProtocolError("Queue owner sent malformed message", {
3658
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
3659
+ origin: "queue",
3660
+ retryable: true
3661
+ })
3662
+ );
3115
3663
  return;
3116
3664
  }
3117
3665
  if (message.type === "accepted") {
3118
3666
  acknowledged = true;
3667
+ options.outputFormatter.setContext({
3668
+ sessionId: options.sessionId,
3669
+ requestId: message.requestId,
3670
+ stream: "prompt"
3671
+ });
3119
3672
  if (!options.waitForCompletion) {
3120
3673
  const queued = {
3121
3674
  queued: true,
@@ -3126,9 +3679,41 @@ async function submitToQueueOwner(owner, options) {
3126
3679
  }
3127
3680
  return;
3128
3681
  }
3682
+ if (message.type === "error") {
3683
+ options.outputFormatter.setContext({
3684
+ sessionId: options.sessionId,
3685
+ requestId: message.requestId,
3686
+ stream: "prompt"
3687
+ });
3688
+ options.outputFormatter.onError({
3689
+ code: message.code ?? "RUNTIME",
3690
+ detailCode: message.detailCode,
3691
+ origin: message.origin ?? "queue",
3692
+ message: message.message,
3693
+ retryable: message.retryable,
3694
+ acp: message.acp
3695
+ });
3696
+ options.outputFormatter.flush();
3697
+ const queueErrorAlreadyEmitted = options.errorEmissionPolicy?.queueErrorAlreadyEmitted ?? true;
3698
+ finishReject(
3699
+ new QueueConnectionError(message.message, {
3700
+ outputCode: message.code,
3701
+ detailCode: message.detailCode,
3702
+ origin: message.origin ?? "queue",
3703
+ retryable: message.retryable,
3704
+ acp: message.acp,
3705
+ ...queueErrorAlreadyEmitted ? { outputAlreadyEmitted: true } : {}
3706
+ })
3707
+ );
3708
+ return;
3709
+ }
3129
3710
  if (!acknowledged) {
3130
3711
  finishReject(
3131
- new QueueConnectionError("Queue owner did not acknowledge request")
3712
+ new QueueConnectionError("Queue owner did not acknowledge request", {
3713
+ detailCode: "QUEUE_ACK_MISSING",
3714
+ origin: "queue",
3715
+ retryable: true
3716
+ })
3132
3717
  );
3133
3718
  return;
3134
3719
  }
@@ -3153,11 +3738,13 @@ async function submitToQueueOwner(owner, options) {
3153
3738
  finishResolve(message.result);
3154
3739
  return;
3155
3740
  }
3156
- if (message.type === "error") {
3157
- finishReject(new QueueConnectionError(message.message));
3158
- return;
3159
- }
3160
- finishReject(new QueueProtocolError("Queue owner returned unexpected response"));
3741
+ finishReject(
3742
+ new QueueProtocolError("Queue owner returned unexpected response", {
3743
+ detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
3744
+ origin: "queue",
3745
+ retryable: true
3746
+ })
3747
+ );
3161
3748
  };
3162
3749
  socket.on("data", (chunk) => {
3163
3750
  buffer += chunk;
@@ -3181,7 +3768,12 @@ async function submitToQueueOwner(owner, options) {
3181
3768
  if (!acknowledged) {
3182
3769
  finishReject(
3183
3770
  new QueueConnectionError(
3184
- "Queue owner disconnected before acknowledging request"
3771
+ "Queue owner disconnected before acknowledging request",
3772
+ {
3773
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_ACK",
3774
+ origin: "queue",
3775
+ retryable: true
3776
+ }
3185
3777
  )
3186
3778
  );
3187
3779
  return;
@@ -3196,7 +3788,11 @@ async function submitToQueueOwner(owner, options) {
3196
3788
  return;
3197
3789
  }
3198
3790
  finishReject(
3199
- new QueueConnectionError("Queue owner disconnected before prompt completion")
3791
+ new QueueConnectionError("Queue owner disconnected before prompt completion", {
3792
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_COMPLETION",
3793
+ origin: "queue",
3794
+ retryable: true
3795
+ })
3200
3796
  );
3201
3797
  });
3202
3798
  socket.write(`${JSON.stringify(request)}
@@ -3240,31 +3836,59 @@ async function submitControlToQueueOwner(owner, request, isExpectedResponse) {
3240
3836
  try {
3241
3837
  parsed = JSON.parse(line);
3242
3838
  } catch {
3243
- finishReject(new QueueProtocolError("Queue owner sent invalid JSON payload"));
3839
+ finishReject(
3840
+ new QueueProtocolError("Queue owner sent invalid JSON payload", {
3841
+ detailCode: "QUEUE_PROTOCOL_INVALID_JSON",
3842
+ origin: "queue",
3843
+ retryable: true
3844
+ })
3845
+ );
3244
3846
  return;
3245
3847
  }
3246
3848
  const message = parseQueueOwnerMessage(parsed);
3247
3849
  if (!message || message.requestId !== request.requestId) {
3248
- finishReject(new QueueProtocolError("Queue owner sent malformed message"));
3850
+ finishReject(
3851
+ new QueueProtocolError("Queue owner sent malformed message", {
3852
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
3853
+ origin: "queue",
3854
+ retryable: true
3855
+ })
3856
+ );
3249
3857
  return;
3250
3858
  }
3251
3859
  if (message.type === "accepted") {
3252
3860
  acknowledged = true;
3253
3861
  return;
3254
3862
  }
3255
- if (!acknowledged) {
3863
+ if (message.type === "error") {
3256
3864
  finishReject(
3257
- new QueueConnectionError("Queue owner did not acknowledge request")
3865
+ new QueueConnectionError(message.message, {
3866
+ outputCode: message.code,
3867
+ detailCode: message.detailCode,
3868
+ origin: message.origin ?? "queue",
3869
+ retryable: message.retryable,
3870
+ acp: message.acp
3871
+ })
3258
3872
  );
3259
3873
  return;
3260
3874
  }
3261
- if (message.type === "error") {
3262
- finishReject(new QueueConnectionError(message.message));
3875
+ if (!acknowledged) {
3876
+ finishReject(
3877
+ new QueueConnectionError("Queue owner did not acknowledge request", {
3878
+ detailCode: "QUEUE_ACK_MISSING",
3879
+ origin: "queue",
3880
+ retryable: true
3881
+ })
3882
+ );
3263
3883
  return;
3264
3884
  }
3265
3885
  if (!isExpectedResponse(message)) {
3266
3886
  finishReject(
3267
- new QueueProtocolError("Queue owner returned unexpected response")
3887
+ new QueueProtocolError("Queue owner returned unexpected response", {
3888
+ detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
3889
+ origin: "queue",
3890
+ retryable: true
3891
+ })
3268
3892
  );
3269
3893
  return;
3270
3894
  }
@@ -3292,13 +3916,22 @@ async function submitControlToQueueOwner(owner, request, isExpectedResponse) {
3292
3916
  if (!acknowledged) {
3293
3917
  finishReject(
3294
3918
  new QueueConnectionError(
3295
- "Queue owner disconnected before acknowledging request"
3919
+ "Queue owner disconnected before acknowledging request",
3920
+ {
3921
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_ACK",
3922
+ origin: "queue",
3923
+ retryable: true
3924
+ }
3296
3925
  )
3297
3926
  );
3298
3927
  return;
3299
3928
  }
3300
3929
  finishReject(
3301
- new QueueConnectionError("Queue owner disconnected before responding")
3930
+ new QueueConnectionError("Queue owner disconnected before responding", {
3931
+ detailCode: "QUEUE_DISCONNECTED_BEFORE_COMPLETION",
3932
+ origin: "queue",
3933
+ retryable: true
3934
+ })
3302
3935
  );
3303
3936
  });
3304
3937
  socket.write(`${JSON.stringify(request)}
@@ -3319,7 +3952,11 @@ async function submitCancelToQueueOwner(owner) {
3319
3952
  return void 0;
3320
3953
  }
3321
3954
  if (response.requestId !== request.requestId) {
3322
- throw new QueueProtocolError("Queue owner returned mismatched cancel response");
3955
+ throw new QueueProtocolError("Queue owner returned mismatched cancel response", {
3956
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
3957
+ origin: "queue",
3958
+ retryable: true
3959
+ });
3323
3960
  }
3324
3961
  return response.cancelled;
3325
3962
  }
@@ -3339,7 +3976,11 @@ async function submitSetModeToQueueOwner(owner, modeId, timeoutMs) {
3339
3976
  return void 0;
3340
3977
  }
3341
3978
  if (response.requestId !== request.requestId) {
3342
- throw new QueueProtocolError("Queue owner returned mismatched set_mode response");
3979
+ throw new QueueProtocolError("Queue owner returned mismatched set_mode response", {
3980
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
3981
+ origin: "queue",
3982
+ retryable: true
3983
+ });
3343
3984
  }
3344
3985
  return true;
3345
3986
  }
@@ -3361,7 +4002,12 @@ async function submitSetConfigOptionToQueueOwner(owner, configId, value, timeout
3361
4002
  }
3362
4003
  if (response.requestId !== request.requestId) {
3363
4004
  throw new QueueProtocolError(
3364
- "Queue owner returned mismatched set_config_option response"
4005
+ "Queue owner returned mismatched set_config_option response",
4006
+ {
4007
+ detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
4008
+ origin: "queue",
4009
+ retryable: true
4010
+ }
3365
4011
  );
3366
4012
  }
3367
4013
  return response.response;
@@ -3390,7 +4036,12 @@ async function trySubmitToRunningOwner(options) {
3390
4036
  return void 0;
3391
4037
  }
3392
4038
  throw new QueueConnectionError(
3393
- "Session queue owner is running but not accepting queue requests"
4039
+ "Session queue owner is running but not accepting queue requests",
4040
+ {
4041
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4042
+ origin: "queue",
4043
+ retryable: true
4044
+ }
3394
4045
  );
3395
4046
  }
3396
4047
  async function tryCancelOnRunningOwner(options) {
@@ -3417,7 +4068,12 @@ async function tryCancelOnRunningOwner(options) {
3417
4068
  return void 0;
3418
4069
  }
3419
4070
  throw new QueueConnectionError(
3420
- "Session queue owner is running but not accepting cancel requests"
4071
+ "Session queue owner is running but not accepting cancel requests",
4072
+ {
4073
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4074
+ origin: "queue",
4075
+ retryable: true
4076
+ }
3421
4077
  );
3422
4078
  }
3423
4079
  async function trySetModeOnRunningOwner(sessionId, modeId, timeoutMs, verbose) {
@@ -3444,7 +4100,12 @@ async function trySetModeOnRunningOwner(sessionId, modeId, timeoutMs, verbose) {
3444
4100
  return void 0;
3445
4101
  }
3446
4102
  throw new QueueConnectionError(
3447
- "Session queue owner is running but not accepting set_mode requests"
4103
+ "Session queue owner is running but not accepting set_mode requests",
4104
+ {
4105
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4106
+ origin: "queue",
4107
+ retryable: true
4108
+ }
3448
4109
  );
3449
4110
  }
3450
4111
  async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, timeoutMs, verbose) {
@@ -3476,7 +4137,12 @@ async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, time
3476
4137
  return void 0;
3477
4138
  }
3478
4139
  throw new QueueConnectionError(
3479
- "Session queue owner is running but not accepting set_config_option requests"
4140
+ "Session queue owner is running but not accepting set_config_option requests",
4141
+ {
4142
+ detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
4143
+ origin: "queue",
4144
+ retryable: true
4145
+ }
3480
4146
  );
3481
4147
  }
3482
4148
  async function terminateQueueOwnerForSession(sessionId) {
@@ -3815,6 +4481,8 @@ var QueueTaskOutputFormatter = class {
3815
4481
  this.requestId = task.requestId;
3816
4482
  this.send = task.send;
3817
4483
  }
4484
+ setContext() {
4485
+ }
3818
4486
  onSessionUpdate(notification) {
3819
4487
  this.send({
3820
4488
  type: "session_update",
@@ -3836,16 +4504,32 @@ var QueueTaskOutputFormatter = class {
3836
4504
  stopReason
3837
4505
  });
3838
4506
  }
4507
+ onError(params) {
4508
+ this.send({
4509
+ type: "error",
4510
+ requestId: this.requestId,
4511
+ code: params.code,
4512
+ detailCode: params.detailCode,
4513
+ origin: params.origin,
4514
+ message: params.message,
4515
+ retryable: params.retryable,
4516
+ acp: params.acp
4517
+ });
4518
+ }
3839
4519
  flush() {
3840
4520
  }
3841
4521
  };
3842
4522
  var DISCARD_OUTPUT_FORMATTER = {
4523
+ setContext() {
4524
+ },
3843
4525
  onSessionUpdate() {
3844
4526
  },
3845
4527
  onClientOperation() {
3846
4528
  },
3847
4529
  onDone() {
3848
4530
  },
4531
+ onError() {
4532
+ },
3849
4533
  flush() {
3850
4534
  }
3851
4535
  };
@@ -3858,22 +4542,6 @@ function normalizeQueueOwnerTtlMs(ttlMs) {
3858
4542
  }
3859
4543
  return Math.round(ttlMs);
3860
4544
  }
3861
- function formatError2(error) {
3862
- if (error instanceof Error) {
3863
- return error.message;
3864
- }
3865
- if (error && typeof error === "object") {
3866
- const maybeMessage = error.message;
3867
- if (typeof maybeMessage === "string" && maybeMessage.length > 0) {
3868
- return maybeMessage;
3869
- }
3870
- try {
3871
- return JSON.stringify(error);
3872
- } catch {
3873
- }
3874
- }
3875
- return String(error);
3876
- }
3877
4545
  function collapseWhitespace2(value) {
3878
4546
  return value.replace(/\s+/g, " ").trim();
3879
4547
  }
@@ -3953,12 +4621,7 @@ function shouldFallbackToNewSession(error) {
3953
4621
  if (error instanceof TimeoutError || error instanceof InterruptedError) {
3954
4622
  return false;
3955
4623
  }
3956
- const message = formatError2(error).toLowerCase();
3957
- if (message.includes("resource_not_found") || message.includes("resource not found") || message.includes("session not found") || message.includes("unknown session") || message.includes("invalid session")) {
3958
- return true;
3959
- }
3960
- const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
3961
- return code === -32001 || code === -32002;
4624
+ return isAcpResourceNotFoundError(error);
3962
4625
  }
3963
4626
  async function connectAndLoadSession(options) {
3964
4627
  const record = options.record;
@@ -3998,7 +4661,7 @@ async function connectAndLoadSession(options) {
3998
4661
  );
3999
4662
  resumed = true;
4000
4663
  } catch (error) {
4001
- loadError = formatError2(error);
4664
+ loadError = formatErrorMessage(error);
4002
4665
  if (!shouldFallbackToNewSession(error)) {
4003
4666
  throw error;
4004
4667
  }
@@ -4026,10 +4689,12 @@ async function runQueuedTask(sessionRecordId, task, options) {
4026
4689
  sessionRecordId,
4027
4690
  message: task.message,
4028
4691
  permissionMode: task.permissionMode,
4692
+ nonInteractivePermissions: task.nonInteractivePermissions ?? options.nonInteractivePermissions,
4029
4693
  authCredentials: options.authCredentials,
4030
4694
  authPolicy: options.authPolicy,
4031
4695
  outputFormatter,
4032
4696
  timeoutMs: task.timeoutMs,
4697
+ suppressSdkConsoleErrors: task.suppressSdkConsoleErrors ?? options.suppressSdkConsoleErrors,
4033
4698
  verbose: options.verbose,
4034
4699
  onClientAvailable: options.onClientAvailable,
4035
4700
  onClientClosed: options.onClientClosed,
@@ -4043,12 +4708,20 @@ async function runQueuedTask(sessionRecordId, task, options) {
4043
4708
  });
4044
4709
  }
4045
4710
  } catch (error) {
4046
- const message = formatError2(error);
4711
+ const normalizedError = normalizeOutputError(error, {
4712
+ origin: "runtime",
4713
+ detailCode: "QUEUE_RUNTIME_PROMPT_FAILED"
4714
+ });
4047
4715
  if (task.waitForCompletion) {
4048
4716
  task.send({
4049
4717
  type: "error",
4050
4718
  requestId: task.requestId,
4051
- message
4719
+ code: normalizedError.code,
4720
+ detailCode: normalizedError.detailCode,
4721
+ origin: normalizedError.origin,
4722
+ message: normalizedError.message,
4723
+ retryable: normalizedError.retryable,
4724
+ acp: normalizedError.acp
4052
4725
  });
4053
4726
  }
4054
4727
  if (error instanceof InterruptedError) {
@@ -4061,13 +4734,19 @@ async function runQueuedTask(sessionRecordId, task, options) {
4061
4734
  async function runSessionPrompt(options) {
4062
4735
  const output = options.outputFormatter;
4063
4736
  const record = await resolveSessionRecord(options.sessionRecordId);
4737
+ output.setContext({
4738
+ sessionId: record.id,
4739
+ stream: "prompt"
4740
+ });
4064
4741
  const assistantSnippets = [];
4065
4742
  const client = new AcpClient({
4066
4743
  agentCommand: record.agentCommand,
4067
4744
  cwd: absolutePath(record.cwd),
4068
4745
  permissionMode: options.permissionMode,
4746
+ nonInteractivePermissions: options.nonInteractivePermissions,
4069
4747
  authCredentials: options.authCredentials,
4070
4748
  authPolicy: options.authPolicy,
4749
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
4071
4750
  verbose: options.verbose,
4072
4751
  onSessionUpdate: (notification) => {
4073
4752
  output.onSessionUpdate(notification);
@@ -4129,7 +4808,7 @@ async function runSessionPrompt(options) {
4129
4808
  } catch (error) {
4130
4809
  if (options.verbose) {
4131
4810
  process.stderr.write(
4132
- `[acpx] onPromptActive hook failed: ${formatError2(error)}
4811
+ `[acpx] onPromptActive hook failed: ${formatErrorMessage(error)}
4133
4812
  `
4134
4813
  );
4135
4814
  }
@@ -4209,6 +4888,7 @@ async function withConnectedSession(options) {
4209
4888
  agentCommand: record.agentCommand,
4210
4889
  cwd: absolutePath(record.cwd),
4211
4890
  permissionMode: options.permissionMode ?? "approve-reads",
4891
+ nonInteractivePermissions: options.nonInteractivePermissions,
4212
4892
  authCredentials: options.authCredentials,
4213
4893
  authPolicy: options.authPolicy,
4214
4894
  verbose: options.verbose
@@ -4288,6 +4968,7 @@ async function withConnectedSession(options) {
4288
4968
  async function runSessionSetModeDirect(options) {
4289
4969
  const result = await withConnectedSession({
4290
4970
  sessionRecordId: options.sessionRecordId,
4971
+ nonInteractivePermissions: options.nonInteractivePermissions,
4291
4972
  authCredentials: options.authCredentials,
4292
4973
  authPolicy: options.authPolicy,
4293
4974
  timeoutMs: options.timeoutMs,
@@ -4310,6 +4991,7 @@ async function runSessionSetModeDirect(options) {
4310
4991
  async function runSessionSetConfigOptionDirect(options) {
4311
4992
  const result = await withConnectedSession({
4312
4993
  sessionRecordId: options.sessionRecordId,
4994
+ nonInteractivePermissions: options.nonInteractivePermissions,
4313
4995
  authCredentials: options.authCredentials,
4314
4996
  authPolicy: options.authPolicy,
4315
4997
  timeoutMs: options.timeoutMs,
@@ -4336,8 +5018,10 @@ async function runOnce(options) {
4336
5018
  agentCommand: options.agentCommand,
4337
5019
  cwd: absolutePath(options.cwd),
4338
5020
  permissionMode: options.permissionMode,
5021
+ nonInteractivePermissions: options.nonInteractivePermissions,
4339
5022
  authCredentials: options.authCredentials,
4340
5023
  authPolicy: options.authPolicy,
5024
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
4341
5025
  verbose: options.verbose,
4342
5026
  onSessionUpdate: (notification) => output.onSessionUpdate(notification),
4343
5027
  onClientOperation: (operation) => output.onClientOperation(operation)
@@ -4350,6 +5034,10 @@ async function runOnce(options) {
4350
5034
  client.createSession(absolutePath(options.cwd)),
4351
5035
  options.timeoutMs
4352
5036
  );
5037
+ output.setContext({
5038
+ sessionId,
5039
+ stream: "prompt"
5040
+ });
4353
5041
  const response = await withTimeout(
4354
5042
  client.prompt(sessionId, options.message),
4355
5043
  options.timeoutMs
@@ -4372,6 +5060,7 @@ async function createSession(options) {
4372
5060
  agentCommand: options.agentCommand,
4373
5061
  cwd: absolutePath(options.cwd),
4374
5062
  permissionMode: options.permissionMode,
5063
+ nonInteractivePermissions: options.nonInteractivePermissions,
4375
5064
  authCredentials: options.authCredentials,
4376
5065
  authPolicy: options.authPolicy,
4377
5066
  verbose: options.verbose
@@ -4413,6 +5102,38 @@ async function createSession(options) {
4413
5102
  await client.close();
4414
5103
  }
4415
5104
  }
5105
+ async function ensureSession(options) {
5106
+ const cwd = absolutePath(options.cwd);
5107
+ const gitRoot = findGitRepositoryRoot(cwd);
5108
+ const walkBoundary = options.walkBoundary ?? gitRoot ?? cwd;
5109
+ const existing = await findSessionByDirectoryWalk({
5110
+ agentCommand: options.agentCommand,
5111
+ cwd,
5112
+ name: options.name,
5113
+ boundary: walkBoundary
5114
+ });
5115
+ if (existing) {
5116
+ return {
5117
+ record: existing,
5118
+ created: false
5119
+ };
5120
+ }
5121
+ const record = await createSession({
5122
+ agentCommand: options.agentCommand,
5123
+ cwd,
5124
+ name: options.name,
5125
+ permissionMode: options.permissionMode,
5126
+ nonInteractivePermissions: options.nonInteractivePermissions,
5127
+ authCredentials: options.authCredentials,
5128
+ authPolicy: options.authPolicy,
5129
+ timeoutMs: options.timeoutMs,
5130
+ verbose: options.verbose
5131
+ });
5132
+ return {
5133
+ record,
5134
+ created: true
5135
+ };
5136
+ }
4416
5137
  async function sendSession(options) {
4417
5138
  const waitForCompletion = options.waitForCompletion !== false;
4418
5139
  const queueOwnerTtlMs = normalizeQueueOwnerTtlMs(options.ttlMs);
@@ -4420,8 +5141,11 @@ async function sendSession(options) {
4420
5141
  sessionId: options.sessionId,
4421
5142
  message: options.message,
4422
5143
  permissionMode: options.permissionMode,
5144
+ nonInteractivePermissions: options.nonInteractivePermissions,
4423
5145
  outputFormatter: options.outputFormatter,
5146
+ errorEmissionPolicy: options.errorEmissionPolicy,
4424
5147
  timeoutMs: options.timeoutMs,
5148
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
4425
5149
  waitForCompletion,
4426
5150
  verbose: options.verbose
4427
5151
  });
@@ -4435,8 +5159,11 @@ async function sendSession(options) {
4435
5159
  sessionId: options.sessionId,
4436
5160
  message: options.message,
4437
5161
  permissionMode: options.permissionMode,
5162
+ nonInteractivePermissions: options.nonInteractivePermissions,
4438
5163
  outputFormatter: options.outputFormatter,
5164
+ errorEmissionPolicy: options.errorEmissionPolicy,
4439
5165
  timeoutMs: options.timeoutMs,
5166
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
4440
5167
  waitForCompletion,
4441
5168
  verbose: options.verbose
4442
5169
  });
@@ -4453,6 +5180,7 @@ async function sendSession(options) {
4453
5180
  await runSessionSetModeDirect({
4454
5181
  sessionRecordId: options.sessionId,
4455
5182
  modeId,
5183
+ nonInteractivePermissions: options.nonInteractivePermissions,
4456
5184
  authCredentials: options.authCredentials,
4457
5185
  authPolicy: options.authPolicy,
4458
5186
  timeoutMs,
@@ -4464,6 +5192,7 @@ async function sendSession(options) {
4464
5192
  sessionRecordId: options.sessionId,
4465
5193
  configId,
4466
5194
  value,
5195
+ nonInteractivePermissions: options.nonInteractivePermissions,
4467
5196
  authCredentials: options.authCredentials,
4468
5197
  authPolicy: options.authPolicy,
4469
5198
  timeoutMs,
@@ -4479,7 +5208,7 @@ async function sendSession(options) {
4479
5208
  void applyPendingCancel().catch((error) => {
4480
5209
  if (options.verbose) {
4481
5210
  process.stderr.write(
4482
- `[acpx] failed to apply deferred cancel: ${formatError2(error)}
5211
+ `[acpx] failed to apply deferred cancel: ${formatErrorMessage(error)}
4483
5212
  `
4484
5213
  );
4485
5214
  }
@@ -4526,10 +5255,12 @@ async function sendSession(options) {
4526
5255
  sessionRecordId: options.sessionId,
4527
5256
  message: options.message,
4528
5257
  permissionMode: options.permissionMode,
5258
+ nonInteractivePermissions: options.nonInteractivePermissions,
4529
5259
  authCredentials: options.authCredentials,
4530
5260
  authPolicy: options.authPolicy,
4531
5261
  outputFormatter: options.outputFormatter,
4532
5262
  timeoutMs: options.timeoutMs,
5263
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
4533
5264
  verbose: options.verbose,
4534
5265
  onClientAvailable: setActiveController,
4535
5266
  onClientClosed: clearActiveController,
@@ -4554,8 +5285,10 @@ async function sendSession(options) {
4554
5285
  await runPromptTurn(async () => {
4555
5286
  await runQueuedTask(options.sessionId, task, {
4556
5287
  verbose: options.verbose,
5288
+ nonInteractivePermissions: options.nonInteractivePermissions,
4557
5289
  authCredentials: options.authCredentials,
4558
5290
  authPolicy: options.authPolicy,
5291
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
4559
5292
  onClientAvailable: setActiveController,
4560
5293
  onClientClosed: clearActiveController,
4561
5294
  onPromptActive: async () => {
@@ -4598,6 +5331,7 @@ async function setSessionMode(options) {
4598
5331
  return await runSessionSetModeDirect({
4599
5332
  sessionRecordId: options.sessionId,
4600
5333
  modeId: options.modeId,
5334
+ nonInteractivePermissions: options.nonInteractivePermissions,
4601
5335
  authCredentials: options.authCredentials,
4602
5336
  authPolicy: options.authPolicy,
4603
5337
  timeoutMs: options.timeoutMs,
@@ -4623,6 +5357,7 @@ async function setSessionConfigOption(options) {
4623
5357
  sessionRecordId: options.sessionId,
4624
5358
  configId: options.configId,
4625
5359
  value: options.value,
5360
+ nonInteractivePermissions: options.nonInteractivePermissions,
4626
5361
  authCredentials: options.authCredentials,
4627
5362
  authPolicy: options.authPolicy,
4628
5363
  timeoutMs: options.timeoutMs,
@@ -4669,19 +5404,6 @@ async function closeSession(sessionId) {
4669
5404
  return record;
4670
5405
  }
4671
5406
 
4672
- // src/types.ts
4673
- var EXIT_CODES = {
4674
- SUCCESS: 0,
4675
- ERROR: 1,
4676
- USAGE: 2,
4677
- TIMEOUT: 3,
4678
- NO_SESSION: 4,
4679
- PERMISSION_DENIED: 5,
4680
- INTERRUPTED: 130
4681
- };
4682
- var OUTPUT_FORMATS = ["text", "json", "quiet"];
4683
- var AUTH_POLICIES = ["skip", "fail"];
4684
-
4685
5407
  // src/cli.ts
4686
5408
  var NoSessionError = class extends Error {
4687
5409
  constructor(message) {
@@ -4716,6 +5438,16 @@ function parseAuthPolicy2(value) {
4716
5438
  }
4717
5439
  return value;
4718
5440
  }
5441
+ function parseNonInteractivePermissionPolicy2(value) {
5442
+ if (!NON_INTERACTIVE_PERMISSION_POLICIES.includes(
5443
+ value
5444
+ )) {
5445
+ throw new InvalidArgumentError(
5446
+ `Invalid non-interactive permission policy "${value}". Expected one of: ${NON_INTERACTIVE_PERMISSION_POLICIES.join(", ")}`
5447
+ );
5448
+ }
5449
+ return value;
5450
+ }
4719
5451
  function parseTimeoutSeconds(value) {
4720
5452
  const parsed = Number(value);
4721
5453
  if (!Number.isFinite(parsed) || parsed <= 0) {
@@ -4817,7 +5549,14 @@ function addGlobalFlags(command) {
4817
5549
  ).option("--approve-all", "Auto-approve all permission requests").option(
4818
5550
  "--approve-reads",
4819
5551
  "Auto-approve read/search requests and prompt for writes"
4820
- ).option("--deny-all", "Deny all permission requests").option("--format <fmt>", "Output format: text, json, quiet", parseOutputFormat2).option(
5552
+ ).option("--deny-all", "Deny all permission requests").option(
5553
+ "--non-interactive-permissions <policy>",
5554
+ "When prompting is unavailable: deny or fail",
5555
+ parseNonInteractivePermissionPolicy2
5556
+ ).option("--format <fmt>", "Output format: text, json, quiet", parseOutputFormat2).option(
5557
+ "--json-strict",
5558
+ "Strict JSON mode: requires --format json and suppresses non-JSON stderr output"
5559
+ ).option(
4821
5560
  "--timeout <seconds>",
4822
5561
  "Maximum time to wait for agent response",
4823
5562
  parseTimeoutSeconds
@@ -4866,19 +5605,39 @@ function addPromptInputOption(command) {
4866
5605
  }
4867
5606
  function resolveGlobalFlags(command, config) {
4868
5607
  const opts = command.optsWithGlobals();
5608
+ const format = opts.format ?? config.format ?? "text";
5609
+ const jsonStrict = opts.jsonStrict === true;
5610
+ const verbose = opts.verbose === true;
5611
+ if (jsonStrict && format !== "json") {
5612
+ throw new InvalidArgumentError("--json-strict requires --format json");
5613
+ }
5614
+ if (jsonStrict && verbose) {
5615
+ throw new InvalidArgumentError("--json-strict cannot be combined with --verbose");
5616
+ }
4869
5617
  return {
4870
5618
  agent: opts.agent,
4871
5619
  cwd: opts.cwd ?? process.cwd(),
4872
5620
  authPolicy: opts.authPolicy ?? config.authPolicy,
5621
+ nonInteractivePermissions: opts.nonInteractivePermissions ?? config.nonInteractivePermissions,
5622
+ jsonStrict,
4873
5623
  timeout: opts.timeout ?? config.timeoutMs,
4874
5624
  ttl: opts.ttl ?? config.ttlMs ?? DEFAULT_QUEUE_OWNER_TTL_MS,
4875
- verbose: opts.verbose === true,
4876
- format: opts.format ?? config.format ?? "text",
5625
+ verbose,
5626
+ format,
4877
5627
  approveAll: opts.approveAll ? true : void 0,
4878
5628
  approveReads: opts.approveReads ? true : void 0,
4879
5629
  denyAll: opts.denyAll ? true : void 0
4880
5630
  };
4881
5631
  }
5632
+ function resolveOutputPolicy(format, jsonStrict) {
5633
+ return {
5634
+ format,
5635
+ jsonStrict,
5636
+ suppressNonJsonStderr: jsonStrict,
5637
+ queueErrorAlreadyEmitted: format !== "quiet",
5638
+ suppressSdkConsoleErrors: jsonStrict
5639
+ };
5640
+ }
4882
5641
  function resolveAgentInvocation(explicitAgentName, globalFlags, config) {
4883
5642
  const override = globalFlags.agent?.trim();
4884
5643
  if (override && explicitAgentName) {
@@ -4966,6 +5725,29 @@ function printNewSessionByFormat(record, replaced, format) {
4966
5725
  process.stdout.write(`${record.id}
4967
5726
  `);
4968
5727
  }
5728
+ function printEnsuredSessionByFormat(record, created, format) {
5729
+ if (format === "json") {
5730
+ process.stdout.write(
5731
+ `${JSON.stringify({
5732
+ type: "session_ensured",
5733
+ id: record.id,
5734
+ sessionId: record.sessionId,
5735
+ name: record.name,
5736
+ created
5737
+ })}
5738
+ `
5739
+ );
5740
+ return;
5741
+ }
5742
+ if (format === "quiet") {
5743
+ process.stdout.write(`${record.id}
5744
+ `);
5745
+ return;
5746
+ }
5747
+ const action = created ? "created" : "existing";
5748
+ process.stdout.write(`${record.id} (${action})
5749
+ `);
5750
+ }
4969
5751
  function printQueuedPromptByFormat(result, format) {
4970
5752
  if (format === "json") {
4971
5753
  process.stdout.write(
@@ -5008,15 +5790,15 @@ function formatPromptSessionBannerLine(record, currentCwd) {
5008
5790
  }
5009
5791
  return `[acpx] session ${label} (${record.id}) \xB7 ${normalizedSessionCwd} \xB7 agent ${status}`;
5010
5792
  }
5011
- function printPromptSessionBanner(record, currentCwd, format) {
5012
- if (format === "quiet") {
5793
+ function printPromptSessionBanner(record, currentCwd, format, jsonStrict = false) {
5794
+ if (format === "quiet" || jsonStrict && format === "json") {
5013
5795
  return;
5014
5796
  }
5015
5797
  process.stderr.write(`${formatPromptSessionBannerLine(record, currentCwd)}
5016
5798
  `);
5017
5799
  }
5018
- function printCreatedSessionBanner(record, agentName, format) {
5019
- if (format === "quiet") {
5800
+ function printCreatedSessionBanner(record, agentName, format, jsonStrict = false) {
5801
+ if (format === "quiet" || jsonStrict && format === "json") {
5020
5802
  return;
5021
5803
  }
5022
5804
  const label = formatSessionLabel(record);
@@ -5047,9 +5829,12 @@ Create one: ${createCmd}`
5047
5829
  }
5048
5830
  async function handlePrompt(explicitAgentName, promptParts, flags, command, config) {
5049
5831
  const globalFlags = resolveGlobalFlags(command, config);
5832
+ const outputPolicy = resolveOutputPolicy(
5833
+ globalFlags.format,
5834
+ globalFlags.jsonStrict === true
5835
+ );
5050
5836
  const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
5051
5837
  const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
5052
- const outputFormatter = createOutputFormatter(globalFlags.format);
5053
5838
  const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
5054
5839
  const record = await findRoutedSessionOrThrow(
5055
5840
  agent.agentCommand,
@@ -5057,21 +5842,37 @@ async function handlePrompt(explicitAgentName, promptParts, flags, command, conf
5057
5842
  agent.cwd,
5058
5843
  flags.session
5059
5844
  );
5060
- printPromptSessionBanner(record, agent.cwd, globalFlags.format);
5845
+ const outputFormatter = createOutputFormatter(outputPolicy.format, {
5846
+ jsonContext: {
5847
+ sessionId: record.id,
5848
+ stream: "prompt"
5849
+ }
5850
+ });
5851
+ printPromptSessionBanner(
5852
+ record,
5853
+ agent.cwd,
5854
+ outputPolicy.format,
5855
+ outputPolicy.jsonStrict
5856
+ );
5061
5857
  const result = await sendSession({
5062
5858
  sessionId: record.id,
5063
5859
  message: prompt,
5064
5860
  permissionMode,
5861
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
5065
5862
  authCredentials: config.auth,
5066
5863
  authPolicy: globalFlags.authPolicy,
5067
5864
  outputFormatter,
5865
+ errorEmissionPolicy: {
5866
+ queueErrorAlreadyEmitted: outputPolicy.queueErrorAlreadyEmitted
5867
+ },
5868
+ suppressSdkConsoleErrors: outputPolicy.suppressSdkConsoleErrors,
5068
5869
  timeoutMs: globalFlags.timeout,
5069
5870
  ttlMs: globalFlags.ttl,
5070
5871
  verbose: globalFlags.verbose,
5071
5872
  waitForCompletion: flags.wait !== false
5072
5873
  });
5073
5874
  if ("queued" in result) {
5074
- printQueuedPromptByFormat(result, globalFlags.format);
5875
+ printQueuedPromptByFormat(result, outputPolicy.format);
5075
5876
  return;
5076
5877
  }
5077
5878
  applyPermissionExitCode(result);
@@ -5084,18 +5885,24 @@ async function handlePrompt(explicitAgentName, promptParts, flags, command, conf
5084
5885
  }
5085
5886
  async function handleExec(explicitAgentName, promptParts, flags, command, config) {
5086
5887
  const globalFlags = resolveGlobalFlags(command, config);
5888
+ const outputPolicy = resolveOutputPolicy(
5889
+ globalFlags.format,
5890
+ globalFlags.jsonStrict === true
5891
+ );
5087
5892
  const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
5088
5893
  const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
5089
- const outputFormatter = createOutputFormatter(globalFlags.format);
5894
+ const outputFormatter = createOutputFormatter(outputPolicy.format);
5090
5895
  const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
5091
5896
  const result = await runOnce({
5092
5897
  agentCommand: agent.agentCommand,
5093
5898
  cwd: agent.cwd,
5094
5899
  message: prompt,
5095
5900
  permissionMode,
5901
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
5096
5902
  authCredentials: config.auth,
5097
5903
  authPolicy: globalFlags.authPolicy,
5098
5904
  outputFormatter,
5905
+ suppressSdkConsoleErrors: outputPolicy.suppressSdkConsoleErrors,
5099
5906
  timeoutMs: globalFlags.timeout,
5100
5907
  verbose: globalFlags.verbose
5101
5908
  });
@@ -5196,6 +6003,7 @@ async function handleSetMode(explicitAgentName, modeId, flags, command, config)
5196
6003
  const result = await setSessionMode({
5197
6004
  sessionId: record.id,
5198
6005
  modeId,
6006
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
5199
6007
  authCredentials: config.auth,
5200
6008
  authPolicy: globalFlags.authPolicy,
5201
6009
  timeoutMs: globalFlags.timeout,
@@ -5222,6 +6030,7 @@ async function handleSetConfigOption(explicitAgentName, configId, value, flags,
5222
6030
  sessionId: record.id,
5223
6031
  configId,
5224
6032
  value,
6033
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
5225
6034
  authCredentials: config.auth,
5226
6035
  authPolicy: globalFlags.authPolicy,
5227
6036
  timeoutMs: globalFlags.timeout,
@@ -5281,12 +6090,18 @@ async function handleSessionsNew(explicitAgentName, flags, command, config) {
5281
6090
  cwd: agent.cwd,
5282
6091
  name: flags.name,
5283
6092
  permissionMode,
6093
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
5284
6094
  authCredentials: config.auth,
5285
6095
  authPolicy: globalFlags.authPolicy,
5286
6096
  timeoutMs: globalFlags.timeout,
5287
6097
  verbose: globalFlags.verbose
5288
6098
  });
5289
- printCreatedSessionBanner(created, agent.agentName, globalFlags.format);
6099
+ printCreatedSessionBanner(
6100
+ created,
6101
+ agent.agentName,
6102
+ globalFlags.format,
6103
+ globalFlags.jsonStrict
6104
+ );
5290
6105
  if (globalFlags.verbose) {
5291
6106
  const scope = flags.name ? `named session "${flags.name}"` : "cwd session";
5292
6107
  process.stderr.write(`[acpx] created ${scope}: ${created.id}
@@ -5294,6 +6109,31 @@ async function handleSessionsNew(explicitAgentName, flags, command, config) {
5294
6109
  }
5295
6110
  printNewSessionByFormat(created, replaced, globalFlags.format);
5296
6111
  }
6112
+ async function handleSessionsEnsure(explicitAgentName, flags, command, config) {
6113
+ const globalFlags = resolveGlobalFlags(command, config);
6114
+ const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
6115
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
6116
+ const result = await ensureSession({
6117
+ agentCommand: agent.agentCommand,
6118
+ cwd: agent.cwd,
6119
+ name: flags.name,
6120
+ permissionMode,
6121
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
6122
+ authCredentials: config.auth,
6123
+ authPolicy: globalFlags.authPolicy,
6124
+ timeoutMs: globalFlags.timeout,
6125
+ verbose: globalFlags.verbose
6126
+ });
6127
+ if (result.created) {
6128
+ printCreatedSessionBanner(
6129
+ result.record,
6130
+ agent.agentName,
6131
+ globalFlags.format,
6132
+ globalFlags.jsonStrict
6133
+ );
6134
+ }
6135
+ printEnsuredSessionByFormat(result.record, result.created, globalFlags.format);
6136
+ }
5297
6137
  function printSessionDetailsByFormat(record, format) {
5298
6138
  if (format === "json") {
5299
6139
  process.stdout.write(`${JSON.stringify(record)}
@@ -5555,7 +6395,7 @@ async function handleConfigInit(command, config) {
5555
6395
  `);
5556
6396
  }
5557
6397
  function registerSessionsCommand(parent, explicitAgentName, config) {
5558
- const sessionsCommand = parent.command("sessions").description("List, create, or close sessions for this agent");
6398
+ const sessionsCommand = parent.command("sessions").description("List, ensure, create, or close sessions for this agent");
5559
6399
  sessionsCommand.action(async function() {
5560
6400
  await handleSessionsList(explicitAgentName, this, config);
5561
6401
  });
@@ -5565,6 +6405,9 @@ function registerSessionsCommand(parent, explicitAgentName, config) {
5565
6405
  sessionsCommand.command("new").description("Create a fresh session for current cwd").option("--name <name>", "Session name", parseSessionName).action(async function(flags) {
5566
6406
  await handleSessionsNew(explicitAgentName, flags, this, config);
5567
6407
  });
6408
+ sessionsCommand.command("ensure").description("Ensure a session exists for current cwd or ancestor").option("--name <name>", "Session name", parseSessionName).action(async function(flags) {
6409
+ await handleSessionsEnsure(explicitAgentName, flags, this, config);
6410
+ });
5568
6411
  sessionsCommand.command("close").description("Close session for current cwd").argument("[name]", "Session name", parseSessionName).action(async function(name) {
5569
6412
  await handleSessionsClose(explicitAgentName, name, this, config);
5570
6413
  });
@@ -5685,14 +6528,14 @@ function detectAgentToken(argv) {
5685
6528
  hasAgentOverride = true;
5686
6529
  continue;
5687
6530
  }
5688
- if (token === "--cwd" || token === "--auth-policy" || token === "--format" || token === "--timeout" || token === "--ttl" || token === "--file") {
6531
+ if (token === "--cwd" || token === "--auth-policy" || token === "--non-interactive-permissions" || token === "--format" || token === "--timeout" || token === "--ttl" || token === "--file") {
5689
6532
  index += 1;
5690
6533
  continue;
5691
6534
  }
5692
- if (token.startsWith("--cwd=") || token.startsWith("--auth-policy=") || token.startsWith("--format=") || token.startsWith("--timeout=") || token.startsWith("--ttl=") || token.startsWith("--file=")) {
6535
+ if (token.startsWith("--cwd=") || token.startsWith("--auth-policy=") || token.startsWith("--non-interactive-permissions=") || token.startsWith("--format=") || token.startsWith("--json-strict=") || token.startsWith("--timeout=") || token.startsWith("--ttl=") || token.startsWith("--file=")) {
5693
6536
  continue;
5694
6537
  }
5695
- if (token === "--approve-all" || token === "--approve-reads" || token === "--deny-all" || token === "--verbose") {
6538
+ if (token === "--approve-all" || token === "--approve-reads" || token === "--deny-all" || token === "--json-strict" || token === "--verbose") {
5696
6539
  continue;
5697
6540
  }
5698
6541
  return { hasAgentOverride };
@@ -5722,15 +6565,103 @@ function detectInitialCwd(argv) {
5722
6565
  }
5723
6566
  return process.cwd();
5724
6567
  }
6568
+ function detectRequestedOutputFormat(argv, fallback) {
6569
+ let detectedFormat = fallback;
6570
+ for (let index = 0; index < argv.length; index += 1) {
6571
+ const token = argv[index];
6572
+ if (token === "--") {
6573
+ break;
6574
+ }
6575
+ if (token === "--json-strict" || token.startsWith("--json-strict=")) {
6576
+ return "json";
6577
+ }
6578
+ if (token === "--format") {
6579
+ const raw = argv[index + 1];
6580
+ if (raw && OUTPUT_FORMATS.includes(raw)) {
6581
+ detectedFormat = raw;
6582
+ }
6583
+ continue;
6584
+ }
6585
+ if (token.startsWith("--format=")) {
6586
+ const raw = token.slice("--format=".length).trim();
6587
+ if (OUTPUT_FORMATS.includes(raw)) {
6588
+ detectedFormat = raw;
6589
+ }
6590
+ }
6591
+ }
6592
+ return detectedFormat;
6593
+ }
6594
+ function detectJsonStrict(argv) {
6595
+ for (let index = 0; index < argv.length; index += 1) {
6596
+ const token = argv[index];
6597
+ if (token === "--") {
6598
+ break;
6599
+ }
6600
+ if (token === "--json-strict") {
6601
+ return true;
6602
+ }
6603
+ if (token.startsWith("--json-strict=")) {
6604
+ return true;
6605
+ }
6606
+ }
6607
+ return false;
6608
+ }
6609
+ function emitJsonErrorEvent(error) {
6610
+ const formatter = createOutputFormatter("json", {
6611
+ jsonContext: {
6612
+ sessionId: "unknown",
6613
+ stream: "control"
6614
+ }
6615
+ });
6616
+ formatter.onError(error);
6617
+ formatter.flush();
6618
+ }
6619
+ function isOutputAlreadyEmitted(error) {
6620
+ if (!error || typeof error !== "object") {
6621
+ return false;
6622
+ }
6623
+ return error.outputAlreadyEmitted === true;
6624
+ }
6625
+ function emitRequestedError(error, normalized, outputPolicy) {
6626
+ if (isOutputAlreadyEmitted(error)) {
6627
+ return;
6628
+ }
6629
+ if (outputPolicy.format === "json") {
6630
+ emitJsonErrorEvent(normalized);
6631
+ } else if (!outputPolicy.suppressNonJsonStderr) {
6632
+ process.stderr.write(`${normalized.message}
6633
+ `);
6634
+ }
6635
+ }
6636
+ async function runWithOutputPolicy(_outputPolicy, run) {
6637
+ return await run();
6638
+ }
5725
6639
  async function main(argv = process.argv) {
5726
6640
  await maybeHandleSkillflag(argv, {
5727
6641
  skillsRoot: findSkillsRoot(import.meta.url),
5728
6642
  includeBundledSkill: false
5729
6643
  });
5730
6644
  const config = await loadResolvedConfig(detectInitialCwd(argv.slice(2)));
6645
+ const requestedJsonStrict = detectJsonStrict(argv.slice(2));
6646
+ const requestedOutputFormat = detectRequestedOutputFormat(
6647
+ argv.slice(2),
6648
+ config.format
6649
+ );
6650
+ const requestedOutputPolicy = resolveOutputPolicy(
6651
+ requestedOutputFormat,
6652
+ requestedJsonStrict
6653
+ );
5731
6654
  const builtInAgents = listBuiltInAgents(config.agents);
5732
6655
  const program = new Command();
5733
6656
  program.name("acpx").description("Headless CLI client for the Agent Client Protocol").enablePositionalOptions().showHelpAfterError();
6657
+ if (requestedJsonStrict) {
6658
+ program.configureOutput({
6659
+ writeOut: () => {
6660
+ },
6661
+ writeErr: () => {
6662
+ }
6663
+ });
6664
+ }
5734
6665
  addGlobalFlags(program);
5735
6666
  for (const agentName of builtInAgents) {
5736
6667
  registerAgentCommand(program, agentName, config);
@@ -5742,6 +6673,11 @@ async function main(argv = process.argv) {
5742
6673
  }
5743
6674
  program.argument("[prompt...]", "Prompt text").action(async function(promptParts) {
5744
6675
  if (promptParts.length === 0 && process.stdin.isTTY) {
6676
+ if (requestedJsonStrict) {
6677
+ throw new InvalidArgumentError(
6678
+ "Prompt is required (pass as argument, --file, or pipe via stdin)"
6679
+ );
6680
+ }
5745
6681
  this.outputHelp();
5746
6682
  return;
5747
6683
  }
@@ -5762,6 +6698,7 @@ Examples:
5762
6698
  acpx codex -s backend "fix the API"
5763
6699
  acpx codex sessions
5764
6700
  acpx codex sessions new --name backend
6701
+ acpx codex sessions ensure --name backend
5765
6702
  acpx codex sessions close backend
5766
6703
  acpx codex status
5767
6704
  acpx config show
@@ -5774,33 +6711,33 @@ Examples:
5774
6711
  program.exitOverride((error) => {
5775
6712
  throw error;
5776
6713
  });
5777
- try {
5778
- await program.parseAsync(argv);
5779
- } catch (error) {
5780
- if (error instanceof CommanderError) {
5781
- if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
5782
- process.exit(EXIT_CODES.SUCCESS);
6714
+ await runWithOutputPolicy(requestedOutputPolicy, async () => {
6715
+ try {
6716
+ await program.parseAsync(argv);
6717
+ } catch (error) {
6718
+ if (error instanceof CommanderError) {
6719
+ if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
6720
+ process.exit(EXIT_CODES.SUCCESS);
6721
+ }
6722
+ const normalized2 = normalizeOutputError(error, {
6723
+ defaultCode: "USAGE",
6724
+ origin: "cli"
6725
+ });
6726
+ if (requestedOutputPolicy.format === "json") {
6727
+ emitRequestedError(error, normalized2, requestedOutputPolicy);
6728
+ }
6729
+ process.exit(exitCodeForOutputErrorCode(normalized2.code));
5783
6730
  }
5784
- process.exit(EXIT_CODES.USAGE);
5785
- }
5786
- if (error instanceof InterruptedError) {
5787
- process.exit(EXIT_CODES.INTERRUPTED);
5788
- }
5789
- if (error instanceof TimeoutError) {
5790
- process.stderr.write(`${error.message}
5791
- `);
5792
- process.exit(EXIT_CODES.TIMEOUT);
5793
- }
5794
- if (error instanceof NoSessionError) {
5795
- process.stderr.write(`${error.message}
5796
- `);
5797
- process.exit(EXIT_CODES.NO_SESSION);
6731
+ if (error instanceof InterruptedError) {
6732
+ process.exit(EXIT_CODES.INTERRUPTED);
6733
+ }
6734
+ const normalized = normalizeOutputError(error, {
6735
+ origin: "cli"
6736
+ });
6737
+ emitRequestedError(error, normalized, requestedOutputPolicy);
6738
+ process.exit(exitCodeForOutputErrorCode(normalized.code));
5798
6739
  }
5799
- const message = error instanceof Error ? error.message : String(error);
5800
- process.stderr.write(`${message}
5801
- `);
5802
- process.exit(EXIT_CODES.ERROR);
5803
- }
6740
+ });
5804
6741
  }
5805
6742
  function isCliEntrypoint(argv) {
5806
6743
  const entry = argv[1];