@hydra-acp/cli 0.1.23 → 0.1.24

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.
package/dist/cli.js CHANGED
@@ -4067,7 +4067,7 @@ var init_session_row = __esm({
4067
4067
 
4068
4068
  // src/cli/commands/sessions.ts
4069
4069
  import * as fs17 from "fs/promises";
4070
- import * as path10 from "path";
4070
+ import * as path11 from "path";
4071
4071
  async function runSessionsList(opts = {}) {
4072
4072
  const config = await loadConfig();
4073
4073
  const serviceToken = await loadServiceToken();
@@ -4195,7 +4195,7 @@ async function runSessionsExport(id, outPath) {
4195
4195
  return;
4196
4196
  }
4197
4197
  const resolved = outPath === "." ? deriveFilenameFrom(response, id) : outPath;
4198
- await fs17.mkdir(path10.dirname(path10.resolve(resolved)), { recursive: true });
4198
+ await fs17.mkdir(path11.dirname(path11.resolve(resolved)), { recursive: true });
4199
4199
  await fs17.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
4200
4200
  process.stdout.write(`Wrote ${resolved}
4201
4201
  `);
@@ -4214,7 +4214,7 @@ async function runSessionsTranscript(idOrFile, outPath) {
4214
4214
  const bundle = decodeBundleOrExit(localFile.raw);
4215
4215
  body = bundleToMarkdown(bundle);
4216
4216
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4217
- defaultName = `${path10.basename(idOrFile, path10.extname(idOrFile))}-${stamp}.md`;
4217
+ defaultName = `${path11.basename(idOrFile, path11.extname(idOrFile))}-${stamp}.md`;
4218
4218
  } else {
4219
4219
  const config = await loadConfig();
4220
4220
  const serviceToken = await loadServiceToken();
@@ -4243,7 +4243,7 @@ async function runSessionsTranscript(idOrFile, outPath) {
4243
4243
  return;
4244
4244
  }
4245
4245
  const resolved = outPath === "." ? defaultName : outPath;
4246
- await fs17.mkdir(path10.dirname(path10.resolve(resolved)), { recursive: true });
4246
+ await fs17.mkdir(path11.dirname(path11.resolve(resolved)), { recursive: true });
4247
4247
  await fs17.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
4248
4248
  process.stdout.write(`Wrote ${resolved}
4249
4249
  `);
@@ -4284,7 +4284,7 @@ async function runSessionsImport(file, opts = {}) {
4284
4284
  }
4285
4285
  let cwdOverride;
4286
4286
  if (opts.cwd !== void 0) {
4287
- const resolved = path10.resolve(opts.cwd);
4287
+ const resolved = path11.resolve(opts.cwd);
4288
4288
  try {
4289
4289
  const stat4 = await fs17.stat(resolved);
4290
4290
  if (!stat4.isDirectory()) {
@@ -4955,6 +4955,10 @@ async function pickSession(term, opts) {
4955
4955
  const indicatorRow = () => startRow + 3 + viewportSize;
4956
4956
  const sessionRow = (sessionIdx) => startRow + 3 + (sessionIdx - scrollOffset);
4957
4957
  const renderFromScratch = () => {
4958
+ if (mode === "help") {
4959
+ renderHelp();
4960
+ return;
4961
+ }
4958
4962
  computeLayout();
4959
4963
  adjustScroll();
4960
4964
  startRow = 1;
@@ -4969,6 +4973,21 @@ async function pickSession(term, opts) {
4969
4973
  paintIndicator();
4970
4974
  term("\n");
4971
4975
  };
4976
+ const renderHelp = () => {
4977
+ term.moveTo(1, 1).eraseDisplayBelow();
4978
+ term.brightWhite.bold.noFormat(" Picker hotkeys")("\n\n");
4979
+ for (const entry of HELP_ENTRIES) {
4980
+ if (entry === null) {
4981
+ term("\n");
4982
+ continue;
4983
+ }
4984
+ const [keys, desc] = entry;
4985
+ term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
4986
+ term.noFormat(desc)("\n");
4987
+ }
4988
+ term("\n");
4989
+ term.dim.noFormat(" press any key to dismiss")("\n");
4990
+ };
4972
4991
  const repaintNewItem = () => {
4973
4992
  term.moveTo(1, startRow).eraseLineAfter();
4974
4993
  paintNewItem();
@@ -5099,6 +5118,16 @@ async function pickSession(term, opts) {
5099
5118
  if (mode === "busy") {
5100
5119
  return;
5101
5120
  }
5121
+ if (mode === "help") {
5122
+ if (name === "CTRL_C") {
5123
+ cleanup();
5124
+ resolve5({ kind: "abort" });
5125
+ return;
5126
+ }
5127
+ mode = "normal";
5128
+ renderFromScratch();
5129
+ return;
5130
+ }
5102
5131
  if (mode === "confirm-kill" || mode === "confirm-delete") {
5103
5132
  if (data?.isCharacter && (name === "y" || name === "Y")) {
5104
5133
  const kind = mode === "confirm-kill" ? "kill" : "delete";
@@ -5114,6 +5143,11 @@ async function pickSession(term, opts) {
5114
5143
  return;
5115
5144
  }
5116
5145
  clearTransient();
5146
+ if (!searchActive && data?.isCharacter && name === "?") {
5147
+ mode = "help";
5148
+ renderHelp();
5149
+ return;
5150
+ }
5117
5151
  if (searchActive) {
5118
5152
  if (data?.isCharacter) {
5119
5153
  searchTerm += name;
@@ -5262,6 +5296,7 @@ async function pickSession(term, opts) {
5262
5296
  }
5263
5297
  case "ESCAPE":
5264
5298
  case "CTRL_C":
5299
+ case "CTRL_D":
5265
5300
  cleanup();
5266
5301
  resolve5({ kind: "abort" });
5267
5302
  return;
@@ -5303,7 +5338,7 @@ function matchesSearch(s, term) {
5303
5338
  }
5304
5339
  return false;
5305
5340
  }
5306
- var ROW_PREFIX_WIDTH;
5341
+ var ROW_PREFIX_WIDTH, HELP_KEYS_WIDTH, HELP_ENTRIES;
5307
5342
  var init_picker = __esm({
5308
5343
  "src/tui/picker.ts"() {
5309
5344
  "use strict";
@@ -5312,13 +5347,31 @@ var init_picker = __esm({
5312
5347
  init_session();
5313
5348
  init_discovery();
5314
5349
  ROW_PREFIX_WIDTH = 2;
5350
+ HELP_KEYS_WIDTH = 20;
5351
+ HELP_ENTRIES = [
5352
+ ["\u2191 / \u2193 or n / p", "navigate"],
5353
+ ["PgUp / PgDn", "page up / page down"],
5354
+ ["Home / End", "first / last"],
5355
+ ["Enter", "open selected session (or create new)"],
5356
+ null,
5357
+ ["/", "search sessions"],
5358
+ ["o", "toggle cwd-only filter"],
5359
+ ["r", "refresh from daemon"],
5360
+ null,
5361
+ ["k", "kill the selected live session"],
5362
+ ["d", "delete the selected cold session"],
5363
+ null,
5364
+ ["c", "create new session"],
5365
+ ["?", "toggle this help"],
5366
+ ["q / Esc / ^C / ^D", "quit picker (detach)"]
5367
+ ];
5315
5368
  }
5316
5369
  });
5317
5370
 
5318
5371
  // src/tui/attachments.ts
5319
- import path11 from "path";
5372
+ import path12 from "path";
5320
5373
  function mimeFromExtension(p) {
5321
- return EXTENSION_TO_MIME[path11.extname(p).toLowerCase()] ?? null;
5374
+ return EXTENSION_TO_MIME[path12.extname(p).toLowerCase()] ?? null;
5322
5375
  }
5323
5376
  function isSupportedImagePath(p) {
5324
5377
  return mimeFromExtension(p) !== null;
@@ -5894,6 +5947,12 @@ function mapKeyName(name) {
5894
5947
  case "ALT_ENTER":
5895
5948
  case "META_ENTER":
5896
5949
  return "alt-enter";
5950
+ case "ALT_B":
5951
+ case "META_B":
5952
+ return "alt-b";
5953
+ case "ALT_F":
5954
+ case "META_F":
5955
+ return "alt-f";
5897
5956
  case "CTRL_T":
5898
5957
  return "ctrl-t";
5899
5958
  case "SHIFT_TAB":
@@ -5928,6 +5987,8 @@ function mapKeyName(name) {
5928
5987
  return "ctrl-e";
5929
5988
  case "CTRL_F":
5930
5989
  return "ctrl-f";
5990
+ case "CTRL_G":
5991
+ return "ctrl-g";
5931
5992
  case "CTRL_K":
5932
5993
  return "ctrl-k";
5933
5994
  case "CTRL_L":
@@ -5956,7 +6017,7 @@ function mapKeyName(name) {
5956
6017
  return null;
5957
6018
  }
5958
6019
  }
5959
- var SESSIONBAR_ROWS, BANNER_ROWS, SEPARATOR_ROWS, MAX_PROMPT_ROWS, MAX_QUEUED_ROWS, MAX_PERMISSION_ROWS, MAX_COMPLETION_ROWS, MAX_CHIP_ROWS, CONFIRM_PROMPT_ROWS, DEFAULT_CONTENT_REPAINT_THROTTLE_MS, DEFAULT_MAX_SCROLLBACK_LINES, BARE_URL_RE, Screen, NON_ASCII, SEGMENTER, TK_MARKUP_STYLE_CHAR, shortId;
6020
+ var SESSIONBAR_ROWS, BANNER_ROWS, SEPARATOR_ROWS, MAX_PROMPT_ROWS, MAX_QUEUED_ROWS, MAX_PERMISSION_ROWS, MAX_HELP_ROWS, MAX_COMPLETION_ROWS, MAX_CHIP_ROWS, CONFIRM_PROMPT_ROWS, DEFAULT_CONTENT_REPAINT_THROTTLE_MS, DEFAULT_MAX_SCROLLBACK_LINES, BARE_URL_RE, Screen, NON_ASCII, SEGMENTER, TK_MARKUP_STYLE_CHAR, shortId;
5960
6021
  var init_screen = __esm({
5961
6022
  "src/tui/screen.ts"() {
5962
6023
  "use strict";
@@ -5970,6 +6031,7 @@ var init_screen = __esm({
5970
6031
  MAX_PROMPT_ROWS = 8;
5971
6032
  MAX_QUEUED_ROWS = 5;
5972
6033
  MAX_PERMISSION_ROWS = 12;
6034
+ MAX_HELP_ROWS = 30;
5973
6035
  MAX_COMPLETION_ROWS = 6;
5974
6036
  MAX_CHIP_ROWS = 4;
5975
6037
  CONFIRM_PROMPT_ROWS = 2;
@@ -6032,6 +6094,7 @@ var init_screen = __esm({
6032
6094
  lastFrameH = 0;
6033
6095
  permissionPrompt = null;
6034
6096
  confirmPrompt = null;
6097
+ helpPrompt = null;
6035
6098
  completions = [];
6036
6099
  // Scrollback offset: 0 = pinned to bottom (live), N = N wrapped lines
6037
6100
  // above the bottom. Mouse wheel and PgUp/PgDn adjust this; new content
@@ -6060,7 +6123,7 @@ var init_screen = __esm({
6060
6123
  banner = {
6061
6124
  status: "ready",
6062
6125
  currentMode: void 0,
6063
- hint: "\u21E7\u21E5 mode \xB7 \u2303V paste \xB7 \u2303P pick \xB7 \u2303C cancel \xB7 \u2303D detach",
6126
+ hint: "\u21E7\u21E5 mode \xB7 \u2303P pick \xB7 \u2303G guide \xB7 \u2303D detach",
6064
6127
  queued: 0
6065
6128
  };
6066
6129
  sessionbar = { agent: "?", cwd: "?", sessionId: "?" };
@@ -6623,6 +6686,16 @@ var init_screen = __esm({
6623
6686
  this.confirmPrompt = spec ? { ...spec } : null;
6624
6687
  this.repaint();
6625
6688
  }
6689
+ // Multi-row help cheatsheet that takes over the prompt area. Used by
6690
+ // the ^G hotkey to surface every binding without dropping the user
6691
+ // out of the session. Pass null to dismiss.
6692
+ setHelpPrompt(spec) {
6693
+ this.helpPrompt = spec ? { ...spec, entries: [...spec.entries] } : null;
6694
+ this.repaint();
6695
+ }
6696
+ isHelpPromptActive() {
6697
+ return this.helpPrompt !== null;
6698
+ }
6626
6699
  // Slash-command completion list shown directly above the separator. App
6627
6700
  // calls this after each keystroke; pass [] to dismiss. Suppressed when
6628
6701
  // the permission modal is active (the modal owns the prompt area).
@@ -7158,7 +7231,7 @@ var init_screen = __esm({
7158
7231
  this.repaint();
7159
7232
  }
7160
7233
  completionRows() {
7161
- if (this.permissionPrompt) {
7234
+ if (this.permissionPrompt || this.confirmPrompt || this.helpPrompt) {
7162
7235
  return 0;
7163
7236
  }
7164
7237
  return Math.min(MAX_COMPLETION_ROWS, this.completions.length);
@@ -7302,6 +7375,10 @@ var init_screen = __esm({
7302
7375
  this.drawConfirmPrompt();
7303
7376
  return;
7304
7377
  }
7378
+ if (this.helpPrompt) {
7379
+ this.drawHelpPrompt();
7380
+ return;
7381
+ }
7305
7382
  const w = this.term.width;
7306
7383
  const room = Math.max(1, w - 2);
7307
7384
  const state = this.dispatcher.state();
@@ -7351,6 +7428,58 @@ var init_screen = __esm({
7351
7428
  this.term.dim(` ${truncate(spec.hint, w - 2)}`);
7352
7429
  });
7353
7430
  }
7431
+ drawHelpPrompt() {
7432
+ const spec = this.helpPrompt;
7433
+ if (!spec) {
7434
+ return;
7435
+ }
7436
+ const w = this.term.width;
7437
+ const rows = this.helpRows();
7438
+ const top = this.term.height - rows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
7439
+ let row = top;
7440
+ const writeRow = (sig, paint) => {
7441
+ if (row >= top + rows) {
7442
+ return;
7443
+ }
7444
+ this.paintRow(row, sig, paint);
7445
+ row += 1;
7446
+ };
7447
+ writeRow(`help|t|${w}|${spec.title}`, () => {
7448
+ this.term.brightYellow(` \u2753 ${truncate(spec.title, w - 5)}`);
7449
+ });
7450
+ const keysWidth = Math.min(
7451
+ 24,
7452
+ Math.max(
7453
+ ...spec.entries.map((e) => e === null ? 0 : e[0].length),
7454
+ 4
7455
+ )
7456
+ );
7457
+ for (const entry of spec.entries) {
7458
+ if (row >= top + rows - 1) {
7459
+ break;
7460
+ }
7461
+ if (entry === null) {
7462
+ writeRow(`help|sep|${w}|${row}`, () => void 0);
7463
+ continue;
7464
+ }
7465
+ const [keys, desc] = entry;
7466
+ const paddedKeys = keys.padEnd(keysWidth);
7467
+ writeRow(`help|e|${w}|${keys}|${desc}`, () => {
7468
+ this.term(" ");
7469
+ this.term.brightCyan.noFormat(paddedKeys);
7470
+ this.term.noFormat(` ${truncate(desc, w - 2 - keysWidth - 1)}`);
7471
+ });
7472
+ }
7473
+ writeRow(`help|hint|${w}|${spec.hint}`, () => {
7474
+ this.term.dim(` ${truncate(spec.hint, w - 2)}`);
7475
+ });
7476
+ }
7477
+ helpRows() {
7478
+ if (!this.helpPrompt) {
7479
+ return 0;
7480
+ }
7481
+ return Math.min(MAX_HELP_ROWS, 2 + this.helpPrompt.entries.length);
7482
+ }
7354
7483
  drawPermissionPrompt() {
7355
7484
  const spec = this.permissionPrompt;
7356
7485
  if (!spec) {
@@ -7460,6 +7589,12 @@ var init_screen = __esm({
7460
7589
  this.term.moveTo(2, top2);
7461
7590
  return;
7462
7591
  }
7592
+ if (this.helpPrompt) {
7593
+ const rows = this.helpRows();
7594
+ const top2 = this.term.height - rows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
7595
+ this.term.moveTo(2, top2);
7596
+ return;
7597
+ }
7463
7598
  if (this.scrollbackSearch) {
7464
7599
  this.term.hideCursor(true);
7465
7600
  return;
@@ -7486,6 +7621,9 @@ var init_screen = __esm({
7486
7621
  if (this.confirmPrompt) {
7487
7622
  return CONFIRM_PROMPT_ROWS;
7488
7623
  }
7624
+ if (this.helpPrompt) {
7625
+ return this.helpRows();
7626
+ }
7489
7627
  const w = this.term.width;
7490
7628
  const room = Math.max(1, w - 2);
7491
7629
  const state = this.dispatcher.state();
@@ -7844,6 +7982,14 @@ var init_input = __esm({
7844
7982
  case "ctrl-f":
7845
7983
  this.moveRight();
7846
7984
  return [];
7985
+ case "ctrl-g":
7986
+ return [{ type: "show-help" }];
7987
+ case "alt-b":
7988
+ this.moveWordBackward();
7989
+ return [];
7990
+ case "alt-f":
7991
+ this.moveWordForward();
7992
+ return [];
7847
7993
  case "ctrl-k":
7848
7994
  this.killToEnd();
7849
7995
  return [];
@@ -8047,6 +8193,44 @@ var init_input = __esm({
8047
8193
  this.col = 0;
8048
8194
  }
8049
8195
  }
8196
+ moveWordBackward() {
8197
+ if (this.col === 0) {
8198
+ if (this.row === 0) {
8199
+ return;
8200
+ }
8201
+ this.row -= 1;
8202
+ this.col = this.currentLine().length;
8203
+ return;
8204
+ }
8205
+ const line = this.currentLine();
8206
+ let i = this.col;
8207
+ while (i > 0 && /\s/.test(line[i - 1] ?? "")) {
8208
+ i -= 1;
8209
+ }
8210
+ while (i > 0 && !/\s/.test(line[i - 1] ?? "")) {
8211
+ i -= 1;
8212
+ }
8213
+ this.col = i;
8214
+ }
8215
+ moveWordForward() {
8216
+ const line = this.currentLine();
8217
+ if (this.col >= line.length) {
8218
+ if (this.row >= this.buffer.length - 1) {
8219
+ return;
8220
+ }
8221
+ this.row += 1;
8222
+ this.col = 0;
8223
+ return;
8224
+ }
8225
+ let i = this.col;
8226
+ while (i < line.length && /\s/.test(line[i] ?? "")) {
8227
+ i += 1;
8228
+ }
8229
+ while (i < line.length && !/\s/.test(line[i] ?? "")) {
8230
+ i += 1;
8231
+ }
8232
+ this.col = i;
8233
+ }
8050
8234
  // Up walks the navigation stack from newest to oldest: pending queue
8051
8235
  // items first (so the user can edit something they just enqueued),
8052
8236
  // then prompt history. Cursor movement within a multi-line buffer
@@ -8362,7 +8546,7 @@ var init_input = __esm({
8362
8546
  import { spawn as nodeSpawn } from "child_process";
8363
8547
  import fs18 from "fs/promises";
8364
8548
  import os4 from "os";
8365
- import path12 from "path";
8549
+ import path13 from "path";
8366
8550
  async function readClipboard(envIn = {}) {
8367
8551
  const env = { ...defaultEnv, ...envIn };
8368
8552
  if (env.platform === "darwin") {
@@ -8377,7 +8561,7 @@ async function readClipboard(envIn = {}) {
8377
8561
  };
8378
8562
  }
8379
8563
  async function readMacOS(env) {
8380
- const tmpPath = path12.join(
8564
+ const tmpPath = path13.join(
8381
8565
  env.tmpdir(),
8382
8566
  `hydra-clipboard-${Date.now()}-${process.pid}.png`
8383
8567
  );
@@ -8981,7 +9165,7 @@ import { appendFileSync, statSync, renameSync } from "fs";
8981
9165
  import { nanoid as nanoid3 } from "nanoid";
8982
9166
  import termkit from "terminal-kit";
8983
9167
  import fs19 from "fs/promises";
8984
- import path13 from "path";
9168
+ import path14 from "path";
8985
9169
  async function runTuiApp(opts) {
8986
9170
  const config = await loadConfig();
8987
9171
  const serviceToken = await ensureServiceToken();
@@ -9087,14 +9271,27 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
9087
9271
  };
9088
9272
  let screenRef = null;
9089
9273
  let dispatcherRef = null;
9090
- conn.onNotification("session/update", (params) => {
9091
- if (teardownStarted) {
9092
- return;
9093
- }
9274
+ let lastSeenMessageId = void 0;
9275
+ let reconnectReplayBuffer = null;
9276
+ const STATE_UPDATE_KINDS2 = /* @__PURE__ */ new Set([
9277
+ "session_info_update",
9278
+ "current_model_update",
9279
+ "current_mode_update",
9280
+ "available_commands_update",
9281
+ "available_modes_update",
9282
+ "usage_update"
9283
+ ]);
9284
+ const handleSessionUpdate = (params) => {
9094
9285
  const { update } = params ?? {};
9095
9286
  const event = mapUpdate(update);
9096
9287
  debugLogUpdate(update, event);
9097
9288
  const rawTag = update?.sessionUpdate;
9289
+ if (typeof rawTag === "string" && !STATE_UPDATE_KINDS2.has(rawTag)) {
9290
+ const u = update ?? {};
9291
+ if (typeof u.messageId === "string") {
9292
+ lastSeenMessageId = u.messageId;
9293
+ }
9294
+ }
9098
9295
  if (rawTag === "prompt_received") {
9099
9296
  adjustPendingTurns(1);
9100
9297
  } else if (event?.kind === "turn-complete") {
@@ -9106,6 +9303,16 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
9106
9303
  }
9107
9304
  appendRender(event);
9108
9305
  maybeDismissPermissionByToolUpdate(update);
9306
+ };
9307
+ conn.onNotification("session/update", (params) => {
9308
+ if (teardownStarted) {
9309
+ return;
9310
+ }
9311
+ if (reconnectReplayBuffer !== null) {
9312
+ reconnectReplayBuffer.push(params);
9313
+ return;
9314
+ }
9315
+ handleSessionUpdate(params);
9109
9316
  });
9110
9317
  conn.onNotification("hydra-acp/session_closed", () => {
9111
9318
  if (teardownStarted) {
@@ -9184,6 +9391,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
9184
9391
  text: echo.text,
9185
9392
  attachments: echo.attachments
9186
9393
  });
9394
+ currentTurnEcho = echo;
9187
9395
  }
9188
9396
  }
9189
9397
  });
@@ -9420,6 +9628,9 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
9420
9628
  if (exitConfirmation && tryHandleExitConfirmKey(ev)) {
9421
9629
  continue;
9422
9630
  }
9631
+ if (tryHandleHelpKey(ev)) {
9632
+ continue;
9633
+ }
9423
9634
  if (tryHandleScrollbackSearchKey(ev)) {
9424
9635
  continue;
9425
9636
  }
@@ -9705,6 +9916,28 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
9705
9916
  }
9706
9917
  return true;
9707
9918
  };
9919
+ const toggleHelpModal = () => {
9920
+ if (screen.isHelpPromptActive()) {
9921
+ screen.setHelpPrompt(null);
9922
+ return;
9923
+ }
9924
+ screen.setHelpPrompt({
9925
+ title: "Hotkeys",
9926
+ entries: HELP_ENTRIES2,
9927
+ hint: "any key dismisses \xB7 /help lists commands"
9928
+ });
9929
+ };
9930
+ const tryHandleHelpKey = (ev) => {
9931
+ if (!screen.isHelpPromptActive()) {
9932
+ return false;
9933
+ }
9934
+ if (ev.type === "key" && ev.name === "ctrl-g") {
9935
+ screen.setHelpPrompt(null);
9936
+ return true;
9937
+ }
9938
+ screen.setHelpPrompt(null);
9939
+ return true;
9940
+ };
9708
9941
  const teardown = () => {
9709
9942
  teardownStarted = true;
9710
9943
  process.off("SIGINT", sigintHandler);
@@ -9885,6 +10118,9 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
9885
10118
  toolsExpanded = !toolsExpanded;
9886
10119
  renderToolsBlock();
9887
10120
  return;
10121
+ case "show-help":
10122
+ toggleHelpModal();
10123
+ return;
9888
10124
  case "escalate-search":
9889
10125
  screen.enterScrollbackSearch();
9890
10126
  screen.updateScrollbackSearchTerm(effect.query);
@@ -9924,7 +10160,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
9924
10160
  }
9925
10161
  const mimeType = mimeFromExtension(token);
9926
10162
  if (!mimeType) {
9927
- screen.notify(`unsupported image type: ${path13.basename(token)}`);
10163
+ screen.notify(`unsupported image type: ${path14.basename(token)}`);
9928
10164
  continue;
9929
10165
  }
9930
10166
  try {
@@ -9938,13 +10174,13 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
9938
10174
  dispatcher.addAttachment({
9939
10175
  mimeType,
9940
10176
  data: buf.toString("base64"),
9941
- name: path13.basename(token),
10177
+ name: path14.basename(token),
9942
10178
  sizeBytes: buf.length
9943
10179
  });
9944
10180
  added++;
9945
10181
  } catch (err) {
9946
10182
  screen.notify(
9947
- `cannot read ${path13.basename(token)}: ${err.message}`
10183
+ `cannot read ${path14.basename(token)}: ${err.message}`
9948
10184
  );
9949
10185
  }
9950
10186
  }
@@ -9998,6 +10234,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
9998
10234
  const queueCache = /* @__PURE__ */ new Map();
9999
10235
  const pendingEchoes = [];
10000
10236
  const ownPendingByMid = /* @__PURE__ */ new Map();
10237
+ let currentTurnEcho = null;
10001
10238
  const refreshQueueDisplay = () => {
10002
10239
  const entries = [...queueCache.values()];
10003
10240
  const displayTexts = entries.map(formatQueueChipText);
@@ -10254,10 +10491,11 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
10254
10491
  } finally {
10255
10492
  turnInFlight = null;
10256
10493
  adjustPendingTurns(-1);
10257
- if (echo.flushed) {
10494
+ if (echo.flushed && currentTurnEcho === echo) {
10258
10495
  appendRender(
10259
10496
  stopReason !== void 0 ? { kind: "turn-complete", stopReason } : { kind: "turn-complete" }
10260
10497
  );
10498
+ currentTurnEcho = null;
10261
10499
  }
10262
10500
  if (pendingPrefill !== null) {
10263
10501
  const { text: pt, attachments: pa } = pendingPrefill;
@@ -10440,6 +10678,11 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
10440
10678
  }
10441
10679
  if (event.kind === "user-text") {
10442
10680
  closeAgentText();
10681
+ if (toolsBlockStartedAt !== null) {
10682
+ toolsBlockEndedAt = Date.now();
10683
+ renderToolsBlock();
10684
+ }
10685
+ currentTurnEcho = null;
10443
10686
  screen.ensureSeparator();
10444
10687
  const formatted2 = formatEvent(event);
10445
10688
  if (formatted2.length > 0) {
@@ -10508,6 +10751,15 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
10508
10751
  toolsBlockStopReason = event.stopReason ?? null;
10509
10752
  renderToolsBlock();
10510
10753
  screen.clearKey("tools");
10754
+ } else if (event.stopReason !== void 0 && event.stopReason !== "end_turn") {
10755
+ screen.appendLines([
10756
+ {
10757
+ prefix: "\u26A0 ",
10758
+ prefixStyle: "tool-status-fail",
10759
+ body: `turn ended: ${event.stopReason}`,
10760
+ bodyStyle: "tool-status-fail"
10761
+ }
10762
+ ]);
10511
10763
  }
10512
10764
  toolStates.clear();
10513
10765
  toolCallOrder.length = 0;
@@ -10555,23 +10807,21 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
10555
10807
  resolve5({ outcome: { outcome: "cancelled" } });
10556
10808
  }
10557
10809
  closeAgentText();
10558
- if (toolsBlockStartedAt !== null) {
10559
- toolsBlockEndedAt = Date.now();
10560
- toolsBlockStopReason = null;
10561
- renderToolsBlock();
10562
- screen.clearKey("tools");
10563
- toolStates.clear();
10564
- toolCallOrder.length = 0;
10565
- toolsBlockStartedAt = null;
10566
- toolsBlockEndedAt = null;
10567
- toolsBlockStopReason = null;
10568
- toolsExpanded = false;
10569
- }
10570
- screen.clearKey("plan");
10571
- lastPlanEvent = null;
10572
- if (pendingTurns > 0) {
10573
- adjustPendingTurns(-pendingTurns);
10810
+ };
10811
+ const markToolsBlockRecoveryFailed = () => {
10812
+ if (toolsBlockStartedAt === null) {
10813
+ return;
10574
10814
  }
10815
+ toolsBlockEndedAt = Date.now();
10816
+ toolsBlockStopReason = "reconnect-recovery-failed";
10817
+ renderToolsBlock();
10818
+ screen.clearKey("tools");
10819
+ toolStates.clear();
10820
+ toolCallOrder.length = 0;
10821
+ toolsBlockStartedAt = null;
10822
+ toolsBlockEndedAt = null;
10823
+ toolsBlockStopReason = null;
10824
+ toolsExpanded = false;
10575
10825
  };
10576
10826
  onDisconnectHook = () => {
10577
10827
  screen.setBanner({ status: "disconnected", elapsedMs: void 0 });
@@ -10595,13 +10845,15 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
10595
10845
  await stream.request(initReq);
10596
10846
  } catch {
10597
10847
  }
10848
+ const useAfterMessage = lastSeenMessageId !== void 0;
10598
10849
  const attachReq = {
10599
10850
  jsonrpc: "2.0",
10600
10851
  id: `tui-reattach-${nanoid3()}`,
10601
10852
  method: "session/attach",
10602
10853
  params: {
10603
10854
  sessionId: resolvedSessionId,
10604
- historyPolicy: "none",
10855
+ historyPolicy: useAfterMessage ? "after_message" : "none",
10856
+ ...useAfterMessage ? { afterMessageId: lastSeenMessageId } : {},
10605
10857
  clientInfo: { name: "hydra-acp-tui", version: HYDRA_VERSION },
10606
10858
  ...upstreamSessionId !== void 0 ? {
10607
10859
  _meta: {
@@ -10616,19 +10868,46 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
10616
10868
  } : {}
10617
10869
  }
10618
10870
  };
10871
+ reconnectReplayBuffer = [];
10872
+ let appliedPolicy;
10873
+ let attachErr;
10619
10874
  try {
10620
10875
  const resp = await stream.request(attachReq);
10621
10876
  if (resp.error) {
10622
10877
  throw new Error(resp.error.message);
10623
10878
  }
10879
+ const result = resp.result ?? {};
10880
+ if (typeof result.historyPolicy === "string") {
10881
+ appliedPolicy = result.historyPolicy;
10882
+ }
10624
10883
  } catch (err) {
10884
+ attachErr = err;
10885
+ }
10886
+ const buffered2 = reconnectReplayBuffer ?? [];
10887
+ reconnectReplayBuffer = null;
10888
+ if (attachErr) {
10889
+ markToolsBlockRecoveryFailed();
10625
10890
  screen.appendLines([
10626
10891
  {
10627
10892
  prefix: " ",
10628
- body: `reattach failed: ${err.message}`,
10893
+ body: `reattach failed: ${attachErr.message}`,
10629
10894
  bodyStyle: "tool-status-fail"
10630
10895
  }
10631
10896
  ]);
10897
+ } else if (useAfterMessage && appliedPolicy !== "after_message") {
10898
+ markToolsBlockRecoveryFailed();
10899
+ screen.appendLines([
10900
+ {
10901
+ prefix: "\u26A0 ",
10902
+ prefixStyle: "tool-status-fail",
10903
+ body: "reconnect couldn't replay events since last seen \u2014 scrollback may be incomplete",
10904
+ bodyStyle: "tool-status-fail"
10905
+ }
10906
+ ]);
10907
+ } else {
10908
+ for (const params of buffered2) {
10909
+ handleSessionUpdate(params);
10910
+ }
10632
10911
  }
10633
10912
  screen.setBanner({
10634
10913
  status: pendingTurns > 0 ? "busy" : "ready",
@@ -10735,7 +11014,7 @@ function rotateIfBig(target) {
10735
11014
  } catch {
10736
11015
  }
10737
11016
  }
10738
- var logMaxBytes;
11017
+ var HELP_ENTRIES2, logMaxBytes;
10739
11018
  var init_app = __esm({
10740
11019
  "src/tui/app.ts"() {
10741
11020
  "use strict";
@@ -10759,6 +11038,34 @@ var init_app = __esm({
10759
11038
  init_completion();
10760
11039
  init_render_update();
10761
11040
  init_format();
11041
+ HELP_ENTRIES2 = [
11042
+ ["Enter", "send prompt (or queue while a turn is running)"],
11043
+ ["Alt+Enter", "newline in prompt"],
11044
+ ["Shift+Tab", "cycle agent modes (plan / accept-edits / etc.)"],
11045
+ ["Tab", "indent \xB7 slash-command completion"],
11046
+ null,
11047
+ ["\u2191 / \u2193", "prompt history \xB7 queue navigation"],
11048
+ ["\u2190/\u2192 Home/End", "cursor movement"],
11049
+ ["Alt+B / Alt+F", "word back / forward"],
11050
+ ["^A / ^E", "line start / end"],
11051
+ ["^W / ^U / ^K", "kill word / line / to end"],
11052
+ ["^Y", "yank last kill"],
11053
+ null,
11054
+ ["^P", "switch session (picker)"],
11055
+ ["^T", "next live session"],
11056
+ ["^V", "paste image from clipboard"],
11057
+ ["^O", "expand / collapse tools block"],
11058
+ null,
11059
+ ["^R / ^S", "history reverse / forward search"],
11060
+ ["PgUp / PgDn", "scroll scrollback"],
11061
+ ["Mouse wheel", "scroll scrollback (when mouse capture is on)"],
11062
+ null,
11063
+ ["^C", "cancel turn (twice to exit)"],
11064
+ ["Esc", "cancel turn and prefill draft"],
11065
+ ["^D", "exit (or delete-forward in prompt)"],
11066
+ ["^L", "force full redraw"],
11067
+ ["^G", "toggle this help"]
11068
+ ];
10762
11069
  logMaxBytes = 5 * 1024 * 1024;
10763
11070
  }
10764
11071
  });
@@ -10891,13 +11198,13 @@ New token: ${newToken}
10891
11198
  init_paths();
10892
11199
  init_config();
10893
11200
  init_service_token();
10894
- import * as fsp6 from "fs/promises";
11201
+ import * as fsp7 from "fs/promises";
10895
11202
  import { setTimeout as sleep2 } from "timers/promises";
10896
11203
 
10897
11204
  // src/daemon/server.ts
10898
11205
  init_config();
10899
11206
  import * as fs15 from "fs";
10900
- import * as fsp4 from "fs/promises";
11207
+ import * as fsp5 from "fs/promises";
10901
11208
  import Fastify from "fastify";
10902
11209
  import websocketPlugin from "@fastify/websocket";
10903
11210
  import pino from "pino";
@@ -11284,10 +11591,12 @@ var RegistryDocument = z2.object({
11284
11591
  extensions: z2.array(z2.unknown()).optional()
11285
11592
  });
11286
11593
  var Registry = class {
11287
- constructor(config) {
11594
+ constructor(config, options = {}) {
11288
11595
  this.config = config;
11596
+ this.options = options;
11289
11597
  }
11290
11598
  config;
11599
+ options;
11291
11600
  cache;
11292
11601
  async load() {
11293
11602
  if (this.cache && this.isFresh(this.cache.fetchedAt)) {
@@ -11337,7 +11646,12 @@ var Registry = class {
11337
11646
  }
11338
11647
  const raw = await response.json();
11339
11648
  const data = RegistryDocument.parse(raw);
11340
- return { fetchedAt: Date.now(), raw, data };
11649
+ const cached2 = { fetchedAt: Date.now(), raw, data };
11650
+ const hook = this.options.onFetched;
11651
+ if (hook) {
11652
+ void Promise.resolve().then(() => hook(data)).catch(() => void 0);
11653
+ }
11654
+ return cached2;
11341
11655
  }
11342
11656
  async readDiskCache() {
11343
11657
  let text;
@@ -11399,6 +11713,7 @@ function npxPackageBasename(agent) {
11399
11713
  return atIdx <= 0 ? afterSlash : afterSlash.slice(0, atIdx);
11400
11714
  }
11401
11715
  async function planSpawn(agent, callerArgs = [], options = {}) {
11716
+ const version = agent.version ?? "current";
11402
11717
  if (agent.distribution.npx) {
11403
11718
  const npx = agent.distribution.npx;
11404
11719
  const tail = callerArgs.length > 0 ? callerArgs : npx.args ?? [];
@@ -11406,13 +11721,14 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
11406
11721
  return {
11407
11722
  command: "npx",
11408
11723
  args: ["-y", npx.package, ...tail],
11409
- env: npx.env ?? {}
11724
+ env: npx.env ?? {},
11725
+ version
11410
11726
  };
11411
11727
  }
11412
11728
  const bin = npx.bin ?? npxPackageBasename(agent) ?? npx.package;
11413
11729
  const binPath = await ensureNpmPackage({
11414
11730
  agentId: agent.id,
11415
- version: agent.version ?? "current",
11731
+ version,
11416
11732
  packageSpec: npx.package,
11417
11733
  bin,
11418
11734
  registry: options.npmRegistry
@@ -11420,7 +11736,8 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
11420
11736
  return {
11421
11737
  command: binPath,
11422
11738
  args: tail,
11423
- env: npx.env ?? {}
11739
+ env: npx.env ?? {},
11740
+ version
11424
11741
  };
11425
11742
  }
11426
11743
  if (agent.distribution.binary) {
@@ -11432,14 +11749,15 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
11432
11749
  }
11433
11750
  const cmdPath = await ensureBinary({
11434
11751
  agentId: agent.id,
11435
- version: agent.version ?? "current",
11752
+ version,
11436
11753
  target
11437
11754
  });
11438
11755
  const tail = callerArgs.length > 0 ? callerArgs : target.args ?? [];
11439
11756
  return {
11440
11757
  command: cmdPath,
11441
11758
  args: tail,
11442
- env: target.env ?? {}
11759
+ env: target.env ?? {},
11760
+ version
11443
11761
  };
11444
11762
  }
11445
11763
  if (agent.distribution.uvx) {
@@ -11448,7 +11766,8 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
11448
11766
  return {
11449
11767
  command: "uvx",
11450
11768
  args: [uvx.package, ...tail],
11451
- env: uvx.env ?? {}
11769
+ env: uvx.env ?? {},
11770
+ version
11452
11771
  };
11453
11772
  }
11454
11773
  throw new Error(`Agent ${agent.id} has no usable distribution method.`);
@@ -11539,6 +11858,9 @@ init_connection();
11539
11858
  var DEFAULT_STDERR_TAIL_BYTES = 4096;
11540
11859
  var AgentInstance = class _AgentInstance {
11541
11860
  agentId;
11861
+ // Version this process was spawned from — used by the registry-fetch
11862
+ // prune sweep to skip install dirs belonging to a live agent.
11863
+ version;
11542
11864
  cwd;
11543
11865
  connection;
11544
11866
  child;
@@ -11550,6 +11872,7 @@ var AgentInstance = class _AgentInstance {
11550
11872
  exitHandlers = [];
11551
11873
  constructor(opts, child) {
11552
11874
  this.agentId = opts.agentId;
11875
+ this.version = opts.plan.version;
11553
11876
  this.cwd = opts.cwd;
11554
11877
  this.child = child;
11555
11878
  this.stderrTailBytes = opts.stderrTailBytes ?? DEFAULT_STDERR_TAIL_BYTES;
@@ -12213,6 +12536,23 @@ var SessionManager = class {
12213
12536
  get(sessionId) {
12214
12537
  return this.sessions.get(sessionId);
12215
12538
  }
12539
+ // Snapshot of which agent versions are currently in use by live
12540
+ // sessions, keyed by agentId. Read by the registry-fetch prune sweep
12541
+ // so it can skip install dirs that still back a running process.
12542
+ activeAgentVersions() {
12543
+ const out = /* @__PURE__ */ new Map();
12544
+ for (const session of this.sessions.values()) {
12545
+ const id = session.agent.agentId;
12546
+ const version = session.agent.version;
12547
+ let set = out.get(id);
12548
+ if (!set) {
12549
+ set = /* @__PURE__ */ new Set();
12550
+ out.set(id, set);
12551
+ }
12552
+ set.add(version);
12553
+ }
12554
+ return out;
12555
+ }
12216
12556
  // Resolve a user-typed session id (which may have the hydra_session_
12217
12557
  // prefix stripped — that's what `sessions list` and the picker show) to
12218
12558
  // the canonical form that actually exists. Tries the input as-given
@@ -13189,12 +13529,91 @@ function withCode2(err, code) {
13189
13529
 
13190
13530
  // src/daemon/server.ts
13191
13531
  init_paths();
13532
+
13533
+ // src/core/agent-prune.ts
13534
+ init_paths();
13535
+ import * as fsp4 from "fs/promises";
13536
+ import * as path8 from "path";
13537
+ var logSink3 = (msg) => {
13538
+ process.stderr.write(msg + "\n");
13539
+ };
13540
+ function setAgentPruneLogger(log) {
13541
+ logSink3 = log ?? ((msg) => process.stderr.write(msg + "\n"));
13542
+ }
13543
+ async function pruneStaleAgentVersions(registry, sessionManager) {
13544
+ const platformKey = currentPlatformKey();
13545
+ if (!platformKey) {
13546
+ return;
13547
+ }
13548
+ const doc = await registry.load();
13549
+ const desiredByAgent = /* @__PURE__ */ new Map();
13550
+ for (const a of doc.agents) {
13551
+ desiredByAgent.set(a.id, a.version ?? "current");
13552
+ }
13553
+ const activeByAgent = sessionManager.activeAgentVersions();
13554
+ const platformDir = path8.join(paths.agentsDir(), platformKey);
13555
+ let agentEntries;
13556
+ try {
13557
+ agentEntries = await fsp4.readdir(platformDir, { withFileTypes: true });
13558
+ } catch (err) {
13559
+ const e = err;
13560
+ if (e.code === "ENOENT") {
13561
+ return;
13562
+ }
13563
+ logSink3(`hydra-acp: prune: failed to read ${platformDir}: ${e.message}`);
13564
+ return;
13565
+ }
13566
+ for (const agentEntry of agentEntries) {
13567
+ if (!agentEntry.isDirectory()) {
13568
+ continue;
13569
+ }
13570
+ const agentId = agentEntry.name;
13571
+ const desired = desiredByAgent.get(agentId);
13572
+ if (desired === void 0) {
13573
+ continue;
13574
+ }
13575
+ const activeVersions = activeByAgent.get(agentId) ?? /* @__PURE__ */ new Set();
13576
+ const agentDir = path8.join(platformDir, agentId);
13577
+ let versionEntries;
13578
+ try {
13579
+ versionEntries = await fsp4.readdir(agentDir, { withFileTypes: true });
13580
+ } catch (err) {
13581
+ logSink3(
13582
+ `hydra-acp: prune: failed to read ${agentDir}: ${err.message}`
13583
+ );
13584
+ continue;
13585
+ }
13586
+ for (const versionEntry of versionEntries) {
13587
+ if (!versionEntry.isDirectory()) {
13588
+ continue;
13589
+ }
13590
+ const version = versionEntry.name;
13591
+ if (version === desired) {
13592
+ continue;
13593
+ }
13594
+ if (activeVersions.has(version)) {
13595
+ continue;
13596
+ }
13597
+ const versionDir = path8.join(agentDir, version);
13598
+ try {
13599
+ await fsp4.rm(versionDir, { recursive: true, force: true });
13600
+ logSink3(`hydra-acp: pruned stale ${agentId} ${version} (${versionDir})`);
13601
+ } catch (err) {
13602
+ logSink3(
13603
+ `hydra-acp: prune: failed to remove ${versionDir}: ${err.message}`
13604
+ );
13605
+ }
13606
+ }
13607
+ }
13608
+ }
13609
+
13610
+ // src/daemon/server.ts
13192
13611
  init_hydra_version();
13193
13612
 
13194
13613
  // src/core/session-tokens.ts
13195
13614
  init_paths();
13196
13615
  import * as fs13 from "fs/promises";
13197
- import * as path8 from "path";
13616
+ import * as path9 from "path";
13198
13617
  import { createHash, randomBytes, timingSafeEqual } from "crypto";
13199
13618
  var TOKEN_PREFIX = "hydra_session_";
13200
13619
  var DEFAULT_TTL_SEC = 60 * 60 * 24 * 30;
@@ -13202,7 +13621,7 @@ var ID_LENGTH = 12;
13202
13621
  var TOKEN_BYTES = 32;
13203
13622
  var WRITE_DEBOUNCE_MS = 50;
13204
13623
  function tokensFilePath() {
13205
- return path8.join(paths.home(), "session-tokens.json");
13624
+ return path9.join(paths.home(), "session-tokens.json");
13206
13625
  }
13207
13626
  function sha256Hex(input) {
13208
13627
  return createHash("sha256").update(input).digest("hex");
@@ -13872,12 +14291,12 @@ import { z as z6 } from "zod";
13872
14291
  // src/core/password.ts
13873
14292
  init_paths();
13874
14293
  import * as fs14 from "fs/promises";
13875
- import * as path9 from "path";
14294
+ import * as path10 from "path";
13876
14295
  import { randomBytes as randomBytes2, scrypt, timingSafeEqual as timingSafeEqual2 } from "crypto";
13877
14296
  import { promisify } from "util";
13878
14297
  var scryptAsync = promisify(scrypt);
13879
14298
  function passwordHashPath() {
13880
- return path9.join(paths.home(), "password-hash");
14299
+ return path10.join(paths.home(), "password-hash");
13881
14300
  }
13882
14301
  var DEFAULT_N = 1 << 15;
13883
14302
  var DEFAULT_R = 8;
@@ -14464,10 +14883,10 @@ function bindClientToSession(connection, session, state, clientInfo, callerClien
14464
14883
  async function startDaemon(config, serviceToken) {
14465
14884
  ensureLoopbackOrTls(config);
14466
14885
  const httpsOptions = config.daemon.tls ? {
14467
- key: await fsp4.readFile(config.daemon.tls.key),
14468
- cert: await fsp4.readFile(config.daemon.tls.cert)
14886
+ key: await fsp5.readFile(config.daemon.tls.key),
14887
+ cert: await fsp5.readFile(config.daemon.tls.cert)
14469
14888
  } : void 0;
14470
- await fsp4.mkdir(paths.home(), { recursive: true });
14889
+ await fsp5.mkdir(paths.home(), { recursive: true });
14471
14890
  const { stream: logStream, fileStream } = await buildLogStream(
14472
14891
  config.daemon.logLevel
14473
14892
  );
@@ -14511,7 +14930,12 @@ async function startDaemon(config, serviceToken) {
14511
14930
  5 * 60 * 1e3
14512
14931
  );
14513
14932
  sweepInterval.unref();
14514
- const registry = new Registry(config);
14933
+ const registry = new Registry(config, {
14934
+ onFetched: () => {
14935
+ void pruneStaleAgentVersions(registry, manager);
14936
+ }
14937
+ });
14938
+ setAgentPruneLogger((msg) => app.log.info(msg));
14515
14939
  const agentLogger = {
14516
14940
  info: (msg) => app.log.info(msg),
14517
14941
  warn: (msg) => app.log.warn(msg)
@@ -14552,8 +14976,8 @@ async function startDaemon(config, serviceToken) {
14552
14976
  await app.listen({ host: config.daemon.host, port: config.daemon.port });
14553
14977
  const address = app.server.address();
14554
14978
  const boundPort = address && typeof address === "object" ? address.port : config.daemon.port;
14555
- await fsp4.mkdir(paths.home(), { recursive: true });
14556
- await fsp4.writeFile(
14979
+ await fsp5.mkdir(paths.home(), { recursive: true });
14980
+ await fsp5.writeFile(
14557
14981
  paths.pidFile(),
14558
14982
  JSON.stringify({
14559
14983
  pid: process.pid,
@@ -14587,6 +15011,7 @@ async function startDaemon(config, serviceToken) {
14587
15011
  await manager.flushMetaWrites();
14588
15012
  setBinaryInstallLogger(null);
14589
15013
  setNpmInstallLogger(null);
15014
+ setAgentPruneLogger(null);
14590
15015
  await app.close();
14591
15016
  try {
14592
15017
  fs15.unlinkSync(paths.pidFile());
@@ -14629,12 +15054,12 @@ init_daemon_bootstrap();
14629
15054
 
14630
15055
  // src/cli/commands/log-tail.ts
14631
15056
  import * as fs16 from "fs";
14632
- import * as fsp5 from "fs/promises";
15057
+ import * as fsp6 from "fs/promises";
14633
15058
  async function runLogTail(logPath, argv, notFoundMessage) {
14634
15059
  const opts = parseLogTailFlags(argv);
14635
15060
  let stat4;
14636
15061
  try {
14637
- stat4 = await fsp5.stat(logPath);
15062
+ stat4 = await fsp6.stat(logPath);
14638
15063
  } catch (err) {
14639
15064
  const e = err;
14640
15065
  if (e.code === "ENOENT") {
@@ -14660,14 +15085,14 @@ async function runLogTail(logPath, argv, notFoundMessage) {
14660
15085
  setImmediate(async () => {
14661
15086
  pending = false;
14662
15087
  try {
14663
- const s = await fsp5.stat(logPath);
15088
+ const s = await fsp6.stat(logPath);
14664
15089
  if (s.size <= position) {
14665
15090
  if (s.size < position) {
14666
15091
  position = s.size;
14667
15092
  }
14668
15093
  return;
14669
15094
  }
14670
- const fd = await fsp5.open(logPath, "r");
15095
+ const fd = await fsp6.open(logPath, "r");
14671
15096
  try {
14672
15097
  const buf = Buffer.alloc(s.size - position);
14673
15098
  await fd.read(buf, 0, buf.length, position);
@@ -14694,7 +15119,7 @@ async function printTail(logPath, fileSize, lines) {
14694
15119
  return fileSize;
14695
15120
  }
14696
15121
  const CHUNK = 64 * 1024;
14697
- const fd = await fsp5.open(logPath, "r");
15122
+ const fd = await fsp6.open(logPath, "r");
14698
15123
  try {
14699
15124
  let position = fileSize;
14700
15125
  let collected = "";
@@ -14866,7 +15291,7 @@ async function runDaemonStatus() {
14866
15291
  }
14867
15292
  async function readPidFile() {
14868
15293
  try {
14869
- const raw = await fsp6.readFile(paths.pidFile(), "utf8");
15294
+ const raw = await fsp7.readFile(paths.pidFile(), "utf8");
14870
15295
  return JSON.parse(raw);
14871
15296
  } catch (err) {
14872
15297
  const e = err;
@@ -14892,7 +15317,7 @@ init_sessions();
14892
15317
  init_config();
14893
15318
  init_service_token();
14894
15319
  init_paths();
14895
- import * as fsp7 from "fs/promises";
15320
+ import * as fsp8 from "fs/promises";
14896
15321
  init_sessions();
14897
15322
  async function runExtensionsList() {
14898
15323
  const config = await loadConfig();
@@ -15091,11 +15516,11 @@ async function runExtensionsRemove(name) {
15091
15516
  }
15092
15517
  }
15093
15518
  async function readRawConfig() {
15094
- const raw = await fsp7.readFile(paths.config(), "utf8");
15519
+ const raw = await fsp8.readFile(paths.config(), "utf8");
15095
15520
  return JSON.parse(raw);
15096
15521
  }
15097
15522
  async function writeRawConfig(raw) {
15098
- await fsp7.writeFile(
15523
+ await fsp8.writeFile(
15099
15524
  paths.config(),
15100
15525
  JSON.stringify(raw, null, 2) + "\n",
15101
15526
  { encoding: "utf8", mode: 384 }