@hydra-acp/cli 0.1.49 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +923 -97
- package/dist/index.d.ts +100 -0
- package/dist/index.js +656 -26
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1304,6 +1304,9 @@ function extractHydraMeta(meta) {
|
|
|
1304
1304
|
if (typeof obj.promptUpdating === "boolean") {
|
|
1305
1305
|
out.promptUpdating = obj.promptUpdating;
|
|
1306
1306
|
}
|
|
1307
|
+
if (typeof obj.mcpStdin === "boolean") {
|
|
1308
|
+
out.mcpStdin = obj.mcpStdin;
|
|
1309
|
+
}
|
|
1307
1310
|
if (typeof obj.promptAmending === "boolean") {
|
|
1308
1311
|
out.promptAmending = obj.promptAmending;
|
|
1309
1312
|
}
|
|
@@ -1419,6 +1422,10 @@ var SessionListEntry = z3.object({
|
|
|
1419
1422
|
importedFromUpstreamSessionId: z3.string().optional(),
|
|
1420
1423
|
// Set when this session was spawned as a child by a transformer.
|
|
1421
1424
|
parentSessionId: z3.string().optional(),
|
|
1425
|
+
// clientInfo from the process that issued session/new. Lets list views
|
|
1426
|
+
// hide cat-style ancillary sessions by default while letting an
|
|
1427
|
+
// override flag surface them.
|
|
1428
|
+
originatingClient: z3.object({ name: z3.string(), version: z3.string().optional() }).optional(),
|
|
1422
1429
|
updatedAt: z3.string(),
|
|
1423
1430
|
attachedClients: z3.number().int().nonnegative(),
|
|
1424
1431
|
status: z3.enum(["live", "cold"]).default("live"),
|
|
@@ -2021,14 +2028,30 @@ import { customAlphabet } from "nanoid";
|
|
|
2021
2028
|
|
|
2022
2029
|
// src/core/stream-buffer.ts
|
|
2023
2030
|
import * as fsp3 from "fs/promises";
|
|
2024
|
-
var DEFAULT_CAPACITY_BYTES =
|
|
2031
|
+
var DEFAULT_CAPACITY_BYTES = 64 * 1024 * 1024;
|
|
2032
|
+
var INITIAL_CAPACITY_BYTES = 1 * 1024 * 1024;
|
|
2025
2033
|
var STREAM_READ_MAX_BYTES = 64 * 1024;
|
|
2026
2034
|
var STREAM_WAIT_MAX_MS = 6e4;
|
|
2035
|
+
var STREAM_GREP_DEFAULT_MATCHES = 100;
|
|
2036
|
+
var STREAM_GREP_MAX_MATCHES = 1e3;
|
|
2037
|
+
var STREAM_GREP_DEFAULT_BYTES = 64 * 1024;
|
|
2038
|
+
var STREAM_GREP_MAX_BYTES = 256 * 1024;
|
|
2039
|
+
var STREAM_GREP_MAX_CONTEXT = 20;
|
|
2040
|
+
function escapeRegex(s) {
|
|
2041
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2042
|
+
}
|
|
2027
2043
|
var SessionStreamBuffer = class {
|
|
2028
2044
|
storage;
|
|
2029
|
-
|
|
2045
|
+
// The configured cap. Eviction begins once writeCursor exceeds this.
|
|
2046
|
+
maxCapacityBytes;
|
|
2047
|
+
// The size of the currently-allocated `storage`. Starts at
|
|
2048
|
+
// INITIAL_CAPACITY_BYTES (clamped to maxCapacityBytes) and doubles on
|
|
2049
|
+
// demand. Once it reaches maxCapacityBytes the ring behaves like a
|
|
2050
|
+
// fixed-size buffer; before then, writeCursor < currentCapacityBytes
|
|
2051
|
+
// always, so no wrap-around math is in play.
|
|
2052
|
+
currentCapacityBytes;
|
|
2030
2053
|
// Absolute monotonic byte offset of the next byte to be written. Also
|
|
2031
|
-
// the count of bytes ever appended. `writeCursor -
|
|
2054
|
+
// the count of bytes ever appended. `writeCursor - currentCapacityBytes`
|
|
2032
2055
|
// (clamped at 0) is the oldest still-resident byte's cursor.
|
|
2033
2056
|
writeCursor = 0;
|
|
2034
2057
|
closed = false;
|
|
@@ -2043,24 +2066,33 @@ var SessionStreamBuffer = class {
|
|
|
2043
2066
|
// calls don't interleave their writes.
|
|
2044
2067
|
fileWriteChain = Promise.resolve();
|
|
2045
2068
|
constructor(opts = {}) {
|
|
2046
|
-
this.
|
|
2047
|
-
if (this.
|
|
2069
|
+
this.maxCapacityBytes = opts.capacityBytes ?? DEFAULT_CAPACITY_BYTES;
|
|
2070
|
+
if (this.maxCapacityBytes <= 0) {
|
|
2048
2071
|
throw new Error("capacityBytes must be > 0");
|
|
2049
2072
|
}
|
|
2050
|
-
this.
|
|
2073
|
+
this.currentCapacityBytes = Math.min(
|
|
2074
|
+
INITIAL_CAPACITY_BYTES,
|
|
2075
|
+
this.maxCapacityBytes
|
|
2076
|
+
);
|
|
2077
|
+
this.storage = Buffer.alloc(this.currentCapacityBytes);
|
|
2051
2078
|
this.filePath = opts.filePath;
|
|
2052
2079
|
this.fileCapBytes = opts.fileCapBytes ?? Number.POSITIVE_INFINITY;
|
|
2053
2080
|
this.onFileCapReached = opts.onFileCapReached;
|
|
2054
2081
|
this.logWriteError = opts.logWriteError;
|
|
2055
2082
|
}
|
|
2056
2083
|
get capacity() {
|
|
2057
|
-
return this.
|
|
2084
|
+
return this.maxCapacityBytes;
|
|
2085
|
+
}
|
|
2086
|
+
// Currently-allocated storage size (for observability / tests). May be
|
|
2087
|
+
// anywhere between INITIAL_CAPACITY_BYTES and capacity.
|
|
2088
|
+
get allocatedBytes() {
|
|
2089
|
+
return this.currentCapacityBytes;
|
|
2058
2090
|
}
|
|
2059
2091
|
get writeCursorPos() {
|
|
2060
2092
|
return this.writeCursor;
|
|
2061
2093
|
}
|
|
2062
2094
|
get oldestAvailable() {
|
|
2063
|
-
return Math.max(0, this.writeCursor - this.
|
|
2095
|
+
return Math.max(0, this.writeCursor - this.currentCapacityBytes);
|
|
2064
2096
|
}
|
|
2065
2097
|
get isClosed() {
|
|
2066
2098
|
return this.closed;
|
|
@@ -2205,6 +2237,136 @@ var SessionStreamBuffer = class {
|
|
|
2205
2237
|
this.waiters.push(waiter);
|
|
2206
2238
|
});
|
|
2207
2239
|
}
|
|
2240
|
+
// Scan the resident region line-by-line, returning lines that match
|
|
2241
|
+
// `pattern`. Server-side filtering so the agent doesn't have to pull
|
|
2242
|
+
// and decode 64 KiB base64 windows just to grep a multi-MB log.
|
|
2243
|
+
//
|
|
2244
|
+
// Lines are split on `\n` (LF). A trailing partial line (no LF) is
|
|
2245
|
+
// skipped when the buffer is still open — its bytes might be the
|
|
2246
|
+
// start of a longer line that's still being written — but is treated
|
|
2247
|
+
// as a final full line once the buffer is closed.
|
|
2248
|
+
//
|
|
2249
|
+
// Caps: max 1000 matches and 256 KiB of output bytes per call. The
|
|
2250
|
+
// agent should re-call with `cursor = nextCursor` to resume when
|
|
2251
|
+
// `truncated:true`.
|
|
2252
|
+
grep(opts) {
|
|
2253
|
+
const oldest = this.oldestAvailable;
|
|
2254
|
+
const requested = opts.cursor;
|
|
2255
|
+
let start = requested ?? oldest;
|
|
2256
|
+
let gap = 0;
|
|
2257
|
+
if (requested !== void 0 && requested < oldest) {
|
|
2258
|
+
gap = oldest - requested;
|
|
2259
|
+
start = oldest;
|
|
2260
|
+
}
|
|
2261
|
+
if (start > this.writeCursor) {
|
|
2262
|
+
start = this.writeCursor;
|
|
2263
|
+
}
|
|
2264
|
+
const slice = this.sliceFromRing(start, this.writeCursor - start);
|
|
2265
|
+
const useRegex = opts.regex ?? true;
|
|
2266
|
+
const flags = opts.caseInsensitive === true ? "i" : "";
|
|
2267
|
+
const re = useRegex ? new RegExp(opts.pattern, flags) : new RegExp(escapeRegex(opts.pattern), flags);
|
|
2268
|
+
const invert = opts.invert ?? false;
|
|
2269
|
+
const maxMatches = Math.max(
|
|
2270
|
+
1,
|
|
2271
|
+
Math.min(
|
|
2272
|
+
opts.maxMatches ?? STREAM_GREP_DEFAULT_MATCHES,
|
|
2273
|
+
STREAM_GREP_MAX_MATCHES
|
|
2274
|
+
)
|
|
2275
|
+
);
|
|
2276
|
+
const maxBytes = Math.max(
|
|
2277
|
+
1,
|
|
2278
|
+
Math.min(opts.maxBytes ?? STREAM_GREP_DEFAULT_BYTES, STREAM_GREP_MAX_BYTES)
|
|
2279
|
+
);
|
|
2280
|
+
const contextBefore = Math.max(
|
|
2281
|
+
0,
|
|
2282
|
+
Math.min(opts.contextBefore ?? 0, STREAM_GREP_MAX_CONTEXT)
|
|
2283
|
+
);
|
|
2284
|
+
const contextAfter = Math.max(
|
|
2285
|
+
0,
|
|
2286
|
+
Math.min(opts.contextAfter ?? 0, STREAM_GREP_MAX_CONTEXT)
|
|
2287
|
+
);
|
|
2288
|
+
const matches = [];
|
|
2289
|
+
const beforeRing = [];
|
|
2290
|
+
const pendingAfter = [];
|
|
2291
|
+
let bytesUsed = 0;
|
|
2292
|
+
let truncated = false;
|
|
2293
|
+
let lineStartByte = 0;
|
|
2294
|
+
let resumeFromLineStart = 0;
|
|
2295
|
+
const processLine = (lineCursor, lineText) => {
|
|
2296
|
+
for (const pa of pendingAfter) {
|
|
2297
|
+
if (pa.remaining > 0) {
|
|
2298
|
+
if (pa.match.after === void 0) {
|
|
2299
|
+
pa.match.after = [];
|
|
2300
|
+
}
|
|
2301
|
+
pa.match.after.push({ cursor: lineCursor, line: lineText });
|
|
2302
|
+
pa.remaining--;
|
|
2303
|
+
bytesUsed += lineText.length;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
while (pendingAfter.length > 0 && pendingAfter[0].remaining === 0) {
|
|
2307
|
+
pendingAfter.shift();
|
|
2308
|
+
}
|
|
2309
|
+
const matched = re.test(lineText) !== invert;
|
|
2310
|
+
if (matched && matches.length < maxMatches) {
|
|
2311
|
+
const m = { cursor: lineCursor, line: lineText };
|
|
2312
|
+
if (contextBefore > 0 && beforeRing.length > 0) {
|
|
2313
|
+
m.before = beforeRing.slice();
|
|
2314
|
+
for (const b of m.before) {
|
|
2315
|
+
bytesUsed += b.line.length;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
bytesUsed += lineText.length;
|
|
2319
|
+
matches.push(m);
|
|
2320
|
+
if (contextAfter > 0) {
|
|
2321
|
+
pendingAfter.push({ match: m, remaining: contextAfter });
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
if (contextBefore > 0) {
|
|
2325
|
+
beforeRing.push({ cursor: lineCursor, line: lineText });
|
|
2326
|
+
while (beforeRing.length > contextBefore) {
|
|
2327
|
+
beforeRing.shift();
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
const hitMaxMatches = matches.length >= maxMatches && pendingAfter.length === 0;
|
|
2331
|
+
const hitMaxBytes = bytesUsed >= maxBytes;
|
|
2332
|
+
return hitMaxMatches || hitMaxBytes;
|
|
2333
|
+
};
|
|
2334
|
+
for (let i = 0; i < slice.length; i++) {
|
|
2335
|
+
if (slice[i] !== 10) {
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
const lineText = slice.subarray(lineStartByte, i).toString("utf8");
|
|
2339
|
+
const lineCursor = start + lineStartByte;
|
|
2340
|
+
lineStartByte = i + 1;
|
|
2341
|
+
resumeFromLineStart = lineStartByte;
|
|
2342
|
+
if (processLine(lineCursor, lineText)) {
|
|
2343
|
+
truncated = true;
|
|
2344
|
+
break;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
if (!truncated && lineStartByte < slice.length && this.closed) {
|
|
2348
|
+
const lineText = slice.subarray(lineStartByte).toString("utf8");
|
|
2349
|
+
const lineCursor = start + lineStartByte;
|
|
2350
|
+
if (processLine(lineCursor, lineText)) {
|
|
2351
|
+
truncated = true;
|
|
2352
|
+
}
|
|
2353
|
+
resumeFromLineStart = slice.length;
|
|
2354
|
+
}
|
|
2355
|
+
const nextCursor = Math.min(start + resumeFromLineStart, this.writeCursor);
|
|
2356
|
+
const result = {
|
|
2357
|
+
matches,
|
|
2358
|
+
truncated,
|
|
2359
|
+
nextCursor,
|
|
2360
|
+
scannedBytes: resumeFromLineStart
|
|
2361
|
+
};
|
|
2362
|
+
if (gap > 0) {
|
|
2363
|
+
result.gap = gap;
|
|
2364
|
+
}
|
|
2365
|
+
if (this.closed && nextCursor >= this.writeCursor) {
|
|
2366
|
+
result.eof = true;
|
|
2367
|
+
}
|
|
2368
|
+
return result;
|
|
2369
|
+
}
|
|
2208
2370
|
wakeWaiters(outcome) {
|
|
2209
2371
|
if (this.waiters.length === 0) {
|
|
2210
2372
|
return;
|
|
@@ -2215,15 +2377,42 @@ var SessionStreamBuffer = class {
|
|
|
2215
2377
|
w.resolve(outcome);
|
|
2216
2378
|
}
|
|
2217
2379
|
}
|
|
2380
|
+
// Grow `storage` if needed to fit `additionalBytes` more bytes without
|
|
2381
|
+
// wrapping. Caps at maxCapacityBytes; once we're at the cap, callers
|
|
2382
|
+
// fall back to ring-wrap behavior. Doubles each grow so we amortize.
|
|
2383
|
+
// Only called before we've ever wrapped (writeCursor < currentCapacity
|
|
2384
|
+
// always holds while we're growing), so the existing bytes live at
|
|
2385
|
+
// storage[0..writeCursor] and we can just copy them flat.
|
|
2386
|
+
growIfNeeded(additionalBytes) {
|
|
2387
|
+
if (this.currentCapacityBytes >= this.maxCapacityBytes) {
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
const needed = this.writeCursor + additionalBytes;
|
|
2391
|
+
if (needed <= this.currentCapacityBytes) {
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
let next = this.currentCapacityBytes;
|
|
2395
|
+
while (next < needed && next < this.maxCapacityBytes) {
|
|
2396
|
+
next = Math.min(this.maxCapacityBytes, next * 2);
|
|
2397
|
+
}
|
|
2398
|
+
if (next === this.currentCapacityBytes) {
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
const newStorage = Buffer.alloc(next);
|
|
2402
|
+
this.storage.copy(newStorage, 0, 0, this.writeCursor);
|
|
2403
|
+
this.storage = newStorage;
|
|
2404
|
+
this.currentCapacityBytes = next;
|
|
2405
|
+
}
|
|
2218
2406
|
writeRing(chunk) {
|
|
2219
2407
|
const len = chunk.length;
|
|
2220
|
-
|
|
2221
|
-
|
|
2408
|
+
this.growIfNeeded(len);
|
|
2409
|
+
if (len >= this.currentCapacityBytes) {
|
|
2410
|
+
const tailStart = len - this.currentCapacityBytes;
|
|
2222
2411
|
chunk.copy(this.storage, 0, tailStart, len);
|
|
2223
2412
|
return;
|
|
2224
2413
|
}
|
|
2225
|
-
const offset = this.writeCursor % this.
|
|
2226
|
-
const tailRoom = this.
|
|
2414
|
+
const offset = this.writeCursor % this.currentCapacityBytes;
|
|
2415
|
+
const tailRoom = this.currentCapacityBytes - offset;
|
|
2227
2416
|
if (len <= tailRoom) {
|
|
2228
2417
|
chunk.copy(this.storage, offset, 0, len);
|
|
2229
2418
|
} else {
|
|
@@ -2236,8 +2425,8 @@ var SessionStreamBuffer = class {
|
|
|
2236
2425
|
return Buffer.alloc(0);
|
|
2237
2426
|
}
|
|
2238
2427
|
const out = Buffer.alloc(length);
|
|
2239
|
-
const offset = fromCursor % this.
|
|
2240
|
-
const tailLen = Math.min(length, this.
|
|
2428
|
+
const offset = fromCursor % this.currentCapacityBytes;
|
|
2429
|
+
const tailLen = Math.min(length, this.currentCapacityBytes - offset);
|
|
2241
2430
|
this.storage.copy(out, 0, offset, offset + tailLen);
|
|
2242
2431
|
if (tailLen < length) {
|
|
2243
2432
|
this.storage.copy(out, tailLen, 0, length - tailLen);
|
|
@@ -2379,6 +2568,7 @@ var Session = class {
|
|
|
2379
2568
|
agentCapabilities;
|
|
2380
2569
|
agentArgs;
|
|
2381
2570
|
parentSessionId;
|
|
2571
|
+
originatingClient;
|
|
2382
2572
|
title;
|
|
2383
2573
|
// Snapshot state delivered to attaching clients via the attach
|
|
2384
2574
|
// response _meta rather than via history replay (which would be
|
|
@@ -2533,6 +2723,7 @@ var Session = class {
|
|
|
2533
2723
|
this.agentCapabilities = init.agentCapabilities;
|
|
2534
2724
|
this.agentArgs = init.agentArgs;
|
|
2535
2725
|
this.parentSessionId = init.parentSessionId;
|
|
2726
|
+
this.originatingClient = init.originatingClient;
|
|
2536
2727
|
this.title = init.title;
|
|
2537
2728
|
this.currentModel = init.currentModel;
|
|
2538
2729
|
this.currentMode = init.currentMode;
|
|
@@ -4720,6 +4911,43 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
4720
4911
|
}
|
|
4721
4912
|
return out;
|
|
4722
4913
|
}
|
|
4914
|
+
streamTail(bytes) {
|
|
4915
|
+
const buf = this.requireStreamBuffer();
|
|
4916
|
+
const r = buf.tail(bytes);
|
|
4917
|
+
return {
|
|
4918
|
+
bytes: r.bytes.toString("base64"),
|
|
4919
|
+
startCursor: r.startCursor,
|
|
4920
|
+
endCursor: r.endCursor,
|
|
4921
|
+
truncated: r.truncated
|
|
4922
|
+
};
|
|
4923
|
+
}
|
|
4924
|
+
streamHead(bytes) {
|
|
4925
|
+
const buf = this.requireStreamBuffer();
|
|
4926
|
+
const r = buf.head(bytes);
|
|
4927
|
+
return {
|
|
4928
|
+
bytes: r.bytes.toString("base64"),
|
|
4929
|
+
startCursor: r.startCursor,
|
|
4930
|
+
endCursor: r.endCursor,
|
|
4931
|
+
truncated: r.truncated
|
|
4932
|
+
};
|
|
4933
|
+
}
|
|
4934
|
+
async streamWaitFor(cursor, timeoutMs) {
|
|
4935
|
+
const buf = this.requireStreamBuffer();
|
|
4936
|
+
return buf.waitForData(cursor, timeoutMs);
|
|
4937
|
+
}
|
|
4938
|
+
streamGrep(opts) {
|
|
4939
|
+
const buf = this.requireStreamBuffer();
|
|
4940
|
+
return buf.grep(opts);
|
|
4941
|
+
}
|
|
4942
|
+
streamInfo() {
|
|
4943
|
+
const buf = this.requireStreamBuffer();
|
|
4944
|
+
return {
|
|
4945
|
+
writeCursor: buf.writeCursorPos,
|
|
4946
|
+
oldestAvailable: buf.oldestAvailable,
|
|
4947
|
+
capacity: buf.capacity,
|
|
4948
|
+
closed: buf.isClosed
|
|
4949
|
+
};
|
|
4950
|
+
}
|
|
4723
4951
|
requireStreamBuffer() {
|
|
4724
4952
|
if (this.streamBuffer === void 0) {
|
|
4725
4953
|
const err = new Error(
|
|
@@ -5479,6 +5707,10 @@ var PersistedUsage = z4.object({
|
|
|
5479
5707
|
costCurrency: z4.string().optional(),
|
|
5480
5708
|
cumulativeCost: z4.number().optional()
|
|
5481
5709
|
});
|
|
5710
|
+
var PersistedOriginatingClient = z4.object({
|
|
5711
|
+
name: z4.string(),
|
|
5712
|
+
version: z4.string().optional()
|
|
5713
|
+
});
|
|
5482
5714
|
var SessionRecord = z4.object({
|
|
5483
5715
|
version: z4.literal(1),
|
|
5484
5716
|
sessionId: z4.string(),
|
|
@@ -5529,6 +5761,10 @@ var SessionRecord = z4.object({
|
|
|
5529
5761
|
// Set when this session was spawned as a child by a transformer via
|
|
5530
5762
|
// hydra-acp/spawn_child_session. Points to the spawning session's id.
|
|
5531
5763
|
parentSessionId: z4.string().optional(),
|
|
5764
|
+
// clientInfo from the process that issued session/new. Picker and
|
|
5765
|
+
// `sessions list` use this to hide cat-style ancillary sessions by
|
|
5766
|
+
// default; carried in meta.json so cold sessions filter the same way.
|
|
5767
|
+
originatingClient: PersistedOriginatingClient.optional(),
|
|
5532
5768
|
createdAt: z4.string(),
|
|
5533
5769
|
updatedAt: z4.string()
|
|
5534
5770
|
});
|
|
@@ -5651,6 +5887,7 @@ function recordFromMemorySession(args) {
|
|
|
5651
5887
|
agentModels: args.agentModels,
|
|
5652
5888
|
pendingHistorySync: args.pendingHistorySync,
|
|
5653
5889
|
parentSessionId: args.parentSessionId,
|
|
5890
|
+
originatingClient: args.originatingClient,
|
|
5654
5891
|
createdAt: args.createdAt ?? now,
|
|
5655
5892
|
updatedAt: args.updatedAt ?? now
|
|
5656
5893
|
};
|
|
@@ -5939,6 +6176,7 @@ var SessionManager = class {
|
|
|
5939
6176
|
agentModels: fresh.initialModels,
|
|
5940
6177
|
transformChain: params.transformChain,
|
|
5941
6178
|
parentSessionId: params.parentSessionId,
|
|
6179
|
+
originatingClient: params.originatingClient,
|
|
5942
6180
|
extensionCommands: this.extensionCommands
|
|
5943
6181
|
});
|
|
5944
6182
|
await this.attachManagerHooks(session);
|
|
@@ -6110,6 +6348,7 @@ var SessionManager = class {
|
|
|
6110
6348
|
// than stay stuck.
|
|
6111
6349
|
firstPromptSeeded: !!params.title,
|
|
6112
6350
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
6351
|
+
originatingClient: params.originatingClient,
|
|
6113
6352
|
extensionCommands: this.extensionCommands
|
|
6114
6353
|
});
|
|
6115
6354
|
await this.attachManagerHooks(session);
|
|
@@ -6177,6 +6416,7 @@ var SessionManager = class {
|
|
|
6177
6416
|
agentModels: advertisedModels,
|
|
6178
6417
|
firstPromptSeeded: !!params.title,
|
|
6179
6418
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
6419
|
+
originatingClient: params.originatingClient,
|
|
6180
6420
|
extensionCommands: this.extensionCommands
|
|
6181
6421
|
});
|
|
6182
6422
|
await this.attachManagerHooks(session);
|
|
@@ -6526,7 +6766,8 @@ var SessionManager = class {
|
|
|
6526
6766
|
agentModes: record.agentModes,
|
|
6527
6767
|
agentModels: record.agentModels,
|
|
6528
6768
|
createdAt: record.createdAt,
|
|
6529
|
-
pendingHistorySync: record.pendingHistorySync
|
|
6769
|
+
pendingHistorySync: record.pendingHistorySync,
|
|
6770
|
+
originatingClient: record.originatingClient
|
|
6530
6771
|
};
|
|
6531
6772
|
}
|
|
6532
6773
|
async clearPendingHistorySync(sessionId) {
|
|
@@ -6627,6 +6868,7 @@ var SessionManager = class {
|
|
|
6627
6868
|
currentModel: session.currentModel,
|
|
6628
6869
|
currentUsage: session.currentUsage,
|
|
6629
6870
|
parentSessionId: session.parentSessionId,
|
|
6871
|
+
originatingClient: session.originatingClient,
|
|
6630
6872
|
updatedAt: used,
|
|
6631
6873
|
attachedClients: session.attachedCount,
|
|
6632
6874
|
status: "live",
|
|
@@ -6656,6 +6898,7 @@ var SessionManager = class {
|
|
|
6656
6898
|
importedFromMachine: r.importedFromMachine,
|
|
6657
6899
|
importedFromUpstreamSessionId: r.importedFromUpstreamSessionId,
|
|
6658
6900
|
parentSessionId: r.parentSessionId,
|
|
6901
|
+
originatingClient: r.originatingClient,
|
|
6659
6902
|
updatedAt: used,
|
|
6660
6903
|
attachedClients: 0,
|
|
6661
6904
|
status: "cold",
|
|
@@ -7001,6 +7244,7 @@ function mergeForPersistence(session, existing) {
|
|
|
7001
7244
|
agentModes,
|
|
7002
7245
|
agentModels,
|
|
7003
7246
|
parentSessionId: session.parentSessionId ?? existing?.parentSessionId,
|
|
7247
|
+
originatingClient: session.originatingClient ?? existing?.originatingClient,
|
|
7004
7248
|
createdAt: existing?.createdAt ?? new Date(session.createdAt).toISOString()
|
|
7005
7249
|
});
|
|
7006
7250
|
}
|
|
@@ -10590,6 +10834,7 @@ function wsToMessageStream(ws) {
|
|
|
10590
10834
|
// src/daemon/acp-ws.ts
|
|
10591
10835
|
import * as os4 from "os";
|
|
10592
10836
|
import * as path12 from "path";
|
|
10837
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
10593
10838
|
function registerAcpWsEndpoint(app, deps) {
|
|
10594
10839
|
app.get("/acp", { websocket: true }, async (socket, request) => {
|
|
10595
10840
|
const token = tokenFromUpgradeRequest({
|
|
@@ -10627,6 +10872,12 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
10627
10872
|
};
|
|
10628
10873
|
connection.onRequest("initialize", async (raw) => {
|
|
10629
10874
|
const params = InitializeParams.parse(raw ?? {});
|
|
10875
|
+
if (params.clientInfo?.name) {
|
|
10876
|
+
state.clientInfo = {
|
|
10877
|
+
name: params.clientInfo.name,
|
|
10878
|
+
...params.clientInfo.version !== void 0 ? { version: params.clientInfo.version } : {}
|
|
10879
|
+
};
|
|
10880
|
+
}
|
|
10630
10881
|
const version = params.clientInfo?.version;
|
|
10631
10882
|
if (version && processIdentity) {
|
|
10632
10883
|
if (processIdentity.kind === "extension") {
|
|
@@ -10806,16 +11057,50 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
10806
11057
|
);
|
|
10807
11058
|
const transformerNames = Array.isArray(hydraMeta.transformers) && hydraMeta.transformers.every((t) => typeof t === "string") ? hydraMeta.transformers : deps.manager.defaultTransformers ?? [];
|
|
10808
11059
|
const transformChain = deps.transformers?.resolveChain(transformerNames) ?? [];
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
11060
|
+
let stdinToken;
|
|
11061
|
+
let stdinReservation;
|
|
11062
|
+
let augmentedMcpServers = params.mcpServers;
|
|
11063
|
+
if (hydraMeta.mcpStdin === true && deps.stdinMcpRegistry !== void 0 && deps.getDaemonOrigin !== void 0) {
|
|
11064
|
+
stdinToken = randomBytes3(32).toString("hex");
|
|
11065
|
+
stdinReservation = deps.stdinMcpRegistry.reserve(stdinToken);
|
|
11066
|
+
const url = `${deps.getDaemonOrigin()}/mcp/stdin`;
|
|
11067
|
+
const descriptor = {
|
|
11068
|
+
name: "hydra_stdin",
|
|
11069
|
+
type: "http",
|
|
11070
|
+
url,
|
|
11071
|
+
headers: [
|
|
11072
|
+
{ name: "Authorization", value: `Bearer ${stdinToken}` }
|
|
11073
|
+
]
|
|
11074
|
+
};
|
|
11075
|
+
augmentedMcpServers = [...params.mcpServers ?? [], descriptor];
|
|
11076
|
+
}
|
|
11077
|
+
let session;
|
|
11078
|
+
try {
|
|
11079
|
+
session = await deps.manager.create({
|
|
11080
|
+
cwd: params.cwd,
|
|
11081
|
+
agentId: params.agentId ?? deps.defaultAgent,
|
|
11082
|
+
mcpServers: augmentedMcpServers,
|
|
11083
|
+
title: hydraMeta.name,
|
|
11084
|
+
agentArgs: hydraMeta.agentArgs,
|
|
11085
|
+
model: hydraMeta.model,
|
|
11086
|
+
onInstallProgress: makeInstallProgressForwarder(connection),
|
|
11087
|
+
transformChain,
|
|
11088
|
+
originatingClient: state.clientInfo
|
|
11089
|
+
});
|
|
11090
|
+
} catch (err) {
|
|
11091
|
+
if (stdinReservation !== void 0) {
|
|
11092
|
+
stdinReservation.abandon(err instanceof Error ? err : void 0);
|
|
11093
|
+
}
|
|
11094
|
+
throw err;
|
|
11095
|
+
}
|
|
11096
|
+
if (stdinToken !== void 0 && stdinReservation !== void 0 && deps.stdinMcpRegistry !== void 0) {
|
|
11097
|
+
const token2 = stdinToken;
|
|
11098
|
+
const registry = deps.stdinMcpRegistry;
|
|
11099
|
+
stdinReservation.complete(session);
|
|
11100
|
+
session.onClose(() => {
|
|
11101
|
+
void registry.unbind(token2);
|
|
11102
|
+
});
|
|
11103
|
+
}
|
|
10819
11104
|
const client = bindClientToSession(connection, session, state);
|
|
10820
11105
|
const { entries: replay } = await session.attach(client, "full");
|
|
10821
11106
|
state.attached.set(session.sessionId, {
|
|
@@ -11536,6 +11821,336 @@ function bindClientToSession(connection, session, state, clientInfo, callerClien
|
|
|
11536
11821
|
};
|
|
11537
11822
|
}
|
|
11538
11823
|
|
|
11824
|
+
// src/daemon/mcp/stdin-registry.ts
|
|
11825
|
+
var StdinMcpRegistry = class {
|
|
11826
|
+
byToken = /* @__PURE__ */ new Map();
|
|
11827
|
+
// Reserve a token slot before the session exists. Used by acp-ws when
|
|
11828
|
+
// we need to inject the bearer into the agent's mcpServers BEFORE
|
|
11829
|
+
// manager.create() returns — claude-acp connects to /mcp/stdin during
|
|
11830
|
+
// session/new initialization (eagerly), so the route handler must be
|
|
11831
|
+
// able to find the token by the time the agent's first request lands.
|
|
11832
|
+
reserve(token) {
|
|
11833
|
+
if (this.byToken.has(token)) {
|
|
11834
|
+
throw new Error(`stdin MCP token already bound`);
|
|
11835
|
+
}
|
|
11836
|
+
let resolveSession;
|
|
11837
|
+
let rejectSession;
|
|
11838
|
+
const sessionReady = new Promise((resolve3, reject) => {
|
|
11839
|
+
resolveSession = resolve3;
|
|
11840
|
+
rejectSession = reject;
|
|
11841
|
+
});
|
|
11842
|
+
sessionReady.catch(() => void 0);
|
|
11843
|
+
const entry = { session: void 0, sessionReady };
|
|
11844
|
+
this.byToken.set(token, entry);
|
|
11845
|
+
return {
|
|
11846
|
+
complete: (session) => {
|
|
11847
|
+
entry.session = session;
|
|
11848
|
+
resolveSession(session);
|
|
11849
|
+
},
|
|
11850
|
+
abandon: (reason) => {
|
|
11851
|
+
this.byToken.delete(token);
|
|
11852
|
+
rejectSession(reason ?? new Error("stdin MCP reservation abandoned"));
|
|
11853
|
+
}
|
|
11854
|
+
};
|
|
11855
|
+
}
|
|
11856
|
+
// Convenience for callers that already have the session in hand (and
|
|
11857
|
+
// for tests). Equivalent to reserve() + complete() back-to-back.
|
|
11858
|
+
bind(token, session) {
|
|
11859
|
+
const { complete } = this.reserve(token);
|
|
11860
|
+
complete(session);
|
|
11861
|
+
}
|
|
11862
|
+
lookup(token) {
|
|
11863
|
+
return this.byToken.get(token);
|
|
11864
|
+
}
|
|
11865
|
+
attachTransport(token, server, transport) {
|
|
11866
|
+
const ep = this.byToken.get(token);
|
|
11867
|
+
if (!ep) {
|
|
11868
|
+
return;
|
|
11869
|
+
}
|
|
11870
|
+
ep.server = server;
|
|
11871
|
+
ep.transport = transport;
|
|
11872
|
+
}
|
|
11873
|
+
async unbind(token) {
|
|
11874
|
+
const ep = this.byToken.get(token);
|
|
11875
|
+
if (!ep) {
|
|
11876
|
+
return;
|
|
11877
|
+
}
|
|
11878
|
+
this.byToken.delete(token);
|
|
11879
|
+
if (ep.transport) {
|
|
11880
|
+
try {
|
|
11881
|
+
await ep.transport.close();
|
|
11882
|
+
} catch {
|
|
11883
|
+
}
|
|
11884
|
+
}
|
|
11885
|
+
if (ep.server) {
|
|
11886
|
+
try {
|
|
11887
|
+
await ep.server.close();
|
|
11888
|
+
} catch {
|
|
11889
|
+
}
|
|
11890
|
+
}
|
|
11891
|
+
}
|
|
11892
|
+
size() {
|
|
11893
|
+
return this.byToken.size;
|
|
11894
|
+
}
|
|
11895
|
+
};
|
|
11896
|
+
|
|
11897
|
+
// src/daemon/mcp/stdin-server.ts
|
|
11898
|
+
import { randomUUID } from "crypto";
|
|
11899
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11900
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
11901
|
+
import { z as z7 } from "zod";
|
|
11902
|
+
var BEARER_PREFIX2 = "Bearer ";
|
|
11903
|
+
function extractBearer(req) {
|
|
11904
|
+
const header = req.headers.authorization;
|
|
11905
|
+
if (typeof header !== "string") {
|
|
11906
|
+
return void 0;
|
|
11907
|
+
}
|
|
11908
|
+
if (!header.startsWith(BEARER_PREFIX2)) {
|
|
11909
|
+
return void 0;
|
|
11910
|
+
}
|
|
11911
|
+
const token = header.slice(BEARER_PREFIX2.length).trim();
|
|
11912
|
+
return token.length > 0 ? token : void 0;
|
|
11913
|
+
}
|
|
11914
|
+
function buildMcpServer(session) {
|
|
11915
|
+
const server = new McpServer(
|
|
11916
|
+
{ name: "hydra-stdin", version: "1.0.0" },
|
|
11917
|
+
{
|
|
11918
|
+
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."
|
|
11919
|
+
}
|
|
11920
|
+
);
|
|
11921
|
+
server.registerTool(
|
|
11922
|
+
"tail_stdin",
|
|
11923
|
+
{
|
|
11924
|
+
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.",
|
|
11925
|
+
inputSchema: {
|
|
11926
|
+
bytes: z7.number().int().min(1).describe("How many trailing bytes to return.")
|
|
11927
|
+
}
|
|
11928
|
+
},
|
|
11929
|
+
async ({ bytes }) => {
|
|
11930
|
+
const r = session.streamTail(bytes);
|
|
11931
|
+
return {
|
|
11932
|
+
content: [
|
|
11933
|
+
{
|
|
11934
|
+
type: "text",
|
|
11935
|
+
text: JSON.stringify(r)
|
|
11936
|
+
}
|
|
11937
|
+
],
|
|
11938
|
+
structuredContent: r
|
|
11939
|
+
};
|
|
11940
|
+
}
|
|
11941
|
+
);
|
|
11942
|
+
server.registerTool(
|
|
11943
|
+
"head_stdin",
|
|
11944
|
+
{
|
|
11945
|
+
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.",
|
|
11946
|
+
inputSchema: {
|
|
11947
|
+
bytes: z7.number().int().min(1).describe("How many leading bytes to return.")
|
|
11948
|
+
}
|
|
11949
|
+
},
|
|
11950
|
+
async ({ bytes }) => {
|
|
11951
|
+
const r = session.streamHead(bytes);
|
|
11952
|
+
return {
|
|
11953
|
+
content: [
|
|
11954
|
+
{
|
|
11955
|
+
type: "text",
|
|
11956
|
+
text: JSON.stringify(r)
|
|
11957
|
+
}
|
|
11958
|
+
],
|
|
11959
|
+
structuredContent: r
|
|
11960
|
+
};
|
|
11961
|
+
}
|
|
11962
|
+
);
|
|
11963
|
+
server.registerTool(
|
|
11964
|
+
"read_stdin",
|
|
11965
|
+
{
|
|
11966
|
+
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.",
|
|
11967
|
+
inputSchema: {
|
|
11968
|
+
cursor: z7.number().int().min(0).describe(
|
|
11969
|
+
"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)."
|
|
11970
|
+
),
|
|
11971
|
+
max_bytes: z7.number().int().min(1).optional().describe(
|
|
11972
|
+
"Optional cap on how many bytes to return. Server caps at 64 KiB regardless."
|
|
11973
|
+
),
|
|
11974
|
+
wait_ms: z7.number().int().min(0).optional().describe(
|
|
11975
|
+
"If no bytes are available, block up to this many ms for more (capped server-side at 60_000)."
|
|
11976
|
+
)
|
|
11977
|
+
}
|
|
11978
|
+
},
|
|
11979
|
+
async ({ cursor, max_bytes, wait_ms }) => {
|
|
11980
|
+
const r = await session.streamRead(cursor, max_bytes, wait_ms);
|
|
11981
|
+
return {
|
|
11982
|
+
content: [
|
|
11983
|
+
{
|
|
11984
|
+
type: "text",
|
|
11985
|
+
text: JSON.stringify(r)
|
|
11986
|
+
}
|
|
11987
|
+
],
|
|
11988
|
+
structuredContent: r
|
|
11989
|
+
};
|
|
11990
|
+
}
|
|
11991
|
+
);
|
|
11992
|
+
server.registerTool(
|
|
11993
|
+
"wait_for_more",
|
|
11994
|
+
{
|
|
11995
|
+
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.",
|
|
11996
|
+
inputSchema: {
|
|
11997
|
+
cursor: z7.number().int().min(0).describe("The cursor you've already consumed up to."),
|
|
11998
|
+
timeout_ms: z7.number().int().min(0).describe("Maximum ms to block (server caps at 60_000).")
|
|
11999
|
+
}
|
|
12000
|
+
},
|
|
12001
|
+
async ({ cursor, timeout_ms }) => {
|
|
12002
|
+
const outcome = await session.streamWaitFor(cursor, timeout_ms);
|
|
12003
|
+
const info = session.streamInfo();
|
|
12004
|
+
const payload = { outcome, writeCursor: info.writeCursor, closed: info.closed };
|
|
12005
|
+
return {
|
|
12006
|
+
content: [
|
|
12007
|
+
{
|
|
12008
|
+
type: "text",
|
|
12009
|
+
text: JSON.stringify(payload)
|
|
12010
|
+
}
|
|
12011
|
+
],
|
|
12012
|
+
structuredContent: payload
|
|
12013
|
+
};
|
|
12014
|
+
}
|
|
12015
|
+
);
|
|
12016
|
+
server.registerTool(
|
|
12017
|
+
"grep_stdin",
|
|
12018
|
+
{
|
|
12019
|
+
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.",
|
|
12020
|
+
inputSchema: {
|
|
12021
|
+
pattern: z7.string().min(1).describe(
|
|
12022
|
+
"Search pattern. Treated as a JavaScript regular expression by default (set `regex:false` for a literal substring match)."
|
|
12023
|
+
),
|
|
12024
|
+
regex: z7.boolean().optional().describe("Default true. Pass false to treat `pattern` as a literal substring."),
|
|
12025
|
+
case_insensitive: z7.boolean().optional().describe("Default false. Pass true for case-insensitive matching."),
|
|
12026
|
+
invert: z7.boolean().optional().describe("Default false. Pass true to return lines that do NOT match the pattern."),
|
|
12027
|
+
max_matches: z7.number().int().min(1).optional().describe("Default 100. Capped server-side at 1000."),
|
|
12028
|
+
max_bytes: z7.number().int().min(1).optional().describe("Default 64 KiB output. Capped server-side at 256 KiB."),
|
|
12029
|
+
context_before: z7.number().int().min(0).optional().describe("Default 0. Number of lines before each match to include (capped at 20)."),
|
|
12030
|
+
context_after: z7.number().int().min(0).optional().describe("Default 0. Number of lines after each match to include (capped at 20)."),
|
|
12031
|
+
cursor: z7.number().int().min(0).optional().describe(
|
|
12032
|
+
"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."
|
|
12033
|
+
)
|
|
12034
|
+
}
|
|
12035
|
+
},
|
|
12036
|
+
async (args) => {
|
|
12037
|
+
const opts = { pattern: args.pattern };
|
|
12038
|
+
if (args.regex !== void 0) {
|
|
12039
|
+
opts.regex = args.regex;
|
|
12040
|
+
}
|
|
12041
|
+
if (args.case_insensitive !== void 0) {
|
|
12042
|
+
opts.caseInsensitive = args.case_insensitive;
|
|
12043
|
+
}
|
|
12044
|
+
if (args.invert !== void 0) {
|
|
12045
|
+
opts.invert = args.invert;
|
|
12046
|
+
}
|
|
12047
|
+
if (args.max_matches !== void 0) {
|
|
12048
|
+
opts.maxMatches = args.max_matches;
|
|
12049
|
+
}
|
|
12050
|
+
if (args.max_bytes !== void 0) {
|
|
12051
|
+
opts.maxBytes = args.max_bytes;
|
|
12052
|
+
}
|
|
12053
|
+
if (args.context_before !== void 0) {
|
|
12054
|
+
opts.contextBefore = args.context_before;
|
|
12055
|
+
}
|
|
12056
|
+
if (args.context_after !== void 0) {
|
|
12057
|
+
opts.contextAfter = args.context_after;
|
|
12058
|
+
}
|
|
12059
|
+
if (args.cursor !== void 0) {
|
|
12060
|
+
opts.cursor = args.cursor;
|
|
12061
|
+
}
|
|
12062
|
+
const r = session.streamGrep(opts);
|
|
12063
|
+
const payload = r;
|
|
12064
|
+
return {
|
|
12065
|
+
content: [{ type: "text", text: JSON.stringify(r) }],
|
|
12066
|
+
structuredContent: payload
|
|
12067
|
+
};
|
|
12068
|
+
}
|
|
12069
|
+
);
|
|
12070
|
+
server.registerTool(
|
|
12071
|
+
"stdin_info",
|
|
12072
|
+
{
|
|
12073
|
+
description: "Report cursor / capacity / closed state of the stdin ring. Cheap; safe to call repeatedly.",
|
|
12074
|
+
inputSchema: {}
|
|
12075
|
+
},
|
|
12076
|
+
async () => {
|
|
12077
|
+
const r = session.streamInfo();
|
|
12078
|
+
return {
|
|
12079
|
+
content: [
|
|
12080
|
+
{
|
|
12081
|
+
type: "text",
|
|
12082
|
+
text: JSON.stringify(r)
|
|
12083
|
+
}
|
|
12084
|
+
],
|
|
12085
|
+
structuredContent: r
|
|
12086
|
+
};
|
|
12087
|
+
}
|
|
12088
|
+
);
|
|
12089
|
+
return server;
|
|
12090
|
+
}
|
|
12091
|
+
async function ensureTransport(token, session, registry) {
|
|
12092
|
+
const existing = registry.lookup(token);
|
|
12093
|
+
if (existing?.transport !== void 0) {
|
|
12094
|
+
return existing.transport;
|
|
12095
|
+
}
|
|
12096
|
+
const server = buildMcpServer(session);
|
|
12097
|
+
const transport = new StreamableHTTPServerTransport({
|
|
12098
|
+
sessionIdGenerator: () => randomUUID()
|
|
12099
|
+
});
|
|
12100
|
+
await server.connect(transport);
|
|
12101
|
+
registry.attachTransport(token, server, transport);
|
|
12102
|
+
return transport;
|
|
12103
|
+
}
|
|
12104
|
+
var SESSION_READY_TIMEOUT_MS = 1e4;
|
|
12105
|
+
async function handle(req, reply, registry) {
|
|
12106
|
+
const token = extractBearer(req);
|
|
12107
|
+
if (token === void 0) {
|
|
12108
|
+
reply.code(401).send({ error: "missing bearer token" });
|
|
12109
|
+
return;
|
|
12110
|
+
}
|
|
12111
|
+
const ep = registry.lookup(token);
|
|
12112
|
+
if (ep === void 0) {
|
|
12113
|
+
reply.code(404).send({ error: "unknown stdin token" });
|
|
12114
|
+
return;
|
|
12115
|
+
}
|
|
12116
|
+
let session;
|
|
12117
|
+
if (ep.session !== void 0) {
|
|
12118
|
+
session = ep.session;
|
|
12119
|
+
} else {
|
|
12120
|
+
let timer;
|
|
12121
|
+
const timeout = new Promise((resolve3) => {
|
|
12122
|
+
timer = setTimeout(() => resolve3(void 0), SESSION_READY_TIMEOUT_MS);
|
|
12123
|
+
});
|
|
12124
|
+
const resolved = await Promise.race([
|
|
12125
|
+
ep.sessionReady.catch(() => void 0),
|
|
12126
|
+
timeout
|
|
12127
|
+
]);
|
|
12128
|
+
if (timer !== void 0) {
|
|
12129
|
+
clearTimeout(timer);
|
|
12130
|
+
}
|
|
12131
|
+
if (resolved === void 0) {
|
|
12132
|
+
reply.code(503).send({ error: "session not ready" });
|
|
12133
|
+
return;
|
|
12134
|
+
}
|
|
12135
|
+
session = resolved;
|
|
12136
|
+
}
|
|
12137
|
+
const transport = await ensureTransport(token, session, registry);
|
|
12138
|
+
reply.hijack();
|
|
12139
|
+
await transport.handleRequest(req.raw, reply.raw, req.body);
|
|
12140
|
+
}
|
|
12141
|
+
function registerStdinMcpRoutes(app, registry) {
|
|
12142
|
+
const opts = { config: { skipAuth: true } };
|
|
12143
|
+
app.post("/mcp/stdin", opts, async (req, reply) => {
|
|
12144
|
+
await handle(req, reply, registry);
|
|
12145
|
+
});
|
|
12146
|
+
app.get("/mcp/stdin", opts, async (req, reply) => {
|
|
12147
|
+
await handle(req, reply, registry);
|
|
12148
|
+
});
|
|
12149
|
+
app.delete("/mcp/stdin", opts, async (req, reply) => {
|
|
12150
|
+
await handle(req, reply, registry);
|
|
12151
|
+
});
|
|
12152
|
+
}
|
|
12153
|
+
|
|
11539
12154
|
// src/daemon/server.ts
|
|
11540
12155
|
async function startDaemon(config, serviceToken) {
|
|
11541
12156
|
ensureLoopbackOrTls(config);
|
|
@@ -11641,6 +12256,19 @@ async function startDaemon(config, serviceToken) {
|
|
|
11641
12256
|
store: sessionTokenStore,
|
|
11642
12257
|
rateLimiter: authRateLimiter
|
|
11643
12258
|
});
|
|
12259
|
+
const stdinMcpRegistry = new StdinMcpRegistry();
|
|
12260
|
+
registerStdinMcpRoutes(app, stdinMcpRegistry);
|
|
12261
|
+
let daemonOriginCached;
|
|
12262
|
+
const getDaemonOrigin = () => {
|
|
12263
|
+
if (daemonOriginCached !== void 0) {
|
|
12264
|
+
return daemonOriginCached;
|
|
12265
|
+
}
|
|
12266
|
+
const addr = app.server.address();
|
|
12267
|
+
const port = addr && typeof addr === "object" ? addr.port : config.daemon.port;
|
|
12268
|
+
const scheme2 = config.daemon.tls ? "https" : "http";
|
|
12269
|
+
daemonOriginCached = `${scheme2}://${config.daemon.host}:${port}`;
|
|
12270
|
+
return daemonOriginCached;
|
|
12271
|
+
};
|
|
11644
12272
|
registerAcpWsEndpoint(app, {
|
|
11645
12273
|
validator,
|
|
11646
12274
|
manager,
|
|
@@ -11649,7 +12277,9 @@ async function startDaemon(config, serviceToken) {
|
|
|
11649
12277
|
onExtensionVersion: (name, version) => extensions.reportVersion(name, version),
|
|
11650
12278
|
onTransformerVersion: (name, version) => transformers.reportVersion(name, version),
|
|
11651
12279
|
transformers,
|
|
11652
|
-
extensionCommands
|
|
12280
|
+
extensionCommands,
|
|
12281
|
+
stdinMcpRegistry,
|
|
12282
|
+
getDaemonOrigin
|
|
11653
12283
|
});
|
|
11654
12284
|
await app.listen({ host: config.daemon.host, port: config.daemon.port });
|
|
11655
12285
|
const address = app.server.address();
|