@termhub/agent 0.5.2 → 0.6.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/cli.js +82 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -128,6 +128,8 @@ var closedReason = z2.enum(["cli_missing", "run_failed", "killed", "missing_sess
|
|
|
128
128
|
var CAPABILITY_CLAUDE = "claude";
|
|
129
129
|
var CAPABILITY_CLAUDE_SYSTEM_PROMPT = "claude.system_prompt";
|
|
130
130
|
var CAPABILITY_SIM = "sim";
|
|
131
|
+
var CAPABILITY_CLAUDE_STREAM_INPUT = "claude.stream_input";
|
|
132
|
+
var STREAM_END_INPUT_LINE = '{"type":"termhub_end_input"}';
|
|
131
133
|
var helloMessage = z2.object({
|
|
132
134
|
type: z2.literal("hello"),
|
|
133
135
|
protocol: z2.number().int().min(1),
|
|
@@ -171,9 +173,13 @@ var claudeOpenParams = z2.object({
|
|
|
171
173
|
// belongs at, so there is no separate, later place to hand it over.
|
|
172
174
|
token: z2.string(),
|
|
173
175
|
model: z2.string().nullable().optional(),
|
|
174
|
-
// Project chats
|
|
176
|
+
// Project chats and streamed runs: the server-composed text forwarded onto the CLI's argv (see
|
|
175
177
|
// `CAPABILITY_CLAUDE_SYSTEM_PROMPT`). Absent for the account-wide chat, whose argv must not change.
|
|
176
|
-
|
|
178
|
+
// 8000 because a streamed run carries the orchestrator prompt next to a project's (at most 4000
|
|
179
|
+
// each); only agents that advertise `CAPABILITY_CLAUDE_STREAM_INPUT` are ever sent more than 4000.
|
|
180
|
+
append_system_prompt: z2.string().max(8e3).nullable().optional(),
|
|
181
|
+
// See `CAPABILITY_CLAUDE_STREAM_INPUT`. Absent on every one-shot run, whose open frame must not change.
|
|
182
|
+
stream_input: z2.boolean().optional()
|
|
177
183
|
});
|
|
178
184
|
var tcpOpenParams = z2.object({ port: wdaPort }).strict();
|
|
179
185
|
var openPty = z2.object({ type: z2.literal("open"), ch: channel, kind: z2.literal("pty"), params: ptyOpenParams });
|
|
@@ -693,6 +699,15 @@ if [ "$KIND_REST" != "$EVENT" ]; then
|
|
|
693
699
|
else
|
|
694
700
|
KIND=
|
|
695
701
|
fi
|
|
702
|
+
# A subagent's event (Claude Code 2.1.69+) carries an "agent_id" key before its own "hook_event_name" \u2014
|
|
703
|
+
# the order is session_id, transcript_path, cwd, prompt_id, permission_mode, agent_id, agent_type,
|
|
704
|
+
# hook_event_name, \u2026 \u2014 and the main thread's never does. Only that prefix is searched, and only for the
|
|
705
|
+
# key form: a value holding the text "agent_id": would have its quotes escaped. The reduced bodies below
|
|
706
|
+
# carry the flag; the server never lets a subagent's event close a card (spec 2026-09-26 \xA74.5). The
|
|
707
|
+
# dedupe marker ignores it.
|
|
708
|
+
BEFORE_KIND=\${EVENT%%'"hook_event_name"'*}
|
|
709
|
+
SUB=
|
|
710
|
+
case "$BEFORE_KIND" in *'"agent_id":'*) SUB=',"subagent":true' ;; esac
|
|
696
711
|
case "$KIND" in
|
|
697
712
|
PreToolUse)
|
|
698
713
|
# The event's own tool name is the FIRST "tool_name" of the payload (Claude Code serialises it
|
|
@@ -742,9 +757,9 @@ case "$KIND" in
|
|
|
742
757
|
[ "$(cat "$MARK" 2>/dev/null)" = "$KEY" ] && exit 0
|
|
743
758
|
printf '%s' "$KEY" 2>/dev/null > "$MARK"
|
|
744
759
|
if [ -n "$VERB" ]; then
|
|
745
|
-
EVENT=$(printf '{"hook_event_name":"PreToolUse","tool_name":"%s","verb":"%s"}' "$NAME" "$VERB")
|
|
760
|
+
EVENT=$(printf '{"hook_event_name":"PreToolUse","tool_name":"%s","verb":"%s"%s}' "$NAME" "$VERB" "$SUB")
|
|
746
761
|
else
|
|
747
|
-
EVENT=$(printf '{"hook_event_name":"PreToolUse","tool_name":"%s"}' "$NAME")
|
|
762
|
+
EVENT=$(printf '{"hook_event_name":"PreToolUse","tool_name":"%s"%s}' "$NAME" "$SUB")
|
|
748
763
|
fi
|
|
749
764
|
fi
|
|
750
765
|
;;
|
|
@@ -757,7 +772,7 @@ case "$KIND" in
|
|
|
757
772
|
REST=\${REST#*'"'}
|
|
758
773
|
NAME=\${REST%%'"'*}
|
|
759
774
|
case "$NAME" in '' | *[!A-Za-z0-9_.-]* | AskUserQuestion) exit 0 ;; esac
|
|
760
|
-
EVENT=$(printf '{"hook_event_name":"PermissionRequest","tool_name":"%s"}' "$NAME")
|
|
775
|
+
EVENT=$(printf '{"hook_event_name":"PermissionRequest","tool_name":"%s"%s}' "$NAME" "$SUB")
|
|
761
776
|
;;
|
|
762
777
|
# A new turn starts fresh, and so does an answered notification: a permission prompt takes the tab
|
|
763
778
|
# out of working, and the tool the person approves is the same one that set the marker, so without
|
|
@@ -1291,6 +1306,10 @@ async function uninstall(params, home = os3.homedir()) {
|
|
|
1291
1306
|
|
|
1292
1307
|
// ../../packages/claude-cli/dist/index.js
|
|
1293
1308
|
var DISALLOWED_TOOLS = "Bash,Read,Write,Edit,WebFetch,WebSearch";
|
|
1309
|
+
var BACKGROUND_AGENT_HOOK = `input=$(cat); case "$input" in *'"agent_id"'*) exit 0;; esac; printf '%s' "$input" | grep -Eq '"run_in_background"[[:space:]]*:[[:space:]]*true' && exit 0; echo 'No chat do termhub todo subagente roda em segundo plano: repita esta chamada do Agent com run_in_background: true.' >&2; exit 2`;
|
|
1310
|
+
var CONCIERGE_SETTINGS = JSON.stringify({
|
|
1311
|
+
hooks: { PreToolUse: [{ matcher: "Agent|Task", hooks: [{ type: "command", command: BACKGROUND_AGENT_HOOK }] }] }
|
|
1312
|
+
});
|
|
1294
1313
|
function buildClaudeArgs(spec) {
|
|
1295
1314
|
return [
|
|
1296
1315
|
"-p",
|
|
@@ -1313,6 +1332,7 @@ function buildClaudeArgs(spec) {
|
|
|
1313
1332
|
"mcp__termhub__*",
|
|
1314
1333
|
"--disallowed-tools",
|
|
1315
1334
|
DISALLOWED_TOOLS,
|
|
1335
|
+
...spec.stream_input ? ["--input-format", "stream-json", "--replay-user-messages", "--settings", CONCIERGE_SETTINGS] : [],
|
|
1316
1336
|
...spec.model ? ["--model", spec.model] : [],
|
|
1317
1337
|
// Last, and only when set: the account-wide chat's argv stays exactly what it was. It is our own
|
|
1318
1338
|
// server-composed text (a project's name, key and paths), never the user's prompt, which still
|
|
@@ -1337,6 +1357,8 @@ import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
|
1337
1357
|
import { tmpdir } from "os";
|
|
1338
1358
|
import { join } from "path";
|
|
1339
1359
|
var DEFAULT_TIMEOUT_MS2 = 10 * 60 * 1e3;
|
|
1360
|
+
var STREAM_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
1361
|
+
var MAX_PENDING_INPUT_BYTES = 256 * 1024;
|
|
1340
1362
|
var KILL_GRACE_MS = 2e3;
|
|
1341
1363
|
var PROMPT_TIMEOUT_MS = 3e4;
|
|
1342
1364
|
var MAX_LINE_BYTES = MAX_FRAME - HEADER_BYTES - 1;
|
|
@@ -1369,7 +1391,8 @@ function createClaudeManager(deps) {
|
|
|
1369
1391
|
}
|
|
1370
1392
|
const runDir = dir;
|
|
1371
1393
|
socket.sendControl({ type: "opened", ch });
|
|
1372
|
-
|
|
1394
|
+
const stream = params.stream_input === true;
|
|
1395
|
+
deps.log("claude run starting", { ch, resume: params.resume, model: params.model ?? null, stream });
|
|
1373
1396
|
const env2 = { ...deps.env ?? agentEnv() };
|
|
1374
1397
|
if (params.config_dir === null) delete env2.CLAUDE_CONFIG_DIR;
|
|
1375
1398
|
else env2.CLAUDE_CONFIG_DIR = params.config_dir;
|
|
@@ -1378,7 +1401,8 @@ function createClaudeManager(deps) {
|
|
|
1378
1401
|
resume: params.resume,
|
|
1379
1402
|
mcp_config_path: mcpConfigPath,
|
|
1380
1403
|
model: params.model ?? null,
|
|
1381
|
-
append_system_prompt: params.append_system_prompt ?? null
|
|
1404
|
+
append_system_prompt: params.append_system_prompt ?? null,
|
|
1405
|
+
stream_input: stream
|
|
1382
1406
|
});
|
|
1383
1407
|
let child;
|
|
1384
1408
|
try {
|
|
@@ -1454,10 +1478,11 @@ function createClaudeManager(deps) {
|
|
|
1454
1478
|
deps.log("claude stream send failed", { ch, error: message(err) });
|
|
1455
1479
|
}
|
|
1456
1480
|
}
|
|
1481
|
+
const timeoutMs = deps.timeoutMs ?? (stream ? STREAM_TIMEOUT_MS : DEFAULT_TIMEOUT_MS2);
|
|
1457
1482
|
const timer = setTimeout(() => {
|
|
1458
|
-
deps.log("claude run timed out", { ch, timeoutMs
|
|
1483
|
+
deps.log("claude run timed out", { ch, timeoutMs });
|
|
1459
1484
|
killRun();
|
|
1460
|
-
},
|
|
1485
|
+
}, timeoutMs);
|
|
1461
1486
|
timer.unref();
|
|
1462
1487
|
const promptTimer = setTimeout(() => {
|
|
1463
1488
|
deps.log("claude run got no prompt", { ch, promptTimeoutMs: deps.promptTimeoutMs ?? PROMPT_TIMEOUT_MS });
|
|
@@ -1470,7 +1495,11 @@ function createClaudeManager(deps) {
|
|
|
1470
1495
|
promptSent: false,
|
|
1471
1496
|
kill: killRun,
|
|
1472
1497
|
promptArrived: () => clearTimeout(promptTimer),
|
|
1473
|
-
settle
|
|
1498
|
+
settle,
|
|
1499
|
+
stream,
|
|
1500
|
+
inputTail: "",
|
|
1501
|
+
inputEnded: false,
|
|
1502
|
+
discarding: false
|
|
1474
1503
|
};
|
|
1475
1504
|
runs.set(ch, run2);
|
|
1476
1505
|
child.stdin?.on("error", () => {
|
|
@@ -1516,13 +1545,51 @@ function createClaudeManager(deps) {
|
|
|
1516
1545
|
write(ch, data) {
|
|
1517
1546
|
const run2 = runs.get(ch);
|
|
1518
1547
|
if (!run2) return false;
|
|
1519
|
-
if (run2.
|
|
1520
|
-
|
|
1548
|
+
if (!run2.stream) {
|
|
1549
|
+
if (run2.promptSent) {
|
|
1550
|
+
deps.log("extra data on a claude channel ignored", { ch, bytes: data.length });
|
|
1551
|
+
return true;
|
|
1552
|
+
}
|
|
1553
|
+
run2.promptSent = true;
|
|
1554
|
+
run2.promptArrived();
|
|
1555
|
+
run2.child.stdin?.end(data);
|
|
1521
1556
|
return true;
|
|
1522
1557
|
}
|
|
1523
1558
|
run2.promptSent = true;
|
|
1524
1559
|
run2.promptArrived();
|
|
1525
|
-
run2.
|
|
1560
|
+
if (run2.inputEnded) {
|
|
1561
|
+
deps.log("claude input after its end ignored", { ch, bytes: data.length });
|
|
1562
|
+
return true;
|
|
1563
|
+
}
|
|
1564
|
+
let text = data.toString("utf8");
|
|
1565
|
+
if (run2.discarding) {
|
|
1566
|
+
const nl = text.indexOf("\n");
|
|
1567
|
+
if (nl === -1) return true;
|
|
1568
|
+
deps.log("claude input line too large, rest dropped", { ch, bytes: Buffer.byteLength(text.slice(0, nl), "utf8") });
|
|
1569
|
+
run2.discarding = false;
|
|
1570
|
+
text = text.slice(nl + 1);
|
|
1571
|
+
}
|
|
1572
|
+
run2.inputTail += text;
|
|
1573
|
+
for (; ; ) {
|
|
1574
|
+
const nl = run2.inputTail.indexOf("\n");
|
|
1575
|
+
if (nl === -1) break;
|
|
1576
|
+
const line = run2.inputTail.slice(0, nl);
|
|
1577
|
+
run2.inputTail = run2.inputTail.slice(nl + 1);
|
|
1578
|
+
if (line === STREAM_END_INPUT_LINE) {
|
|
1579
|
+
run2.inputEnded = true;
|
|
1580
|
+
if (run2.inputTail.length > 0) deps.log("claude input after its end ignored", { ch, bytes: Buffer.byteLength(run2.inputTail, "utf8") });
|
|
1581
|
+
run2.inputTail = "";
|
|
1582
|
+
run2.child.stdin?.end();
|
|
1583
|
+
return true;
|
|
1584
|
+
}
|
|
1585
|
+
if (line.trim()) run2.child.stdin?.write(`${line}
|
|
1586
|
+
`);
|
|
1587
|
+
}
|
|
1588
|
+
if (Buffer.byteLength(run2.inputTail, "utf8") > (deps.maxPendingInputBytes ?? MAX_PENDING_INPUT_BYTES)) {
|
|
1589
|
+
deps.log("claude input line too large, dropped", { ch, bytes: Buffer.byteLength(run2.inputTail, "utf8") });
|
|
1590
|
+
run2.inputTail = "";
|
|
1591
|
+
run2.discarding = true;
|
|
1592
|
+
}
|
|
1526
1593
|
return true;
|
|
1527
1594
|
},
|
|
1528
1595
|
close(ch) {
|
|
@@ -2317,7 +2384,7 @@ async function status3() {
|
|
|
2317
2384
|
}
|
|
2318
2385
|
|
|
2319
2386
|
// src/version.ts
|
|
2320
|
-
var AGENT_VERSION = "0.
|
|
2387
|
+
var AGENT_VERSION = "0.6.0";
|
|
2321
2388
|
|
|
2322
2389
|
// src/rpc/update.ts
|
|
2323
2390
|
var PACKAGE = "@termhub/agent";
|
|
@@ -2456,7 +2523,7 @@ function detectOs(platform = process.platform) {
|
|
|
2456
2523
|
if (platform === "linux") return "linux";
|
|
2457
2524
|
return null;
|
|
2458
2525
|
}
|
|
2459
|
-
var CAPABILITIES = [CAPABILITY_CLAUDE, CAPABILITY_CLAUDE_SYSTEM_PROMPT];
|
|
2526
|
+
var CAPABILITIES = [CAPABILITY_CLAUDE, CAPABILITY_CLAUDE_SYSTEM_PROMPT, CAPABILITY_CLAUDE_STREAM_INPUT];
|
|
2460
2527
|
function capabilitiesFor(osName) {
|
|
2461
2528
|
return osName === "macos" ? [...CAPABILITIES, CAPABILITY_SIM] : [...CAPABILITIES];
|
|
2462
2529
|
}
|
package/package.json
CHANGED