@hydra-acp/cli 0.1.49 → 0.1.51
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 +923 -97
- package/dist/index.d.ts +100 -0
- package/dist/index.js +656 -26
- 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"),
|
|
@@ -1547,18 +1554,34 @@ var init_connection = __esm({
|
|
|
1547
1554
|
|
|
1548
1555
|
// src/core/stream-buffer.ts
|
|
1549
1556
|
import * as fsp3 from "fs/promises";
|
|
1550
|
-
|
|
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;
|
|
1551
1561
|
var init_stream_buffer = __esm({
|
|
1552
1562
|
"src/core/stream-buffer.ts"() {
|
|
1553
1563
|
"use strict";
|
|
1554
|
-
DEFAULT_CAPACITY_BYTES =
|
|
1564
|
+
DEFAULT_CAPACITY_BYTES = 64 * 1024 * 1024;
|
|
1565
|
+
INITIAL_CAPACITY_BYTES = 1 * 1024 * 1024;
|
|
1555
1566
|
STREAM_READ_MAX_BYTES = 64 * 1024;
|
|
1556
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;
|
|
1557
1573
|
SessionStreamBuffer = class {
|
|
1558
1574
|
storage;
|
|
1559
|
-
|
|
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;
|
|
1560
1583
|
// Absolute monotonic byte offset of the next byte to be written. Also
|
|
1561
|
-
// the count of bytes ever appended. `writeCursor -
|
|
1584
|
+
// the count of bytes ever appended. `writeCursor - currentCapacityBytes`
|
|
1562
1585
|
// (clamped at 0) is the oldest still-resident byte's cursor.
|
|
1563
1586
|
writeCursor = 0;
|
|
1564
1587
|
closed = false;
|
|
@@ -1573,24 +1596,33 @@ var init_stream_buffer = __esm({
|
|
|
1573
1596
|
// calls don't interleave their writes.
|
|
1574
1597
|
fileWriteChain = Promise.resolve();
|
|
1575
1598
|
constructor(opts = {}) {
|
|
1576
|
-
this.
|
|
1577
|
-
if (this.
|
|
1599
|
+
this.maxCapacityBytes = opts.capacityBytes ?? DEFAULT_CAPACITY_BYTES;
|
|
1600
|
+
if (this.maxCapacityBytes <= 0) {
|
|
1578
1601
|
throw new Error("capacityBytes must be > 0");
|
|
1579
1602
|
}
|
|
1580
|
-
this.
|
|
1603
|
+
this.currentCapacityBytes = Math.min(
|
|
1604
|
+
INITIAL_CAPACITY_BYTES,
|
|
1605
|
+
this.maxCapacityBytes
|
|
1606
|
+
);
|
|
1607
|
+
this.storage = Buffer.alloc(this.currentCapacityBytes);
|
|
1581
1608
|
this.filePath = opts.filePath;
|
|
1582
1609
|
this.fileCapBytes = opts.fileCapBytes ?? Number.POSITIVE_INFINITY;
|
|
1583
1610
|
this.onFileCapReached = opts.onFileCapReached;
|
|
1584
1611
|
this.logWriteError = opts.logWriteError;
|
|
1585
1612
|
}
|
|
1586
1613
|
get capacity() {
|
|
1587
|
-
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;
|
|
1588
1620
|
}
|
|
1589
1621
|
get writeCursorPos() {
|
|
1590
1622
|
return this.writeCursor;
|
|
1591
1623
|
}
|
|
1592
1624
|
get oldestAvailable() {
|
|
1593
|
-
return Math.max(0, this.writeCursor - this.
|
|
1625
|
+
return Math.max(0, this.writeCursor - this.currentCapacityBytes);
|
|
1594
1626
|
}
|
|
1595
1627
|
get isClosed() {
|
|
1596
1628
|
return this.closed;
|
|
@@ -1735,6 +1767,136 @@ var init_stream_buffer = __esm({
|
|
|
1735
1767
|
this.waiters.push(waiter);
|
|
1736
1768
|
});
|
|
1737
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
|
+
}
|
|
1738
1900
|
wakeWaiters(outcome) {
|
|
1739
1901
|
if (this.waiters.length === 0) {
|
|
1740
1902
|
return;
|
|
@@ -1745,15 +1907,42 @@ var init_stream_buffer = __esm({
|
|
|
1745
1907
|
w.resolve(outcome);
|
|
1746
1908
|
}
|
|
1747
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
|
+
}
|
|
1748
1936
|
writeRing(chunk) {
|
|
1749
1937
|
const len = chunk.length;
|
|
1750
|
-
|
|
1751
|
-
|
|
1938
|
+
this.growIfNeeded(len);
|
|
1939
|
+
if (len >= this.currentCapacityBytes) {
|
|
1940
|
+
const tailStart = len - this.currentCapacityBytes;
|
|
1752
1941
|
chunk.copy(this.storage, 0, tailStart, len);
|
|
1753
1942
|
return;
|
|
1754
1943
|
}
|
|
1755
|
-
const offset = this.writeCursor % this.
|
|
1756
|
-
const tailRoom = this.
|
|
1944
|
+
const offset = this.writeCursor % this.currentCapacityBytes;
|
|
1945
|
+
const tailRoom = this.currentCapacityBytes - offset;
|
|
1757
1946
|
if (len <= tailRoom) {
|
|
1758
1947
|
chunk.copy(this.storage, offset, 0, len);
|
|
1759
1948
|
} else {
|
|
@@ -1766,8 +1955,8 @@ var init_stream_buffer = __esm({
|
|
|
1766
1955
|
return Buffer.alloc(0);
|
|
1767
1956
|
}
|
|
1768
1957
|
const out = Buffer.alloc(length);
|
|
1769
|
-
const offset = fromCursor % this.
|
|
1770
|
-
const tailLen = Math.min(length, this.
|
|
1958
|
+
const offset = fromCursor % this.currentCapacityBytes;
|
|
1959
|
+
const tailLen = Math.min(length, this.currentCapacityBytes - offset);
|
|
1771
1960
|
this.storage.copy(out, 0, offset, offset + tailLen);
|
|
1772
1961
|
if (tailLen < length) {
|
|
1773
1962
|
this.storage.copy(out, tailLen, 0, length - tailLen);
|
|
@@ -2191,6 +2380,7 @@ var init_session = __esm({
|
|
|
2191
2380
|
agentCapabilities;
|
|
2192
2381
|
agentArgs;
|
|
2193
2382
|
parentSessionId;
|
|
2383
|
+
originatingClient;
|
|
2194
2384
|
title;
|
|
2195
2385
|
// Snapshot state delivered to attaching clients via the attach
|
|
2196
2386
|
// response _meta rather than via history replay (which would be
|
|
@@ -2345,6 +2535,7 @@ var init_session = __esm({
|
|
|
2345
2535
|
this.agentCapabilities = init.agentCapabilities;
|
|
2346
2536
|
this.agentArgs = init.agentArgs;
|
|
2347
2537
|
this.parentSessionId = init.parentSessionId;
|
|
2538
|
+
this.originatingClient = init.originatingClient;
|
|
2348
2539
|
this.title = init.title;
|
|
2349
2540
|
this.currentModel = init.currentModel;
|
|
2350
2541
|
this.currentMode = init.currentMode;
|
|
@@ -4532,6 +4723,43 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
4532
4723
|
}
|
|
4533
4724
|
return out;
|
|
4534
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
|
+
}
|
|
4535
4763
|
requireStreamBuffer() {
|
|
4536
4764
|
if (this.streamBuffer === void 0) {
|
|
4537
4765
|
const err = new Error(
|
|
@@ -5122,11 +5350,12 @@ function resolveVersion() {
|
|
|
5122
5350
|
}
|
|
5123
5351
|
return "0.0.0";
|
|
5124
5352
|
}
|
|
5125
|
-
var HYDRA_VERSION;
|
|
5353
|
+
var HYDRA_VERSION, HYDRA_CAT_CLIENT_NAME;
|
|
5126
5354
|
var init_hydra_version = __esm({
|
|
5127
5355
|
"src/core/hydra-version.ts"() {
|
|
5128
5356
|
"use strict";
|
|
5129
5357
|
HYDRA_VERSION = resolveVersion();
|
|
5358
|
+
HYDRA_CAT_CLIENT_NAME = "hydra-acp-cat";
|
|
5130
5359
|
}
|
|
5131
5360
|
});
|
|
5132
5361
|
|
|
@@ -5714,7 +5943,8 @@ async function listSessions(target, opts = {}, fetchImpl = fetch) {
|
|
|
5714
5943
|
title: s.title,
|
|
5715
5944
|
importedFromMachine: s.importedFromMachine,
|
|
5716
5945
|
importedFromUpstreamSessionId: s.importedFromUpstreamSessionId,
|
|
5717
|
-
busy: s.busy
|
|
5946
|
+
busy: s.busy,
|
|
5947
|
+
originatingClient: s.originatingClient
|
|
5718
5948
|
}));
|
|
5719
5949
|
}
|
|
5720
5950
|
async function killSession(target, id, fetchImpl = fetch) {
|
|
@@ -6234,6 +6464,47 @@ var init_resilient_ws = __esm({
|
|
|
6234
6464
|
}
|
|
6235
6465
|
});
|
|
6236
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
|
+
|
|
6237
6508
|
// src/core/update-check.ts
|
|
6238
6509
|
function disabled() {
|
|
6239
6510
|
if (process.env.NO_UPDATE_NOTIFIER === "1") {
|
|
@@ -10582,7 +10853,9 @@ var init_import_action_prompt = __esm({
|
|
|
10582
10853
|
|
|
10583
10854
|
// src/tui/picker.ts
|
|
10584
10855
|
function createPickerPrefs() {
|
|
10585
|
-
return {
|
|
10856
|
+
return {
|
|
10857
|
+
filters: { cwdOnly: false, hostFilter: "__local", showCat: false }
|
|
10858
|
+
};
|
|
10586
10859
|
}
|
|
10587
10860
|
async function pickSession(term, opts) {
|
|
10588
10861
|
process.stdout.write("\x1B[<u");
|
|
@@ -10624,6 +10897,11 @@ async function pickSession(term, opts) {
|
|
|
10624
10897
|
if (prefs.filters.cwdOnly) {
|
|
10625
10898
|
base = base.filter((s) => s.cwd === opts.cwd);
|
|
10626
10899
|
}
|
|
10900
|
+
if (!prefs.filters.showCat) {
|
|
10901
|
+
base = base.filter(
|
|
10902
|
+
(s) => s.originatingClient?.name !== HYDRA_CAT_CLIENT_NAME
|
|
10903
|
+
);
|
|
10904
|
+
}
|
|
10627
10905
|
base = filterByHost(base, prefs.filters.hostFilter);
|
|
10628
10906
|
return base;
|
|
10629
10907
|
};
|
|
@@ -10816,6 +11094,9 @@ async function pickSession(term, opts) {
|
|
|
10816
11094
|
prefs.filters.hostFilter === "__local" ? "host: local" : `host: ${prefs.filters.hostFilter}`
|
|
10817
11095
|
);
|
|
10818
11096
|
}
|
|
11097
|
+
if (prefs.filters.showCat) {
|
|
11098
|
+
parts.push("+cat");
|
|
11099
|
+
}
|
|
10819
11100
|
if (above > 0) {
|
|
10820
11101
|
parts.push(`\u2191 ${above} above`);
|
|
10821
11102
|
}
|
|
@@ -12012,6 +12293,14 @@ ${cells}`;
|
|
|
12012
12293
|
renderFromScratch();
|
|
12013
12294
|
return;
|
|
12014
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);
|
|
12301
|
+
renderFromScratch();
|
|
12302
|
+
return;
|
|
12303
|
+
}
|
|
12015
12304
|
if (name === "r" || name === "R") {
|
|
12016
12305
|
const currentId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
12017
12306
|
void refresh(currentId);
|
|
@@ -12238,6 +12527,7 @@ var init_picker = __esm({
|
|
|
12238
12527
|
init_session_row();
|
|
12239
12528
|
init_paths();
|
|
12240
12529
|
init_session();
|
|
12530
|
+
init_hydra_version();
|
|
12241
12531
|
init_discovery();
|
|
12242
12532
|
init_input();
|
|
12243
12533
|
init_screen();
|
|
@@ -12262,6 +12552,7 @@ var init_picker = __esm({
|
|
|
12262
12552
|
["^f", "find in session history (content + tool inputs)"],
|
|
12263
12553
|
["o", "toggle cwd-only filter"],
|
|
12264
12554
|
["h", "cycle host filter (local / <peer> / all)"],
|
|
12555
|
+
["i", "toggle include-cat filter"],
|
|
12265
12556
|
["r", "refresh from daemon"],
|
|
12266
12557
|
null,
|
|
12267
12558
|
["k", "kill the selected live session"],
|
|
@@ -13798,6 +14089,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
13798
14089
|
if (teardownStarted) {
|
|
13799
14090
|
return { outcome: { outcome: "cancelled" } };
|
|
13800
14091
|
}
|
|
14092
|
+
if (opts.dangerouslySkipPermissions) {
|
|
14093
|
+
return buildApproveResponse(params);
|
|
14094
|
+
}
|
|
13801
14095
|
const p = params ?? {};
|
|
13802
14096
|
const rawOptions = Array.isArray(p.options) ? p.options : [];
|
|
13803
14097
|
const options = rawOptions.map((o) => ({
|
|
@@ -15810,6 +16104,7 @@ var init_app = __esm({
|
|
|
15810
16104
|
init_session();
|
|
15811
16105
|
init_paths();
|
|
15812
16106
|
init_hydra_version();
|
|
16107
|
+
init_permission_pick();
|
|
15813
16108
|
init_update_check();
|
|
15814
16109
|
init_history();
|
|
15815
16110
|
init_discovery();
|
|
@@ -15878,12 +16173,14 @@ import { dirname as dirname6, resolve as resolve5 } from "path";
|
|
|
15878
16173
|
// src/cli/parse-args.ts
|
|
15879
16174
|
var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
15880
16175
|
"all",
|
|
16176
|
+
"dangerously-skip-permissions",
|
|
15881
16177
|
"detach",
|
|
15882
16178
|
"disabled",
|
|
15883
16179
|
"follow",
|
|
15884
16180
|
"force",
|
|
15885
16181
|
"foreground",
|
|
15886
16182
|
"help",
|
|
16183
|
+
"include-cat",
|
|
15887
16184
|
"info",
|
|
15888
16185
|
"json",
|
|
15889
16186
|
"new",
|
|
@@ -15891,7 +16188,6 @@ var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
15891
16188
|
"readonly",
|
|
15892
16189
|
"replace",
|
|
15893
16190
|
"rotate-token",
|
|
15894
|
-
"stream",
|
|
15895
16191
|
"version"
|
|
15896
16192
|
]);
|
|
15897
16193
|
var KNOWN_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -15907,7 +16203,6 @@ var KNOWN_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
15907
16203
|
"prompt",
|
|
15908
16204
|
"session",
|
|
15909
16205
|
"stream-bytes",
|
|
15910
|
-
"stream-file-cap",
|
|
15911
16206
|
"stream-threshold",
|
|
15912
16207
|
"tail"
|
|
15913
16208
|
]);
|
|
@@ -17065,6 +17360,10 @@ var PersistedUsage = z4.object({
|
|
|
17065
17360
|
costCurrency: z4.string().optional(),
|
|
17066
17361
|
cumulativeCost: z4.number().optional()
|
|
17067
17362
|
});
|
|
17363
|
+
var PersistedOriginatingClient = z4.object({
|
|
17364
|
+
name: z4.string(),
|
|
17365
|
+
version: z4.string().optional()
|
|
17366
|
+
});
|
|
17068
17367
|
var SessionRecord = z4.object({
|
|
17069
17368
|
version: z4.literal(1),
|
|
17070
17369
|
sessionId: z4.string(),
|
|
@@ -17115,6 +17414,10 @@ var SessionRecord = z4.object({
|
|
|
17115
17414
|
// Set when this session was spawned as a child by a transformer via
|
|
17116
17415
|
// hydra-acp/spawn_child_session. Points to the spawning session's id.
|
|
17117
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(),
|
|
17118
17421
|
createdAt: z4.string(),
|
|
17119
17422
|
updatedAt: z4.string()
|
|
17120
17423
|
});
|
|
@@ -17237,6 +17540,7 @@ function recordFromMemorySession(args) {
|
|
|
17237
17540
|
agentModels: args.agentModels,
|
|
17238
17541
|
pendingHistorySync: args.pendingHistorySync,
|
|
17239
17542
|
parentSessionId: args.parentSessionId,
|
|
17543
|
+
originatingClient: args.originatingClient,
|
|
17240
17544
|
createdAt: args.createdAt ?? now,
|
|
17241
17545
|
updatedAt: args.updatedAt ?? now
|
|
17242
17546
|
};
|
|
@@ -17495,6 +17799,7 @@ var SessionManager = class {
|
|
|
17495
17799
|
agentModels: fresh.initialModels,
|
|
17496
17800
|
transformChain: params.transformChain,
|
|
17497
17801
|
parentSessionId: params.parentSessionId,
|
|
17802
|
+
originatingClient: params.originatingClient,
|
|
17498
17803
|
extensionCommands: this.extensionCommands
|
|
17499
17804
|
});
|
|
17500
17805
|
await this.attachManagerHooks(session);
|
|
@@ -17666,6 +17971,7 @@ var SessionManager = class {
|
|
|
17666
17971
|
// than stay stuck.
|
|
17667
17972
|
firstPromptSeeded: !!params.title,
|
|
17668
17973
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
17974
|
+
originatingClient: params.originatingClient,
|
|
17669
17975
|
extensionCommands: this.extensionCommands
|
|
17670
17976
|
});
|
|
17671
17977
|
await this.attachManagerHooks(session);
|
|
@@ -17733,6 +18039,7 @@ var SessionManager = class {
|
|
|
17733
18039
|
agentModels: advertisedModels,
|
|
17734
18040
|
firstPromptSeeded: !!params.title,
|
|
17735
18041
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
18042
|
+
originatingClient: params.originatingClient,
|
|
17736
18043
|
extensionCommands: this.extensionCommands
|
|
17737
18044
|
});
|
|
17738
18045
|
await this.attachManagerHooks(session);
|
|
@@ -18082,7 +18389,8 @@ var SessionManager = class {
|
|
|
18082
18389
|
agentModes: record.agentModes,
|
|
18083
18390
|
agentModels: record.agentModels,
|
|
18084
18391
|
createdAt: record.createdAt,
|
|
18085
|
-
pendingHistorySync: record.pendingHistorySync
|
|
18392
|
+
pendingHistorySync: record.pendingHistorySync,
|
|
18393
|
+
originatingClient: record.originatingClient
|
|
18086
18394
|
};
|
|
18087
18395
|
}
|
|
18088
18396
|
async clearPendingHistorySync(sessionId) {
|
|
@@ -18183,6 +18491,7 @@ var SessionManager = class {
|
|
|
18183
18491
|
currentModel: session.currentModel,
|
|
18184
18492
|
currentUsage: session.currentUsage,
|
|
18185
18493
|
parentSessionId: session.parentSessionId,
|
|
18494
|
+
originatingClient: session.originatingClient,
|
|
18186
18495
|
updatedAt: used,
|
|
18187
18496
|
attachedClients: session.attachedCount,
|
|
18188
18497
|
status: "live",
|
|
@@ -18212,6 +18521,7 @@ var SessionManager = class {
|
|
|
18212
18521
|
importedFromMachine: r.importedFromMachine,
|
|
18213
18522
|
importedFromUpstreamSessionId: r.importedFromUpstreamSessionId,
|
|
18214
18523
|
parentSessionId: r.parentSessionId,
|
|
18524
|
+
originatingClient: r.originatingClient,
|
|
18215
18525
|
updatedAt: used,
|
|
18216
18526
|
attachedClients: 0,
|
|
18217
18527
|
status: "cold",
|
|
@@ -18557,6 +18867,7 @@ function mergeForPersistence(session, existing) {
|
|
|
18557
18867
|
agentModes,
|
|
18558
18868
|
agentModels,
|
|
18559
18869
|
parentSessionId: session.parentSessionId ?? existing?.parentSessionId,
|
|
18870
|
+
originatingClient: session.originatingClient ?? existing?.originatingClient,
|
|
18560
18871
|
createdAt: existing?.createdAt ?? new Date(session.createdAt).toISOString()
|
|
18561
18872
|
});
|
|
18562
18873
|
}
|
|
@@ -21705,6 +22016,7 @@ import { nanoid as nanoid2 } from "nanoid";
|
|
|
21705
22016
|
import * as os5 from "os";
|
|
21706
22017
|
import * as path13 from "path";
|
|
21707
22018
|
init_hydra_version();
|
|
22019
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
21708
22020
|
function registerAcpWsEndpoint(app, deps) {
|
|
21709
22021
|
app.get("/acp", { websocket: true }, async (socket, request) => {
|
|
21710
22022
|
const token = tokenFromUpgradeRequest({
|
|
@@ -21742,6 +22054,12 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
21742
22054
|
};
|
|
21743
22055
|
connection.onRequest("initialize", async (raw) => {
|
|
21744
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
|
+
}
|
|
21745
22063
|
const version = params.clientInfo?.version;
|
|
21746
22064
|
if (version && processIdentity) {
|
|
21747
22065
|
if (processIdentity.kind === "extension") {
|
|
@@ -21921,16 +22239,50 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
21921
22239
|
);
|
|
21922
22240
|
const transformerNames = Array.isArray(hydraMeta.transformers) && hydraMeta.transformers.every((t) => typeof t === "string") ? hydraMeta.transformers : deps.manager.defaultTransformers ?? [];
|
|
21923
22241
|
const transformChain = deps.transformers?.resolveChain(transformerNames) ?? [];
|
|
21924
|
-
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
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
|
+
}
|
|
21934
22286
|
const client = bindClientToSession(connection, session, state);
|
|
21935
22287
|
const { entries: replay } = await session.attach(client, "full");
|
|
21936
22288
|
state.attached.set(session.sessionId, {
|
|
@@ -22651,6 +23003,336 @@ function bindClientToSession(connection, session, state, clientInfo, callerClien
|
|
|
22651
23003
|
};
|
|
22652
23004
|
}
|
|
22653
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
|
+
|
|
22654
23336
|
// src/daemon/server.ts
|
|
22655
23337
|
async function startDaemon(config, serviceToken) {
|
|
22656
23338
|
ensureLoopbackOrTls(config);
|
|
@@ -22756,6 +23438,19 @@ async function startDaemon(config, serviceToken) {
|
|
|
22756
23438
|
store: sessionTokenStore,
|
|
22757
23439
|
rateLimiter: authRateLimiter
|
|
22758
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
|
+
};
|
|
22759
23454
|
registerAcpWsEndpoint(app, {
|
|
22760
23455
|
validator,
|
|
22761
23456
|
manager,
|
|
@@ -22764,7 +23459,9 @@ async function startDaemon(config, serviceToken) {
|
|
|
22764
23459
|
onExtensionVersion: (name, version) => extensions.reportVersion(name, version),
|
|
22765
23460
|
onTransformerVersion: (name, version) => transformers.reportVersion(name, version),
|
|
22766
23461
|
transformers,
|
|
22767
|
-
extensionCommands
|
|
23462
|
+
extensionCommands,
|
|
23463
|
+
stdinMcpRegistry,
|
|
23464
|
+
getDaemonOrigin
|
|
22768
23465
|
});
|
|
22769
23466
|
await app.listen({ host: config.daemon.host, port: config.daemon.port });
|
|
22770
23467
|
const address = app.server.address();
|
|
@@ -23020,14 +23717,14 @@ async function runDaemonStart(flags = {}) {
|
|
|
23020
23717
|
}
|
|
23021
23718
|
if (flagBool(flags, "foreground")) {
|
|
23022
23719
|
process.title = "hydra-daemon";
|
|
23023
|
-
const
|
|
23720
|
+
const handle2 = await startDaemon(config, serviceToken);
|
|
23024
23721
|
process.stdout.write(
|
|
23025
23722
|
`hydra-acp daemon listening on ${config.daemon.host}:${config.daemon.port}
|
|
23026
23723
|
`
|
|
23027
23724
|
);
|
|
23028
23725
|
const shutdown = async () => {
|
|
23029
23726
|
process.stdout.write("Shutting down...\n");
|
|
23030
|
-
await
|
|
23727
|
+
await handle2.shutdown();
|
|
23031
23728
|
process.exit(0);
|
|
23032
23729
|
};
|
|
23033
23730
|
process.on("SIGINT", () => void shutdown());
|
|
@@ -23197,6 +23894,7 @@ init_remote_target();
|
|
|
23197
23894
|
init_remote_url();
|
|
23198
23895
|
init_session();
|
|
23199
23896
|
init_discovery();
|
|
23897
|
+
init_hydra_version();
|
|
23200
23898
|
import * as fs19 from "fs/promises";
|
|
23201
23899
|
import * as path14 from "path";
|
|
23202
23900
|
init_session_row();
|
|
@@ -23214,10 +23912,13 @@ async function runSessionsList(opts = {}) {
|
|
|
23214
23912
|
process.exit(1);
|
|
23215
23913
|
}
|
|
23216
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
|
+
);
|
|
23217
23918
|
const host = opts.host ?? "local";
|
|
23218
|
-
const hostFiltered = host === "all" ?
|
|
23919
|
+
const hostFiltered = host === "all" ? sessionsAfterCatFilter : host === "local" ? sessionsAfterCatFilter.filter(
|
|
23219
23920
|
(s) => !s.importedFromMachine || !!s.upstreamSessionId
|
|
23220
|
-
) :
|
|
23921
|
+
) : sessionsAfterCatFilter.filter(
|
|
23221
23922
|
(s) => s.importedFromMachine === host && !s.upstreamSessionId
|
|
23222
23923
|
);
|
|
23223
23924
|
if (opts.json) {
|
|
@@ -25068,6 +25769,9 @@ function isResponse2(msg) {
|
|
|
25068
25769
|
return !("method" in msg) && "id" in msg;
|
|
25069
25770
|
}
|
|
25070
25771
|
|
|
25772
|
+
// src/shim/proxy.ts
|
|
25773
|
+
init_permission_pick();
|
|
25774
|
+
|
|
25071
25775
|
// src/core/process-title.ts
|
|
25072
25776
|
init_bin_name();
|
|
25073
25777
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -25152,6 +25856,14 @@ function wireShim({
|
|
|
25152
25856
|
}) {
|
|
25153
25857
|
upstream.onMessage((msg) => {
|
|
25154
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
|
+
}
|
|
25155
25867
|
maybeReplyToResolvedPermission(msg, tracker, downstream);
|
|
25156
25868
|
void downstream.send(msg);
|
|
25157
25869
|
});
|
|
@@ -25301,6 +26013,9 @@ async function replayAttach(stream, ctx, afterMessageId) {
|
|
|
25301
26013
|
function isSessionNewRequest(msg) {
|
|
25302
26014
|
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "session/new";
|
|
25303
26015
|
}
|
|
26016
|
+
function isPermissionRequest(msg) {
|
|
26017
|
+
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "session/request_permission";
|
|
26018
|
+
}
|
|
25304
26019
|
function buildAttachFromNew(msg, sessionId) {
|
|
25305
26020
|
return {
|
|
25306
26021
|
jsonrpc: "2.0",
|
|
@@ -25347,6 +26062,10 @@ init_daemon_bootstrap();
|
|
|
25347
26062
|
init_render_update();
|
|
25348
26063
|
init_types();
|
|
25349
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";
|
|
25350
26069
|
import { WebSocket as WebSocket2 } from "ws";
|
|
25351
26070
|
|
|
25352
26071
|
// src/cli/commands/cat-chunker.ts
|
|
@@ -25401,8 +26120,39 @@ function createChunker(opts) {
|
|
|
25401
26120
|
}
|
|
25402
26121
|
|
|
25403
26122
|
// src/cli/commands/cat.ts
|
|
25404
|
-
var DEFAULT_STREAM_THRESHOLD =
|
|
25405
|
-
var
|
|
26123
|
+
var DEFAULT_STREAM_THRESHOLD = 1 * 1024 * 1024;
|
|
26124
|
+
var HYDRA_STDIN_TOOL_PREFIX = "mcp__hydra_stdin__";
|
|
26125
|
+
function deriveTitleFromPrompt(prompt) {
|
|
26126
|
+
if (!prompt) {
|
|
26127
|
+
return void 0;
|
|
26128
|
+
}
|
|
26129
|
+
for (const raw of prompt.split(/\r?\n/)) {
|
|
26130
|
+
const line = raw.trim();
|
|
26131
|
+
if (!line) {
|
|
26132
|
+
continue;
|
|
26133
|
+
}
|
|
26134
|
+
return line.length > 80 ? `${line.slice(0, 80)}\u2026` : line;
|
|
26135
|
+
}
|
|
26136
|
+
return void 0;
|
|
26137
|
+
}
|
|
26138
|
+
function isHydraStdinPermissionRequest(params) {
|
|
26139
|
+
if (!params || typeof params !== "object") {
|
|
26140
|
+
return false;
|
|
26141
|
+
}
|
|
26142
|
+
const toolCall = params.toolCall;
|
|
26143
|
+
if (!toolCall || typeof toolCall !== "object") {
|
|
26144
|
+
return false;
|
|
26145
|
+
}
|
|
26146
|
+
const title = toolCall.title;
|
|
26147
|
+
if (typeof title === "string" && title.startsWith(HYDRA_STDIN_TOOL_PREFIX)) {
|
|
26148
|
+
return true;
|
|
26149
|
+
}
|
|
26150
|
+
const toolName = toolCall.toolName;
|
|
26151
|
+
if (typeof toolName === "string" && toolName.startsWith(HYDRA_STDIN_TOOL_PREFIX)) {
|
|
26152
|
+
return true;
|
|
26153
|
+
}
|
|
26154
|
+
return false;
|
|
26155
|
+
}
|
|
25406
26156
|
async function runCat(opts) {
|
|
25407
26157
|
setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
|
|
25408
26158
|
if (process.stdin.isTTY && !opts.prompt && !opts.sessionId) {
|
|
@@ -25412,6 +26162,18 @@ async function runCat(opts) {
|
|
|
25412
26162
|
process.exit(2);
|
|
25413
26163
|
return;
|
|
25414
26164
|
}
|
|
26165
|
+
if (!opts.sessionId && opts.cwd === void 0 && process.stdin.isTTY !== true) {
|
|
26166
|
+
const sandbox = mkdtempSync(join11(tmpdir2(), "hydra-cat-"));
|
|
26167
|
+
opts.cwd = sandbox;
|
|
26168
|
+
if (!opts.detach) {
|
|
26169
|
+
process.on("exit", () => {
|
|
26170
|
+
try {
|
|
26171
|
+
rmSync(sandbox, { recursive: true, force: true });
|
|
26172
|
+
} catch {
|
|
26173
|
+
}
|
|
26174
|
+
});
|
|
26175
|
+
}
|
|
26176
|
+
}
|
|
25415
26177
|
const config = await loadConfig();
|
|
25416
26178
|
const target = opts.target ?? await resolveLocalTarget(config);
|
|
25417
26179
|
if (target.isLocal && !opts.target) {
|
|
@@ -25435,9 +26197,19 @@ async function runCat(opts) {
|
|
|
25435
26197
|
}
|
|
25436
26198
|
async function runCatLoop(args) {
|
|
25437
26199
|
const { conn, opts, stdin, stdinIsTty, stdout, stderr } = args;
|
|
26200
|
+
const useAutoStream = !stdinIsTty && opts.sessionId === void 0 && opts.follow !== true;
|
|
25438
26201
|
conn.setDefaultHandler(async () => {
|
|
25439
26202
|
return { error: { code: -32601, message: "method not implemented" } };
|
|
25440
26203
|
});
|
|
26204
|
+
conn.onRequest("session/request_permission", async (params) => {
|
|
26205
|
+
if (opts.dangerouslySkipPermissions) {
|
|
26206
|
+
return buildApproveResponse(params);
|
|
26207
|
+
}
|
|
26208
|
+
if (!isHydraStdinPermissionRequest(params)) {
|
|
26209
|
+
return buildRejectResponse(params);
|
|
26210
|
+
}
|
|
26211
|
+
return buildApproveResponse(params);
|
|
26212
|
+
});
|
|
25441
26213
|
try {
|
|
25442
26214
|
await conn.request("initialize", {
|
|
25443
26215
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
@@ -25445,11 +26217,11 @@ async function runCatLoop(args) {
|
|
|
25445
26217
|
fs: { readTextFile: false, writeTextFile: false },
|
|
25446
26218
|
terminal: false
|
|
25447
26219
|
},
|
|
25448
|
-
clientInfo: { name:
|
|
26220
|
+
clientInfo: { name: HYDRA_CAT_CLIENT_NAME, version: HYDRA_VERSION }
|
|
25449
26221
|
});
|
|
25450
26222
|
} catch {
|
|
25451
26223
|
}
|
|
25452
|
-
const sessionId = await openOrAttachSession(conn, opts);
|
|
26224
|
+
const sessionId = await openOrAttachSession(conn, opts, useAutoStream);
|
|
25453
26225
|
let turnHadOutput = false;
|
|
25454
26226
|
let lastCharWasNewline = true;
|
|
25455
26227
|
const writeStdout = (text) => {
|
|
@@ -25478,9 +26250,10 @@ async function runCatLoop(args) {
|
|
|
25478
26250
|
finalizeTurn();
|
|
25479
26251
|
}
|
|
25480
26252
|
});
|
|
26253
|
+
let firstChunkSent = false;
|
|
25481
26254
|
const sendChunk = async (text) => {
|
|
25482
26255
|
const promptBlocks = [];
|
|
25483
|
-
if (opts.prompt) {
|
|
26256
|
+
if (opts.prompt && !firstChunkSent) {
|
|
25484
26257
|
promptBlocks.push({ type: "text", text: opts.prompt });
|
|
25485
26258
|
}
|
|
25486
26259
|
if (text.length > 0) {
|
|
@@ -25494,6 +26267,7 @@ async function runCatLoop(args) {
|
|
|
25494
26267
|
sessionId,
|
|
25495
26268
|
prompt: promptBlocks
|
|
25496
26269
|
});
|
|
26270
|
+
firstChunkSent = true;
|
|
25497
26271
|
} catch (err) {
|
|
25498
26272
|
stderr(`hydra-acp cat: prompt failed: ${err.message}
|
|
25499
26273
|
`);
|
|
@@ -25552,22 +26326,6 @@ async function runCatLoop(args) {
|
|
|
25552
26326
|
}
|
|
25553
26327
|
}
|
|
25554
26328
|
};
|
|
25555
|
-
const chunker = createChunker({
|
|
25556
|
-
// setImmediate fires in the libuv "check" phase, after pending
|
|
25557
|
-
// I/O has been polled and any back-to-back "data" events have
|
|
25558
|
-
// been emitted. That makes it the natural hook for "the writer
|
|
25559
|
-
// has paused, time to flush": if more bytes were sitting in the
|
|
25560
|
-
// pipe buffer, Node would have emitted another "data" event
|
|
25561
|
-
// before this fires, and the chunker would detect that and defer.
|
|
25562
|
-
scheduleFlushCheck: (cb) => {
|
|
25563
|
-
const h = setImmediate(cb);
|
|
25564
|
-
return () => clearImmediate(h);
|
|
25565
|
-
},
|
|
25566
|
-
onChunk: (text) => {
|
|
25567
|
-
chunkQueue.push(text);
|
|
25568
|
-
void drainQueue();
|
|
25569
|
-
}
|
|
25570
|
-
});
|
|
25571
26329
|
if (stdinIsTty && !opts.sessionId) {
|
|
25572
26330
|
if (opts.prompt) {
|
|
25573
26331
|
await sendChunk("");
|
|
@@ -25575,7 +26333,7 @@ async function runCatLoop(args) {
|
|
|
25575
26333
|
await settle(0);
|
|
25576
26334
|
return done;
|
|
25577
26335
|
}
|
|
25578
|
-
if (
|
|
26336
|
+
if (useAutoStream) {
|
|
25579
26337
|
if (typeof stdin.setEncoding === "function") {
|
|
25580
26338
|
stdin.setEncoding("utf8");
|
|
25581
26339
|
}
|
|
@@ -25616,15 +26374,49 @@ async function runCatLoop(args) {
|
|
|
25616
26374
|
if (typeof stdin.setEncoding === "function") {
|
|
25617
26375
|
stdin.setEncoding("utf8");
|
|
25618
26376
|
}
|
|
26377
|
+
const useFollow = opts.follow === true || stdinIsTty && Boolean(opts.sessionId);
|
|
26378
|
+
if (useFollow) {
|
|
26379
|
+
const chunker = createChunker({
|
|
26380
|
+
scheduleFlushCheck: (cb) => {
|
|
26381
|
+
const h = setImmediate(cb);
|
|
26382
|
+
return () => clearImmediate(h);
|
|
26383
|
+
},
|
|
26384
|
+
onChunk: (text) => {
|
|
26385
|
+
chunkQueue.push(text);
|
|
26386
|
+
void drainQueue();
|
|
26387
|
+
}
|
|
26388
|
+
});
|
|
26389
|
+
stdin.on("data", (data) => {
|
|
26390
|
+
chunker.feed(typeof data === "string" ? data : data.toString("utf8"));
|
|
26391
|
+
});
|
|
26392
|
+
stdin.on("end", () => {
|
|
26393
|
+
chunker.eof();
|
|
26394
|
+
stdinEnded = true;
|
|
26395
|
+
if (!draining && chunkQueue.length === 0) {
|
|
26396
|
+
void settle(exitCode);
|
|
26397
|
+
}
|
|
26398
|
+
});
|
|
26399
|
+
stdin.on("error", (err) => {
|
|
26400
|
+
stderr(`hydra-acp cat: stdin error: ${err.message}
|
|
26401
|
+
`);
|
|
26402
|
+
exitCode = 1;
|
|
26403
|
+
stdinEnded = true;
|
|
26404
|
+
if (!draining && chunkQueue.length === 0) {
|
|
26405
|
+
void settle(exitCode);
|
|
26406
|
+
}
|
|
26407
|
+
});
|
|
26408
|
+
return done;
|
|
26409
|
+
}
|
|
26410
|
+
let oneShotBuffer = "";
|
|
25619
26411
|
stdin.on("data", (data) => {
|
|
25620
|
-
|
|
26412
|
+
oneShotBuffer += typeof data === "string" ? data : data.toString("utf8");
|
|
25621
26413
|
});
|
|
25622
26414
|
stdin.on("end", () => {
|
|
25623
|
-
chunker.eof();
|
|
25624
26415
|
stdinEnded = true;
|
|
25625
|
-
if (
|
|
25626
|
-
|
|
26416
|
+
if (oneShotBuffer.length > 0) {
|
|
26417
|
+
chunkQueue.push(oneShotBuffer);
|
|
25627
26418
|
}
|
|
26419
|
+
void drainQueue();
|
|
25628
26420
|
});
|
|
25629
26421
|
stdin.on("error", (err) => {
|
|
25630
26422
|
stderr(`hydra-acp cat: stdin error: ${err.message}
|
|
@@ -25677,12 +26469,11 @@ function runStreamingPath(args) {
|
|
|
25677
26469
|
try {
|
|
25678
26470
|
const openParams = {
|
|
25679
26471
|
sessionId,
|
|
25680
|
-
mode: "
|
|
26472
|
+
mode: "memory"
|
|
25681
26473
|
};
|
|
25682
26474
|
if (opts.streamBufferBytes !== void 0) {
|
|
25683
26475
|
openParams.capacityBytes = opts.streamBufferBytes;
|
|
25684
26476
|
}
|
|
25685
|
-
openParams.fileCapBytes = opts.streamFileCapBytes ?? DEFAULT_STREAM_FILE_CAP;
|
|
25686
26477
|
open2 = await conn.request("hydra-acp/stream_open", openParams);
|
|
25687
26478
|
} catch (err) {
|
|
25688
26479
|
args.onPromptFailed(
|
|
@@ -25690,23 +26481,12 @@ function runStreamingPath(args) {
|
|
|
25690
26481
|
);
|
|
25691
26482
|
return;
|
|
25692
26483
|
}
|
|
25693
|
-
const filePath = open2.filePath;
|
|
25694
|
-
if (filePath === void 0) {
|
|
25695
|
-
args.onPromptFailed(
|
|
25696
|
-
new Error("daemon did not return a filePath for stream mode")
|
|
25697
|
-
);
|
|
25698
|
-
return;
|
|
25699
|
-
}
|
|
25700
26484
|
if (headBuffer.length > 0) {
|
|
25701
26485
|
writeToStream(headBuffer, false);
|
|
25702
26486
|
headBuffer = Buffer.alloc(0);
|
|
25703
26487
|
}
|
|
25704
26488
|
await writeChain.catch(() => void 0);
|
|
25705
|
-
const promptText = buildStreamPromptText(
|
|
25706
|
-
opts.prompt,
|
|
25707
|
-
filePath,
|
|
25708
|
-
opts.streamFileCapBytes ?? DEFAULT_STREAM_FILE_CAP
|
|
25709
|
-
);
|
|
26489
|
+
const promptText = buildStreamPromptText(opts.prompt, open2.capacityBytes);
|
|
25710
26490
|
const promptDone = conn.request("session/prompt", {
|
|
25711
26491
|
sessionId,
|
|
25712
26492
|
prompt: [{ type: "text", text: promptText }]
|
|
@@ -25749,32 +26529,53 @@ function runStreamingPath(args) {
|
|
|
25749
26529
|
});
|
|
25750
26530
|
stdin.on("error", args.onError);
|
|
25751
26531
|
}
|
|
25752
|
-
function buildStreamPromptText(standing,
|
|
25753
|
-
const capHuman =
|
|
25754
|
-
const
|
|
26532
|
+
function buildStreamPromptText(standing, ringCapacityBytes) {
|
|
26533
|
+
const capHuman = ringCapacityBytes >= 1024 * 1024 ? `${(ringCapacityBytes / (1024 * 1024)).toFixed(0)} MB` : `${(ringCapacityBytes / 1024).toFixed(0)} KB`;
|
|
26534
|
+
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:
|
|
26535
|
+
- \`stdin_info()\` \u2014 current writeCursor / oldestAvailable / capacity / closed. Cheap; call first to see how much data is there.
|
|
26536
|
+
- \`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.
|
|
26537
|
+
- \`head_stdin({bytes})\` \u2014 first N bytes (good for headers / preamble / file signatures).
|
|
26538
|
+
- \`tail_stdin({bytes})\` \u2014 most recent N bytes (good for log endings / recent errors).
|
|
26539
|
+
- \`read_stdin({cursor, max_bytes, wait_ms})\` \u2014 windowed read at an absolute byte cursor; iterate to sweep the whole stream.
|
|
26540
|
+
- \`wait_for_more({cursor, timeout_ms})\` \u2014 block for new bytes past a cursor (only useful for live tails).
|
|
26541
|
+
|
|
26542
|
+
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.`;
|
|
25755
26543
|
if (standing && standing.length > 0) {
|
|
25756
|
-
return `${
|
|
26544
|
+
return `${toolNote}
|
|
26545
|
+
|
|
26546
|
+
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.
|
|
25757
26547
|
|
|
25758
|
-
|
|
26548
|
+
User's question:
|
|
26549
|
+
${standing}`;
|
|
25759
26550
|
}
|
|
25760
|
-
return
|
|
26551
|
+
return `${toolNote}
|
|
26552
|
+
|
|
26553
|
+
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.`;
|
|
25761
26554
|
}
|
|
25762
|
-
async function openOrAttachSession(conn, opts) {
|
|
26555
|
+
async function openOrAttachSession(conn, opts, useAutoStream) {
|
|
25763
26556
|
if (opts.sessionId) {
|
|
25764
26557
|
const attached = await conn.request("session/attach", {
|
|
25765
26558
|
sessionId: opts.sessionId,
|
|
25766
26559
|
historyPolicy: "pending_only",
|
|
25767
|
-
clientInfo: { name:
|
|
26560
|
+
clientInfo: { name: HYDRA_CAT_CLIENT_NAME, version: HYDRA_VERSION }
|
|
25768
26561
|
});
|
|
25769
26562
|
return attached.sessionId;
|
|
25770
26563
|
}
|
|
25771
26564
|
const hydraMeta = {};
|
|
25772
26565
|
if (opts.name) {
|
|
25773
26566
|
hydraMeta.name = opts.name;
|
|
26567
|
+
} else {
|
|
26568
|
+
const derived = deriveTitleFromPrompt(opts.prompt);
|
|
26569
|
+
if (derived) {
|
|
26570
|
+
hydraMeta.name = derived;
|
|
26571
|
+
}
|
|
25774
26572
|
}
|
|
25775
26573
|
if (opts.model) {
|
|
25776
26574
|
hydraMeta.model = opts.model;
|
|
25777
26575
|
}
|
|
26576
|
+
if (useAutoStream) {
|
|
26577
|
+
hydraMeta.mcpStdin = true;
|
|
26578
|
+
}
|
|
25778
26579
|
const cwd = opts.cwd ?? process.cwd();
|
|
25779
26580
|
const params = { cwd };
|
|
25780
26581
|
if (opts.agentId) {
|
|
@@ -25806,6 +26607,16 @@ async function openWs2(url, subprotocols) {
|
|
|
25806
26607
|
// src/cli.ts
|
|
25807
26608
|
init_update_check();
|
|
25808
26609
|
var suppressUpdateNotice = false;
|
|
26610
|
+
var dangerousNoticePrinted = false;
|
|
26611
|
+
function warnIfDangerouslySkipping(active) {
|
|
26612
|
+
if (!active || dangerousNoticePrinted) {
|
|
26613
|
+
return;
|
|
26614
|
+
}
|
|
26615
|
+
dangerousNoticePrinted = true;
|
|
26616
|
+
process.stderr.write(
|
|
26617
|
+
"hydra-acp: --dangerously-skip-permissions is set \u2014 all tool permission requests will be auto-approved.\n"
|
|
26618
|
+
);
|
|
26619
|
+
}
|
|
25809
26620
|
async function main() {
|
|
25810
26621
|
const argv = process.argv.slice(2);
|
|
25811
26622
|
const launchIdx = argv.indexOf("launch");
|
|
@@ -25838,11 +26649,14 @@ async function main() {
|
|
|
25838
26649
|
const name2 = resolveOption(flags2, "name");
|
|
25839
26650
|
const model2 = resolveOption(flags2, "model");
|
|
25840
26651
|
suppressUpdateNotice = true;
|
|
26652
|
+
const dangerous = flags2["dangerously-skip-permissions"] === true;
|
|
26653
|
+
warnIfDangerouslySkipping(dangerous);
|
|
25841
26654
|
const shimOpts = {
|
|
25842
26655
|
agentId,
|
|
25843
26656
|
agentArgs,
|
|
25844
26657
|
name: name2,
|
|
25845
|
-
model: model2
|
|
26658
|
+
model: model2,
|
|
26659
|
+
dangerouslySkipPermissions: dangerous
|
|
25846
26660
|
};
|
|
25847
26661
|
if (resolved2?.sessionId !== void 0) {
|
|
25848
26662
|
shimOpts.sessionId = resolved2.sessionId;
|
|
@@ -25864,10 +26678,14 @@ async function main() {
|
|
|
25864
26678
|
printHelp();
|
|
25865
26679
|
return;
|
|
25866
26680
|
}
|
|
25867
|
-
const
|
|
26681
|
+
const inlinePromptPresent = readShortPrompt(argv) !== void 0 || typeof flags.prompt === "string";
|
|
26682
|
+
const rawSubcommand = positional[0];
|
|
26683
|
+
const subcommand = rawSubcommand !== void 0 && !rawSubcommand.startsWith("-") ? rawSubcommand : inlinePromptPresent ? "cat" : rawSubcommand;
|
|
25868
26684
|
const name = resolveOption(flags, "name");
|
|
25869
26685
|
const agentIdFromFlag = resolveOption(flags, "agent");
|
|
25870
26686
|
const model = resolveOption(flags, "model");
|
|
26687
|
+
const dangerouslySkipPermissions = flags["dangerously-skip-permissions"] === true;
|
|
26688
|
+
warnIfDangerouslySkipping(dangerouslySkipPermissions);
|
|
25871
26689
|
const sessionInput = readSessionInput(flags);
|
|
25872
26690
|
const interactive = subcommand === "tui" || subcommand === void 0 && process.stdout.isTTY;
|
|
25873
26691
|
const resolved = await resolveSessionFlagOrExit(sessionInput, {
|
|
@@ -25889,7 +26707,8 @@ async function main() {
|
|
|
25889
26707
|
agentId: agentIdFromFlag,
|
|
25890
26708
|
name,
|
|
25891
26709
|
model,
|
|
25892
|
-
target: sessionTarget
|
|
26710
|
+
target: sessionTarget,
|
|
26711
|
+
dangerouslySkipPermissions
|
|
25893
26712
|
});
|
|
25894
26713
|
return;
|
|
25895
26714
|
}
|
|
@@ -25897,7 +26716,8 @@ async function main() {
|
|
|
25897
26716
|
const shimOpts = {
|
|
25898
26717
|
name,
|
|
25899
26718
|
model,
|
|
25900
|
-
agentId: agentIdFromFlag
|
|
26719
|
+
agentId: agentIdFromFlag,
|
|
26720
|
+
dangerouslySkipPermissions
|
|
25901
26721
|
};
|
|
25902
26722
|
if (sessionId !== void 0) {
|
|
25903
26723
|
shimOpts.sessionId = sessionId;
|
|
@@ -25914,7 +26734,8 @@ async function main() {
|
|
|
25914
26734
|
const shimOpts = {
|
|
25915
26735
|
name,
|
|
25916
26736
|
model,
|
|
25917
|
-
agentId: agentIdFromFlag
|
|
26737
|
+
agentId: agentIdFromFlag,
|
|
26738
|
+
dangerouslySkipPermissions
|
|
25918
26739
|
};
|
|
25919
26740
|
if (sessionId !== void 0) {
|
|
25920
26741
|
shimOpts.sessionId = sessionId;
|
|
@@ -25937,7 +26758,8 @@ async function main() {
|
|
|
25937
26758
|
model,
|
|
25938
26759
|
agentId: agentIdFromFlag,
|
|
25939
26760
|
detach: flags.detach === true,
|
|
25940
|
-
|
|
26761
|
+
follow: flags.follow === true,
|
|
26762
|
+
dangerouslySkipPermissions
|
|
25941
26763
|
};
|
|
25942
26764
|
if (cwd !== void 0) {
|
|
25943
26765
|
catOpts.cwd = cwd;
|
|
@@ -25953,10 +26775,6 @@ async function main() {
|
|
|
25953
26775
|
if (streamBufferBytes !== void 0) {
|
|
25954
26776
|
catOpts.streamBufferBytes = streamBufferBytes;
|
|
25955
26777
|
}
|
|
25956
|
-
const streamFileCap = parseNumericFlag(flags, "stream-file-cap");
|
|
25957
|
-
if (streamFileCap !== void 0) {
|
|
25958
|
-
catOpts.streamFileCapBytes = streamFileCap;
|
|
25959
|
-
}
|
|
25960
26778
|
suppressUpdateNotice = true;
|
|
25961
26779
|
await runCat(catOpts);
|
|
25962
26780
|
return;
|
|
@@ -26000,7 +26818,8 @@ async function main() {
|
|
|
26000
26818
|
await runSessionsList({
|
|
26001
26819
|
all: flags.all === true,
|
|
26002
26820
|
json: flags.json === true,
|
|
26003
|
-
host: typeof flags.host === "string" ? flags.host : void 0
|
|
26821
|
+
host: typeof flags.host === "string" ? flags.host : void 0,
|
|
26822
|
+
includeCat: flags["include-cat"] === true
|
|
26004
26823
|
});
|
|
26005
26824
|
return;
|
|
26006
26825
|
}
|
|
@@ -26182,7 +27001,8 @@ async function main() {
|
|
|
26182
27001
|
agentId: agentIdFromFlag,
|
|
26183
27002
|
name,
|
|
26184
27003
|
model,
|
|
26185
|
-
target: sessionTarget
|
|
27004
|
+
target: sessionTarget,
|
|
27005
|
+
dangerouslySkipPermissions
|
|
26186
27006
|
});
|
|
26187
27007
|
return;
|
|
26188
27008
|
default:
|
|
@@ -26224,6 +27044,9 @@ async function dispatchTui(flags, base) {
|
|
|
26224
27044
|
if (base.target !== void 0) {
|
|
26225
27045
|
tuiOpts.target = base.target;
|
|
26226
27046
|
}
|
|
27047
|
+
if (base.dangerouslySkipPermissions === true) {
|
|
27048
|
+
tuiOpts.dangerouslySkipPermissions = true;
|
|
27049
|
+
}
|
|
26227
27050
|
await runTui(tuiOpts);
|
|
26228
27051
|
}
|
|
26229
27052
|
function parseNumericFlag(flags, name) {
|
|
@@ -26296,6 +27119,7 @@ function printHelp() {
|
|
|
26296
27119
|
"Usage:",
|
|
26297
27120
|
" hydra-acp [--session <id-or-url>] [--reattach] [opts]",
|
|
26298
27121
|
" Auto: TUI when stdout is a TTY, shim otherwise (the editor-spawned case).",
|
|
27122
|
+
" With -p / --prompt and no subcommand, auto-dispatch to cat.",
|
|
26299
27123
|
" hydra-acp tui [same flags] Force TUI explicitly.",
|
|
26300
27124
|
" hydra-acp shim [same flags] Force shim explicitly (non-interactive; password prompts not allowed).",
|
|
26301
27125
|
" hydra-acp cat [-p <prompt>] [--session <id-or-url>] [--detach] [--agent <id>] [--model <id>] [--name <label>]",
|
|
@@ -26325,15 +27149,17 @@ function printHelp() {
|
|
|
26325
27149
|
" --reattach Pick the most-recent session for the current cwd.",
|
|
26326
27150
|
" --new Force a fresh session.",
|
|
26327
27151
|
" --readonly Open a session as a transcript viewer (requires --session).",
|
|
27152
|
+
" --dangerously-skip-permissions Auto-approve every tool permission request (tui / shim / launch / cat).",
|
|
26328
27153
|
" HYDRA_ACP_SESSION Env var equivalent of --session (flag wins).",
|
|
26329
27154
|
" hydra-acp init [--rotate-token] Initialize ~/.hydra-acp/config.json",
|
|
26330
27155
|
" hydra-acp daemon [status] Show daemon pid/version (default when no subcommand)",
|
|
26331
27156
|
" hydra-acp daemon start [--foreground] Start daemon (detached by default; --foreground to attach)",
|
|
26332
27157
|
" hydra-acp daemon stop|restart",
|
|
26333
27158
|
" hydra-acp daemon logs [-f] [-n N] Tail or follow the daemon log",
|
|
26334
|
-
" hydra-acp session [list] [--all] [--json] [--host=<host>]",
|
|
27159
|
+
" hydra-acp session [list] [--all] [--json] [--host=<host>] [--include-cat]",
|
|
26335
27160
|
" List sessions (live + 20 most-recent cold; --all for everything; --json emits JSON for scripts).",
|
|
26336
27161
|
" --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.",
|
|
27162
|
+
" --include-cat surfaces sessions spawned by `hydra cat` (hidden by default).",
|
|
26337
27163
|
" hydra-acp session kill <id> Demote a live session to cold (keeps the on-disk record)",
|
|
26338
27164
|
" hydra-acp session remove <id> Remove a session entirely (live or cold)",
|
|
26339
27165
|
" hydra-acp session export <id> [--out <file>|.]",
|