@hmharness/cli 0.4.3 → 0.5.0

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/tui.d.ts CHANGED
@@ -124,4 +124,9 @@ export declare class TuiRuntime {
124
124
  private onKey;
125
125
  render(): void;
126
126
  }
127
+ /** First user line of a session file WITHOUT a full parse: peek the first
128
+ * 64KB, scan lines for the first user event. With hundreds of sessions a
129
+ * full loadTranscript per row would stall the picker for seconds; the user
130
+ * event is always near the head, so the peek is O(64KB) per session. */
131
+ export declare function firstUserLinePeek(file: string): Promise<string>;
127
132
  export declare function tui(yes: boolean, noWeb?: boolean): Promise<void>;
package/dist/tui.js CHANGED
@@ -13,6 +13,7 @@
13
13
  import { stdin, stdout } from 'node:process';
14
14
  import { basename, join } from 'node:path';
15
15
  import { createRequire } from 'node:module';
16
+ import { open as fopen } from 'node:fs/promises';
16
17
  import { loadConfig, homeDir, resolveProvider, listProviders, setChatRoute, setLocale, PROVIDER_PRESETS, addProviders, detectLocalProviders } from '@hmharness/kernel';
17
18
  import { listDrafts, listSkills, runBench, runEvolution } from '@hmharness/evolution';
18
19
  import { buildRegistry, runAgentTask, strings } from '@hmharness/agent';
@@ -871,6 +872,34 @@ export class TuiRuntime {
871
872
  }
872
873
  }
873
874
  /* ---------------- driver ---------------- */
875
+ /** First user line of a session file WITHOUT a full parse: peek the first
876
+ * 64KB, scan lines for the first user event. With hundreds of sessions a
877
+ * full loadTranscript per row would stall the picker for seconds; the user
878
+ * event is always near the head, so the peek is O(64KB) per session. */
879
+ export async function firstUserLinePeek(file) {
880
+ try {
881
+ const fh = await fopen(file, 'r');
882
+ try {
883
+ const buf = Buffer.alloc(65_536);
884
+ const { bytesRead } = await fh.read(buf, 0, 65_536, 0);
885
+ for (const line of buf.toString('utf8', 0, bytesRead).split('\n')) {
886
+ if (!line.includes('"user"'))
887
+ continue;
888
+ try {
889
+ const ev = JSON.parse(line);
890
+ if (ev.t === 'user' && typeof ev.text === 'string')
891
+ return ev.text;
892
+ }
893
+ catch { /* partial line at the buffer edge - no preview */ }
894
+ }
895
+ }
896
+ finally {
897
+ await fh.close();
898
+ }
899
+ }
900
+ catch { /* unreadable file - no preview */ }
901
+ return '';
902
+ }
874
903
  export async function tui(yes, noWeb = false) {
875
904
  let cfg = await loadConfig();
876
905
  let autoApprove = yes || cfg.approval === 'auto';
@@ -958,22 +987,21 @@ export async function tui(yes, noWeb = false) {
958
987
  const arg = line.slice(8).trim();
959
988
  const { latestSession, loadTranscript } = await import('@hmharness/kernel');
960
989
  const { readdir } = await import('node:fs/promises');
961
- // refresh the picker rows: id prefix + first user line as the preview
990
+ // ALL sessions, newest first - no arbitrary "recent 8" cap (user
991
+ // challenge: "不应该是所有历史会话吗"). Previews use the 64KB peek,
992
+ // never a full parse, so hundreds of rows still open instantly; the
993
+ // palette scrolls and head-prefix filtering narrows fast.
962
994
  let files = [];
963
995
  try {
964
996
  files = (await readdir(join(home, 'sessions'))).filter((f) => f.endsWith('.jsonl'));
965
997
  }
966
998
  catch { /* none */ }
967
999
  files.sort();
968
- const recent = files.slice(-8).reverse();
1000
+ const recent = files.reverse();
969
1001
  const rows = [];
970
1002
  for (const f of recent) {
971
- try {
972
- const tr = await loadTranscript(join(home, 'sessions', f));
973
- const firstUser = tr?.messages.find((m) => m.role === 'user')?.content ?? '';
974
- rows.push({ name: f.slice(0, 18), desc: (firstUser || '(no user line)').replace(/\n/g, ' ').slice(0, 56) });
975
- }
976
- catch { /* skip unreadable */ }
1003
+ const firstUser = await firstUserLinePeek(join(home, 'sessions', f));
1004
+ rows.push({ name: f.slice(0, 18), desc: (firstUser || '(无预览)').replace(/\n/g, ' ').slice(0, 56) });
977
1005
  }
978
1006
  rt.setSessionChoices(rows);
979
1007
  if (!arg) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/cli",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "hmharness command line: one-shot tasks, an interactive REPL, a fullscreen TUI, the web frontend, and direct tool invocation.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -43,11 +43,11 @@
43
43
  "build": "tsc -p tsconfig.build.json"
44
44
  },
45
45
  "dependencies": {
46
- "@hmharness/kernel": "0.4.3",
47
- "@hmharness/evolution": "0.4.3",
48
- "@hmharness/domain-harmony": "0.4.3",
49
- "@hmharness/domain-ops": "0.4.3",
50
- "@hmharness/agent": "0.4.3",
51
- "@hmharness/web": "0.4.3"
46
+ "@hmharness/kernel": "0.5.0",
47
+ "@hmharness/evolution": "0.5.0",
48
+ "@hmharness/domain-harmony": "0.5.0",
49
+ "@hmharness/domain-ops": "0.5.0",
50
+ "@hmharness/agent": "0.5.0",
51
+ "@hmharness/web": "0.5.0"
52
52
  }
53
53
  }