@hydra-acp/cli 0.1.46 → 0.1.48

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 +109 -40
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4916,6 +4916,25 @@ function buildCombinedHistory(global, session) {
4916
4916
  const filteredGlobal = global.filter((e) => !sessionSet.has(e));
4917
4917
  return [...filteredGlobal, ...session];
4918
4918
  }
4919
+ function mergeReplayedEntries(existing, replayed, cap = HISTORY_CAP) {
4920
+ if (replayed.length === 0) {
4921
+ return existing;
4922
+ }
4923
+ const seen = new Set(existing);
4924
+ let out = existing;
4925
+ for (const raw of replayed) {
4926
+ const trimmed = raw.replace(/\n+$/, "");
4927
+ if (trimmed.length === 0) {
4928
+ continue;
4929
+ }
4930
+ if (seen.has(trimmed)) {
4931
+ continue;
4932
+ }
4933
+ seen.add(trimmed);
4934
+ out = appendEntry(out, trimmed, cap);
4935
+ }
4936
+ return out;
4937
+ }
4919
4938
  var HISTORY_CAP;
4920
4939
  var init_history = __esm({
4921
4940
  "src/tui/history.ts"() {
@@ -6388,7 +6407,7 @@ var init_input = __esm({
6388
6407
  case "ctrl-r":
6389
6408
  return this.startHistorySearch();
6390
6409
  case "ctrl-s":
6391
- return [];
6410
+ return this.amend();
6392
6411
  case "ctrl-u":
6393
6412
  this.killLine();
6394
6413
  return [];
@@ -6906,22 +6925,30 @@ var init_input = __esm({
6906
6925
  this.clearBuffer();
6907
6926
  return [{ type: "send", text, planMode, attachments }];
6908
6927
  }
6909
- // Shift+Enter: amend the in-flight turn. Editing a queued slot
6910
- // delegates to the existing queue-edit / queue-remove path Shift+Enter
6911
- // there has no special meaning since the entry is already queued (not
6912
- // running). With an empty draft and no attachments we emit nothing
6913
- // (no-op). Otherwise emit an "amend" effect; the app decides whether
6914
- // to route through amend_prompt or fall through to a regular send.
6928
+ // Shift+Enter (also Ctrl+Enter / ^S): amend the in-flight turn.
6929
+ // While editing a queued slot, this is the "drop and amend" chord:
6930
+ // emit queue-remove for the slot AND amend with the loaded (possibly
6931
+ // edited) text, so the queued prompt becomes the amendment for the
6932
+ // running turn in a single keystroke. Empty buffer + no attachments
6933
+ // on a slot collapses to just queue-remove (matches empty-Enter).
6934
+ // Outside queue editing, an empty draft is a no-op. The app decides
6935
+ // whether to route the amend through amend_prompt or fall through to
6936
+ // a regular send when no turn is in flight.
6915
6937
  amend() {
6916
6938
  const text = this.bufferText();
6917
6939
  if (this.queueIndex >= 0 && this.queueIndex < this.queue.length) {
6918
6940
  const index = this.queueIndex;
6941
+ const planMode2 = this.planMode;
6919
6942
  const attachments2 = [...this.attachments];
6943
+ const empty = text.trim().length === 0 && attachments2.length === 0;
6920
6944
  this.clearBuffer();
6921
- if (text.trim().length === 0) {
6945
+ if (empty) {
6922
6946
  return [{ type: "queue-remove", index }];
6923
6947
  }
6924
- return [{ type: "queue-edit", index, text, attachments: attachments2 }];
6948
+ return [
6949
+ { type: "queue-remove", index },
6950
+ { type: "amend", text, planMode: planMode2, attachments: attachments2 }
6951
+ ];
6925
6952
  }
6926
6953
  if (text.trim().length === 0 && this.attachments.length === 0) {
6927
6954
  return [];
@@ -9956,6 +9983,9 @@ uncaught: ${err.stack ?? err.message}
9956
9983
  });
9957
9984
 
9958
9985
  // src/tui/picker.ts
9986
+ function createPickerPrefs() {
9987
+ return { filters: { cwdOnly: false, hostFilter: "__local" } };
9988
+ }
9959
9989
  async function pickSession(term, opts) {
9960
9990
  process.stdout.write("\x1B[<u");
9961
9991
  process.stdout.write("\x1B[?2004l");
@@ -9981,18 +10011,25 @@ async function pickSession(term, opts) {
9981
10011
  return b.updatedAt.slice(0, 16).localeCompare(a.updatedAt.slice(0, 16));
9982
10012
  });
9983
10013
  };
9984
- let cwdOnly = false;
9985
- let hostFilter = "__local";
9986
- if (opts.currentSessionId !== void 0) {
10014
+ const prefs = opts.prefs ?? createPickerPrefs();
10015
+ if (opts.prefs === void 0 && opts.currentSessionId !== void 0) {
9987
10016
  const current = opts.sessions.find(
9988
10017
  (s) => s.sessionId === opts.currentSessionId
9989
10018
  );
9990
10019
  if (current?.importedFromMachine) {
9991
- hostFilter = "__all";
10020
+ prefs.filters.hostFilter = "__all";
9992
10021
  }
9993
10022
  }
9994
10023
  let allSessions = sortSessions(opts.sessions);
9995
- let visible = filterByHost(allSessions, hostFilter);
10024
+ const applyPrefsFilters = (sessions) => {
10025
+ let base = sessions;
10026
+ if (prefs.filters.cwdOnly) {
10027
+ base = base.filter((s) => s.cwd === opts.cwd);
10028
+ }
10029
+ base = filterByHost(base, prefs.filters.hostFilter);
10030
+ return base;
10031
+ };
10032
+ let visible = applyPrefsFilters(allSessions);
9996
10033
  let rows = visible.map((s) => toRow(s, Date.now()));
9997
10034
  let widths = computeWidths(rows);
9998
10035
  let total = 1 + visible.length;
@@ -10060,11 +10097,7 @@ async function pickSession(term, opts) {
10060
10097
  computeLayout();
10061
10098
  };
10062
10099
  const applyFilter = () => {
10063
- let base = allSessions;
10064
- if (cwdOnly) {
10065
- base = base.filter((s) => s.cwd === opts.cwd);
10066
- }
10067
- base = filterByHost(base, hostFilter);
10100
+ const base = applyPrefsFilters(allSessions);
10068
10101
  if (searchActive && searchTerm.length > 0) {
10069
10102
  visible = base.filter((s) => matchesSearch(s, searchTerm));
10070
10103
  } else {
@@ -10150,12 +10183,12 @@ async function pickSession(term, opts) {
10150
10183
  const above = scrollOffset;
10151
10184
  const below = Math.max(0, visible.length - scrollOffset - viewportSize);
10152
10185
  const parts = [];
10153
- if (cwdOnly) {
10186
+ if (prefs.filters.cwdOnly) {
10154
10187
  parts.push("cwd-only");
10155
10188
  }
10156
- if (hostFilter !== "__all") {
10189
+ if (prefs.filters.hostFilter !== "__all") {
10157
10190
  parts.push(
10158
- hostFilter === "__local" ? "host: local" : `host: ${hostFilter}`
10191
+ prefs.filters.hostFilter === "__local" ? "host: local" : `host: ${prefs.filters.hostFilter}`
10159
10192
  );
10160
10193
  }
10161
10194
  if (above > 0) {
@@ -10785,7 +10818,7 @@ ${cells}`;
10785
10818
  }
10786
10819
  if (name === "o" || name === "O") {
10787
10820
  const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
10788
- cwdOnly = !cwdOnly;
10821
+ prefs.filters.cwdOnly = !prefs.filters.cwdOnly;
10789
10822
  applyFilter();
10790
10823
  if (keepId !== void 0) {
10791
10824
  const idx = visible.findIndex((s) => s.sessionId === keepId);
@@ -10799,7 +10832,10 @@ ${cells}`;
10799
10832
  }
10800
10833
  if (name === "h" || name === "H") {
10801
10834
  const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
10802
- hostFilter = nextHostFilter(hostFilter, allSessions);
10835
+ prefs.filters.hostFilter = nextHostFilter(
10836
+ prefs.filters.hostFilter,
10837
+ allSessions
10838
+ );
10803
10839
  applyFilter();
10804
10840
  if (keepId !== void 0) {
10805
10841
  const idx = visible.findIndex((s) => s.sessionId === keepId);
@@ -11198,7 +11234,7 @@ async function promptForImportCwd(term, session, opts = {}) {
11198
11234
  const contentHeight = 9;
11199
11235
  layout = drawBox(term, {
11200
11236
  contentHeight,
11201
- title: "Run locally \u2014 choose cwd"
11237
+ title: "Fork locally \u2014 choose cwd"
11202
11238
  });
11203
11239
  const innerW = layout.contentW;
11204
11240
  const headerRows = [
@@ -11437,7 +11473,10 @@ async function promptForImportAction(term, session) {
11437
11473
  const shortId2 = stripHydraSessionPrefix(session.sessionId);
11438
11474
  const fromMachine = session.importedFromMachine ?? "another machine";
11439
11475
  const originalCwd = shortenHomePath(session.cwd);
11440
- let selected = 0;
11476
+ let selected = ACTION_CHOICES.findIndex((c) => c.key === "view");
11477
+ if (selected < 0) {
11478
+ selected = 0;
11479
+ }
11441
11480
  const render = () => {
11442
11481
  const choiceRows = ACTION_CHOICES.length * 2;
11443
11482
  const contentHeight = 7 + choiceRows + 2;
@@ -11482,7 +11521,7 @@ async function promptForImportAction(term, session) {
11482
11521
  }
11483
11522
  row++;
11484
11523
  term.moveTo(layout.contentX, layout.contentY + row);
11485
- term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 r/v jump \xB7 Esc back");
11524
+ term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 f/v jump \xB7 Esc back");
11486
11525
  return layout;
11487
11526
  };
11488
11527
  render();
@@ -11583,10 +11622,10 @@ var init_import_action_prompt = __esm({
11583
11622
  init_prompt_utils();
11584
11623
  ACTION_CHOICES = [
11585
11624
  {
11586
- key: "run-local",
11587
- label: "Run locally",
11588
- hotkey: "r",
11589
- description: "spawn the agent on this machine with a local cwd"
11625
+ key: "fork-local",
11626
+ label: "Fork locally",
11627
+ hotkey: "f",
11628
+ description: "spawn a local fork \u2014 original imported copy stays as-is"
11590
11629
  },
11591
11630
  {
11592
11631
  key: "view",
@@ -12460,6 +12499,7 @@ async function runTuiApp(opts) {
12460
12499
  const viewPrefs = {
12461
12500
  showThoughts: config.tui.showThoughts
12462
12501
  };
12502
+ const pickerPrefs = createPickerPrefs();
12463
12503
  let altScreenEngaged = false;
12464
12504
  const enterAltScreen = () => {
12465
12505
  if (altScreenEngaged) {
@@ -12487,7 +12527,15 @@ async function runTuiApp(opts) {
12487
12527
  let nextOpts = opts;
12488
12528
  try {
12489
12529
  while (nextOpts !== null) {
12490
- nextOpts = await runSession(term, config, target, nextOpts, exitHint, viewPrefs);
12530
+ nextOpts = await runSession(
12531
+ term,
12532
+ config,
12533
+ target,
12534
+ nextOpts,
12535
+ exitHint,
12536
+ viewPrefs,
12537
+ pickerPrefs
12538
+ );
12491
12539
  }
12492
12540
  } finally {
12493
12541
  leaveAltScreen();
@@ -12507,8 +12555,8 @@ async function runTuiApp(opts) {
12507
12555
  );
12508
12556
  }
12509
12557
  }
12510
- async function runSession(term, config, target, opts, exitHint, viewPrefs) {
12511
- const ctx = await resolveSession(term, config, target, opts);
12558
+ async function runSession(term, config, target, opts, exitHint, viewPrefs, pickerPrefs) {
12559
+ const ctx = await resolveSession(term, config, target, opts, pickerPrefs);
12512
12560
  if (!ctx) {
12513
12561
  term.grabInput(false);
12514
12562
  return null;
@@ -13017,6 +13065,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13017
13065
  history: buildCombinedHistory(globalHistory, history)
13018
13066
  });
13019
13067
  dispatcherRef = dispatcher;
13068
+ let livePeerHistoryRecording = false;
13020
13069
  const recordHistoryEntry = (entry) => {
13021
13070
  const trimmed = entry.replace(/\n+$/, "");
13022
13071
  if (trimmed.length === 0) {
@@ -13355,10 +13404,10 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13355
13404
  const amendDesc = "amend the in-flight turn (cancel + replace)";
13356
13405
  const head = config.tui.defaultEnterAction === "amend" ? [
13357
13406
  ["Enter", amendDesc],
13358
- ["Ctrl+Enter / Shift+Enter", enqueueDesc]
13407
+ ["Ctrl+Enter / Shift+Enter / ^S", enqueueDesc]
13359
13408
  ] : [
13360
13409
  ["Enter", enqueueDesc],
13361
- ["Ctrl+Enter / Shift+Enter", amendDesc]
13410
+ ["Ctrl+Enter / Shift+Enter / ^S", amendDesc]
13362
13411
  ];
13363
13412
  return [...head, ...HELP_ENTRIES_TAIL];
13364
13413
  };
@@ -13426,7 +13475,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13426
13475
  sessions,
13427
13476
  config,
13428
13477
  target,
13429
- currentSessionId: resolvedSessionId
13478
+ currentSessionId: resolvedSessionId,
13479
+ prefs: pickerPrefs
13430
13480
  });
13431
13481
  if (choice2.kind === "abort") {
13432
13482
  screen.start({ skipFullscreen: true });
@@ -14256,6 +14306,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
14256
14306
  return;
14257
14307
  }
14258
14308
  if (event.kind === "user-text") {
14309
+ if (livePeerHistoryRecording) {
14310
+ recordHistoryEntry(event.text);
14311
+ }
14259
14312
  closeAgentText();
14260
14313
  if (toolsBlockStartedAt !== null) {
14261
14314
  toolsBlockEndedAt = Date.now();
@@ -14388,6 +14441,12 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
14388
14441
  };
14389
14442
  const buffered = bufferedEvents;
14390
14443
  bufferedEvents = [];
14444
+ const replayedPromptTexts = [];
14445
+ for (const event of buffered) {
14446
+ if (event.kind === "user-text" && typeof event.text === "string") {
14447
+ replayedPromptTexts.push(event.text);
14448
+ }
14449
+ }
14391
14450
  screen.pauseRepaint();
14392
14451
  try {
14393
14452
  for (const event of buffered) {
@@ -14396,6 +14455,15 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
14396
14455
  } finally {
14397
14456
  screen.resumeRepaint();
14398
14457
  }
14458
+ if (replayedPromptTexts.length > 0) {
14459
+ const merged = mergeReplayedEntries(history, replayedPromptTexts);
14460
+ if (merged !== history) {
14461
+ history = merged;
14462
+ dispatcher.setHistory(buildCombinedHistory(globalHistory, history));
14463
+ saveHistory(historyFile, history).catch(() => void 0);
14464
+ }
14465
+ }
14466
+ livePeerHistoryRecording = true;
14399
14467
  if (initialTurnStartedAt !== void 0 && pendingTurns > 0) {
14400
14468
  sessionBusySince = initialTurnStartedAt;
14401
14469
  screen.setBanner({
@@ -14550,7 +14618,7 @@ connection lost: ${err.message}
14550
14618
  }
14551
14619
  return await sessionDone;
14552
14620
  }
14553
- async function resolveSession(term, config, target, opts) {
14621
+ async function resolveSession(term, config, target, opts, pickerPrefs) {
14554
14622
  const cwd = opts.cwd ?? process.cwd();
14555
14623
  if (opts.sessionId) {
14556
14624
  const ctx = {
@@ -14586,7 +14654,8 @@ async function resolveSession(term, config, target, opts) {
14586
14654
  cwd,
14587
14655
  sessions,
14588
14656
  config,
14589
- target
14657
+ target,
14658
+ prefs: pickerPrefs
14590
14659
  });
14591
14660
  if (choice.kind === "abort") {
14592
14661
  return null;
@@ -14834,7 +14903,7 @@ var init_app = __esm({
14834
14903
  ["^V", "paste image from clipboard"],
14835
14904
  ["^O", "expand / collapse tools block"],
14836
14905
  null,
14837
- ["^R / ^S", "history reverse / forward search"],
14906
+ ["^R", "history reverse search (^S walks forward once engaged)"],
14838
14907
  ["PgUp / PgDn", "scroll scrollback"],
14839
14908
  ["Mouse wheel", "scroll scrollback (when mouse capture is on)"],
14840
14909
  ["^X", "toggle mouse capture (wheel scroll vs. text selection)"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "Multi-client ACP session daemon: spawn agents, attach over WSS, multiplex sessions across editors.",
5
5
  "license": "MIT",
6
6
  "type": "module",