@hmharness/cli 0.5.5 → 0.6.1
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.js +77 -52
- package/package.json +7 -7
package/dist/tui.js
CHANGED
|
@@ -932,12 +932,88 @@ export async function tui(yes, noWeb = false) {
|
|
|
932
932
|
void notifyUpdate(home, current, (latest) => rt.addText(`↑ ${t.updateHint(latest)}`, 'dim'));
|
|
933
933
|
}
|
|
934
934
|
let history = [];
|
|
935
|
+
// Task queue: new submissions during a running task are queued (not
|
|
936
|
+
// rejected, not run concurrently — sequential execution preserves history
|
|
937
|
+
// integrity). Slash commands still run immediately (they're quick).
|
|
938
|
+
const taskQueue = [];
|
|
939
|
+
let taskRunning = false;
|
|
935
940
|
rt.onSubmit(() => {
|
|
936
941
|
const line = rt.consumeInput().trim();
|
|
937
942
|
if (!line)
|
|
938
943
|
return;
|
|
939
|
-
|
|
944
|
+
if (line.startsWith('/')) {
|
|
945
|
+
void handleLine(line);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (taskRunning) {
|
|
949
|
+
taskQueue.push(line);
|
|
950
|
+
rt.addText(`📋 queued: "${line.slice(0, 60)}${line.length > 60 ? '…' : ''}" (${taskQueue.length} waiting)`, 'dim');
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
void executeTaskQueue(line);
|
|
940
954
|
});
|
|
955
|
+
async function executeTaskQueue(firstTask) {
|
|
956
|
+
taskRunning = true;
|
|
957
|
+
let task = firstTask;
|
|
958
|
+
while (task) {
|
|
959
|
+
await runSingleTask(task);
|
|
960
|
+
task = taskQueue.shift();
|
|
961
|
+
if (task)
|
|
962
|
+
rt.addText(`▶ next queued: "${task.slice(0, 60)}${task.length > 60 ? '…' : ''}"`, 'dim');
|
|
963
|
+
}
|
|
964
|
+
taskRunning = false;
|
|
965
|
+
}
|
|
966
|
+
async function runSingleTask(line) {
|
|
967
|
+
rt.addUser(line);
|
|
968
|
+
rt.setBusy(true, t.running);
|
|
969
|
+
let appender = null;
|
|
970
|
+
let kind = null;
|
|
971
|
+
try {
|
|
972
|
+
const result = await runAgentTask({
|
|
973
|
+
task: line,
|
|
974
|
+
registry: reg,
|
|
975
|
+
cfg,
|
|
976
|
+
yes: autoApprove,
|
|
977
|
+
resumeMessages: history,
|
|
978
|
+
approvalAsk: (name, args) => rt.requestApproval(name, args),
|
|
979
|
+
events: {
|
|
980
|
+
onLine: (l) => { if (kind === 'reasoning')
|
|
981
|
+
rt.foldThinking(); appender = null; kind = null; rt.addText(l, 'dim'); },
|
|
982
|
+
onDelta: (k, chunk) => {
|
|
983
|
+
if (k !== kind) {
|
|
984
|
+
if (kind === 'reasoning')
|
|
985
|
+
rt.foldThinking();
|
|
986
|
+
appender = rt.startStream(k === 'reasoning' ? 'think' : 'say');
|
|
987
|
+
kind = k;
|
|
988
|
+
}
|
|
989
|
+
appender?.(chunk);
|
|
990
|
+
},
|
|
991
|
+
onToolCall: (name, args) => {
|
|
992
|
+
if (kind === 'reasoning')
|
|
993
|
+
rt.foldThinking();
|
|
994
|
+
appender = null;
|
|
995
|
+
kind = null;
|
|
996
|
+
const brief = name === 'run_command' && typeof args.command === 'string'
|
|
997
|
+
? args.command
|
|
998
|
+
: JSON.stringify(args);
|
|
999
|
+
rt.addText(`${YELLOW('●')} ${CYAN(name)} ${DIM(brief.replace(/\s+/g, ' ').slice(0, 90))}`);
|
|
1000
|
+
},
|
|
1001
|
+
onToolResult: (name, output, isError) => {
|
|
1002
|
+
const dot = isError ? RED('✗') : GREEN('•');
|
|
1003
|
+
const first = output.split('\n').find((l) => l.trim()) ?? '';
|
|
1004
|
+
rt.addText(` ${dot} ${DIM('⎿ ' + first.trim().slice(0, 100))}`);
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
1007
|
+
});
|
|
1008
|
+
rt.setBusy(false);
|
|
1009
|
+
rt.setStatus(`↑${result.usage.promptTokens} ↓${result.usage.completionTokens} tok · ${result.turns} turns · ${result.toolUses} tools`);
|
|
1010
|
+
history = [...history, { role: 'user', content: line }, ...result.messages.slice(history.length + 2)];
|
|
1011
|
+
}
|
|
1012
|
+
catch (err) {
|
|
1013
|
+
rt.setBusy(false);
|
|
1014
|
+
rt.addText(String(err), 'err');
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
941
1017
|
async function handleLine(line) {
|
|
942
1018
|
if (line === '/exit' || line === '/quit') {
|
|
943
1019
|
rt.destroy();
|
|
@@ -1176,57 +1252,6 @@ export async function tui(yes, noWeb = false) {
|
|
|
1176
1252
|
}
|
|
1177
1253
|
return;
|
|
1178
1254
|
}
|
|
1179
|
-
rt.addUser(line);
|
|
1180
|
-
rt.setBusy(true, t.running);
|
|
1181
|
-
let appender = null;
|
|
1182
|
-
let kind = null;
|
|
1183
|
-
try {
|
|
1184
|
-
const result = await runAgentTask({
|
|
1185
|
-
task: line,
|
|
1186
|
-
registry: reg,
|
|
1187
|
-
cfg,
|
|
1188
|
-
yes: autoApprove,
|
|
1189
|
-
resumeMessages: history,
|
|
1190
|
-
approvalAsk: (name, args) => rt.requestApproval(name, args),
|
|
1191
|
-
events: {
|
|
1192
|
-
onLine: (l) => { if (kind === 'reasoning')
|
|
1193
|
-
rt.foldThinking(); appender = null; kind = null; rt.addText(l, 'dim'); },
|
|
1194
|
-
onDelta: (k, chunk) => {
|
|
1195
|
-
if (k !== kind) {
|
|
1196
|
-
if (kind === 'reasoning')
|
|
1197
|
-
rt.foldThinking(); // collapse before the next phase
|
|
1198
|
-
appender = rt.startStream(k === 'reasoning' ? 'think' : 'say');
|
|
1199
|
-
kind = k;
|
|
1200
|
-
}
|
|
1201
|
-
appender?.(chunk);
|
|
1202
|
-
},
|
|
1203
|
-
onToolCall: (name, args) => {
|
|
1204
|
-
if (kind === 'reasoning')
|
|
1205
|
-
rt.foldThinking();
|
|
1206
|
-
appender = null;
|
|
1207
|
-
kind = null;
|
|
1208
|
-
// fold the args to their essence: for run_command the command
|
|
1209
|
-
// string itself, otherwise a short JSON tail
|
|
1210
|
-
const brief = name === 'run_command' && typeof args.command === 'string'
|
|
1211
|
-
? args.command
|
|
1212
|
-
: JSON.stringify(args);
|
|
1213
|
-
rt.addText(`${YELLOW('●')} ${CYAN(name)} ${DIM(brief.replace(/\s+/g, ' ').slice(0, 90))}`);
|
|
1214
|
-
},
|
|
1215
|
-
onToolResult: (name, output, isError) => {
|
|
1216
|
-
const dot = isError ? RED('✗') : GREEN('•');
|
|
1217
|
-
const first = output.split('\n').find((l) => l.trim()) ?? '';
|
|
1218
|
-
rt.addText(` ${dot} ${DIM('⎿ ' + first.trim().slice(0, 100))}`);
|
|
1219
|
-
},
|
|
1220
|
-
},
|
|
1221
|
-
});
|
|
1222
|
-
rt.setBusy(false);
|
|
1223
|
-
rt.setStatus(`↑${result.usage.promptTokens} ↓${result.usage.completionTokens} tok · ${result.turns} turns · ${result.toolUses} tools`);
|
|
1224
|
-
history = [...history, { role: 'user', content: line }, ...result.messages.slice(history.length + 2)];
|
|
1225
|
-
}
|
|
1226
|
-
catch (err) {
|
|
1227
|
-
rt.setBusy(false);
|
|
1228
|
-
rt.addText(String(err), 'err');
|
|
1229
|
-
}
|
|
1230
1255
|
}
|
|
1231
1256
|
await rt.waitExit();
|
|
1232
1257
|
rt.destroy();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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.
|
|
47
|
-
"@hmharness/evolution": "0.
|
|
48
|
-
"@hmharness/domain-harmony": "0.
|
|
49
|
-
"@hmharness/domain-ops": "0.
|
|
50
|
-
"@hmharness/agent": "0.
|
|
51
|
-
"@hmharness/web": "0.
|
|
46
|
+
"@hmharness/kernel": "0.6.1",
|
|
47
|
+
"@hmharness/evolution": "0.6.1",
|
|
48
|
+
"@hmharness/domain-harmony": "0.6.1",
|
|
49
|
+
"@hmharness/domain-ops": "0.6.1",
|
|
50
|
+
"@hmharness/agent": "0.6.1",
|
|
51
|
+
"@hmharness/web": "0.6.1"
|
|
52
52
|
}
|
|
53
53
|
}
|