@hydra-acp/cli 0.1.49 → 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 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
- var DEFAULT_CAPACITY_BYTES, STREAM_READ_MAX_BYTES, STREAM_WAIT_MAX_MS, SessionStreamBuffer;
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 = 16 * 1024 * 1024;
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
- capacityBytes;
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 - capacityBytes`
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.capacityBytes = opts.capacityBytes ?? DEFAULT_CAPACITY_BYTES;
1577
- if (this.capacityBytes <= 0) {
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.storage = Buffer.alloc(this.capacityBytes);
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.capacityBytes;
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.capacityBytes);
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
- if (len >= this.capacityBytes) {
1751
- const tailStart = len - this.capacityBytes;
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.capacityBytes;
1756
- const tailRoom = this.capacityBytes - offset;
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.capacityBytes;
1770
- const tailLen = Math.min(length, this.capacityBytes - offset);
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 { filters: { cwdOnly: false, hostFilter: "__local" } };
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
- const session = await deps.manager.create({
21925
- cwd: params.cwd,
21926
- agentId: params.agentId ?? deps.defaultAgent,
21927
- mcpServers: params.mcpServers,
21928
- title: hydraMeta.name,
21929
- agentArgs: hydraMeta.agentArgs,
21930
- model: hydraMeta.model,
21931
- onInstallProgress: makeInstallProgressForwarder(connection),
21932
- transformChain
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 handle = await startDaemon(config, serviceToken);
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 handle.shutdown();
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" ? body.sessions : host === "local" ? body.sessions.filter(
23919
+ const hostFiltered = host === "all" ? sessionsAfterCatFilter : host === "local" ? sessionsAfterCatFilter.filter(
23219
23920
  (s) => !s.importedFromMachine || !!s.upstreamSessionId
23220
- ) : body.sessions.filter(
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,26 @@ function createChunker(opts) {
25401
26120
  }
25402
26121
 
25403
26122
  // src/cli/commands/cat.ts
25404
- var DEFAULT_STREAM_THRESHOLD = 32 * 1024;
25405
- var DEFAULT_STREAM_FILE_CAP = 64 * 1024 * 1024;
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
+ }
25406
26143
  async function runCat(opts) {
25407
26144
  setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
25408
26145
  if (process.stdin.isTTY && !opts.prompt && !opts.sessionId) {
@@ -25412,6 +26149,18 @@ async function runCat(opts) {
25412
26149
  process.exit(2);
25413
26150
  return;
25414
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
+ }
25415
26164
  const config = await loadConfig();
25416
26165
  const target = opts.target ?? await resolveLocalTarget(config);
25417
26166
  if (target.isLocal && !opts.target) {
@@ -25435,9 +26184,19 @@ async function runCat(opts) {
25435
26184
  }
25436
26185
  async function runCatLoop(args) {
25437
26186
  const { conn, opts, stdin, stdinIsTty, stdout, stderr } = args;
26187
+ const useAutoStream = !stdinIsTty && opts.sessionId === void 0 && opts.follow !== true;
25438
26188
  conn.setDefaultHandler(async () => {
25439
26189
  return { error: { code: -32601, message: "method not implemented" } };
25440
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
+ });
25441
26200
  try {
25442
26201
  await conn.request("initialize", {
25443
26202
  protocolVersion: ACP_PROTOCOL_VERSION,
@@ -25445,11 +26204,11 @@ async function runCatLoop(args) {
25445
26204
  fs: { readTextFile: false, writeTextFile: false },
25446
26205
  terminal: false
25447
26206
  },
25448
- clientInfo: { name: "hydra-acp-cat", version: HYDRA_VERSION }
26207
+ clientInfo: { name: HYDRA_CAT_CLIENT_NAME, version: HYDRA_VERSION }
25449
26208
  });
25450
26209
  } catch {
25451
26210
  }
25452
- const sessionId = await openOrAttachSession(conn, opts);
26211
+ const sessionId = await openOrAttachSession(conn, opts, useAutoStream);
25453
26212
  let turnHadOutput = false;
25454
26213
  let lastCharWasNewline = true;
25455
26214
  const writeStdout = (text) => {
@@ -25478,9 +26237,10 @@ async function runCatLoop(args) {
25478
26237
  finalizeTurn();
25479
26238
  }
25480
26239
  });
26240
+ let firstChunkSent = false;
25481
26241
  const sendChunk = async (text) => {
25482
26242
  const promptBlocks = [];
25483
- if (opts.prompt) {
26243
+ if (opts.prompt && !firstChunkSent) {
25484
26244
  promptBlocks.push({ type: "text", text: opts.prompt });
25485
26245
  }
25486
26246
  if (text.length > 0) {
@@ -25494,6 +26254,7 @@ async function runCatLoop(args) {
25494
26254
  sessionId,
25495
26255
  prompt: promptBlocks
25496
26256
  });
26257
+ firstChunkSent = true;
25497
26258
  } catch (err) {
25498
26259
  stderr(`hydra-acp cat: prompt failed: ${err.message}
25499
26260
  `);
@@ -25552,22 +26313,6 @@ async function runCatLoop(args) {
25552
26313
  }
25553
26314
  }
25554
26315
  };
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
26316
  if (stdinIsTty && !opts.sessionId) {
25572
26317
  if (opts.prompt) {
25573
26318
  await sendChunk("");
@@ -25575,7 +26320,7 @@ async function runCatLoop(args) {
25575
26320
  await settle(0);
25576
26321
  return done;
25577
26322
  }
25578
- if (opts.stream && !stdinIsTty) {
26323
+ if (useAutoStream) {
25579
26324
  if (typeof stdin.setEncoding === "function") {
25580
26325
  stdin.setEncoding("utf8");
25581
26326
  }
@@ -25616,15 +26361,49 @@ async function runCatLoop(args) {
25616
26361
  if (typeof stdin.setEncoding === "function") {
25617
26362
  stdin.setEncoding("utf8");
25618
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 = "";
25619
26398
  stdin.on("data", (data) => {
25620
- chunker.feed(typeof data === "string" ? data : data.toString("utf8"));
26399
+ oneShotBuffer += typeof data === "string" ? data : data.toString("utf8");
25621
26400
  });
25622
26401
  stdin.on("end", () => {
25623
- chunker.eof();
25624
26402
  stdinEnded = true;
25625
- if (!draining && chunkQueue.length === 0) {
25626
- void settle(exitCode);
26403
+ if (oneShotBuffer.length > 0) {
26404
+ chunkQueue.push(oneShotBuffer);
25627
26405
  }
26406
+ void drainQueue();
25628
26407
  });
25629
26408
  stdin.on("error", (err) => {
25630
26409
  stderr(`hydra-acp cat: stdin error: ${err.message}
@@ -25677,12 +26456,11 @@ function runStreamingPath(args) {
25677
26456
  try {
25678
26457
  const openParams = {
25679
26458
  sessionId,
25680
- mode: "file"
26459
+ mode: "memory"
25681
26460
  };
25682
26461
  if (opts.streamBufferBytes !== void 0) {
25683
26462
  openParams.capacityBytes = opts.streamBufferBytes;
25684
26463
  }
25685
- openParams.fileCapBytes = opts.streamFileCapBytes ?? DEFAULT_STREAM_FILE_CAP;
25686
26464
  open2 = await conn.request("hydra-acp/stream_open", openParams);
25687
26465
  } catch (err) {
25688
26466
  args.onPromptFailed(
@@ -25690,23 +26468,12 @@ function runStreamingPath(args) {
25690
26468
  );
25691
26469
  return;
25692
26470
  }
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
26471
  if (headBuffer.length > 0) {
25701
26472
  writeToStream(headBuffer, false);
25702
26473
  headBuffer = Buffer.alloc(0);
25703
26474
  }
25704
26475
  await writeChain.catch(() => void 0);
25705
- const promptText = buildStreamPromptText(
25706
- opts.prompt,
25707
- filePath,
25708
- opts.streamFileCapBytes ?? DEFAULT_STREAM_FILE_CAP
25709
- );
26476
+ const promptText = buildStreamPromptText(opts.prompt, open2.capacityBytes);
25710
26477
  const promptDone = conn.request("session/prompt", {
25711
26478
  sessionId,
25712
26479
  prompt: [{ type: "text", text: promptText }]
@@ -25749,22 +26516,35 @@ function runStreamingPath(args) {
25749
26516
  });
25750
26517
  stdin.on("error", args.onError);
25751
26518
  }
25752
- function buildStreamPromptText(standing, filePath, fileCapBytes) {
25753
- const capHuman = fileCapBytes >= 1024 * 1024 ? `${(fileCapBytes / (1024 * 1024)).toFixed(0)} MB` : `${(fileCapBytes / 1024).toFixed(0)} KB`;
25754
- const note = `Stdin is being streamed to ${filePath}. The file is being appended to live; use whatever shell tools fit (\`tail -f\`, \`head\`, \`grep\`, \`wc -l\`, etc.). Soft cap ${capHuman} \u2014 if more is written past that, older bytes are dropped.`;
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.`;
25755
26530
  if (standing && standing.length > 0) {
25756
- return `${standing}
26531
+ return `${toolNote}
25757
26532
 
25758
- ${note}`;
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}`;
25759
26537
  }
25760
- return note;
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.`;
25761
26541
  }
25762
- async function openOrAttachSession(conn, opts) {
26542
+ async function openOrAttachSession(conn, opts, useAutoStream) {
25763
26543
  if (opts.sessionId) {
25764
26544
  const attached = await conn.request("session/attach", {
25765
26545
  sessionId: opts.sessionId,
25766
26546
  historyPolicy: "pending_only",
25767
- clientInfo: { name: "hydra-acp-cat", version: HYDRA_VERSION }
26547
+ clientInfo: { name: HYDRA_CAT_CLIENT_NAME, version: HYDRA_VERSION }
25768
26548
  });
25769
26549
  return attached.sessionId;
25770
26550
  }
@@ -25775,6 +26555,9 @@ async function openOrAttachSession(conn, opts) {
25775
26555
  if (opts.model) {
25776
26556
  hydraMeta.model = opts.model;
25777
26557
  }
26558
+ if (useAutoStream) {
26559
+ hydraMeta.mcpStdin = true;
26560
+ }
25778
26561
  const cwd = opts.cwd ?? process.cwd();
25779
26562
  const params = { cwd };
25780
26563
  if (opts.agentId) {
@@ -25806,6 +26589,16 @@ async function openWs2(url, subprotocols) {
25806
26589
  // src/cli.ts
25807
26590
  init_update_check();
25808
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
+ }
25809
26602
  async function main() {
25810
26603
  const argv = process.argv.slice(2);
25811
26604
  const launchIdx = argv.indexOf("launch");
@@ -25838,11 +26631,14 @@ async function main() {
25838
26631
  const name2 = resolveOption(flags2, "name");
25839
26632
  const model2 = resolveOption(flags2, "model");
25840
26633
  suppressUpdateNotice = true;
26634
+ const dangerous = flags2["dangerously-skip-permissions"] === true;
26635
+ warnIfDangerouslySkipping(dangerous);
25841
26636
  const shimOpts = {
25842
26637
  agentId,
25843
26638
  agentArgs,
25844
26639
  name: name2,
25845
- model: model2
26640
+ model: model2,
26641
+ dangerouslySkipPermissions: dangerous
25846
26642
  };
25847
26643
  if (resolved2?.sessionId !== void 0) {
25848
26644
  shimOpts.sessionId = resolved2.sessionId;
@@ -25868,6 +26664,8 @@ async function main() {
25868
26664
  const name = resolveOption(flags, "name");
25869
26665
  const agentIdFromFlag = resolveOption(flags, "agent");
25870
26666
  const model = resolveOption(flags, "model");
26667
+ const dangerouslySkipPermissions = flags["dangerously-skip-permissions"] === true;
26668
+ warnIfDangerouslySkipping(dangerouslySkipPermissions);
25871
26669
  const sessionInput = readSessionInput(flags);
25872
26670
  const interactive = subcommand === "tui" || subcommand === void 0 && process.stdout.isTTY;
25873
26671
  const resolved = await resolveSessionFlagOrExit(sessionInput, {
@@ -25889,7 +26687,8 @@ async function main() {
25889
26687
  agentId: agentIdFromFlag,
25890
26688
  name,
25891
26689
  model,
25892
- target: sessionTarget
26690
+ target: sessionTarget,
26691
+ dangerouslySkipPermissions
25893
26692
  });
25894
26693
  return;
25895
26694
  }
@@ -25897,7 +26696,8 @@ async function main() {
25897
26696
  const shimOpts = {
25898
26697
  name,
25899
26698
  model,
25900
- agentId: agentIdFromFlag
26699
+ agentId: agentIdFromFlag,
26700
+ dangerouslySkipPermissions
25901
26701
  };
25902
26702
  if (sessionId !== void 0) {
25903
26703
  shimOpts.sessionId = sessionId;
@@ -25914,7 +26714,8 @@ async function main() {
25914
26714
  const shimOpts = {
25915
26715
  name,
25916
26716
  model,
25917
- agentId: agentIdFromFlag
26717
+ agentId: agentIdFromFlag,
26718
+ dangerouslySkipPermissions
25918
26719
  };
25919
26720
  if (sessionId !== void 0) {
25920
26721
  shimOpts.sessionId = sessionId;
@@ -25937,7 +26738,8 @@ async function main() {
25937
26738
  model,
25938
26739
  agentId: agentIdFromFlag,
25939
26740
  detach: flags.detach === true,
25940
- stream: flags.stream === true
26741
+ follow: flags.follow === true,
26742
+ dangerouslySkipPermissions
25941
26743
  };
25942
26744
  if (cwd !== void 0) {
25943
26745
  catOpts.cwd = cwd;
@@ -25953,10 +26755,6 @@ async function main() {
25953
26755
  if (streamBufferBytes !== void 0) {
25954
26756
  catOpts.streamBufferBytes = streamBufferBytes;
25955
26757
  }
25956
- const streamFileCap = parseNumericFlag(flags, "stream-file-cap");
25957
- if (streamFileCap !== void 0) {
25958
- catOpts.streamFileCapBytes = streamFileCap;
25959
- }
25960
26758
  suppressUpdateNotice = true;
25961
26759
  await runCat(catOpts);
25962
26760
  return;
@@ -26000,7 +26798,8 @@ async function main() {
26000
26798
  await runSessionsList({
26001
26799
  all: flags.all === true,
26002
26800
  json: flags.json === true,
26003
- 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
26004
26803
  });
26005
26804
  return;
26006
26805
  }
@@ -26182,7 +26981,8 @@ async function main() {
26182
26981
  agentId: agentIdFromFlag,
26183
26982
  name,
26184
26983
  model,
26185
- target: sessionTarget
26984
+ target: sessionTarget,
26985
+ dangerouslySkipPermissions
26186
26986
  });
26187
26987
  return;
26188
26988
  default:
@@ -26224,6 +27024,9 @@ async function dispatchTui(flags, base) {
26224
27024
  if (base.target !== void 0) {
26225
27025
  tuiOpts.target = base.target;
26226
27026
  }
27027
+ if (base.dangerouslySkipPermissions === true) {
27028
+ tuiOpts.dangerouslySkipPermissions = true;
27029
+ }
26227
27030
  await runTui(tuiOpts);
26228
27031
  }
26229
27032
  function parseNumericFlag(flags, name) {
@@ -26325,15 +27128,17 @@ function printHelp() {
26325
27128
  " --reattach Pick the most-recent session for the current cwd.",
26326
27129
  " --new Force a fresh session.",
26327
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).",
26328
27132
  " HYDRA_ACP_SESSION Env var equivalent of --session (flag wins).",
26329
27133
  " hydra-acp init [--rotate-token] Initialize ~/.hydra-acp/config.json",
26330
27134
  " hydra-acp daemon [status] Show daemon pid/version (default when no subcommand)",
26331
27135
  " hydra-acp daemon start [--foreground] Start daemon (detached by default; --foreground to attach)",
26332
27136
  " hydra-acp daemon stop|restart",
26333
27137
  " hydra-acp daemon logs [-f] [-n N] Tail or follow the daemon log",
26334
- " hydra-acp session [list] [--all] [--json] [--host=<host>]",
27138
+ " hydra-acp session [list] [--all] [--json] [--host=<host>] [--include-cat]",
26335
27139
  " List sessions (live + 20 most-recent cold; --all for everything; --json emits JSON for scripts).",
26336
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).",
26337
27142
  " hydra-acp session kill <id> Demote a live session to cold (keeps the on-disk record)",
26338
27143
  " hydra-acp session remove <id> Remove a session entirely (live or cold)",
26339
27144
  " hydra-acp session export <id> [--out <file>|.]",