@hydra-acp/cli 0.1.48 → 0.1.50
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 +2983 -655
- package/dist/index.d.ts +145 -13
- package/dist/index.js +1404 -81
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -930,6 +930,9 @@ function extractHydraMeta(meta) {
|
|
|
930
930
|
if (typeof obj.promptUpdating === "boolean") {
|
|
931
931
|
out.promptUpdating = obj.promptUpdating;
|
|
932
932
|
}
|
|
933
|
+
if (typeof obj.mcpStdin === "boolean") {
|
|
934
|
+
out.mcpStdin = obj.mcpStdin;
|
|
935
|
+
}
|
|
933
936
|
if (typeof obj.promptAmending === "boolean") {
|
|
934
937
|
out.promptAmending = obj.promptAmending;
|
|
935
938
|
}
|
|
@@ -1163,6 +1166,10 @@ var init_types = __esm({
|
|
|
1163
1166
|
importedFromUpstreamSessionId: z3.string().optional(),
|
|
1164
1167
|
// Set when this session was spawned as a child by a transformer.
|
|
1165
1168
|
parentSessionId: z3.string().optional(),
|
|
1169
|
+
// clientInfo from the process that issued session/new. Lets list views
|
|
1170
|
+
// hide cat-style ancillary sessions by default while letting an
|
|
1171
|
+
// override flag surface them.
|
|
1172
|
+
originatingClient: z3.object({ name: z3.string(), version: z3.string().optional() }).optional(),
|
|
1166
1173
|
updatedAt: z3.string(),
|
|
1167
1174
|
attachedClients: z3.number().int().nonnegative(),
|
|
1168
1175
|
status: z3.enum(["live", "cold"]).default("live"),
|
|
@@ -1397,7 +1404,10 @@ var init_connection = __esm({
|
|
|
1397
1404
|
// every entry would be re-appended to history.jsonl, doubling the log
|
|
1398
1405
|
// each time the session was woken up.
|
|
1399
1406
|
drainBuffered(method) {
|
|
1407
|
+
const buf = this.bufferedNotifications.get(method);
|
|
1408
|
+
const count = buf?.length ?? 0;
|
|
1400
1409
|
this.bufferedNotifications.delete(method);
|
|
1410
|
+
return count;
|
|
1401
1411
|
}
|
|
1402
1412
|
onClose(handler) {
|
|
1403
1413
|
this.closeHandlers.push(handler);
|
|
@@ -1544,18 +1554,34 @@ var init_connection = __esm({
|
|
|
1544
1554
|
|
|
1545
1555
|
// src/core/stream-buffer.ts
|
|
1546
1556
|
import * as fsp3 from "fs/promises";
|
|
1547
|
-
|
|
1557
|
+
function escapeRegex(s) {
|
|
1558
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1559
|
+
}
|
|
1560
|
+
var DEFAULT_CAPACITY_BYTES, INITIAL_CAPACITY_BYTES, STREAM_READ_MAX_BYTES, STREAM_WAIT_MAX_MS, STREAM_GREP_DEFAULT_MATCHES, STREAM_GREP_MAX_MATCHES, STREAM_GREP_DEFAULT_BYTES, STREAM_GREP_MAX_BYTES, STREAM_GREP_MAX_CONTEXT, SessionStreamBuffer;
|
|
1548
1561
|
var init_stream_buffer = __esm({
|
|
1549
1562
|
"src/core/stream-buffer.ts"() {
|
|
1550
1563
|
"use strict";
|
|
1551
|
-
DEFAULT_CAPACITY_BYTES =
|
|
1564
|
+
DEFAULT_CAPACITY_BYTES = 64 * 1024 * 1024;
|
|
1565
|
+
INITIAL_CAPACITY_BYTES = 1 * 1024 * 1024;
|
|
1552
1566
|
STREAM_READ_MAX_BYTES = 64 * 1024;
|
|
1553
1567
|
STREAM_WAIT_MAX_MS = 6e4;
|
|
1568
|
+
STREAM_GREP_DEFAULT_MATCHES = 100;
|
|
1569
|
+
STREAM_GREP_MAX_MATCHES = 1e3;
|
|
1570
|
+
STREAM_GREP_DEFAULT_BYTES = 64 * 1024;
|
|
1571
|
+
STREAM_GREP_MAX_BYTES = 256 * 1024;
|
|
1572
|
+
STREAM_GREP_MAX_CONTEXT = 20;
|
|
1554
1573
|
SessionStreamBuffer = class {
|
|
1555
1574
|
storage;
|
|
1556
|
-
|
|
1575
|
+
// The configured cap. Eviction begins once writeCursor exceeds this.
|
|
1576
|
+
maxCapacityBytes;
|
|
1577
|
+
// The size of the currently-allocated `storage`. Starts at
|
|
1578
|
+
// INITIAL_CAPACITY_BYTES (clamped to maxCapacityBytes) and doubles on
|
|
1579
|
+
// demand. Once it reaches maxCapacityBytes the ring behaves like a
|
|
1580
|
+
// fixed-size buffer; before then, writeCursor < currentCapacityBytes
|
|
1581
|
+
// always, so no wrap-around math is in play.
|
|
1582
|
+
currentCapacityBytes;
|
|
1557
1583
|
// Absolute monotonic byte offset of the next byte to be written. Also
|
|
1558
|
-
// the count of bytes ever appended. `writeCursor -
|
|
1584
|
+
// the count of bytes ever appended. `writeCursor - currentCapacityBytes`
|
|
1559
1585
|
// (clamped at 0) is the oldest still-resident byte's cursor.
|
|
1560
1586
|
writeCursor = 0;
|
|
1561
1587
|
closed = false;
|
|
@@ -1570,24 +1596,33 @@ var init_stream_buffer = __esm({
|
|
|
1570
1596
|
// calls don't interleave their writes.
|
|
1571
1597
|
fileWriteChain = Promise.resolve();
|
|
1572
1598
|
constructor(opts = {}) {
|
|
1573
|
-
this.
|
|
1574
|
-
if (this.
|
|
1599
|
+
this.maxCapacityBytes = opts.capacityBytes ?? DEFAULT_CAPACITY_BYTES;
|
|
1600
|
+
if (this.maxCapacityBytes <= 0) {
|
|
1575
1601
|
throw new Error("capacityBytes must be > 0");
|
|
1576
1602
|
}
|
|
1577
|
-
this.
|
|
1603
|
+
this.currentCapacityBytes = Math.min(
|
|
1604
|
+
INITIAL_CAPACITY_BYTES,
|
|
1605
|
+
this.maxCapacityBytes
|
|
1606
|
+
);
|
|
1607
|
+
this.storage = Buffer.alloc(this.currentCapacityBytes);
|
|
1578
1608
|
this.filePath = opts.filePath;
|
|
1579
1609
|
this.fileCapBytes = opts.fileCapBytes ?? Number.POSITIVE_INFINITY;
|
|
1580
1610
|
this.onFileCapReached = opts.onFileCapReached;
|
|
1581
1611
|
this.logWriteError = opts.logWriteError;
|
|
1582
1612
|
}
|
|
1583
1613
|
get capacity() {
|
|
1584
|
-
return this.
|
|
1614
|
+
return this.maxCapacityBytes;
|
|
1615
|
+
}
|
|
1616
|
+
// Currently-allocated storage size (for observability / tests). May be
|
|
1617
|
+
// anywhere between INITIAL_CAPACITY_BYTES and capacity.
|
|
1618
|
+
get allocatedBytes() {
|
|
1619
|
+
return this.currentCapacityBytes;
|
|
1585
1620
|
}
|
|
1586
1621
|
get writeCursorPos() {
|
|
1587
1622
|
return this.writeCursor;
|
|
1588
1623
|
}
|
|
1589
1624
|
get oldestAvailable() {
|
|
1590
|
-
return Math.max(0, this.writeCursor - this.
|
|
1625
|
+
return Math.max(0, this.writeCursor - this.currentCapacityBytes);
|
|
1591
1626
|
}
|
|
1592
1627
|
get isClosed() {
|
|
1593
1628
|
return this.closed;
|
|
@@ -1732,6 +1767,136 @@ var init_stream_buffer = __esm({
|
|
|
1732
1767
|
this.waiters.push(waiter);
|
|
1733
1768
|
});
|
|
1734
1769
|
}
|
|
1770
|
+
// Scan the resident region line-by-line, returning lines that match
|
|
1771
|
+
// `pattern`. Server-side filtering so the agent doesn't have to pull
|
|
1772
|
+
// and decode 64 KiB base64 windows just to grep a multi-MB log.
|
|
1773
|
+
//
|
|
1774
|
+
// Lines are split on `\n` (LF). A trailing partial line (no LF) is
|
|
1775
|
+
// skipped when the buffer is still open — its bytes might be the
|
|
1776
|
+
// start of a longer line that's still being written — but is treated
|
|
1777
|
+
// as a final full line once the buffer is closed.
|
|
1778
|
+
//
|
|
1779
|
+
// Caps: max 1000 matches and 256 KiB of output bytes per call. The
|
|
1780
|
+
// agent should re-call with `cursor = nextCursor` to resume when
|
|
1781
|
+
// `truncated:true`.
|
|
1782
|
+
grep(opts) {
|
|
1783
|
+
const oldest = this.oldestAvailable;
|
|
1784
|
+
const requested = opts.cursor;
|
|
1785
|
+
let start = requested ?? oldest;
|
|
1786
|
+
let gap = 0;
|
|
1787
|
+
if (requested !== void 0 && requested < oldest) {
|
|
1788
|
+
gap = oldest - requested;
|
|
1789
|
+
start = oldest;
|
|
1790
|
+
}
|
|
1791
|
+
if (start > this.writeCursor) {
|
|
1792
|
+
start = this.writeCursor;
|
|
1793
|
+
}
|
|
1794
|
+
const slice = this.sliceFromRing(start, this.writeCursor - start);
|
|
1795
|
+
const useRegex = opts.regex ?? true;
|
|
1796
|
+
const flags = opts.caseInsensitive === true ? "i" : "";
|
|
1797
|
+
const re = useRegex ? new RegExp(opts.pattern, flags) : new RegExp(escapeRegex(opts.pattern), flags);
|
|
1798
|
+
const invert = opts.invert ?? false;
|
|
1799
|
+
const maxMatches = Math.max(
|
|
1800
|
+
1,
|
|
1801
|
+
Math.min(
|
|
1802
|
+
opts.maxMatches ?? STREAM_GREP_DEFAULT_MATCHES,
|
|
1803
|
+
STREAM_GREP_MAX_MATCHES
|
|
1804
|
+
)
|
|
1805
|
+
);
|
|
1806
|
+
const maxBytes = Math.max(
|
|
1807
|
+
1,
|
|
1808
|
+
Math.min(opts.maxBytes ?? STREAM_GREP_DEFAULT_BYTES, STREAM_GREP_MAX_BYTES)
|
|
1809
|
+
);
|
|
1810
|
+
const contextBefore = Math.max(
|
|
1811
|
+
0,
|
|
1812
|
+
Math.min(opts.contextBefore ?? 0, STREAM_GREP_MAX_CONTEXT)
|
|
1813
|
+
);
|
|
1814
|
+
const contextAfter = Math.max(
|
|
1815
|
+
0,
|
|
1816
|
+
Math.min(opts.contextAfter ?? 0, STREAM_GREP_MAX_CONTEXT)
|
|
1817
|
+
);
|
|
1818
|
+
const matches = [];
|
|
1819
|
+
const beforeRing = [];
|
|
1820
|
+
const pendingAfter = [];
|
|
1821
|
+
let bytesUsed = 0;
|
|
1822
|
+
let truncated = false;
|
|
1823
|
+
let lineStartByte = 0;
|
|
1824
|
+
let resumeFromLineStart = 0;
|
|
1825
|
+
const processLine = (lineCursor, lineText) => {
|
|
1826
|
+
for (const pa of pendingAfter) {
|
|
1827
|
+
if (pa.remaining > 0) {
|
|
1828
|
+
if (pa.match.after === void 0) {
|
|
1829
|
+
pa.match.after = [];
|
|
1830
|
+
}
|
|
1831
|
+
pa.match.after.push({ cursor: lineCursor, line: lineText });
|
|
1832
|
+
pa.remaining--;
|
|
1833
|
+
bytesUsed += lineText.length;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
while (pendingAfter.length > 0 && pendingAfter[0].remaining === 0) {
|
|
1837
|
+
pendingAfter.shift();
|
|
1838
|
+
}
|
|
1839
|
+
const matched = re.test(lineText) !== invert;
|
|
1840
|
+
if (matched && matches.length < maxMatches) {
|
|
1841
|
+
const m = { cursor: lineCursor, line: lineText };
|
|
1842
|
+
if (contextBefore > 0 && beforeRing.length > 0) {
|
|
1843
|
+
m.before = beforeRing.slice();
|
|
1844
|
+
for (const b of m.before) {
|
|
1845
|
+
bytesUsed += b.line.length;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
bytesUsed += lineText.length;
|
|
1849
|
+
matches.push(m);
|
|
1850
|
+
if (contextAfter > 0) {
|
|
1851
|
+
pendingAfter.push({ match: m, remaining: contextAfter });
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
if (contextBefore > 0) {
|
|
1855
|
+
beforeRing.push({ cursor: lineCursor, line: lineText });
|
|
1856
|
+
while (beforeRing.length > contextBefore) {
|
|
1857
|
+
beforeRing.shift();
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
const hitMaxMatches = matches.length >= maxMatches && pendingAfter.length === 0;
|
|
1861
|
+
const hitMaxBytes = bytesUsed >= maxBytes;
|
|
1862
|
+
return hitMaxMatches || hitMaxBytes;
|
|
1863
|
+
};
|
|
1864
|
+
for (let i = 0; i < slice.length; i++) {
|
|
1865
|
+
if (slice[i] !== 10) {
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
const lineText = slice.subarray(lineStartByte, i).toString("utf8");
|
|
1869
|
+
const lineCursor = start + lineStartByte;
|
|
1870
|
+
lineStartByte = i + 1;
|
|
1871
|
+
resumeFromLineStart = lineStartByte;
|
|
1872
|
+
if (processLine(lineCursor, lineText)) {
|
|
1873
|
+
truncated = true;
|
|
1874
|
+
break;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
if (!truncated && lineStartByte < slice.length && this.closed) {
|
|
1878
|
+
const lineText = slice.subarray(lineStartByte).toString("utf8");
|
|
1879
|
+
const lineCursor = start + lineStartByte;
|
|
1880
|
+
if (processLine(lineCursor, lineText)) {
|
|
1881
|
+
truncated = true;
|
|
1882
|
+
}
|
|
1883
|
+
resumeFromLineStart = slice.length;
|
|
1884
|
+
}
|
|
1885
|
+
const nextCursor = Math.min(start + resumeFromLineStart, this.writeCursor);
|
|
1886
|
+
const result = {
|
|
1887
|
+
matches,
|
|
1888
|
+
truncated,
|
|
1889
|
+
nextCursor,
|
|
1890
|
+
scannedBytes: resumeFromLineStart
|
|
1891
|
+
};
|
|
1892
|
+
if (gap > 0) {
|
|
1893
|
+
result.gap = gap;
|
|
1894
|
+
}
|
|
1895
|
+
if (this.closed && nextCursor >= this.writeCursor) {
|
|
1896
|
+
result.eof = true;
|
|
1897
|
+
}
|
|
1898
|
+
return result;
|
|
1899
|
+
}
|
|
1735
1900
|
wakeWaiters(outcome) {
|
|
1736
1901
|
if (this.waiters.length === 0) {
|
|
1737
1902
|
return;
|
|
@@ -1742,15 +1907,42 @@ var init_stream_buffer = __esm({
|
|
|
1742
1907
|
w.resolve(outcome);
|
|
1743
1908
|
}
|
|
1744
1909
|
}
|
|
1910
|
+
// Grow `storage` if needed to fit `additionalBytes` more bytes without
|
|
1911
|
+
// wrapping. Caps at maxCapacityBytes; once we're at the cap, callers
|
|
1912
|
+
// fall back to ring-wrap behavior. Doubles each grow so we amortize.
|
|
1913
|
+
// Only called before we've ever wrapped (writeCursor < currentCapacity
|
|
1914
|
+
// always holds while we're growing), so the existing bytes live at
|
|
1915
|
+
// storage[0..writeCursor] and we can just copy them flat.
|
|
1916
|
+
growIfNeeded(additionalBytes) {
|
|
1917
|
+
if (this.currentCapacityBytes >= this.maxCapacityBytes) {
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
const needed = this.writeCursor + additionalBytes;
|
|
1921
|
+
if (needed <= this.currentCapacityBytes) {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
let next = this.currentCapacityBytes;
|
|
1925
|
+
while (next < needed && next < this.maxCapacityBytes) {
|
|
1926
|
+
next = Math.min(this.maxCapacityBytes, next * 2);
|
|
1927
|
+
}
|
|
1928
|
+
if (next === this.currentCapacityBytes) {
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
const newStorage = Buffer.alloc(next);
|
|
1932
|
+
this.storage.copy(newStorage, 0, 0, this.writeCursor);
|
|
1933
|
+
this.storage = newStorage;
|
|
1934
|
+
this.currentCapacityBytes = next;
|
|
1935
|
+
}
|
|
1745
1936
|
writeRing(chunk) {
|
|
1746
1937
|
const len = chunk.length;
|
|
1747
|
-
|
|
1748
|
-
|
|
1938
|
+
this.growIfNeeded(len);
|
|
1939
|
+
if (len >= this.currentCapacityBytes) {
|
|
1940
|
+
const tailStart = len - this.currentCapacityBytes;
|
|
1749
1941
|
chunk.copy(this.storage, 0, tailStart, len);
|
|
1750
1942
|
return;
|
|
1751
1943
|
}
|
|
1752
|
-
const offset = this.writeCursor % this.
|
|
1753
|
-
const tailRoom = this.
|
|
1944
|
+
const offset = this.writeCursor % this.currentCapacityBytes;
|
|
1945
|
+
const tailRoom = this.currentCapacityBytes - offset;
|
|
1754
1946
|
if (len <= tailRoom) {
|
|
1755
1947
|
chunk.copy(this.storage, offset, 0, len);
|
|
1756
1948
|
} else {
|
|
@@ -1763,8 +1955,8 @@ var init_stream_buffer = __esm({
|
|
|
1763
1955
|
return Buffer.alloc(0);
|
|
1764
1956
|
}
|
|
1765
1957
|
const out = Buffer.alloc(length);
|
|
1766
|
-
const offset = fromCursor % this.
|
|
1767
|
-
const tailLen = Math.min(length, this.
|
|
1958
|
+
const offset = fromCursor % this.currentCapacityBytes;
|
|
1959
|
+
const tailLen = Math.min(length, this.currentCapacityBytes - offset);
|
|
1768
1960
|
this.storage.copy(out, 0, offset, offset + tailLen);
|
|
1769
1961
|
if (tailLen < length) {
|
|
1770
1962
|
this.storage.copy(out, tailLen, 0, length - tailLen);
|
|
@@ -2188,6 +2380,7 @@ var init_session = __esm({
|
|
|
2188
2380
|
agentCapabilities;
|
|
2189
2381
|
agentArgs;
|
|
2190
2382
|
parentSessionId;
|
|
2383
|
+
originatingClient;
|
|
2191
2384
|
title;
|
|
2192
2385
|
// Snapshot state delivered to attaching clients via the attach
|
|
2193
2386
|
// response _meta rather than via history replay (which would be
|
|
@@ -2265,6 +2458,8 @@ var init_session = __esm({
|
|
|
2265
2458
|
listSessions;
|
|
2266
2459
|
logger;
|
|
2267
2460
|
transformChain;
|
|
2461
|
+
extensionCommands;
|
|
2462
|
+
extensionCommandsUnsub;
|
|
2268
2463
|
// Outstanding "processing" claims: token → claim waiting for respondsTo discharge.
|
|
2269
2464
|
pendingClaims = /* @__PURE__ */ new Map();
|
|
2270
2465
|
agentChangeHandlers = [];
|
|
@@ -2340,6 +2535,7 @@ var init_session = __esm({
|
|
|
2340
2535
|
this.agentCapabilities = init.agentCapabilities;
|
|
2341
2536
|
this.agentArgs = init.agentArgs;
|
|
2342
2537
|
this.parentSessionId = init.parentSessionId;
|
|
2538
|
+
this.originatingClient = init.originatingClient;
|
|
2343
2539
|
this.title = init.title;
|
|
2344
2540
|
this.currentModel = init.currentModel;
|
|
2345
2541
|
this.currentMode = init.currentMode;
|
|
@@ -2360,6 +2556,14 @@ var init_session = __esm({
|
|
|
2360
2556
|
this.listSessions = init.listSessions;
|
|
2361
2557
|
this.logger = init.logger;
|
|
2362
2558
|
this.transformChain = init.transformChain ?? [];
|
|
2559
|
+
this.extensionCommands = init.extensionCommands;
|
|
2560
|
+
if (this.extensionCommands) {
|
|
2561
|
+
this.extensionCommandsUnsub = this.extensionCommands.onChange(() => {
|
|
2562
|
+
if (!this.closed) {
|
|
2563
|
+
this.broadcastMergedCommands();
|
|
2564
|
+
}
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2363
2567
|
if (init.firstPromptSeeded) {
|
|
2364
2568
|
this.firstPromptSeeded = true;
|
|
2365
2569
|
}
|
|
@@ -2374,18 +2578,11 @@ var init_session = __esm({
|
|
|
2374
2578
|
this.notifyChain("session.opened", {});
|
|
2375
2579
|
}
|
|
2376
2580
|
broadcastMergedCommands() {
|
|
2377
|
-
const merged = [
|
|
2378
|
-
...hydraCommandsAsAdvertised(),
|
|
2379
|
-
{ name: "model <model-id>", description: "Switch model; omit arg to list available models" },
|
|
2380
|
-
{ name: "sessions", description: "List all sessions" },
|
|
2381
|
-
{ name: "help", description: "Show available commands" },
|
|
2382
|
-
...this.agentAdvertisedCommands
|
|
2383
|
-
];
|
|
2384
2581
|
this.recordAndBroadcast("session/update", {
|
|
2385
2582
|
sessionId: this.upstreamSessionId,
|
|
2386
2583
|
update: {
|
|
2387
2584
|
sessionUpdate: "available_commands_update",
|
|
2388
|
-
availableCommands:
|
|
2585
|
+
availableCommands: this.mergedAvailableCommands()
|
|
2389
2586
|
}
|
|
2390
2587
|
});
|
|
2391
2588
|
}
|
|
@@ -3548,6 +3745,9 @@ var init_session = __esm({
|
|
|
3548
3745
|
if (!trimmed || trimmed === this.currentModel) {
|
|
3549
3746
|
return true;
|
|
3550
3747
|
}
|
|
3748
|
+
this.logger?.info(
|
|
3749
|
+
`live current_model_update: sessionId=${this.sessionId} ${JSON.stringify(this.currentModel)} \u2192 ${JSON.stringify(trimmed)}`
|
|
3750
|
+
);
|
|
3551
3751
|
this.currentModel = trimmed;
|
|
3552
3752
|
for (const handler of this.modelHandlers) {
|
|
3553
3753
|
try {
|
|
@@ -3593,6 +3793,9 @@ var init_session = __esm({
|
|
|
3593
3793
|
if (typeof cv === "string") {
|
|
3594
3794
|
const trimmed = cv.trim();
|
|
3595
3795
|
if (trimmed && trimmed !== this.currentModel) {
|
|
3796
|
+
this.logger?.info(
|
|
3797
|
+
`live config_option_update(model): sessionId=${this.sessionId} ${JSON.stringify(this.currentModel)} \u2192 ${JSON.stringify(trimmed)}`
|
|
3798
|
+
);
|
|
3596
3799
|
this.currentModel = trimmed;
|
|
3597
3800
|
for (const handler of this.modelHandlers) {
|
|
3598
3801
|
try {
|
|
@@ -3761,6 +3964,9 @@ var init_session = __esm({
|
|
|
3761
3964
|
this.broadcastAvailableModes();
|
|
3762
3965
|
}
|
|
3763
3966
|
setAgentAdvertisedModels(models) {
|
|
3967
|
+
this.logger?.info(
|
|
3968
|
+
`setAgentAdvertisedModels: sessionId=${this.sessionId} currentModel=${JSON.stringify(this.currentModel)} newList=[${models.map((m) => m.modelId).join(",")}]`
|
|
3969
|
+
);
|
|
3764
3970
|
if (sameAdvertisedModels(this.agentAdvertisedModels, models)) {
|
|
3765
3971
|
this.broadcastAvailableModels();
|
|
3766
3972
|
return;
|
|
@@ -3792,6 +3998,38 @@ var init_session = __esm({
|
|
|
3792
3998
|
onModeChange(handler) {
|
|
3793
3999
|
this.modeHandlers.push(handler);
|
|
3794
4000
|
}
|
|
4001
|
+
// Apply a model change initiated by a client request (session/set_model)
|
|
4002
|
+
// when the agent doesn't emit a current_model_update notification, or
|
|
4003
|
+
// emits a non-spec shape (e.g. config_option_update). Fires modelHandlers
|
|
4004
|
+
// (persistence) and broadcasts a synthetic current_model_update so all
|
|
4005
|
+
// attached clients — including the originator — repaint immediately.
|
|
4006
|
+
applyModelChange(modelId) {
|
|
4007
|
+
const trimmed = modelId.trim();
|
|
4008
|
+
if (!trimmed || trimmed === this.currentModel) {
|
|
4009
|
+
return;
|
|
4010
|
+
}
|
|
4011
|
+
this.logger?.info(
|
|
4012
|
+
`applyModelChange: sessionId=${this.sessionId} ${JSON.stringify(this.currentModel)} \u2192 ${JSON.stringify(trimmed)}`
|
|
4013
|
+
);
|
|
4014
|
+
this.currentModel = trimmed;
|
|
4015
|
+
for (const handler of this.modelHandlers) {
|
|
4016
|
+
try {
|
|
4017
|
+
handler(trimmed);
|
|
4018
|
+
} catch {
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
const update = {
|
|
4022
|
+
sessionUpdate: "current_model_update",
|
|
4023
|
+
currentModel: trimmed
|
|
4024
|
+
};
|
|
4025
|
+
if (this.agentAdvertisedModels.length > 0) {
|
|
4026
|
+
update.availableModels = [...this.agentAdvertisedModels];
|
|
4027
|
+
}
|
|
4028
|
+
this.recordAndBroadcast("session/update", {
|
|
4029
|
+
sessionId: this.upstreamSessionId,
|
|
4030
|
+
update
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
3795
4033
|
// Apply a mode change initiated by a client request (session/set_mode)
|
|
3796
4034
|
// when the agent doesn't emit a current_mode_update notification on its
|
|
3797
4035
|
// own. Fires modeHandlers so the persistence hook and any other listeners
|
|
@@ -3815,11 +4053,31 @@ var init_session = __esm({
|
|
|
3815
4053
|
onUsageChange(handler) {
|
|
3816
4054
|
this.usageHandlers.push(handler);
|
|
3817
4055
|
}
|
|
3818
|
-
// Returns a freshly merged command list (hydra ∪ agent) for
|
|
3819
|
-
// that need a snapshot — notably acp-ws.ts's buildResponseMeta
|
|
3820
|
-
// assembling the attach response.
|
|
4056
|
+
// Returns a freshly merged command list (hydra ∪ extension ∪ agent) for
|
|
4057
|
+
// callers that need a snapshot — notably acp-ws.ts's buildResponseMeta
|
|
4058
|
+
// when assembling the attach response. Order: built-in hydra verbs,
|
|
4059
|
+
// top-level daemon verbs (/model, /sessions, /help), extension-registered
|
|
4060
|
+
// entries, then whatever the agent advertised.
|
|
3821
4061
|
mergedAvailableCommands() {
|
|
3822
|
-
|
|
4062
|
+
const out = [
|
|
4063
|
+
...hydraCommandsAsAdvertised(),
|
|
4064
|
+
{ name: "model <model-id>", description: "Switch model; omit arg to list available models" },
|
|
4065
|
+
{ name: "sessions", description: "List all sessions" },
|
|
4066
|
+
{ name: "help", description: "Show available commands" }
|
|
4067
|
+
];
|
|
4068
|
+
if (this.extensionCommands) {
|
|
4069
|
+
for (const { name, command } of this.extensionCommands.list()) {
|
|
4070
|
+
const head = `hydra ${name} ${command.verb}`;
|
|
4071
|
+
const display = command.argsHint ? `${head} ${command.argsHint}` : head;
|
|
4072
|
+
const entry = { name: display };
|
|
4073
|
+
if (command.description) {
|
|
4074
|
+
entry.description = command.description;
|
|
4075
|
+
}
|
|
4076
|
+
out.push(entry);
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
out.push(...this.agentAdvertisedCommands);
|
|
4080
|
+
return out;
|
|
3823
4081
|
}
|
|
3824
4082
|
// The agent's own advertised commands (not merged with hydra verbs).
|
|
3825
4083
|
// Used by SessionManager to persist into meta.json so cold resurrect
|
|
@@ -3871,39 +4129,118 @@ var init_session = __esm({
|
|
|
3871
4129
|
// caller's promise resolves like a normal turn. To add a verb: append
|
|
3872
4130
|
// an entry to HYDRA_COMMANDS (drives validation + client advertising)
|
|
3873
4131
|
// and a dispatch case in the switch below.
|
|
4132
|
+
//
|
|
4133
|
+
// Extensions/transformers can also bind verbs via the
|
|
4134
|
+
// ExtensionCommandRegistry: "/hydra <process-name> <verb> [args]" routes
|
|
4135
|
+
// to that process's WS connection. Built-in hydra verbs win on name
|
|
4136
|
+
// collision so an extension can never shadow them.
|
|
3874
4137
|
async handleSlashCommand(text) {
|
|
3875
4138
|
const rest = text.slice("/hydra".length).trim();
|
|
3876
4139
|
const match = rest.match(/^(\S+)(?:\s+([\s\S]*))?$/);
|
|
3877
|
-
const
|
|
3878
|
-
const
|
|
3879
|
-
if (
|
|
4140
|
+
const first = match?.[1] ?? "";
|
|
4141
|
+
const remainder = (match?.[2] ?? "").trim();
|
|
4142
|
+
if (first === "") {
|
|
3880
4143
|
return { stopReason: "end_turn" };
|
|
3881
4144
|
}
|
|
3882
|
-
if (
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
4145
|
+
if (HYDRA_COMMANDS.some((c) => c.verb === first)) {
|
|
4146
|
+
switch (first) {
|
|
4147
|
+
case "title":
|
|
4148
|
+
return this.runTitleCommand(remainder);
|
|
4149
|
+
case "agent":
|
|
4150
|
+
return this.runAgentCommand(remainder);
|
|
4151
|
+
case "kill":
|
|
4152
|
+
return this.runKillCommand();
|
|
4153
|
+
case "restart":
|
|
4154
|
+
return this.runRestartCommand();
|
|
4155
|
+
default: {
|
|
4156
|
+
const err2 = new Error(
|
|
4157
|
+
`no dispatcher for /hydra verb ${first}`
|
|
4158
|
+
);
|
|
4159
|
+
err2.code = JsonRpcErrorCodes.InternalError;
|
|
4160
|
+
throw err2;
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
3889
4163
|
}
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
`no dispatcher for /hydra verb ${verb}`
|
|
3902
|
-
);
|
|
3903
|
-
err.code = JsonRpcErrorCodes.InternalError;
|
|
3904
|
-
throw err;
|
|
4164
|
+
if (this.extensionCommands?.has(first)) {
|
|
4165
|
+
return this.runExtensionCommand(first, remainder);
|
|
4166
|
+
}
|
|
4167
|
+
const known = HYDRA_COMMANDS.map((c) => c.verb);
|
|
4168
|
+
if (this.extensionCommands) {
|
|
4169
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4170
|
+
for (const { name } of this.extensionCommands.list()) {
|
|
4171
|
+
if (!seen.has(name)) {
|
|
4172
|
+
known.push(name);
|
|
4173
|
+
seen.add(name);
|
|
4174
|
+
}
|
|
3905
4175
|
}
|
|
3906
4176
|
}
|
|
4177
|
+
const err = new Error(
|
|
4178
|
+
`unknown /hydra verb: ${first} (known: ${known.join(", ")})`
|
|
4179
|
+
);
|
|
4180
|
+
err.code = JsonRpcErrorCodes.InvalidParams;
|
|
4181
|
+
throw err;
|
|
4182
|
+
}
|
|
4183
|
+
// "/hydra <name> <verb> [args]" — name matches a registered extension
|
|
4184
|
+
// or transformer. We split the remainder into verb + args, validate the
|
|
4185
|
+
// verb against what the process advertised, and forward as a
|
|
4186
|
+
// hydra-acp/extension_command request on the process's WS connection.
|
|
4187
|
+
// The reply's text (if any) is broadcast as a synthetic
|
|
4188
|
+
// agent_message_chunk so it appears in the conversation alongside the
|
|
4189
|
+
// user's invocation.
|
|
4190
|
+
runExtensionCommand(name, remainder) {
|
|
4191
|
+
return this.enqueuePrompt(async () => {
|
|
4192
|
+
const entry = this.extensionCommands?.get(name);
|
|
4193
|
+
if (!entry) {
|
|
4194
|
+
return this.emitExtensionReply(
|
|
4195
|
+
`extension "${name}" is no longer connected`
|
|
4196
|
+
);
|
|
4197
|
+
}
|
|
4198
|
+
const m = remainder.match(/^(\S+)(?:\s+([\s\S]*))?$/);
|
|
4199
|
+
const verb = m?.[1] ?? "";
|
|
4200
|
+
const args = (m?.[2] ?? "").trim();
|
|
4201
|
+
if (verb === "") {
|
|
4202
|
+
const verbs = entry.commands.map((c) => c.verb).join(", ");
|
|
4203
|
+
return this.emitExtensionReply(
|
|
4204
|
+
`/hydra ${name} requires a verb (known: ${verbs || "(none)"})`
|
|
4205
|
+
);
|
|
4206
|
+
}
|
|
4207
|
+
if (!entry.commands.some((c) => c.verb === verb)) {
|
|
4208
|
+
const verbs = entry.commands.map((c) => c.verb).join(", ");
|
|
4209
|
+
return this.emitExtensionReply(
|
|
4210
|
+
`unknown verb "${verb}" for ${name} (known: ${verbs || "(none)"})`
|
|
4211
|
+
);
|
|
4212
|
+
}
|
|
4213
|
+
let reply;
|
|
4214
|
+
try {
|
|
4215
|
+
reply = await entry.connection.request("hydra-acp/extension_command", {
|
|
4216
|
+
sessionId: this.sessionId,
|
|
4217
|
+
verb,
|
|
4218
|
+
args
|
|
4219
|
+
});
|
|
4220
|
+
} catch (err) {
|
|
4221
|
+
return this.emitExtensionReply(
|
|
4222
|
+
`${name} ${verb}: ${err.message}`
|
|
4223
|
+
);
|
|
4224
|
+
}
|
|
4225
|
+
const text = reply && typeof reply === "object" && typeof reply.text === "string" ? reply.text : "";
|
|
4226
|
+
if (text.length > 0) {
|
|
4227
|
+
return this.emitExtensionReply(text);
|
|
4228
|
+
}
|
|
4229
|
+
return { stopReason: "end_turn" };
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
emitExtensionReply(text) {
|
|
4233
|
+
this.recordAndBroadcast("session/update", {
|
|
4234
|
+
sessionId: this.upstreamSessionId,
|
|
4235
|
+
update: {
|
|
4236
|
+
sessionUpdate: "agent_message_chunk",
|
|
4237
|
+
content: { type: "text", text: `
|
|
4238
|
+
${text}
|
|
4239
|
+
` },
|
|
4240
|
+
_meta: { "hydra-acp": { synthetic: true } }
|
|
4241
|
+
}
|
|
4242
|
+
});
|
|
4243
|
+
return { stopReason: "end_turn" };
|
|
3907
4244
|
}
|
|
3908
4245
|
async handleSessionsCommand() {
|
|
3909
4246
|
let text;
|
|
@@ -3966,11 +4303,15 @@ ${text}
|
|
|
3966
4303
|
if (models.length === 0) {
|
|
3967
4304
|
body = current ? `Current model: ${current}` : "_(no models advertised yet)_";
|
|
3968
4305
|
} else {
|
|
4306
|
+
const inList = current ? models.some((m) => m.modelId === current) : true;
|
|
3969
4307
|
const lines = models.map((m) => {
|
|
3970
4308
|
const marker = m.modelId === current ? " \u25C0" : "";
|
|
3971
4309
|
const desc = m.name && m.name !== m.modelId ? ` ${m.name}` : "";
|
|
3972
4310
|
return `${m.modelId}${marker}${desc}`;
|
|
3973
4311
|
});
|
|
4312
|
+
if (!inList && current) {
|
|
4313
|
+
lines.unshift(`${current} \u25C0`);
|
|
4314
|
+
}
|
|
3974
4315
|
body = lines.join("\n");
|
|
3975
4316
|
}
|
|
3976
4317
|
this.recordAndBroadcast("session/update", {
|
|
@@ -4382,6 +4723,43 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
4382
4723
|
}
|
|
4383
4724
|
return out;
|
|
4384
4725
|
}
|
|
4726
|
+
streamTail(bytes) {
|
|
4727
|
+
const buf = this.requireStreamBuffer();
|
|
4728
|
+
const r = buf.tail(bytes);
|
|
4729
|
+
return {
|
|
4730
|
+
bytes: r.bytes.toString("base64"),
|
|
4731
|
+
startCursor: r.startCursor,
|
|
4732
|
+
endCursor: r.endCursor,
|
|
4733
|
+
truncated: r.truncated
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4736
|
+
streamHead(bytes) {
|
|
4737
|
+
const buf = this.requireStreamBuffer();
|
|
4738
|
+
const r = buf.head(bytes);
|
|
4739
|
+
return {
|
|
4740
|
+
bytes: r.bytes.toString("base64"),
|
|
4741
|
+
startCursor: r.startCursor,
|
|
4742
|
+
endCursor: r.endCursor,
|
|
4743
|
+
truncated: r.truncated
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
async streamWaitFor(cursor, timeoutMs) {
|
|
4747
|
+
const buf = this.requireStreamBuffer();
|
|
4748
|
+
return buf.waitForData(cursor, timeoutMs);
|
|
4749
|
+
}
|
|
4750
|
+
streamGrep(opts) {
|
|
4751
|
+
const buf = this.requireStreamBuffer();
|
|
4752
|
+
return buf.grep(opts);
|
|
4753
|
+
}
|
|
4754
|
+
streamInfo() {
|
|
4755
|
+
const buf = this.requireStreamBuffer();
|
|
4756
|
+
return {
|
|
4757
|
+
writeCursor: buf.writeCursorPos,
|
|
4758
|
+
oldestAvailable: buf.oldestAvailable,
|
|
4759
|
+
capacity: buf.capacity,
|
|
4760
|
+
closed: buf.isClosed
|
|
4761
|
+
};
|
|
4762
|
+
}
|
|
4385
4763
|
requireStreamBuffer() {
|
|
4386
4764
|
if (this.streamBuffer === void 0) {
|
|
4387
4765
|
const err = new Error(
|
|
@@ -4398,6 +4776,10 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
4398
4776
|
}
|
|
4399
4777
|
this.closed = true;
|
|
4400
4778
|
this.cancelIdleTimer();
|
|
4779
|
+
if (this.extensionCommandsUnsub) {
|
|
4780
|
+
this.extensionCommandsUnsub();
|
|
4781
|
+
this.extensionCommandsUnsub = void 0;
|
|
4782
|
+
}
|
|
4401
4783
|
if (this.currentEntry?.kind === "user") {
|
|
4402
4784
|
this.broadcastTurnComplete(
|
|
4403
4785
|
this.currentEntry.clientId,
|
|
@@ -4968,11 +5350,12 @@ function resolveVersion() {
|
|
|
4968
5350
|
}
|
|
4969
5351
|
return "0.0.0";
|
|
4970
5352
|
}
|
|
4971
|
-
var HYDRA_VERSION;
|
|
5353
|
+
var HYDRA_VERSION, HYDRA_CAT_CLIENT_NAME;
|
|
4972
5354
|
var init_hydra_version = __esm({
|
|
4973
5355
|
"src/core/hydra-version.ts"() {
|
|
4974
5356
|
"use strict";
|
|
4975
5357
|
HYDRA_VERSION = resolveVersion();
|
|
5358
|
+
HYDRA_CAT_CLIENT_NAME = "hydra-acp-cat";
|
|
4976
5359
|
}
|
|
4977
5360
|
});
|
|
4978
5361
|
|
|
@@ -5560,7 +5943,8 @@ async function listSessions(target, opts = {}, fetchImpl = fetch) {
|
|
|
5560
5943
|
title: s.title,
|
|
5561
5944
|
importedFromMachine: s.importedFromMachine,
|
|
5562
5945
|
importedFromUpstreamSessionId: s.importedFromUpstreamSessionId,
|
|
5563
|
-
busy: s.busy
|
|
5946
|
+
busy: s.busy,
|
|
5947
|
+
originatingClient: s.originatingClient
|
|
5564
5948
|
}));
|
|
5565
5949
|
}
|
|
5566
5950
|
async function killSession(target, id, fetchImpl = fetch) {
|
|
@@ -5598,6 +5982,20 @@ async function regenSessionTitle(target, id, fetchImpl = fetch) {
|
|
|
5598
5982
|
throw new Error(`daemon returned HTTP ${response.status}`);
|
|
5599
5983
|
}
|
|
5600
5984
|
}
|
|
5985
|
+
async function searchSessions(target, query, opts = {}, fetchImpl = fetch) {
|
|
5986
|
+
const url = new URL(`${target.baseUrl}/v1/sessions/search`);
|
|
5987
|
+
url.searchParams.set("q", query);
|
|
5988
|
+
if (opts.sessionIds && opts.sessionIds.length > 0) {
|
|
5989
|
+
url.searchParams.set("sessionIds", opts.sessionIds.join(","));
|
|
5990
|
+
}
|
|
5991
|
+
const response = await fetchImpl(url.toString(), {
|
|
5992
|
+
headers: { Authorization: `Bearer ${target.token}` }
|
|
5993
|
+
});
|
|
5994
|
+
if (!response.ok) {
|
|
5995
|
+
throw new Error(`daemon returned HTTP ${response.status}`);
|
|
5996
|
+
}
|
|
5997
|
+
return await response.json();
|
|
5998
|
+
}
|
|
5601
5999
|
async function deleteSession(target, id, fetchImpl = fetch) {
|
|
5602
6000
|
const response = await fetchImpl(`${target.baseUrl}/v1/sessions/${id}`, {
|
|
5603
6001
|
method: "DELETE",
|
|
@@ -6066,6 +6464,47 @@ var init_resilient_ws = __esm({
|
|
|
6066
6464
|
}
|
|
6067
6465
|
});
|
|
6068
6466
|
|
|
6467
|
+
// src/acp/permission-pick.ts
|
|
6468
|
+
function pickPermissionOptionId(params, preferredKinds) {
|
|
6469
|
+
const options = params && typeof params === "object" ? params.options : void 0;
|
|
6470
|
+
if (Array.isArray(options)) {
|
|
6471
|
+
for (const kind of preferredKinds) {
|
|
6472
|
+
const match = options.find(
|
|
6473
|
+
(o) => typeof o === "object" && o !== null && o.kind === kind && typeof o.optionId === "string"
|
|
6474
|
+
);
|
|
6475
|
+
if (match?.optionId !== void 0) {
|
|
6476
|
+
return match.optionId;
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
const fallback = options.find(
|
|
6480
|
+
(o) => typeof o === "object" && o !== null && typeof o.optionId === "string"
|
|
6481
|
+
);
|
|
6482
|
+
if (fallback?.optionId !== void 0) {
|
|
6483
|
+
return fallback.optionId;
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
return preferredKinds[0] ?? "allow";
|
|
6487
|
+
}
|
|
6488
|
+
function buildApproveResponse(params) {
|
|
6489
|
+
const optionId = pickPermissionOptionId(params, [
|
|
6490
|
+
"allow_once",
|
|
6491
|
+
"allow_always"
|
|
6492
|
+
]);
|
|
6493
|
+
return { outcome: { outcome: "selected", optionId } };
|
|
6494
|
+
}
|
|
6495
|
+
function buildRejectResponse(params) {
|
|
6496
|
+
const optionId = pickPermissionOptionId(params, [
|
|
6497
|
+
"reject_once",
|
|
6498
|
+
"reject_always"
|
|
6499
|
+
]);
|
|
6500
|
+
return { outcome: { outcome: "selected", optionId } };
|
|
6501
|
+
}
|
|
6502
|
+
var init_permission_pick = __esm({
|
|
6503
|
+
"src/acp/permission-pick.ts"() {
|
|
6504
|
+
"use strict";
|
|
6505
|
+
}
|
|
6506
|
+
});
|
|
6507
|
+
|
|
6069
6508
|
// src/core/update-check.ts
|
|
6070
6509
|
function disabled() {
|
|
6071
6510
|
if (process.env.NO_UPDATE_NOTIFIER === "1") {
|
|
@@ -7277,7 +7716,7 @@ function writeStyled(term, text, style) {
|
|
|
7277
7716
|
term(text);
|
|
7278
7717
|
return;
|
|
7279
7718
|
case "thought":
|
|
7280
|
-
term.brightBlack
|
|
7719
|
+
term.brightBlack(text);
|
|
7281
7720
|
return;
|
|
7282
7721
|
case "tool":
|
|
7283
7722
|
term.brightBlue.noFormat(text);
|
|
@@ -9982,11 +10421,8 @@ uncaught: ${err.stack ?? err.message}
|
|
|
9982
10421
|
}
|
|
9983
10422
|
});
|
|
9984
10423
|
|
|
9985
|
-
// src/tui/
|
|
9986
|
-
function
|
|
9987
|
-
return { filters: { cwdOnly: false, hostFilter: "__local" } };
|
|
9988
|
-
}
|
|
9989
|
-
async function pickSession(term, opts) {
|
|
10424
|
+
// src/tui/prompt-utils.ts
|
|
10425
|
+
function resetTerminalModes() {
|
|
9990
10426
|
process.stdout.write("\x1B[<u");
|
|
9991
10427
|
process.stdout.write("\x1B[?2004l");
|
|
9992
10428
|
process.stdout.write("\x1B[>4;0m");
|
|
@@ -9996,36 +10432,476 @@ async function pickSession(term, opts) {
|
|
|
9996
10432
|
process.stdout.write("\x1B[?1006l");
|
|
9997
10433
|
process.stdout.write("\x1B[?1l");
|
|
9998
10434
|
process.stdout.write("\x1B>");
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10435
|
+
}
|
|
10436
|
+
function readTermWidth(term) {
|
|
10437
|
+
return term.width ?? 80;
|
|
10438
|
+
}
|
|
10439
|
+
function readTermHeight(term) {
|
|
10440
|
+
return term.height ?? 24;
|
|
10441
|
+
}
|
|
10442
|
+
function drawBox(term, opts) {
|
|
10443
|
+
const termW = readTermWidth(term);
|
|
10444
|
+
const termH = readTermHeight(term);
|
|
10445
|
+
const desiredContentW = opts.contentWidth ?? MAX_BOX_WIDTH;
|
|
10446
|
+
const maxContentW = Math.max(10, Math.min(MAX_BOX_WIDTH, termW - 4));
|
|
10447
|
+
const contentW = Math.min(desiredContentW, maxContentW);
|
|
10448
|
+
const w = contentW + 2;
|
|
10449
|
+
const contentH = Math.max(1, Math.min(opts.contentHeight, termH - 4));
|
|
10450
|
+
const h = contentH + 2;
|
|
10451
|
+
const x = Math.max(1, Math.floor((termW - w) / 2) + 1);
|
|
10452
|
+
const y = Math.max(1, Math.floor((termH - h) / 2) + 1);
|
|
10453
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
10454
|
+
const topInner = HORIZ.repeat(w - 2);
|
|
10455
|
+
const top = renderTitleStrip(topInner, opts.title);
|
|
10456
|
+
term.moveTo(x, y);
|
|
10457
|
+
term.dim.noFormat(TL);
|
|
10458
|
+
paintTopStrip(term, top);
|
|
10459
|
+
term.dim.noFormat(TR);
|
|
10460
|
+
for (let row = 1; row <= contentH; row++) {
|
|
10461
|
+
term.moveTo(x, y + row);
|
|
10462
|
+
term.dim.noFormat(VERT);
|
|
10463
|
+
term.moveTo(x + w - 1, y + row);
|
|
10464
|
+
term.dim.noFormat(VERT);
|
|
10465
|
+
}
|
|
10466
|
+
term.moveTo(x, y + h - 1);
|
|
10467
|
+
term.dim.noFormat(BL + HORIZ.repeat(w - 2) + BR);
|
|
10468
|
+
return {
|
|
10469
|
+
x,
|
|
10470
|
+
y,
|
|
10471
|
+
w,
|
|
10472
|
+
h,
|
|
10473
|
+
contentX: x + 1,
|
|
10474
|
+
contentY: y + 1,
|
|
10475
|
+
contentW,
|
|
10476
|
+
contentH
|
|
10013
10477
|
};
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
);
|
|
10019
|
-
if (current?.importedFromMachine) {
|
|
10020
|
-
prefs.filters.hostFilter = "__all";
|
|
10021
|
-
}
|
|
10478
|
+
}
|
|
10479
|
+
function renderTitleStrip(innerDashes, title) {
|
|
10480
|
+
if (!title) {
|
|
10481
|
+
return { dashes: innerDashes };
|
|
10022
10482
|
}
|
|
10023
|
-
|
|
10024
|
-
|
|
10483
|
+
const chip = ` ${title} `;
|
|
10484
|
+
if (chip.length + 4 > innerDashes.length) {
|
|
10485
|
+
return { dashes: innerDashes };
|
|
10486
|
+
}
|
|
10487
|
+
const offset = 2;
|
|
10488
|
+
const dashes = innerDashes.slice(0, offset) + " ".repeat(chip.length) + innerDashes.slice(offset + chip.length);
|
|
10489
|
+
return { dashes, title: { offset, text: chip } };
|
|
10490
|
+
}
|
|
10491
|
+
function paintTopStrip(term, strip) {
|
|
10492
|
+
if (!strip.title) {
|
|
10493
|
+
term.dim.noFormat(strip.dashes);
|
|
10494
|
+
return;
|
|
10495
|
+
}
|
|
10496
|
+
term.dim.noFormat(strip.dashes.slice(0, strip.title.offset));
|
|
10497
|
+
term.brightCyan.noFormat(strip.title.text);
|
|
10498
|
+
term.dim.noFormat(strip.dashes.slice(strip.title.offset + strip.title.text.length));
|
|
10499
|
+
}
|
|
10500
|
+
var MAX_BOX_WIDTH, HORIZ, VERT, TL, TR, BL, BR;
|
|
10501
|
+
var init_prompt_utils = __esm({
|
|
10502
|
+
"src/tui/prompt-utils.ts"() {
|
|
10503
|
+
"use strict";
|
|
10504
|
+
MAX_BOX_WIDTH = 64;
|
|
10505
|
+
HORIZ = "\u2500";
|
|
10506
|
+
VERT = "\u2502";
|
|
10507
|
+
TL = "\u250C";
|
|
10508
|
+
TR = "\u2510";
|
|
10509
|
+
BL = "\u2514";
|
|
10510
|
+
BR = "\u2518";
|
|
10511
|
+
}
|
|
10512
|
+
});
|
|
10513
|
+
|
|
10514
|
+
// src/tui/import-action-prompt.ts
|
|
10515
|
+
function actionPromptStep(selected, key, choices = ACTION_CHOICES) {
|
|
10516
|
+
if (key.kind === "cancel") {
|
|
10517
|
+
return { kind: "cancel" };
|
|
10518
|
+
}
|
|
10519
|
+
if (key.kind === "back") {
|
|
10520
|
+
return { kind: "back" };
|
|
10521
|
+
}
|
|
10522
|
+
if (key.kind === "enter") {
|
|
10523
|
+
const choice = choices[selected];
|
|
10524
|
+
if (!choice) {
|
|
10525
|
+
return { kind: "back" };
|
|
10526
|
+
}
|
|
10527
|
+
return { kind: "resolve", action: choice.key };
|
|
10528
|
+
}
|
|
10529
|
+
if (key.kind === "up") {
|
|
10530
|
+
return {
|
|
10531
|
+
kind: "continue",
|
|
10532
|
+
selected: Math.max(0, selected - 1)
|
|
10533
|
+
};
|
|
10534
|
+
}
|
|
10535
|
+
if (key.kind === "down") {
|
|
10536
|
+
return {
|
|
10537
|
+
kind: "continue",
|
|
10538
|
+
selected: Math.min(choices.length - 1, selected + 1)
|
|
10539
|
+
};
|
|
10540
|
+
}
|
|
10541
|
+
if (key.kind === "char") {
|
|
10542
|
+
const lower = key.ch.toLowerCase();
|
|
10543
|
+
if (lower === "n") {
|
|
10544
|
+
return {
|
|
10545
|
+
kind: "continue",
|
|
10546
|
+
selected: Math.min(choices.length - 1, selected + 1)
|
|
10547
|
+
};
|
|
10548
|
+
}
|
|
10549
|
+
if (lower === "p") {
|
|
10550
|
+
return {
|
|
10551
|
+
kind: "continue",
|
|
10552
|
+
selected: Math.max(0, selected - 1)
|
|
10553
|
+
};
|
|
10554
|
+
}
|
|
10555
|
+
const idx = choices.findIndex((c) => c.hotkey.toLowerCase() === lower);
|
|
10556
|
+
if (idx >= 0) {
|
|
10557
|
+
const choice = choices[idx];
|
|
10558
|
+
if (choice) {
|
|
10559
|
+
return { kind: "resolve", action: choice.key };
|
|
10560
|
+
}
|
|
10561
|
+
}
|
|
10562
|
+
}
|
|
10563
|
+
return { kind: "continue", selected };
|
|
10564
|
+
}
|
|
10565
|
+
async function promptForImportAction(term, session) {
|
|
10566
|
+
resetTerminalModes();
|
|
10567
|
+
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
10568
|
+
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
10569
|
+
const originalCwd = shortenHomePath(session.cwd);
|
|
10570
|
+
let selected = ACTION_CHOICES.findIndex((c) => c.key === "view");
|
|
10571
|
+
if (selected < 0) {
|
|
10572
|
+
selected = 0;
|
|
10573
|
+
}
|
|
10574
|
+
const render = () => {
|
|
10575
|
+
const choiceRows = ACTION_CHOICES.length * 2;
|
|
10576
|
+
const contentHeight = 7 + choiceRows + 2;
|
|
10577
|
+
const layout = drawBox(term, {
|
|
10578
|
+
contentHeight,
|
|
10579
|
+
title: "Imported session"
|
|
10580
|
+
});
|
|
10581
|
+
const innerW = layout.contentW;
|
|
10582
|
+
const headerRows = [
|
|
10583
|
+
{ label: "session: ", value: shortId2 },
|
|
10584
|
+
{ label: "from: ", value: fromMachine },
|
|
10585
|
+
{ label: "cwd: ", value: originalCwd }
|
|
10586
|
+
];
|
|
10587
|
+
let row = 0;
|
|
10588
|
+
for (const hr of headerRows) {
|
|
10589
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10590
|
+
term.dim.noFormat(` ${hr.label}`);
|
|
10591
|
+
term.noFormat(truncate2(hr.value, innerW - hr.label.length - 2));
|
|
10592
|
+
row++;
|
|
10593
|
+
}
|
|
10594
|
+
row++;
|
|
10595
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10596
|
+
term.noFormat(" What do you want to do?");
|
|
10597
|
+
row += 2;
|
|
10598
|
+
for (let i = 0; i < ACTION_CHOICES.length; i++) {
|
|
10599
|
+
const choice = ACTION_CHOICES[i];
|
|
10600
|
+
if (!choice) {
|
|
10601
|
+
continue;
|
|
10602
|
+
}
|
|
10603
|
+
const pointer = i === selected ? "\u276F" : " ";
|
|
10604
|
+
const label = ` ${pointer} ${choice.label}`;
|
|
10605
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10606
|
+
if (i === selected) {
|
|
10607
|
+
term.brightWhite.bgBlue.noFormat(padRight(label, innerW));
|
|
10608
|
+
} else {
|
|
10609
|
+
term.noFormat(label);
|
|
10610
|
+
}
|
|
10611
|
+
row++;
|
|
10612
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10613
|
+
term.dim.noFormat(` ${choice.description}`);
|
|
10614
|
+
row++;
|
|
10615
|
+
}
|
|
10616
|
+
row++;
|
|
10617
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10618
|
+
term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 f/v jump \xB7 Esc back");
|
|
10619
|
+
return layout;
|
|
10620
|
+
};
|
|
10621
|
+
render();
|
|
10622
|
+
term.hideCursor();
|
|
10623
|
+
return await new Promise((resolve6) => {
|
|
10624
|
+
let resolved = false;
|
|
10625
|
+
const cleanup = () => {
|
|
10626
|
+
if (resolved) {
|
|
10627
|
+
return;
|
|
10628
|
+
}
|
|
10629
|
+
resolved = true;
|
|
10630
|
+
term.off("key", onKey);
|
|
10631
|
+
term.off("resize", onResize);
|
|
10632
|
+
term.grabInput(false);
|
|
10633
|
+
term.hideCursor(false);
|
|
10634
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
10635
|
+
};
|
|
10636
|
+
const finish = (value) => {
|
|
10637
|
+
cleanup();
|
|
10638
|
+
resolve6(value);
|
|
10639
|
+
};
|
|
10640
|
+
const onResize = () => {
|
|
10641
|
+
if (resolved) {
|
|
10642
|
+
return;
|
|
10643
|
+
}
|
|
10644
|
+
render();
|
|
10645
|
+
};
|
|
10646
|
+
const onKey = (name, _matches, data) => {
|
|
10647
|
+
const input = mapKey(name, data);
|
|
10648
|
+
if (!input) {
|
|
10649
|
+
return;
|
|
10650
|
+
}
|
|
10651
|
+
const step = actionPromptStep(selected, input);
|
|
10652
|
+
if (step.kind === "cancel") {
|
|
10653
|
+
finish("cancel");
|
|
10654
|
+
return;
|
|
10655
|
+
}
|
|
10656
|
+
if (step.kind === "back") {
|
|
10657
|
+
finish("back");
|
|
10658
|
+
return;
|
|
10659
|
+
}
|
|
10660
|
+
if (step.kind === "resolve") {
|
|
10661
|
+
finish(step.action);
|
|
10662
|
+
return;
|
|
10663
|
+
}
|
|
10664
|
+
if (step.selected !== selected) {
|
|
10665
|
+
selected = step.selected;
|
|
10666
|
+
render();
|
|
10667
|
+
}
|
|
10668
|
+
};
|
|
10669
|
+
term.grabInput({});
|
|
10670
|
+
term.on("key", onKey);
|
|
10671
|
+
term.on("resize", onResize);
|
|
10672
|
+
});
|
|
10673
|
+
}
|
|
10674
|
+
function mapKey(name, data) {
|
|
10675
|
+
if (name === "UP") {
|
|
10676
|
+
return { kind: "up" };
|
|
10677
|
+
}
|
|
10678
|
+
if (name === "DOWN") {
|
|
10679
|
+
return { kind: "down" };
|
|
10680
|
+
}
|
|
10681
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
10682
|
+
return { kind: "enter" };
|
|
10683
|
+
}
|
|
10684
|
+
if (name === "ESCAPE") {
|
|
10685
|
+
return { kind: "back" };
|
|
10686
|
+
}
|
|
10687
|
+
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
10688
|
+
return { kind: "cancel" };
|
|
10689
|
+
}
|
|
10690
|
+
if (data?.isCharacter) {
|
|
10691
|
+
return { kind: "char", ch: name };
|
|
10692
|
+
}
|
|
10693
|
+
return null;
|
|
10694
|
+
}
|
|
10695
|
+
async function promptForLaunchOrView(term, session, focus) {
|
|
10696
|
+
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
10697
|
+
const titleOrCwd = session.title ?? shortenHomePath(session.cwd);
|
|
10698
|
+
let selected = 1;
|
|
10699
|
+
const CHOICES = [
|
|
10700
|
+
{ label: "Launch", hotkey: "l", description: "start a new agent session" },
|
|
10701
|
+
{ label: "View transcript", hotkey: "v", description: "open read-only, no agent spawn" }
|
|
10702
|
+
];
|
|
10703
|
+
const render = () => {
|
|
10704
|
+
const layout = drawBox(term, { contentHeight: 11, title: "Open session" });
|
|
10705
|
+
const innerW = layout.contentW;
|
|
10706
|
+
let row = 0;
|
|
10707
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10708
|
+
term.dim.noFormat(" session: ");
|
|
10709
|
+
term.noFormat(truncate2(shortId2, innerW - 10));
|
|
10710
|
+
row++;
|
|
10711
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10712
|
+
term.noFormat(" " + truncate2(titleOrCwd, innerW - 2));
|
|
10713
|
+
row++;
|
|
10714
|
+
row++;
|
|
10715
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10716
|
+
term.noFormat(" What do you want to do?");
|
|
10717
|
+
row += 2;
|
|
10718
|
+
for (let i = 0; i < CHOICES.length; i++) {
|
|
10719
|
+
const choice = CHOICES[i];
|
|
10720
|
+
if (!choice) {
|
|
10721
|
+
continue;
|
|
10722
|
+
}
|
|
10723
|
+
const pointer = i === selected ? "\u276F" : " ";
|
|
10724
|
+
const label = ` ${pointer} ${choice.label}`;
|
|
10725
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10726
|
+
if (i === selected) {
|
|
10727
|
+
term.brightWhite.bgBlue.noFormat(padRight(label, innerW));
|
|
10728
|
+
} else {
|
|
10729
|
+
term.noFormat(label);
|
|
10730
|
+
}
|
|
10731
|
+
row++;
|
|
10732
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10733
|
+
term.dim.noFormat(` ${choice.description}`);
|
|
10734
|
+
row++;
|
|
10735
|
+
}
|
|
10736
|
+
row++;
|
|
10737
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10738
|
+
term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 l/v jump \xB7 Esc back");
|
|
10739
|
+
};
|
|
10740
|
+
render();
|
|
10741
|
+
term.hideCursor();
|
|
10742
|
+
return await new Promise((resolve6) => {
|
|
10743
|
+
let resolved = false;
|
|
10744
|
+
const cleanup = () => {
|
|
10745
|
+
resolved = true;
|
|
10746
|
+
};
|
|
10747
|
+
const finish = (value) => {
|
|
10748
|
+
cleanup();
|
|
10749
|
+
focus.pop();
|
|
10750
|
+
resolve6(value);
|
|
10751
|
+
};
|
|
10752
|
+
const onKey = (name, _m, data) => {
|
|
10753
|
+
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
10754
|
+
finish("cancel");
|
|
10755
|
+
return;
|
|
10756
|
+
}
|
|
10757
|
+
if (name === "ESCAPE") {
|
|
10758
|
+
finish("back");
|
|
10759
|
+
return;
|
|
10760
|
+
}
|
|
10761
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
10762
|
+
finish(selected === 0 ? "launch" : "view");
|
|
10763
|
+
return;
|
|
10764
|
+
}
|
|
10765
|
+
if (name === "UP" || name === "SHIFT_TAB") {
|
|
10766
|
+
if (selected > 0) {
|
|
10767
|
+
selected--;
|
|
10768
|
+
render();
|
|
10769
|
+
}
|
|
10770
|
+
return;
|
|
10771
|
+
}
|
|
10772
|
+
if (name === "DOWN" || name === "TAB") {
|
|
10773
|
+
if (selected < CHOICES.length - 1) {
|
|
10774
|
+
selected++;
|
|
10775
|
+
render();
|
|
10776
|
+
}
|
|
10777
|
+
return;
|
|
10778
|
+
}
|
|
10779
|
+
if (data?.isCharacter) {
|
|
10780
|
+
const lower = name.toLowerCase();
|
|
10781
|
+
if (lower === "l") {
|
|
10782
|
+
finish("launch");
|
|
10783
|
+
return;
|
|
10784
|
+
}
|
|
10785
|
+
if (lower === "v") {
|
|
10786
|
+
finish("view");
|
|
10787
|
+
return;
|
|
10788
|
+
}
|
|
10789
|
+
if (lower === "n") {
|
|
10790
|
+
if (selected < CHOICES.length - 1) {
|
|
10791
|
+
selected++;
|
|
10792
|
+
render();
|
|
10793
|
+
}
|
|
10794
|
+
return;
|
|
10795
|
+
}
|
|
10796
|
+
if (lower === "p") {
|
|
10797
|
+
if (selected > 0) {
|
|
10798
|
+
selected--;
|
|
10799
|
+
render();
|
|
10800
|
+
}
|
|
10801
|
+
return;
|
|
10802
|
+
}
|
|
10803
|
+
}
|
|
10804
|
+
};
|
|
10805
|
+
focus.push({
|
|
10806
|
+
onKey: (name, _m, data) => {
|
|
10807
|
+
if (!resolved) onKey(name, _m, data);
|
|
10808
|
+
},
|
|
10809
|
+
onResize: () => {
|
|
10810
|
+
if (!resolved) render();
|
|
10811
|
+
}
|
|
10812
|
+
});
|
|
10813
|
+
});
|
|
10814
|
+
}
|
|
10815
|
+
function truncate2(s, max) {
|
|
10816
|
+
if (max <= 1) {
|
|
10817
|
+
return "";
|
|
10818
|
+
}
|
|
10819
|
+
if (s.length <= max) {
|
|
10820
|
+
return s;
|
|
10821
|
+
}
|
|
10822
|
+
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
10823
|
+
}
|
|
10824
|
+
function padRight(s, w) {
|
|
10825
|
+
if (s.length >= w) {
|
|
10826
|
+
return s.slice(0, w);
|
|
10827
|
+
}
|
|
10828
|
+
return s + " ".repeat(w - s.length);
|
|
10829
|
+
}
|
|
10830
|
+
var ACTION_CHOICES;
|
|
10831
|
+
var init_import_action_prompt = __esm({
|
|
10832
|
+
"src/tui/import-action-prompt.ts"() {
|
|
10833
|
+
"use strict";
|
|
10834
|
+
init_paths();
|
|
10835
|
+
init_session();
|
|
10836
|
+
init_prompt_utils();
|
|
10837
|
+
ACTION_CHOICES = [
|
|
10838
|
+
{
|
|
10839
|
+
key: "fork-local",
|
|
10840
|
+
label: "Fork locally",
|
|
10841
|
+
hotkey: "f",
|
|
10842
|
+
description: "spawn a local fork \u2014 original imported copy stays as-is"
|
|
10843
|
+
},
|
|
10844
|
+
{
|
|
10845
|
+
key: "view",
|
|
10846
|
+
label: "View transcript",
|
|
10847
|
+
hotkey: "v",
|
|
10848
|
+
description: "open read-only, no agent spawn"
|
|
10849
|
+
}
|
|
10850
|
+
];
|
|
10851
|
+
}
|
|
10852
|
+
});
|
|
10853
|
+
|
|
10854
|
+
// src/tui/picker.ts
|
|
10855
|
+
function createPickerPrefs() {
|
|
10856
|
+
return {
|
|
10857
|
+
filters: { cwdOnly: false, hostFilter: "__local", showCat: false }
|
|
10858
|
+
};
|
|
10859
|
+
}
|
|
10860
|
+
async function pickSession(term, opts) {
|
|
10861
|
+
process.stdout.write("\x1B[<u");
|
|
10862
|
+
process.stdout.write("\x1B[?2004l");
|
|
10863
|
+
process.stdout.write("\x1B[>4;0m");
|
|
10864
|
+
process.stdout.write("\x1B[>5;0m");
|
|
10865
|
+
process.stdout.write("\x1B[?1000l");
|
|
10866
|
+
process.stdout.write("\x1B[?1002l");
|
|
10867
|
+
process.stdout.write("\x1B[?1006l");
|
|
10868
|
+
process.stdout.write("\x1B[?1l");
|
|
10869
|
+
process.stdout.write("\x1B>");
|
|
10870
|
+
const sortSessions = (sessions) => {
|
|
10871
|
+
const score = (s) => {
|
|
10872
|
+
if (s.status !== "live") {
|
|
10873
|
+
return 0;
|
|
10874
|
+
}
|
|
10875
|
+
return s.cwd === opts.cwd ? 2 : 1;
|
|
10876
|
+
};
|
|
10877
|
+
return [...sessions].sort((a, b) => {
|
|
10878
|
+
const tier = score(b) - score(a);
|
|
10879
|
+
if (tier !== 0) {
|
|
10880
|
+
return tier;
|
|
10881
|
+
}
|
|
10882
|
+
return b.updatedAt.slice(0, 16).localeCompare(a.updatedAt.slice(0, 16));
|
|
10883
|
+
});
|
|
10884
|
+
};
|
|
10885
|
+
const prefs = opts.prefs ?? createPickerPrefs();
|
|
10886
|
+
if (opts.prefs === void 0 && opts.currentSessionId !== void 0) {
|
|
10887
|
+
const current = opts.sessions.find(
|
|
10888
|
+
(s) => s.sessionId === opts.currentSessionId
|
|
10889
|
+
);
|
|
10890
|
+
if (current?.importedFromMachine) {
|
|
10891
|
+
prefs.filters.hostFilter = "__all";
|
|
10892
|
+
}
|
|
10893
|
+
}
|
|
10894
|
+
let allSessions = sortSessions(opts.sessions);
|
|
10895
|
+
const applyPrefsFilters = (sessions) => {
|
|
10025
10896
|
let base = sessions;
|
|
10026
10897
|
if (prefs.filters.cwdOnly) {
|
|
10027
10898
|
base = base.filter((s) => s.cwd === opts.cwd);
|
|
10028
10899
|
}
|
|
10900
|
+
if (!prefs.filters.showCat) {
|
|
10901
|
+
base = base.filter(
|
|
10902
|
+
(s) => s.originatingClient?.name !== HYDRA_CAT_CLIENT_NAME
|
|
10903
|
+
);
|
|
10904
|
+
}
|
|
10029
10905
|
base = filterByHost(base, prefs.filters.hostFilter);
|
|
10030
10906
|
return base;
|
|
10031
10907
|
};
|
|
@@ -10045,11 +10921,19 @@ async function pickSession(term, opts) {
|
|
|
10045
10921
|
let searchTerm = "";
|
|
10046
10922
|
let mode = "normal";
|
|
10047
10923
|
let pendingAction = null;
|
|
10924
|
+
let findSubMode = "input";
|
|
10925
|
+
let findComposer = new InputDispatcher({ history: [] });
|
|
10926
|
+
let findResults = [];
|
|
10927
|
+
let findTruncated = false;
|
|
10928
|
+
let findSelectedIdx = 0;
|
|
10929
|
+
let findSnippetIdx = 0;
|
|
10930
|
+
let findError = null;
|
|
10931
|
+
let findInFlight = false;
|
|
10048
10932
|
let renameBuffer = "";
|
|
10049
10933
|
let transientStatus = null;
|
|
10050
10934
|
const composer = new InputDispatcher({ history: [] });
|
|
10051
|
-
let termHeight =
|
|
10052
|
-
let termWidth =
|
|
10935
|
+
let termHeight = readTermHeight2(term);
|
|
10936
|
+
let termWidth = readTermWidth2(term);
|
|
10053
10937
|
let viewportSize = 0;
|
|
10054
10938
|
let composerTitle = "";
|
|
10055
10939
|
let composerRoom = 0;
|
|
@@ -10061,10 +10945,16 @@ async function pickSession(term, opts) {
|
|
|
10061
10945
|
let headerLine = "";
|
|
10062
10946
|
let sessionLines = [];
|
|
10063
10947
|
let startRow = 1;
|
|
10948
|
+
let findRoom = 0;
|
|
10949
|
+
let findVisualRows = [];
|
|
10950
|
+
let findBoxRows = 1;
|
|
10951
|
+
let findBoxWindowStart = 0;
|
|
10952
|
+
let findBoxCursorVisualRow = 0;
|
|
10953
|
+
let findBoxCursorVisualCol = 0;
|
|
10064
10954
|
const cwdMaxWidth = opts.config.tui.cwdColumnMaxWidth;
|
|
10065
10955
|
const computeLayout = () => {
|
|
10066
|
-
termHeight =
|
|
10067
|
-
termWidth =
|
|
10956
|
+
termHeight = readTermHeight2(term);
|
|
10957
|
+
termWidth = readTermWidth2(term);
|
|
10068
10958
|
const rowMaxWidth = Math.max(10, termWidth - ROW_PREFIX_WIDTH);
|
|
10069
10959
|
composerRoom = Math.max(10, termWidth - BOX_HORIZONTAL_PAD);
|
|
10070
10960
|
const titleBudget = Math.max(10, termWidth - 8);
|
|
@@ -10115,6 +11005,19 @@ async function pickSession(term, opts) {
|
|
|
10115
11005
|
}
|
|
10116
11006
|
adjustScroll();
|
|
10117
11007
|
};
|
|
11008
|
+
const restoreCursorAfterFilter = (keepId) => {
|
|
11009
|
+
if (keepId !== void 0) {
|
|
11010
|
+
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
11011
|
+
if (idx >= 0) {
|
|
11012
|
+
selectedIdx = idx + 1;
|
|
11013
|
+
adjustScroll();
|
|
11014
|
+
return;
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
selectedIdx = visible.length > 0 ? 1 : 0;
|
|
11018
|
+
scrollOffset = 0;
|
|
11019
|
+
adjustScroll();
|
|
11020
|
+
};
|
|
10118
11021
|
const adjustScroll = () => {
|
|
10119
11022
|
if (selectedIdx === 0) {
|
|
10120
11023
|
return;
|
|
@@ -10191,6 +11094,9 @@ async function pickSession(term, opts) {
|
|
|
10191
11094
|
prefs.filters.hostFilter === "__local" ? "host: local" : `host: ${prefs.filters.hostFilter}`
|
|
10192
11095
|
);
|
|
10193
11096
|
}
|
|
11097
|
+
if (prefs.filters.showCat) {
|
|
11098
|
+
parts.push("+cat");
|
|
11099
|
+
}
|
|
10194
11100
|
if (above > 0) {
|
|
10195
11101
|
parts.push(`\u2191 ${above} above`);
|
|
10196
11102
|
}
|
|
@@ -10240,59 +11146,370 @@ async function pickSession(term, opts) {
|
|
|
10240
11146
|
if (visualOffset < 0 || visualOffset >= composerRows) {
|
|
10241
11147
|
return;
|
|
10242
11148
|
}
|
|
10243
|
-
const col = 3 + composerCursorCol;
|
|
10244
|
-
term.moveTo(col, composerBodyRow(visualOffset));
|
|
11149
|
+
const col = 3 + composerCursorCol;
|
|
11150
|
+
term.moveTo(col, composerBodyRow(visualOffset));
|
|
11151
|
+
};
|
|
11152
|
+
const renderFromScratch = () => {
|
|
11153
|
+
withSync(() => {
|
|
11154
|
+
term.hideCursor();
|
|
11155
|
+
computeLayout();
|
|
11156
|
+
adjustScroll();
|
|
11157
|
+
startRow = 1;
|
|
11158
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
11159
|
+
paintComposerTopBorder();
|
|
11160
|
+
term("\n");
|
|
11161
|
+
for (let v = 0; v < composerRows; v++) {
|
|
11162
|
+
paintComposerBodyRow(composerWindowStart + v);
|
|
11163
|
+
term("\n");
|
|
11164
|
+
}
|
|
11165
|
+
paintComposerBottomBorder();
|
|
11166
|
+
term("\n\n");
|
|
11167
|
+
term.dim.noFormat(` ${headerLine}`)("\n");
|
|
11168
|
+
for (let v = 0; v < viewportSize; v++) {
|
|
11169
|
+
paintSessionRow(scrollOffset + v);
|
|
11170
|
+
term("\n");
|
|
11171
|
+
}
|
|
11172
|
+
paintIndicator();
|
|
11173
|
+
term("\n");
|
|
11174
|
+
if (selectedIdx === 0) {
|
|
11175
|
+
placeComposerCursor();
|
|
11176
|
+
term.hideCursor(false);
|
|
11177
|
+
}
|
|
11178
|
+
});
|
|
11179
|
+
};
|
|
11180
|
+
const renderHelp = () => {
|
|
11181
|
+
withSync(() => {
|
|
11182
|
+
term.hideCursor();
|
|
11183
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
11184
|
+
term.brightWhite.bold.noFormat(" Picker hotkeys")("\n\n");
|
|
11185
|
+
for (const entry of HELP_ENTRIES) {
|
|
11186
|
+
if (entry === null) {
|
|
11187
|
+
term("\n");
|
|
11188
|
+
continue;
|
|
11189
|
+
}
|
|
11190
|
+
const [keys, desc] = entry;
|
|
11191
|
+
term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
|
|
11192
|
+
term.noFormat(desc)("\n");
|
|
11193
|
+
}
|
|
11194
|
+
term("\n");
|
|
11195
|
+
term.dim.noFormat(" press any key to dismiss")("\n");
|
|
11196
|
+
});
|
|
11197
|
+
};
|
|
11198
|
+
const findResultsStartRow = () => findBoxRows + 4;
|
|
11199
|
+
const FIND_FOOTER_ROWS = 2;
|
|
11200
|
+
let findScrollOffset = 0;
|
|
11201
|
+
const findViewportSize = () => {
|
|
11202
|
+
termHeight = readTermHeight2(term);
|
|
11203
|
+
const avail = Math.max(2, termHeight - (findBoxRows + 3) - FIND_FOOTER_ROWS);
|
|
11204
|
+
return Math.max(1, Math.floor(avail / 2));
|
|
11205
|
+
};
|
|
11206
|
+
const adjustFindScroll = () => {
|
|
11207
|
+
const v = findViewportSize();
|
|
11208
|
+
if (findSelectedIdx < findScrollOffset) {
|
|
11209
|
+
findScrollOffset = findSelectedIdx;
|
|
11210
|
+
} else if (findSelectedIdx >= findScrollOffset + v) {
|
|
11211
|
+
findScrollOffset = findSelectedIdx - v + 1;
|
|
11212
|
+
}
|
|
11213
|
+
if (findScrollOffset + v > findResults.length) {
|
|
11214
|
+
findScrollOffset = Math.max(0, findResults.length - v);
|
|
11215
|
+
}
|
|
11216
|
+
if (findScrollOffset < 0) {
|
|
11217
|
+
findScrollOffset = 0;
|
|
11218
|
+
}
|
|
11219
|
+
};
|
|
11220
|
+
const paintFindBoxTopBorder = (focused) => {
|
|
11221
|
+
termWidth = readTermWidth2(term);
|
|
11222
|
+
const inner = Math.max(2, termWidth - 2);
|
|
11223
|
+
const title = "\u2500 Find sessions ";
|
|
11224
|
+
const dashes = "\u2500".repeat(Math.max(1, inner - title.length));
|
|
11225
|
+
if (focused) {
|
|
11226
|
+
term.brightBlue.noFormat(`\u256D${title}${dashes}\u256E`);
|
|
11227
|
+
} else {
|
|
11228
|
+
term.dim.noFormat(`\u256D${title}${dashes}\u256E`);
|
|
11229
|
+
}
|
|
11230
|
+
term.styleReset();
|
|
11231
|
+
};
|
|
11232
|
+
const computeFindBoxLayout = () => {
|
|
11233
|
+
termWidth = readTermWidth2(term);
|
|
11234
|
+
findRoom = Math.max(10, termWidth - BOX_HORIZONTAL_PAD);
|
|
11235
|
+
const state = findComposer.state();
|
|
11236
|
+
findVisualRows = computePromptVisualRows(state.buffer, findRoom);
|
|
11237
|
+
const layout = computePromptLayout(findVisualRows, state, FIND_BOX_MAX_ROWS);
|
|
11238
|
+
findBoxRows = layout.rendered;
|
|
11239
|
+
findBoxWindowStart = layout.windowStart;
|
|
11240
|
+
findBoxCursorVisualRow = layout.cursorVisualRow;
|
|
11241
|
+
findBoxCursorVisualCol = layout.cursorVisualCol;
|
|
11242
|
+
};
|
|
11243
|
+
const paintFindBoxBodyRow = (visualIdx, focused) => {
|
|
11244
|
+
termWidth = readTermWidth2(term);
|
|
11245
|
+
const inner = Math.max(2, termWidth - 2);
|
|
11246
|
+
const vr = findVisualRows[visualIdx];
|
|
11247
|
+
let slice = "";
|
|
11248
|
+
if (vr) {
|
|
11249
|
+
slice = (findComposer.state().buffer[vr.bufferIdx] ?? "").slice(
|
|
11250
|
+
vr.startCol,
|
|
11251
|
+
vr.endCol
|
|
11252
|
+
);
|
|
11253
|
+
}
|
|
11254
|
+
const padWidth = Math.max(0, inner - 1 - slice.length);
|
|
11255
|
+
const pad = " ".repeat(padWidth);
|
|
11256
|
+
if (focused) {
|
|
11257
|
+
term.brightBlue.noFormat("\u2502");
|
|
11258
|
+
term.noFormat(` ${slice}${pad}`);
|
|
11259
|
+
term.brightBlue.noFormat("\u2502");
|
|
11260
|
+
} else {
|
|
11261
|
+
term.dim.noFormat("\u2502");
|
|
11262
|
+
term.noFormat(` ${slice}${pad}`);
|
|
11263
|
+
term.dim.noFormat("\u2502");
|
|
11264
|
+
}
|
|
11265
|
+
term.styleReset();
|
|
11266
|
+
};
|
|
11267
|
+
const paintFindBoxBottomBorder = (focused) => {
|
|
11268
|
+
termWidth = readTermWidth2(term);
|
|
11269
|
+
const inner = Math.max(2, termWidth - 2);
|
|
11270
|
+
const dashes = "\u2500".repeat(inner);
|
|
11271
|
+
if (focused) {
|
|
11272
|
+
term.brightBlue.noFormat(`\u2570${dashes}\u256F`);
|
|
11273
|
+
} else {
|
|
11274
|
+
term.dim.noFormat(`\u2570${dashes}\u256F`);
|
|
11275
|
+
}
|
|
11276
|
+
term.styleReset();
|
|
11277
|
+
};
|
|
11278
|
+
const findBoxCursorCol = () => 3 + findBoxCursorVisualCol;
|
|
11279
|
+
const findBoxCursorScreenRow = () => 2 + (findBoxCursorVisualRow - findBoxWindowStart);
|
|
11280
|
+
const repaintFindBoxChrome = () => {
|
|
11281
|
+
const focused = findSubMode === "input";
|
|
11282
|
+
withSync(() => {
|
|
11283
|
+
if (focused) {
|
|
11284
|
+
term.hideCursor();
|
|
11285
|
+
}
|
|
11286
|
+
term.moveTo(1, 1);
|
|
11287
|
+
paintFindBoxTopBorder(focused);
|
|
11288
|
+
for (let v = 0; v < findBoxRows; v++) {
|
|
11289
|
+
term.moveTo(1, 2 + v);
|
|
11290
|
+
paintFindBoxBodyRow(findBoxWindowStart + v, focused);
|
|
11291
|
+
}
|
|
11292
|
+
term.moveTo(1, 2 + findBoxRows);
|
|
11293
|
+
paintFindBoxBottomBorder(focused);
|
|
11294
|
+
if (focused) {
|
|
11295
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
11296
|
+
term.hideCursor(false);
|
|
11297
|
+
}
|
|
11298
|
+
});
|
|
11299
|
+
};
|
|
11300
|
+
const repaintFindBoxBodyRows = () => {
|
|
11301
|
+
withSync(() => {
|
|
11302
|
+
term.hideCursor();
|
|
11303
|
+
for (let v = 0; v < findBoxRows; v++) {
|
|
11304
|
+
term.moveTo(1, 2 + v);
|
|
11305
|
+
paintFindBoxBodyRow(findBoxWindowStart + v, true);
|
|
11306
|
+
}
|
|
11307
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
11308
|
+
term.hideCursor(false);
|
|
11309
|
+
});
|
|
11310
|
+
};
|
|
11311
|
+
const SNIPPET_KIND_GLYPH = {
|
|
11312
|
+
user: "user",
|
|
11313
|
+
agent: "agent",
|
|
11314
|
+
thought: "thought",
|
|
11315
|
+
tool: "tool",
|
|
11316
|
+
"tool-input": "tool-input"
|
|
11317
|
+
};
|
|
11318
|
+
const findResultData = (idx, focused) => {
|
|
11319
|
+
const hit = findResults[idx];
|
|
11320
|
+
if (!hit) {
|
|
11321
|
+
return { rowBudget: 20, line1: "", line2: "", focusedRow: false };
|
|
11322
|
+
}
|
|
11323
|
+
const w = readTermWidth2(term);
|
|
11324
|
+
const rowBudget = Math.max(20, w - ROW_PREFIX_WIDTH);
|
|
11325
|
+
const shortId3 = stripHydraSessionPrefix(hit.sessionId);
|
|
11326
|
+
const title = hit.title ?? shortenHomePath(hit.cwd);
|
|
11327
|
+
const counterText = focused && hit.snippets.length > 1 ? ` [${findSnippetIdx + 1}/${hit.snippets.length}]` : focused && hit.totalMatches > hit.snippets.length ? ` [${hit.snippets.length} of ${hit.totalMatches}]` : "";
|
|
11328
|
+
const head = `${shortId3} ${hit.status === "live" ? "live" : "cold"}`;
|
|
11329
|
+
const titleBudget = Math.max(5, rowBudget - head.length - counterText.length - 2);
|
|
11330
|
+
const titleSlice = truncateMiddle(title, titleBudget);
|
|
11331
|
+
const line1 = `${head} ${titleSlice}${counterText}`.padEnd(rowBudget);
|
|
11332
|
+
const snippet = hit.snippets[focused ? findSnippetIdx : 0];
|
|
11333
|
+
const kind = snippet ? SNIPPET_KIND_GLYPH[snippet.kind] ?? snippet.kind : "";
|
|
11334
|
+
const prefix = snippet?.toolName ? `${kind} \xB7 ${snippet.toolName}` : kind;
|
|
11335
|
+
const snippetBudget = Math.max(10, rowBudget - prefix.length - 6);
|
|
11336
|
+
const text = snippet ? truncateMiddle(snippet.text, snippetBudget) : "";
|
|
11337
|
+
const line2 = snippet ? ` ${prefix} ${text}` : " (no snippet)";
|
|
11338
|
+
return { rowBudget, line1, line2: line2.padEnd(rowBudget + ROW_PREFIX_WIDTH), focusedRow: focused };
|
|
11339
|
+
};
|
|
11340
|
+
const paintFindResultA = (idx, focused) => {
|
|
11341
|
+
const { line1, focusedRow } = findResultData(idx, focused);
|
|
11342
|
+
if (focusedRow) {
|
|
11343
|
+
term.brightWhite.bgBlue.noFormat(`\u276F ${line1}`);
|
|
11344
|
+
} else {
|
|
11345
|
+
term.noFormat(` ${line1}`);
|
|
11346
|
+
}
|
|
11347
|
+
term.styleReset();
|
|
11348
|
+
};
|
|
11349
|
+
const paintFindResultB = (idx, focused) => {
|
|
11350
|
+
const { line2 } = findResultData(idx, focused);
|
|
11351
|
+
term.dim.noFormat(line2);
|
|
11352
|
+
term.styleReset();
|
|
11353
|
+
};
|
|
11354
|
+
const paintFindIndicator = () => {
|
|
11355
|
+
if (findInFlight) {
|
|
11356
|
+
term.dim.noFormat(" searching\u2026");
|
|
11357
|
+
term.styleReset();
|
|
11358
|
+
term.eraseLineAfter();
|
|
11359
|
+
} else if (findError !== null) {
|
|
11360
|
+
term.brightRed.noFormat(` ${findError}`);
|
|
11361
|
+
term.styleReset();
|
|
11362
|
+
term.eraseLineAfter();
|
|
11363
|
+
} else if (findSubMode === "input") {
|
|
11364
|
+
if (findResults.length > 0) {
|
|
11365
|
+
term.dim.noFormat(" Enter to search \xB7 \u2193 browse results \xB7 Esc cancel");
|
|
11366
|
+
} else {
|
|
11367
|
+
term.dim.noFormat(" Enter to search \xB7 Esc cancel");
|
|
11368
|
+
}
|
|
11369
|
+
term.styleReset();
|
|
11370
|
+
term.eraseLineAfter();
|
|
11371
|
+
} else {
|
|
11372
|
+
const sCount = findResults.length;
|
|
11373
|
+
const truncSuffix = findTruncated ? " \xB7 truncated" : "";
|
|
11374
|
+
const countPart = sCount > 0 ? ` ${sCount} ${sCount === 1 ? "session" : "sessions"} match${truncSuffix} \xB7 ` : " ";
|
|
11375
|
+
term.dim.noFormat(
|
|
11376
|
+
`${countPart}\u2191 edit query \xB7 Up/Down sessions \xB7 n/p snippets \xB7 Enter open \xB7 Esc back`
|
|
11377
|
+
);
|
|
11378
|
+
term.styleReset();
|
|
11379
|
+
term.eraseLineAfter();
|
|
11380
|
+
}
|
|
10245
11381
|
};
|
|
10246
|
-
const
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
}
|
|
11382
|
+
const renderFind = () => {
|
|
11383
|
+
computeFindBoxLayout();
|
|
11384
|
+
const focused = findSubMode === "input";
|
|
11385
|
+
const queryText = findComposer.state().buffer.join("\n");
|
|
10251
11386
|
withSync(() => {
|
|
10252
11387
|
term.hideCursor();
|
|
10253
|
-
computeLayout();
|
|
10254
|
-
adjustScroll();
|
|
10255
|
-
startRow = 1;
|
|
10256
11388
|
term.moveTo(1, 1).eraseDisplayBelow();
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
11389
|
+
paintFindBoxTopBorder(focused);
|
|
11390
|
+
for (let v = 0; v < findBoxRows; v++) {
|
|
11391
|
+
term.moveTo(1, 2 + v);
|
|
11392
|
+
paintFindBoxBodyRow(findBoxWindowStart + v, focused);
|
|
11393
|
+
}
|
|
11394
|
+
term.moveTo(1, 2 + findBoxRows);
|
|
11395
|
+
paintFindBoxBottomBorder(focused);
|
|
11396
|
+
const sCount = findResults.length;
|
|
11397
|
+
if (sCount === 0) {
|
|
11398
|
+
term.moveTo(1, findResultsStartRow());
|
|
11399
|
+
if (findInFlight) {
|
|
11400
|
+
} else if (findError === null && queryText.trim().length === 0) {
|
|
11401
|
+
term.dim.noFormat(" type a query in the box above, then press Enter");
|
|
11402
|
+
term.eraseLineAfter();
|
|
11403
|
+
} else if (findError === null) {
|
|
11404
|
+
term.dim.noFormat(" no matches");
|
|
11405
|
+
term.eraseLineAfter();
|
|
11406
|
+
}
|
|
11407
|
+
term.moveTo(1, findResultsStartRow() + 1);
|
|
11408
|
+
paintFindIndicator();
|
|
11409
|
+
} else {
|
|
11410
|
+
adjustFindScroll();
|
|
11411
|
+
const v = findViewportSize();
|
|
11412
|
+
const listFocused = findSubMode !== "input";
|
|
11413
|
+
for (let i = 0; i < v; i++) {
|
|
11414
|
+
const idx = findScrollOffset + i;
|
|
11415
|
+
term.moveTo(1, findResultsStartRow() + i * 2);
|
|
11416
|
+
if (idx < sCount) {
|
|
11417
|
+
paintFindResultA(idx, listFocused && idx === findSelectedIdx);
|
|
11418
|
+
} else {
|
|
11419
|
+
term.eraseLineAfter();
|
|
11420
|
+
}
|
|
11421
|
+
term.moveTo(1, findResultsStartRow() + i * 2 + 1);
|
|
11422
|
+
if (idx < sCount) {
|
|
11423
|
+
paintFindResultB(idx, listFocused && idx === findSelectedIdx);
|
|
11424
|
+
} else {
|
|
11425
|
+
term.eraseLineAfter();
|
|
11426
|
+
}
|
|
11427
|
+
}
|
|
11428
|
+
term.moveTo(1, findResultsStartRow() + v * 2);
|
|
11429
|
+
paintFindIndicator();
|
|
10269
11430
|
}
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
if (selectedIdx === 0) {
|
|
10273
|
-
placeComposerCursor();
|
|
11431
|
+
if (focused) {
|
|
11432
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
10274
11433
|
term.hideCursor(false);
|
|
10275
11434
|
}
|
|
10276
11435
|
});
|
|
10277
11436
|
};
|
|
10278
|
-
const
|
|
11437
|
+
const repaintFindResult = (idx, focused) => {
|
|
11438
|
+
const viewportIdx = idx - findScrollOffset;
|
|
11439
|
+
if (viewportIdx < 0 || viewportIdx >= findViewportSize()) {
|
|
11440
|
+
return;
|
|
11441
|
+
}
|
|
10279
11442
|
withSync(() => {
|
|
10280
|
-
term.
|
|
10281
|
-
|
|
10282
|
-
term.
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
11443
|
+
term.moveTo(1, findResultsStartRow() + viewportIdx * 2);
|
|
11444
|
+
paintFindResultA(idx, focused);
|
|
11445
|
+
term.moveTo(1, findResultsStartRow() + viewportIdx * 2 + 1);
|
|
11446
|
+
paintFindResultB(idx, focused);
|
|
11447
|
+
});
|
|
11448
|
+
};
|
|
11449
|
+
const repaintFindIndicatorRow = () => {
|
|
11450
|
+
withSync(() => {
|
|
11451
|
+
term.moveTo(1, findResultsStartRow() + findViewportSize() * 2);
|
|
11452
|
+
paintFindIndicator();
|
|
11453
|
+
});
|
|
11454
|
+
};
|
|
11455
|
+
const repaintFindViewport = () => {
|
|
11456
|
+
withSync(() => {
|
|
11457
|
+
const v = findViewportSize();
|
|
11458
|
+
const sCount = findResults.length;
|
|
11459
|
+
const listFocused = findSubMode !== "input";
|
|
11460
|
+
for (let i = 0; i < v; i++) {
|
|
11461
|
+
const idx = findScrollOffset + i;
|
|
11462
|
+
term.moveTo(1, findResultsStartRow() + i * 2);
|
|
11463
|
+
if (idx < sCount) {
|
|
11464
|
+
paintFindResultA(idx, listFocused && idx === findSelectedIdx);
|
|
11465
|
+
} else {
|
|
11466
|
+
term.eraseLineAfter();
|
|
11467
|
+
}
|
|
11468
|
+
term.moveTo(1, findResultsStartRow() + i * 2 + 1);
|
|
11469
|
+
if (idx < sCount) {
|
|
11470
|
+
paintFindResultB(idx, listFocused && idx === findSelectedIdx);
|
|
11471
|
+
} else {
|
|
11472
|
+
term.eraseLineAfter();
|
|
10287
11473
|
}
|
|
10288
|
-
const [keys, desc] = entry;
|
|
10289
|
-
term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
|
|
10290
|
-
term.noFormat(desc)("\n");
|
|
10291
11474
|
}
|
|
10292
|
-
term(
|
|
10293
|
-
|
|
11475
|
+
term.moveTo(1, findResultsStartRow() + v * 2);
|
|
11476
|
+
paintFindIndicator();
|
|
10294
11477
|
});
|
|
10295
11478
|
};
|
|
11479
|
+
const findQueryText = () => findComposer.state().buffer.join("\n");
|
|
11480
|
+
const runFind = async () => {
|
|
11481
|
+
const query = findQueryText().trim();
|
|
11482
|
+
if (query.length === 0) {
|
|
11483
|
+
return;
|
|
11484
|
+
}
|
|
11485
|
+
if (visible.length === 0) {
|
|
11486
|
+
findError = "no sessions in view to search";
|
|
11487
|
+
renderFind();
|
|
11488
|
+
return;
|
|
11489
|
+
}
|
|
11490
|
+
findInFlight = true;
|
|
11491
|
+
findError = null;
|
|
11492
|
+
renderFind();
|
|
11493
|
+
try {
|
|
11494
|
+
const out = await searchSessions(opts.target, query, {
|
|
11495
|
+
sessionIds: visible.map((s) => s.sessionId)
|
|
11496
|
+
});
|
|
11497
|
+
findResults = out.results;
|
|
11498
|
+
findTruncated = out.truncated;
|
|
11499
|
+
findSelectedIdx = 0;
|
|
11500
|
+
findSnippetIdx = 0;
|
|
11501
|
+
findScrollOffset = 0;
|
|
11502
|
+
findSubMode = out.results.length > 0 ? "results" : "input";
|
|
11503
|
+
computeFindBoxLayout();
|
|
11504
|
+
} catch (err) {
|
|
11505
|
+
findError = `search failed: ${err.message}`;
|
|
11506
|
+
} finally {
|
|
11507
|
+
findInFlight = false;
|
|
11508
|
+
renderFind();
|
|
11509
|
+
}
|
|
11510
|
+
};
|
|
11511
|
+
let exitFind = () => {
|
|
11512
|
+
};
|
|
10296
11513
|
const repaintComposerChrome = () => {
|
|
10297
11514
|
withSync(() => {
|
|
10298
11515
|
const showCursor = selectedIdx === 0;
|
|
@@ -10441,23 +11658,48 @@ async function pickSession(term, opts) {
|
|
|
10441
11658
|
let resolved = false;
|
|
10442
11659
|
let autoRefreshTimer = null;
|
|
10443
11660
|
let autoRefreshInFlight = false;
|
|
10444
|
-
const
|
|
10445
|
-
|
|
10446
|
-
|
|
11661
|
+
const focusStack = [];
|
|
11662
|
+
const pushLayer = (layer) => {
|
|
11663
|
+
focusStack.push(layer);
|
|
11664
|
+
};
|
|
11665
|
+
const popLayer = () => {
|
|
11666
|
+
focusStack.pop();
|
|
11667
|
+
if (!resolved) {
|
|
11668
|
+
focusStack[focusStack.length - 1]?.onResize();
|
|
10447
11669
|
}
|
|
10448
|
-
|
|
11670
|
+
};
|
|
11671
|
+
const focus = { push: pushLayer, pop: popLayer };
|
|
11672
|
+
exitFind = () => {
|
|
11673
|
+
findComposer = new InputDispatcher({ history: [] });
|
|
11674
|
+
findResults = [];
|
|
11675
|
+
findTruncated = false;
|
|
11676
|
+
findSelectedIdx = 0;
|
|
11677
|
+
findSnippetIdx = 0;
|
|
11678
|
+
findScrollOffset = 0;
|
|
11679
|
+
findError = null;
|
|
11680
|
+
findInFlight = false;
|
|
11681
|
+
findSubMode = "input";
|
|
11682
|
+
popLayer();
|
|
11683
|
+
};
|
|
11684
|
+
const dispatch = (name, _matches, data) => {
|
|
11685
|
+
focusStack[focusStack.length - 1]?.onKey(name, _matches, data);
|
|
11686
|
+
};
|
|
11687
|
+
const dispatchResize = () => {
|
|
11688
|
+
if (resolved) return;
|
|
11689
|
+
focusStack[focusStack.length - 1]?.onResize();
|
|
10449
11690
|
};
|
|
10450
11691
|
const cleanup = () => {
|
|
10451
11692
|
if (resolved) {
|
|
10452
11693
|
return;
|
|
10453
11694
|
}
|
|
10454
11695
|
resolved = true;
|
|
11696
|
+
focusStack.length = 0;
|
|
10455
11697
|
if (autoRefreshTimer) {
|
|
10456
11698
|
clearInterval(autoRefreshTimer);
|
|
10457
11699
|
autoRefreshTimer = null;
|
|
10458
11700
|
}
|
|
10459
|
-
term.off("key",
|
|
10460
|
-
term.off("resize",
|
|
11701
|
+
term.off("key", dispatch);
|
|
11702
|
+
term.off("resize", dispatchResize);
|
|
10461
11703
|
process.stdout.write("\x1B[?2004l");
|
|
10462
11704
|
const tClean = term;
|
|
10463
11705
|
if (tClean.stdin && tkStdinHandler) {
|
|
@@ -10613,18 +11855,231 @@ ${cells}`;
|
|
|
10613
11855
|
paintIndicator();
|
|
10614
11856
|
return true;
|
|
10615
11857
|
};
|
|
10616
|
-
const
|
|
10617
|
-
|
|
11858
|
+
const openHelpLayer = () => {
|
|
11859
|
+
renderHelp();
|
|
11860
|
+
pushLayer({
|
|
11861
|
+
onKey: (name) => {
|
|
11862
|
+
if (name === "CTRL_C") {
|
|
11863
|
+
cleanup();
|
|
11864
|
+
resolve6({ kind: "abort" });
|
|
11865
|
+
return;
|
|
11866
|
+
}
|
|
11867
|
+
popLayer();
|
|
11868
|
+
},
|
|
11869
|
+
onResize: () => renderHelp()
|
|
11870
|
+
});
|
|
11871
|
+
};
|
|
11872
|
+
const openFindLayer = () => {
|
|
11873
|
+
if (visible.length === 0) {
|
|
11874
|
+
transientStatus = "no sessions to search";
|
|
11875
|
+
paintIndicator();
|
|
10618
11876
|
return;
|
|
10619
11877
|
}
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
11878
|
+
findComposer = new InputDispatcher({ history: [] });
|
|
11879
|
+
findResults = [];
|
|
11880
|
+
findTruncated = false;
|
|
11881
|
+
findSelectedIdx = 0;
|
|
11882
|
+
findSnippetIdx = 0;
|
|
11883
|
+
findScrollOffset = 0;
|
|
11884
|
+
findError = null;
|
|
11885
|
+
findInFlight = false;
|
|
11886
|
+
findSubMode = "input";
|
|
11887
|
+
computeFindBoxLayout();
|
|
11888
|
+
renderFind();
|
|
11889
|
+
const findOnKey = (name, _matches, data) => {
|
|
11890
|
+
if (findSubMode === "input") {
|
|
11891
|
+
if (findInFlight) {
|
|
11892
|
+
return;
|
|
11893
|
+
}
|
|
11894
|
+
if (name === "ESCAPE" || name === "CTRL_C") {
|
|
11895
|
+
exitFind();
|
|
11896
|
+
return;
|
|
11897
|
+
}
|
|
11898
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
11899
|
+
if (findQueryText().trim().length === 0) {
|
|
11900
|
+
return;
|
|
11901
|
+
}
|
|
11902
|
+
void runFind();
|
|
11903
|
+
return;
|
|
11904
|
+
}
|
|
11905
|
+
if ((name === "DOWN" || name === "TAB" || name === "CTRL_N") && findResults.length > 0) {
|
|
11906
|
+
findSubMode = "results";
|
|
11907
|
+
findSelectedIdx = 0;
|
|
11908
|
+
findSnippetIdx = 0;
|
|
11909
|
+
withSync(() => {
|
|
11910
|
+
repaintFindBoxChrome();
|
|
11911
|
+
repaintFindResult(0, true);
|
|
11912
|
+
repaintFindIndicatorRow();
|
|
11913
|
+
term.hideCursor();
|
|
11914
|
+
});
|
|
11915
|
+
return;
|
|
11916
|
+
}
|
|
11917
|
+
const before = findComposer.state();
|
|
11918
|
+
let event = null;
|
|
11919
|
+
if (data?.isCharacter) {
|
|
11920
|
+
event = { type: "char", ch: name };
|
|
11921
|
+
} else {
|
|
11922
|
+
const mapped = mapKeyName(name);
|
|
11923
|
+
if (mapped !== null)
|
|
11924
|
+
event = { type: "key", name: mapped };
|
|
11925
|
+
}
|
|
11926
|
+
if (event === null) {
|
|
11927
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
11928
|
+
return;
|
|
11929
|
+
}
|
|
11930
|
+
findComposer.feed(event);
|
|
11931
|
+
const after = findComposer.state();
|
|
11932
|
+
const unchanged = before.buffer.length === after.buffer.length && before.buffer.every((l, i) => l === after.buffer[i]) && before.row === after.row && before.col === after.col;
|
|
11933
|
+
if (unchanged) {
|
|
11934
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
11935
|
+
return;
|
|
11936
|
+
}
|
|
11937
|
+
const prevRows = findBoxRows;
|
|
11938
|
+
computeFindBoxLayout();
|
|
11939
|
+
if (findBoxRows !== prevRows) {
|
|
11940
|
+
renderFind();
|
|
11941
|
+
} else {
|
|
11942
|
+
repaintFindBoxBodyRows();
|
|
11943
|
+
}
|
|
10624
11944
|
return;
|
|
10625
11945
|
}
|
|
10626
|
-
|
|
10627
|
-
|
|
11946
|
+
if (findSubMode === "results") {
|
|
11947
|
+
if (name === "ESCAPE" || name === "CTRL_C") {
|
|
11948
|
+
exitFind();
|
|
11949
|
+
return;
|
|
11950
|
+
}
|
|
11951
|
+
if (name === "CTRL_F") {
|
|
11952
|
+
findSubMode = "input";
|
|
11953
|
+
repaintFindViewport();
|
|
11954
|
+
repaintFindIndicatorRow();
|
|
11955
|
+
repaintFindBoxChrome();
|
|
11956
|
+
return;
|
|
11957
|
+
}
|
|
11958
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
11959
|
+
const hit = findResults[findSelectedIdx];
|
|
11960
|
+
if (!hit) {
|
|
11961
|
+
return;
|
|
11962
|
+
}
|
|
11963
|
+
const session = visible.find((s) => s.sessionId === hit.sessionId);
|
|
11964
|
+
const isImportedPassive = !!session?.importedFromMachine && !session.upstreamSessionId;
|
|
11965
|
+
if (isImportedPassive) {
|
|
11966
|
+
cleanup();
|
|
11967
|
+
const result = {
|
|
11968
|
+
kind: "attach",
|
|
11969
|
+
sessionId: hit.sessionId
|
|
11970
|
+
};
|
|
11971
|
+
if (session.agentId !== void 0) {
|
|
11972
|
+
result.agentId = session.agentId;
|
|
11973
|
+
}
|
|
11974
|
+
resolve6(result);
|
|
11975
|
+
return;
|
|
11976
|
+
}
|
|
11977
|
+
void (async () => {
|
|
11978
|
+
const action = await promptForLaunchOrView(term, {
|
|
11979
|
+
sessionId: hit.sessionId,
|
|
11980
|
+
title: hit.title,
|
|
11981
|
+
cwd: hit.cwd
|
|
11982
|
+
}, focus);
|
|
11983
|
+
if (action === "cancel") {
|
|
11984
|
+
cleanup();
|
|
11985
|
+
resolve6({ kind: "abort" });
|
|
11986
|
+
return;
|
|
11987
|
+
}
|
|
11988
|
+
if (action === "back") return;
|
|
11989
|
+
cleanup();
|
|
11990
|
+
const result = {
|
|
11991
|
+
kind: "attach",
|
|
11992
|
+
sessionId: hit.sessionId,
|
|
11993
|
+
readonly: action === "view"
|
|
11994
|
+
};
|
|
11995
|
+
if (session?.agentId !== void 0) {
|
|
11996
|
+
result.agentId = session.agentId;
|
|
11997
|
+
}
|
|
11998
|
+
resolve6(result);
|
|
11999
|
+
})();
|
|
12000
|
+
return;
|
|
12001
|
+
}
|
|
12002
|
+
if (data?.isCharacter && (name === "n" || name === "N")) {
|
|
12003
|
+
const hit = findResults[findSelectedIdx];
|
|
12004
|
+
if (!hit || hit.snippets.length <= 1) {
|
|
12005
|
+
return;
|
|
12006
|
+
}
|
|
12007
|
+
findSnippetIdx = (findSnippetIdx + 1) % hit.snippets.length;
|
|
12008
|
+
repaintFindResult(findSelectedIdx, true);
|
|
12009
|
+
return;
|
|
12010
|
+
}
|
|
12011
|
+
if (data?.isCharacter && (name === "p" || name === "P")) {
|
|
12012
|
+
const hit = findResults[findSelectedIdx];
|
|
12013
|
+
if (!hit || hit.snippets.length <= 1) {
|
|
12014
|
+
return;
|
|
12015
|
+
}
|
|
12016
|
+
findSnippetIdx = (findSnippetIdx - 1 + hit.snippets.length) % hit.snippets.length;
|
|
12017
|
+
repaintFindResult(findSelectedIdx, true);
|
|
12018
|
+
return;
|
|
12019
|
+
}
|
|
12020
|
+
const moveDeep = (delta) => {
|
|
12021
|
+
if (delta < 0 && findSelectedIdx === 0) {
|
|
12022
|
+
findSubMode = "input";
|
|
12023
|
+
withSync(() => {
|
|
12024
|
+
repaintFindResult(0, false);
|
|
12025
|
+
repaintFindIndicatorRow();
|
|
12026
|
+
repaintFindBoxChrome();
|
|
12027
|
+
});
|
|
12028
|
+
return;
|
|
12029
|
+
}
|
|
12030
|
+
const next = Math.min(
|
|
12031
|
+
findResults.length - 1,
|
|
12032
|
+
Math.max(0, findSelectedIdx + delta)
|
|
12033
|
+
);
|
|
12034
|
+
if (next === findSelectedIdx) {
|
|
12035
|
+
return;
|
|
12036
|
+
}
|
|
12037
|
+
const oldIdx = findSelectedIdx;
|
|
12038
|
+
const oldScroll = findScrollOffset;
|
|
12039
|
+
findSelectedIdx = next;
|
|
12040
|
+
findSnippetIdx = 0;
|
|
12041
|
+
adjustFindScroll();
|
|
12042
|
+
if (findScrollOffset !== oldScroll) {
|
|
12043
|
+
repaintFindViewport();
|
|
12044
|
+
} else {
|
|
12045
|
+
withSync(() => {
|
|
12046
|
+
repaintFindResult(oldIdx, false);
|
|
12047
|
+
repaintFindResult(findSelectedIdx, true);
|
|
12048
|
+
});
|
|
12049
|
+
repaintFindIndicatorRow();
|
|
12050
|
+
}
|
|
12051
|
+
};
|
|
12052
|
+
switch (name) {
|
|
12053
|
+
case "UP":
|
|
12054
|
+
case "SHIFT_TAB":
|
|
12055
|
+
case "CTRL_P":
|
|
12056
|
+
moveDeep(-1);
|
|
12057
|
+
return;
|
|
12058
|
+
case "DOWN":
|
|
12059
|
+
case "TAB":
|
|
12060
|
+
case "CTRL_N":
|
|
12061
|
+
moveDeep(1);
|
|
12062
|
+
return;
|
|
12063
|
+
case "PAGE_UP":
|
|
12064
|
+
moveDeep(-findViewportSize());
|
|
12065
|
+
return;
|
|
12066
|
+
case "PAGE_DOWN":
|
|
12067
|
+
moveDeep(findViewportSize());
|
|
12068
|
+
return;
|
|
12069
|
+
case "HOME":
|
|
12070
|
+
moveDeep(-findSelectedIdx);
|
|
12071
|
+
return;
|
|
12072
|
+
case "END":
|
|
12073
|
+
moveDeep(findResults.length);
|
|
12074
|
+
return;
|
|
12075
|
+
}
|
|
12076
|
+
return;
|
|
12077
|
+
}
|
|
12078
|
+
};
|
|
12079
|
+
pushLayer({ onKey: findOnKey, onResize: () => renderFind() });
|
|
12080
|
+
};
|
|
12081
|
+
const onKey = (name, _matches, data) => {
|
|
12082
|
+
if (mode === "busy") {
|
|
10628
12083
|
return;
|
|
10629
12084
|
}
|
|
10630
12085
|
if (mode === "rename") {
|
|
@@ -10688,6 +12143,10 @@ ${cells}`;
|
|
|
10688
12143
|
return;
|
|
10689
12144
|
}
|
|
10690
12145
|
clearTransient();
|
|
12146
|
+
if (name === "CTRL_F") {
|
|
12147
|
+
openFindLayer();
|
|
12148
|
+
return;
|
|
12149
|
+
}
|
|
10691
12150
|
if (selectedIdx === 0 && !searchActive) {
|
|
10692
12151
|
if (name === "ESCAPE" || name === "CTRL_C" || name === "CTRL_D") {
|
|
10693
12152
|
cleanup();
|
|
@@ -10759,8 +12218,7 @@ ${cells}`;
|
|
|
10759
12218
|
return;
|
|
10760
12219
|
}
|
|
10761
12220
|
if (!searchActive && data?.isCharacter && name === "?") {
|
|
10762
|
-
|
|
10763
|
-
renderHelp();
|
|
12221
|
+
openHelpLayer();
|
|
10764
12222
|
return;
|
|
10765
12223
|
}
|
|
10766
12224
|
if (searchActive) {
|
|
@@ -10820,13 +12278,7 @@ ${cells}`;
|
|
|
10820
12278
|
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
10821
12279
|
prefs.filters.cwdOnly = !prefs.filters.cwdOnly;
|
|
10822
12280
|
applyFilter();
|
|
10823
|
-
|
|
10824
|
-
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
10825
|
-
if (idx >= 0) {
|
|
10826
|
-
selectedIdx = idx + 1;
|
|
10827
|
-
adjustScroll();
|
|
10828
|
-
}
|
|
10829
|
-
}
|
|
12281
|
+
restoreCursorAfterFilter(keepId);
|
|
10830
12282
|
renderFromScratch();
|
|
10831
12283
|
return;
|
|
10832
12284
|
}
|
|
@@ -10837,13 +12289,15 @@ ${cells}`;
|
|
|
10837
12289
|
allSessions
|
|
10838
12290
|
);
|
|
10839
12291
|
applyFilter();
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
12292
|
+
restoreCursorAfterFilter(keepId);
|
|
12293
|
+
renderFromScratch();
|
|
12294
|
+
return;
|
|
12295
|
+
}
|
|
12296
|
+
if (name === "i" || name === "I") {
|
|
12297
|
+
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
12298
|
+
prefs.filters.showCat = !prefs.filters.showCat;
|
|
12299
|
+
applyFilter();
|
|
12300
|
+
restoreCursorAfterFilter(keepId);
|
|
10847
12301
|
renderFromScratch();
|
|
10848
12302
|
return;
|
|
10849
12303
|
}
|
|
@@ -10980,6 +12434,12 @@ ${cells}`;
|
|
|
10980
12434
|
return;
|
|
10981
12435
|
}
|
|
10982
12436
|
};
|
|
12437
|
+
pushLayer({
|
|
12438
|
+
onKey: (name, _matches, data) => onKey(name, _matches, data),
|
|
12439
|
+
onResize: () => {
|
|
12440
|
+
if (!resolved) renderFromScratch();
|
|
12441
|
+
}
|
|
12442
|
+
});
|
|
10983
12443
|
term.grabInput({});
|
|
10984
12444
|
const tSetup = term;
|
|
10985
12445
|
if (tSetup.stdin && typeof tSetup.onStdin === "function") {
|
|
@@ -10988,10 +12448,10 @@ ${cells}`;
|
|
|
10988
12448
|
tSetup.stdin.on("data", rawStdinHandler);
|
|
10989
12449
|
process.stdout.write("\x1B[?2004h");
|
|
10990
12450
|
}
|
|
10991
|
-
term.on("key",
|
|
10992
|
-
term.on("resize",
|
|
12451
|
+
term.on("key", dispatch);
|
|
12452
|
+
term.on("resize", dispatchResize);
|
|
10993
12453
|
autoRefreshTimer = setInterval(() => {
|
|
10994
|
-
if (resolved || mode !== "normal" || searchActive || autoRefreshInFlight) {
|
|
12454
|
+
if (resolved || focusStack.length > 1 || mode !== "normal" || searchActive || autoRefreshInFlight) {
|
|
10995
12455
|
return;
|
|
10996
12456
|
}
|
|
10997
12457
|
const currentId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
@@ -11002,10 +12462,10 @@ ${cells}`;
|
|
|
11002
12462
|
}, 3e3);
|
|
11003
12463
|
});
|
|
11004
12464
|
}
|
|
11005
|
-
function
|
|
12465
|
+
function readTermHeight2(term) {
|
|
11006
12466
|
return term.height ?? 24;
|
|
11007
12467
|
}
|
|
11008
|
-
function
|
|
12468
|
+
function readTermWidth2(term) {
|
|
11009
12469
|
return term.width ?? 80;
|
|
11010
12470
|
}
|
|
11011
12471
|
function formatComposerTitle(cwd, maxWidth) {
|
|
@@ -11060,19 +12520,22 @@ function matchesSearch(s, term) {
|
|
|
11060
12520
|
}
|
|
11061
12521
|
return false;
|
|
11062
12522
|
}
|
|
11063
|
-
var ROW_PREFIX_WIDTH, PICKER_COMPOSER_MAX_ROWS, BOX_HORIZONTAL_PAD, HELP_KEYS_WIDTH, HELP_ENTRIES;
|
|
12523
|
+
var ROW_PREFIX_WIDTH, PICKER_COMPOSER_MAX_ROWS, FIND_BOX_MAX_ROWS, BOX_HORIZONTAL_PAD, HELP_KEYS_WIDTH, HELP_ENTRIES;
|
|
11064
12524
|
var init_picker = __esm({
|
|
11065
12525
|
"src/tui/picker.ts"() {
|
|
11066
12526
|
"use strict";
|
|
11067
12527
|
init_session_row();
|
|
11068
12528
|
init_paths();
|
|
11069
12529
|
init_session();
|
|
12530
|
+
init_hydra_version();
|
|
11070
12531
|
init_discovery();
|
|
11071
12532
|
init_input();
|
|
11072
12533
|
init_screen();
|
|
12534
|
+
init_import_action_prompt();
|
|
11073
12535
|
init_sync();
|
|
11074
12536
|
ROW_PREFIX_WIDTH = 2;
|
|
11075
12537
|
PICKER_COMPOSER_MAX_ROWS = 4;
|
|
12538
|
+
FIND_BOX_MAX_ROWS = 4;
|
|
11076
12539
|
BOX_HORIZONTAL_PAD = 4;
|
|
11077
12540
|
HELP_KEYS_WIDTH = 20;
|
|
11078
12541
|
HELP_ENTRIES = [
|
|
@@ -11085,9 +12548,11 @@ var init_picker = __esm({
|
|
|
11085
12548
|
["Enter", "open selected session"],
|
|
11086
12549
|
["v", "view-only (open transcript without spawning the agent)"],
|
|
11087
12550
|
null,
|
|
11088
|
-
["/", "search sessions"],
|
|
12551
|
+
["/", "search sessions (metadata)"],
|
|
12552
|
+
["^f", "find in session history (content + tool inputs)"],
|
|
11089
12553
|
["o", "toggle cwd-only filter"],
|
|
11090
12554
|
["h", "cycle host filter (local / <peer> / all)"],
|
|
12555
|
+
["i", "toggle include-cat filter"],
|
|
11091
12556
|
["r", "refresh from daemon"],
|
|
11092
12557
|
null,
|
|
11093
12558
|
["k", "kill the selected live session"],
|
|
@@ -11116,105 +12581,15 @@ async function validateLocalCwd(input) {
|
|
|
11116
12581
|
} catch {
|
|
11117
12582
|
return { ok: false, reason: `${resolved} does not exist` };
|
|
11118
12583
|
}
|
|
11119
|
-
if (!stat5.isDirectory()) {
|
|
11120
|
-
return { ok: false, reason: `${resolved} is not a directory` };
|
|
11121
|
-
}
|
|
11122
|
-
return { ok: true, path: resolved };
|
|
11123
|
-
}
|
|
11124
|
-
var init_cwd = __esm({
|
|
11125
|
-
"src/core/cwd.ts"() {
|
|
11126
|
-
"use strict";
|
|
11127
|
-
init_config();
|
|
11128
|
-
}
|
|
11129
|
-
});
|
|
11130
|
-
|
|
11131
|
-
// src/tui/prompt-utils.ts
|
|
11132
|
-
function resetTerminalModes() {
|
|
11133
|
-
process.stdout.write("\x1B[<u");
|
|
11134
|
-
process.stdout.write("\x1B[?2004l");
|
|
11135
|
-
process.stdout.write("\x1B[>4;0m");
|
|
11136
|
-
process.stdout.write("\x1B[>5;0m");
|
|
11137
|
-
process.stdout.write("\x1B[?1000l");
|
|
11138
|
-
process.stdout.write("\x1B[?1002l");
|
|
11139
|
-
process.stdout.write("\x1B[?1006l");
|
|
11140
|
-
process.stdout.write("\x1B[?1l");
|
|
11141
|
-
process.stdout.write("\x1B>");
|
|
11142
|
-
}
|
|
11143
|
-
function readTermWidth2(term) {
|
|
11144
|
-
return term.width ?? 80;
|
|
11145
|
-
}
|
|
11146
|
-
function readTermHeight2(term) {
|
|
11147
|
-
return term.height ?? 24;
|
|
11148
|
-
}
|
|
11149
|
-
function drawBox(term, opts) {
|
|
11150
|
-
const termW = readTermWidth2(term);
|
|
11151
|
-
const termH = readTermHeight2(term);
|
|
11152
|
-
const desiredContentW = opts.contentWidth ?? MAX_BOX_WIDTH;
|
|
11153
|
-
const maxContentW = Math.max(10, Math.min(MAX_BOX_WIDTH, termW - 4));
|
|
11154
|
-
const contentW = Math.min(desiredContentW, maxContentW);
|
|
11155
|
-
const w = contentW + 2;
|
|
11156
|
-
const contentH = Math.max(1, Math.min(opts.contentHeight, termH - 4));
|
|
11157
|
-
const h = contentH + 2;
|
|
11158
|
-
const x = Math.max(1, Math.floor((termW - w) / 2) + 1);
|
|
11159
|
-
const y = Math.max(1, Math.floor((termH - h) / 2) + 1);
|
|
11160
|
-
term.moveTo(1, 1).eraseDisplayBelow();
|
|
11161
|
-
const topInner = HORIZ.repeat(w - 2);
|
|
11162
|
-
const top = renderTitleStrip(topInner, opts.title);
|
|
11163
|
-
term.moveTo(x, y);
|
|
11164
|
-
term.dim.noFormat(TL);
|
|
11165
|
-
paintTopStrip(term, top);
|
|
11166
|
-
term.dim.noFormat(TR);
|
|
11167
|
-
for (let row = 1; row <= contentH; row++) {
|
|
11168
|
-
term.moveTo(x, y + row);
|
|
11169
|
-
term.dim.noFormat(VERT);
|
|
11170
|
-
term.moveTo(x + w - 1, y + row);
|
|
11171
|
-
term.dim.noFormat(VERT);
|
|
11172
|
-
}
|
|
11173
|
-
term.moveTo(x, y + h - 1);
|
|
11174
|
-
term.dim.noFormat(BL + HORIZ.repeat(w - 2) + BR);
|
|
11175
|
-
return {
|
|
11176
|
-
x,
|
|
11177
|
-
y,
|
|
11178
|
-
w,
|
|
11179
|
-
h,
|
|
11180
|
-
contentX: x + 1,
|
|
11181
|
-
contentY: y + 1,
|
|
11182
|
-
contentW,
|
|
11183
|
-
contentH
|
|
11184
|
-
};
|
|
11185
|
-
}
|
|
11186
|
-
function renderTitleStrip(innerDashes, title) {
|
|
11187
|
-
if (!title) {
|
|
11188
|
-
return { dashes: innerDashes };
|
|
11189
|
-
}
|
|
11190
|
-
const chip = ` ${title} `;
|
|
11191
|
-
if (chip.length + 4 > innerDashes.length) {
|
|
11192
|
-
return { dashes: innerDashes };
|
|
11193
|
-
}
|
|
11194
|
-
const offset = 2;
|
|
11195
|
-
const dashes = innerDashes.slice(0, offset) + " ".repeat(chip.length) + innerDashes.slice(offset + chip.length);
|
|
11196
|
-
return { dashes, title: { offset, text: chip } };
|
|
11197
|
-
}
|
|
11198
|
-
function paintTopStrip(term, strip) {
|
|
11199
|
-
if (!strip.title) {
|
|
11200
|
-
term.dim.noFormat(strip.dashes);
|
|
11201
|
-
return;
|
|
12584
|
+
if (!stat5.isDirectory()) {
|
|
12585
|
+
return { ok: false, reason: `${resolved} is not a directory` };
|
|
11202
12586
|
}
|
|
11203
|
-
|
|
11204
|
-
term.brightCyan.noFormat(strip.title.text);
|
|
11205
|
-
term.dim.noFormat(strip.dashes.slice(strip.title.offset + strip.title.text.length));
|
|
12587
|
+
return { ok: true, path: resolved };
|
|
11206
12588
|
}
|
|
11207
|
-
var
|
|
11208
|
-
|
|
11209
|
-
"src/tui/prompt-utils.ts"() {
|
|
12589
|
+
var init_cwd = __esm({
|
|
12590
|
+
"src/core/cwd.ts"() {
|
|
11210
12591
|
"use strict";
|
|
11211
|
-
|
|
11212
|
-
HORIZ = "\u2500";
|
|
11213
|
-
VERT = "\u2502";
|
|
11214
|
-
TL = "\u250C";
|
|
11215
|
-
TR = "\u2510";
|
|
11216
|
-
BL = "\u2514";
|
|
11217
|
-
BR = "\u2518";
|
|
12592
|
+
init_config();
|
|
11218
12593
|
}
|
|
11219
12594
|
});
|
|
11220
12595
|
|
|
@@ -11246,7 +12621,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11246
12621
|
for (const hr of headerRows) {
|
|
11247
12622
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11248
12623
|
term.dim.noFormat(` ${hr.label}`);
|
|
11249
|
-
term.noFormat(
|
|
12624
|
+
term.noFormat(truncate3(hr.value, innerW - hr.label.length - 2));
|
|
11250
12625
|
row++;
|
|
11251
12626
|
}
|
|
11252
12627
|
row++;
|
|
@@ -11257,7 +12632,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11257
12632
|
row += 2;
|
|
11258
12633
|
if (errorLine !== null) {
|
|
11259
12634
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11260
|
-
term.red.noFormat(` ${
|
|
12635
|
+
term.red.noFormat(` ${truncate3(errorLine, innerW - 2)}`);
|
|
11261
12636
|
} else {
|
|
11262
12637
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11263
12638
|
term.dim.noFormat(
|
|
@@ -11293,7 +12668,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11293
12668
|
term.dim.noFormat("\u2502");
|
|
11294
12669
|
term.moveTo(layout.contentX, layout.contentY + errRow);
|
|
11295
12670
|
if (errorLine !== null) {
|
|
11296
|
-
term.red.noFormat(` ${
|
|
12671
|
+
term.red.noFormat(` ${truncate3(errorLine, layout.contentW - 2)}`);
|
|
11297
12672
|
} else {
|
|
11298
12673
|
term.dim.noFormat(
|
|
11299
12674
|
" Enter accept \xB7 Esc back \xB7 ^U clear \xB7 ^W kill word"
|
|
@@ -11389,7 +12764,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11389
12764
|
term.on("resize", onResize);
|
|
11390
12765
|
});
|
|
11391
12766
|
}
|
|
11392
|
-
function
|
|
12767
|
+
function truncate3(s, max) {
|
|
11393
12768
|
if (max <= 1) {
|
|
11394
12769
|
return "";
|
|
11395
12770
|
}
|
|
@@ -11417,226 +12792,6 @@ var init_import_cwd_prompt = __esm({
|
|
|
11417
12792
|
}
|
|
11418
12793
|
});
|
|
11419
12794
|
|
|
11420
|
-
// src/tui/import-action-prompt.ts
|
|
11421
|
-
function actionPromptStep(selected, key, choices = ACTION_CHOICES) {
|
|
11422
|
-
if (key.kind === "cancel") {
|
|
11423
|
-
return { kind: "cancel" };
|
|
11424
|
-
}
|
|
11425
|
-
if (key.kind === "back") {
|
|
11426
|
-
return { kind: "back" };
|
|
11427
|
-
}
|
|
11428
|
-
if (key.kind === "enter") {
|
|
11429
|
-
const choice = choices[selected];
|
|
11430
|
-
if (!choice) {
|
|
11431
|
-
return { kind: "back" };
|
|
11432
|
-
}
|
|
11433
|
-
return { kind: "resolve", action: choice.key };
|
|
11434
|
-
}
|
|
11435
|
-
if (key.kind === "up") {
|
|
11436
|
-
return {
|
|
11437
|
-
kind: "continue",
|
|
11438
|
-
selected: Math.max(0, selected - 1)
|
|
11439
|
-
};
|
|
11440
|
-
}
|
|
11441
|
-
if (key.kind === "down") {
|
|
11442
|
-
return {
|
|
11443
|
-
kind: "continue",
|
|
11444
|
-
selected: Math.min(choices.length - 1, selected + 1)
|
|
11445
|
-
};
|
|
11446
|
-
}
|
|
11447
|
-
if (key.kind === "char") {
|
|
11448
|
-
const lower = key.ch.toLowerCase();
|
|
11449
|
-
if (lower === "n") {
|
|
11450
|
-
return {
|
|
11451
|
-
kind: "continue",
|
|
11452
|
-
selected: Math.min(choices.length - 1, selected + 1)
|
|
11453
|
-
};
|
|
11454
|
-
}
|
|
11455
|
-
if (lower === "p") {
|
|
11456
|
-
return {
|
|
11457
|
-
kind: "continue",
|
|
11458
|
-
selected: Math.max(0, selected - 1)
|
|
11459
|
-
};
|
|
11460
|
-
}
|
|
11461
|
-
const idx = choices.findIndex((c) => c.hotkey.toLowerCase() === lower);
|
|
11462
|
-
if (idx >= 0) {
|
|
11463
|
-
const choice = choices[idx];
|
|
11464
|
-
if (choice) {
|
|
11465
|
-
return { kind: "resolve", action: choice.key };
|
|
11466
|
-
}
|
|
11467
|
-
}
|
|
11468
|
-
}
|
|
11469
|
-
return { kind: "continue", selected };
|
|
11470
|
-
}
|
|
11471
|
-
async function promptForImportAction(term, session) {
|
|
11472
|
-
resetTerminalModes();
|
|
11473
|
-
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
11474
|
-
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
11475
|
-
const originalCwd = shortenHomePath(session.cwd);
|
|
11476
|
-
let selected = ACTION_CHOICES.findIndex((c) => c.key === "view");
|
|
11477
|
-
if (selected < 0) {
|
|
11478
|
-
selected = 0;
|
|
11479
|
-
}
|
|
11480
|
-
const render = () => {
|
|
11481
|
-
const choiceRows = ACTION_CHOICES.length * 2;
|
|
11482
|
-
const contentHeight = 7 + choiceRows + 2;
|
|
11483
|
-
const layout = drawBox(term, {
|
|
11484
|
-
contentHeight,
|
|
11485
|
-
title: "Imported session"
|
|
11486
|
-
});
|
|
11487
|
-
const innerW = layout.contentW;
|
|
11488
|
-
const headerRows = [
|
|
11489
|
-
{ label: "session: ", value: shortId2 },
|
|
11490
|
-
{ label: "from: ", value: fromMachine },
|
|
11491
|
-
{ label: "cwd: ", value: originalCwd }
|
|
11492
|
-
];
|
|
11493
|
-
let row = 0;
|
|
11494
|
-
for (const hr of headerRows) {
|
|
11495
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11496
|
-
term.dim.noFormat(` ${hr.label}`);
|
|
11497
|
-
term.noFormat(truncate3(hr.value, innerW - hr.label.length - 2));
|
|
11498
|
-
row++;
|
|
11499
|
-
}
|
|
11500
|
-
row++;
|
|
11501
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11502
|
-
term.noFormat(" What do you want to do?");
|
|
11503
|
-
row += 2;
|
|
11504
|
-
for (let i = 0; i < ACTION_CHOICES.length; i++) {
|
|
11505
|
-
const choice = ACTION_CHOICES[i];
|
|
11506
|
-
if (!choice) {
|
|
11507
|
-
continue;
|
|
11508
|
-
}
|
|
11509
|
-
const pointer = i === selected ? "\u276F" : " ";
|
|
11510
|
-
const label = ` ${pointer} ${choice.label}`;
|
|
11511
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11512
|
-
if (i === selected) {
|
|
11513
|
-
term.brightWhite.bgBlue.noFormat(padRight(label, innerW));
|
|
11514
|
-
} else {
|
|
11515
|
-
term.noFormat(label);
|
|
11516
|
-
}
|
|
11517
|
-
row++;
|
|
11518
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11519
|
-
term.dim.noFormat(` ${choice.description}`);
|
|
11520
|
-
row++;
|
|
11521
|
-
}
|
|
11522
|
-
row++;
|
|
11523
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11524
|
-
term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 f/v jump \xB7 Esc back");
|
|
11525
|
-
return layout;
|
|
11526
|
-
};
|
|
11527
|
-
render();
|
|
11528
|
-
term.hideCursor();
|
|
11529
|
-
return await new Promise((resolve6) => {
|
|
11530
|
-
let resolved = false;
|
|
11531
|
-
const cleanup = () => {
|
|
11532
|
-
if (resolved) {
|
|
11533
|
-
return;
|
|
11534
|
-
}
|
|
11535
|
-
resolved = true;
|
|
11536
|
-
term.off("key", onKey);
|
|
11537
|
-
term.off("resize", onResize);
|
|
11538
|
-
term.grabInput(false);
|
|
11539
|
-
term.hideCursor(false);
|
|
11540
|
-
term.moveTo(1, 1).eraseDisplayBelow();
|
|
11541
|
-
};
|
|
11542
|
-
const finish = (value) => {
|
|
11543
|
-
cleanup();
|
|
11544
|
-
resolve6(value);
|
|
11545
|
-
};
|
|
11546
|
-
const onResize = () => {
|
|
11547
|
-
if (resolved) {
|
|
11548
|
-
return;
|
|
11549
|
-
}
|
|
11550
|
-
render();
|
|
11551
|
-
};
|
|
11552
|
-
const onKey = (name, _matches, data) => {
|
|
11553
|
-
const input = mapKey(name, data);
|
|
11554
|
-
if (!input) {
|
|
11555
|
-
return;
|
|
11556
|
-
}
|
|
11557
|
-
const step = actionPromptStep(selected, input);
|
|
11558
|
-
if (step.kind === "cancel") {
|
|
11559
|
-
finish("cancel");
|
|
11560
|
-
return;
|
|
11561
|
-
}
|
|
11562
|
-
if (step.kind === "back") {
|
|
11563
|
-
finish("back");
|
|
11564
|
-
return;
|
|
11565
|
-
}
|
|
11566
|
-
if (step.kind === "resolve") {
|
|
11567
|
-
finish(step.action);
|
|
11568
|
-
return;
|
|
11569
|
-
}
|
|
11570
|
-
if (step.selected !== selected) {
|
|
11571
|
-
selected = step.selected;
|
|
11572
|
-
render();
|
|
11573
|
-
}
|
|
11574
|
-
};
|
|
11575
|
-
term.grabInput({});
|
|
11576
|
-
term.on("key", onKey);
|
|
11577
|
-
term.on("resize", onResize);
|
|
11578
|
-
});
|
|
11579
|
-
}
|
|
11580
|
-
function mapKey(name, data) {
|
|
11581
|
-
if (name === "UP") {
|
|
11582
|
-
return { kind: "up" };
|
|
11583
|
-
}
|
|
11584
|
-
if (name === "DOWN") {
|
|
11585
|
-
return { kind: "down" };
|
|
11586
|
-
}
|
|
11587
|
-
if (name === "ENTER" || name === "KP_ENTER") {
|
|
11588
|
-
return { kind: "enter" };
|
|
11589
|
-
}
|
|
11590
|
-
if (name === "ESCAPE") {
|
|
11591
|
-
return { kind: "back" };
|
|
11592
|
-
}
|
|
11593
|
-
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
11594
|
-
return { kind: "cancel" };
|
|
11595
|
-
}
|
|
11596
|
-
if (data?.isCharacter) {
|
|
11597
|
-
return { kind: "char", ch: name };
|
|
11598
|
-
}
|
|
11599
|
-
return null;
|
|
11600
|
-
}
|
|
11601
|
-
function truncate3(s, max) {
|
|
11602
|
-
if (max <= 1) {
|
|
11603
|
-
return "";
|
|
11604
|
-
}
|
|
11605
|
-
if (s.length <= max) {
|
|
11606
|
-
return s;
|
|
11607
|
-
}
|
|
11608
|
-
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
11609
|
-
}
|
|
11610
|
-
function padRight(s, w) {
|
|
11611
|
-
if (s.length >= w) {
|
|
11612
|
-
return s.slice(0, w);
|
|
11613
|
-
}
|
|
11614
|
-
return s + " ".repeat(w - s.length);
|
|
11615
|
-
}
|
|
11616
|
-
var ACTION_CHOICES;
|
|
11617
|
-
var init_import_action_prompt = __esm({
|
|
11618
|
-
"src/tui/import-action-prompt.ts"() {
|
|
11619
|
-
"use strict";
|
|
11620
|
-
init_paths();
|
|
11621
|
-
init_session();
|
|
11622
|
-
init_prompt_utils();
|
|
11623
|
-
ACTION_CHOICES = [
|
|
11624
|
-
{
|
|
11625
|
-
key: "fork-local",
|
|
11626
|
-
label: "Fork locally",
|
|
11627
|
-
hotkey: "f",
|
|
11628
|
-
description: "spawn a local fork \u2014 original imported copy stays as-is"
|
|
11629
|
-
},
|
|
11630
|
-
{
|
|
11631
|
-
key: "view",
|
|
11632
|
-
label: "View transcript",
|
|
11633
|
-
hotkey: "v",
|
|
11634
|
-
description: "open read-only, no agent spawn"
|
|
11635
|
-
}
|
|
11636
|
-
];
|
|
11637
|
-
}
|
|
11638
|
-
});
|
|
11639
|
-
|
|
11640
12795
|
// src/tui/clipboard.ts
|
|
11641
12796
|
import { spawn as nodeSpawn } from "child_process";
|
|
11642
12797
|
import fs21 from "fs/promises";
|
|
@@ -12035,41 +13190,69 @@ function formatEvent(event) {
|
|
|
12035
13190
|
return [];
|
|
12036
13191
|
}
|
|
12037
13192
|
}
|
|
12038
|
-
function applyInlineMarkup(text) {
|
|
13193
|
+
function applyInlineMarkup(text, opts) {
|
|
13194
|
+
const codeOpen = opts?.codeOpen ?? "^C";
|
|
13195
|
+
const boldReset = opts?.boldReset ?? "^:";
|
|
13196
|
+
const codeReset = opts?.codeReset ?? "^:";
|
|
12039
13197
|
let s = text.replace(/\^/g, "^^");
|
|
12040
|
-
s = s.replace(/\*\*(.+?)\*\*/g,
|
|
12041
|
-
s = s.replace(/`([^`]+)`/g,
|
|
13198
|
+
s = s.replace(/\*\*(.+?)\*\*/g, `^+$1${boldReset}`);
|
|
13199
|
+
s = s.replace(/`([^`]+)`/g, `${codeOpen}$1${codeReset}`);
|
|
12042
13200
|
return s;
|
|
12043
13201
|
}
|
|
12044
|
-
function
|
|
13202
|
+
function parseMarkdown(text, opts) {
|
|
13203
|
+
const {
|
|
13204
|
+
proseStyle,
|
|
13205
|
+
highlightCode,
|
|
13206
|
+
prefixStyle,
|
|
13207
|
+
firstPrefix = " ",
|
|
13208
|
+
inlineOpts
|
|
13209
|
+
} = opts;
|
|
12045
13210
|
const out = [];
|
|
12046
13211
|
const lines = text.split("\n");
|
|
12047
13212
|
let inCode = false;
|
|
12048
13213
|
let codeLang = "";
|
|
12049
13214
|
let codeBuffer = [];
|
|
13215
|
+
let firstNonBlank = firstPrefix !== " ";
|
|
13216
|
+
const line = (body, bodyStyle, prefix = " ") => {
|
|
13217
|
+
const entry = { prefix, body, bodyStyle };
|
|
13218
|
+
if (prefixStyle !== void 0)
|
|
13219
|
+
entry.prefixStyle = prefixStyle;
|
|
13220
|
+
out.push(entry);
|
|
13221
|
+
};
|
|
13222
|
+
const nextPrefix = () => {
|
|
13223
|
+
if (!firstNonBlank)
|
|
13224
|
+
return " ";
|
|
13225
|
+
firstNonBlank = false;
|
|
13226
|
+
return firstPrefix;
|
|
13227
|
+
};
|
|
12050
13228
|
const flushCode = () => {
|
|
12051
|
-
if (codeBuffer.length === 0)
|
|
13229
|
+
if (codeBuffer.length === 0)
|
|
12052
13230
|
return;
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
13231
|
+
if (highlightCode) {
|
|
13232
|
+
const highlighted = highlightFencedBlock(codeLang, codeBuffer);
|
|
13233
|
+
for (const piece of highlighted) {
|
|
13234
|
+
const entry = {
|
|
13235
|
+
prefix: " ",
|
|
13236
|
+
body: piece.body,
|
|
13237
|
+
bodyStyle: "code",
|
|
13238
|
+
fillRow: true
|
|
13239
|
+
};
|
|
13240
|
+
if (prefixStyle !== void 0)
|
|
13241
|
+
entry.prefixStyle = prefixStyle;
|
|
13242
|
+
if (piece.ansi)
|
|
13243
|
+
entry.ansi = true;
|
|
13244
|
+
out.push(entry);
|
|
12064
13245
|
}
|
|
12065
|
-
|
|
13246
|
+
} else {
|
|
13247
|
+
for (const cl of codeBuffer)
|
|
13248
|
+
line(cl.replace(/\^/g, "^^"), proseStyle);
|
|
12066
13249
|
}
|
|
12067
13250
|
codeBuffer = [];
|
|
12068
13251
|
codeLang = "";
|
|
12069
13252
|
};
|
|
12070
13253
|
for (let i = 0; i < lines.length; i++) {
|
|
12071
|
-
const
|
|
12072
|
-
const fence =
|
|
13254
|
+
const l = lines[i];
|
|
13255
|
+
const fence = l.match(/^\s*```\s*(\w*)\s*$/);
|
|
12073
13256
|
if (fence) {
|
|
12074
13257
|
if (!inCode) {
|
|
12075
13258
|
inCode = true;
|
|
@@ -12081,68 +13264,81 @@ function parseAgentMarkdown(text) {
|
|
|
12081
13264
|
continue;
|
|
12082
13265
|
}
|
|
12083
13266
|
if (inCode) {
|
|
12084
|
-
codeBuffer.push(
|
|
13267
|
+
codeBuffer.push(l);
|
|
12085
13268
|
continue;
|
|
12086
13269
|
}
|
|
12087
|
-
const heading =
|
|
13270
|
+
const heading = l.match(/^(#{1,6})\s+(.*)$/);
|
|
12088
13271
|
if (heading) {
|
|
12089
13272
|
const level = heading[1].length;
|
|
12090
|
-
const
|
|
12091
|
-
const
|
|
12092
|
-
|
|
12093
|
-
prefix: " ",
|
|
12094
|
-
body: text2,
|
|
12095
|
-
bodyStyle: style
|
|
12096
|
-
});
|
|
13273
|
+
const headingText = heading[2] ?? "";
|
|
13274
|
+
const headingStyle = highlightCode ? level === 1 ? "heading-1" : level === 2 ? "heading-2" : "heading-3" : proseStyle;
|
|
13275
|
+
line(headingText, headingStyle, nextPrefix());
|
|
12097
13276
|
continue;
|
|
12098
13277
|
}
|
|
12099
13278
|
const next = lines[i + 1];
|
|
12100
|
-
if (
|
|
12101
|
-
const header = parseTableRow(
|
|
13279
|
+
if (l.includes("|") && next !== void 0 && isTableSeparatorLine(next) && parseTableRow(l).length === parseTableRow(next).length) {
|
|
13280
|
+
const header = parseTableRow(l);
|
|
12102
13281
|
const body = [];
|
|
12103
13282
|
let j = i + 2;
|
|
12104
13283
|
while (j < lines.length && lines[j].includes("|")) {
|
|
12105
13284
|
body.push(parseTableRow(lines[j]));
|
|
12106
13285
|
j++;
|
|
12107
13286
|
}
|
|
12108
|
-
|
|
13287
|
+
const tableLines = formatTable(header, body);
|
|
13288
|
+
for (const tl of tableLines) {
|
|
13289
|
+
if (prefixStyle !== void 0)
|
|
13290
|
+
tl.prefixStyle = prefixStyle;
|
|
13291
|
+
out.push(tl);
|
|
13292
|
+
}
|
|
12109
13293
|
i = j - 1;
|
|
12110
13294
|
continue;
|
|
12111
13295
|
}
|
|
12112
|
-
const bullet =
|
|
13296
|
+
const bullet = l.match(/^(\s*)[-*+]\s+(.*)$/);
|
|
12113
13297
|
if (bullet) {
|
|
12114
13298
|
const indent = bullet[1] ?? "";
|
|
12115
13299
|
const item = bullet[2] ?? "";
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
13300
|
+
line(
|
|
13301
|
+
`${indent}\u2022 ${applyInlineMarkup(item, inlineOpts)}`,
|
|
13302
|
+
proseStyle,
|
|
13303
|
+
nextPrefix()
|
|
13304
|
+
);
|
|
12121
13305
|
continue;
|
|
12122
13306
|
}
|
|
12123
|
-
const ordered =
|
|
13307
|
+
const ordered = l.match(/^(\s*)(\d+)\.\s+(.*)$/);
|
|
12124
13308
|
if (ordered) {
|
|
12125
13309
|
const indent = ordered[1] ?? "";
|
|
12126
13310
|
const num = ordered[2] ?? "";
|
|
12127
13311
|
const item = ordered[3] ?? "";
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
13312
|
+
line(
|
|
13313
|
+
`${indent}${num}. ${applyInlineMarkup(item, inlineOpts)}`,
|
|
13314
|
+
proseStyle,
|
|
13315
|
+
nextPrefix()
|
|
13316
|
+
);
|
|
12133
13317
|
continue;
|
|
12134
13318
|
}
|
|
12135
|
-
|
|
12136
|
-
|
|
12137
|
-
|
|
12138
|
-
|
|
12139
|
-
|
|
13319
|
+
const isBlank = l.trim() === "";
|
|
13320
|
+
line(
|
|
13321
|
+
applyInlineMarkup(l, inlineOpts),
|
|
13322
|
+
proseStyle,
|
|
13323
|
+
isBlank ? " " : nextPrefix()
|
|
13324
|
+
);
|
|
12140
13325
|
}
|
|
12141
|
-
if (inCode)
|
|
13326
|
+
if (inCode)
|
|
12142
13327
|
flushCode();
|
|
12143
|
-
}
|
|
12144
13328
|
return out;
|
|
12145
13329
|
}
|
|
13330
|
+
function parseAgentMarkdown(text) {
|
|
13331
|
+
return parseMarkdown(text, { proseStyle: "agent", highlightCode: true });
|
|
13332
|
+
}
|
|
13333
|
+
function parseThoughtMarkdown(text) {
|
|
13334
|
+
return parseMarkdown(text, {
|
|
13335
|
+
proseStyle: "thought",
|
|
13336
|
+
highlightCode: false,
|
|
13337
|
+
prefixStyle: "thought",
|
|
13338
|
+
firstPrefix: "\xB7 ",
|
|
13339
|
+
inlineOpts: { codeOpen: "^c", boldReset: "^-", codeReset: "^K" }
|
|
13340
|
+
});
|
|
13341
|
+
}
|
|
12146
13342
|
function parseTableRow(line) {
|
|
12147
13343
|
let s = line.trim();
|
|
12148
13344
|
if (s.startsWith("|")) {
|
|
@@ -12893,6 +14089,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
12893
14089
|
if (teardownStarted) {
|
|
12894
14090
|
return { outcome: { outcome: "cancelled" } };
|
|
12895
14091
|
}
|
|
14092
|
+
if (opts.dangerouslySkipPermissions) {
|
|
14093
|
+
return buildApproveResponse(params);
|
|
14094
|
+
}
|
|
12896
14095
|
const p = params ?? {};
|
|
12897
14096
|
const rawOptions = Array.isArray(p.options) ? p.options : [];
|
|
12898
14097
|
const options = rawOptions.map((o) => ({
|
|
@@ -14169,6 +15368,33 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14169
15368
|
agentKey = null;
|
|
14170
15369
|
agentBuffer = "";
|
|
14171
15370
|
};
|
|
15371
|
+
let thoughtBuffer = "";
|
|
15372
|
+
let thoughtKey = null;
|
|
15373
|
+
let thoughtSeq = 0;
|
|
15374
|
+
const renderThoughtBlock = () => {
|
|
15375
|
+
if (thoughtKey === null)
|
|
15376
|
+
return;
|
|
15377
|
+
const lines = parseThoughtMarkdown(thoughtBuffer);
|
|
15378
|
+
if (lines.length === 0)
|
|
15379
|
+
return;
|
|
15380
|
+
screen.upsertLines(thoughtKey, lines);
|
|
15381
|
+
};
|
|
15382
|
+
const appendThought = (text) => {
|
|
15383
|
+
if (text.length === 0)
|
|
15384
|
+
return;
|
|
15385
|
+
if (thoughtKey === null) {
|
|
15386
|
+
screen.ensureSeparator();
|
|
15387
|
+
thoughtKey = `thought:${thoughtSeq}`;
|
|
15388
|
+
thoughtSeq += 1;
|
|
15389
|
+
thoughtBuffer = "";
|
|
15390
|
+
}
|
|
15391
|
+
thoughtBuffer += text;
|
|
15392
|
+
renderThoughtBlock();
|
|
15393
|
+
};
|
|
15394
|
+
const closeThought = () => {
|
|
15395
|
+
thoughtKey = null;
|
|
15396
|
+
thoughtBuffer = "";
|
|
15397
|
+
};
|
|
14172
15398
|
const renderToolsBlock = () => {
|
|
14173
15399
|
if (toolsBlockStartedAt === null) {
|
|
14174
15400
|
return;
|
|
@@ -14310,6 +15536,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14310
15536
|
recordHistoryEntry(event.text);
|
|
14311
15537
|
}
|
|
14312
15538
|
closeAgentText();
|
|
15539
|
+
closeThought();
|
|
14313
15540
|
if (toolsBlockStartedAt !== null) {
|
|
14314
15541
|
toolsBlockEndedAt = Date.now();
|
|
14315
15542
|
renderToolsBlock();
|
|
@@ -14333,16 +15560,18 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14333
15560
|
return;
|
|
14334
15561
|
}
|
|
14335
15562
|
if (event.kind === "agent-text") {
|
|
15563
|
+
closeThought();
|
|
14336
15564
|
appendAgentText(event.text);
|
|
14337
15565
|
return;
|
|
14338
15566
|
}
|
|
14339
15567
|
if (event.kind === "agent-thought") {
|
|
14340
15568
|
closeAgentText();
|
|
14341
|
-
|
|
15569
|
+
appendThought(event.text);
|
|
14342
15570
|
return;
|
|
14343
15571
|
}
|
|
14344
15572
|
if (event.kind === "exit-plan-mode") {
|
|
14345
15573
|
closeAgentText();
|
|
15574
|
+
closeThought();
|
|
14346
15575
|
const existing = exitPlanStates.get(event.toolCallId);
|
|
14347
15576
|
const merged = {
|
|
14348
15577
|
plan: event.plan ?? existing?.plan ?? "",
|
|
@@ -14360,12 +15589,14 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14360
15589
|
}
|
|
14361
15590
|
if (event.kind === "tool-call") {
|
|
14362
15591
|
closeAgentText();
|
|
15592
|
+
closeThought();
|
|
14363
15593
|
recordToolCall(event.toolCallId, event.title, event.status, void 0);
|
|
14364
15594
|
renderToolsBlock();
|
|
14365
15595
|
return;
|
|
14366
15596
|
}
|
|
14367
15597
|
if (event.kind === "plan") {
|
|
14368
15598
|
closeAgentText();
|
|
15599
|
+
closeThought();
|
|
14369
15600
|
lastPlanEvent = event;
|
|
14370
15601
|
const lines = formatEvent(event);
|
|
14371
15602
|
if (lines.length > 0) {
|
|
@@ -14375,6 +15606,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14375
15606
|
}
|
|
14376
15607
|
if (event.kind === "tool-call-update") {
|
|
14377
15608
|
closeAgentText();
|
|
15609
|
+
closeThought();
|
|
14378
15610
|
recordToolCall(
|
|
14379
15611
|
event.toolCallId,
|
|
14380
15612
|
event.title,
|
|
@@ -14397,6 +15629,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14397
15629
|
if (event.kind === "turn-complete") {
|
|
14398
15630
|
currentHeadMessageId = void 0;
|
|
14399
15631
|
closeAgentText();
|
|
15632
|
+
closeThought();
|
|
14400
15633
|
let effectiveStopReason = event.amended ? "amended" : event.stopReason;
|
|
14401
15634
|
if (!event.amended && upstreamInterruptedSeen && (effectiveStopReason === void 0 || effectiveStopReason === "end_turn")) {
|
|
14402
15635
|
effectiveStopReason = "error";
|
|
@@ -14491,6 +15724,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14491
15724
|
resolve6({ outcome: { outcome: "cancelled" } });
|
|
14492
15725
|
}
|
|
14493
15726
|
closeAgentText();
|
|
15727
|
+
closeThought();
|
|
14494
15728
|
};
|
|
14495
15729
|
const markToolsBlockRecoveryFailed = () => {
|
|
14496
15730
|
if (toolsBlockStartedAt === null) {
|
|
@@ -14870,6 +16104,7 @@ var init_app = __esm({
|
|
|
14870
16104
|
init_session();
|
|
14871
16105
|
init_paths();
|
|
14872
16106
|
init_hydra_version();
|
|
16107
|
+
init_permission_pick();
|
|
14873
16108
|
init_update_check();
|
|
14874
16109
|
init_history();
|
|
14875
16110
|
init_discovery();
|
|
@@ -14938,9 +16173,14 @@ import { dirname as dirname6, resolve as resolve5 } from "path";
|
|
|
14938
16173
|
// src/cli/parse-args.ts
|
|
14939
16174
|
var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
14940
16175
|
"all",
|
|
16176
|
+
"dangerously-skip-permissions",
|
|
14941
16177
|
"detach",
|
|
16178
|
+
"disabled",
|
|
16179
|
+
"follow",
|
|
16180
|
+
"force",
|
|
14942
16181
|
"foreground",
|
|
14943
16182
|
"help",
|
|
16183
|
+
"include-cat",
|
|
14944
16184
|
"info",
|
|
14945
16185
|
"json",
|
|
14946
16186
|
"new",
|
|
@@ -14948,9 +16188,32 @@ var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
14948
16188
|
"readonly",
|
|
14949
16189
|
"replace",
|
|
14950
16190
|
"rotate-token",
|
|
14951
|
-
"stream",
|
|
14952
16191
|
"version"
|
|
14953
16192
|
]);
|
|
16193
|
+
var KNOWN_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
16194
|
+
"agent",
|
|
16195
|
+
"args",
|
|
16196
|
+
"command",
|
|
16197
|
+
"cwd",
|
|
16198
|
+
"env",
|
|
16199
|
+
"host",
|
|
16200
|
+
"model",
|
|
16201
|
+
"name",
|
|
16202
|
+
"out",
|
|
16203
|
+
"prompt",
|
|
16204
|
+
"session",
|
|
16205
|
+
"stream-bytes",
|
|
16206
|
+
"stream-threshold",
|
|
16207
|
+
"tail"
|
|
16208
|
+
]);
|
|
16209
|
+
function validateKnownFlags(flags) {
|
|
16210
|
+
for (const key of Object.keys(flags)) {
|
|
16211
|
+
if (!KNOWN_BOOLEAN_FLAGS.has(key) && !KNOWN_VALUE_FLAGS.has(key)) {
|
|
16212
|
+
return key;
|
|
16213
|
+
}
|
|
16214
|
+
}
|
|
16215
|
+
return void 0;
|
|
16216
|
+
}
|
|
14954
16217
|
function parseArgs(argv) {
|
|
14955
16218
|
const positional = [];
|
|
14956
16219
|
const flags = {};
|
|
@@ -16097,6 +17360,10 @@ var PersistedUsage = z4.object({
|
|
|
16097
17360
|
costCurrency: z4.string().optional(),
|
|
16098
17361
|
cumulativeCost: z4.number().optional()
|
|
16099
17362
|
});
|
|
17363
|
+
var PersistedOriginatingClient = z4.object({
|
|
17364
|
+
name: z4.string(),
|
|
17365
|
+
version: z4.string().optional()
|
|
17366
|
+
});
|
|
16100
17367
|
var SessionRecord = z4.object({
|
|
16101
17368
|
version: z4.literal(1),
|
|
16102
17369
|
sessionId: z4.string(),
|
|
@@ -16147,6 +17414,10 @@ var SessionRecord = z4.object({
|
|
|
16147
17414
|
// Set when this session was spawned as a child by a transformer via
|
|
16148
17415
|
// hydra-acp/spawn_child_session. Points to the spawning session's id.
|
|
16149
17416
|
parentSessionId: z4.string().optional(),
|
|
17417
|
+
// clientInfo from the process that issued session/new. Picker and
|
|
17418
|
+
// `sessions list` use this to hide cat-style ancillary sessions by
|
|
17419
|
+
// default; carried in meta.json so cold sessions filter the same way.
|
|
17420
|
+
originatingClient: PersistedOriginatingClient.optional(),
|
|
16150
17421
|
createdAt: z4.string(),
|
|
16151
17422
|
updatedAt: z4.string()
|
|
16152
17423
|
});
|
|
@@ -16269,6 +17540,7 @@ function recordFromMemorySession(args) {
|
|
|
16269
17540
|
agentModels: args.agentModels,
|
|
16270
17541
|
pendingHistorySync: args.pendingHistorySync,
|
|
16271
17542
|
parentSessionId: args.parentSessionId,
|
|
17543
|
+
originatingClient: args.originatingClient,
|
|
16272
17544
|
createdAt: args.createdAt ?? now,
|
|
16273
17545
|
updatedAt: args.updatedAt ?? now
|
|
16274
17546
|
};
|
|
@@ -16453,6 +17725,7 @@ var SessionManager = class {
|
|
|
16453
17725
|
this.defaultTransformers = options.defaultTransformers ?? [];
|
|
16454
17726
|
this.logger = options.logger;
|
|
16455
17727
|
this.npmRegistry = options.npmRegistry;
|
|
17728
|
+
this.extensionCommands = options.extensionCommands;
|
|
16456
17729
|
}
|
|
16457
17730
|
registry;
|
|
16458
17731
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -16471,6 +17744,7 @@ var SessionManager = class {
|
|
|
16471
17744
|
metaWriteQueues = /* @__PURE__ */ new Map();
|
|
16472
17745
|
logger;
|
|
16473
17746
|
npmRegistry;
|
|
17747
|
+
extensionCommands;
|
|
16474
17748
|
async create(params) {
|
|
16475
17749
|
const fresh = await this.bootstrapAgent({
|
|
16476
17750
|
agentId: params.agentId,
|
|
@@ -16524,7 +17798,9 @@ var SessionManager = class {
|
|
|
16524
17798
|
agentModes: fresh.initialModes,
|
|
16525
17799
|
agentModels: fresh.initialModels,
|
|
16526
17800
|
transformChain: params.transformChain,
|
|
16527
|
-
parentSessionId: params.parentSessionId
|
|
17801
|
+
parentSessionId: params.parentSessionId,
|
|
17802
|
+
originatingClient: params.originatingClient,
|
|
17803
|
+
extensionCommands: this.extensionCommands
|
|
16528
17804
|
});
|
|
16529
17805
|
await this.attachManagerHooks(session);
|
|
16530
17806
|
return session;
|
|
@@ -16595,12 +17871,14 @@ var SessionManager = class {
|
|
|
16595
17871
|
}
|
|
16596
17872
|
let loadResult;
|
|
16597
17873
|
try {
|
|
17874
|
+
const loadMeta = buildSessionLoadMeta(params.agentId, params.currentModel);
|
|
16598
17875
|
loadResult = await agent.connection.request(
|
|
16599
17876
|
"session/load",
|
|
16600
17877
|
{
|
|
16601
17878
|
sessionId: params.upstreamSessionId,
|
|
16602
17879
|
cwd: params.cwd,
|
|
16603
|
-
mcpServers: []
|
|
17880
|
+
mcpServers: [],
|
|
17881
|
+
...loadMeta && { _meta: loadMeta }
|
|
16604
17882
|
}
|
|
16605
17883
|
);
|
|
16606
17884
|
} catch (err) {
|
|
@@ -16616,7 +17894,10 @@ var SessionManager = class {
|
|
|
16616
17894
|
() => void 0
|
|
16617
17895
|
);
|
|
16618
17896
|
} else {
|
|
16619
|
-
agent.connection.drainBuffered("session/update");
|
|
17897
|
+
const drain1Count = agent.connection.drainBuffered("session/update");
|
|
17898
|
+
this.logger?.info(
|
|
17899
|
+
`resurrect: drain1 dropped ${drain1Count} buffered session/update(s) for sessionId=${params.hydraSessionId}`
|
|
17900
|
+
);
|
|
16620
17901
|
}
|
|
16621
17902
|
const agentReportedMode = extractInitialCurrentMode(loadResult ?? {});
|
|
16622
17903
|
const advertisedModes = params.agentModes ?? nonEmptyOrUndefined(extractInitialModes(loadResult ?? {}));
|
|
@@ -16634,6 +17915,30 @@ var SessionManager = class {
|
|
|
16634
17915
|
this.logger?.info(
|
|
16635
17916
|
`resurrect: effectiveMode=${JSON.stringify(effectiveMode)} for sessionId=${params.hydraSessionId}`
|
|
16636
17917
|
);
|
|
17918
|
+
const agentReportedModel = extractInitialModel(loadResult ?? {});
|
|
17919
|
+
const advertisedModels = nonEmptyOrUndefined(extractInitialModels(loadResult ?? {})) ?? params.agentModels;
|
|
17920
|
+
this.logger?.info(
|
|
17921
|
+
`resurrect: sessionId=${params.hydraSessionId} persistedModel=${JSON.stringify(params.currentModel)} agentReportedModel=${JSON.stringify(agentReportedModel)} advertisedModels=${JSON.stringify(advertisedModels?.map((m) => m.modelId))}`
|
|
17922
|
+
);
|
|
17923
|
+
if (params.pendingHistorySync !== true) {
|
|
17924
|
+
const drain2Count = agent.connection.drainBuffered("session/update");
|
|
17925
|
+
this.logger?.info(
|
|
17926
|
+
`resurrect: drain2 (post-mode-restore) dropped ${drain2Count} buffered session/update(s) for sessionId=${params.hydraSessionId}`
|
|
17927
|
+
);
|
|
17928
|
+
}
|
|
17929
|
+
const effectiveModel = await restoreCurrentModel({
|
|
17930
|
+
agent,
|
|
17931
|
+
upstreamSessionId: params.upstreamSessionId,
|
|
17932
|
+
persistedModel: params.currentModel,
|
|
17933
|
+
agentReportedModel,
|
|
17934
|
+
logger: this.logger
|
|
17935
|
+
});
|
|
17936
|
+
if (params.pendingHistorySync !== true) {
|
|
17937
|
+
const drain3Count = agent.connection.drainBuffered("session/update");
|
|
17938
|
+
this.logger?.info(
|
|
17939
|
+
`resurrect: drain3 (post-model-restore) dropped ${drain3Count} buffered session/update(s) for sessionId=${params.hydraSessionId}`
|
|
17940
|
+
);
|
|
17941
|
+
}
|
|
16637
17942
|
const session = new Session({
|
|
16638
17943
|
sessionId: params.hydraSessionId,
|
|
16639
17944
|
cwd: params.cwd,
|
|
@@ -16650,11 +17955,7 @@ var SessionManager = class {
|
|
|
16650
17955
|
listSessions: () => this.list(),
|
|
16651
17956
|
historyStore: this.histories,
|
|
16652
17957
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
16653
|
-
|
|
16654
|
-
// we never captured one (e.g. old opencode sessions on disk before
|
|
16655
|
-
// this fix), fall back to the model the agent ships in its
|
|
16656
|
-
// session/load response body.
|
|
16657
|
-
currentModel: params.currentModel ?? extractInitialModel(loadResult ?? {}),
|
|
17958
|
+
currentModel: effectiveModel,
|
|
16658
17959
|
currentMode: effectiveMode,
|
|
16659
17960
|
currentUsage: params.currentUsage,
|
|
16660
17961
|
agentCommands: params.agentCommands,
|
|
@@ -16663,13 +17964,15 @@ var SessionManager = class {
|
|
|
16663
17964
|
// snapshot — the proxy's available models can change between daemon
|
|
16664
17965
|
// restarts (quota resets, rollouts), so meta.json is intentionally
|
|
16665
17966
|
// treated as a cold fallback here, not the authoritative source.
|
|
16666
|
-
agentModels:
|
|
17967
|
+
agentModels: advertisedModels,
|
|
16667
17968
|
// Only gate the first-prompt title heuristic when we actually have
|
|
16668
17969
|
// a title to preserve. A title-less session (lost to a write race
|
|
16669
17970
|
// or never seeded) should re-derive from the next prompt rather
|
|
16670
17971
|
// than stay stuck.
|
|
16671
17972
|
firstPromptSeeded: !!params.title,
|
|
16672
|
-
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0
|
|
17973
|
+
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
17974
|
+
originatingClient: params.originatingClient,
|
|
17975
|
+
extensionCommands: this.extensionCommands
|
|
16673
17976
|
});
|
|
16674
17977
|
await this.attachManagerHooks(session);
|
|
16675
17978
|
return session;
|
|
@@ -16688,7 +17991,11 @@ var SessionManager = class {
|
|
|
16688
17991
|
cwd,
|
|
16689
17992
|
agentArgs: params.agentArgs,
|
|
16690
17993
|
mcpServers: [],
|
|
16691
|
-
onInstallProgress: params.onInstallProgress
|
|
17994
|
+
onInstallProgress: params.onInstallProgress,
|
|
17995
|
+
// Pass the persisted model so bootstrapAgent calls session/set_model
|
|
17996
|
+
// during session/new — the only context where the agent reliably
|
|
17997
|
+
// honours the switch.
|
|
17998
|
+
model: params.currentModel
|
|
16692
17999
|
});
|
|
16693
18000
|
const advertisedModes = params.agentModes ?? fresh.initialModes;
|
|
16694
18001
|
const effectiveMode = await restoreCurrentMode({
|
|
@@ -16699,6 +18006,15 @@ var SessionManager = class {
|
|
|
16699
18006
|
advertisedModes,
|
|
16700
18007
|
logger: this.logger
|
|
16701
18008
|
});
|
|
18009
|
+
const advertisedModels = params.agentModels ?? fresh.initialModels;
|
|
18010
|
+
const effectiveModel = await restoreCurrentModel({
|
|
18011
|
+
agent: fresh.agent,
|
|
18012
|
+
upstreamSessionId: fresh.upstreamSessionId,
|
|
18013
|
+
persistedModel: params.currentModel,
|
|
18014
|
+
agentReportedModel: fresh.initialModel,
|
|
18015
|
+
logger: this.logger
|
|
18016
|
+
});
|
|
18017
|
+
fresh.agent.connection.drainBuffered("session/update");
|
|
16702
18018
|
const session = new Session({
|
|
16703
18019
|
sessionId: params.hydraSessionId,
|
|
16704
18020
|
cwd,
|
|
@@ -16715,16 +18031,16 @@ var SessionManager = class {
|
|
|
16715
18031
|
listSessions: () => this.list(),
|
|
16716
18032
|
historyStore: this.histories,
|
|
16717
18033
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
16718
|
-
|
|
16719
|
-
// fall back to whatever the agent ships in its session/new response.
|
|
16720
|
-
currentModel: params.currentModel ?? fresh.initialModel,
|
|
18034
|
+
currentModel: effectiveModel,
|
|
16721
18035
|
currentMode: effectiveMode,
|
|
16722
18036
|
currentUsage: params.currentUsage,
|
|
16723
18037
|
agentCommands: params.agentCommands,
|
|
16724
18038
|
agentModes: advertisedModes,
|
|
16725
|
-
agentModels:
|
|
18039
|
+
agentModels: advertisedModels,
|
|
16726
18040
|
firstPromptSeeded: !!params.title,
|
|
16727
|
-
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0
|
|
18041
|
+
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
18042
|
+
originatingClient: params.originatingClient,
|
|
18043
|
+
extensionCommands: this.extensionCommands
|
|
16728
18044
|
});
|
|
16729
18045
|
await this.attachManagerHooks(session);
|
|
16730
18046
|
void session.seedFromImport().catch(() => void 0);
|
|
@@ -17073,7 +18389,8 @@ var SessionManager = class {
|
|
|
17073
18389
|
agentModes: record.agentModes,
|
|
17074
18390
|
agentModels: record.agentModels,
|
|
17075
18391
|
createdAt: record.createdAt,
|
|
17076
|
-
pendingHistorySync: record.pendingHistorySync
|
|
18392
|
+
pendingHistorySync: record.pendingHistorySync,
|
|
18393
|
+
originatingClient: record.originatingClient
|
|
17077
18394
|
};
|
|
17078
18395
|
}
|
|
17079
18396
|
async clearPendingHistorySync(sessionId) {
|
|
@@ -17174,6 +18491,7 @@ var SessionManager = class {
|
|
|
17174
18491
|
currentModel: session.currentModel,
|
|
17175
18492
|
currentUsage: session.currentUsage,
|
|
17176
18493
|
parentSessionId: session.parentSessionId,
|
|
18494
|
+
originatingClient: session.originatingClient,
|
|
17177
18495
|
updatedAt: used,
|
|
17178
18496
|
attachedClients: session.attachedCount,
|
|
17179
18497
|
status: "live",
|
|
@@ -17203,6 +18521,7 @@ var SessionManager = class {
|
|
|
17203
18521
|
importedFromMachine: r.importedFromMachine,
|
|
17204
18522
|
importedFromUpstreamSessionId: r.importedFromUpstreamSessionId,
|
|
17205
18523
|
parentSessionId: r.parentSessionId,
|
|
18524
|
+
originatingClient: r.originatingClient,
|
|
17206
18525
|
updatedAt: used,
|
|
17207
18526
|
attachedClients: 0,
|
|
17208
18527
|
status: "cold",
|
|
@@ -17548,6 +18867,7 @@ function mergeForPersistence(session, existing) {
|
|
|
17548
18867
|
agentModes,
|
|
17549
18868
|
agentModels,
|
|
17550
18869
|
parentSessionId: session.parentSessionId ?? existing?.parentSessionId,
|
|
18870
|
+
originatingClient: session.originatingClient ?? existing?.originatingClient,
|
|
17551
18871
|
createdAt: existing?.createdAt ?? new Date(session.createdAt).toISOString()
|
|
17552
18872
|
});
|
|
17553
18873
|
}
|
|
@@ -17576,6 +18896,13 @@ function usageSnapshotToPersisted(usage) {
|
|
|
17576
18896
|
function persistedUsageToSnapshot(usage) {
|
|
17577
18897
|
return usage ? { ...usage } : void 0;
|
|
17578
18898
|
}
|
|
18899
|
+
function buildSessionLoadMeta(agentId, model) {
|
|
18900
|
+
if (!model)
|
|
18901
|
+
return void 0;
|
|
18902
|
+
if (agentId === "claude-acp")
|
|
18903
|
+
return { claudeCode: { options: { model } } };
|
|
18904
|
+
return void 0;
|
|
18905
|
+
}
|
|
17579
18906
|
function extractInitialModel(result) {
|
|
17580
18907
|
const direct = asString(result.currentModelId) ?? asString(result.currentModel) ?? asString(result.modelId) ?? asString(result.model);
|
|
17581
18908
|
if (direct) {
|
|
@@ -17755,6 +19082,33 @@ async function restoreCurrentMode(opts) {
|
|
|
17755
19082
|
return agentReportedMode;
|
|
17756
19083
|
}
|
|
17757
19084
|
}
|
|
19085
|
+
async function restoreCurrentModel(opts) {
|
|
19086
|
+
const { agent, upstreamSessionId, persistedModel, agentReportedModel, logger } = opts;
|
|
19087
|
+
if (!persistedModel) {
|
|
19088
|
+
return agentReportedModel;
|
|
19089
|
+
}
|
|
19090
|
+
if (persistedModel === agentReportedModel) {
|
|
19091
|
+
return persistedModel;
|
|
19092
|
+
}
|
|
19093
|
+
try {
|
|
19094
|
+
logger?.info(
|
|
19095
|
+
`resurrect: pushing persisted modelId=${JSON.stringify(persistedModel)} to agent (agentReported=${JSON.stringify(agentReportedModel)})`
|
|
19096
|
+
);
|
|
19097
|
+
await agent.connection.request("session/set_model", {
|
|
19098
|
+
sessionId: upstreamSessionId,
|
|
19099
|
+
modelId: persistedModel
|
|
19100
|
+
});
|
|
19101
|
+
logger?.info(
|
|
19102
|
+
`resurrect: session/set_model accepted, effectiveModel=${JSON.stringify(persistedModel)}`
|
|
19103
|
+
);
|
|
19104
|
+
return persistedModel;
|
|
19105
|
+
} catch (err) {
|
|
19106
|
+
logger?.warn(
|
|
19107
|
+
`resurrect: session/set_model rejected by agent for modelId=${JSON.stringify(persistedModel)} (${err.message}); session will use ${JSON.stringify(agentReportedModel)}`
|
|
19108
|
+
);
|
|
19109
|
+
return agentReportedModel;
|
|
19110
|
+
}
|
|
19111
|
+
}
|
|
17758
19112
|
function parseModesList(list) {
|
|
17759
19113
|
if (!Array.isArray(list)) {
|
|
17760
19114
|
return [];
|
|
@@ -18710,6 +20064,55 @@ function withCode3(err, code) {
|
|
|
18710
20064
|
return err;
|
|
18711
20065
|
}
|
|
18712
20066
|
|
|
20067
|
+
// src/core/extension-commands.ts
|
|
20068
|
+
var ExtensionCommandRegistry = class {
|
|
20069
|
+
entries = /* @__PURE__ */ new Map();
|
|
20070
|
+
changeHandlers = [];
|
|
20071
|
+
register(name, connection, commands) {
|
|
20072
|
+
this.entries.set(name, { connection, commands: [...commands] });
|
|
20073
|
+
this.fireChanged();
|
|
20074
|
+
}
|
|
20075
|
+
clear(name) {
|
|
20076
|
+
if (this.entries.delete(name)) {
|
|
20077
|
+
this.fireChanged();
|
|
20078
|
+
}
|
|
20079
|
+
}
|
|
20080
|
+
get(name) {
|
|
20081
|
+
return this.entries.get(name);
|
|
20082
|
+
}
|
|
20083
|
+
has(name) {
|
|
20084
|
+
return this.entries.has(name);
|
|
20085
|
+
}
|
|
20086
|
+
// Snapshot of every (name, command) pair. Order is stable per-name
|
|
20087
|
+
// (insertion order of the map and the original commands list).
|
|
20088
|
+
list() {
|
|
20089
|
+
const out = [];
|
|
20090
|
+
for (const [name, entry] of this.entries) {
|
|
20091
|
+
for (const command of entry.commands) {
|
|
20092
|
+
out.push({ name, command });
|
|
20093
|
+
}
|
|
20094
|
+
}
|
|
20095
|
+
return out;
|
|
20096
|
+
}
|
|
20097
|
+
onChange(handler) {
|
|
20098
|
+
this.changeHandlers.push(handler);
|
|
20099
|
+
return () => {
|
|
20100
|
+
const i = this.changeHandlers.indexOf(handler);
|
|
20101
|
+
if (i >= 0) {
|
|
20102
|
+
this.changeHandlers.splice(i, 1);
|
|
20103
|
+
}
|
|
20104
|
+
};
|
|
20105
|
+
}
|
|
20106
|
+
fireChanged() {
|
|
20107
|
+
for (const h of this.changeHandlers) {
|
|
20108
|
+
try {
|
|
20109
|
+
h();
|
|
20110
|
+
} catch {
|
|
20111
|
+
}
|
|
20112
|
+
}
|
|
20113
|
+
}
|
|
20114
|
+
};
|
|
20115
|
+
|
|
18713
20116
|
// src/daemon/server.ts
|
|
18714
20117
|
init_paths();
|
|
18715
20118
|
|
|
@@ -19443,37 +20846,410 @@ function isVisible(event) {
|
|
|
19443
20846
|
return true;
|
|
19444
20847
|
}
|
|
19445
20848
|
}
|
|
19446
|
-
function formatToolLine(state) {
|
|
19447
|
-
const status = state.status;
|
|
19448
|
-
const suffix = status === "completed" || status === void 0 ? "" : ` _(${status})_`;
|
|
19449
|
-
return `${escapeInline(state.title)}${suffix}`;
|
|
20849
|
+
function formatToolLine(state) {
|
|
20850
|
+
const status = state.status;
|
|
20851
|
+
const suffix = status === "completed" || status === void 0 ? "" : ` _(${status})_`;
|
|
20852
|
+
return `${escapeInline(state.title)}${suffix}`;
|
|
20853
|
+
}
|
|
20854
|
+
function statusGlyph(status) {
|
|
20855
|
+
switch (status) {
|
|
20856
|
+
case "completed":
|
|
20857
|
+
return "\u2713";
|
|
20858
|
+
case "failed":
|
|
20859
|
+
return "\u2717";
|
|
20860
|
+
case "cancelled":
|
|
20861
|
+
case "rejected":
|
|
20862
|
+
return "\u2298";
|
|
20863
|
+
case "in_progress":
|
|
20864
|
+
return "\u21BB";
|
|
20865
|
+
default:
|
|
20866
|
+
return "\xB7";
|
|
20867
|
+
}
|
|
20868
|
+
}
|
|
20869
|
+
function escapeInline(text) {
|
|
20870
|
+
return text.replace(/</g, "<").replace(/>/g, ">");
|
|
20871
|
+
}
|
|
20872
|
+
function formatNumber(n) {
|
|
20873
|
+
return n.toLocaleString("en-US");
|
|
20874
|
+
}
|
|
20875
|
+
|
|
20876
|
+
// src/daemon/routes/sessions.ts
|
|
20877
|
+
init_types();
|
|
20878
|
+
init_hydra_version();
|
|
20879
|
+
init_remote_url();
|
|
20880
|
+
|
|
20881
|
+
// src/core/history-search.ts
|
|
20882
|
+
init_render_update();
|
|
20883
|
+
function parseQuery(raw) {
|
|
20884
|
+
const trimmed = raw.trim();
|
|
20885
|
+
if (trimmed.length === 0) {
|
|
20886
|
+
return { operator: "OR", terms: [] };
|
|
20887
|
+
}
|
|
20888
|
+
const tokenRe = /\w+:"[^"]*"|"[^"]*"|\S+/g;
|
|
20889
|
+
const tokens = [];
|
|
20890
|
+
let m;
|
|
20891
|
+
while ((m = tokenRe.exec(trimmed)) !== null) {
|
|
20892
|
+
tokens.push(m[0]);
|
|
20893
|
+
}
|
|
20894
|
+
let operator = "OR";
|
|
20895
|
+
let sawAnd = false;
|
|
20896
|
+
let sawOr = false;
|
|
20897
|
+
const termTokens = [];
|
|
20898
|
+
for (const tok of tokens) {
|
|
20899
|
+
const upper = tok.toUpperCase();
|
|
20900
|
+
if (upper === "AND") {
|
|
20901
|
+
sawAnd = true;
|
|
20902
|
+
} else if (upper === "OR") {
|
|
20903
|
+
sawOr = true;
|
|
20904
|
+
} else {
|
|
20905
|
+
termTokens.push(tok);
|
|
20906
|
+
}
|
|
20907
|
+
}
|
|
20908
|
+
if (sawAnd) {
|
|
20909
|
+
operator = "AND";
|
|
20910
|
+
} else if (sawOr) {
|
|
20911
|
+
operator = "OR";
|
|
20912
|
+
}
|
|
20913
|
+
const terms = termTokens.map((tok) => parseTermToken(tok)).filter((t) => t.term.length > 0);
|
|
20914
|
+
return { operator, terms };
|
|
20915
|
+
}
|
|
20916
|
+
function parseTermToken(tok) {
|
|
20917
|
+
const pq = /^(\w+):"([^"]*)"$/.exec(tok);
|
|
20918
|
+
if (pq) {
|
|
20919
|
+
return { scope: prefixToScope(pq[1]), term: pq[2] };
|
|
20920
|
+
}
|
|
20921
|
+
const q = /^"([^"]*)"$/.exec(tok);
|
|
20922
|
+
if (q) {
|
|
20923
|
+
return { scope: "all", term: q[1] };
|
|
20924
|
+
}
|
|
20925
|
+
const pb = /^(prompt|response|tool):([\s\S]*)$/i.exec(tok);
|
|
20926
|
+
if (pb) {
|
|
20927
|
+
return { scope: prefixToScope(pb[1]), term: pb[2].trim() };
|
|
20928
|
+
}
|
|
20929
|
+
return { scope: "all", term: tok.trim() };
|
|
20930
|
+
}
|
|
20931
|
+
function prefixToScope(prefix) {
|
|
20932
|
+
switch (prefix.toLowerCase()) {
|
|
20933
|
+
case "prompt":
|
|
20934
|
+
return "user";
|
|
20935
|
+
case "response":
|
|
20936
|
+
return "agent";
|
|
20937
|
+
case "tool":
|
|
20938
|
+
return "tool";
|
|
20939
|
+
default:
|
|
20940
|
+
return "all";
|
|
20941
|
+
}
|
|
20942
|
+
}
|
|
20943
|
+
function scopeMatchesKind(scope, kind) {
|
|
20944
|
+
if (scope === "all") {
|
|
20945
|
+
return true;
|
|
20946
|
+
}
|
|
20947
|
+
if (scope === "user") {
|
|
20948
|
+
return kind === "user";
|
|
20949
|
+
}
|
|
20950
|
+
if (scope === "agent") {
|
|
20951
|
+
return kind === "agent" || kind === "thought";
|
|
20952
|
+
}
|
|
20953
|
+
return kind === "tool" || kind === "tool-input";
|
|
20954
|
+
}
|
|
20955
|
+
var DEFAULT_MAX_SNIPPETS_PER_SESSION = 5;
|
|
20956
|
+
var DEFAULT_MAX_SESSIONS = 200;
|
|
20957
|
+
var SNIPPET_SIDE = 30;
|
|
20958
|
+
async function searchHistories(manager, query, opts = {}) {
|
|
20959
|
+
const parsed = parseQuery(query);
|
|
20960
|
+
if (parsed.terms.length === 0) {
|
|
20961
|
+
return { query, truncated: false, results: [] };
|
|
20962
|
+
}
|
|
20963
|
+
const maxPerSession = opts.maxSnippetsPerSession ?? DEFAULT_MAX_SNIPPETS_PER_SESSION;
|
|
20964
|
+
const maxSessions = opts.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
20965
|
+
const allow = opts.sessionIds ? new Set(opts.sessionIds) : null;
|
|
20966
|
+
const all = await manager.list();
|
|
20967
|
+
const candidates = allow ? all.filter((s) => allow.has(s.sessionId)) : all;
|
|
20968
|
+
const results = [];
|
|
20969
|
+
let truncated = false;
|
|
20970
|
+
for (const candidate of candidates) {
|
|
20971
|
+
if (results.length >= maxSessions) {
|
|
20972
|
+
truncated = true;
|
|
20973
|
+
break;
|
|
20974
|
+
}
|
|
20975
|
+
const entries = await manager.loadHistory(candidate.sessionId).catch(
|
|
20976
|
+
() => []
|
|
20977
|
+
);
|
|
20978
|
+
const found = scanSessionEntries(entries, parsed, maxPerSession);
|
|
20979
|
+
if (found.snippets.length === 0) {
|
|
20980
|
+
continue;
|
|
20981
|
+
}
|
|
20982
|
+
const hit = {
|
|
20983
|
+
sessionId: candidate.sessionId,
|
|
20984
|
+
cwd: candidate.cwd,
|
|
20985
|
+
status: candidate.status,
|
|
20986
|
+
updatedAt: candidate.updatedAt,
|
|
20987
|
+
totalMatches: found.totalMatches,
|
|
20988
|
+
snippets: found.snippets
|
|
20989
|
+
};
|
|
20990
|
+
if (candidate.title !== void 0) {
|
|
20991
|
+
hit.title = candidate.title;
|
|
20992
|
+
}
|
|
20993
|
+
results.push(hit);
|
|
20994
|
+
}
|
|
20995
|
+
return { query, truncated, results };
|
|
20996
|
+
}
|
|
20997
|
+
function scanSessionEntries(entries, query, maxSnippets) {
|
|
20998
|
+
if (query.terms.length === 0) {
|
|
20999
|
+
return { totalMatches: 0, snippets: [] };
|
|
21000
|
+
}
|
|
21001
|
+
let totalMatches = 0;
|
|
21002
|
+
const snippets = [];
|
|
21003
|
+
for (const { scope, term } of query.terms) {
|
|
21004
|
+
const result = scanForTerm(entries, term, scope, maxSnippets - snippets.length);
|
|
21005
|
+
if (query.operator === "AND" && result.totalMatches === 0) {
|
|
21006
|
+
return { totalMatches: 0, snippets: [] };
|
|
21007
|
+
}
|
|
21008
|
+
totalMatches += result.totalMatches;
|
|
21009
|
+
snippets.push(...result.snippets);
|
|
21010
|
+
}
|
|
21011
|
+
return { totalMatches, snippets };
|
|
21012
|
+
}
|
|
21013
|
+
function scanForTerm(entries, term, scope, snippetBudget) {
|
|
21014
|
+
const needle = term.toLowerCase();
|
|
21015
|
+
let totalMatches = 0;
|
|
21016
|
+
const snippets = [];
|
|
21017
|
+
for (const entry of entries) {
|
|
21018
|
+
const fragments = extractSearchableFragments(entry).filter(
|
|
21019
|
+
(f) => scopeMatchesKind(scope, f.kind)
|
|
21020
|
+
);
|
|
21021
|
+
for (const frag of fragments) {
|
|
21022
|
+
const hay = frag.text.toLowerCase();
|
|
21023
|
+
let idx = hay.indexOf(needle);
|
|
21024
|
+
if (idx === -1) {
|
|
21025
|
+
continue;
|
|
21026
|
+
}
|
|
21027
|
+
let occurrences = 0;
|
|
21028
|
+
while (idx !== -1) {
|
|
21029
|
+
occurrences++;
|
|
21030
|
+
idx = hay.indexOf(needle, idx + needle.length);
|
|
21031
|
+
}
|
|
21032
|
+
totalMatches += occurrences;
|
|
21033
|
+
if (snippets.length < snippetBudget) {
|
|
21034
|
+
const first = hay.indexOf(needle);
|
|
21035
|
+
const snippet = {
|
|
21036
|
+
kind: frag.kind,
|
|
21037
|
+
text: buildSnippet(frag.text, first, needle.length),
|
|
21038
|
+
recordedAt: entry.recordedAt
|
|
21039
|
+
};
|
|
21040
|
+
if (frag.toolName !== void 0) {
|
|
21041
|
+
snippet.toolName = frag.toolName;
|
|
21042
|
+
}
|
|
21043
|
+
snippets.push(snippet);
|
|
21044
|
+
}
|
|
21045
|
+
}
|
|
21046
|
+
}
|
|
21047
|
+
return { totalMatches, snippets };
|
|
21048
|
+
}
|
|
21049
|
+
function extractSearchableFragments(entry) {
|
|
21050
|
+
if (entry.method !== "session/update") {
|
|
21051
|
+
return [];
|
|
21052
|
+
}
|
|
21053
|
+
const params = entry.params;
|
|
21054
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
21055
|
+
return [];
|
|
21056
|
+
}
|
|
21057
|
+
const update = params.update;
|
|
21058
|
+
if (!update || typeof update !== "object" || Array.isArray(update)) {
|
|
21059
|
+
return [];
|
|
21060
|
+
}
|
|
21061
|
+
const u = update;
|
|
21062
|
+
const tag = typeof u.sessionUpdate === "string" ? u.sessionUpdate : u.kind;
|
|
21063
|
+
if (typeof tag !== "string") {
|
|
21064
|
+
return [];
|
|
21065
|
+
}
|
|
21066
|
+
switch (tag) {
|
|
21067
|
+
case "agent_message_chunk": {
|
|
21068
|
+
const text = readContentText(u.content);
|
|
21069
|
+
return text ? [{ kind: "agent", text }] : [];
|
|
21070
|
+
}
|
|
21071
|
+
case "agent_thought":
|
|
21072
|
+
case "agent_thought_chunk": {
|
|
21073
|
+
const text = typeof u.text === "string" ? sanitizeWireText(u.text) : readContentText(u.content);
|
|
21074
|
+
return text ? [{ kind: "thought", text }] : [];
|
|
21075
|
+
}
|
|
21076
|
+
case "user_message_chunk": {
|
|
21077
|
+
if (isCompatPromptReceived(u)) {
|
|
21078
|
+
return [];
|
|
21079
|
+
}
|
|
21080
|
+
const text = readContentText(u.content);
|
|
21081
|
+
return text ? [{ kind: "user", text }] : [];
|
|
21082
|
+
}
|
|
21083
|
+
case "prompt_received": {
|
|
21084
|
+
const text = readPromptText(u.prompt);
|
|
21085
|
+
return text ? [{ kind: "user", text }] : [];
|
|
21086
|
+
}
|
|
21087
|
+
case "tool_call":
|
|
21088
|
+
case "tool_call_update": {
|
|
21089
|
+
return extractToolFragments(u);
|
|
21090
|
+
}
|
|
21091
|
+
default:
|
|
21092
|
+
return [];
|
|
21093
|
+
}
|
|
21094
|
+
}
|
|
21095
|
+
function extractToolFragments(u) {
|
|
21096
|
+
const toolName = readString2(u, "name");
|
|
21097
|
+
const title = readString2(u, "title");
|
|
21098
|
+
const out = [];
|
|
21099
|
+
if (title !== void 0) {
|
|
21100
|
+
const sanitized = sanitizeSingleLine(title);
|
|
21101
|
+
if (sanitized.length > 0) {
|
|
21102
|
+
const frag = { kind: "tool", text: sanitized };
|
|
21103
|
+
if (toolName !== void 0) {
|
|
21104
|
+
frag.toolName = toolName;
|
|
21105
|
+
}
|
|
21106
|
+
out.push(frag);
|
|
21107
|
+
}
|
|
21108
|
+
}
|
|
21109
|
+
if (toolName !== void 0 && toolName !== title) {
|
|
21110
|
+
const sanitized = sanitizeSingleLine(toolName);
|
|
21111
|
+
if (sanitized.length > 0) {
|
|
21112
|
+
out.push({ kind: "tool", toolName, text: sanitized });
|
|
21113
|
+
}
|
|
21114
|
+
}
|
|
21115
|
+
const rawInput = u.rawInput;
|
|
21116
|
+
if (rawInput && typeof rawInput === "object") {
|
|
21117
|
+
const serialized = safeStringify(rawInput);
|
|
21118
|
+
if (serialized.length > 0) {
|
|
21119
|
+
const frag = {
|
|
21120
|
+
kind: "tool-input",
|
|
21121
|
+
text: sanitizeSingleLine(serialized)
|
|
21122
|
+
};
|
|
21123
|
+
if (toolName !== void 0) {
|
|
21124
|
+
frag.toolName = toolName;
|
|
21125
|
+
}
|
|
21126
|
+
out.push(frag);
|
|
21127
|
+
}
|
|
21128
|
+
}
|
|
21129
|
+
const locations = u.locations;
|
|
21130
|
+
if (Array.isArray(locations) && locations.length > 0) {
|
|
21131
|
+
const serialized = safeStringify(locations);
|
|
21132
|
+
if (serialized.length > 0) {
|
|
21133
|
+
const frag = {
|
|
21134
|
+
kind: "tool-input",
|
|
21135
|
+
text: sanitizeSingleLine(serialized)
|
|
21136
|
+
};
|
|
21137
|
+
if (toolName !== void 0) {
|
|
21138
|
+
frag.toolName = toolName;
|
|
21139
|
+
}
|
|
21140
|
+
out.push(frag);
|
|
21141
|
+
}
|
|
21142
|
+
}
|
|
21143
|
+
const errorText = extractToolErrorText(u);
|
|
21144
|
+
if (errorText !== null) {
|
|
21145
|
+
const frag = { kind: "tool", text: errorText };
|
|
21146
|
+
if (toolName !== void 0) {
|
|
21147
|
+
frag.toolName = toolName;
|
|
21148
|
+
}
|
|
21149
|
+
out.push(frag);
|
|
21150
|
+
}
|
|
21151
|
+
return out;
|
|
21152
|
+
}
|
|
21153
|
+
function extractToolErrorText(u) {
|
|
21154
|
+
const content = u.content;
|
|
21155
|
+
if (Array.isArray(content)) {
|
|
21156
|
+
for (const block of content) {
|
|
21157
|
+
if (!block || typeof block !== "object") {
|
|
21158
|
+
continue;
|
|
21159
|
+
}
|
|
21160
|
+
const b = block;
|
|
21161
|
+
const inner = b.content;
|
|
21162
|
+
if (!inner || typeof inner !== "object") {
|
|
21163
|
+
continue;
|
|
21164
|
+
}
|
|
21165
|
+
const i = inner;
|
|
21166
|
+
if (i.type === "text" && typeof i.text === "string") {
|
|
21167
|
+
const s = sanitizeSingleLine(i.text);
|
|
21168
|
+
if (s.length > 0) {
|
|
21169
|
+
return s;
|
|
21170
|
+
}
|
|
21171
|
+
}
|
|
21172
|
+
}
|
|
21173
|
+
}
|
|
21174
|
+
const rawOutput = u.rawOutput;
|
|
21175
|
+
if (rawOutput && typeof rawOutput === "object") {
|
|
21176
|
+
const err = rawOutput.error;
|
|
21177
|
+
if (typeof err === "string") {
|
|
21178
|
+
const s = sanitizeSingleLine(err);
|
|
21179
|
+
if (s.length > 0) {
|
|
21180
|
+
return s;
|
|
21181
|
+
}
|
|
21182
|
+
}
|
|
21183
|
+
}
|
|
21184
|
+
return null;
|
|
21185
|
+
}
|
|
21186
|
+
function isCompatPromptReceived(u) {
|
|
21187
|
+
const meta = u._meta;
|
|
21188
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
21189
|
+
return false;
|
|
21190
|
+
}
|
|
21191
|
+
const hydra = meta["hydra-acp"];
|
|
21192
|
+
if (!hydra || typeof hydra !== "object" || Array.isArray(hydra)) {
|
|
21193
|
+
return false;
|
|
21194
|
+
}
|
|
21195
|
+
return hydra.compatFor === "prompt_received";
|
|
21196
|
+
}
|
|
21197
|
+
function readContentText(content) {
|
|
21198
|
+
if (typeof content === "string") {
|
|
21199
|
+
return sanitizeWireText(content);
|
|
21200
|
+
}
|
|
21201
|
+
if (!content || typeof content !== "object" || Array.isArray(content)) {
|
|
21202
|
+
return "";
|
|
21203
|
+
}
|
|
21204
|
+
const c = content;
|
|
21205
|
+
if (typeof c.text === "string") {
|
|
21206
|
+
return sanitizeWireText(c.text);
|
|
21207
|
+
}
|
|
21208
|
+
return "";
|
|
21209
|
+
}
|
|
21210
|
+
function readPromptText(prompt) {
|
|
21211
|
+
if (!Array.isArray(prompt)) {
|
|
21212
|
+
return "";
|
|
21213
|
+
}
|
|
21214
|
+
const parts = [];
|
|
21215
|
+
for (const block of prompt) {
|
|
21216
|
+
const text = readContentText(block);
|
|
21217
|
+
if (text.length > 0) {
|
|
21218
|
+
parts.push(text);
|
|
21219
|
+
}
|
|
21220
|
+
}
|
|
21221
|
+
return parts.join("");
|
|
21222
|
+
}
|
|
21223
|
+
function readString2(u, key) {
|
|
21224
|
+
const v = u[key];
|
|
21225
|
+
return typeof v === "string" ? v : void 0;
|
|
19450
21226
|
}
|
|
19451
|
-
function
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
|
|
19455
|
-
|
|
19456
|
-
return "\u2717";
|
|
19457
|
-
case "cancelled":
|
|
19458
|
-
case "rejected":
|
|
19459
|
-
return "\u2298";
|
|
19460
|
-
case "in_progress":
|
|
19461
|
-
return "\u21BB";
|
|
19462
|
-
default:
|
|
19463
|
-
return "\xB7";
|
|
21227
|
+
function safeStringify(value) {
|
|
21228
|
+
try {
|
|
21229
|
+
return JSON.stringify(value);
|
|
21230
|
+
} catch {
|
|
21231
|
+
return "";
|
|
19464
21232
|
}
|
|
19465
21233
|
}
|
|
19466
|
-
function
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
21234
|
+
function buildSnippet(text, matchIdx, matchLen) {
|
|
21235
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
21236
|
+
if (flat.length === 0) {
|
|
21237
|
+
return "";
|
|
21238
|
+
}
|
|
21239
|
+
const flatLower = flat.toLowerCase();
|
|
21240
|
+
const needleSlice = text.slice(matchIdx, matchIdx + matchLen).toLowerCase().replace(/\s+/g, " ").trim();
|
|
21241
|
+
let pos = needleSlice.length > 0 ? flatLower.indexOf(needleSlice) : 0;
|
|
21242
|
+
if (pos === -1) {
|
|
21243
|
+
pos = 0;
|
|
21244
|
+
}
|
|
21245
|
+
const start = Math.max(0, pos - SNIPPET_SIDE);
|
|
21246
|
+
const end = Math.min(flat.length, pos + needleSlice.length + SNIPPET_SIDE);
|
|
21247
|
+
const head = start > 0 ? "\u2026" : "";
|
|
21248
|
+
const tail = end < flat.length ? "\u2026" : "";
|
|
21249
|
+
return `${head}${flat.slice(start, end)}${tail}`;
|
|
19471
21250
|
}
|
|
19472
21251
|
|
|
19473
21252
|
// src/daemon/routes/sessions.ts
|
|
19474
|
-
init_types();
|
|
19475
|
-
init_hydra_version();
|
|
19476
|
-
init_remote_url();
|
|
19477
21253
|
function resolveHydraHost(defaults) {
|
|
19478
21254
|
if (defaults.publicHost && defaults.publicHost.length > 0) {
|
|
19479
21255
|
return defaults.publicHost;
|
|
@@ -19489,6 +21265,17 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
19489
21265
|
const sessions = await manager.list({ cwd: query?.cwd });
|
|
19490
21266
|
return { sessions };
|
|
19491
21267
|
});
|
|
21268
|
+
app.get("/v1/sessions/search", async (request, reply) => {
|
|
21269
|
+
const query = request.query;
|
|
21270
|
+
const q = query?.q ?? "";
|
|
21271
|
+
if (q.trim().length === 0) {
|
|
21272
|
+
reply.code(400).send({ error: "q is required" });
|
|
21273
|
+
return reply;
|
|
21274
|
+
}
|
|
21275
|
+
const ids = query?.sessionIds ? query.sessionIds.split(",").filter((s) => s.length > 0) : void 0;
|
|
21276
|
+
const out = await searchHistories(manager, q, { sessionIds: ids });
|
|
21277
|
+
return out;
|
|
21278
|
+
});
|
|
19492
21279
|
app.post("/v1/sessions", async (request, reply) => {
|
|
19493
21280
|
const body = request.body ?? {};
|
|
19494
21281
|
const cwd = expandHome(body.cwd ?? defaults.cwd);
|
|
@@ -20229,6 +22016,7 @@ import { nanoid as nanoid2 } from "nanoid";
|
|
|
20229
22016
|
import * as os5 from "os";
|
|
20230
22017
|
import * as path13 from "path";
|
|
20231
22018
|
init_hydra_version();
|
|
22019
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
20232
22020
|
function registerAcpWsEndpoint(app, deps) {
|
|
20233
22021
|
app.get("/acp", { websocket: true }, async (socket, request) => {
|
|
20234
22022
|
const token = tokenFromUpgradeRequest({
|
|
@@ -20266,6 +22054,12 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20266
22054
|
};
|
|
20267
22055
|
connection.onRequest("initialize", async (raw) => {
|
|
20268
22056
|
const params = InitializeParams.parse(raw ?? {});
|
|
22057
|
+
if (params.clientInfo?.name) {
|
|
22058
|
+
state.clientInfo = {
|
|
22059
|
+
name: params.clientInfo.name,
|
|
22060
|
+
...params.clientInfo.version !== void 0 ? { version: params.clientInfo.version } : {}
|
|
22061
|
+
};
|
|
22062
|
+
}
|
|
20269
22063
|
const version = params.clientInfo?.version;
|
|
20270
22064
|
if (version && processIdentity) {
|
|
20271
22065
|
if (processIdentity.kind === "extension") {
|
|
@@ -20276,6 +22070,34 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20276
22070
|
}
|
|
20277
22071
|
return buildInitializeResult();
|
|
20278
22072
|
});
|
|
22073
|
+
if (processIdentity && deps.extensionCommands) {
|
|
22074
|
+
const registry = deps.extensionCommands;
|
|
22075
|
+
connection.onRequest("hydra-acp/register_commands", async (raw) => {
|
|
22076
|
+
const params = raw ?? {};
|
|
22077
|
+
const commands = Array.isArray(params.commands) ? params.commands.map((c) => {
|
|
22078
|
+
if (!c || typeof c !== "object") {
|
|
22079
|
+
return void 0;
|
|
22080
|
+
}
|
|
22081
|
+
const obj = c;
|
|
22082
|
+
if (typeof obj.verb !== "string" || obj.verb.length === 0) {
|
|
22083
|
+
return void 0;
|
|
22084
|
+
}
|
|
22085
|
+
const spec = { verb: obj.verb };
|
|
22086
|
+
if (typeof obj.argsHint === "string") {
|
|
22087
|
+
spec.argsHint = obj.argsHint;
|
|
22088
|
+
}
|
|
22089
|
+
if (typeof obj.description === "string") {
|
|
22090
|
+
spec.description = obj.description;
|
|
22091
|
+
}
|
|
22092
|
+
return spec;
|
|
22093
|
+
}).filter((s) => s !== void 0) : [];
|
|
22094
|
+
registry.register(processIdentity.name, connection, commands);
|
|
22095
|
+
return { ok: true, registered: commands.length };
|
|
22096
|
+
});
|
|
22097
|
+
connection.onClose(() => {
|
|
22098
|
+
registry.clear(processIdentity.name);
|
|
22099
|
+
});
|
|
22100
|
+
}
|
|
20279
22101
|
if (processIdentity?.kind === "transformer") {
|
|
20280
22102
|
connection.onRequest("transformer/initialize", async (raw) => {
|
|
20281
22103
|
const params = raw ?? {};
|
|
@@ -20417,16 +22239,50 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20417
22239
|
);
|
|
20418
22240
|
const transformerNames = Array.isArray(hydraMeta.transformers) && hydraMeta.transformers.every((t) => typeof t === "string") ? hydraMeta.transformers : deps.manager.defaultTransformers ?? [];
|
|
20419
22241
|
const transformChain = deps.transformers?.resolveChain(transformerNames) ?? [];
|
|
20420
|
-
|
|
20421
|
-
|
|
20422
|
-
|
|
20423
|
-
|
|
20424
|
-
|
|
20425
|
-
|
|
20426
|
-
|
|
20427
|
-
|
|
20428
|
-
|
|
20429
|
-
|
|
22242
|
+
let stdinToken;
|
|
22243
|
+
let stdinReservation;
|
|
22244
|
+
let augmentedMcpServers = params.mcpServers;
|
|
22245
|
+
if (hydraMeta.mcpStdin === true && deps.stdinMcpRegistry !== void 0 && deps.getDaemonOrigin !== void 0) {
|
|
22246
|
+
stdinToken = randomBytes3(32).toString("hex");
|
|
22247
|
+
stdinReservation = deps.stdinMcpRegistry.reserve(stdinToken);
|
|
22248
|
+
const url = `${deps.getDaemonOrigin()}/mcp/stdin`;
|
|
22249
|
+
const descriptor = {
|
|
22250
|
+
name: "hydra_stdin",
|
|
22251
|
+
type: "http",
|
|
22252
|
+
url,
|
|
22253
|
+
headers: [
|
|
22254
|
+
{ name: "Authorization", value: `Bearer ${stdinToken}` }
|
|
22255
|
+
]
|
|
22256
|
+
};
|
|
22257
|
+
augmentedMcpServers = [...params.mcpServers ?? [], descriptor];
|
|
22258
|
+
}
|
|
22259
|
+
let session;
|
|
22260
|
+
try {
|
|
22261
|
+
session = await deps.manager.create({
|
|
22262
|
+
cwd: params.cwd,
|
|
22263
|
+
agentId: params.agentId ?? deps.defaultAgent,
|
|
22264
|
+
mcpServers: augmentedMcpServers,
|
|
22265
|
+
title: hydraMeta.name,
|
|
22266
|
+
agentArgs: hydraMeta.agentArgs,
|
|
22267
|
+
model: hydraMeta.model,
|
|
22268
|
+
onInstallProgress: makeInstallProgressForwarder(connection),
|
|
22269
|
+
transformChain,
|
|
22270
|
+
originatingClient: state.clientInfo
|
|
22271
|
+
});
|
|
22272
|
+
} catch (err) {
|
|
22273
|
+
if (stdinReservation !== void 0) {
|
|
22274
|
+
stdinReservation.abandon(err instanceof Error ? err : void 0);
|
|
22275
|
+
}
|
|
22276
|
+
throw err;
|
|
22277
|
+
}
|
|
22278
|
+
if (stdinToken !== void 0 && stdinReservation !== void 0 && deps.stdinMcpRegistry !== void 0) {
|
|
22279
|
+
const token2 = stdinToken;
|
|
22280
|
+
const registry = deps.stdinMcpRegistry;
|
|
22281
|
+
stdinReservation.complete(session);
|
|
22282
|
+
session.onClose(() => {
|
|
22283
|
+
void registry.unbind(token2);
|
|
22284
|
+
});
|
|
22285
|
+
}
|
|
20430
22286
|
const client = bindClientToSession(connection, session, state);
|
|
20431
22287
|
const { entries: replay } = await session.attach(client, "full");
|
|
20432
22288
|
state.attached.set(session.sessionId, {
|
|
@@ -20823,7 +22679,10 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20823
22679
|
return null;
|
|
20824
22680
|
}
|
|
20825
22681
|
app.log.info(decision.logMessage);
|
|
20826
|
-
|
|
22682
|
+
const { modelId } = rawParams;
|
|
22683
|
+
const result = await decision.session.forwardRequest("session/set_model", rawParams);
|
|
22684
|
+
decision.session.applyModelChange(modelId);
|
|
22685
|
+
return result;
|
|
20827
22686
|
});
|
|
20828
22687
|
connection.onRequest("session/set_mode", async (rawParams) => {
|
|
20829
22688
|
const params = rawParams;
|
|
@@ -21144,6 +23003,336 @@ function bindClientToSession(connection, session, state, clientInfo, callerClien
|
|
|
21144
23003
|
};
|
|
21145
23004
|
}
|
|
21146
23005
|
|
|
23006
|
+
// src/daemon/mcp/stdin-registry.ts
|
|
23007
|
+
var StdinMcpRegistry = class {
|
|
23008
|
+
byToken = /* @__PURE__ */ new Map();
|
|
23009
|
+
// Reserve a token slot before the session exists. Used by acp-ws when
|
|
23010
|
+
// we need to inject the bearer into the agent's mcpServers BEFORE
|
|
23011
|
+
// manager.create() returns — claude-acp connects to /mcp/stdin during
|
|
23012
|
+
// session/new initialization (eagerly), so the route handler must be
|
|
23013
|
+
// able to find the token by the time the agent's first request lands.
|
|
23014
|
+
reserve(token) {
|
|
23015
|
+
if (this.byToken.has(token)) {
|
|
23016
|
+
throw new Error(`stdin MCP token already bound`);
|
|
23017
|
+
}
|
|
23018
|
+
let resolveSession2;
|
|
23019
|
+
let rejectSession;
|
|
23020
|
+
const sessionReady = new Promise((resolve6, reject) => {
|
|
23021
|
+
resolveSession2 = resolve6;
|
|
23022
|
+
rejectSession = reject;
|
|
23023
|
+
});
|
|
23024
|
+
sessionReady.catch(() => void 0);
|
|
23025
|
+
const entry = { session: void 0, sessionReady };
|
|
23026
|
+
this.byToken.set(token, entry);
|
|
23027
|
+
return {
|
|
23028
|
+
complete: (session) => {
|
|
23029
|
+
entry.session = session;
|
|
23030
|
+
resolveSession2(session);
|
|
23031
|
+
},
|
|
23032
|
+
abandon: (reason) => {
|
|
23033
|
+
this.byToken.delete(token);
|
|
23034
|
+
rejectSession(reason ?? new Error("stdin MCP reservation abandoned"));
|
|
23035
|
+
}
|
|
23036
|
+
};
|
|
23037
|
+
}
|
|
23038
|
+
// Convenience for callers that already have the session in hand (and
|
|
23039
|
+
// for tests). Equivalent to reserve() + complete() back-to-back.
|
|
23040
|
+
bind(token, session) {
|
|
23041
|
+
const { complete } = this.reserve(token);
|
|
23042
|
+
complete(session);
|
|
23043
|
+
}
|
|
23044
|
+
lookup(token) {
|
|
23045
|
+
return this.byToken.get(token);
|
|
23046
|
+
}
|
|
23047
|
+
attachTransport(token, server, transport) {
|
|
23048
|
+
const ep = this.byToken.get(token);
|
|
23049
|
+
if (!ep) {
|
|
23050
|
+
return;
|
|
23051
|
+
}
|
|
23052
|
+
ep.server = server;
|
|
23053
|
+
ep.transport = transport;
|
|
23054
|
+
}
|
|
23055
|
+
async unbind(token) {
|
|
23056
|
+
const ep = this.byToken.get(token);
|
|
23057
|
+
if (!ep) {
|
|
23058
|
+
return;
|
|
23059
|
+
}
|
|
23060
|
+
this.byToken.delete(token);
|
|
23061
|
+
if (ep.transport) {
|
|
23062
|
+
try {
|
|
23063
|
+
await ep.transport.close();
|
|
23064
|
+
} catch {
|
|
23065
|
+
}
|
|
23066
|
+
}
|
|
23067
|
+
if (ep.server) {
|
|
23068
|
+
try {
|
|
23069
|
+
await ep.server.close();
|
|
23070
|
+
} catch {
|
|
23071
|
+
}
|
|
23072
|
+
}
|
|
23073
|
+
}
|
|
23074
|
+
size() {
|
|
23075
|
+
return this.byToken.size;
|
|
23076
|
+
}
|
|
23077
|
+
};
|
|
23078
|
+
|
|
23079
|
+
// src/daemon/mcp/stdin-server.ts
|
|
23080
|
+
import { randomUUID } from "crypto";
|
|
23081
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23082
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
23083
|
+
import { z as z7 } from "zod";
|
|
23084
|
+
var BEARER_PREFIX2 = "Bearer ";
|
|
23085
|
+
function extractBearer(req) {
|
|
23086
|
+
const header = req.headers.authorization;
|
|
23087
|
+
if (typeof header !== "string") {
|
|
23088
|
+
return void 0;
|
|
23089
|
+
}
|
|
23090
|
+
if (!header.startsWith(BEARER_PREFIX2)) {
|
|
23091
|
+
return void 0;
|
|
23092
|
+
}
|
|
23093
|
+
const token = header.slice(BEARER_PREFIX2.length).trim();
|
|
23094
|
+
return token.length > 0 ? token : void 0;
|
|
23095
|
+
}
|
|
23096
|
+
function buildMcpServer(session) {
|
|
23097
|
+
const server = new McpServer(
|
|
23098
|
+
{ name: "hydra-stdin", version: "1.0.0" },
|
|
23099
|
+
{
|
|
23100
|
+
instructions: "Piped input from `hydra cat --stream` is exposed here as a byte stream. Use `tail_stdin` for the latest N bytes (good for finding the end of a log), `head_stdin` for the first N bytes (good for headers/preamble), `read_stdin` for windowed reads against an absolute byte cursor, `wait_for_more` to block until new bytes arrive past a cursor, and `stdin_info` for the current cursors/capacity/closed status. Byte payloads come back base64-encoded."
|
|
23101
|
+
}
|
|
23102
|
+
);
|
|
23103
|
+
server.registerTool(
|
|
23104
|
+
"tail_stdin",
|
|
23105
|
+
{
|
|
23106
|
+
description: "Return the most recent `bytes` bytes of piped stdin (capped server-side, default 64 KiB max). `truncated:true` means older bytes existed but have been evicted from the ring.",
|
|
23107
|
+
inputSchema: {
|
|
23108
|
+
bytes: z7.number().int().min(1).describe("How many trailing bytes to return.")
|
|
23109
|
+
}
|
|
23110
|
+
},
|
|
23111
|
+
async ({ bytes }) => {
|
|
23112
|
+
const r = session.streamTail(bytes);
|
|
23113
|
+
return {
|
|
23114
|
+
content: [
|
|
23115
|
+
{
|
|
23116
|
+
type: "text",
|
|
23117
|
+
text: JSON.stringify(r)
|
|
23118
|
+
}
|
|
23119
|
+
],
|
|
23120
|
+
structuredContent: r
|
|
23121
|
+
};
|
|
23122
|
+
}
|
|
23123
|
+
);
|
|
23124
|
+
server.registerTool(
|
|
23125
|
+
"head_stdin",
|
|
23126
|
+
{
|
|
23127
|
+
description: "Return the first `bytes` bytes of piped stdin (capped server-side, default 64 KiB max). `truncated:true` means the head has already been evicted from the ring and the returned bytes start at the oldest still-resident cursor.",
|
|
23128
|
+
inputSchema: {
|
|
23129
|
+
bytes: z7.number().int().min(1).describe("How many leading bytes to return.")
|
|
23130
|
+
}
|
|
23131
|
+
},
|
|
23132
|
+
async ({ bytes }) => {
|
|
23133
|
+
const r = session.streamHead(bytes);
|
|
23134
|
+
return {
|
|
23135
|
+
content: [
|
|
23136
|
+
{
|
|
23137
|
+
type: "text",
|
|
23138
|
+
text: JSON.stringify(r)
|
|
23139
|
+
}
|
|
23140
|
+
],
|
|
23141
|
+
structuredContent: r
|
|
23142
|
+
};
|
|
23143
|
+
}
|
|
23144
|
+
);
|
|
23145
|
+
server.registerTool(
|
|
23146
|
+
"read_stdin",
|
|
23147
|
+
{
|
|
23148
|
+
description: "Read up to `max_bytes` bytes starting at absolute byte `cursor`. Returns `{bytes, nextCursor, gap?, eof?}` \u2014 `gap` is the number of bytes silently skipped because the ring had evicted them; `eof:true` means the producer closed and there is nothing left to read.",
|
|
23149
|
+
inputSchema: {
|
|
23150
|
+
cursor: z7.number().int().min(0).describe(
|
|
23151
|
+
"Absolute byte offset to start reading from. Use 0 to read from the very beginning (may produce a gap if old bytes have been evicted)."
|
|
23152
|
+
),
|
|
23153
|
+
max_bytes: z7.number().int().min(1).optional().describe(
|
|
23154
|
+
"Optional cap on how many bytes to return. Server caps at 64 KiB regardless."
|
|
23155
|
+
),
|
|
23156
|
+
wait_ms: z7.number().int().min(0).optional().describe(
|
|
23157
|
+
"If no bytes are available, block up to this many ms for more (capped server-side at 60_000)."
|
|
23158
|
+
)
|
|
23159
|
+
}
|
|
23160
|
+
},
|
|
23161
|
+
async ({ cursor, max_bytes, wait_ms }) => {
|
|
23162
|
+
const r = await session.streamRead(cursor, max_bytes, wait_ms);
|
|
23163
|
+
return {
|
|
23164
|
+
content: [
|
|
23165
|
+
{
|
|
23166
|
+
type: "text",
|
|
23167
|
+
text: JSON.stringify(r)
|
|
23168
|
+
}
|
|
23169
|
+
],
|
|
23170
|
+
structuredContent: r
|
|
23171
|
+
};
|
|
23172
|
+
}
|
|
23173
|
+
);
|
|
23174
|
+
server.registerTool(
|
|
23175
|
+
"wait_for_more",
|
|
23176
|
+
{
|
|
23177
|
+
description: "Block until bytes are available past `cursor`, the stream closes, or `timeout_ms` elapses. Returns one of {data, eof, timeout} plus the current `writeCursor`. Use this when you've consumed everything up to a cursor and want to wait for more without busy-polling.",
|
|
23178
|
+
inputSchema: {
|
|
23179
|
+
cursor: z7.number().int().min(0).describe("The cursor you've already consumed up to."),
|
|
23180
|
+
timeout_ms: z7.number().int().min(0).describe("Maximum ms to block (server caps at 60_000).")
|
|
23181
|
+
}
|
|
23182
|
+
},
|
|
23183
|
+
async ({ cursor, timeout_ms }) => {
|
|
23184
|
+
const outcome = await session.streamWaitFor(cursor, timeout_ms);
|
|
23185
|
+
const info = session.streamInfo();
|
|
23186
|
+
const payload = { outcome, writeCursor: info.writeCursor, closed: info.closed };
|
|
23187
|
+
return {
|
|
23188
|
+
content: [
|
|
23189
|
+
{
|
|
23190
|
+
type: "text",
|
|
23191
|
+
text: JSON.stringify(payload)
|
|
23192
|
+
}
|
|
23193
|
+
],
|
|
23194
|
+
structuredContent: payload
|
|
23195
|
+
};
|
|
23196
|
+
}
|
|
23197
|
+
);
|
|
23198
|
+
server.registerTool(
|
|
23199
|
+
"grep_stdin",
|
|
23200
|
+
{
|
|
23201
|
+
description: "Scan piped stdin line-by-line and return lines matching `pattern`. Prefer this over `read_stdin` when the question is 'find lines that mention X' \u2014 it filters server-side so you don't pull and decode 64 KiB base64 windows. Returns `{matches: [{cursor, line, before?, after?}], truncated, nextCursor, gap?, scannedBytes, eof?}`. Lines come back as decoded UTF-8 strings (not base64). When `truncated:true`, re-call with `cursor: nextCursor` to resume.",
|
|
23202
|
+
inputSchema: {
|
|
23203
|
+
pattern: z7.string().min(1).describe(
|
|
23204
|
+
"Search pattern. Treated as a JavaScript regular expression by default (set `regex:false` for a literal substring match)."
|
|
23205
|
+
),
|
|
23206
|
+
regex: z7.boolean().optional().describe("Default true. Pass false to treat `pattern` as a literal substring."),
|
|
23207
|
+
case_insensitive: z7.boolean().optional().describe("Default false. Pass true for case-insensitive matching."),
|
|
23208
|
+
invert: z7.boolean().optional().describe("Default false. Pass true to return lines that do NOT match the pattern."),
|
|
23209
|
+
max_matches: z7.number().int().min(1).optional().describe("Default 100. Capped server-side at 1000."),
|
|
23210
|
+
max_bytes: z7.number().int().min(1).optional().describe("Default 64 KiB output. Capped server-side at 256 KiB."),
|
|
23211
|
+
context_before: z7.number().int().min(0).optional().describe("Default 0. Number of lines before each match to include (capped at 20)."),
|
|
23212
|
+
context_after: z7.number().int().min(0).optional().describe("Default 0. Number of lines after each match to include (capped at 20)."),
|
|
23213
|
+
cursor: z7.number().int().min(0).optional().describe(
|
|
23214
|
+
"Optional absolute byte offset to start scanning from. Omit to scan from the oldest still-resident byte. Pass the `nextCursor` from a previous truncated call to resume."
|
|
23215
|
+
)
|
|
23216
|
+
}
|
|
23217
|
+
},
|
|
23218
|
+
async (args) => {
|
|
23219
|
+
const opts = { pattern: args.pattern };
|
|
23220
|
+
if (args.regex !== void 0) {
|
|
23221
|
+
opts.regex = args.regex;
|
|
23222
|
+
}
|
|
23223
|
+
if (args.case_insensitive !== void 0) {
|
|
23224
|
+
opts.caseInsensitive = args.case_insensitive;
|
|
23225
|
+
}
|
|
23226
|
+
if (args.invert !== void 0) {
|
|
23227
|
+
opts.invert = args.invert;
|
|
23228
|
+
}
|
|
23229
|
+
if (args.max_matches !== void 0) {
|
|
23230
|
+
opts.maxMatches = args.max_matches;
|
|
23231
|
+
}
|
|
23232
|
+
if (args.max_bytes !== void 0) {
|
|
23233
|
+
opts.maxBytes = args.max_bytes;
|
|
23234
|
+
}
|
|
23235
|
+
if (args.context_before !== void 0) {
|
|
23236
|
+
opts.contextBefore = args.context_before;
|
|
23237
|
+
}
|
|
23238
|
+
if (args.context_after !== void 0) {
|
|
23239
|
+
opts.contextAfter = args.context_after;
|
|
23240
|
+
}
|
|
23241
|
+
if (args.cursor !== void 0) {
|
|
23242
|
+
opts.cursor = args.cursor;
|
|
23243
|
+
}
|
|
23244
|
+
const r = session.streamGrep(opts);
|
|
23245
|
+
const payload = r;
|
|
23246
|
+
return {
|
|
23247
|
+
content: [{ type: "text", text: JSON.stringify(r) }],
|
|
23248
|
+
structuredContent: payload
|
|
23249
|
+
};
|
|
23250
|
+
}
|
|
23251
|
+
);
|
|
23252
|
+
server.registerTool(
|
|
23253
|
+
"stdin_info",
|
|
23254
|
+
{
|
|
23255
|
+
description: "Report cursor / capacity / closed state of the stdin ring. Cheap; safe to call repeatedly.",
|
|
23256
|
+
inputSchema: {}
|
|
23257
|
+
},
|
|
23258
|
+
async () => {
|
|
23259
|
+
const r = session.streamInfo();
|
|
23260
|
+
return {
|
|
23261
|
+
content: [
|
|
23262
|
+
{
|
|
23263
|
+
type: "text",
|
|
23264
|
+
text: JSON.stringify(r)
|
|
23265
|
+
}
|
|
23266
|
+
],
|
|
23267
|
+
structuredContent: r
|
|
23268
|
+
};
|
|
23269
|
+
}
|
|
23270
|
+
);
|
|
23271
|
+
return server;
|
|
23272
|
+
}
|
|
23273
|
+
async function ensureTransport(token, session, registry) {
|
|
23274
|
+
const existing = registry.lookup(token);
|
|
23275
|
+
if (existing?.transport !== void 0) {
|
|
23276
|
+
return existing.transport;
|
|
23277
|
+
}
|
|
23278
|
+
const server = buildMcpServer(session);
|
|
23279
|
+
const transport = new StreamableHTTPServerTransport({
|
|
23280
|
+
sessionIdGenerator: () => randomUUID()
|
|
23281
|
+
});
|
|
23282
|
+
await server.connect(transport);
|
|
23283
|
+
registry.attachTransport(token, server, transport);
|
|
23284
|
+
return transport;
|
|
23285
|
+
}
|
|
23286
|
+
var SESSION_READY_TIMEOUT_MS = 1e4;
|
|
23287
|
+
async function handle(req, reply, registry) {
|
|
23288
|
+
const token = extractBearer(req);
|
|
23289
|
+
if (token === void 0) {
|
|
23290
|
+
reply.code(401).send({ error: "missing bearer token" });
|
|
23291
|
+
return;
|
|
23292
|
+
}
|
|
23293
|
+
const ep = registry.lookup(token);
|
|
23294
|
+
if (ep === void 0) {
|
|
23295
|
+
reply.code(404).send({ error: "unknown stdin token" });
|
|
23296
|
+
return;
|
|
23297
|
+
}
|
|
23298
|
+
let session;
|
|
23299
|
+
if (ep.session !== void 0) {
|
|
23300
|
+
session = ep.session;
|
|
23301
|
+
} else {
|
|
23302
|
+
let timer;
|
|
23303
|
+
const timeout = new Promise((resolve6) => {
|
|
23304
|
+
timer = setTimeout(() => resolve6(void 0), SESSION_READY_TIMEOUT_MS);
|
|
23305
|
+
});
|
|
23306
|
+
const resolved = await Promise.race([
|
|
23307
|
+
ep.sessionReady.catch(() => void 0),
|
|
23308
|
+
timeout
|
|
23309
|
+
]);
|
|
23310
|
+
if (timer !== void 0) {
|
|
23311
|
+
clearTimeout(timer);
|
|
23312
|
+
}
|
|
23313
|
+
if (resolved === void 0) {
|
|
23314
|
+
reply.code(503).send({ error: "session not ready" });
|
|
23315
|
+
return;
|
|
23316
|
+
}
|
|
23317
|
+
session = resolved;
|
|
23318
|
+
}
|
|
23319
|
+
const transport = await ensureTransport(token, session, registry);
|
|
23320
|
+
reply.hijack();
|
|
23321
|
+
await transport.handleRequest(req.raw, reply.raw, req.body);
|
|
23322
|
+
}
|
|
23323
|
+
function registerStdinMcpRoutes(app, registry) {
|
|
23324
|
+
const opts = { config: { skipAuth: true } };
|
|
23325
|
+
app.post("/mcp/stdin", opts, async (req, reply) => {
|
|
23326
|
+
await handle(req, reply, registry);
|
|
23327
|
+
});
|
|
23328
|
+
app.get("/mcp/stdin", opts, async (req, reply) => {
|
|
23329
|
+
await handle(req, reply, registry);
|
|
23330
|
+
});
|
|
23331
|
+
app.delete("/mcp/stdin", opts, async (req, reply) => {
|
|
23332
|
+
await handle(req, reply, registry);
|
|
23333
|
+
});
|
|
23334
|
+
}
|
|
23335
|
+
|
|
21147
23336
|
// src/daemon/server.ts
|
|
21148
23337
|
async function startDaemon(config, serviceToken) {
|
|
21149
23338
|
ensureLoopbackOrTls(config);
|
|
@@ -21214,13 +23403,15 @@ async function startDaemon(config, serviceToken) {
|
|
|
21214
23403
|
stderrTailBytes: config.daemon.agentStderrTailBytes,
|
|
21215
23404
|
logger: agentLogger
|
|
21216
23405
|
});
|
|
23406
|
+
const extensionCommands = new ExtensionCommandRegistry();
|
|
21217
23407
|
const manager = new SessionManager(registry, spawner, void 0, {
|
|
21218
23408
|
idleTimeoutMs: config.daemon.sessionIdleTimeoutSeconds * 1e3,
|
|
21219
23409
|
defaultModels: config.defaultModels,
|
|
21220
23410
|
defaultTransformers: config.defaultTransformers,
|
|
21221
23411
|
sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries,
|
|
21222
23412
|
logger: agentLogger,
|
|
21223
|
-
npmRegistry: config.npmRegistry
|
|
23413
|
+
npmRegistry: config.npmRegistry,
|
|
23414
|
+
extensionCommands
|
|
21224
23415
|
});
|
|
21225
23416
|
const extensions = new ExtensionManager(extensionList(config), void 0, {
|
|
21226
23417
|
tokenRegistry: processRegistry
|
|
@@ -21247,6 +23438,19 @@ async function startDaemon(config, serviceToken) {
|
|
|
21247
23438
|
store: sessionTokenStore,
|
|
21248
23439
|
rateLimiter: authRateLimiter
|
|
21249
23440
|
});
|
|
23441
|
+
const stdinMcpRegistry = new StdinMcpRegistry();
|
|
23442
|
+
registerStdinMcpRoutes(app, stdinMcpRegistry);
|
|
23443
|
+
let daemonOriginCached;
|
|
23444
|
+
const getDaemonOrigin = () => {
|
|
23445
|
+
if (daemonOriginCached !== void 0) {
|
|
23446
|
+
return daemonOriginCached;
|
|
23447
|
+
}
|
|
23448
|
+
const addr = app.server.address();
|
|
23449
|
+
const port = addr && typeof addr === "object" ? addr.port : config.daemon.port;
|
|
23450
|
+
const scheme2 = config.daemon.tls ? "https" : "http";
|
|
23451
|
+
daemonOriginCached = `${scheme2}://${config.daemon.host}:${port}`;
|
|
23452
|
+
return daemonOriginCached;
|
|
23453
|
+
};
|
|
21250
23454
|
registerAcpWsEndpoint(app, {
|
|
21251
23455
|
validator,
|
|
21252
23456
|
manager,
|
|
@@ -21254,7 +23458,10 @@ async function startDaemon(config, serviceToken) {
|
|
|
21254
23458
|
processRegistry,
|
|
21255
23459
|
onExtensionVersion: (name, version) => extensions.reportVersion(name, version),
|
|
21256
23460
|
onTransformerVersion: (name, version) => transformers.reportVersion(name, version),
|
|
21257
|
-
transformers
|
|
23461
|
+
transformers,
|
|
23462
|
+
extensionCommands,
|
|
23463
|
+
stdinMcpRegistry,
|
|
23464
|
+
getDaemonOrigin
|
|
21258
23465
|
});
|
|
21259
23466
|
await app.listen({ host: config.daemon.host, port: config.daemon.port });
|
|
21260
23467
|
const address = app.server.address();
|
|
@@ -21510,14 +23717,14 @@ async function runDaemonStart(flags = {}) {
|
|
|
21510
23717
|
}
|
|
21511
23718
|
if (flagBool(flags, "foreground")) {
|
|
21512
23719
|
process.title = "hydra-daemon";
|
|
21513
|
-
const
|
|
23720
|
+
const handle2 = await startDaemon(config, serviceToken);
|
|
21514
23721
|
process.stdout.write(
|
|
21515
23722
|
`hydra-acp daemon listening on ${config.daemon.host}:${config.daemon.port}
|
|
21516
23723
|
`
|
|
21517
23724
|
);
|
|
21518
23725
|
const shutdown = async () => {
|
|
21519
23726
|
process.stdout.write("Shutting down...\n");
|
|
21520
|
-
await
|
|
23727
|
+
await handle2.shutdown();
|
|
21521
23728
|
process.exit(0);
|
|
21522
23729
|
};
|
|
21523
23730
|
process.on("SIGINT", () => void shutdown());
|
|
@@ -21687,6 +23894,7 @@ init_remote_target();
|
|
|
21687
23894
|
init_remote_url();
|
|
21688
23895
|
init_session();
|
|
21689
23896
|
init_discovery();
|
|
23897
|
+
init_hydra_version();
|
|
21690
23898
|
import * as fs19 from "fs/promises";
|
|
21691
23899
|
import * as path14 from "path";
|
|
21692
23900
|
init_session_row();
|
|
@@ -21704,10 +23912,13 @@ async function runSessionsList(opts = {}) {
|
|
|
21704
23912
|
process.exit(1);
|
|
21705
23913
|
}
|
|
21706
23914
|
const body = await response.json();
|
|
23915
|
+
const sessionsAfterCatFilter = opts.includeCat ? body.sessions : body.sessions.filter(
|
|
23916
|
+
(s) => s.originatingClient?.name !== HYDRA_CAT_CLIENT_NAME
|
|
23917
|
+
);
|
|
21707
23918
|
const host = opts.host ?? "local";
|
|
21708
|
-
const hostFiltered = host === "all" ?
|
|
23919
|
+
const hostFiltered = host === "all" ? sessionsAfterCatFilter : host === "local" ? sessionsAfterCatFilter.filter(
|
|
21709
23920
|
(s) => !s.importedFromMachine || !!s.upstreamSessionId
|
|
21710
|
-
) :
|
|
23921
|
+
) : sessionsAfterCatFilter.filter(
|
|
21711
23922
|
(s) => s.importedFromMachine === host && !s.upstreamSessionId
|
|
21712
23923
|
);
|
|
21713
23924
|
if (opts.json) {
|
|
@@ -23558,6 +25769,9 @@ function isResponse2(msg) {
|
|
|
23558
25769
|
return !("method" in msg) && "id" in msg;
|
|
23559
25770
|
}
|
|
23560
25771
|
|
|
25772
|
+
// src/shim/proxy.ts
|
|
25773
|
+
init_permission_pick();
|
|
25774
|
+
|
|
23561
25775
|
// src/core/process-title.ts
|
|
23562
25776
|
init_bin_name();
|
|
23563
25777
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -23642,6 +25856,14 @@ function wireShim({
|
|
|
23642
25856
|
}) {
|
|
23643
25857
|
upstream.onMessage((msg) => {
|
|
23644
25858
|
tracker.observeFromServer(msg);
|
|
25859
|
+
if (opts.dangerouslySkipPermissions === true && isPermissionRequest(msg)) {
|
|
25860
|
+
void upstream.send({
|
|
25861
|
+
jsonrpc: "2.0",
|
|
25862
|
+
id: msg.id,
|
|
25863
|
+
result: buildApproveResponse(msg.params)
|
|
25864
|
+
});
|
|
25865
|
+
return;
|
|
25866
|
+
}
|
|
23645
25867
|
maybeReplyToResolvedPermission(msg, tracker, downstream);
|
|
23646
25868
|
void downstream.send(msg);
|
|
23647
25869
|
});
|
|
@@ -23791,6 +26013,9 @@ async function replayAttach(stream, ctx, afterMessageId) {
|
|
|
23791
26013
|
function isSessionNewRequest(msg) {
|
|
23792
26014
|
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "session/new";
|
|
23793
26015
|
}
|
|
26016
|
+
function isPermissionRequest(msg) {
|
|
26017
|
+
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "session/request_permission";
|
|
26018
|
+
}
|
|
23794
26019
|
function buildAttachFromNew(msg, sessionId) {
|
|
23795
26020
|
return {
|
|
23796
26021
|
jsonrpc: "2.0",
|
|
@@ -23837,6 +26062,10 @@ init_daemon_bootstrap();
|
|
|
23837
26062
|
init_render_update();
|
|
23838
26063
|
init_types();
|
|
23839
26064
|
init_hydra_version();
|
|
26065
|
+
init_permission_pick();
|
|
26066
|
+
import { mkdtempSync, rmSync } from "fs";
|
|
26067
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
26068
|
+
import { join as join11 } from "path";
|
|
23840
26069
|
import { WebSocket as WebSocket2 } from "ws";
|
|
23841
26070
|
|
|
23842
26071
|
// src/cli/commands/cat-chunker.ts
|
|
@@ -23891,8 +26120,26 @@ function createChunker(opts) {
|
|
|
23891
26120
|
}
|
|
23892
26121
|
|
|
23893
26122
|
// src/cli/commands/cat.ts
|
|
23894
|
-
var DEFAULT_STREAM_THRESHOLD =
|
|
23895
|
-
var
|
|
26123
|
+
var DEFAULT_STREAM_THRESHOLD = 1 * 1024 * 1024;
|
|
26124
|
+
var HYDRA_STDIN_TOOL_PREFIX = "mcp__hydra_stdin__";
|
|
26125
|
+
function isHydraStdinPermissionRequest(params) {
|
|
26126
|
+
if (!params || typeof params !== "object") {
|
|
26127
|
+
return false;
|
|
26128
|
+
}
|
|
26129
|
+
const toolCall = params.toolCall;
|
|
26130
|
+
if (!toolCall || typeof toolCall !== "object") {
|
|
26131
|
+
return false;
|
|
26132
|
+
}
|
|
26133
|
+
const title = toolCall.title;
|
|
26134
|
+
if (typeof title === "string" && title.startsWith(HYDRA_STDIN_TOOL_PREFIX)) {
|
|
26135
|
+
return true;
|
|
26136
|
+
}
|
|
26137
|
+
const toolName = toolCall.toolName;
|
|
26138
|
+
if (typeof toolName === "string" && toolName.startsWith(HYDRA_STDIN_TOOL_PREFIX)) {
|
|
26139
|
+
return true;
|
|
26140
|
+
}
|
|
26141
|
+
return false;
|
|
26142
|
+
}
|
|
23896
26143
|
async function runCat(opts) {
|
|
23897
26144
|
setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
|
|
23898
26145
|
if (process.stdin.isTTY && !opts.prompt && !opts.sessionId) {
|
|
@@ -23902,6 +26149,18 @@ async function runCat(opts) {
|
|
|
23902
26149
|
process.exit(2);
|
|
23903
26150
|
return;
|
|
23904
26151
|
}
|
|
26152
|
+
if (!opts.sessionId && opts.cwd === void 0 && process.stdin.isTTY !== true) {
|
|
26153
|
+
const sandbox = mkdtempSync(join11(tmpdir2(), "hydra-cat-"));
|
|
26154
|
+
opts.cwd = sandbox;
|
|
26155
|
+
if (!opts.detach) {
|
|
26156
|
+
process.on("exit", () => {
|
|
26157
|
+
try {
|
|
26158
|
+
rmSync(sandbox, { recursive: true, force: true });
|
|
26159
|
+
} catch {
|
|
26160
|
+
}
|
|
26161
|
+
});
|
|
26162
|
+
}
|
|
26163
|
+
}
|
|
23905
26164
|
const config = await loadConfig();
|
|
23906
26165
|
const target = opts.target ?? await resolveLocalTarget(config);
|
|
23907
26166
|
if (target.isLocal && !opts.target) {
|
|
@@ -23925,9 +26184,19 @@ async function runCat(opts) {
|
|
|
23925
26184
|
}
|
|
23926
26185
|
async function runCatLoop(args) {
|
|
23927
26186
|
const { conn, opts, stdin, stdinIsTty, stdout, stderr } = args;
|
|
26187
|
+
const useAutoStream = !stdinIsTty && opts.sessionId === void 0 && opts.follow !== true;
|
|
23928
26188
|
conn.setDefaultHandler(async () => {
|
|
23929
26189
|
return { error: { code: -32601, message: "method not implemented" } };
|
|
23930
26190
|
});
|
|
26191
|
+
conn.onRequest("session/request_permission", async (params) => {
|
|
26192
|
+
if (opts.dangerouslySkipPermissions) {
|
|
26193
|
+
return buildApproveResponse(params);
|
|
26194
|
+
}
|
|
26195
|
+
if (!isHydraStdinPermissionRequest(params)) {
|
|
26196
|
+
return buildRejectResponse(params);
|
|
26197
|
+
}
|
|
26198
|
+
return buildApproveResponse(params);
|
|
26199
|
+
});
|
|
23931
26200
|
try {
|
|
23932
26201
|
await conn.request("initialize", {
|
|
23933
26202
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
@@ -23935,11 +26204,11 @@ async function runCatLoop(args) {
|
|
|
23935
26204
|
fs: { readTextFile: false, writeTextFile: false },
|
|
23936
26205
|
terminal: false
|
|
23937
26206
|
},
|
|
23938
|
-
clientInfo: { name:
|
|
26207
|
+
clientInfo: { name: HYDRA_CAT_CLIENT_NAME, version: HYDRA_VERSION }
|
|
23939
26208
|
});
|
|
23940
26209
|
} catch {
|
|
23941
26210
|
}
|
|
23942
|
-
const sessionId = await openOrAttachSession(conn, opts);
|
|
26211
|
+
const sessionId = await openOrAttachSession(conn, opts, useAutoStream);
|
|
23943
26212
|
let turnHadOutput = false;
|
|
23944
26213
|
let lastCharWasNewline = true;
|
|
23945
26214
|
const writeStdout = (text) => {
|
|
@@ -23968,9 +26237,10 @@ async function runCatLoop(args) {
|
|
|
23968
26237
|
finalizeTurn();
|
|
23969
26238
|
}
|
|
23970
26239
|
});
|
|
26240
|
+
let firstChunkSent = false;
|
|
23971
26241
|
const sendChunk = async (text) => {
|
|
23972
26242
|
const promptBlocks = [];
|
|
23973
|
-
if (opts.prompt) {
|
|
26243
|
+
if (opts.prompt && !firstChunkSent) {
|
|
23974
26244
|
promptBlocks.push({ type: "text", text: opts.prompt });
|
|
23975
26245
|
}
|
|
23976
26246
|
if (text.length > 0) {
|
|
@@ -23984,6 +26254,7 @@ async function runCatLoop(args) {
|
|
|
23984
26254
|
sessionId,
|
|
23985
26255
|
prompt: promptBlocks
|
|
23986
26256
|
});
|
|
26257
|
+
firstChunkSent = true;
|
|
23987
26258
|
} catch (err) {
|
|
23988
26259
|
stderr(`hydra-acp cat: prompt failed: ${err.message}
|
|
23989
26260
|
`);
|
|
@@ -24042,22 +26313,6 @@ async function runCatLoop(args) {
|
|
|
24042
26313
|
}
|
|
24043
26314
|
}
|
|
24044
26315
|
};
|
|
24045
|
-
const chunker = createChunker({
|
|
24046
|
-
// setImmediate fires in the libuv "check" phase, after pending
|
|
24047
|
-
// I/O has been polled and any back-to-back "data" events have
|
|
24048
|
-
// been emitted. That makes it the natural hook for "the writer
|
|
24049
|
-
// has paused, time to flush": if more bytes were sitting in the
|
|
24050
|
-
// pipe buffer, Node would have emitted another "data" event
|
|
24051
|
-
// before this fires, and the chunker would detect that and defer.
|
|
24052
|
-
scheduleFlushCheck: (cb) => {
|
|
24053
|
-
const h = setImmediate(cb);
|
|
24054
|
-
return () => clearImmediate(h);
|
|
24055
|
-
},
|
|
24056
|
-
onChunk: (text) => {
|
|
24057
|
-
chunkQueue.push(text);
|
|
24058
|
-
void drainQueue();
|
|
24059
|
-
}
|
|
24060
|
-
});
|
|
24061
26316
|
if (stdinIsTty && !opts.sessionId) {
|
|
24062
26317
|
if (opts.prompt) {
|
|
24063
26318
|
await sendChunk("");
|
|
@@ -24065,7 +26320,7 @@ async function runCatLoop(args) {
|
|
|
24065
26320
|
await settle(0);
|
|
24066
26321
|
return done;
|
|
24067
26322
|
}
|
|
24068
|
-
if (
|
|
26323
|
+
if (useAutoStream) {
|
|
24069
26324
|
if (typeof stdin.setEncoding === "function") {
|
|
24070
26325
|
stdin.setEncoding("utf8");
|
|
24071
26326
|
}
|
|
@@ -24106,15 +26361,49 @@ async function runCatLoop(args) {
|
|
|
24106
26361
|
if (typeof stdin.setEncoding === "function") {
|
|
24107
26362
|
stdin.setEncoding("utf8");
|
|
24108
26363
|
}
|
|
26364
|
+
const useFollow = opts.follow === true || stdinIsTty && Boolean(opts.sessionId);
|
|
26365
|
+
if (useFollow) {
|
|
26366
|
+
const chunker = createChunker({
|
|
26367
|
+
scheduleFlushCheck: (cb) => {
|
|
26368
|
+
const h = setImmediate(cb);
|
|
26369
|
+
return () => clearImmediate(h);
|
|
26370
|
+
},
|
|
26371
|
+
onChunk: (text) => {
|
|
26372
|
+
chunkQueue.push(text);
|
|
26373
|
+
void drainQueue();
|
|
26374
|
+
}
|
|
26375
|
+
});
|
|
26376
|
+
stdin.on("data", (data) => {
|
|
26377
|
+
chunker.feed(typeof data === "string" ? data : data.toString("utf8"));
|
|
26378
|
+
});
|
|
26379
|
+
stdin.on("end", () => {
|
|
26380
|
+
chunker.eof();
|
|
26381
|
+
stdinEnded = true;
|
|
26382
|
+
if (!draining && chunkQueue.length === 0) {
|
|
26383
|
+
void settle(exitCode);
|
|
26384
|
+
}
|
|
26385
|
+
});
|
|
26386
|
+
stdin.on("error", (err) => {
|
|
26387
|
+
stderr(`hydra-acp cat: stdin error: ${err.message}
|
|
26388
|
+
`);
|
|
26389
|
+
exitCode = 1;
|
|
26390
|
+
stdinEnded = true;
|
|
26391
|
+
if (!draining && chunkQueue.length === 0) {
|
|
26392
|
+
void settle(exitCode);
|
|
26393
|
+
}
|
|
26394
|
+
});
|
|
26395
|
+
return done;
|
|
26396
|
+
}
|
|
26397
|
+
let oneShotBuffer = "";
|
|
24109
26398
|
stdin.on("data", (data) => {
|
|
24110
|
-
|
|
26399
|
+
oneShotBuffer += typeof data === "string" ? data : data.toString("utf8");
|
|
24111
26400
|
});
|
|
24112
26401
|
stdin.on("end", () => {
|
|
24113
|
-
chunker.eof();
|
|
24114
26402
|
stdinEnded = true;
|
|
24115
|
-
if (
|
|
24116
|
-
|
|
26403
|
+
if (oneShotBuffer.length > 0) {
|
|
26404
|
+
chunkQueue.push(oneShotBuffer);
|
|
24117
26405
|
}
|
|
26406
|
+
void drainQueue();
|
|
24118
26407
|
});
|
|
24119
26408
|
stdin.on("error", (err) => {
|
|
24120
26409
|
stderr(`hydra-acp cat: stdin error: ${err.message}
|
|
@@ -24167,12 +26456,11 @@ function runStreamingPath(args) {
|
|
|
24167
26456
|
try {
|
|
24168
26457
|
const openParams = {
|
|
24169
26458
|
sessionId,
|
|
24170
|
-
mode: "
|
|
26459
|
+
mode: "memory"
|
|
24171
26460
|
};
|
|
24172
26461
|
if (opts.streamBufferBytes !== void 0) {
|
|
24173
26462
|
openParams.capacityBytes = opts.streamBufferBytes;
|
|
24174
26463
|
}
|
|
24175
|
-
openParams.fileCapBytes = opts.streamFileCapBytes ?? DEFAULT_STREAM_FILE_CAP;
|
|
24176
26464
|
open2 = await conn.request("hydra-acp/stream_open", openParams);
|
|
24177
26465
|
} catch (err) {
|
|
24178
26466
|
args.onPromptFailed(
|
|
@@ -24180,23 +26468,12 @@ function runStreamingPath(args) {
|
|
|
24180
26468
|
);
|
|
24181
26469
|
return;
|
|
24182
26470
|
}
|
|
24183
|
-
const filePath = open2.filePath;
|
|
24184
|
-
if (filePath === void 0) {
|
|
24185
|
-
args.onPromptFailed(
|
|
24186
|
-
new Error("daemon did not return a filePath for stream mode")
|
|
24187
|
-
);
|
|
24188
|
-
return;
|
|
24189
|
-
}
|
|
24190
26471
|
if (headBuffer.length > 0) {
|
|
24191
26472
|
writeToStream(headBuffer, false);
|
|
24192
26473
|
headBuffer = Buffer.alloc(0);
|
|
24193
26474
|
}
|
|
24194
26475
|
await writeChain.catch(() => void 0);
|
|
24195
|
-
const promptText = buildStreamPromptText(
|
|
24196
|
-
opts.prompt,
|
|
24197
|
-
filePath,
|
|
24198
|
-
opts.streamFileCapBytes ?? DEFAULT_STREAM_FILE_CAP
|
|
24199
|
-
);
|
|
26476
|
+
const promptText = buildStreamPromptText(opts.prompt, open2.capacityBytes);
|
|
24200
26477
|
const promptDone = conn.request("session/prompt", {
|
|
24201
26478
|
sessionId,
|
|
24202
26479
|
prompt: [{ type: "text", text: promptText }]
|
|
@@ -24239,22 +26516,35 @@ function runStreamingPath(args) {
|
|
|
24239
26516
|
});
|
|
24240
26517
|
stdin.on("error", args.onError);
|
|
24241
26518
|
}
|
|
24242
|
-
function buildStreamPromptText(standing,
|
|
24243
|
-
const capHuman =
|
|
24244
|
-
const
|
|
26519
|
+
function buildStreamPromptText(standing, ringCapacityBytes) {
|
|
26520
|
+
const capHuman = ringCapacityBytes >= 1024 * 1024 ? `${(ringCapacityBytes / (1024 * 1024)).toFixed(0)} MB` : `${(ringCapacityBytes / 1024).toFixed(0)} KB`;
|
|
26521
|
+
const toolNote = `The user has piped data into this session. The bytes are NOT in your prompt; they live in the \`hydra_stdin\` MCP server and you read them via its tools:
|
|
26522
|
+
- \`stdin_info()\` \u2014 current writeCursor / oldestAvailable / capacity / closed. Cheap; call first to see how much data is there.
|
|
26523
|
+
- \`grep_stdin({pattern, regex?, case_insensitive?, context_before?, context_after?, cursor?})\` \u2014 server-side line filter; returns matching lines as decoded strings (not base64). Prefer this for "find lines that mention X" questions on multi-MB inputs.
|
|
26524
|
+
- \`head_stdin({bytes})\` \u2014 first N bytes (good for headers / preamble / file signatures).
|
|
26525
|
+
- \`tail_stdin({bytes})\` \u2014 most recent N bytes (good for log endings / recent errors).
|
|
26526
|
+
- \`read_stdin({cursor, max_bytes, wait_ms})\` \u2014 windowed read at an absolute byte cursor; iterate to sweep the whole stream.
|
|
26527
|
+
- \`wait_for_more({cursor, timeout_ms})\` \u2014 block for new bytes past a cursor (only useful for live tails).
|
|
26528
|
+
|
|
26529
|
+
Byte payloads (head/tail/read) come back base64-encoded \u2014 decode before reading them as text. \`grep_stdin\` returns plain strings. The ring holds the most recent ~${capHuman}; older bytes are evicted, and the byte tools report the gap when that happens. Per-call cap is 64 KiB for byte tools; loop \`read_stdin\` (advancing the cursor by \`nextCursor\`) when you need more.`;
|
|
24245
26530
|
if (standing && standing.length > 0) {
|
|
24246
|
-
return `${
|
|
26531
|
+
return `${toolNote}
|
|
24247
26532
|
|
|
24248
|
-
|
|
26533
|
+
Use those tools NOW to answer the user's question \u2014 do not ask whether to check stdin; just check it. Pick the right tool for the question (grep_stdin for finding specific lines, head for preamble / file type, tail for recent events, read_stdin + cursor sweep for whole-stream scans), then answer.
|
|
26534
|
+
|
|
26535
|
+
User's question:
|
|
26536
|
+
${standing}`;
|
|
24249
26537
|
}
|
|
24250
|
-
return
|
|
26538
|
+
return `${toolNote}
|
|
26539
|
+
|
|
26540
|
+
Use those tools to inspect the piped input and report what's there. Start with \`stdin_info()\` to see the size, then \`head_stdin\` and/or \`tail_stdin\` to look at the bytes.`;
|
|
24251
26541
|
}
|
|
24252
|
-
async function openOrAttachSession(conn, opts) {
|
|
26542
|
+
async function openOrAttachSession(conn, opts, useAutoStream) {
|
|
24253
26543
|
if (opts.sessionId) {
|
|
24254
26544
|
const attached = await conn.request("session/attach", {
|
|
24255
26545
|
sessionId: opts.sessionId,
|
|
24256
26546
|
historyPolicy: "pending_only",
|
|
24257
|
-
clientInfo: { name:
|
|
26547
|
+
clientInfo: { name: HYDRA_CAT_CLIENT_NAME, version: HYDRA_VERSION }
|
|
24258
26548
|
});
|
|
24259
26549
|
return attached.sessionId;
|
|
24260
26550
|
}
|
|
@@ -24265,6 +26555,9 @@ async function openOrAttachSession(conn, opts) {
|
|
|
24265
26555
|
if (opts.model) {
|
|
24266
26556
|
hydraMeta.model = opts.model;
|
|
24267
26557
|
}
|
|
26558
|
+
if (useAutoStream) {
|
|
26559
|
+
hydraMeta.mcpStdin = true;
|
|
26560
|
+
}
|
|
24268
26561
|
const cwd = opts.cwd ?? process.cwd();
|
|
24269
26562
|
const params = { cwd };
|
|
24270
26563
|
if (opts.agentId) {
|
|
@@ -24296,6 +26589,16 @@ async function openWs2(url, subprotocols) {
|
|
|
24296
26589
|
// src/cli.ts
|
|
24297
26590
|
init_update_check();
|
|
24298
26591
|
var suppressUpdateNotice = false;
|
|
26592
|
+
var dangerousNoticePrinted = false;
|
|
26593
|
+
function warnIfDangerouslySkipping(active) {
|
|
26594
|
+
if (!active || dangerousNoticePrinted) {
|
|
26595
|
+
return;
|
|
26596
|
+
}
|
|
26597
|
+
dangerousNoticePrinted = true;
|
|
26598
|
+
process.stderr.write(
|
|
26599
|
+
"hydra-acp: --dangerously-skip-permissions is set \u2014 all tool permission requests will be auto-approved.\n"
|
|
26600
|
+
);
|
|
26601
|
+
}
|
|
24299
26602
|
async function main() {
|
|
24300
26603
|
const argv = process.argv.slice(2);
|
|
24301
26604
|
const launchIdx = argv.indexOf("launch");
|
|
@@ -24305,6 +26608,7 @@ async function main() {
|
|
|
24305
26608
|
const positionalAgentId = afterLaunch[0];
|
|
24306
26609
|
const agentArgs = afterLaunch.slice(1);
|
|
24307
26610
|
const { flags: flags2 } = parseArgs(beforeLaunch);
|
|
26611
|
+
rejectUnknownFlags(flags2);
|
|
24308
26612
|
if (flags2.reattach === true) {
|
|
24309
26613
|
process.stderr.write(
|
|
24310
26614
|
"hydra-acp launch: --reattach is not valid here. Pass --session <id-or-url> to attach to a specific session.\n"
|
|
@@ -24327,11 +26631,14 @@ async function main() {
|
|
|
24327
26631
|
const name2 = resolveOption(flags2, "name");
|
|
24328
26632
|
const model2 = resolveOption(flags2, "model");
|
|
24329
26633
|
suppressUpdateNotice = true;
|
|
26634
|
+
const dangerous = flags2["dangerously-skip-permissions"] === true;
|
|
26635
|
+
warnIfDangerouslySkipping(dangerous);
|
|
24330
26636
|
const shimOpts = {
|
|
24331
26637
|
agentId,
|
|
24332
26638
|
agentArgs,
|
|
24333
26639
|
name: name2,
|
|
24334
|
-
model: model2
|
|
26640
|
+
model: model2,
|
|
26641
|
+
dangerouslySkipPermissions: dangerous
|
|
24335
26642
|
};
|
|
24336
26643
|
if (resolved2?.sessionId !== void 0) {
|
|
24337
26644
|
shimOpts.sessionId = resolved2.sessionId;
|
|
@@ -24343,6 +26650,7 @@ async function main() {
|
|
|
24343
26650
|
return;
|
|
24344
26651
|
}
|
|
24345
26652
|
const { positional, flags } = parseArgs(argv);
|
|
26653
|
+
rejectUnknownFlags(flags);
|
|
24346
26654
|
if (flags.version === true || positional[0] === "--version") {
|
|
24347
26655
|
process.stdout.write(`hydra-acp ${readVersion()}
|
|
24348
26656
|
`);
|
|
@@ -24356,6 +26664,8 @@ async function main() {
|
|
|
24356
26664
|
const name = resolveOption(flags, "name");
|
|
24357
26665
|
const agentIdFromFlag = resolveOption(flags, "agent");
|
|
24358
26666
|
const model = resolveOption(flags, "model");
|
|
26667
|
+
const dangerouslySkipPermissions = flags["dangerously-skip-permissions"] === true;
|
|
26668
|
+
warnIfDangerouslySkipping(dangerouslySkipPermissions);
|
|
24359
26669
|
const sessionInput = readSessionInput(flags);
|
|
24360
26670
|
const interactive = subcommand === "tui" || subcommand === void 0 && process.stdout.isTTY;
|
|
24361
26671
|
const resolved = await resolveSessionFlagOrExit(sessionInput, {
|
|
@@ -24377,7 +26687,8 @@ async function main() {
|
|
|
24377
26687
|
agentId: agentIdFromFlag,
|
|
24378
26688
|
name,
|
|
24379
26689
|
model,
|
|
24380
|
-
target: sessionTarget
|
|
26690
|
+
target: sessionTarget,
|
|
26691
|
+
dangerouslySkipPermissions
|
|
24381
26692
|
});
|
|
24382
26693
|
return;
|
|
24383
26694
|
}
|
|
@@ -24385,7 +26696,8 @@ async function main() {
|
|
|
24385
26696
|
const shimOpts = {
|
|
24386
26697
|
name,
|
|
24387
26698
|
model,
|
|
24388
|
-
agentId: agentIdFromFlag
|
|
26699
|
+
agentId: agentIdFromFlag,
|
|
26700
|
+
dangerouslySkipPermissions
|
|
24389
26701
|
};
|
|
24390
26702
|
if (sessionId !== void 0) {
|
|
24391
26703
|
shimOpts.sessionId = sessionId;
|
|
@@ -24402,7 +26714,8 @@ async function main() {
|
|
|
24402
26714
|
const shimOpts = {
|
|
24403
26715
|
name,
|
|
24404
26716
|
model,
|
|
24405
|
-
agentId: agentIdFromFlag
|
|
26717
|
+
agentId: agentIdFromFlag,
|
|
26718
|
+
dangerouslySkipPermissions
|
|
24406
26719
|
};
|
|
24407
26720
|
if (sessionId !== void 0) {
|
|
24408
26721
|
shimOpts.sessionId = sessionId;
|
|
@@ -24425,7 +26738,8 @@ async function main() {
|
|
|
24425
26738
|
model,
|
|
24426
26739
|
agentId: agentIdFromFlag,
|
|
24427
26740
|
detach: flags.detach === true,
|
|
24428
|
-
|
|
26741
|
+
follow: flags.follow === true,
|
|
26742
|
+
dangerouslySkipPermissions
|
|
24429
26743
|
};
|
|
24430
26744
|
if (cwd !== void 0) {
|
|
24431
26745
|
catOpts.cwd = cwd;
|
|
@@ -24441,10 +26755,6 @@ async function main() {
|
|
|
24441
26755
|
if (streamBufferBytes !== void 0) {
|
|
24442
26756
|
catOpts.streamBufferBytes = streamBufferBytes;
|
|
24443
26757
|
}
|
|
24444
|
-
const streamFileCap = parseNumericFlag(flags, "stream-file-cap");
|
|
24445
|
-
if (streamFileCap !== void 0) {
|
|
24446
|
-
catOpts.streamFileCapBytes = streamFileCap;
|
|
24447
|
-
}
|
|
24448
26758
|
suppressUpdateNotice = true;
|
|
24449
26759
|
await runCat(catOpts);
|
|
24450
26760
|
return;
|
|
@@ -24488,7 +26798,8 @@ async function main() {
|
|
|
24488
26798
|
await runSessionsList({
|
|
24489
26799
|
all: flags.all === true,
|
|
24490
26800
|
json: flags.json === true,
|
|
24491
|
-
host: typeof flags.host === "string" ? flags.host : void 0
|
|
26801
|
+
host: typeof flags.host === "string" ? flags.host : void 0,
|
|
26802
|
+
includeCat: flags["include-cat"] === true
|
|
24492
26803
|
});
|
|
24493
26804
|
return;
|
|
24494
26805
|
}
|
|
@@ -24670,7 +26981,8 @@ async function main() {
|
|
|
24670
26981
|
agentId: agentIdFromFlag,
|
|
24671
26982
|
name,
|
|
24672
26983
|
model,
|
|
24673
|
-
target: sessionTarget
|
|
26984
|
+
target: sessionTarget,
|
|
26985
|
+
dangerouslySkipPermissions
|
|
24674
26986
|
});
|
|
24675
26987
|
return;
|
|
24676
26988
|
default:
|
|
@@ -24712,6 +27024,9 @@ async function dispatchTui(flags, base) {
|
|
|
24712
27024
|
if (base.target !== void 0) {
|
|
24713
27025
|
tuiOpts.target = base.target;
|
|
24714
27026
|
}
|
|
27027
|
+
if (base.dangerouslySkipPermissions === true) {
|
|
27028
|
+
tuiOpts.dangerouslySkipPermissions = true;
|
|
27029
|
+
}
|
|
24715
27030
|
await runTui(tuiOpts);
|
|
24716
27031
|
}
|
|
24717
27032
|
function parseNumericFlag(flags, name) {
|
|
@@ -24730,6 +27045,17 @@ function parseNumericFlag(flags, name) {
|
|
|
24730
27045
|
}
|
|
24731
27046
|
return void 0;
|
|
24732
27047
|
}
|
|
27048
|
+
function rejectUnknownFlags(flags) {
|
|
27049
|
+
const unknown = validateKnownFlags(flags);
|
|
27050
|
+
if (unknown === void 0) {
|
|
27051
|
+
return;
|
|
27052
|
+
}
|
|
27053
|
+
process.stderr.write(`hydra-acp: unknown flag: --${unknown}
|
|
27054
|
+
|
|
27055
|
+
`);
|
|
27056
|
+
printHelp();
|
|
27057
|
+
process.exit(2);
|
|
27058
|
+
}
|
|
24733
27059
|
function readShortPrompt(argv) {
|
|
24734
27060
|
for (let i = 0; i < argv.length; i += 1) {
|
|
24735
27061
|
const tok = argv[i];
|
|
@@ -24802,15 +27128,17 @@ function printHelp() {
|
|
|
24802
27128
|
" --reattach Pick the most-recent session for the current cwd.",
|
|
24803
27129
|
" --new Force a fresh session.",
|
|
24804
27130
|
" --readonly Open a session as a transcript viewer (requires --session).",
|
|
27131
|
+
" --dangerously-skip-permissions Auto-approve every tool permission request (tui / shim / launch / cat).",
|
|
24805
27132
|
" HYDRA_ACP_SESSION Env var equivalent of --session (flag wins).",
|
|
24806
27133
|
" hydra-acp init [--rotate-token] Initialize ~/.hydra-acp/config.json",
|
|
24807
27134
|
" hydra-acp daemon [status] Show daemon pid/version (default when no subcommand)",
|
|
24808
27135
|
" hydra-acp daemon start [--foreground] Start daemon (detached by default; --foreground to attach)",
|
|
24809
27136
|
" hydra-acp daemon stop|restart",
|
|
24810
27137
|
" hydra-acp daemon logs [-f] [-n N] Tail or follow the daemon log",
|
|
24811
|
-
" hydra-acp session [list] [--all] [--json] [--host=<host>]",
|
|
27138
|
+
" hydra-acp session [list] [--all] [--json] [--host=<host>] [--include-cat]",
|
|
24812
27139
|
" List sessions (live + 20 most-recent cold; --all for everything; --json emits JSON for scripts).",
|
|
24813
27140
|
" --host filters by origin machine: 'local' (default) shows only sessions created here, 'all' shows everything, or pass a hostname (e.g. machine-b) to show only imports from that peer.",
|
|
27141
|
+
" --include-cat surfaces sessions spawned by `hydra cat` (hidden by default).",
|
|
24814
27142
|
" hydra-acp session kill <id> Demote a live session to cold (keeps the on-disk record)",
|
|
24815
27143
|
" hydra-acp session remove <id> Remove a session entirely (live or cold)",
|
|
24816
27144
|
" hydra-acp session export <id> [--out <file>|.]",
|