@triflux/core 10.49.0 → 10.50.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/hooks/agy-session-hook.mjs +1 -1
- package/hooks/claude-cwd-projection-refresh.mjs +7 -79
- package/hooks/codex-session-hook.mjs +3 -8
- package/hooks/hook-orchestrator.mjs +8 -648
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hub/bridge.mjs +290 -51
- package/hub/team/claude-daemon-control.mjs +54 -6
- package/hub/team/claude-transcript.mjs +240 -0
- package/hub/team/session-context.mjs +203 -0
- package/hud/constants.mjs +10 -0
- package/hud/hud-qos-status.mjs +23 -4
- package/hud/providers/gemini.mjs +4 -1
- package/hud/renderers.mjs +10 -6
- package/package.json +1 -1
- package/scripts/lib/doctor-env-checks.mjs +0 -85
- package/scripts/lib/legacy-hook-cleanup.mjs +230 -0
- package/scripts/lib/legacy-mcp-cleanup.mjs +772 -0
- package/scripts/lib/mcp-guard-engine.mjs +44 -271
- package/scripts/lib/mcp-manifest.mjs +2 -2
- package/scripts/lib/session-presence.mjs +249 -0
- package/hooks/agent-route-guard.mjs +0 -113
- package/hooks/cross-review-tracker.mjs +0 -135
- package/hooks/cto-north-star-brief.mjs +0 -133
- package/hooks/error-context.mjs +0 -185
- package/hooks/hook-adaptive-collector.mjs +0 -88
- package/hooks/hook-manager.mjs +0 -426
- package/hooks/hook-registry.json +0 -367
- package/hooks/hooks.json +0 -149
- package/hooks/keyword-rules.json +0 -676
- package/hooks/mcp-config-watcher.mjs +0 -84
- package/hooks/permission-safe-allow.mjs +0 -267
- package/hooks/pipeline-stop.mjs +0 -435
- package/hooks/post-tool-tips.mjs +0 -306
- package/hooks/pre-compact-snapshot.mjs +0 -181
- package/hooks/safety-guard.mjs +0 -466
- package/hooks/session-end-cleanup.mjs +0 -360
- package/hooks/session-start-fast.mjs +0 -579
- package/hooks/session-start-lake.mjs +0 -81
- package/hooks/subagent-tracker.mjs +0 -281
- package/hooks/subagent-verifier.mjs +0 -85
- package/scripts/lib/cross-review-utils.mjs +0 -59
- package/scripts/lib/hook-utils.mjs +0 -16
- package/scripts/lib/keyword-rules.mjs +0 -296
- package/scripts/lib/mcp-gateway-health-check.mjs +0 -224
- package/scripts/lib/mcp-gateway-servers.mjs +0 -70
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +0 -35
package/hub/bridge.mjs
CHANGED
|
@@ -1317,7 +1317,8 @@ function daemonAttachErrorResult(error) {
|
|
|
1317
1317
|
matchedCompletion: false,
|
|
1318
1318
|
timedOut: false,
|
|
1319
1319
|
closed: false,
|
|
1320
|
-
inputSent: error?.inputSent
|
|
1320
|
+
inputSent: error?.inputSent ?? null,
|
|
1321
|
+
status: error?.inputSent === false ? "failed" : "unknown",
|
|
1321
1322
|
error: error?.message || String(error),
|
|
1322
1323
|
};
|
|
1323
1324
|
}
|
|
@@ -1388,87 +1389,323 @@ async function cmdDaemonProbe(args) {
|
|
|
1388
1389
|
try {
|
|
1389
1390
|
const payload = readBridgePayload(args);
|
|
1390
1391
|
const { probeClaudeDaemonCandidates } = await loadDaemonControl();
|
|
1391
|
-
const probe = await
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
tmpRoot: payload.tmpRoot,
|
|
1398
|
-
});
|
|
1399
|
-
return emitJson(probe);
|
|
1392
|
+
const { probe, recoveredFrom } = await probeDaemonTarget(
|
|
1393
|
+
payload,
|
|
1394
|
+
probeClaudeDaemonCandidates,
|
|
1395
|
+
payload.includeContext === true,
|
|
1396
|
+
);
|
|
1397
|
+
return emitJson({ ...probe, ...(recoveredFrom ? { recoveredFrom } : {}) });
|
|
1400
1398
|
} catch (error) {
|
|
1401
1399
|
return emitJson(daemonErrorResult(error));
|
|
1402
1400
|
}
|
|
1403
1401
|
}
|
|
1404
1402
|
|
|
1403
|
+
function staleCandidate(probe) {
|
|
1404
|
+
return probe?.candidateResults?.find((candidate) =>
|
|
1405
|
+
["daemon-dir-missing", "stale-control-socket"].includes(
|
|
1406
|
+
candidate.errorCode,
|
|
1407
|
+
),
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function daemonProbeOptions(payload, includeContext = false) {
|
|
1412
|
+
return {
|
|
1413
|
+
configDir: payload.configDir,
|
|
1414
|
+
env: process.env,
|
|
1415
|
+
short: payload.short,
|
|
1416
|
+
sessionId: payload.sessionId,
|
|
1417
|
+
timeoutMs: numericOption(payload.timeoutMs, 6000),
|
|
1418
|
+
tmpRoot: payload.tmpRoot,
|
|
1419
|
+
includeContext,
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
async function probeDaemonTarget(
|
|
1424
|
+
payload,
|
|
1425
|
+
probeCandidates,
|
|
1426
|
+
includeContext = false,
|
|
1427
|
+
) {
|
|
1428
|
+
const options = daemonProbeOptions(payload, includeContext);
|
|
1429
|
+
const probe = await probeCandidates(options);
|
|
1430
|
+
const stale = staleCandidate(probe);
|
|
1431
|
+
if (probe.ok || !(payload.short || payload.sessionId) || !stale) {
|
|
1432
|
+
return { probe, recoveredFrom: null };
|
|
1433
|
+
}
|
|
1434
|
+
const retry = await probeCandidates({
|
|
1435
|
+
...options,
|
|
1436
|
+
candidateSourceConfigDir: stale.sourceConfigDir ?? stale.configDir,
|
|
1437
|
+
});
|
|
1438
|
+
return {
|
|
1439
|
+
probe: retry,
|
|
1440
|
+
recoveredFrom: retry.ok ? stale : null,
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1405
1444
|
async function cmdDaemonAttach(args) {
|
|
1406
1445
|
try {
|
|
1407
1446
|
const payload = readBridgePayload(args);
|
|
1408
1447
|
if (!payload.prompt) throw new Error("prompt is required");
|
|
1409
|
-
|
|
1410
1448
|
const {
|
|
1411
1449
|
attachClaudeDaemonSession,
|
|
1412
1450
|
buildDaemonControlAuth,
|
|
1413
1451
|
probeClaudeDaemonCandidates,
|
|
1414
1452
|
} = await loadDaemonControl();
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
timeoutMs: numericOption(payload.timeoutMs, 6000),
|
|
1421
|
-
tmpRoot: payload.tmpRoot,
|
|
1422
|
-
});
|
|
1453
|
+
let { probe, recoveredFrom } = await probeDaemonTarget(
|
|
1454
|
+
payload,
|
|
1455
|
+
probeClaudeDaemonCandidates,
|
|
1456
|
+
false,
|
|
1457
|
+
);
|
|
1423
1458
|
if (!probe.ok) return emitJson(daemonAttachProbeFailureResult(probe));
|
|
1424
|
-
const
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
ok: false,
|
|
1430
|
-
reason: "target-not-found",
|
|
1431
|
-
error: "short or resolvable sessionId is required",
|
|
1432
|
-
}),
|
|
1433
|
-
);
|
|
1434
|
-
}
|
|
1435
|
-
const controlAuth = await buildDaemonControlAuth(
|
|
1436
|
-
probe.daemon?.configDir ?? payload.configDir,
|
|
1459
|
+
const { findClaudeTranscript, readClaudeTranscript } = await import(
|
|
1460
|
+
"./team/claude-transcript.mjs"
|
|
1461
|
+
);
|
|
1462
|
+
const { contextGuard, modelContext } = await import(
|
|
1463
|
+
"./team/session-context.mjs"
|
|
1437
1464
|
);
|
|
1438
|
-
|
|
1439
1465
|
let result;
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1466
|
+
let context = modelContext("claude", null);
|
|
1467
|
+
let guard = {};
|
|
1468
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1469
|
+
const short = payload.short ?? probe.target?.short;
|
|
1470
|
+
if (!short) {
|
|
1471
|
+
return emitJson(
|
|
1472
|
+
daemonAttachProbeFailureResult({
|
|
1473
|
+
...probe,
|
|
1474
|
+
reason: "target-not-found",
|
|
1475
|
+
error: "short or resolvable sessionId is required",
|
|
1476
|
+
}),
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
const transcript = await findClaudeTranscript({
|
|
1480
|
+
configDir: probe.daemon?.configDir,
|
|
1481
|
+
sourceConfigDir: probe.daemon?.sourceConfigDir,
|
|
1482
|
+
sessionId: probe.target?.sessionId,
|
|
1483
|
+
cwd: probe.target?.cwd,
|
|
1449
1484
|
});
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1485
|
+
context =
|
|
1486
|
+
(await readClaudeTranscript(transcript).catch(() => null))?.context ??
|
|
1487
|
+
modelContext("claude", null);
|
|
1488
|
+
guard = contextGuard("claude", context, {
|
|
1489
|
+
warnContextPct: payload.warnContextPct,
|
|
1490
|
+
maxContextPct: payload.maxContextPct,
|
|
1454
1491
|
});
|
|
1492
|
+
if (guard.ok === false) {
|
|
1493
|
+
return emitJson({
|
|
1494
|
+
...guard,
|
|
1495
|
+
...(recoveredFrom ? { recoveredFrom } : {}),
|
|
1496
|
+
...daemonProbeMetadata(probe),
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
const controlAuth = await buildDaemonControlAuth(
|
|
1500
|
+
probe.daemon?.configDir ?? payload.configDir,
|
|
1501
|
+
);
|
|
1502
|
+
try {
|
|
1503
|
+
result = await attachClaudeDaemonSession({
|
|
1504
|
+
controlSock: probe.controlSock,
|
|
1505
|
+
short,
|
|
1506
|
+
input: payload.prompt,
|
|
1507
|
+
...controlAuth,
|
|
1508
|
+
cols: numericOption(payload.cols, undefined),
|
|
1509
|
+
rows: numericOption(payload.rows, undefined),
|
|
1510
|
+
timeoutMs: numericOption(payload.timeoutMs, 30_000),
|
|
1511
|
+
noWait: payload.noWait === true,
|
|
1512
|
+
});
|
|
1513
|
+
break;
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
if (
|
|
1516
|
+
attempt === 1 ||
|
|
1517
|
+
error?.code !== "ENOENT" ||
|
|
1518
|
+
error?.inputSent !== false
|
|
1519
|
+
) {
|
|
1520
|
+
return emitJson({
|
|
1521
|
+
...daemonAttachErrorResult(error),
|
|
1522
|
+
...(recoveredFrom ? { recoveredFrom } : {}),
|
|
1523
|
+
...daemonProbeMetadata(probe),
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
const stale = probe.daemon;
|
|
1527
|
+
const recovered = await probeClaudeDaemonCandidates({
|
|
1528
|
+
...daemonProbeOptions(payload),
|
|
1529
|
+
candidateSourceConfigDir: stale.sourceConfigDir ?? stale.configDir,
|
|
1530
|
+
});
|
|
1531
|
+
if (!recovered.ok) {
|
|
1532
|
+
return emitJson({
|
|
1533
|
+
...daemonAttachErrorResult(error),
|
|
1534
|
+
...daemonProbeMetadata(recovered),
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
probe = recovered;
|
|
1538
|
+
recoveredFrom = stale;
|
|
1539
|
+
}
|
|
1455
1540
|
}
|
|
1456
1541
|
|
|
1457
1542
|
return emitJson({
|
|
1458
|
-
ok:
|
|
1543
|
+
ok:
|
|
1544
|
+
payload.noWait === true
|
|
1545
|
+
? result.inputSent === true
|
|
1546
|
+
: result.matchedCompletion === true,
|
|
1547
|
+
...(payload.noWait === true
|
|
1548
|
+
? {
|
|
1549
|
+
status:
|
|
1550
|
+
result.inputSent === true
|
|
1551
|
+
? "submitted"
|
|
1552
|
+
: result.inputSent === false
|
|
1553
|
+
? "failed"
|
|
1554
|
+
: "unknown",
|
|
1555
|
+
done: false,
|
|
1556
|
+
...(result.inputSent === true
|
|
1557
|
+
? { submittedAt: new Date().toISOString() }
|
|
1558
|
+
: {}),
|
|
1559
|
+
target: probe.target,
|
|
1560
|
+
}
|
|
1561
|
+
: {}),
|
|
1459
1562
|
text: result.text,
|
|
1460
1563
|
raw: result.streamText,
|
|
1461
1564
|
responseRaw: result.responseStreamText,
|
|
1462
1565
|
matchedCompletion: result.matchedCompletion === true,
|
|
1463
|
-
|
|
1566
|
+
...(payload.noWait === true
|
|
1567
|
+
? {}
|
|
1568
|
+
: { timedOut: result.timedOut === true }),
|
|
1464
1569
|
closed: result.closed === true,
|
|
1465
|
-
inputSent: result.inputSent
|
|
1570
|
+
inputSent: result.inputSent ?? null,
|
|
1466
1571
|
error:
|
|
1467
1572
|
result.handshake?.ok === false ? result.handshake?.error : undefined,
|
|
1573
|
+
...context,
|
|
1574
|
+
...guard,
|
|
1575
|
+
...(recoveredFrom ? { recoveredFrom } : {}),
|
|
1468
1576
|
...daemonProbeMetadata(probe),
|
|
1469
1577
|
});
|
|
1470
1578
|
} catch (error) {
|
|
1471
|
-
return emitJson(
|
|
1579
|
+
return emitJson(
|
|
1580
|
+
daemonAttachErrorResult({
|
|
1581
|
+
inputSent: false,
|
|
1582
|
+
message: error?.message || String(error),
|
|
1583
|
+
}),
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
async function cmdDaemonWait(args) {
|
|
1589
|
+
let requestId = null;
|
|
1590
|
+
const emptyContext = {
|
|
1591
|
+
estimatedContextTokens: null,
|
|
1592
|
+
contextLimitTokens: null,
|
|
1593
|
+
contextLimitSource: null,
|
|
1594
|
+
contextPct: null,
|
|
1595
|
+
};
|
|
1596
|
+
try {
|
|
1597
|
+
const payload = readBridgePayload(args);
|
|
1598
|
+
requestId = payload.requestId ?? null;
|
|
1599
|
+
if (!payload.short && !payload.sessionId)
|
|
1600
|
+
throw new Error("short or sessionId is required");
|
|
1601
|
+
const { probeClaudeDaemonCandidates } = await loadDaemonControl();
|
|
1602
|
+
const { findClaudeTranscript, readClaudeTranscript } = await import(
|
|
1603
|
+
"./team/claude-transcript.mjs"
|
|
1604
|
+
);
|
|
1605
|
+
const timeoutMs = numericOption(payload.timeoutMs, 30_000);
|
|
1606
|
+
const pollIntervalMs = Math.max(
|
|
1607
|
+
10,
|
|
1608
|
+
numericOption(payload.pollIntervalMs, 500),
|
|
1609
|
+
);
|
|
1610
|
+
const deadline = Date.now() + timeoutMs;
|
|
1611
|
+
let last = null;
|
|
1612
|
+
do {
|
|
1613
|
+
const probe = await probeClaudeDaemonCandidates({
|
|
1614
|
+
configDir: payload.configDir,
|
|
1615
|
+
env: process.env,
|
|
1616
|
+
short: payload.short,
|
|
1617
|
+
sessionId: payload.sessionId,
|
|
1618
|
+
timeoutMs: Math.max(1, Math.min(6000, deadline - Date.now())),
|
|
1619
|
+
tmpRoot: payload.tmpRoot,
|
|
1620
|
+
includeContext: false,
|
|
1621
|
+
});
|
|
1622
|
+
if (!probe.ok) {
|
|
1623
|
+
if (Date.now() >= deadline) {
|
|
1624
|
+
return emitJson({
|
|
1625
|
+
...(last ?? emptyContext),
|
|
1626
|
+
ok: true,
|
|
1627
|
+
status: "working",
|
|
1628
|
+
done: false,
|
|
1629
|
+
timedOut: true,
|
|
1630
|
+
requestId,
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
return emitJson({
|
|
1634
|
+
ok: false,
|
|
1635
|
+
status: "unknown",
|
|
1636
|
+
done: false,
|
|
1637
|
+
timedOut: false,
|
|
1638
|
+
...emptyContext,
|
|
1639
|
+
requestId,
|
|
1640
|
+
...daemonProbeMetadata(probe),
|
|
1641
|
+
error: probe.error || probe.reason,
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
const transcriptPath = await findClaudeTranscript({
|
|
1645
|
+
configDir: probe.daemon?.configDir,
|
|
1646
|
+
sourceConfigDir: probe.daemon?.sourceConfigDir,
|
|
1647
|
+
sessionId: probe.target?.sessionId,
|
|
1648
|
+
cwd: probe.target?.cwd,
|
|
1649
|
+
});
|
|
1650
|
+
const transcript = await readClaudeTranscript(transcriptPath, {
|
|
1651
|
+
requestId: payload.requestId,
|
|
1652
|
+
});
|
|
1653
|
+
const context = transcript?.context ?? emptyContext;
|
|
1654
|
+
const idle = [
|
|
1655
|
+
probe.target?.state,
|
|
1656
|
+
probe.target?.status,
|
|
1657
|
+
probe.target?.tempo,
|
|
1658
|
+
].some((value) =>
|
|
1659
|
+
["idle", "done", "ready"].includes(String(value || "").toLowerCase()),
|
|
1660
|
+
);
|
|
1661
|
+
last = {
|
|
1662
|
+
ok: true,
|
|
1663
|
+
status: transcript?.userSeen ? "working" : "submitted",
|
|
1664
|
+
done: false,
|
|
1665
|
+
timedOut: false,
|
|
1666
|
+
response: transcript?.response || "",
|
|
1667
|
+
...context,
|
|
1668
|
+
requestId,
|
|
1669
|
+
target: probe.target,
|
|
1670
|
+
...daemonProbeMetadata(probe),
|
|
1671
|
+
};
|
|
1672
|
+
if (transcript?.error) {
|
|
1673
|
+
return emitJson({
|
|
1674
|
+
...last,
|
|
1675
|
+
ok: false,
|
|
1676
|
+
status: "failed",
|
|
1677
|
+
error: transcript.error,
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
if (
|
|
1681
|
+
(idle || transcript?.sectionClosed) &&
|
|
1682
|
+
transcript?.userSeen &&
|
|
1683
|
+
transcript.turnEnded
|
|
1684
|
+
) {
|
|
1685
|
+
if (!transcript.response.trim())
|
|
1686
|
+
return emitJson({
|
|
1687
|
+
...last,
|
|
1688
|
+
ok: false,
|
|
1689
|
+
status: "failed",
|
|
1690
|
+
error: "turn ended without assistant text",
|
|
1691
|
+
});
|
|
1692
|
+
return emitJson({ ...last, status: "completed", done: true });
|
|
1693
|
+
}
|
|
1694
|
+
if (Date.now() >= deadline) break;
|
|
1695
|
+
await new Promise((resolve) =>
|
|
1696
|
+
setTimeout(resolve, Math.min(pollIntervalMs, deadline - Date.now())),
|
|
1697
|
+
);
|
|
1698
|
+
} while (Date.now() < deadline);
|
|
1699
|
+
return emitJson({ ...last, status: "working", timedOut: true });
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
return emitJson({
|
|
1702
|
+
...daemonErrorResult(error),
|
|
1703
|
+
status: "unknown",
|
|
1704
|
+
done: false,
|
|
1705
|
+
timedOut: false,
|
|
1706
|
+
...emptyContext,
|
|
1707
|
+
requestId,
|
|
1708
|
+
});
|
|
1472
1709
|
}
|
|
1473
1710
|
}
|
|
1474
1711
|
|
|
@@ -1807,6 +2044,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1807
2044
|
return await cmdDaemonProbe(args);
|
|
1808
2045
|
case "daemon-attach":
|
|
1809
2046
|
return await cmdDaemonAttach(args);
|
|
2047
|
+
case "daemon-wait":
|
|
2048
|
+
return await cmdDaemonWait(args);
|
|
1810
2049
|
case "daemon-interrupt":
|
|
1811
2050
|
return await cmdDaemonInterrupt(args);
|
|
1812
2051
|
case "retry-run":
|
|
@@ -1817,7 +2056,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1817
2056
|
return await cmdInterveneRun(args);
|
|
1818
2057
|
default:
|
|
1819
2058
|
console.error(
|
|
1820
|
-
"사용법: bridge.mjs <register|heartbeat|result|control|handoff|publish|takeover-role|send-input|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status|hitl-request|hitl-submit|hitl-pending|daemon-probe|daemon-attach|daemon-interrupt|retry-run|retry-status|intervene-run> [--옵션]",
|
|
2059
|
+
"사용법: bridge.mjs <register|heartbeat|result|control|handoff|publish|takeover-role|send-input|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status|hitl-request|hitl-submit|hitl-pending|daemon-probe|daemon-attach|daemon-wait|daemon-interrupt|retry-run|retry-status|intervene-run> [--옵션]",
|
|
1821
2060
|
);
|
|
1822
2061
|
process.exit(1);
|
|
1823
2062
|
}
|
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
removeClaudeSessionProjection,
|
|
14
14
|
writeClaudeSessionProjection,
|
|
15
15
|
} from "./claude-session-projection.mjs";
|
|
16
|
+
import {
|
|
17
|
+
findClaudeTranscript,
|
|
18
|
+
readClaudeTranscript,
|
|
19
|
+
} from "./claude-transcript.mjs";
|
|
16
20
|
|
|
17
21
|
export function resolveClaudeConfigDir(env = process.env) {
|
|
18
22
|
if (env.CLAUDE_CONFIG_DIR) return path.resolve(env.CLAUDE_CONFIG_DIR);
|
|
@@ -223,6 +227,21 @@ function sessionsFromDaemonList(listResponse) {
|
|
|
223
227
|
return extractClaudeAgentSessions(listResponse);
|
|
224
228
|
}
|
|
225
229
|
|
|
230
|
+
async function addSessionContexts(sessions, candidate) {
|
|
231
|
+
return await Promise.all(
|
|
232
|
+
sessions.map(async (session) => {
|
|
233
|
+
const transcript = await findClaudeTranscript({
|
|
234
|
+
configDir: candidate.configDir,
|
|
235
|
+
sourceConfigDir: candidate.sourceConfigDir,
|
|
236
|
+
sessionId: session.sessionId,
|
|
237
|
+
cwd: session.cwd,
|
|
238
|
+
});
|
|
239
|
+
const snapshot = await readClaudeTranscript(transcript).catch(() => null);
|
|
240
|
+
return { ...session, context: snapshot?.context ?? null };
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
226
245
|
function errorMessage(error) {
|
|
227
246
|
return error?.message || String(error);
|
|
228
247
|
}
|
|
@@ -322,12 +341,21 @@ export async function probeClaudeDaemonCandidates({
|
|
|
322
341
|
sessionId,
|
|
323
342
|
timeoutMs = 6000,
|
|
324
343
|
tmpRoot = "/tmp",
|
|
344
|
+
includeContext = false,
|
|
345
|
+
candidateSourceConfigDir,
|
|
325
346
|
} = {}) {
|
|
326
|
-
const
|
|
347
|
+
const discovered = await buildClaudeDaemonDiscoveryCandidates({
|
|
327
348
|
configDir,
|
|
328
349
|
env,
|
|
329
350
|
tmpRoot,
|
|
330
351
|
});
|
|
352
|
+
const candidates = candidateSourceConfigDir
|
|
353
|
+
? discovered.filter(
|
|
354
|
+
(candidate) =>
|
|
355
|
+
(candidate.sourceConfigDir ?? candidate.configDir) ===
|
|
356
|
+
candidateSourceConfigDir,
|
|
357
|
+
)
|
|
358
|
+
: discovered;
|
|
331
359
|
const callerProvenance = detectCallerProvenance(env);
|
|
332
360
|
const targetRequested = Boolean(short || sessionId);
|
|
333
361
|
const results = [];
|
|
@@ -339,7 +367,10 @@ export async function probeClaudeDaemonCandidates({
|
|
|
339
367
|
{ proto: 1, op: "list" },
|
|
340
368
|
{ timeoutMs },
|
|
341
369
|
);
|
|
342
|
-
const
|
|
370
|
+
const normalized = sessionsFromDaemonList(list);
|
|
371
|
+
const sessions = includeContext
|
|
372
|
+
? await addSessionContexts(normalized, candidate)
|
|
373
|
+
: normalized;
|
|
343
374
|
const ok = list?.ok !== false;
|
|
344
375
|
results.push({
|
|
345
376
|
ok,
|
|
@@ -1038,10 +1069,10 @@ function defaultClaudeAttachCompletionMatched(
|
|
|
1038
1069
|
);
|
|
1039
1070
|
}
|
|
1040
1071
|
|
|
1041
|
-
function writeClaudeAttachInput(socket, input) {
|
|
1072
|
+
function writeClaudeAttachInput(socket, input, onSubmitted) {
|
|
1042
1073
|
socket.write("\x15");
|
|
1043
1074
|
socket.write(`\x1b[200~${String(input)}\x1b[201~`);
|
|
1044
|
-
socket.write("\r");
|
|
1075
|
+
socket.write("\r", onSubmitted);
|
|
1045
1076
|
}
|
|
1046
1077
|
|
|
1047
1078
|
function requiresPostInputTransition(handshake) {
|
|
@@ -1066,6 +1097,7 @@ export function attachClaudeDaemonSession({
|
|
|
1066
1097
|
timeoutMs = 30_000,
|
|
1067
1098
|
completionQuiescenceMs = DEFAULT_ATTACH_COMPLETION_QUIESCENCE_MS,
|
|
1068
1099
|
completionMatched = defaultClaudeAttachCompletionMatched,
|
|
1100
|
+
noWait = false,
|
|
1069
1101
|
} = {}) {
|
|
1070
1102
|
if (!controlSock) throw new Error("controlSock is required");
|
|
1071
1103
|
return new Promise((resolve, reject) => {
|
|
@@ -1198,8 +1230,24 @@ export function attachClaudeDaemonSession({
|
|
|
1198
1230
|
{ cols },
|
|
1199
1231
|
);
|
|
1200
1232
|
responseStartOffset = stream.length;
|
|
1201
|
-
|
|
1202
|
-
|
|
1233
|
+
inputSent = noWait ? null : true;
|
|
1234
|
+
writeClaudeAttachInput(
|
|
1235
|
+
socket,
|
|
1236
|
+
input,
|
|
1237
|
+
noWait
|
|
1238
|
+
? (error) => {
|
|
1239
|
+
if (error) fail(error);
|
|
1240
|
+
else {
|
|
1241
|
+
inputSent = true;
|
|
1242
|
+
finish({
|
|
1243
|
+
timedOut: false,
|
|
1244
|
+
matchedCompletion: false,
|
|
1245
|
+
closed: false,
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
: undefined,
|
|
1250
|
+
);
|
|
1203
1251
|
}, initialDrainMs);
|
|
1204
1252
|
}
|
|
1205
1253
|
} else {
|