@hmharness/cli 0.6.7 → 0.7.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/main.js +41 -0
- package/dist/mcp-server.js +5 -2
- package/dist/tui.d.ts +3 -1
- package/dist/tui.js +17 -1
- package/package.json +8 -7
package/dist/main.js
CHANGED
|
@@ -31,6 +31,7 @@ const DIM = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
|
31
31
|
const CYAN = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
32
32
|
const YELLOW = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
33
33
|
const GREEN = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
34
|
+
const RED = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
34
35
|
async function uiStrings() {
|
|
35
36
|
const cfg = await loadConfig();
|
|
36
37
|
return strings((cfg.locale ?? 'zh'));
|
|
@@ -639,6 +640,46 @@ flags:
|
|
|
639
640
|
: DIM(' no backups yet - run "hmh state backup"\n'));
|
|
640
641
|
return;
|
|
641
642
|
}
|
|
643
|
+
if (cmd === 'replay') {
|
|
644
|
+
// V2 M1: typed trajectory replay. Bare `hmh replay` lists recent runs;
|
|
645
|
+
// `hmh replay <run-id>` renders the event timeline; --json dumps raw.
|
|
646
|
+
await initHome();
|
|
647
|
+
const { jsonlTrajectoryStore } = await import('@hmharness/observability');
|
|
648
|
+
const store = jsonlTrajectoryStore(homeDir());
|
|
649
|
+
const id = rest.find((a) => !a.startsWith('-'));
|
|
650
|
+
if (!id) {
|
|
651
|
+
const runs = await store.listRuns(20);
|
|
652
|
+
stdout.write(runs.length
|
|
653
|
+
? runs.map((r) => {
|
|
654
|
+
const okc = r.outcome ? (r.outcome.success ? GREEN('✓') : RED('✗')) : YELLOW('…');
|
|
655
|
+
const m = r.metrics ? ` · ${r.metrics.turns ?? '?'}t/${r.metrics.toolUses ?? '?'}x` : '';
|
|
656
|
+
return ` ${okc} ${r.runId} ${DIM(r.task.slice(0, 52))}${m}`;
|
|
657
|
+
}).join('\n') + '\n'
|
|
658
|
+
: DIM(' no runs yet - every task now records a trajectory automatically\n'));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (rest.includes('--json')) {
|
|
662
|
+
stdout.write(await store.exportRun(id, 'json'));
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const t = await store.getRun(id);
|
|
666
|
+
if (t.events.length === 0) {
|
|
667
|
+
stdout.write(DIM(`no trajectory found for ${id}\n`));
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
const t0 = Date.parse(t.events[0].ts);
|
|
671
|
+
stdout.write(`run ${id} · ${t.model ?? '?'} · ${t.task.slice(0, 80)}\n`);
|
|
672
|
+
for (const e of t.events) {
|
|
673
|
+
const off = String(Math.max(0, Date.parse(e.ts) - t0)).padStart(7);
|
|
674
|
+
const icon = e.type.startsWith('tool.') ? CYAN('⚙') : e.type.startsWith('run.') ? (e.type === 'run.completed' ? GREEN('✓') : e.type === 'run.failed' ? RED('✗') : '▶') : e.type === 'error.observed' ? RED('!') : '·';
|
|
675
|
+
stdout.write(` +${off}ms ${icon} ${e.type.padEnd(20)} ${DIM(JSON.stringify(e.payload).slice(0, 110))}\n`);
|
|
676
|
+
}
|
|
677
|
+
if (t.outcome) {
|
|
678
|
+
stdout.write(` ${t.outcome.success ? GREEN('outcome: success') : RED('outcome: failed')} (${t.outcome.reason ?? '?'})`
|
|
679
|
+
+ (t.metrics ? ` · ${t.metrics.turns ?? '?'} turns · ${t.metrics.toolUses ?? '?'} tools · ↑${t.metrics.promptTokens ?? '?'} ↓${t.metrics.completionTokens ?? '?'} tok` : '') + '\n');
|
|
680
|
+
}
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
642
683
|
if (cmd === 'tui') {
|
|
643
684
|
await initHome();
|
|
644
685
|
// tui(yes, noWeb): inside the TTY check the TUI auto-links the web UI
|
package/dist/mcp-server.js
CHANGED
|
@@ -109,8 +109,11 @@ export async function serveMcp() {
|
|
|
109
109
|
const name = msg.params?.name ?? '';
|
|
110
110
|
const tool = byName.get(name);
|
|
111
111
|
if (!tool) {
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
// log-then-reply: the call log is an audit trail ("external agents'
|
|
113
|
+
// HarmonyOS usage"), so it must be durable BEFORE the caller can see
|
|
114
|
+
// the response. Fire-and-forget here raced with the client reading
|
|
115
|
+
// the file, so a rejected call sometimes had no log line yet.
|
|
116
|
+
void logCall(name, false, 0).then(() => reply(msg.id, { content: [{ type: 'text', text: `unknown or not exposed tool: ${name}` }], isError: true }));
|
|
114
117
|
break;
|
|
115
118
|
}
|
|
116
119
|
const t0 = Date.now();
|
package/dist/tui.d.ts
CHANGED
|
@@ -114,7 +114,9 @@ export declare class TuiRuntime {
|
|
|
114
114
|
* below, right-aligned to the terminal width so it reads as "the human
|
|
115
115
|
* side" against left-aligned model output. */
|
|
116
116
|
addUser(text: string): void;
|
|
117
|
-
startStream(kind: 'think' | 'say'): (chunk: string) => void
|
|
117
|
+
startStream(kind: 'think' | 'say'): ((chunk: string) => void) & {
|
|
118
|
+
drop: () => void;
|
|
119
|
+
};
|
|
118
120
|
/** Collapse a streamed thinking block to its final folded summary line. */
|
|
119
121
|
foldThinking(): void;
|
|
120
122
|
setBusy(busy: boolean, label?: string): void;
|
package/dist/tui.js
CHANGED
|
@@ -386,10 +386,19 @@ export class TuiRuntime {
|
|
|
386
386
|
repaint();
|
|
387
387
|
this.entries.push(entry);
|
|
388
388
|
this.scrollFromBottom = 0;
|
|
389
|
-
|
|
389
|
+
const append = (chunk) => {
|
|
390
390
|
buf += chunk;
|
|
391
391
|
repaint();
|
|
392
392
|
};
|
|
393
|
+
// a provider retry restarts the response: remove THIS block so the
|
|
394
|
+
// regenerated text does not appear as a second copy of a half answer
|
|
395
|
+
append.drop = () => {
|
|
396
|
+
const i = this.entries.indexOf(entry);
|
|
397
|
+
if (i >= 0)
|
|
398
|
+
this.entries.splice(i, 1);
|
|
399
|
+
this.dirty = true;
|
|
400
|
+
};
|
|
401
|
+
return append;
|
|
393
402
|
}
|
|
394
403
|
/** Collapse a streamed thinking block to its final folded summary line. */
|
|
395
404
|
foldThinking() {
|
|
@@ -1005,6 +1014,13 @@ export async function tui(yes, noWeb = false) {
|
|
|
1005
1014
|
onLine: (l) => { if (kind === 'reasoning')
|
|
1006
1015
|
rt.foldThinking(); appender = null; kind = null; rt.addText(l, 'dim'); },
|
|
1007
1016
|
onDelta: (k, chunk) => {
|
|
1017
|
+
if (k === 'reset') {
|
|
1018
|
+
// provider retry after a mid-stream cut: drop the half answer
|
|
1019
|
+
appender?.drop?.();
|
|
1020
|
+
appender = null;
|
|
1021
|
+
kind = null;
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1008
1024
|
if (k !== kind) {
|
|
1009
1025
|
if (kind === 'reasoning')
|
|
1010
1026
|
rt.foldThinking();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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,12 @@
|
|
|
43
43
|
"build": "tsc -p tsconfig.build.json"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@hmharness/kernel": "0.
|
|
47
|
-
"@hmharness/
|
|
48
|
-
"@hmharness/
|
|
49
|
-
"@hmharness/domain-
|
|
50
|
-
"@hmharness/
|
|
51
|
-
"@hmharness/
|
|
46
|
+
"@hmharness/kernel": "0.7.0",
|
|
47
|
+
"@hmharness/observability": "0.7.0",
|
|
48
|
+
"@hmharness/evolution": "0.7.0",
|
|
49
|
+
"@hmharness/domain-harmony": "0.7.0",
|
|
50
|
+
"@hmharness/domain-ops": "0.7.0",
|
|
51
|
+
"@hmharness/agent": "0.7.0",
|
|
52
|
+
"@hmharness/web": "0.7.0"
|
|
52
53
|
}
|
|
53
54
|
}
|