acpx 0.1.8 → 0.1.10

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/dist/cli.d.ts +1 -1
  2. package/dist/cli.js +471 -266
  3. package/package.json +1 -1
package/dist/cli.d.ts CHANGED
@@ -10,7 +10,7 @@ type SessionHistoryEntry = {
10
10
  type SessionRecord = {
11
11
  id: string;
12
12
  sessionId: string;
13
- runtimeSessionId?: string;
13
+ agentSessionId?: string;
14
14
  agentCommand: string;
15
15
  cwd: string;
16
16
  name?: string;
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import { findSkillsRoot, maybeHandleSkillflag } from "skillflag";
11
11
  // src/agent-registry.ts
12
12
  var AGENT_REGISTRY = {
13
13
  codex: "npx @zed-industries/codex-acp",
14
- claude: "npx @zed-industries/claude-agent-acp",
14
+ claude: "npx -y @zed-industries/claude-agent-acp",
15
15
  gemini: "gemini",
16
16
  opencode: "npx opencode-ai",
17
17
  pi: "npx pi-acp"
@@ -388,6 +388,117 @@ var PermissionPromptUnavailableError = class extends AcpxOperationalError {
388
388
  }
389
389
  };
390
390
 
391
+ // src/acp-error-shapes.ts
392
+ var RESOURCE_NOT_FOUND_ACP_CODES = /* @__PURE__ */ new Set([-32001, -32002]);
393
+ function asRecord(value) {
394
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
395
+ return void 0;
396
+ }
397
+ return value;
398
+ }
399
+ function toAcpErrorPayload(value) {
400
+ const record = asRecord(value);
401
+ if (!record) {
402
+ return void 0;
403
+ }
404
+ if (typeof record.code !== "number" || !Number.isFinite(record.code)) {
405
+ return void 0;
406
+ }
407
+ if (typeof record.message !== "string" || record.message.length === 0) {
408
+ return void 0;
409
+ }
410
+ return {
411
+ code: record.code,
412
+ message: record.message,
413
+ data: record.data
414
+ };
415
+ }
416
+ function extractAcpErrorInternal(value, depth) {
417
+ if (depth > 5) {
418
+ return void 0;
419
+ }
420
+ const direct = toAcpErrorPayload(value);
421
+ if (direct) {
422
+ return direct;
423
+ }
424
+ const record = asRecord(value);
425
+ if (!record) {
426
+ return void 0;
427
+ }
428
+ if ("error" in record) {
429
+ const nested = extractAcpErrorInternal(record.error, depth + 1);
430
+ if (nested) {
431
+ return nested;
432
+ }
433
+ }
434
+ if ("cause" in record) {
435
+ const nested = extractAcpErrorInternal(record.cause, depth + 1);
436
+ if (nested) {
437
+ return nested;
438
+ }
439
+ }
440
+ return void 0;
441
+ }
442
+ function formatUnknownErrorMessage(error) {
443
+ if (error instanceof Error) {
444
+ return error.message;
445
+ }
446
+ if (error && typeof error === "object") {
447
+ const maybeMessage = error.message;
448
+ if (typeof maybeMessage === "string" && maybeMessage.length > 0) {
449
+ return maybeMessage;
450
+ }
451
+ try {
452
+ return JSON.stringify(error);
453
+ } catch {
454
+ }
455
+ }
456
+ return String(error);
457
+ }
458
+ function isSessionNotFoundText(value) {
459
+ if (typeof value !== "string") {
460
+ return false;
461
+ }
462
+ const normalized = value.toLowerCase();
463
+ return normalized.includes("resource_not_found") || normalized.includes("resource not found") || normalized.includes("session not found") || normalized.includes("unknown session");
464
+ }
465
+ function hasSessionNotFoundHint(value, depth = 0) {
466
+ if (depth > 4) {
467
+ return false;
468
+ }
469
+ if (isSessionNotFoundText(value)) {
470
+ return true;
471
+ }
472
+ if (Array.isArray(value)) {
473
+ return value.some((entry) => hasSessionNotFoundHint(entry, depth + 1));
474
+ }
475
+ const record = asRecord(value);
476
+ if (!record) {
477
+ return false;
478
+ }
479
+ return Object.values(record).some(
480
+ (entry) => hasSessionNotFoundHint(entry, depth + 1)
481
+ );
482
+ }
483
+ function extractAcpError(error) {
484
+ return extractAcpErrorInternal(error, 0);
485
+ }
486
+ function isAcpResourceNotFoundError(error) {
487
+ const acp = extractAcpError(error);
488
+ if (acp && RESOURCE_NOT_FOUND_ACP_CODES.has(acp.code)) {
489
+ return true;
490
+ }
491
+ if (acp) {
492
+ if (isSessionNotFoundText(acp.message)) {
493
+ return true;
494
+ }
495
+ if (hasSessionNotFoundHint(acp.data)) {
496
+ return true;
497
+ }
498
+ }
499
+ return isSessionNotFoundText(formatUnknownErrorMessage(error));
500
+ }
501
+
391
502
  // src/types.ts
392
503
  var EXIT_CODES = {
393
504
  SUCCESS: 0,
@@ -412,9 +523,8 @@ var OUTPUT_ERROR_CODES = [
412
523
  var OUTPUT_ERROR_ORIGINS = ["cli", "runtime", "queue", "acp"];
413
524
 
414
525
  // src/error-normalization.ts
415
- var RESOURCE_NOT_FOUND_ACP_CODES = /* @__PURE__ */ new Set([-32001, -32002]);
416
526
  var AUTH_REQUIRED_ACP_CODES = /* @__PURE__ */ new Set([-32e3]);
417
- function asRecord(value) {
527
+ function asRecord2(value) {
418
528
  if (!value || typeof value !== "object" || Array.isArray(value)) {
419
529
  return void 0;
420
530
  }
@@ -437,7 +547,7 @@ function isAcpAuthRequiredPayload(acp) {
437
547
  if (isAuthRequiredMessage(acp.message)) {
438
548
  return true;
439
549
  }
440
- const data = asRecord(acp.data);
550
+ const data = asRecord2(acp.data);
441
551
  if (!data) {
442
552
  return false;
443
553
  }
@@ -461,7 +571,7 @@ function isOutputErrorOrigin(value) {
461
571
  return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
462
572
  }
463
573
  function readOutputErrorMeta(error) {
464
- const record = asRecord(error);
574
+ const record = asRecord2(error);
465
575
  if (!record) {
466
576
  return {};
467
577
  }
@@ -469,7 +579,7 @@ function readOutputErrorMeta(error) {
469
579
  const detailCode = typeof record.detailCode === "string" && record.detailCode.trim().length > 0 ? record.detailCode : void 0;
470
580
  const origin = isOutputErrorOrigin(record.origin) ? record.origin : void 0;
471
581
  const retryable = typeof record.retryable === "boolean" ? record.retryable : void 0;
472
- const acp = toAcpErrorPayload(record.acp);
582
+ const acp = toAcpErrorPayload2(record.acp);
473
583
  return {
474
584
  outputCode,
475
585
  detailCode,
@@ -478,8 +588,8 @@ function readOutputErrorMeta(error) {
478
588
  acp
479
589
  };
480
590
  }
481
- function toAcpErrorPayload(value) {
482
- const record = asRecord(value);
591
+ function toAcpErrorPayload2(value) {
592
+ const record = asRecord2(value);
483
593
  if (!record) {
484
594
  return void 0;
485
595
  }
@@ -495,32 +605,6 @@ function toAcpErrorPayload(value) {
495
605
  data: record.data
496
606
  };
497
607
  }
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
608
  function isTimeoutLike(error) {
525
609
  return error instanceof Error && error.name === "TimeoutError";
526
610
  }
@@ -531,7 +615,7 @@ function isUsageLike(error) {
531
615
  if (!(error instanceof Error)) {
532
616
  return false;
533
617
  }
534
- return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord(error)?.code === "commander.invalidArgument";
618
+ return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord2(error)?.code === "commander.invalidArgument";
535
619
  }
536
620
  function formatErrorMessage(error) {
537
621
  if (error instanceof Error) {
@@ -549,17 +633,6 @@ function formatErrorMessage(error) {
549
633
  }
550
634
  return String(error);
551
635
  }
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
636
  function mapErrorCode(error) {
564
637
  if (error instanceof PermissionPromptUnavailableError) {
565
638
  return "PERMISSION_PROMPT_UNAVAILABLE";
@@ -658,7 +731,7 @@ function toStatusLabel(status) {
658
731
  return "running";
659
732
  }
660
733
  }
661
- function asRecord2(value) {
734
+ function asRecord3(value) {
662
735
  if (!value || typeof value !== "object" || Array.isArray(value)) {
663
736
  return void 0;
664
737
  }
@@ -752,7 +825,7 @@ function summarizeToolInput(rawInput) {
752
825
  if (typeof rawInput === "string" || typeof rawInput === "number" || typeof rawInput === "boolean") {
753
826
  return toInline(String(rawInput));
754
827
  }
755
- const record = asRecord2(rawInput);
828
+ const record = asRecord3(rawInput);
756
829
  if (record) {
757
830
  const command = readFirstString(record, ["command", "cmd", "program"]);
758
831
  const args = readFirstStringArray(record, ["args", "arguments"]);
@@ -886,7 +959,7 @@ function extractOutputText(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
886
959
  }
887
960
  return dedupeStrings(parts).join("\n");
888
961
  }
889
- const record = asRecord2(value);
962
+ const record = asRecord3(value);
890
963
  if (!record) {
891
964
  return void 0;
892
965
  }
@@ -1715,14 +1788,9 @@ function classifyPermissionDecision(params, response) {
1715
1788
  return "denied";
1716
1789
  }
1717
1790
 
1718
- // src/runtime-session-id.ts
1719
- var RUNTIME_SESSION_ID_META_KEYS = [
1720
- "runtimeSessionId",
1721
- "providerSessionId",
1722
- "codexSessionId",
1723
- "claudeSessionId"
1724
- ];
1725
- function normalizeRuntimeSessionId(value) {
1791
+ // src/agent-session-id.ts
1792
+ var AGENT_SESSION_ID_META_KEYS = ["agentSessionId", "sessionId"];
1793
+ function normalizeAgentSessionId(value) {
1726
1794
  if (typeof value !== "string") {
1727
1795
  return void 0;
1728
1796
  }
@@ -1735,13 +1803,13 @@ function asMetaRecord(meta) {
1735
1803
  }
1736
1804
  return meta;
1737
1805
  }
1738
- function extractRuntimeSessionId(meta) {
1806
+ function extractAgentSessionId(meta) {
1739
1807
  const record = asMetaRecord(meta);
1740
1808
  if (!record) {
1741
1809
  return void 0;
1742
1810
  }
1743
- for (const key of RUNTIME_SESSION_ID_META_KEYS) {
1744
- const normalized = normalizeRuntimeSessionId(record[key]);
1811
+ for (const key of AGENT_SESSION_ID_META_KEYS) {
1812
+ const normalized = normalizeAgentSessionId(record[key]);
1745
1813
  if (normalized) {
1746
1814
  return normalized;
1747
1815
  }
@@ -2091,6 +2159,9 @@ var TerminalManager = class {
2091
2159
  var REPLAY_IDLE_MS = 80;
2092
2160
  var REPLAY_DRAIN_TIMEOUT_MS = 5e3;
2093
2161
  var DRAIN_POLL_INTERVAL_MS = 20;
2162
+ var AGENT_CLOSE_AFTER_STDIN_END_MS = 100;
2163
+ var AGENT_CLOSE_TERM_GRACE_MS = 1500;
2164
+ var AGENT_CLOSE_KILL_GRACE_MS = 1e3;
2094
2165
  function shouldSuppressSdkConsoleError(args) {
2095
2166
  if (args.length === 0) {
2096
2167
  return false;
@@ -2126,6 +2197,38 @@ function waitForSpawn2(child) {
2126
2197
  child.once("error", onError);
2127
2198
  });
2128
2199
  }
2200
+ function isChildProcessRunning(child) {
2201
+ return child.exitCode == null && child.signalCode == null;
2202
+ }
2203
+ function waitForChildExit(child, timeoutMs) {
2204
+ if (!isChildProcessRunning(child)) {
2205
+ return Promise.resolve(true);
2206
+ }
2207
+ return new Promise((resolve) => {
2208
+ let settled = false;
2209
+ const timer = setTimeout(
2210
+ () => {
2211
+ finish(false);
2212
+ },
2213
+ Math.max(0, timeoutMs)
2214
+ );
2215
+ const finish = (value) => {
2216
+ if (settled) {
2217
+ return;
2218
+ }
2219
+ settled = true;
2220
+ child.off("close", onExitLike);
2221
+ child.off("exit", onExitLike);
2222
+ clearTimeout(timer);
2223
+ resolve(value);
2224
+ };
2225
+ const onExitLike = () => {
2226
+ finish(true);
2227
+ };
2228
+ child.once("close", onExitLike);
2229
+ child.once("exit", onExitLike);
2230
+ });
2231
+ }
2129
2232
  function splitCommandLine(value) {
2130
2233
  const parts = [];
2131
2234
  let current = "";
@@ -2409,7 +2512,7 @@ var AcpClient = class {
2409
2512
  });
2410
2513
  return {
2411
2514
  sessionId: result.sessionId,
2412
- runtimeSessionId: extractRuntimeSessionId(result._meta)
2515
+ agentSessionId: extractAgentSessionId(result._meta)
2413
2516
  };
2414
2517
  }
2415
2518
  async loadSession(sessionId, cwd = this.options.cwd) {
@@ -2435,7 +2538,7 @@ var AcpClient = class {
2435
2538
  this.suppressSessionUpdates = previousSuppression;
2436
2539
  }
2437
2540
  return {
2438
- runtimeSessionId: extractRuntimeSessionId(response?._meta)
2541
+ agentSessionId: extractAgentSessionId(response?._meta)
2439
2542
  };
2440
2543
  }
2441
2544
  async prompt(sessionId, text) {
@@ -2547,8 +2650,8 @@ var AcpClient = class {
2547
2650
  async close() {
2548
2651
  this.closing = true;
2549
2652
  await this.terminalManager.shutdown();
2550
- if (this.agent && !this.agent.killed) {
2551
- this.agent.kill();
2653
+ if (this.agent) {
2654
+ await this.terminateAgentProcess(this.agent);
2552
2655
  }
2553
2656
  this.sessionUpdateChain = Promise.resolve();
2554
2657
  this.observedSessionUpdates = 0;
@@ -2560,6 +2663,44 @@ var AcpClient = class {
2560
2663
  this.connection = void 0;
2561
2664
  this.agent = void 0;
2562
2665
  }
2666
+ async terminateAgentProcess(child) {
2667
+ if (!child.stdin.destroyed) {
2668
+ try {
2669
+ child.stdin.end();
2670
+ } catch {
2671
+ }
2672
+ }
2673
+ let exited = await waitForChildExit(child, AGENT_CLOSE_AFTER_STDIN_END_MS);
2674
+ if (!exited && isChildProcessRunning(child)) {
2675
+ try {
2676
+ child.kill("SIGTERM");
2677
+ } catch {
2678
+ }
2679
+ exited = await waitForChildExit(child, AGENT_CLOSE_TERM_GRACE_MS);
2680
+ }
2681
+ if (!exited && isChildProcessRunning(child)) {
2682
+ this.log(
2683
+ `agent did not exit after ${AGENT_CLOSE_TERM_GRACE_MS}ms; forcing SIGKILL`
2684
+ );
2685
+ try {
2686
+ child.kill("SIGKILL");
2687
+ } catch {
2688
+ }
2689
+ exited = await waitForChildExit(child, AGENT_CLOSE_KILL_GRACE_MS);
2690
+ }
2691
+ if (!child.stdin.destroyed) {
2692
+ child.stdin.destroy();
2693
+ }
2694
+ if (!child.stdout.destroyed) {
2695
+ child.stdout.destroy();
2696
+ }
2697
+ if (!child.stderr.destroyed) {
2698
+ child.stderr.destroy();
2699
+ }
2700
+ if (!exited) {
2701
+ child.unref();
2702
+ }
2703
+ }
2563
2704
  getConnection() {
2564
2705
  if (!this.connection) {
2565
2706
  throw new Error("ACP client not started");
@@ -2884,7 +3025,7 @@ import os2 from "os";
2884
3025
  import path4 from "path";
2885
3026
 
2886
3027
  // src/queue-messages.ts
2887
- function asRecord3(value) {
3028
+ function asRecord4(value) {
2888
3029
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2889
3030
  return void 0;
2890
3031
  }
@@ -2903,7 +3044,7 @@ function isOutputErrorOrigin2(value) {
2903
3044
  return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
2904
3045
  }
2905
3046
  function parseAcpError(value) {
2906
- const record = asRecord3(value);
3047
+ const record = asRecord4(value);
2907
3048
  if (!record) {
2908
3049
  return void 0;
2909
3050
  }
@@ -2920,7 +3061,7 @@ function parseAcpError(value) {
2920
3061
  };
2921
3062
  }
2922
3063
  function parseQueueRequest(raw) {
2923
- const request = asRecord3(raw);
3064
+ const request = asRecord4(raw);
2924
3065
  if (!request) {
2925
3066
  return null;
2926
3067
  }
@@ -2978,15 +3119,15 @@ function parseQueueRequest(raw) {
2978
3119
  return null;
2979
3120
  }
2980
3121
  function parseSessionSendResult(raw) {
2981
- const result = asRecord3(raw);
3122
+ const result = asRecord4(raw);
2982
3123
  if (!result) {
2983
3124
  return null;
2984
3125
  }
2985
3126
  if (typeof result.stopReason !== "string" || typeof result.sessionId !== "string" || typeof result.resumed !== "boolean") {
2986
3127
  return null;
2987
3128
  }
2988
- const permissionStats = asRecord3(result.permissionStats);
2989
- const record = asRecord3(result.record);
3129
+ const permissionStats = asRecord4(result.permissionStats);
3130
+ const record = asRecord4(result.record);
2990
3131
  if (!permissionStats || !record) {
2991
3132
  return null;
2992
3133
  }
@@ -3001,7 +3142,7 @@ function parseSessionSendResult(raw) {
3001
3142
  return result;
3002
3143
  }
3003
3144
  function parseQueueOwnerMessage(raw) {
3004
- const message = asRecord3(raw);
3145
+ const message = asRecord4(raw);
3005
3146
  if (!message || typeof message.type !== "string") {
3006
3147
  return null;
3007
3148
  }
@@ -3026,7 +3167,7 @@ function parseQueueOwnerMessage(raw) {
3026
3167
  };
3027
3168
  }
3028
3169
  if (message.type === "client_operation") {
3029
- const operation = asRecord3(message.operation);
3170
+ const operation = asRecord4(message.operation);
3030
3171
  if (!operation || typeof operation.method !== "string" || typeof operation.status !== "string" || typeof operation.summary !== "string" || typeof operation.timestamp !== "string") {
3031
3172
  return null;
3032
3173
  }
@@ -3081,7 +3222,7 @@ function parseQueueOwnerMessage(raw) {
3081
3222
  };
3082
3223
  }
3083
3224
  if (message.type === "set_config_option_result") {
3084
- const response = asRecord3(message.response);
3225
+ const response = asRecord4(message.response);
3085
3226
  if (!response || !Array.isArray(response.configOptions)) {
3086
3227
  return null;
3087
3228
  }
@@ -4244,7 +4385,7 @@ function parseSessionRecord(raw) {
4244
4385
  return null;
4245
4386
  }
4246
4387
  const record = raw;
4247
- const runtimeSessionId = normalizeRuntimeSessionId(record.runtimeSessionId);
4388
+ const agentSessionId = normalizeAgentSessionId(record.agentSessionId);
4248
4389
  const name = record.name == null ? void 0 : typeof record.name === "string" && record.name.trim().length > 0 ? record.name.trim() : null;
4249
4390
  const pid = record.pid == null ? void 0 : Number.isInteger(record.pid) && record.pid > 0 ? record.pid : null;
4250
4391
  const closed = record.closed == null ? false : typeof record.closed === "boolean" ? record.closed : null;
@@ -4267,7 +4408,7 @@ function parseSessionRecord(raw) {
4267
4408
  ...record,
4268
4409
  id: record.id,
4269
4410
  sessionId: record.sessionId,
4270
- runtimeSessionId,
4411
+ agentSessionId,
4271
4412
  agentCommand: record.agentCommand,
4272
4413
  cwd: record.cwd,
4273
4414
  name,
@@ -4445,11 +4586,196 @@ async function findSessionByDirectoryWalk(options) {
4445
4586
  }
4446
4587
  }
4447
4588
 
4589
+ // src/session-runtime-history.ts
4590
+ var SESSION_HISTORY_MAX_ENTRIES = 500;
4591
+ var SESSION_HISTORY_PREVIEW_CHARS = 220;
4592
+ function collapseWhitespace2(value) {
4593
+ return value.replace(/\s+/g, " ").trim();
4594
+ }
4595
+ function toPreviewText(value) {
4596
+ const collapsed = collapseWhitespace2(value);
4597
+ if (collapsed.length <= SESSION_HISTORY_PREVIEW_CHARS) {
4598
+ return collapsed;
4599
+ }
4600
+ if (SESSION_HISTORY_PREVIEW_CHARS <= 3) {
4601
+ return collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS);
4602
+ }
4603
+ return `${collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS - 3)}...`;
4604
+ }
4605
+ function textFromContent(content) {
4606
+ if (content.type === "text") {
4607
+ return content.text;
4608
+ }
4609
+ if (content.type === "resource_link") {
4610
+ return content.title ?? content.name ?? content.uri;
4611
+ }
4612
+ if (content.type === "resource") {
4613
+ if ("text" in content.resource && typeof content.resource.text === "string") {
4614
+ return content.resource.text;
4615
+ }
4616
+ return content.resource.uri;
4617
+ }
4618
+ return void 0;
4619
+ }
4620
+ function toHistoryEntryFromUpdate(notification) {
4621
+ const update = notification.update;
4622
+ if (update.sessionUpdate !== "user_message_chunk" && update.sessionUpdate !== "agent_message_chunk") {
4623
+ return void 0;
4624
+ }
4625
+ const text = textFromContent(update.content);
4626
+ if (!text) {
4627
+ return void 0;
4628
+ }
4629
+ const textPreview = toPreviewText(text);
4630
+ if (!textPreview) {
4631
+ return void 0;
4632
+ }
4633
+ return {
4634
+ role: update.sessionUpdate === "user_message_chunk" ? "user" : "assistant",
4635
+ timestamp: isoNow2(),
4636
+ textPreview
4637
+ };
4638
+ }
4639
+ function appendHistoryEntries(current, entries) {
4640
+ const base = current ? [...current] : [];
4641
+ for (const entry of entries) {
4642
+ if (!entry.textPreview.trim()) {
4643
+ continue;
4644
+ }
4645
+ base.push(entry);
4646
+ }
4647
+ if (base.length <= SESSION_HISTORY_MAX_ENTRIES) {
4648
+ return base;
4649
+ }
4650
+ return base.slice(base.length - SESSION_HISTORY_MAX_ENTRIES);
4651
+ }
4652
+
4653
+ // src/session-runtime-lifecycle.ts
4654
+ function applyLifecycleSnapshotToRecord(record, snapshot) {
4655
+ record.pid = snapshot.pid;
4656
+ record.agentStartedAt = snapshot.startedAt;
4657
+ if (snapshot.lastExit) {
4658
+ record.lastAgentExitCode = snapshot.lastExit.exitCode;
4659
+ record.lastAgentExitSignal = snapshot.lastExit.signal;
4660
+ record.lastAgentExitAt = snapshot.lastExit.exitedAt;
4661
+ record.lastAgentDisconnectReason = snapshot.lastExit.reason;
4662
+ return;
4663
+ }
4664
+ record.lastAgentExitCode = void 0;
4665
+ record.lastAgentExitSignal = void 0;
4666
+ record.lastAgentExitAt = void 0;
4667
+ record.lastAgentDisconnectReason = void 0;
4668
+ }
4669
+ function reconcileAgentSessionId(record, agentSessionId) {
4670
+ const normalized = normalizeAgentSessionId(agentSessionId);
4671
+ if (!normalized) {
4672
+ return;
4673
+ }
4674
+ record.agentSessionId = normalized;
4675
+ }
4676
+
4677
+ // src/session-runtime-reconnect.ts
4678
+ function loadSessionCandidates(record) {
4679
+ const candidates = [normalizeAgentSessionId(record.agentSessionId), record.sessionId];
4680
+ const unique = [];
4681
+ for (const candidate of candidates) {
4682
+ if (!candidate || unique.includes(candidate)) {
4683
+ continue;
4684
+ }
4685
+ unique.push(candidate);
4686
+ }
4687
+ return unique;
4688
+ }
4689
+ async function connectAndLoadSession(options) {
4690
+ const record = options.record;
4691
+ const client = options.client;
4692
+ const storedProcessAlive = isProcessAlive(record.pid);
4693
+ const shouldReconnect = Boolean(record.pid) && !storedProcessAlive;
4694
+ if (options.verbose) {
4695
+ if (storedProcessAlive) {
4696
+ process.stderr.write(
4697
+ `[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession
4698
+ `
4699
+ );
4700
+ } else if (shouldReconnect) {
4701
+ process.stderr.write(
4702
+ `[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session/load
4703
+ `
4704
+ );
4705
+ }
4706
+ }
4707
+ await options.withTimeout(client.start(), options.timeoutMs);
4708
+ options.onClientAvailable?.(options.activeController);
4709
+ applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
4710
+ record.closed = false;
4711
+ record.closedAt = void 0;
4712
+ options.onConnectedRecord?.(record);
4713
+ await writeSessionRecord(record);
4714
+ let resumed = false;
4715
+ let loadError;
4716
+ let sessionId = record.sessionId;
4717
+ if (client.supportsLoadSession()) {
4718
+ const candidates = loadSessionCandidates(record);
4719
+ for (const candidate of candidates) {
4720
+ if (options.verbose && candidates.length > 1) {
4721
+ process.stderr.write(`[acpx] attempting session/load with ${candidate}
4722
+ `);
4723
+ }
4724
+ try {
4725
+ const loadResult = await options.withTimeout(
4726
+ client.loadSessionWithOptions(candidate, record.cwd, {
4727
+ suppressReplayUpdates: true
4728
+ }),
4729
+ options.timeoutMs
4730
+ );
4731
+ reconcileAgentSessionId(record, loadResult.agentSessionId);
4732
+ resumed = true;
4733
+ sessionId = candidate;
4734
+ loadError = void 0;
4735
+ break;
4736
+ } catch (error) {
4737
+ loadError = formatErrorMessage(error);
4738
+ if (!options.shouldFallbackToNewSession(error)) {
4739
+ throw error;
4740
+ }
4741
+ if (options.verbose) {
4742
+ process.stderr.write(
4743
+ `[acpx] session/load failed for ${candidate}: ${loadError}
4744
+ `
4745
+ );
4746
+ }
4747
+ }
4748
+ }
4749
+ if (!resumed) {
4750
+ const createdSession = await options.withTimeout(
4751
+ client.createSession(record.cwd),
4752
+ options.timeoutMs
4753
+ );
4754
+ sessionId = createdSession.sessionId;
4755
+ record.sessionId = sessionId;
4756
+ reconcileAgentSessionId(record, createdSession.agentSessionId);
4757
+ }
4758
+ } else {
4759
+ const createdSession = await options.withTimeout(
4760
+ client.createSession(record.cwd),
4761
+ options.timeoutMs
4762
+ );
4763
+ sessionId = createdSession.sessionId;
4764
+ record.sessionId = sessionId;
4765
+ reconcileAgentSessionId(record, createdSession.agentSessionId);
4766
+ }
4767
+ options.onSessionIdResolved?.(sessionId);
4768
+ return {
4769
+ sessionId,
4770
+ agentSessionId: record.agentSessionId,
4771
+ resumed,
4772
+ loadError
4773
+ };
4774
+ }
4775
+
4448
4776
  // src/session-runtime.ts
4449
4777
  var DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
4450
4778
  var INTERRUPT_CANCEL_WAIT_MS = 2500;
4451
- var SESSION_HISTORY_MAX_ENTRIES = 500;
4452
- var SESSION_HISTORY_PREVIEW_CHARS = 220;
4453
4779
  var TimeoutError = class extends Error {
4454
4780
  constructor(timeoutMs) {
4455
4781
  super(`Timed out after ${timeoutMs}ms`);
@@ -4585,162 +4911,12 @@ function normalizeQueueOwnerTtlMs(ttlMs) {
4585
4911
  }
4586
4912
  return Math.round(ttlMs);
4587
4913
  }
4588
- function collapseWhitespace2(value) {
4589
- return value.replace(/\s+/g, " ").trim();
4590
- }
4591
- function toPreviewText(value) {
4592
- const collapsed = collapseWhitespace2(value);
4593
- if (collapsed.length <= SESSION_HISTORY_PREVIEW_CHARS) {
4594
- return collapsed;
4595
- }
4596
- if (SESSION_HISTORY_PREVIEW_CHARS <= 3) {
4597
- return collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS);
4598
- }
4599
- return `${collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS - 3)}...`;
4600
- }
4601
- function textFromContent(content) {
4602
- if (content.type === "text") {
4603
- return content.text;
4604
- }
4605
- if (content.type === "resource_link") {
4606
- return content.title ?? content.name ?? content.uri;
4607
- }
4608
- if (content.type === "resource") {
4609
- if ("text" in content.resource && typeof content.resource.text === "string") {
4610
- return content.resource.text;
4611
- }
4612
- return content.resource.uri;
4613
- }
4614
- return void 0;
4615
- }
4616
- function toHistoryEntryFromUpdate(notification) {
4617
- const update = notification.update;
4618
- if (update.sessionUpdate !== "user_message_chunk" && update.sessionUpdate !== "agent_message_chunk") {
4619
- return void 0;
4620
- }
4621
- const text = textFromContent(update.content);
4622
- if (!text) {
4623
- return void 0;
4624
- }
4625
- const textPreview = toPreviewText(text);
4626
- if (!textPreview) {
4627
- return void 0;
4628
- }
4629
- return {
4630
- role: update.sessionUpdate === "user_message_chunk" ? "user" : "assistant",
4631
- timestamp: isoNow2(),
4632
- textPreview
4633
- };
4634
- }
4635
- function appendHistoryEntries(current, entries) {
4636
- const base = current ? [...current] : [];
4637
- for (const entry of entries) {
4638
- if (!entry.textPreview.trim()) {
4639
- continue;
4640
- }
4641
- base.push(entry);
4642
- }
4643
- if (base.length <= SESSION_HISTORY_MAX_ENTRIES) {
4644
- return base;
4645
- }
4646
- return base.slice(base.length - SESSION_HISTORY_MAX_ENTRIES);
4647
- }
4648
- function applyLifecycleSnapshotToRecord(record, snapshot) {
4649
- record.pid = snapshot.pid;
4650
- record.agentStartedAt = snapshot.startedAt;
4651
- if (snapshot.lastExit) {
4652
- record.lastAgentExitCode = snapshot.lastExit.exitCode;
4653
- record.lastAgentExitSignal = snapshot.lastExit.signal;
4654
- record.lastAgentExitAt = snapshot.lastExit.exitedAt;
4655
- record.lastAgentDisconnectReason = snapshot.lastExit.reason;
4656
- return;
4657
- }
4658
- record.lastAgentExitCode = void 0;
4659
- record.lastAgentExitSignal = void 0;
4660
- record.lastAgentExitAt = void 0;
4661
- record.lastAgentDisconnectReason = void 0;
4662
- }
4663
- function reconcileRuntimeSessionId(record, runtimeSessionId) {
4664
- const normalized = normalizeRuntimeSessionId(runtimeSessionId);
4665
- if (!normalized) {
4666
- return;
4667
- }
4668
- record.runtimeSessionId = normalized;
4669
- }
4670
4914
  function shouldFallbackToNewSession(error) {
4671
4915
  if (error instanceof TimeoutError || error instanceof InterruptedError) {
4672
4916
  return false;
4673
4917
  }
4674
4918
  return isAcpResourceNotFoundError(error);
4675
4919
  }
4676
- async function connectAndLoadSession(options) {
4677
- const record = options.record;
4678
- const client = options.client;
4679
- const storedProcessAlive = isProcessAlive(record.pid);
4680
- const shouldReconnect = Boolean(record.pid) && !storedProcessAlive;
4681
- if (options.verbose) {
4682
- if (storedProcessAlive) {
4683
- process.stderr.write(
4684
- `[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession
4685
- `
4686
- );
4687
- } else if (shouldReconnect) {
4688
- process.stderr.write(
4689
- `[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session/load
4690
- `
4691
- );
4692
- }
4693
- }
4694
- await withTimeout(client.start(), options.timeoutMs);
4695
- options.onClientAvailable?.(options.activeController);
4696
- applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
4697
- record.closed = false;
4698
- record.closedAt = void 0;
4699
- options.onConnectedRecord?.(record);
4700
- await writeSessionRecord(record);
4701
- let resumed = false;
4702
- let loadError;
4703
- let sessionId = record.sessionId;
4704
- if (client.supportsLoadSession()) {
4705
- try {
4706
- const loadResult = await withTimeout(
4707
- client.loadSessionWithOptions(record.sessionId, record.cwd, {
4708
- suppressReplayUpdates: true
4709
- }),
4710
- options.timeoutMs
4711
- );
4712
- reconcileRuntimeSessionId(record, loadResult.runtimeSessionId);
4713
- resumed = true;
4714
- } catch (error) {
4715
- loadError = formatErrorMessage(error);
4716
- if (!shouldFallbackToNewSession(error)) {
4717
- throw error;
4718
- }
4719
- const createdSession = await withTimeout(
4720
- client.createSession(record.cwd),
4721
- options.timeoutMs
4722
- );
4723
- sessionId = createdSession.sessionId;
4724
- record.sessionId = sessionId;
4725
- reconcileRuntimeSessionId(record, createdSession.runtimeSessionId);
4726
- }
4727
- } else {
4728
- const createdSession = await withTimeout(
4729
- client.createSession(record.cwd),
4730
- options.timeoutMs
4731
- );
4732
- sessionId = createdSession.sessionId;
4733
- record.sessionId = sessionId;
4734
- reconcileRuntimeSessionId(record, createdSession.runtimeSessionId);
4735
- }
4736
- options.onSessionIdResolved?.(sessionId);
4737
- return {
4738
- sessionId,
4739
- runtimeSessionId: record.runtimeSessionId,
4740
- resumed,
4741
- loadError
4742
- };
4743
- }
4744
4920
  async function runQueuedTask(sessionRecordId, task, options) {
4745
4921
  const outputFormatter = task.waitForCompletion ? new QueueTaskOutputFormatter(task) : DISCARD_OUTPUT_FORMATTER;
4746
4922
  try {
@@ -4847,6 +5023,8 @@ async function runSessionPrompt(options) {
4847
5023
  timeoutMs: options.timeoutMs,
4848
5024
  verbose: options.verbose,
4849
5025
  activeController,
5026
+ withTimeout,
5027
+ shouldFallbackToNewSession,
4850
5028
  onClientAvailable: (controller) => {
4851
5029
  options.onClientAvailable?.(controller);
4852
5030
  notifiedClientAvailable = true;
@@ -4981,6 +5159,8 @@ async function withConnectedSession(options) {
4981
5159
  timeoutMs: options.timeoutMs,
4982
5160
  verbose: options.verbose,
4983
5161
  activeController,
5162
+ withTimeout,
5163
+ shouldFallbackToNewSession,
4984
5164
  onClientAvailable: (controller) => {
4985
5165
  options.onClientAvailable?.(controller);
4986
5166
  notifiedClientAvailable = true;
@@ -5139,7 +5319,7 @@ async function createSession(options) {
5139
5319
  const record = {
5140
5320
  id: sessionId,
5141
5321
  sessionId,
5142
- runtimeSessionId: createdSession.runtimeSessionId,
5322
+ agentSessionId: createdSession.agentSessionId,
5143
5323
  agentCommand: options.agentCommand,
5144
5324
  cwd: absolutePath(options.cwd),
5145
5325
  name: normalizeName(options.name),
@@ -5717,8 +5897,22 @@ function resolveAgentInvocation(explicitAgentName, globalFlags, config) {
5717
5897
  }
5718
5898
  function printSessionsByFormat(sessions, format) {
5719
5899
  if (format === "json") {
5720
- process.stdout.write(`${JSON.stringify(sessions)}
5721
- `);
5900
+ process.stdout.write(
5901
+ `${JSON.stringify(
5902
+ sessions.map((session) => {
5903
+ const { id, sessionId, agentSessionId, ...rest } = session;
5904
+ return {
5905
+ ...rest,
5906
+ ...canonicalSessionIdentity({
5907
+ id,
5908
+ sessionId,
5909
+ agentSessionId
5910
+ })
5911
+ };
5912
+ })
5913
+ )}
5914
+ `
5915
+ );
5722
5916
  return;
5723
5917
  }
5724
5918
  if (format === "quiet") {
@@ -5746,8 +5940,7 @@ function printClosedSessionByFormat(record, format) {
5746
5940
  process.stdout.write(
5747
5941
  `${JSON.stringify({
5748
5942
  type: "session_closed",
5749
- id: record.id,
5750
- sessionId: record.sessionId,
5943
+ ...canonicalSessionIdentity(record),
5751
5944
  name: record.name
5752
5945
  })}
5753
5946
  `
@@ -5760,23 +5953,22 @@ function printClosedSessionByFormat(record, format) {
5760
5953
  process.stdout.write(`${record.id}
5761
5954
  `);
5762
5955
  }
5763
- function runtimeSessionIdPayload(runtimeSessionId) {
5764
- const normalized = normalizeRuntimeSessionId(runtimeSessionId);
5765
- if (!normalized) {
5766
- return {};
5767
- }
5768
- return { runtimeSessionId: normalized };
5956
+ function canonicalSessionIdentity(record) {
5957
+ const normalizedAgentSessionId = normalizeAgentSessionId(record.agentSessionId);
5958
+ return {
5959
+ acpxRecordId: record.id,
5960
+ acpxSessionId: record.sessionId,
5961
+ ...normalizedAgentSessionId ? { agentSessionId: normalizedAgentSessionId } : {}
5962
+ };
5769
5963
  }
5770
5964
  function printNewSessionByFormat(record, replaced, format) {
5771
5965
  if (format === "json") {
5772
5966
  process.stdout.write(
5773
5967
  `${JSON.stringify({
5774
5968
  type: "session_created",
5775
- id: record.id,
5776
- sessionId: record.sessionId,
5969
+ ...canonicalSessionIdentity(record),
5777
5970
  name: record.name,
5778
- replacedSessionId: replaced?.id,
5779
- ...runtimeSessionIdPayload(record.runtimeSessionId)
5971
+ ...replaced ? { replacedAcpxRecordId: replaced.id } : {}
5780
5972
  })}
5781
5973
  `
5782
5974
  );
@@ -5800,11 +5992,9 @@ function printEnsuredSessionByFormat(record, created, format) {
5800
5992
  process.stdout.write(
5801
5993
  `${JSON.stringify({
5802
5994
  type: "session_ensured",
5803
- id: record.id,
5804
- sessionId: record.sessionId,
5995
+ ...canonicalSessionIdentity(record),
5805
5996
  name: record.name,
5806
- created,
5807
- ...runtimeSessionIdPayload(record.runtimeSessionId)
5997
+ created
5808
5998
  })}
5809
5999
  `
5810
6000
  );
@@ -5824,7 +6014,7 @@ function printQueuedPromptByFormat(result, format) {
5824
6014
  process.stdout.write(
5825
6015
  `${JSON.stringify({
5826
6016
  type: "queued",
5827
- sessionId: result.sessionId,
6017
+ acpxRecordId: result.sessionId,
5828
6018
  requestId: result.requestId
5829
6019
  })}
5830
6020
  `
@@ -5981,8 +6171,13 @@ async function handleExec(explicitAgentName, promptParts, flags, command, config
5981
6171
  }
5982
6172
  function printCancelResultByFormat(result, format) {
5983
6173
  if (format === "json") {
5984
- process.stdout.write(`${JSON.stringify(result)}
5985
- `);
6174
+ process.stdout.write(
6175
+ `${JSON.stringify({
6176
+ acpxRecordId: result.sessionId || null,
6177
+ cancelled: result.cancelled
6178
+ })}
6179
+ `
6180
+ );
5986
6181
  return;
5987
6182
  }
5988
6183
  if (result.cancelled) {
@@ -5995,7 +6190,7 @@ function printSetModeResultByFormat(modeId, result, format) {
5995
6190
  if (format === "json") {
5996
6191
  process.stdout.write(
5997
6192
  `${JSON.stringify({
5998
- sessionId: result.record.id,
6193
+ ...canonicalSessionIdentity(result.record),
5999
6194
  modeId,
6000
6195
  resumed: result.resumed
6001
6196
  })}
@@ -6015,7 +6210,7 @@ function printSetConfigOptionResultByFormat(configId, value, result, format) {
6015
6210
  if (format === "json") {
6016
6211
  process.stdout.write(
6017
6212
  `${JSON.stringify({
6018
- sessionId: result.record.id,
6213
+ ...canonicalSessionIdentity(result.record),
6019
6214
  configId,
6020
6215
  value,
6021
6216
  resumed: result.resumed,
@@ -6207,8 +6402,18 @@ async function handleSessionsEnsure(explicitAgentName, flags, command, config) {
6207
6402
  }
6208
6403
  function printSessionDetailsByFormat(record, format) {
6209
6404
  if (format === "json") {
6210
- process.stdout.write(`${JSON.stringify(record)}
6211
- `);
6405
+ const { id, sessionId, agentSessionId, ...rest } = record;
6406
+ process.stdout.write(
6407
+ `${JSON.stringify({
6408
+ ...rest,
6409
+ ...canonicalSessionIdentity({
6410
+ id,
6411
+ sessionId,
6412
+ agentSessionId
6413
+ })
6414
+ })}
6415
+ `
6416
+ );
6212
6417
  return;
6213
6418
  }
6214
6419
  if (format === "quiet") {
@@ -6216,11 +6421,11 @@ function printSessionDetailsByFormat(record, format) {
6216
6421
  `);
6217
6422
  return;
6218
6423
  }
6219
- process.stdout.write(`id: ${record.id}
6424
+ process.stdout.write(`acpxRecordId: ${record.id}
6220
6425
  `);
6221
- process.stdout.write(`sessionId: ${record.sessionId}
6426
+ process.stdout.write(`acpxSessionId: ${record.sessionId}
6222
6427
  `);
6223
- process.stdout.write(`runtimeSessionId: ${record.runtimeSessionId ?? "-"}
6428
+ process.stdout.write(`agentSessionId: ${record.agentSessionId ?? "-"}
6224
6429
  `);
6225
6430
  process.stdout.write(`agent: ${record.agentCommand}
6226
6431
  `);
@@ -6261,8 +6466,7 @@ function printSessionHistoryByFormat(record, limit, format) {
6261
6466
  if (format === "json") {
6262
6467
  process.stdout.write(
6263
6468
  `${JSON.stringify({
6264
- id: record.id,
6265
- sessionId: record.sessionId,
6469
+ ...canonicalSessionIdentity(record),
6266
6470
  limit,
6267
6471
  count: visible.length,
6268
6472
  entries: visible
@@ -6348,7 +6552,9 @@ async function handleStatus(explicitAgentName, flags, command, config) {
6348
6552
  });
6349
6553
  if (!record) {
6350
6554
  const payload2 = {
6351
- sessionId: null,
6555
+ acpxRecordId: null,
6556
+ acpxSessionId: null,
6557
+ agentSessionId: null,
6352
6558
  agentCommand: agent.agentCommand,
6353
6559
  pid: null,
6354
6560
  status: "no-session",
@@ -6382,15 +6588,14 @@ async function handleStatus(explicitAgentName, flags, command, config) {
6382
6588
  }
6383
6589
  const running = isProcessAlive(record.pid);
6384
6590
  const payload = {
6385
- sessionId: record.id,
6591
+ ...canonicalSessionIdentity(record),
6386
6592
  agentCommand: record.agentCommand,
6387
6593
  pid: record.pid ?? null,
6388
6594
  status: running ? "running" : "dead",
6389
6595
  uptime: running ? formatUptime(record.agentStartedAt) ?? null : null,
6390
6596
  lastPromptTime: record.lastPromptAt ?? null,
6391
6597
  exitCode: running ? null : record.lastAgentExitCode ?? null,
6392
- signal: running ? null : record.lastAgentExitSignal ?? null,
6393
- ...runtimeSessionIdPayload(record.runtimeSessionId)
6598
+ signal: running ? null : record.lastAgentExitSignal ?? null
6394
6599
  };
6395
6600
  if (globalFlags.format === "json") {
6396
6601
  process.stdout.write(`${JSON.stringify(payload)}
@@ -6402,12 +6607,12 @@ async function handleStatus(explicitAgentName, flags, command, config) {
6402
6607
  `);
6403
6608
  return;
6404
6609
  }
6405
- process.stdout.write(`session: ${payload.sessionId}
6610
+ process.stdout.write(`acpxRecordId: ${payload.acpxRecordId}
6406
6611
  `);
6407
- if ("runtimeSessionId" in payload) {
6408
- process.stdout.write(`runtimeSessionId: ${payload.runtimeSessionId}
6612
+ process.stdout.write(`acpxSessionId: ${payload.acpxSessionId}
6613
+ `);
6614
+ process.stdout.write(`agentSessionId: ${payload.agentSessionId ?? "-"}
6409
6615
  `);
6410
- }
6411
6616
  process.stdout.write(`agent: ${payload.agentCommand}
6412
6617
  `);
6413
6618
  process.stdout.write(`pid: ${payload.pid ?? "-"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acpx",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Headless CLI client for the Agent Client Protocol (ACP) — talk to coding agents from the command line",
5
5
  "type": "module",
6
6
  "files": [