@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 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,39 @@ 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 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: "hydra-acp-cat", version: HYDRA_VERSION }
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 (opts.stream && !stdinIsTty) {
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
- chunker.feed(typeof data === "string" ? data : data.toString("utf8"));
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 (!draining && chunkQueue.length === 0) {
25626
- void settle(exitCode);
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: "file"
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, 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.`;
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 `${standing}
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
- ${note}`;
26548
+ User's question:
26549
+ ${standing}`;
25759
26550
  }
25760
- return note;
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: "hydra-acp-cat", version: HYDRA_VERSION }
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 subcommand = positional[0];
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
- stream: flags.stream === true
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>|.]",