acpx 0.1.9 → 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 (2) hide show
  1. package/dist/cli.js +397 -212
  2. package/package.json +1 -1
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
  }
@@ -2086,6 +2159,9 @@ var TerminalManager = class {
2086
2159
  var REPLAY_IDLE_MS = 80;
2087
2160
  var REPLAY_DRAIN_TIMEOUT_MS = 5e3;
2088
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;
2089
2165
  function shouldSuppressSdkConsoleError(args) {
2090
2166
  if (args.length === 0) {
2091
2167
  return false;
@@ -2121,6 +2197,38 @@ function waitForSpawn2(child) {
2121
2197
  child.once("error", onError);
2122
2198
  });
2123
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
+ }
2124
2232
  function splitCommandLine(value) {
2125
2233
  const parts = [];
2126
2234
  let current = "";
@@ -2542,8 +2650,8 @@ var AcpClient = class {
2542
2650
  async close() {
2543
2651
  this.closing = true;
2544
2652
  await this.terminalManager.shutdown();
2545
- if (this.agent && !this.agent.killed) {
2546
- this.agent.kill();
2653
+ if (this.agent) {
2654
+ await this.terminateAgentProcess(this.agent);
2547
2655
  }
2548
2656
  this.sessionUpdateChain = Promise.resolve();
2549
2657
  this.observedSessionUpdates = 0;
@@ -2555,6 +2663,44 @@ var AcpClient = class {
2555
2663
  this.connection = void 0;
2556
2664
  this.agent = void 0;
2557
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
+ }
2558
2704
  getConnection() {
2559
2705
  if (!this.connection) {
2560
2706
  throw new Error("ACP client not started");
@@ -2879,7 +3025,7 @@ import os2 from "os";
2879
3025
  import path4 from "path";
2880
3026
 
2881
3027
  // src/queue-messages.ts
2882
- function asRecord3(value) {
3028
+ function asRecord4(value) {
2883
3029
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2884
3030
  return void 0;
2885
3031
  }
@@ -2898,7 +3044,7 @@ function isOutputErrorOrigin2(value) {
2898
3044
  return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
2899
3045
  }
2900
3046
  function parseAcpError(value) {
2901
- const record = asRecord3(value);
3047
+ const record = asRecord4(value);
2902
3048
  if (!record) {
2903
3049
  return void 0;
2904
3050
  }
@@ -2915,7 +3061,7 @@ function parseAcpError(value) {
2915
3061
  };
2916
3062
  }
2917
3063
  function parseQueueRequest(raw) {
2918
- const request = asRecord3(raw);
3064
+ const request = asRecord4(raw);
2919
3065
  if (!request) {
2920
3066
  return null;
2921
3067
  }
@@ -2973,15 +3119,15 @@ function parseQueueRequest(raw) {
2973
3119
  return null;
2974
3120
  }
2975
3121
  function parseSessionSendResult(raw) {
2976
- const result = asRecord3(raw);
3122
+ const result = asRecord4(raw);
2977
3123
  if (!result) {
2978
3124
  return null;
2979
3125
  }
2980
3126
  if (typeof result.stopReason !== "string" || typeof result.sessionId !== "string" || typeof result.resumed !== "boolean") {
2981
3127
  return null;
2982
3128
  }
2983
- const permissionStats = asRecord3(result.permissionStats);
2984
- const record = asRecord3(result.record);
3129
+ const permissionStats = asRecord4(result.permissionStats);
3130
+ const record = asRecord4(result.record);
2985
3131
  if (!permissionStats || !record) {
2986
3132
  return null;
2987
3133
  }
@@ -2996,7 +3142,7 @@ function parseSessionSendResult(raw) {
2996
3142
  return result;
2997
3143
  }
2998
3144
  function parseQueueOwnerMessage(raw) {
2999
- const message = asRecord3(raw);
3145
+ const message = asRecord4(raw);
3000
3146
  if (!message || typeof message.type !== "string") {
3001
3147
  return null;
3002
3148
  }
@@ -3021,7 +3167,7 @@ function parseQueueOwnerMessage(raw) {
3021
3167
  };
3022
3168
  }
3023
3169
  if (message.type === "client_operation") {
3024
- const operation = asRecord3(message.operation);
3170
+ const operation = asRecord4(message.operation);
3025
3171
  if (!operation || typeof operation.method !== "string" || typeof operation.status !== "string" || typeof operation.summary !== "string" || typeof operation.timestamp !== "string") {
3026
3172
  return null;
3027
3173
  }
@@ -3076,7 +3222,7 @@ function parseQueueOwnerMessage(raw) {
3076
3222
  };
3077
3223
  }
3078
3224
  if (message.type === "set_config_option_result") {
3079
- const response = asRecord3(message.response);
3225
+ const response = asRecord4(message.response);
3080
3226
  if (!response || !Array.isArray(response.configOptions)) {
3081
3227
  return null;
3082
3228
  }
@@ -4440,11 +4586,196 @@ async function findSessionByDirectoryWalk(options) {
4440
4586
  }
4441
4587
  }
4442
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
+
4443
4776
  // src/session-runtime.ts
4444
4777
  var DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
4445
4778
  var INTERRUPT_CANCEL_WAIT_MS = 2500;
4446
- var SESSION_HISTORY_MAX_ENTRIES = 500;
4447
- var SESSION_HISTORY_PREVIEW_CHARS = 220;
4448
4779
  var TimeoutError = class extends Error {
4449
4780
  constructor(timeoutMs) {
4450
4781
  super(`Timed out after ${timeoutMs}ms`);
@@ -4580,162 +4911,12 @@ function normalizeQueueOwnerTtlMs(ttlMs) {
4580
4911
  }
4581
4912
  return Math.round(ttlMs);
4582
4913
  }
4583
- function collapseWhitespace2(value) {
4584
- return value.replace(/\s+/g, " ").trim();
4585
- }
4586
- function toPreviewText(value) {
4587
- const collapsed = collapseWhitespace2(value);
4588
- if (collapsed.length <= SESSION_HISTORY_PREVIEW_CHARS) {
4589
- return collapsed;
4590
- }
4591
- if (SESSION_HISTORY_PREVIEW_CHARS <= 3) {
4592
- return collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS);
4593
- }
4594
- return `${collapsed.slice(0, SESSION_HISTORY_PREVIEW_CHARS - 3)}...`;
4595
- }
4596
- function textFromContent(content) {
4597
- if (content.type === "text") {
4598
- return content.text;
4599
- }
4600
- if (content.type === "resource_link") {
4601
- return content.title ?? content.name ?? content.uri;
4602
- }
4603
- if (content.type === "resource") {
4604
- if ("text" in content.resource && typeof content.resource.text === "string") {
4605
- return content.resource.text;
4606
- }
4607
- return content.resource.uri;
4608
- }
4609
- return void 0;
4610
- }
4611
- function toHistoryEntryFromUpdate(notification) {
4612
- const update = notification.update;
4613
- if (update.sessionUpdate !== "user_message_chunk" && update.sessionUpdate !== "agent_message_chunk") {
4614
- return void 0;
4615
- }
4616
- const text = textFromContent(update.content);
4617
- if (!text) {
4618
- return void 0;
4619
- }
4620
- const textPreview = toPreviewText(text);
4621
- if (!textPreview) {
4622
- return void 0;
4623
- }
4624
- return {
4625
- role: update.sessionUpdate === "user_message_chunk" ? "user" : "assistant",
4626
- timestamp: isoNow2(),
4627
- textPreview
4628
- };
4629
- }
4630
- function appendHistoryEntries(current, entries) {
4631
- const base = current ? [...current] : [];
4632
- for (const entry of entries) {
4633
- if (!entry.textPreview.trim()) {
4634
- continue;
4635
- }
4636
- base.push(entry);
4637
- }
4638
- if (base.length <= SESSION_HISTORY_MAX_ENTRIES) {
4639
- return base;
4640
- }
4641
- return base.slice(base.length - SESSION_HISTORY_MAX_ENTRIES);
4642
- }
4643
- function applyLifecycleSnapshotToRecord(record, snapshot) {
4644
- record.pid = snapshot.pid;
4645
- record.agentStartedAt = snapshot.startedAt;
4646
- if (snapshot.lastExit) {
4647
- record.lastAgentExitCode = snapshot.lastExit.exitCode;
4648
- record.lastAgentExitSignal = snapshot.lastExit.signal;
4649
- record.lastAgentExitAt = snapshot.lastExit.exitedAt;
4650
- record.lastAgentDisconnectReason = snapshot.lastExit.reason;
4651
- return;
4652
- }
4653
- record.lastAgentExitCode = void 0;
4654
- record.lastAgentExitSignal = void 0;
4655
- record.lastAgentExitAt = void 0;
4656
- record.lastAgentDisconnectReason = void 0;
4657
- }
4658
- function reconcileAgentSessionId(record, agentSessionId) {
4659
- const normalized = normalizeAgentSessionId(agentSessionId);
4660
- if (!normalized) {
4661
- return;
4662
- }
4663
- record.agentSessionId = normalized;
4664
- }
4665
4914
  function shouldFallbackToNewSession(error) {
4666
4915
  if (error instanceof TimeoutError || error instanceof InterruptedError) {
4667
4916
  return false;
4668
4917
  }
4669
4918
  return isAcpResourceNotFoundError(error);
4670
4919
  }
4671
- async function connectAndLoadSession(options) {
4672
- const record = options.record;
4673
- const client = options.client;
4674
- const storedProcessAlive = isProcessAlive(record.pid);
4675
- const shouldReconnect = Boolean(record.pid) && !storedProcessAlive;
4676
- if (options.verbose) {
4677
- if (storedProcessAlive) {
4678
- process.stderr.write(
4679
- `[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession
4680
- `
4681
- );
4682
- } else if (shouldReconnect) {
4683
- process.stderr.write(
4684
- `[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session/load
4685
- `
4686
- );
4687
- }
4688
- }
4689
- await withTimeout(client.start(), options.timeoutMs);
4690
- options.onClientAvailable?.(options.activeController);
4691
- applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
4692
- record.closed = false;
4693
- record.closedAt = void 0;
4694
- options.onConnectedRecord?.(record);
4695
- await writeSessionRecord(record);
4696
- let resumed = false;
4697
- let loadError;
4698
- let sessionId = record.sessionId;
4699
- if (client.supportsLoadSession()) {
4700
- try {
4701
- const loadResult = await withTimeout(
4702
- client.loadSessionWithOptions(record.sessionId, record.cwd, {
4703
- suppressReplayUpdates: true
4704
- }),
4705
- options.timeoutMs
4706
- );
4707
- reconcileAgentSessionId(record, loadResult.agentSessionId);
4708
- resumed = true;
4709
- } catch (error) {
4710
- loadError = formatErrorMessage(error);
4711
- if (!shouldFallbackToNewSession(error)) {
4712
- throw error;
4713
- }
4714
- const createdSession = await withTimeout(
4715
- client.createSession(record.cwd),
4716
- options.timeoutMs
4717
- );
4718
- sessionId = createdSession.sessionId;
4719
- record.sessionId = sessionId;
4720
- reconcileAgentSessionId(record, createdSession.agentSessionId);
4721
- }
4722
- } else {
4723
- const createdSession = await withTimeout(
4724
- client.createSession(record.cwd),
4725
- options.timeoutMs
4726
- );
4727
- sessionId = createdSession.sessionId;
4728
- record.sessionId = sessionId;
4729
- reconcileAgentSessionId(record, createdSession.agentSessionId);
4730
- }
4731
- options.onSessionIdResolved?.(sessionId);
4732
- return {
4733
- sessionId,
4734
- agentSessionId: record.agentSessionId,
4735
- resumed,
4736
- loadError
4737
- };
4738
- }
4739
4920
  async function runQueuedTask(sessionRecordId, task, options) {
4740
4921
  const outputFormatter = task.waitForCompletion ? new QueueTaskOutputFormatter(task) : DISCARD_OUTPUT_FORMATTER;
4741
4922
  try {
@@ -4842,6 +5023,8 @@ async function runSessionPrompt(options) {
4842
5023
  timeoutMs: options.timeoutMs,
4843
5024
  verbose: options.verbose,
4844
5025
  activeController,
5026
+ withTimeout,
5027
+ shouldFallbackToNewSession,
4845
5028
  onClientAvailable: (controller) => {
4846
5029
  options.onClientAvailable?.(controller);
4847
5030
  notifiedClientAvailable = true;
@@ -4976,6 +5159,8 @@ async function withConnectedSession(options) {
4976
5159
  timeoutMs: options.timeoutMs,
4977
5160
  verbose: options.verbose,
4978
5161
  activeController,
5162
+ withTimeout,
5163
+ shouldFallbackToNewSession,
4979
5164
  onClientAvailable: (controller) => {
4980
5165
  options.onClientAvailable?.(controller);
4981
5166
  notifiedClientAvailable = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acpx",
3
- "version": "0.1.9",
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": [