@hydra-acp/cli 0.1.62 → 0.1.63
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 +1403 -396
- package/dist/index.d.ts +34 -3
- package/dist/index.js +763 -132
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -86,6 +86,12 @@ var init_paths = __esm({
|
|
|
86
86
|
sessionDir: (id) => path.join(hydraHome(), "sessions", id),
|
|
87
87
|
sessionFile: (id) => path.join(hydraHome(), "sessions", id, "meta.json"),
|
|
88
88
|
historyFile: (id) => path.join(hydraHome(), "sessions", id, "history.jsonl"),
|
|
89
|
+
// Content-addressed store for heavy tool payload (diff bodies, stdout)
|
|
90
|
+
// externalized out of history.jsonl. One file per unique blob, named by
|
|
91
|
+
// its sha256, so repeated identical content (e.g. an agent re-emitting
|
|
92
|
+
// the same full-file diff on every status tick) dedupes to one file.
|
|
93
|
+
toolsDir: (id) => path.join(hydraHome(), "sessions", id, "tools"),
|
|
94
|
+
toolBlobFile: (id, hash) => path.join(hydraHome(), "sessions", id, "tools", hash),
|
|
89
95
|
// Persisted prompt queue for a session. ndjson, one record per
|
|
90
96
|
// entry. Survives daemon restarts so queued prompts get a chance to
|
|
91
97
|
// run rather than being silently lost. Entries are removed BEFORE
|
|
@@ -499,6 +505,29 @@ var init_config = __esm({
|
|
|
499
505
|
// suppress them — the TUI hotkey ^T toggles this at runtime without
|
|
500
506
|
// persisting back to config.
|
|
501
507
|
showThoughts: z.boolean().default(true),
|
|
508
|
+
// How the terminal renders East-Asian "Ambiguous" width glyphs (em-dash
|
|
509
|
+
// —, smart quotes “ ”, ellipsis …, middle-dot ·). Most modern terminals
|
|
510
|
+
// draw them 1 col wide ("narrow"); CJK-locale / legacy setups draw them 2
|
|
511
|
+
// cols wide ("wide"). Defaults to "wide": counting ambiguous glyphs as 2
|
|
512
|
+
// cols never overflows the right margin (the worst case is wrapping a
|
|
513
|
+
// column early on narrow terminals, which is benign), whereas "narrow"
|
|
514
|
+
// bleeds past the margin on wide terminals. The thought gutter uses an
|
|
515
|
+
// ASCII marker, so this no longer affects marker alignment either way.
|
|
516
|
+
ambiguousWidth: z.enum(["narrow", "wide"]).default("wide"),
|
|
517
|
+
// How the TUI receives tool payload on attach/replay.
|
|
518
|
+
// "references" — the lean path (default): the daemon ships blob refs and
|
|
519
|
+
// the TUI fetches a diff/output body on demand when
|
|
520
|
+
// expanded, cutting replay size on tool-heavy sessions.
|
|
521
|
+
// Collapsed rows never fetch (they show a size hint), and
|
|
522
|
+
// old inline sessions/live turns are unaffected (no refs).
|
|
523
|
+
// "inline" — full content up front (the pre-externalization shape).
|
|
524
|
+
toolContent: z.enum(["inline", "references"]).default("references"),
|
|
525
|
+
// Unchanged context lines shown around each change in an expanded Edited
|
|
526
|
+
// diff. Some agents (e.g. pi) report edits as full-file old/new text via
|
|
527
|
+
// ACP "diff" content blocks; without hunking a 1-line edit would render
|
|
528
|
+
// the entire file. This bounds the context so only the changed region (±N
|
|
529
|
+
// lines) shows, with runs of unchanged lines collapsed to a marker.
|
|
530
|
+
diffContextLines: z.number().int().min(0).default(3),
|
|
502
531
|
// Cap on entries kept in the cross-session global prompt-history file
|
|
503
532
|
// (~/.hydra-acp/prompt-history). This is the ^P / ^R recall list
|
|
504
533
|
// shared across all sessions; it's append-only on disk, so long-lived
|
|
@@ -614,6 +643,12 @@ var init_config = __esm({
|
|
|
614
643
|
// a literal string ("~", "~/dev", "$HOME/work") so the config file is
|
|
615
644
|
// portable across machines; expanded via expandHome at use time.
|
|
616
645
|
defaultCwd: z.string().default("~"),
|
|
646
|
+
// Gzip externalized tool-content blobs at rest (tools/<sha256>.gz).
|
|
647
|
+
// Default true — text diffs/output compress ~3.5x and decompression is
|
|
648
|
+
// lazy (only on diff expand in references mode). Set false to write plain
|
|
649
|
+
// blobs instead, as an escape hatch if gzip CPU is ever a problem; reads
|
|
650
|
+
// transparently handle both, so flipping it only affects new writes.
|
|
651
|
+
compressToolContent: z.boolean().default(true),
|
|
617
652
|
// Cap on cold sessions shown in CLI `sessions` listing and the TUI
|
|
618
653
|
// picker. Live sessions are always included; cold are sorted by
|
|
619
654
|
// recency and truncated to this count. `--all` overrides in the CLI.
|
|
@@ -635,6 +670,9 @@ var init_config = __esm({
|
|
|
635
670
|
progressIndicator: true,
|
|
636
671
|
defaultEnterAction: "amend",
|
|
637
672
|
showThoughts: true,
|
|
673
|
+
ambiguousWidth: "wide",
|
|
674
|
+
toolContent: "references",
|
|
675
|
+
diffContextLines: 3,
|
|
638
676
|
promptHistoryMaxEntries: 2e3,
|
|
639
677
|
maxToolItems: 5,
|
|
640
678
|
maxPlanItems: 5,
|
|
@@ -850,7 +888,7 @@ async function promptPassword(prompt) {
|
|
|
850
888
|
if (!process.stdin.isTTY) {
|
|
851
889
|
return readLineFromStdin();
|
|
852
890
|
}
|
|
853
|
-
return new Promise((
|
|
891
|
+
return new Promise((resolve9, reject) => {
|
|
854
892
|
const stdin = process.stdin;
|
|
855
893
|
const wasRaw = stdin.isRaw === true;
|
|
856
894
|
let buffer = "";
|
|
@@ -867,7 +905,7 @@ async function promptPassword(prompt) {
|
|
|
867
905
|
if (byte === 10 || byte === 13) {
|
|
868
906
|
process.stdout.write("\n");
|
|
869
907
|
cleanup();
|
|
870
|
-
|
|
908
|
+
resolve9(buffer);
|
|
871
909
|
return;
|
|
872
910
|
}
|
|
873
911
|
if (byte === 3) {
|
|
@@ -893,7 +931,7 @@ async function promptPassword(prompt) {
|
|
|
893
931
|
});
|
|
894
932
|
}
|
|
895
933
|
function readLineFromStdin() {
|
|
896
|
-
return new Promise((
|
|
934
|
+
return new Promise((resolve9, reject) => {
|
|
897
935
|
let buffer = "";
|
|
898
936
|
process.stdin.setEncoding("utf8");
|
|
899
937
|
const onData = (chunk) => {
|
|
@@ -902,7 +940,7 @@ function readLineFromStdin() {
|
|
|
902
940
|
if (nl !== -1) {
|
|
903
941
|
process.stdin.removeListener("data", onData);
|
|
904
942
|
process.stdin.removeListener("error", onError);
|
|
905
|
-
|
|
943
|
+
resolve9(buffer.slice(0, nl).replace(/\r$/, ""));
|
|
906
944
|
}
|
|
907
945
|
};
|
|
908
946
|
const onError = (err) => {
|
|
@@ -1102,6 +1140,9 @@ function extractHydraMeta(meta) {
|
|
|
1102
1140
|
if (typeof obj.dripSpeed === "number" && obj.dripSpeed > 0) {
|
|
1103
1141
|
out.dripSpeed = obj.dripSpeed;
|
|
1104
1142
|
}
|
|
1143
|
+
if (obj.toolContent === "inline" || obj.toolContent === "references") {
|
|
1144
|
+
out.toolContent = obj.toolContent;
|
|
1145
|
+
}
|
|
1105
1146
|
if (obj.detachStatus === "detached") {
|
|
1106
1147
|
out.detachStatus = obj.detachStatus;
|
|
1107
1148
|
}
|
|
@@ -1736,9 +1777,9 @@ var init_connection = __esm({
|
|
|
1736
1777
|
}
|
|
1737
1778
|
const id = nanoid();
|
|
1738
1779
|
const message = { jsonrpc: "2.0", id, method, params };
|
|
1739
|
-
const response = new Promise((
|
|
1780
|
+
const response = new Promise((resolve9, reject) => {
|
|
1740
1781
|
this.pending.set(id, {
|
|
1741
|
-
resolve: (result) =>
|
|
1782
|
+
resolve: (result) => resolve9(result),
|
|
1742
1783
|
reject
|
|
1743
1784
|
});
|
|
1744
1785
|
this.stream.send(message).catch((err) => {
|
|
@@ -2069,14 +2110,14 @@ var init_stream_buffer = __esm({
|
|
|
2069
2110
|
if (cap === 0) {
|
|
2070
2111
|
return Promise.resolve("timeout");
|
|
2071
2112
|
}
|
|
2072
|
-
return new Promise((
|
|
2113
|
+
return new Promise((resolve9) => {
|
|
2073
2114
|
const waiter = {
|
|
2074
2115
|
resolve: (outcome) => {
|
|
2075
2116
|
if (waiter.timer !== void 0) {
|
|
2076
2117
|
clearTimeout(waiter.timer);
|
|
2077
2118
|
waiter.timer = void 0;
|
|
2078
2119
|
}
|
|
2079
|
-
|
|
2120
|
+
resolve9(outcome);
|
|
2080
2121
|
},
|
|
2081
2122
|
timer: setTimeout(() => {
|
|
2082
2123
|
const idx = this.waiters.indexOf(waiter);
|
|
@@ -2084,7 +2125,7 @@ var init_stream_buffer = __esm({
|
|
|
2084
2125
|
this.waiters.splice(idx, 1);
|
|
2085
2126
|
}
|
|
2086
2127
|
waiter.timer = void 0;
|
|
2087
|
-
|
|
2128
|
+
resolve9("timeout");
|
|
2088
2129
|
}, cap)
|
|
2089
2130
|
};
|
|
2090
2131
|
this.waiters.push(waiter);
|
|
@@ -2287,8 +2328,8 @@ var init_stream_buffer = __esm({
|
|
|
2287
2328
|
return out;
|
|
2288
2329
|
}
|
|
2289
2330
|
scheduleFileWrite(chunk) {
|
|
2290
|
-
const
|
|
2291
|
-
if (
|
|
2331
|
+
const path22 = this.filePath;
|
|
2332
|
+
if (path22 === void 0) {
|
|
2292
2333
|
return;
|
|
2293
2334
|
}
|
|
2294
2335
|
if (this.fileCapReached) {
|
|
@@ -2303,7 +2344,7 @@ var init_stream_buffer = __esm({
|
|
|
2303
2344
|
const slice = chunk.length <= remaining ? chunk : chunk.subarray(0, remaining);
|
|
2304
2345
|
this.fileBytesWritten += slice.length;
|
|
2305
2346
|
const willHitCap = this.fileBytesWritten >= this.fileCapBytes;
|
|
2306
|
-
this.fileWriteChain = this.fileWriteChain.then(() => fsp3.appendFile(
|
|
2347
|
+
this.fileWriteChain = this.fileWriteChain.then(() => fsp3.appendFile(path22, slice)).catch((err) => {
|
|
2307
2348
|
this.logWriteError?.(err);
|
|
2308
2349
|
});
|
|
2309
2350
|
if (willHitCap && !this.fileCapReached) {
|
|
@@ -2353,6 +2394,35 @@ var init_hydra_commands = __esm({
|
|
|
2353
2394
|
}
|
|
2354
2395
|
});
|
|
2355
2396
|
|
|
2397
|
+
// src/core/model-resolve.ts
|
|
2398
|
+
function trailingSegment(modelId) {
|
|
2399
|
+
const slash = modelId.lastIndexOf("/");
|
|
2400
|
+
const tail = slash === -1 ? modelId : modelId.slice(slash + 1);
|
|
2401
|
+
return tail.toLowerCase();
|
|
2402
|
+
}
|
|
2403
|
+
function resolveModelId(requested, advertised) {
|
|
2404
|
+
if (advertised.length === 0) {
|
|
2405
|
+
return { kind: "none", requested };
|
|
2406
|
+
}
|
|
2407
|
+
if (advertised.some((m) => m.modelId === requested)) {
|
|
2408
|
+
return { kind: "exact", modelId: requested };
|
|
2409
|
+
}
|
|
2410
|
+
const wantKey = trailingSegment(requested);
|
|
2411
|
+
const candidates = advertised.map((m) => m.modelId).filter((id) => trailingSegment(id) === wantKey);
|
|
2412
|
+
if (candidates.length === 1) {
|
|
2413
|
+
return { kind: "resolved", modelId: candidates[0], requested };
|
|
2414
|
+
}
|
|
2415
|
+
if (candidates.length > 1) {
|
|
2416
|
+
return { kind: "ambiguous", requested, candidates };
|
|
2417
|
+
}
|
|
2418
|
+
return { kind: "unknown", requested };
|
|
2419
|
+
}
|
|
2420
|
+
var init_model_resolve = __esm({
|
|
2421
|
+
"src/core/model-resolve.ts"() {
|
|
2422
|
+
"use strict";
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
|
|
2356
2426
|
// src/core/coalesce-replay.ts
|
|
2357
2427
|
function coalesceReplay(entries) {
|
|
2358
2428
|
if (entries.length === 0) {
|
|
@@ -2360,6 +2430,7 @@ function coalesceReplay(entries) {
|
|
|
2360
2430
|
}
|
|
2361
2431
|
const lastToolUpdateIndex = /* @__PURE__ */ new Map();
|
|
2362
2432
|
const mergedToolContent = /* @__PURE__ */ new Map();
|
|
2433
|
+
const carriedRawInput = /* @__PURE__ */ new Map();
|
|
2363
2434
|
for (let i = 0; i < entries.length; i++) {
|
|
2364
2435
|
const entry = entries[i];
|
|
2365
2436
|
if (entry === void 0) {
|
|
@@ -2374,6 +2445,9 @@ function coalesceReplay(entries) {
|
|
|
2374
2445
|
continue;
|
|
2375
2446
|
}
|
|
2376
2447
|
lastToolUpdateIndex.set(id, i);
|
|
2448
|
+
if (upd.rawInput && typeof upd.rawInput === "object" && !Array.isArray(upd.rawInput) && Object.keys(upd.rawInput).length > 0) {
|
|
2449
|
+
carriedRawInput.set(id, upd.rawInput);
|
|
2450
|
+
}
|
|
2377
2451
|
if (Array.isArray(upd.content) && upd.content.length > 0) {
|
|
2378
2452
|
const buf = mergedToolContent.get(id);
|
|
2379
2453
|
if (buf) {
|
|
@@ -2413,11 +2487,11 @@ function coalesceReplay(entries) {
|
|
|
2413
2487
|
if (id !== void 0 && lastToolUpdateIndex.get(id) !== i) {
|
|
2414
2488
|
continue;
|
|
2415
2489
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
out.push(entry);
|
|
2490
|
+
let emitted = id !== void 0 && mergedToolContent.has(id) ? withReplacedContent(entry, mergedToolContent.get(id) ?? []) : entry;
|
|
2491
|
+
if (id !== void 0 && carriedRawInput.has(id) && !hasRawInput(emitted)) {
|
|
2492
|
+
emitted = withRawInput(emitted, carriedRawInput.get(id));
|
|
2420
2493
|
}
|
|
2494
|
+
out.push(emitted);
|
|
2421
2495
|
continue;
|
|
2422
2496
|
}
|
|
2423
2497
|
if (kind === "plan") {
|
|
@@ -2488,6 +2562,22 @@ function withReplacedContent(entry, content) {
|
|
|
2488
2562
|
}
|
|
2489
2563
|
};
|
|
2490
2564
|
}
|
|
2565
|
+
function hasRawInput(entry) {
|
|
2566
|
+
const update = readUpdate(entry);
|
|
2567
|
+
const ri = update?.rawInput;
|
|
2568
|
+
return !!ri && typeof ri === "object" && !Array.isArray(ri) && Object.keys(ri).length > 0;
|
|
2569
|
+
}
|
|
2570
|
+
function withRawInput(entry, rawInput) {
|
|
2571
|
+
const params = entry.params ?? {};
|
|
2572
|
+
const update = params.update ?? {};
|
|
2573
|
+
return {
|
|
2574
|
+
...entry,
|
|
2575
|
+
params: {
|
|
2576
|
+
...params,
|
|
2577
|
+
update: { ...update, rawInput }
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2491
2581
|
var init_coalesce_replay = __esm({
|
|
2492
2582
|
"src/core/coalesce-replay.ts"() {
|
|
2493
2583
|
"use strict";
|
|
@@ -2495,22 +2585,22 @@ var init_coalesce_replay = __esm({
|
|
|
2495
2585
|
});
|
|
2496
2586
|
|
|
2497
2587
|
// src/core/queue-store.ts
|
|
2498
|
-
import * as
|
|
2588
|
+
import * as fs9 from "fs/promises";
|
|
2499
2589
|
async function rewriteQueue(sessionId, entries) {
|
|
2500
2590
|
const file = paths.queueFile(sessionId);
|
|
2501
2591
|
if (entries.length === 0) {
|
|
2502
|
-
await
|
|
2592
|
+
await fs9.unlink(file).catch(() => void 0);
|
|
2503
2593
|
return;
|
|
2504
2594
|
}
|
|
2505
|
-
await
|
|
2595
|
+
await fs9.mkdir(paths.sessionDir(sessionId), { recursive: true });
|
|
2506
2596
|
const body = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2507
|
-
await
|
|
2597
|
+
await fs9.writeFile(file, body, "utf8");
|
|
2508
2598
|
}
|
|
2509
2599
|
async function loadQueue(sessionId) {
|
|
2510
2600
|
const file = paths.queueFile(sessionId);
|
|
2511
2601
|
let text;
|
|
2512
2602
|
try {
|
|
2513
|
-
text = await
|
|
2603
|
+
text = await fs9.readFile(file, "utf8");
|
|
2514
2604
|
} catch (err) {
|
|
2515
2605
|
if (err.code === "ENOENT") {
|
|
2516
2606
|
return [];
|
|
@@ -2532,7 +2622,7 @@ async function loadQueue(sessionId) {
|
|
|
2532
2622
|
}
|
|
2533
2623
|
async function deleteQueue(sessionId) {
|
|
2534
2624
|
const file = paths.queueFile(sessionId);
|
|
2535
|
-
await
|
|
2625
|
+
await fs9.unlink(file).catch(() => void 0);
|
|
2536
2626
|
}
|
|
2537
2627
|
var init_queue_store = __esm({
|
|
2538
2628
|
"src/core/queue-store.ts"() {
|
|
@@ -2840,6 +2930,7 @@ var init_session = __esm({
|
|
|
2840
2930
|
"use strict";
|
|
2841
2931
|
init_stream_buffer();
|
|
2842
2932
|
init_hydra_commands();
|
|
2933
|
+
init_model_resolve();
|
|
2843
2934
|
init_coalesce_replay();
|
|
2844
2935
|
init_queue_store();
|
|
2845
2936
|
init_types();
|
|
@@ -3236,7 +3327,7 @@ var init_session = __esm({
|
|
|
3236
3327
|
const claimIdx = i;
|
|
3237
3328
|
const claimEnvelope = envelope;
|
|
3238
3329
|
const claimOriginatedBy = new Set(originatedBy);
|
|
3239
|
-
await new Promise((
|
|
3330
|
+
await new Promise((resolve9) => {
|
|
3240
3331
|
const timer = setTimeout(() => {
|
|
3241
3332
|
if (this.pendingClaims.delete(token)) {
|
|
3242
3333
|
this.broadcastQueueNotification(
|
|
@@ -3247,14 +3338,14 @@ var init_session = __esm({
|
|
|
3247
3338
|
claimEnvelope,
|
|
3248
3339
|
/* @__PURE__ */ new Set([...claimOriginatedBy, t.name]),
|
|
3249
3340
|
claimIdx + 1
|
|
3250
|
-
).then(
|
|
3341
|
+
).then(resolve9);
|
|
3251
3342
|
}
|
|
3252
3343
|
}, TRANSFORMER_CLAIM_TIMEOUT_MS);
|
|
3253
3344
|
if (typeof timer.unref === "function") {
|
|
3254
3345
|
timer.unref();
|
|
3255
3346
|
}
|
|
3256
3347
|
this.pendingClaims.set(token, {
|
|
3257
|
-
resolve: () =>
|
|
3348
|
+
resolve: () => resolve9(),
|
|
3258
3349
|
timer,
|
|
3259
3350
|
transformerName: t.name,
|
|
3260
3351
|
method: "session/update",
|
|
@@ -3348,11 +3439,11 @@ var init_session = __esm({
|
|
|
3348
3439
|
// Read the persisted history from disk. Returns [] if no history
|
|
3349
3440
|
// file exists (fresh session, never prompted). Used by attach() and
|
|
3350
3441
|
// the HTTP /history endpoint.
|
|
3351
|
-
async getHistorySnapshot() {
|
|
3442
|
+
async getHistorySnapshot(tools = "inline") {
|
|
3352
3443
|
if (!this.historyStore) {
|
|
3353
3444
|
return [];
|
|
3354
3445
|
}
|
|
3355
|
-
return this.historyStore.load(this.sessionId).catch(() => []);
|
|
3446
|
+
return this.historyStore.load(this.sessionId, { tools }).catch(() => []);
|
|
3356
3447
|
}
|
|
3357
3448
|
// Subscribe to recordable broadcast entries — fires once per entry
|
|
3358
3449
|
// that lands in history (so snapshot-shaped session_info/model/mode/
|
|
@@ -3402,7 +3493,7 @@ var init_session = __esm({
|
|
|
3402
3493
|
}
|
|
3403
3494
|
async loadReplay(historyPolicy, opts) {
|
|
3404
3495
|
const maybeCoalesce = (entries) => opts.raw ? entries : coalesceReplay(entries);
|
|
3405
|
-
const raw = await this.getHistorySnapshot();
|
|
3496
|
+
const raw = await this.getHistorySnapshot(opts.toolContent ?? "inline");
|
|
3406
3497
|
const state = this.buildStateSnapshotReplay();
|
|
3407
3498
|
if (historyPolicy === "after_message") {
|
|
3408
3499
|
const cutoff = opts.afterMessageId ? findMessageIdIndex(raw, opts.afterMessageId) : -1;
|
|
@@ -4132,7 +4223,7 @@ var init_session = __esm({
|
|
|
4132
4223
|
const claimIdx = i;
|
|
4133
4224
|
const claimEnvelope = envelope;
|
|
4134
4225
|
const claimOriginatedBy = new Set(originatedBy);
|
|
4135
|
-
return new Promise((
|
|
4226
|
+
return new Promise((resolve9) => {
|
|
4136
4227
|
const timer = setTimeout(() => {
|
|
4137
4228
|
if (this.pendingClaims.delete(token)) {
|
|
4138
4229
|
this.broadcastQueueNotification(
|
|
@@ -4144,14 +4235,14 @@ var init_session = __esm({
|
|
|
4144
4235
|
claimEnvelope,
|
|
4145
4236
|
/* @__PURE__ */ new Set([...claimOriginatedBy, t.name]),
|
|
4146
4237
|
claimIdx + 1
|
|
4147
|
-
).then(
|
|
4238
|
+
).then(resolve9).catch(() => resolve9(defaultStopPayload(method)));
|
|
4148
4239
|
}
|
|
4149
4240
|
}, TRANSFORMER_CLAIM_TIMEOUT_MS);
|
|
4150
4241
|
if (typeof timer.unref === "function") {
|
|
4151
4242
|
timer.unref();
|
|
4152
4243
|
}
|
|
4153
4244
|
this.pendingClaims.set(token, {
|
|
4154
|
-
resolve:
|
|
4245
|
+
resolve: resolve9,
|
|
4155
4246
|
timer,
|
|
4156
4247
|
transformerName: t.name,
|
|
4157
4248
|
method,
|
|
@@ -5071,6 +5162,27 @@ ${text}
|
|
|
5071
5162
|
sessionUpdate: "agent_message_chunk",
|
|
5072
5163
|
content: { type: "text", text: `
|
|
5073
5164
|
${body}
|
|
5165
|
+
` },
|
|
5166
|
+
_meta: { "hydra-acp": { synthetic: true } }
|
|
5167
|
+
}
|
|
5168
|
+
});
|
|
5169
|
+
return { stopReason: "end_turn" };
|
|
5170
|
+
}
|
|
5171
|
+
const resolution = resolveModelId(arg, this.agentAdvertisedModels);
|
|
5172
|
+
let modelId = arg;
|
|
5173
|
+
if (resolution.kind === "resolved") {
|
|
5174
|
+
modelId = resolution.modelId;
|
|
5175
|
+
} else if (resolution.kind === "ambiguous" || resolution.kind === "unknown") {
|
|
5176
|
+
const known = this.agentAdvertisedModels.map((m) => m.modelId).join("\n ");
|
|
5177
|
+
const reason = resolution.kind === "ambiguous" ? `"${arg}" matches multiple models: ${resolution.candidates.join(", ")}` : `"${arg}" is not an available model`;
|
|
5178
|
+
this.recordAndBroadcast("session/update", {
|
|
5179
|
+
sessionId: this.upstreamSessionId,
|
|
5180
|
+
update: {
|
|
5181
|
+
sessionUpdate: "agent_message_chunk",
|
|
5182
|
+
content: { type: "text", text: `
|
|
5183
|
+
${reason}.
|
|
5184
|
+
Available models:
|
|
5185
|
+
${known}
|
|
5074
5186
|
` },
|
|
5075
5187
|
_meta: { "hydra-acp": { synthetic: true } }
|
|
5076
5188
|
}
|
|
@@ -5079,7 +5191,7 @@ ${body}
|
|
|
5079
5191
|
}
|
|
5080
5192
|
await this.forwardRequest("session/set_model", {
|
|
5081
5193
|
sessionId: this.sessionId,
|
|
5082
|
-
modelId
|
|
5194
|
+
modelId
|
|
5083
5195
|
});
|
|
5084
5196
|
return { stopReason: "end_turn" };
|
|
5085
5197
|
}
|
|
@@ -5625,12 +5737,12 @@ ${body}
|
|
|
5625
5737
|
this.clients.clear();
|
|
5626
5738
|
if (this.streamBuffer !== void 0) {
|
|
5627
5739
|
const buf = this.streamBuffer;
|
|
5628
|
-
const
|
|
5740
|
+
const path22 = this.streamFilePath;
|
|
5629
5741
|
this.streamBuffer = void 0;
|
|
5630
5742
|
this.streamFilePath = void 0;
|
|
5631
5743
|
buf.close();
|
|
5632
|
-
if (
|
|
5633
|
-
void buf.drainFileWrites().then(() => fsp4.unlink(
|
|
5744
|
+
if (path22 !== void 0) {
|
|
5745
|
+
void buf.drainFileWrites().then(() => fsp4.unlink(path22).catch(() => void 0));
|
|
5634
5746
|
}
|
|
5635
5747
|
}
|
|
5636
5748
|
for (const handler of this.closeHandlers) {
|
|
@@ -5792,7 +5904,7 @@ ${body}
|
|
|
5792
5904
|
}
|
|
5793
5905
|
const clientParams = this.rewriteForClient(params);
|
|
5794
5906
|
const toolCallId = extractToolCallId(clientParams);
|
|
5795
|
-
return new Promise((
|
|
5907
|
+
return new Promise((resolve9, reject) => {
|
|
5796
5908
|
let settled = false;
|
|
5797
5909
|
const outbound = [];
|
|
5798
5910
|
const entry = { addClient: sendTo };
|
|
@@ -5831,7 +5943,7 @@ ${body}
|
|
|
5831
5943
|
update
|
|
5832
5944
|
}).catch(() => void 0);
|
|
5833
5945
|
}
|
|
5834
|
-
|
|
5946
|
+
resolve9(result);
|
|
5835
5947
|
});
|
|
5836
5948
|
}).catch((err) => {
|
|
5837
5949
|
settle(() => reject(err));
|
|
@@ -5847,14 +5959,14 @@ ${body}
|
|
|
5847
5959
|
// in flight, but doesn't emit prompt_queue_* broadcasts — clients
|
|
5848
5960
|
// shouldn't see hydra's housekeeping in their chip list.
|
|
5849
5961
|
async enqueuePrompt(task) {
|
|
5850
|
-
return new Promise((
|
|
5962
|
+
return new Promise((resolve9, reject) => {
|
|
5851
5963
|
const entry = {
|
|
5852
5964
|
kind: "internal",
|
|
5853
5965
|
messageId: generateMessageId(),
|
|
5854
5966
|
enqueuedAt: Date.now(),
|
|
5855
5967
|
cancelled: false,
|
|
5856
5968
|
task,
|
|
5857
|
-
resolve:
|
|
5969
|
+
resolve: resolve9,
|
|
5858
5970
|
reject
|
|
5859
5971
|
};
|
|
5860
5972
|
this.promptQueue.push(entry);
|
|
@@ -5873,7 +5985,7 @@ ${body}
|
|
|
5873
5985
|
if (client.clientInfo?.name) originator.name = client.clientInfo.name;
|
|
5874
5986
|
if (client.clientInfo?.version)
|
|
5875
5987
|
originator.version = client.clientInfo.version;
|
|
5876
|
-
return new Promise((
|
|
5988
|
+
return new Promise((resolve9, reject) => {
|
|
5877
5989
|
const entry = {
|
|
5878
5990
|
kind: "user",
|
|
5879
5991
|
messageId,
|
|
@@ -5882,7 +5994,7 @@ ${body}
|
|
|
5882
5994
|
prompt: promptArray,
|
|
5883
5995
|
enqueuedAt: Date.now(),
|
|
5884
5996
|
cancelled: false,
|
|
5885
|
-
resolve:
|
|
5997
|
+
resolve: resolve9,
|
|
5886
5998
|
reject
|
|
5887
5999
|
};
|
|
5888
6000
|
this.promptQueue.push(entry);
|
|
@@ -6062,14 +6174,14 @@ ${body}
|
|
|
6062
6174
|
// src/core/hydra-version.ts
|
|
6063
6175
|
import { fileURLToPath } from "url";
|
|
6064
6176
|
import * as path8 from "path";
|
|
6065
|
-
import * as
|
|
6177
|
+
import * as fs12 from "fs";
|
|
6066
6178
|
function resolveVersion() {
|
|
6067
6179
|
try {
|
|
6068
6180
|
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
6069
6181
|
for (let i = 0; i < 8; i += 1) {
|
|
6070
6182
|
const candidate = path8.join(dir, "package.json");
|
|
6071
|
-
if (
|
|
6072
|
-
const pkg = JSON.parse(
|
|
6183
|
+
if (fs12.existsSync(candidate)) {
|
|
6184
|
+
const pkg = JSON.parse(fs12.readFileSync(candidate, "utf8"));
|
|
6073
6185
|
if (typeof pkg.version === "string" && pkg.version.length > 0 && (typeof pkg.name !== "string" || pkg.name.includes("hydra-acp"))) {
|
|
6074
6186
|
return pkg.version;
|
|
6075
6187
|
}
|
|
@@ -6094,12 +6206,12 @@ var init_hydra_version = __esm({
|
|
|
6094
6206
|
});
|
|
6095
6207
|
|
|
6096
6208
|
// src/tui/history.ts
|
|
6097
|
-
import { promises as
|
|
6209
|
+
import { promises as fs15 } from "fs";
|
|
6098
6210
|
import * as path9 from "path";
|
|
6099
6211
|
async function loadHistory(file) {
|
|
6100
6212
|
let text;
|
|
6101
6213
|
try {
|
|
6102
|
-
text = await
|
|
6214
|
+
text = await fs15.readFile(file, "utf8");
|
|
6103
6215
|
} catch (err) {
|
|
6104
6216
|
if (err.code === "ENOENT") {
|
|
6105
6217
|
return [];
|
|
@@ -6139,17 +6251,17 @@ function appendEntry(history, entry, cap = HISTORY_CAP) {
|
|
|
6139
6251
|
return out;
|
|
6140
6252
|
}
|
|
6141
6253
|
async function saveHistory(file, history) {
|
|
6142
|
-
await
|
|
6254
|
+
await fs15.mkdir(path9.dirname(file), { recursive: true });
|
|
6143
6255
|
const lines = history.map((entry) => JSON.stringify(entry));
|
|
6144
|
-
await
|
|
6256
|
+
await fs15.writeFile(file, lines.length > 0 ? lines.join("\n") + "\n" : "");
|
|
6145
6257
|
}
|
|
6146
6258
|
async function appendHistoryLine(file, entry) {
|
|
6147
6259
|
const trimmed = entry.replace(/\n+$/, "");
|
|
6148
6260
|
if (trimmed.length === 0) {
|
|
6149
6261
|
return;
|
|
6150
6262
|
}
|
|
6151
|
-
await
|
|
6152
|
-
await
|
|
6263
|
+
await fs15.mkdir(path9.dirname(file), { recursive: true });
|
|
6264
|
+
await fs15.appendFile(file, JSON.stringify(trimmed) + "\n", {
|
|
6153
6265
|
encoding: "utf8"
|
|
6154
6266
|
});
|
|
6155
6267
|
}
|
|
@@ -6422,6 +6534,23 @@ function isExitPlanModeTool(name) {
|
|
|
6422
6534
|
const normalised = name.toLowerCase().replace(/[_\s-]/g, "");
|
|
6423
6535
|
return normalised === "exitplanmode";
|
|
6424
6536
|
}
|
|
6537
|
+
function readDiffField(value) {
|
|
6538
|
+
if (typeof value === "string") {
|
|
6539
|
+
return { text: value };
|
|
6540
|
+
}
|
|
6541
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
6542
|
+
const v = value;
|
|
6543
|
+
if (typeof v.__hydraBlob === "string") {
|
|
6544
|
+
return {
|
|
6545
|
+
ref: {
|
|
6546
|
+
hash: v.__hydraBlob,
|
|
6547
|
+
bytes: typeof v.bytes === "number" ? v.bytes : 0
|
|
6548
|
+
}
|
|
6549
|
+
};
|
|
6550
|
+
}
|
|
6551
|
+
}
|
|
6552
|
+
return void 0;
|
|
6553
|
+
}
|
|
6425
6554
|
function extractEditDiff(u) {
|
|
6426
6555
|
const content = u.content;
|
|
6427
6556
|
if (Array.isArray(content)) {
|
|
@@ -6433,16 +6562,18 @@ function extractEditDiff(u) {
|
|
|
6433
6562
|
if (b.type !== "diff") {
|
|
6434
6563
|
continue;
|
|
6435
6564
|
}
|
|
6436
|
-
const
|
|
6437
|
-
const
|
|
6438
|
-
if (
|
|
6565
|
+
const oldField = readDiffField(b.oldText);
|
|
6566
|
+
const newField = readDiffField(b.newText);
|
|
6567
|
+
if (oldField === void 0 && newField === void 0) {
|
|
6439
6568
|
continue;
|
|
6440
6569
|
}
|
|
6441
|
-
const
|
|
6570
|
+
const path22 = typeof b.path === "string" ? b.path : void 0;
|
|
6442
6571
|
return {
|
|
6443
|
-
...
|
|
6444
|
-
oldText:
|
|
6445
|
-
newText:
|
|
6572
|
+
...path22 !== void 0 ? { path: path22 } : {},
|
|
6573
|
+
oldText: oldField?.text ?? "",
|
|
6574
|
+
newText: newField?.text ?? "",
|
|
6575
|
+
...oldField?.ref ? { oldRef: oldField.ref } : {},
|
|
6576
|
+
...newField?.ref ? { newRef: newField.ref } : {}
|
|
6446
6577
|
};
|
|
6447
6578
|
}
|
|
6448
6579
|
}
|
|
@@ -6510,8 +6641,35 @@ function mapToolCall(u) {
|
|
|
6510
6641
|
if (diff !== null) {
|
|
6511
6642
|
event.editDiff = diff;
|
|
6512
6643
|
}
|
|
6644
|
+
const detail = extractToolDetail(u);
|
|
6645
|
+
if (detail !== void 0) {
|
|
6646
|
+
event.detail = detail;
|
|
6647
|
+
}
|
|
6513
6648
|
return event;
|
|
6514
6649
|
}
|
|
6650
|
+
function extractToolDetail(u) {
|
|
6651
|
+
const rawInput = u.rawInput;
|
|
6652
|
+
if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) {
|
|
6653
|
+
return void 0;
|
|
6654
|
+
}
|
|
6655
|
+
const r = rawInput;
|
|
6656
|
+
if (typeof r.command === "string" && r.command.trim().length > 0) {
|
|
6657
|
+
const firstLine3 = sanitizeSingleLine(r.command).trim();
|
|
6658
|
+
const cmd = firstLine3.replace(/^cd\s+\S+\s+&&\s+/, "");
|
|
6659
|
+
return clipHead(cmd, TOOL_DETAIL_MAX);
|
|
6660
|
+
}
|
|
6661
|
+
const path22 = typeof r.file_path === "string" ? r.file_path : typeof r.path === "string" ? r.path : void 0;
|
|
6662
|
+
if (path22 !== void 0 && path22.length > 0) {
|
|
6663
|
+
return clipTail(shortenHomePath(sanitizeSingleLine(path22)), TOOL_DETAIL_MAX);
|
|
6664
|
+
}
|
|
6665
|
+
return void 0;
|
|
6666
|
+
}
|
|
6667
|
+
function clipHead(s, max) {
|
|
6668
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
6669
|
+
}
|
|
6670
|
+
function clipTail(s, max) {
|
|
6671
|
+
return s.length > max ? `\u2026${s.slice(-(max - 1))}` : s;
|
|
6672
|
+
}
|
|
6515
6673
|
function mapToolCallUpdate(u) {
|
|
6516
6674
|
const toolCallId = readString(u, "toolCallId") ?? readString(u, "id");
|
|
6517
6675
|
if (!toolCallId) {
|
|
@@ -6521,7 +6679,8 @@ function mapToolCallUpdate(u) {
|
|
|
6521
6679
|
const title = rawTitle !== void 0 ? sanitizeSingleLine(rawTitle) : void 0;
|
|
6522
6680
|
const status = readString(u, "status");
|
|
6523
6681
|
const diff = extractEditDiff(u);
|
|
6524
|
-
const
|
|
6682
|
+
const detail = extractToolDetail(u);
|
|
6683
|
+
const meaningful = title !== void 0 || diff !== null || detail !== void 0 || status === "completed" || status === "failed" || status === "rejected" || status === "cancelled";
|
|
6525
6684
|
if (!meaningful) {
|
|
6526
6685
|
return null;
|
|
6527
6686
|
}
|
|
@@ -6541,6 +6700,9 @@ function mapToolCallUpdate(u) {
|
|
|
6541
6700
|
if (title !== void 0) {
|
|
6542
6701
|
event.title = title;
|
|
6543
6702
|
}
|
|
6703
|
+
if (detail !== void 0) {
|
|
6704
|
+
event.detail = detail;
|
|
6705
|
+
}
|
|
6544
6706
|
if (status !== void 0) {
|
|
6545
6707
|
event.status = status;
|
|
6546
6708
|
}
|
|
@@ -6693,11 +6855,13 @@ function readString(u, key) {
|
|
|
6693
6855
|
const v = u[key];
|
|
6694
6856
|
return typeof v === "string" ? v : void 0;
|
|
6695
6857
|
}
|
|
6696
|
-
var STRIP_CONTROLS;
|
|
6858
|
+
var STRIP_CONTROLS, TOOL_DETAIL_MAX;
|
|
6697
6859
|
var init_render_update = __esm({
|
|
6698
6860
|
"src/core/render-update.ts"() {
|
|
6699
6861
|
"use strict";
|
|
6862
|
+
init_paths();
|
|
6700
6863
|
STRIP_CONTROLS = /[\x00-\x08\x0b-\x1f\x7f]/g;
|
|
6864
|
+
TOOL_DETAIL_MAX = 64;
|
|
6701
6865
|
}
|
|
6702
6866
|
});
|
|
6703
6867
|
|
|
@@ -6746,13 +6910,13 @@ function wsToMessageStream(ws) {
|
|
|
6746
6910
|
throw new Error("ws is closed");
|
|
6747
6911
|
}
|
|
6748
6912
|
const text = JSON.stringify(message);
|
|
6749
|
-
await new Promise((
|
|
6913
|
+
await new Promise((resolve9, reject) => {
|
|
6750
6914
|
ws.send(text, (err) => {
|
|
6751
6915
|
if (err) {
|
|
6752
6916
|
reject(err);
|
|
6753
6917
|
return;
|
|
6754
6918
|
}
|
|
6755
|
-
|
|
6919
|
+
resolve9();
|
|
6756
6920
|
});
|
|
6757
6921
|
});
|
|
6758
6922
|
},
|
|
@@ -7354,7 +7518,7 @@ function formatEvent(event, options = {}) {
|
|
|
7354
7518
|
case "agent-text":
|
|
7355
7519
|
return formatBlock(event.text, " ", "agent");
|
|
7356
7520
|
case "agent-thought":
|
|
7357
|
-
return formatBlock(event.text, "
|
|
7521
|
+
return formatBlock(event.text, " ", "thought", "thought");
|
|
7358
7522
|
case "tool-call":
|
|
7359
7523
|
case "tool-call-update":
|
|
7360
7524
|
return [];
|
|
@@ -7417,7 +7581,7 @@ function parseMarkdown(text, opts) {
|
|
|
7417
7581
|
maxWidth
|
|
7418
7582
|
} = opts;
|
|
7419
7583
|
const out = [];
|
|
7420
|
-
const lines = text.split("\n");
|
|
7584
|
+
const lines = text.replace(/^\s+/, "").split("\n");
|
|
7421
7585
|
let inCode = false;
|
|
7422
7586
|
let codeLang = "";
|
|
7423
7587
|
let codeBuffer = [];
|
|
@@ -7553,7 +7717,7 @@ function parseThoughtMarkdown(text) {
|
|
|
7553
7717
|
proseStyle: "thought",
|
|
7554
7718
|
highlightCode: false,
|
|
7555
7719
|
prefixStyle: "thought",
|
|
7556
|
-
firstPrefix: "
|
|
7720
|
+
firstPrefix: " ",
|
|
7557
7721
|
inlineOpts: { codeOpen: "^c", boldReset: "^-", codeReset: "^K" }
|
|
7558
7722
|
});
|
|
7559
7723
|
}
|
|
@@ -7848,7 +8012,7 @@ function highlightFencedBlock(lang, lines) {
|
|
|
7848
8012
|
}));
|
|
7849
8013
|
}
|
|
7850
8014
|
function formatBlock(text, prefix, bodyStyle, prefixStyle, sentBy, fillRow) {
|
|
7851
|
-
const lines = text.split("\n");
|
|
8015
|
+
const lines = text.replace(/^\s+/, "").split("\n");
|
|
7852
8016
|
const out = [];
|
|
7853
8017
|
if (sentBy) {
|
|
7854
8018
|
out.push({
|
|
@@ -7872,6 +8036,15 @@ function formatBlock(text, prefix, bodyStyle, prefixStyle, sentBy, fillRow) {
|
|
|
7872
8036
|
}
|
|
7873
8037
|
return out;
|
|
7874
8038
|
}
|
|
8039
|
+
function formatDiffBytes(bytes) {
|
|
8040
|
+
if (bytes < 1024) {
|
|
8041
|
+
return `${bytes} B`;
|
|
8042
|
+
}
|
|
8043
|
+
if (bytes < 1024 * 1024) {
|
|
8044
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
8045
|
+
}
|
|
8046
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
8047
|
+
}
|
|
7875
8048
|
function formatElapsed(ms) {
|
|
7876
8049
|
const totalSec = Math.floor(ms / 1e3);
|
|
7877
8050
|
if (totalSec < 60) {
|
|
@@ -7899,6 +8072,9 @@ function formatToolLine2(state, now = Date.now()) {
|
|
|
7899
8072
|
} else {
|
|
7900
8073
|
title = `${initial} \xB7 ${latest}`;
|
|
7901
8074
|
}
|
|
8075
|
+
if (state.detail && !title.includes(state.detail)) {
|
|
8076
|
+
title = `${title} \xB7 ${state.detail}`;
|
|
8077
|
+
}
|
|
7902
8078
|
if (state.startedAt !== void 0) {
|
|
7903
8079
|
const end = state.endedAt ?? now;
|
|
7904
8080
|
title = `${title} \xB7 ${formatElapsed(end - state.startedAt)}`;
|
|
@@ -7920,17 +8096,27 @@ function formatToolLine2(state, now = Date.now()) {
|
|
|
7920
8096
|
}
|
|
7921
8097
|
return lines;
|
|
7922
8098
|
}
|
|
7923
|
-
function
|
|
8099
|
+
function setDiffContextLines(n) {
|
|
8100
|
+
diffContextLines = n >= 0 ? n : 0;
|
|
8101
|
+
}
|
|
8102
|
+
function formatEditDiffBlock(diff, mode, opts = {}) {
|
|
7924
8103
|
const lines = [];
|
|
7925
|
-
const
|
|
7926
|
-
|
|
7927
|
-
if (
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
8104
|
+
const deferred = diff.oldRef !== void 0 || diff.newRef !== void 0;
|
|
8105
|
+
let summary;
|
|
8106
|
+
if (deferred) {
|
|
8107
|
+
const bytes = (diff.oldRef?.bytes ?? 0) + (diff.newRef?.bytes ?? 0);
|
|
8108
|
+
summary = ` (~${formatDiffBytes(bytes)})`;
|
|
8109
|
+
} else {
|
|
8110
|
+
const counts = countDiffChanges(diff);
|
|
8111
|
+
const summaryParts = [];
|
|
8112
|
+
if (counts.added > 0) {
|
|
8113
|
+
summaryParts.push(`+${counts.added}`);
|
|
8114
|
+
}
|
|
8115
|
+
if (counts.removed > 0) {
|
|
8116
|
+
summaryParts.push(`-${counts.removed}`);
|
|
8117
|
+
}
|
|
8118
|
+
summary = summaryParts.length > 0 ? ` (${summaryParts.join(" ")})` : "";
|
|
7932
8119
|
}
|
|
7933
|
-
const summary = summaryParts.length > 0 ? ` (${summaryParts.join(" ")})` : "";
|
|
7934
8120
|
const header = (open2) => ({
|
|
7935
8121
|
prefix: " ",
|
|
7936
8122
|
body: `${open2 ? "\u25BE" : "\u25B8"} Edited ${sanitizeSingleLine(shortenHomePath(diff.path))}${summary}`,
|
|
@@ -7942,6 +8128,24 @@ function formatEditDiffBlock(diff, mode) {
|
|
|
7942
8128
|
}
|
|
7943
8129
|
return lines;
|
|
7944
8130
|
}
|
|
8131
|
+
if (deferred) {
|
|
8132
|
+
if (diff.path) {
|
|
8133
|
+
lines.push(header(true));
|
|
8134
|
+
lines.push(
|
|
8135
|
+
opts.deferredStatus === "error" ? {
|
|
8136
|
+
prefix: " ",
|
|
8137
|
+
body: "\u26A0 failed to load diff",
|
|
8138
|
+
bodyStyle: "tool-status-fail"
|
|
8139
|
+
} : {
|
|
8140
|
+
prefix: " ",
|
|
8141
|
+
body: "\u22EF fetching diff\u2026",
|
|
8142
|
+
bodyStyle: "dim"
|
|
8143
|
+
}
|
|
8144
|
+
);
|
|
8145
|
+
lines.unshift({ body: "" });
|
|
8146
|
+
}
|
|
8147
|
+
return lines;
|
|
8148
|
+
}
|
|
7945
8149
|
const body = buildUnifiedDiff(diff, { maxLines: Infinity });
|
|
7946
8150
|
if (body.length === 0) {
|
|
7947
8151
|
if (diff.path) {
|
|
@@ -7983,32 +8187,100 @@ function countDiffChanges(diff) {
|
|
|
7983
8187
|
}
|
|
7984
8188
|
return { added, removed };
|
|
7985
8189
|
}
|
|
8190
|
+
function renderDiffOp(op) {
|
|
8191
|
+
if (op.op === "=") {
|
|
8192
|
+
return ` ${op.text}`;
|
|
8193
|
+
}
|
|
8194
|
+
return op.op === "-" ? `- ${op.text}` : `+ ${op.text}`;
|
|
8195
|
+
}
|
|
7986
8196
|
function buildUnifiedDiff(diff, opts = {}) {
|
|
7987
8197
|
const maxLines = opts.maxLines ?? EDIT_DIFF_MAX_LINES;
|
|
8198
|
+
const ctx = opts.contextLines ?? diffContextLines;
|
|
7988
8199
|
const { oldLines, newLines } = diffLinePair(diff);
|
|
7989
8200
|
const ops = diffLines(oldLines, newLines);
|
|
8201
|
+
const display = [];
|
|
8202
|
+
if (!Number.isFinite(ctx)) {
|
|
8203
|
+
for (const op of ops) {
|
|
8204
|
+
display.push(renderDiffOp(op));
|
|
8205
|
+
}
|
|
8206
|
+
} else {
|
|
8207
|
+
const hasChange = ops.some((o) => o.op !== "=");
|
|
8208
|
+
if (!hasChange) {
|
|
8209
|
+
return "";
|
|
8210
|
+
}
|
|
8211
|
+
const keep = new Array(ops.length).fill(false);
|
|
8212
|
+
for (let i2 = 0; i2 < ops.length; i2++) {
|
|
8213
|
+
if (ops[i2].op !== "=") {
|
|
8214
|
+
const lo = Math.max(0, i2 - ctx);
|
|
8215
|
+
const hi = Math.min(ops.length - 1, i2 + ctx);
|
|
8216
|
+
for (let k = lo; k <= hi; k++) {
|
|
8217
|
+
keep[k] = true;
|
|
8218
|
+
}
|
|
8219
|
+
}
|
|
8220
|
+
}
|
|
8221
|
+
let i = 0;
|
|
8222
|
+
while (i < ops.length) {
|
|
8223
|
+
if (keep[i]) {
|
|
8224
|
+
display.push(renderDiffOp(ops[i]));
|
|
8225
|
+
i++;
|
|
8226
|
+
continue;
|
|
8227
|
+
}
|
|
8228
|
+
let j = i;
|
|
8229
|
+
while (j < ops.length && !keep[j]) {
|
|
8230
|
+
j++;
|
|
8231
|
+
}
|
|
8232
|
+
const skipped = j - i;
|
|
8233
|
+
display.push(` \u22EF ${skipped} unchanged line${skipped === 1 ? "" : "s"}`);
|
|
8234
|
+
i = j;
|
|
8235
|
+
}
|
|
8236
|
+
}
|
|
7990
8237
|
const rendered = [];
|
|
7991
|
-
for (let idx = 0; idx <
|
|
7992
|
-
const
|
|
7993
|
-
const wouldTruncate = rendered.length >= maxLines - 1 && idx < ops.length - 1;
|
|
8238
|
+
for (let idx = 0; idx < display.length; idx++) {
|
|
8239
|
+
const wouldTruncate = rendered.length >= maxLines - 1 && idx < display.length - 1;
|
|
7994
8240
|
if (wouldTruncate) {
|
|
7995
|
-
const remaining =
|
|
8241
|
+
const remaining = display.length - idx;
|
|
7996
8242
|
rendered.push(`\u2026 ${remaining} more line${remaining === 1 ? "" : "s"}`);
|
|
7997
8243
|
break;
|
|
7998
8244
|
}
|
|
7999
|
-
|
|
8000
|
-
rendered.push(` ${op.text}`);
|
|
8001
|
-
} else if (op.op === "-") {
|
|
8002
|
-
rendered.push(`- ${op.text}`);
|
|
8003
|
-
} else {
|
|
8004
|
-
rendered.push(`+ ${op.text}`);
|
|
8005
|
-
}
|
|
8245
|
+
rendered.push(display[idx]);
|
|
8006
8246
|
}
|
|
8007
8247
|
return rendered.join("\n");
|
|
8008
8248
|
}
|
|
8009
8249
|
function diffLines(a, b) {
|
|
8250
|
+
let start = 0;
|
|
8251
|
+
const minLen = Math.min(a.length, b.length);
|
|
8252
|
+
while (start < minLen && a[start] === b[start]) {
|
|
8253
|
+
start++;
|
|
8254
|
+
}
|
|
8255
|
+
let endA = a.length;
|
|
8256
|
+
let endB = b.length;
|
|
8257
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
8258
|
+
endA--;
|
|
8259
|
+
endB--;
|
|
8260
|
+
}
|
|
8261
|
+
const out = [];
|
|
8262
|
+
for (let k = 0; k < start; k++) {
|
|
8263
|
+
out.push({ op: "=", text: a[k] });
|
|
8264
|
+
}
|
|
8265
|
+
out.push(...lcsDiff(a.slice(start, endA), b.slice(start, endB)));
|
|
8266
|
+
for (let k = endA; k < a.length; k++) {
|
|
8267
|
+
out.push({ op: "=", text: a[k] });
|
|
8268
|
+
}
|
|
8269
|
+
return out;
|
|
8270
|
+
}
|
|
8271
|
+
function lcsDiff(a, b) {
|
|
8010
8272
|
const m = a.length;
|
|
8011
8273
|
const n = b.length;
|
|
8274
|
+
if (m === 0 || n === 0) {
|
|
8275
|
+
const out2 = [];
|
|
8276
|
+
for (const text of a) {
|
|
8277
|
+
out2.push({ op: "-", text });
|
|
8278
|
+
}
|
|
8279
|
+
for (const text of b) {
|
|
8280
|
+
out2.push({ op: "+", text });
|
|
8281
|
+
}
|
|
8282
|
+
return out2;
|
|
8283
|
+
}
|
|
8012
8284
|
const dp = Array.from(
|
|
8013
8285
|
{ length: m + 1 },
|
|
8014
8286
|
() => new Array(n + 1).fill(0)
|
|
@@ -8245,7 +8517,7 @@ function toolStatusStyle(status) {
|
|
|
8245
8517
|
return "tool-status-pending";
|
|
8246
8518
|
}
|
|
8247
8519
|
}
|
|
8248
|
-
var TABLE_MIN_COL, TABLE_PREFIX_WIDTH, TABLE_SEP_WIDTH, highlightChalk, HIGHLIGHT_THEME, EDIT_DIFF_MAX_LINES, PLAN_VISIBLE_LIMIT;
|
|
8520
|
+
var TABLE_MIN_COL, TABLE_PREFIX_WIDTH, TABLE_SEP_WIDTH, highlightChalk, HIGHLIGHT_THEME, EDIT_DIFF_MAX_LINES, diffContextLines, PLAN_VISIBLE_LIMIT;
|
|
8249
8521
|
var init_format = __esm({
|
|
8250
8522
|
"src/tui/format.ts"() {
|
|
8251
8523
|
"use strict";
|
|
@@ -8280,6 +8552,7 @@ var init_format = __esm({
|
|
|
8280
8552
|
name: highlightChalk.cyanBright
|
|
8281
8553
|
};
|
|
8282
8554
|
EDIT_DIFF_MAX_LINES = 40;
|
|
8555
|
+
diffContextLines = Number.POSITIVE_INFINITY;
|
|
8283
8556
|
PLAN_VISIBLE_LIMIT = 5;
|
|
8284
8557
|
}
|
|
8285
8558
|
});
|
|
@@ -8291,11 +8564,11 @@ function isResponse(msg) {
|
|
|
8291
8564
|
return !("method" in msg) && "id" in msg && msg.id !== void 0;
|
|
8292
8565
|
}
|
|
8293
8566
|
async function openWs(url, subprotocols) {
|
|
8294
|
-
return new Promise((
|
|
8567
|
+
return new Promise((resolve9, reject) => {
|
|
8295
8568
|
const ws = new WebSocket(url, subprotocols);
|
|
8296
8569
|
const onOpen = () => {
|
|
8297
8570
|
ws.off("error", onError);
|
|
8298
|
-
|
|
8571
|
+
resolve9(wsToMessageStream(ws));
|
|
8299
8572
|
};
|
|
8300
8573
|
const onError = (err) => {
|
|
8301
8574
|
ws.off("open", onOpen);
|
|
@@ -8366,8 +8639,8 @@ var init_resilient_ws = __esm({
|
|
|
8366
8639
|
throw new Error("resilient ws stream not connected");
|
|
8367
8640
|
}
|
|
8368
8641
|
const id = message.id;
|
|
8369
|
-
const promise = new Promise((
|
|
8370
|
-
this.pendingRequests.set(id, { resolve:
|
|
8642
|
+
const promise = new Promise((resolve9, reject) => {
|
|
8643
|
+
this.pendingRequests.set(id, { resolve: resolve9, reject });
|
|
8371
8644
|
});
|
|
8372
8645
|
try {
|
|
8373
8646
|
await this.current.send(message);
|
|
@@ -8395,8 +8668,8 @@ var init_resilient_ws = __esm({
|
|
|
8395
8668
|
this.bindStream(stream);
|
|
8396
8669
|
const wasFirst = this.firstConnect;
|
|
8397
8670
|
this.firstConnect = false;
|
|
8398
|
-
this.connectGate = new Promise((
|
|
8399
|
-
this.releaseConnectGate =
|
|
8671
|
+
this.connectGate = new Promise((resolve9) => {
|
|
8672
|
+
this.releaseConnectGate = resolve9;
|
|
8400
8673
|
});
|
|
8401
8674
|
try {
|
|
8402
8675
|
if (this.opts.onConnect) {
|
|
@@ -8717,7 +8990,7 @@ var init_update_check = __esm({
|
|
|
8717
8990
|
});
|
|
8718
8991
|
|
|
8719
8992
|
// src/core/cwd.ts
|
|
8720
|
-
import * as
|
|
8993
|
+
import * as fs24 from "fs/promises";
|
|
8721
8994
|
import * as path17 from "path";
|
|
8722
8995
|
async function validateLocalCwd(input) {
|
|
8723
8996
|
const trimmed = input.trim();
|
|
@@ -8727,7 +9000,7 @@ async function validateLocalCwd(input) {
|
|
|
8727
9000
|
const resolved = path17.resolve(expandHome(trimmed));
|
|
8728
9001
|
let stat5;
|
|
8729
9002
|
try {
|
|
8730
|
-
stat5 = await
|
|
9003
|
+
stat5 = await fs24.stat(resolved);
|
|
8731
9004
|
} catch {
|
|
8732
9005
|
return { ok: false, reason: `${resolved} does not exist` };
|
|
8733
9006
|
}
|
|
@@ -8753,7 +9026,7 @@ async function pickInitialLocalCwd(sessionCwd) {
|
|
|
8753
9026
|
}
|
|
8754
9027
|
for (const candidate of candidates) {
|
|
8755
9028
|
try {
|
|
8756
|
-
const stat5 = await
|
|
9029
|
+
const stat5 = await fs24.stat(candidate);
|
|
8757
9030
|
if (stat5.isDirectory()) {
|
|
8758
9031
|
return candidate;
|
|
8759
9032
|
}
|
|
@@ -8779,7 +9052,7 @@ async function completeLocalPath(input) {
|
|
|
8779
9052
|
const resolvedDir = path17.resolve(expandHome(dirForRead));
|
|
8780
9053
|
let entries;
|
|
8781
9054
|
try {
|
|
8782
|
-
const list = await
|
|
9055
|
+
const list = await fs24.readdir(resolvedDir, { withFileTypes: true });
|
|
8783
9056
|
entries = list.map((e) => ({ name: e.name, isDir: e.isDirectory() }));
|
|
8784
9057
|
} catch {
|
|
8785
9058
|
return { prefix, basePrefix, matches: [] };
|
|
@@ -8918,6 +9191,17 @@ var init_input = __esm({
|
|
|
8918
9191
|
this.col = text.length;
|
|
8919
9192
|
}
|
|
8920
9193
|
}
|
|
9194
|
+
// Replace a [start, end) span on the current line with `text`, landing
|
|
9195
|
+
// the cursor at the end of the inserted text. Used by file-path tab
|
|
9196
|
+
// completion: the typed path token gets swapped for the completed one.
|
|
9197
|
+
// Out-of-range bounds are clamped to the line length.
|
|
9198
|
+
replaceRangeOnCurrentLine(start, end, text) {
|
|
9199
|
+
const line = this.currentLine();
|
|
9200
|
+
const s = Math.max(0, Math.min(start, line.length));
|
|
9201
|
+
const e = Math.max(s, Math.min(end, line.length));
|
|
9202
|
+
this.setCurrentLine(line.slice(0, s) + text + line.slice(e));
|
|
9203
|
+
this.col = s + text.length;
|
|
9204
|
+
}
|
|
8921
9205
|
// Public seed for the buffer (used for Escape pre-fill). Treated like a
|
|
8922
9206
|
// fresh draft: nav state and any saved draft are cleared, cursor lands
|
|
8923
9207
|
// at the end so the user can edit immediately. Attachments restore
|
|
@@ -9999,7 +10283,12 @@ function writeBodyWithHighlight(termObj, text, style, term, activeCol = null, _a
|
|
|
9999
10283
|
}
|
|
10000
10284
|
}
|
|
10001
10285
|
function bodyStyleUsesMarkup(style) {
|
|
10002
|
-
return style === "agent" ||
|
|
10286
|
+
return style === "agent" || // Thoughts switched to the markup-interpreting writer (writeStyled's
|
|
10287
|
+
// "thought" case uses term.brightBlack without .noFormat), so their
|
|
10288
|
+
// caret spans (^ccode^K, ^+bold^-) are zero-width on screen. wrap/
|
|
10289
|
+
// truncate must strip them too or thought lines wrap several columns
|
|
10290
|
+
// short of the margin whenever they contain inline code/bold.
|
|
10291
|
+
style === "thought" || style === "heading-1" || style === "heading-2" || style === "heading-3";
|
|
10003
10292
|
}
|
|
10004
10293
|
function writeStyled(term, text, style) {
|
|
10005
10294
|
if (text.length === 0) {
|
|
@@ -10082,6 +10371,12 @@ function wrapAnsiBody(text, width) {
|
|
|
10082
10371
|
}
|
|
10083
10372
|
return wrapAnsi(text, width, { hard: true, trim: false }).split("\n");
|
|
10084
10373
|
}
|
|
10374
|
+
function setAmbiguousWide(wide) {
|
|
10375
|
+
ambiguousWide = wide;
|
|
10376
|
+
}
|
|
10377
|
+
function cellWidth(text) {
|
|
10378
|
+
return stringWidth2(text, { ambiguousIsNarrow: !ambiguousWide });
|
|
10379
|
+
}
|
|
10085
10380
|
function matchTkMarkupAt(text, i) {
|
|
10086
10381
|
if (text.charCodeAt(i) !== 94) {
|
|
10087
10382
|
return null;
|
|
@@ -10139,7 +10434,7 @@ function* segmentForWidth(text) {
|
|
|
10139
10434
|
continue;
|
|
10140
10435
|
}
|
|
10141
10436
|
for (const { segment } of SEGMENTER.segment(text.slice(i, runEnd))) {
|
|
10142
|
-
yield { text: segment, width:
|
|
10437
|
+
yield { text: segment, width: cellWidth(segment) };
|
|
10143
10438
|
}
|
|
10144
10439
|
i = runEnd;
|
|
10145
10440
|
}
|
|
@@ -10233,7 +10528,7 @@ function wrapVisible(text, width, stripMarkup) {
|
|
|
10233
10528
|
function graphemeSegments(text) {
|
|
10234
10529
|
const out = [];
|
|
10235
10530
|
for (const { segment } of SEGMENTER.segment(text)) {
|
|
10236
|
-
out.push({ text: segment, width:
|
|
10531
|
+
out.push({ text: segment, width: cellWidth(segment) });
|
|
10237
10532
|
}
|
|
10238
10533
|
return out;
|
|
10239
10534
|
}
|
|
@@ -10246,7 +10541,7 @@ function truncate(text, max, opts = {}) {
|
|
|
10246
10541
|
return text;
|
|
10247
10542
|
}
|
|
10248
10543
|
if (!stripMarkup) {
|
|
10249
|
-
const visible2 =
|
|
10544
|
+
const visible2 = cellWidth(text);
|
|
10250
10545
|
if (visible2 <= max) {
|
|
10251
10546
|
return text;
|
|
10252
10547
|
}
|
|
@@ -10275,7 +10570,7 @@ function takeByWidth(text, budget) {
|
|
|
10275
10570
|
let out = "";
|
|
10276
10571
|
let used = 0;
|
|
10277
10572
|
for (const { segment } of SEGMENTER.segment(text)) {
|
|
10278
|
-
const w =
|
|
10573
|
+
const w = cellWidth(segment);
|
|
10279
10574
|
if (used + w > budget) {
|
|
10280
10575
|
break;
|
|
10281
10576
|
}
|
|
@@ -10540,7 +10835,7 @@ function mapCsiUToKeyName(code, mod) {
|
|
|
10540
10835
|
}
|
|
10541
10836
|
return null;
|
|
10542
10837
|
}
|
|
10543
|
-
var SESSIONBAR_ROWS, BANNER_ROWS, SEPARATOR_ROWS, MAX_PROMPT_ROWS, MAX_QUEUED_ROWS, MAX_PERMISSION_ROWS, MAX_OPTIONS_ROWS, MAX_HELP_ROWS, MAX_COMPLETION_ROWS, MAX_CHIP_ROWS, CONFIRM_PROMPT_ROWS, DEFAULT_CONTENT_REPAINT_THROTTLE_MS, DEFAULT_MAX_SCROLLBACK_LINES, BARE_URL_RE, Screen, NON_ASCII, SEGMENTER, TK_MARKUP_STYLE_CHAR, shortId;
|
|
10838
|
+
var SESSIONBAR_ROWS, BANNER_ROWS, SEPARATOR_ROWS, MAX_PROMPT_ROWS, MAX_QUEUED_ROWS, MAX_PERMISSION_ROWS, MAX_OPTIONS_ROWS, MAX_HELP_ROWS, MAX_COMPLETION_ROWS, MAX_CHIP_ROWS, CONFIRM_PROMPT_ROWS, DEFAULT_CONTENT_REPAINT_THROTTLE_MS, DEFAULT_MAX_SCROLLBACK_LINES, BARE_URL_RE, Screen, NON_ASCII, SEGMENTER, ambiguousWide, TK_MARKUP_STYLE_CHAR, shortId;
|
|
10544
10839
|
var init_screen = __esm({
|
|
10545
10840
|
"src/tui/screen.ts"() {
|
|
10546
10841
|
"use strict";
|
|
@@ -10569,6 +10864,9 @@ var init_screen = __esm({
|
|
|
10569
10864
|
dispatcher;
|
|
10570
10865
|
onKey;
|
|
10571
10866
|
onBlockClick;
|
|
10867
|
+
onBlockVisible;
|
|
10868
|
+
// Keyed blocks awaiting a one-shot "became visible" notification.
|
|
10869
|
+
pendingVisibleKeys = /* @__PURE__ */ new Set();
|
|
10572
10870
|
lines = [];
|
|
10573
10871
|
// Tracks contiguous blocks of lines that callers may want to mutate in
|
|
10574
10872
|
// place (e.g. tool-call rows that update from "pending" to "completed",
|
|
@@ -10716,6 +11014,7 @@ var init_screen = __esm({
|
|
|
10716
11014
|
this.dispatcher = opts.dispatcher;
|
|
10717
11015
|
this.onKey = opts.onKey;
|
|
10718
11016
|
this.onBlockClick = opts.onBlockClick;
|
|
11017
|
+
this.onBlockVisible = opts.onBlockVisible;
|
|
10719
11018
|
this.contentRepaintThrottleMs = opts.repaintThrottleMs ?? DEFAULT_CONTENT_REPAINT_THROTTLE_MS;
|
|
10720
11019
|
this.maxScrollbackLines = opts.maxScrollbackLines ?? DEFAULT_MAX_SCROLLBACK_LINES;
|
|
10721
11020
|
this.mouseEnabled = opts.mouse ?? false;
|
|
@@ -11721,7 +12020,11 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11721
12020
|
// plan to its eventual tail position, and the separator we're adding
|
|
11722
12021
|
// here is for whatever content is *about* to be appended (which will
|
|
11723
12022
|
// land at sticky.start and push the plan back via moveStickyToEnd).
|
|
11724
|
-
|
|
12023
|
+
// `bodyStyle` tags the inserted blank so a draw-time filter can drop it
|
|
12024
|
+
// together with the block it precedes. Thoughts pass "thought" so the ^T
|
|
12025
|
+
// hide-thoughts filter removes the gap above a hidden thought instead of
|
|
12026
|
+
// leaving an orphaned blank line in scrollback.
|
|
12027
|
+
ensureSeparator(bodyStyle) {
|
|
11725
12028
|
if (this.lines.length === 0) {
|
|
11726
12029
|
return;
|
|
11727
12030
|
}
|
|
@@ -11736,6 +12039,9 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11736
12039
|
return;
|
|
11737
12040
|
}
|
|
11738
12041
|
const sep2 = { body: "" };
|
|
12042
|
+
if (bodyStyle !== void 0) {
|
|
12043
|
+
sep2.bodyStyle = bodyStyle;
|
|
12044
|
+
}
|
|
11739
12045
|
if (stickyAtEnd) {
|
|
11740
12046
|
this.lines.splice(sticky.start, 0, sep2);
|
|
11741
12047
|
sticky.start += 1;
|
|
@@ -12299,6 +12605,36 @@ uncaught: ${err.stack ?? err.message}
|
|
|
12299
12605
|
}
|
|
12300
12606
|
});
|
|
12301
12607
|
}
|
|
12608
|
+
if (this.onBlockVisible && this.pendingVisibleKeys.size > 0) {
|
|
12609
|
+
const visible = /* @__PURE__ */ new Set();
|
|
12610
|
+
for (const r of slice) {
|
|
12611
|
+
if (r.blockKey !== void 0) {
|
|
12612
|
+
visible.add(r.blockKey);
|
|
12613
|
+
}
|
|
12614
|
+
}
|
|
12615
|
+
const fire = [];
|
|
12616
|
+
for (const key of this.pendingVisibleKeys) {
|
|
12617
|
+
if (visible.has(key)) {
|
|
12618
|
+
fire.push(key);
|
|
12619
|
+
}
|
|
12620
|
+
}
|
|
12621
|
+
for (const key of fire) {
|
|
12622
|
+
this.pendingVisibleKeys.delete(key);
|
|
12623
|
+
this.onBlockVisible(key);
|
|
12624
|
+
}
|
|
12625
|
+
}
|
|
12626
|
+
}
|
|
12627
|
+
// Register a keyed block to receive a single onBlockVisible callback the
|
|
12628
|
+
// next time any of its rows are painted in the visible window. If it's
|
|
12629
|
+
// already on screen the pending repaint fires it promptly; otherwise it
|
|
12630
|
+
// waits until the block scrolls into view. No-op without an onBlockVisible
|
|
12631
|
+
// handler.
|
|
12632
|
+
notifyWhenVisible(key) {
|
|
12633
|
+
if (!this.onBlockVisible) {
|
|
12634
|
+
return;
|
|
12635
|
+
}
|
|
12636
|
+
this.pendingVisibleKeys.add(key);
|
|
12637
|
+
this.scheduleRepaint();
|
|
12302
12638
|
}
|
|
12303
12639
|
queuedRows() {
|
|
12304
12640
|
return Math.min(MAX_QUEUED_ROWS, this.queuedTexts.length);
|
|
@@ -12878,7 +13214,8 @@ uncaught: ${err.stack ?? err.message}
|
|
|
12878
13214
|
}
|
|
12879
13215
|
}
|
|
12880
13216
|
const prefix = line.prefix ?? "";
|
|
12881
|
-
const
|
|
13217
|
+
const prefixCols = cellWidth(prefix);
|
|
13218
|
+
const room = Math.max(1, width - prefixCols);
|
|
12882
13219
|
const stripMarkup = bodyStyleUsesMarkup(line.bodyStyle);
|
|
12883
13220
|
const chunks = line.ansi ? wrapAnsiBody(line.body, room) : wrap(line.body, room, { stripMarkup });
|
|
12884
13221
|
const wrapped = [];
|
|
@@ -12886,7 +13223,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
12886
13223
|
for (let i = 0; i < chunks.length; i++) {
|
|
12887
13224
|
const chunk = chunks[i] ?? "";
|
|
12888
13225
|
const wrappedLine = {
|
|
12889
|
-
prefix: i === 0 ? line.prefix : " ".repeat(
|
|
13226
|
+
prefix: i === 0 ? line.prefix : " ".repeat(prefixCols),
|
|
12890
13227
|
body: chunk
|
|
12891
13228
|
};
|
|
12892
13229
|
if (line.prefixStyle !== void 0) {
|
|
@@ -12927,7 +13264,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
12927
13264
|
if (line.prefix) {
|
|
12928
13265
|
writeStyled(this.term, line.prefix, line.prefixStyle ?? line.bodyStyle);
|
|
12929
13266
|
}
|
|
12930
|
-
const remaining = Math.max(0, width - (line.prefix
|
|
13267
|
+
const remaining = Math.max(0, width - cellWidth(line.prefix ?? ""));
|
|
12931
13268
|
const stripMarkup = bodyStyleUsesMarkup(line.bodyStyle);
|
|
12932
13269
|
const bodyText = line.ansi ? line.body : truncate(line.body, remaining, { stripMarkup });
|
|
12933
13270
|
if (this.scrollbackHighlight !== null && !line.ansi) {
|
|
@@ -12943,7 +13280,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
12943
13280
|
writeStyled(this.term, bodyText, line.bodyStyle);
|
|
12944
13281
|
}
|
|
12945
13282
|
if (line.fillRow) {
|
|
12946
|
-
const visible = line.ansi ? stringWidth2(bodyText) : bodyText
|
|
13283
|
+
const visible = line.ansi ? stringWidth2(bodyText) : cellWidth(bodyText);
|
|
12947
13284
|
const pad = remaining - visible;
|
|
12948
13285
|
if (pad > 0) {
|
|
12949
13286
|
writeStyled(this.term, " ".repeat(pad), line.bodyStyle);
|
|
@@ -12962,6 +13299,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
12962
13299
|
};
|
|
12963
13300
|
NON_ASCII = /[^\x20-\x7e]/;
|
|
12964
13301
|
SEGMENTER = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
13302
|
+
ambiguousWide = false;
|
|
12965
13303
|
TK_MARKUP_STYLE_CHAR = /[a-zA-Z+\-:_!#/]/;
|
|
12966
13304
|
shortId = stripHydraSessionPrefix;
|
|
12967
13305
|
}
|
|
@@ -13166,7 +13504,7 @@ async function promptForImportAction(term, session) {
|
|
|
13166
13504
|
};
|
|
13167
13505
|
render();
|
|
13168
13506
|
term.hideCursor();
|
|
13169
|
-
return await new Promise((
|
|
13507
|
+
return await new Promise((resolve9) => {
|
|
13170
13508
|
let resolved = false;
|
|
13171
13509
|
const cleanup = () => {
|
|
13172
13510
|
if (resolved) {
|
|
@@ -13181,7 +13519,7 @@ async function promptForImportAction(term, session) {
|
|
|
13181
13519
|
};
|
|
13182
13520
|
const finish = (value) => {
|
|
13183
13521
|
cleanup();
|
|
13184
|
-
|
|
13522
|
+
resolve9(value);
|
|
13185
13523
|
};
|
|
13186
13524
|
const onResize = () => {
|
|
13187
13525
|
if (resolved) {
|
|
@@ -13285,7 +13623,7 @@ async function promptForLaunchOrView(term, session, focus) {
|
|
|
13285
13623
|
};
|
|
13286
13624
|
render();
|
|
13287
13625
|
term.hideCursor();
|
|
13288
|
-
return await new Promise((
|
|
13626
|
+
return await new Promise((resolve9) => {
|
|
13289
13627
|
let resolved = false;
|
|
13290
13628
|
const cleanup = () => {
|
|
13291
13629
|
resolved = true;
|
|
@@ -13293,7 +13631,7 @@ async function promptForLaunchOrView(term, session, focus) {
|
|
|
13293
13631
|
const finish = (value) => {
|
|
13294
13632
|
cleanup();
|
|
13295
13633
|
focus.pop();
|
|
13296
|
-
|
|
13634
|
+
resolve9(value);
|
|
13297
13635
|
};
|
|
13298
13636
|
const onKey = (name, _m, data) => {
|
|
13299
13637
|
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
@@ -14228,7 +14566,7 @@ async function pickSession(term, opts) {
|
|
|
14228
14566
|
}
|
|
14229
14567
|
};
|
|
14230
14568
|
renderFromScratch();
|
|
14231
|
-
return await new Promise((
|
|
14569
|
+
return await new Promise((resolve9) => {
|
|
14232
14570
|
let resolved = false;
|
|
14233
14571
|
let autoRefreshTimer = null;
|
|
14234
14572
|
let autoRefreshInFlight = false;
|
|
@@ -14315,7 +14653,7 @@ async function pickSession(term, opts) {
|
|
|
14315
14653
|
};
|
|
14316
14654
|
const tryAbort = () => {
|
|
14317
14655
|
cleanup();
|
|
14318
|
-
|
|
14656
|
+
resolve9({ kind: currentSessionGone ? "exit" : "abort" });
|
|
14319
14657
|
return true;
|
|
14320
14658
|
};
|
|
14321
14659
|
const renderFingerprint = () => {
|
|
@@ -14495,7 +14833,7 @@ ${cells}`;
|
|
|
14495
14833
|
onKey: (name) => {
|
|
14496
14834
|
if (name === "CTRL_C") {
|
|
14497
14835
|
cleanup();
|
|
14498
|
-
|
|
14836
|
+
resolve9({ kind: "abort" });
|
|
14499
14837
|
return;
|
|
14500
14838
|
}
|
|
14501
14839
|
popLayer();
|
|
@@ -14609,7 +14947,7 @@ ${cells}`;
|
|
|
14609
14947
|
if (session.agentId !== void 0) {
|
|
14610
14948
|
result.agentId = session.agentId;
|
|
14611
14949
|
}
|
|
14612
|
-
|
|
14950
|
+
resolve9(result);
|
|
14613
14951
|
return;
|
|
14614
14952
|
}
|
|
14615
14953
|
void (async () => {
|
|
@@ -14620,7 +14958,7 @@ ${cells}`;
|
|
|
14620
14958
|
}, focus);
|
|
14621
14959
|
if (action === "cancel") {
|
|
14622
14960
|
cleanup();
|
|
14623
|
-
|
|
14961
|
+
resolve9({ kind: "abort" });
|
|
14624
14962
|
return;
|
|
14625
14963
|
}
|
|
14626
14964
|
if (action === "back") return;
|
|
@@ -14633,7 +14971,7 @@ ${cells}`;
|
|
|
14633
14971
|
if (session?.agentId !== void 0) {
|
|
14634
14972
|
result.agentId = session.agentId;
|
|
14635
14973
|
}
|
|
14636
|
-
|
|
14974
|
+
resolve9(result);
|
|
14637
14975
|
})();
|
|
14638
14976
|
return;
|
|
14639
14977
|
}
|
|
@@ -14794,9 +15132,9 @@ ${cells}`;
|
|
|
14794
15132
|
cleanup();
|
|
14795
15133
|
const text = composer.expandedText();
|
|
14796
15134
|
if (text.trim().length === 0) {
|
|
14797
|
-
|
|
15135
|
+
resolve9({ kind: "new" });
|
|
14798
15136
|
} else {
|
|
14799
|
-
|
|
15137
|
+
resolve9({ kind: "new", prompt: text });
|
|
14800
15138
|
}
|
|
14801
15139
|
return;
|
|
14802
15140
|
}
|
|
@@ -14920,7 +15258,7 @@ ${cells}`;
|
|
|
14920
15258
|
}
|
|
14921
15259
|
if (name === "c" || name === "C") {
|
|
14922
15260
|
cleanup();
|
|
14923
|
-
|
|
15261
|
+
resolve9({ kind: "new" });
|
|
14924
15262
|
return;
|
|
14925
15263
|
}
|
|
14926
15264
|
if (name === "q" || name === "Q") {
|
|
@@ -14977,7 +15315,7 @@ ${cells}`;
|
|
|
14977
15315
|
if (session.agentId !== void 0) {
|
|
14978
15316
|
result.agentId = session.agentId;
|
|
14979
15317
|
}
|
|
14980
|
-
|
|
15318
|
+
resolve9(result);
|
|
14981
15319
|
return;
|
|
14982
15320
|
}
|
|
14983
15321
|
if ((name === "f" || name === "F") && selectedIdx > 0) {
|
|
@@ -15000,7 +15338,7 @@ ${cells}`;
|
|
|
15000
15338
|
if (session.upstreamSessionId !== void 0) {
|
|
15001
15339
|
result.sourceUpstreamSessionId = session.upstreamSessionId;
|
|
15002
15340
|
}
|
|
15003
|
-
|
|
15341
|
+
resolve9(result);
|
|
15004
15342
|
return;
|
|
15005
15343
|
}
|
|
15006
15344
|
if ((name === "k" || name === "K") && selectedIdx > 0) {
|
|
@@ -15089,12 +15427,12 @@ ${cells}`;
|
|
|
15089
15427
|
case "KP_ENTER": {
|
|
15090
15428
|
cleanup();
|
|
15091
15429
|
if (selectedIdx === 0) {
|
|
15092
|
-
|
|
15430
|
+
resolve9({ kind: "new" });
|
|
15093
15431
|
return;
|
|
15094
15432
|
}
|
|
15095
15433
|
const session = visible[selectedIdx - 1];
|
|
15096
15434
|
if (!session) {
|
|
15097
|
-
|
|
15435
|
+
resolve9({ kind: "abort" });
|
|
15098
15436
|
return;
|
|
15099
15437
|
}
|
|
15100
15438
|
const result = {
|
|
@@ -15104,7 +15442,7 @@ ${cells}`;
|
|
|
15104
15442
|
if (session.agentId !== void 0) {
|
|
15105
15443
|
result.agentId = session.agentId;
|
|
15106
15444
|
}
|
|
15107
|
-
|
|
15445
|
+
resolve9(result);
|
|
15108
15446
|
return;
|
|
15109
15447
|
}
|
|
15110
15448
|
case "ESCAPE":
|
|
@@ -15385,7 +15723,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
15385
15723
|
}
|
|
15386
15724
|
};
|
|
15387
15725
|
render();
|
|
15388
|
-
return await new Promise((
|
|
15726
|
+
return await new Promise((resolve9) => {
|
|
15389
15727
|
let resolved = false;
|
|
15390
15728
|
const cleanup = () => {
|
|
15391
15729
|
if (resolved) {
|
|
@@ -15400,7 +15738,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
15400
15738
|
};
|
|
15401
15739
|
const finish = (value) => {
|
|
15402
15740
|
cleanup();
|
|
15403
|
-
|
|
15741
|
+
resolve9(value);
|
|
15404
15742
|
};
|
|
15405
15743
|
const onResize = () => {
|
|
15406
15744
|
if (resolved) {
|
|
@@ -15606,7 +15944,7 @@ async function promptForAgent(term, agents) {
|
|
|
15606
15944
|
};
|
|
15607
15945
|
render();
|
|
15608
15946
|
term.hideCursor();
|
|
15609
|
-
return await new Promise((
|
|
15947
|
+
return await new Promise((resolve9) => {
|
|
15610
15948
|
let resolved = false;
|
|
15611
15949
|
const cleanup = () => {
|
|
15612
15950
|
if (resolved) {
|
|
@@ -15621,7 +15959,7 @@ async function promptForAgent(term, agents) {
|
|
|
15621
15959
|
};
|
|
15622
15960
|
const finish = (value) => {
|
|
15623
15961
|
cleanup();
|
|
15624
|
-
|
|
15962
|
+
resolve9(value);
|
|
15625
15963
|
};
|
|
15626
15964
|
const onResize = () => {
|
|
15627
15965
|
if (resolved) {
|
|
@@ -15717,7 +16055,7 @@ var init_agent_prompt = __esm({
|
|
|
15717
16055
|
|
|
15718
16056
|
// src/tui/clipboard.ts
|
|
15719
16057
|
import { spawn as nodeSpawn } from "child_process";
|
|
15720
|
-
import
|
|
16058
|
+
import fs25 from "fs/promises";
|
|
15721
16059
|
import os6 from "os";
|
|
15722
16060
|
import path19 from "path";
|
|
15723
16061
|
async function readClipboard(envIn = {}) {
|
|
@@ -15758,7 +16096,7 @@ async function readMacOS(env) {
|
|
|
15758
16096
|
return img;
|
|
15759
16097
|
}
|
|
15760
16098
|
} catch {
|
|
15761
|
-
await
|
|
16099
|
+
await fs25.unlink(tmpPath).catch(() => void 0);
|
|
15762
16100
|
}
|
|
15763
16101
|
try {
|
|
15764
16102
|
const buf = await runCapture(env.spawn, "pbpaste", []);
|
|
@@ -15873,9 +16211,9 @@ async function which(env, cmd) {
|
|
|
15873
16211
|
}
|
|
15874
16212
|
async function readFileAsAttachment(p, unlinkAfter) {
|
|
15875
16213
|
try {
|
|
15876
|
-
const buf = await
|
|
16214
|
+
const buf = await fs25.readFile(p);
|
|
15877
16215
|
if (unlinkAfter) {
|
|
15878
|
-
await
|
|
16216
|
+
await fs25.unlink(p).catch(() => void 0);
|
|
15879
16217
|
}
|
|
15880
16218
|
if (buf.length === 0) {
|
|
15881
16219
|
return { ok: false, reason: "no image on clipboard" };
|
|
@@ -15901,14 +16239,14 @@ async function readFileAsAttachment(p, unlinkAfter) {
|
|
|
15901
16239
|
}
|
|
15902
16240
|
}
|
|
15903
16241
|
function run2(spawn7, cmd, args) {
|
|
15904
|
-
return new Promise((
|
|
16242
|
+
return new Promise((resolve9, reject) => {
|
|
15905
16243
|
const proc = spawn7(cmd, args);
|
|
15906
16244
|
proc.stdout?.on("data", () => void 0);
|
|
15907
16245
|
proc.stderr?.on("data", () => void 0);
|
|
15908
16246
|
proc.on("error", reject);
|
|
15909
16247
|
proc.on("close", (code) => {
|
|
15910
16248
|
if (code === 0) {
|
|
15911
|
-
|
|
16249
|
+
resolve9();
|
|
15912
16250
|
} else {
|
|
15913
16251
|
reject(new Error(`${cmd} exited ${code}`));
|
|
15914
16252
|
}
|
|
@@ -15916,7 +16254,7 @@ function run2(spawn7, cmd, args) {
|
|
|
15916
16254
|
});
|
|
15917
16255
|
}
|
|
15918
16256
|
function runCapture(spawn7, cmd, args) {
|
|
15919
|
-
return new Promise((
|
|
16257
|
+
return new Promise((resolve9, reject) => {
|
|
15920
16258
|
const proc = spawn7(cmd, args);
|
|
15921
16259
|
const chunks = [];
|
|
15922
16260
|
let stdoutEnded = proc.stdout === null;
|
|
@@ -15928,7 +16266,7 @@ function runCapture(spawn7, cmd, args) {
|
|
|
15928
16266
|
}
|
|
15929
16267
|
settled = true;
|
|
15930
16268
|
if (closedCode === 0) {
|
|
15931
|
-
|
|
16269
|
+
resolve9(Buffer.concat(chunks));
|
|
15932
16270
|
} else {
|
|
15933
16271
|
reject(new Error(`${cmd} exited ${closedCode}`));
|
|
15934
16272
|
}
|
|
@@ -15974,6 +16312,109 @@ var init_clipboard = __esm({
|
|
|
15974
16312
|
}
|
|
15975
16313
|
});
|
|
15976
16314
|
|
|
16315
|
+
// src/tui/file-completion.ts
|
|
16316
|
+
import * as fs26 from "fs";
|
|
16317
|
+
import * as os7 from "os";
|
|
16318
|
+
import * as path20 from "path";
|
|
16319
|
+
function extractPathToken(line, col) {
|
|
16320
|
+
let start = col;
|
|
16321
|
+
while (start > 0) {
|
|
16322
|
+
const ch = line[start - 1] ?? "";
|
|
16323
|
+
if (/\s/.test(ch)) {
|
|
16324
|
+
if (ch === " " && line[start - 2] === "\\") {
|
|
16325
|
+
start -= 2;
|
|
16326
|
+
continue;
|
|
16327
|
+
}
|
|
16328
|
+
break;
|
|
16329
|
+
}
|
|
16330
|
+
start -= 1;
|
|
16331
|
+
}
|
|
16332
|
+
if (start === col) {
|
|
16333
|
+
return null;
|
|
16334
|
+
}
|
|
16335
|
+
return { token: line.slice(start, col), start };
|
|
16336
|
+
}
|
|
16337
|
+
function looksLikePath(token) {
|
|
16338
|
+
if (token.length === 0) {
|
|
16339
|
+
return false;
|
|
16340
|
+
}
|
|
16341
|
+
if (token.includes("/")) {
|
|
16342
|
+
return true;
|
|
16343
|
+
}
|
|
16344
|
+
return token === "~" || token === "." || token === "..";
|
|
16345
|
+
}
|
|
16346
|
+
function unescapeToken(token) {
|
|
16347
|
+
return token.replace(/\\ /g, " ");
|
|
16348
|
+
}
|
|
16349
|
+
function escapeToken(token) {
|
|
16350
|
+
return token.replace(/ /g, "\\ ");
|
|
16351
|
+
}
|
|
16352
|
+
function splitToken(token) {
|
|
16353
|
+
const slash = token.lastIndexOf("/");
|
|
16354
|
+
if (slash === -1) {
|
|
16355
|
+
return { dirPrefix: "", base: token };
|
|
16356
|
+
}
|
|
16357
|
+
return { dirPrefix: token.slice(0, slash + 1), base: token.slice(slash + 1) };
|
|
16358
|
+
}
|
|
16359
|
+
function resolveDir(dirPrefix, cwd) {
|
|
16360
|
+
let p = dirPrefix.length === 0 ? "." : dirPrefix;
|
|
16361
|
+
if (p === "~" || p.startsWith("~/")) {
|
|
16362
|
+
p = os7.homedir() + p.slice(1);
|
|
16363
|
+
}
|
|
16364
|
+
return path20.isAbsolute(p) ? p : path20.resolve(cwd, p);
|
|
16365
|
+
}
|
|
16366
|
+
function readDir(dir) {
|
|
16367
|
+
let dirents;
|
|
16368
|
+
try {
|
|
16369
|
+
dirents = fs26.readdirSync(dir, { withFileTypes: true });
|
|
16370
|
+
} catch {
|
|
16371
|
+
return null;
|
|
16372
|
+
}
|
|
16373
|
+
return dirents.map((d) => ({
|
|
16374
|
+
name: d.name,
|
|
16375
|
+
isDir: d.isDirectory()
|
|
16376
|
+
}));
|
|
16377
|
+
}
|
|
16378
|
+
function completePathToken(token, cwd, listDir = readDir) {
|
|
16379
|
+
if (!looksLikePath(token)) {
|
|
16380
|
+
return null;
|
|
16381
|
+
}
|
|
16382
|
+
const raw = unescapeToken(token);
|
|
16383
|
+
const { dirPrefix, base } = splitToken(raw);
|
|
16384
|
+
const dir = resolveDir(dirPrefix, cwd);
|
|
16385
|
+
const entries = listDir(dir);
|
|
16386
|
+
if (entries === null) {
|
|
16387
|
+
return null;
|
|
16388
|
+
}
|
|
16389
|
+
const showHidden = base.startsWith(".");
|
|
16390
|
+
const matched = entries.filter(
|
|
16391
|
+
(e) => e.name.startsWith(base) && (showHidden || !e.name.startsWith("."))
|
|
16392
|
+
);
|
|
16393
|
+
if (matched.length === 0) {
|
|
16394
|
+
return null;
|
|
16395
|
+
}
|
|
16396
|
+
const candidates = matched.map((e) => e.isDir ? e.name + "/" : e.name);
|
|
16397
|
+
if (matched.length === 1) {
|
|
16398
|
+
const only = matched[0];
|
|
16399
|
+
const completedBase = only.isDir ? only.name + "/" : only.name;
|
|
16400
|
+
return {
|
|
16401
|
+
replacement: escapeToken(dirPrefix + completedBase),
|
|
16402
|
+
candidates
|
|
16403
|
+
};
|
|
16404
|
+
}
|
|
16405
|
+
const common = longestCommonPrefix(matched.map((e) => e.name));
|
|
16406
|
+
return {
|
|
16407
|
+
replacement: escapeToken(dirPrefix + common),
|
|
16408
|
+
candidates
|
|
16409
|
+
};
|
|
16410
|
+
}
|
|
16411
|
+
var init_file_completion = __esm({
|
|
16412
|
+
"src/tui/file-completion.ts"() {
|
|
16413
|
+
"use strict";
|
|
16414
|
+
init_completion();
|
|
16415
|
+
}
|
|
16416
|
+
});
|
|
16417
|
+
|
|
15977
16418
|
// src/tui/reconnect-state.ts
|
|
15978
16419
|
function parseReattachResponse(result) {
|
|
15979
16420
|
const out = {};
|
|
@@ -16024,8 +16465,8 @@ var init_reconnect_state = __esm({
|
|
|
16024
16465
|
import { appendFileSync, statSync as statSync2, renameSync as renameSync2 } from "fs";
|
|
16025
16466
|
import { nanoid as nanoid3 } from "nanoid";
|
|
16026
16467
|
import termkit from "terminal-kit";
|
|
16027
|
-
import
|
|
16028
|
-
import
|
|
16468
|
+
import fs27 from "fs/promises";
|
|
16469
|
+
import path21 from "path";
|
|
16029
16470
|
function parseResponseConfigOptions(raw) {
|
|
16030
16471
|
if (!Array.isArray(raw)) {
|
|
16031
16472
|
return void 0;
|
|
@@ -16441,10 +16882,10 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16441
16882
|
if (pendingPermission.toolCallId && toolCallId && pendingPermission.toolCallId !== toolCallId) {
|
|
16442
16883
|
return;
|
|
16443
16884
|
}
|
|
16444
|
-
const
|
|
16885
|
+
const resolve9 = pendingPermission.resolve;
|
|
16445
16886
|
pendingPermission = null;
|
|
16446
16887
|
screen.setPermissionPrompt(null);
|
|
16447
|
-
|
|
16888
|
+
resolve9(result ?? { outcome: { outcome: "cancelled" } });
|
|
16448
16889
|
};
|
|
16449
16890
|
const maybeDismissPermissionByToolUpdate = (update) => {
|
|
16450
16891
|
if (!pendingPermission?.toolCallId) {
|
|
@@ -16478,14 +16919,14 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16478
16919
|
if (!pendingPermission) {
|
|
16479
16920
|
return;
|
|
16480
16921
|
}
|
|
16481
|
-
const { options, resolve:
|
|
16922
|
+
const { options, resolve: resolve9 } = pendingPermission;
|
|
16482
16923
|
pendingPermission = null;
|
|
16483
16924
|
screen.setPermissionPrompt(null);
|
|
16484
16925
|
if (optionId === null) {
|
|
16485
|
-
|
|
16926
|
+
resolve9({ outcome: { outcome: "cancelled" } });
|
|
16486
16927
|
return;
|
|
16487
16928
|
}
|
|
16488
|
-
|
|
16929
|
+
resolve9({ outcome: { outcome: "selected", optionId } });
|
|
16489
16930
|
void options;
|
|
16490
16931
|
};
|
|
16491
16932
|
conn.onRequest("session/request_permission", async (params) => {
|
|
@@ -16518,13 +16959,13 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16518
16959
|
]);
|
|
16519
16960
|
return { outcome: { outcome: "cancelled" } };
|
|
16520
16961
|
}
|
|
16521
|
-
return new Promise((
|
|
16962
|
+
return new Promise((resolve9) => {
|
|
16522
16963
|
pendingPermission = {
|
|
16523
16964
|
title,
|
|
16524
16965
|
detail,
|
|
16525
16966
|
options,
|
|
16526
16967
|
selectedIndex: 0,
|
|
16527
|
-
resolve:
|
|
16968
|
+
resolve: resolve9,
|
|
16528
16969
|
toolCallId
|
|
16529
16970
|
};
|
|
16530
16971
|
refreshPermissionPrompt();
|
|
@@ -16617,6 +17058,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16617
17058
|
if (opts.readonly === true) {
|
|
16618
17059
|
attachHydraMeta.readonly = true;
|
|
16619
17060
|
}
|
|
17061
|
+
if (config.tui.toolContent === "references") {
|
|
17062
|
+
attachHydraMeta.toolContent = "references";
|
|
17063
|
+
}
|
|
16620
17064
|
if (opts.drip === true) {
|
|
16621
17065
|
attachHydraMeta.replayMode = "drip";
|
|
16622
17066
|
if (opts.dripSpeed !== void 0) {
|
|
@@ -16705,6 +17149,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16705
17149
|
}
|
|
16706
17150
|
let turnInFlight = null;
|
|
16707
17151
|
let pendingPrefill = null;
|
|
17152
|
+
setAmbiguousWide(config.tui.ambiguousWidth === "wide");
|
|
17153
|
+
setDiffContextLines(config.tui.diffContextLines);
|
|
16708
17154
|
const screen = new Screen({
|
|
16709
17155
|
term,
|
|
16710
17156
|
dispatcher,
|
|
@@ -16719,6 +17165,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16719
17165
|
onBlockClick: (key) => {
|
|
16720
17166
|
handleBlockClick(key);
|
|
16721
17167
|
},
|
|
17168
|
+
// Lazy-load deferred (references-mode) diff bodies only when the block
|
|
17169
|
+
// scrolls into view.
|
|
17170
|
+
onBlockVisible: (key) => {
|
|
17171
|
+
handleBlockVisible(key);
|
|
17172
|
+
},
|
|
16722
17173
|
onKey: (events) => {
|
|
16723
17174
|
for (const ev of events) {
|
|
16724
17175
|
if (pendingPermission && tryHandlePermissionKey(ev)) {
|
|
@@ -16798,26 +17249,58 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16798
17249
|
}
|
|
16799
17250
|
return matches;
|
|
16800
17251
|
};
|
|
17252
|
+
let fileCompletions = [];
|
|
16801
17253
|
const refreshCompletions = () => {
|
|
16802
|
-
|
|
17254
|
+
const slash = currentCompletions();
|
|
17255
|
+
if (slash.length > 0) {
|
|
17256
|
+
fileCompletions = [];
|
|
17257
|
+
screen.setCompletions(slash);
|
|
17258
|
+
return;
|
|
17259
|
+
}
|
|
17260
|
+
screen.setCompletions(fileCompletions);
|
|
16803
17261
|
};
|
|
16804
17262
|
const tryHandleCompletionKey = (ev) => {
|
|
16805
17263
|
if (ev.type !== "key" || ev.name !== "tab") {
|
|
17264
|
+
if (fileCompletions.length > 0) {
|
|
17265
|
+
fileCompletions = [];
|
|
17266
|
+
}
|
|
16806
17267
|
return false;
|
|
16807
17268
|
}
|
|
16808
17269
|
const matches = currentCompletions();
|
|
16809
|
-
if (matches.length
|
|
17270
|
+
if (matches.length > 0) {
|
|
17271
|
+
fileCompletions = [];
|
|
17272
|
+
const firstLine3 = dispatcher.state().buffer[0] ?? "";
|
|
17273
|
+
const next = computeTabCompletion({
|
|
17274
|
+
matches: matches.map((m) => m.name),
|
|
17275
|
+
firstLine: firstLine3
|
|
17276
|
+
});
|
|
17277
|
+
if (next === null) {
|
|
17278
|
+
return true;
|
|
17279
|
+
}
|
|
17280
|
+
dispatcher.replaceFirstLine(next);
|
|
17281
|
+
return true;
|
|
17282
|
+
}
|
|
17283
|
+
return tryHandleFileCompletion();
|
|
17284
|
+
};
|
|
17285
|
+
const tryHandleFileCompletion = () => {
|
|
17286
|
+
const st = dispatcher.state();
|
|
17287
|
+
const line = st.buffer[st.row] ?? "";
|
|
17288
|
+
const tok = extractPathToken(line, st.col);
|
|
17289
|
+
if (tok === null) {
|
|
16810
17290
|
return false;
|
|
16811
17291
|
}
|
|
16812
|
-
const
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
firstLine: firstLine3
|
|
16816
|
-
});
|
|
16817
|
-
if (next === null) {
|
|
16818
|
-
return true;
|
|
17292
|
+
const result = completePathToken(tok.token, resolvedCwd);
|
|
17293
|
+
if (result === null) {
|
|
17294
|
+
return false;
|
|
16819
17295
|
}
|
|
16820
|
-
|
|
17296
|
+
if (result.replacement !== tok.token) {
|
|
17297
|
+
dispatcher.replaceRangeOnCurrentLine(
|
|
17298
|
+
tok.start,
|
|
17299
|
+
st.col,
|
|
17300
|
+
result.replacement
|
|
17301
|
+
);
|
|
17302
|
+
}
|
|
17303
|
+
fileCompletions = result.candidates.length > 1 ? result.candidates.map((name) => ({ name })) : [];
|
|
16821
17304
|
return true;
|
|
16822
17305
|
};
|
|
16823
17306
|
const tryHandleScrollbackSearchKey = (ev) => {
|
|
@@ -16937,8 +17420,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16937
17420
|
}
|
|
16938
17421
|
});
|
|
16939
17422
|
let finishSession = null;
|
|
16940
|
-
const sessionDone = new Promise((
|
|
16941
|
-
finishSession =
|
|
17423
|
+
const sessionDone = new Promise((resolve9) => {
|
|
17424
|
+
finishSession = resolve9;
|
|
16942
17425
|
});
|
|
16943
17426
|
const cancelRemoteTurn = () => {
|
|
16944
17427
|
conn.notify("session/cancel", { sessionId: resolvedSessionId }).catch(() => void 0);
|
|
@@ -17557,11 +18040,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
17557
18040
|
}
|
|
17558
18041
|
const mimeType = mimeFromExtension(token);
|
|
17559
18042
|
if (!mimeType) {
|
|
17560
|
-
screen.notify(`unsupported image type: ${
|
|
18043
|
+
screen.notify(`unsupported image type: ${path21.basename(token)}`);
|
|
17561
18044
|
continue;
|
|
17562
18045
|
}
|
|
17563
18046
|
try {
|
|
17564
|
-
const buf = await
|
|
18047
|
+
const buf = await fs27.readFile(token);
|
|
17565
18048
|
if (buf.length > MAX_ATTACHMENT_BYTES) {
|
|
17566
18049
|
screen.notify(
|
|
17567
18050
|
`image too large (${formatSize(buf.length)}, max ${formatSize(MAX_ATTACHMENT_BYTES)})`
|
|
@@ -17571,13 +18054,13 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
17571
18054
|
dispatcher.addAttachment({
|
|
17572
18055
|
mimeType,
|
|
17573
18056
|
data: buf.toString("base64"),
|
|
17574
|
-
name:
|
|
18057
|
+
name: path21.basename(token),
|
|
17575
18058
|
sizeBytes: buf.length
|
|
17576
18059
|
});
|
|
17577
18060
|
added++;
|
|
17578
18061
|
} catch (err) {
|
|
17579
18062
|
screen.notify(
|
|
17580
|
-
`cannot read ${
|
|
18063
|
+
`cannot read ${path21.basename(token)}: ${err.message}`
|
|
17581
18064
|
);
|
|
17582
18065
|
}
|
|
17583
18066
|
}
|
|
@@ -18132,7 +18615,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18132
18615
|
if (text.length === 0)
|
|
18133
18616
|
return;
|
|
18134
18617
|
if (thoughtKey === null) {
|
|
18135
|
-
screen.ensureSeparator();
|
|
18618
|
+
screen.ensureSeparator("thought");
|
|
18136
18619
|
thoughtKey = `thought:${thoughtSeq}`;
|
|
18137
18620
|
thoughtSeq += 1;
|
|
18138
18621
|
thoughtBuffer = "";
|
|
@@ -18250,7 +18733,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18250
18733
|
toolsBlockStopReason = null;
|
|
18251
18734
|
renderToolsBlock();
|
|
18252
18735
|
};
|
|
18253
|
-
const recordToolCall = (id, title, status, errorText, editDiff) => {
|
|
18736
|
+
const recordToolCall = (id, title, status, errorText, editDiff, detail) => {
|
|
18254
18737
|
const wasNew = !toolStates.has(id);
|
|
18255
18738
|
const existing = toolStates.get(id);
|
|
18256
18739
|
const state = existing ?? {
|
|
@@ -18262,6 +18745,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18262
18745
|
if (existing && title !== void 0) {
|
|
18263
18746
|
state.latestTitle = title;
|
|
18264
18747
|
}
|
|
18748
|
+
if (detail !== void 0 && state.detail === void 0) {
|
|
18749
|
+
state.detail = detail;
|
|
18750
|
+
}
|
|
18265
18751
|
if (existing && status !== void 0) {
|
|
18266
18752
|
state.status = status;
|
|
18267
18753
|
}
|
|
@@ -18289,41 +18775,100 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18289
18775
|
toolCallOrder.push(id);
|
|
18290
18776
|
}
|
|
18291
18777
|
};
|
|
18292
|
-
const
|
|
18293
|
-
|
|
18294
|
-
|
|
18295
|
-
|
|
18296
|
-
|
|
18297
|
-
|
|
18778
|
+
const fetchToolContent = async (hash) => {
|
|
18779
|
+
try {
|
|
18780
|
+
const res = await conn.request("hydra-acp/session/tool_content", {
|
|
18781
|
+
sessionId: resolvedSessionId,
|
|
18782
|
+
hash
|
|
18783
|
+
});
|
|
18784
|
+
return typeof res?.content === "string" ? res.content : null;
|
|
18785
|
+
} catch {
|
|
18786
|
+
return null;
|
|
18787
|
+
}
|
|
18788
|
+
};
|
|
18789
|
+
const fetchingDiffs = /* @__PURE__ */ new Set();
|
|
18790
|
+
const resolveDeferredDiff = (toolCallId, diff) => {
|
|
18791
|
+
if (fetchingDiffs.has(toolCallId)) {
|
|
18792
|
+
return;
|
|
18793
|
+
}
|
|
18794
|
+
if (diff.oldRef === void 0 && diff.newRef === void 0) {
|
|
18795
|
+
return;
|
|
18796
|
+
}
|
|
18797
|
+
fetchingDiffs.add(toolCallId);
|
|
18798
|
+
void (async () => {
|
|
18799
|
+
const [oldText, newText] = await Promise.all([
|
|
18800
|
+
diff.oldRef ? fetchToolContent(diff.oldRef.hash) : Promise.resolve(diff.oldText),
|
|
18801
|
+
diff.newRef ? fetchToolContent(diff.newRef.hash) : Promise.resolve(diff.newText)
|
|
18802
|
+
]);
|
|
18803
|
+
const failed = diff.oldRef !== void 0 && oldText === null || diff.newRef !== void 0 && newText === null;
|
|
18804
|
+
if (failed) {
|
|
18805
|
+
fetchingDiffs.delete(toolCallId);
|
|
18806
|
+
const out = formatEditDiffBlock(diff, "diff", { deferredStatus: "error" });
|
|
18807
|
+
if (out.length > 0) {
|
|
18808
|
+
screen.upsertLines(`editdiff:${toolCallId}`, out);
|
|
18809
|
+
screen.repaintNow();
|
|
18810
|
+
}
|
|
18811
|
+
return;
|
|
18298
18812
|
}
|
|
18299
|
-
const
|
|
18300
|
-
|
|
18301
|
-
|
|
18813
|
+
const resolved = {
|
|
18814
|
+
...diff.path !== void 0 ? { path: diff.path } : {},
|
|
18815
|
+
oldText: oldText ?? "",
|
|
18816
|
+
newText: newText ?? ""
|
|
18817
|
+
};
|
|
18818
|
+
renderedEditDiffs.set(toolCallId, resolved);
|
|
18819
|
+
const st = toolStates.get(toolCallId);
|
|
18820
|
+
if (st?.editDiff) {
|
|
18821
|
+
st.editDiff = resolved;
|
|
18302
18822
|
}
|
|
18823
|
+
fetchingDiffs.delete(toolCallId);
|
|
18303
18824
|
const override = editDiffOverrides.get(toolCallId);
|
|
18304
|
-
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
);
|
|
18309
|
-
return out2.length > 0 ? out2 : null;
|
|
18825
|
+
const mode = override === void 0 ? viewPrefs.showFileUpdates : override ? "diff" : "edit";
|
|
18826
|
+
if (mode !== "none") {
|
|
18827
|
+
renderEditDiffBlock(toolCallId, resolved, mode === "diff" ? "diff" : "edit");
|
|
18828
|
+
screen.repaintNow();
|
|
18310
18829
|
}
|
|
18311
|
-
if (mode === "diff") {
|
|
18312
|
-
const out2 = formatEditDiffBlock(state.editDiff, "diff");
|
|
18313
|
-
return out2.length > 0 ? out2 : null;
|
|
18314
|
-
}
|
|
18315
|
-
const out = formatEditDiffBlock(state.editDiff, "edit");
|
|
18316
|
-
return out.length > 0 ? out : null;
|
|
18317
18830
|
})();
|
|
18318
|
-
|
|
18831
|
+
};
|
|
18832
|
+
const renderEditDiffBlock = (toolCallId, diff, mode) => {
|
|
18833
|
+
const key = `editdiff:${toolCallId}`;
|
|
18834
|
+
const out = formatEditDiffBlock(diff, mode);
|
|
18835
|
+
if (out.length === 0) {
|
|
18319
18836
|
screen.removeKey(key);
|
|
18320
18837
|
return;
|
|
18321
18838
|
}
|
|
18322
|
-
|
|
18323
|
-
if (diff) {
|
|
18324
|
-
|
|
18839
|
+
screen.upsertLines(key, out);
|
|
18840
|
+
if (mode === "diff" && (diff.oldRef !== void 0 || diff.newRef !== void 0)) {
|
|
18841
|
+
screen.notifyWhenVisible(key);
|
|
18325
18842
|
}
|
|
18326
|
-
|
|
18843
|
+
};
|
|
18844
|
+
const handleBlockVisible = (key) => {
|
|
18845
|
+
if (!key.startsWith("editdiff:")) {
|
|
18846
|
+
return;
|
|
18847
|
+
}
|
|
18848
|
+
const id = key.slice("editdiff:".length);
|
|
18849
|
+
const diff = renderedEditDiffs.get(id);
|
|
18850
|
+
if (diff && (diff.oldRef !== void 0 || diff.newRef !== void 0)) {
|
|
18851
|
+
resolveDeferredDiff(id, diff);
|
|
18852
|
+
}
|
|
18853
|
+
};
|
|
18854
|
+
const maybeRenderEditDiff = (toolCallId) => {
|
|
18855
|
+
const key = `editdiff:${toolCallId}`;
|
|
18856
|
+
const globalMode = viewPrefs.showFileUpdates;
|
|
18857
|
+
const state = toolStates.get(toolCallId);
|
|
18858
|
+
let mode;
|
|
18859
|
+
if (globalMode === "none" || !state?.editDiff || state.status !== "completed") {
|
|
18860
|
+
mode = null;
|
|
18861
|
+
} else {
|
|
18862
|
+
const override = editDiffOverrides.get(toolCallId);
|
|
18863
|
+
mode = override !== void 0 ? override ? "diff" : "edit" : globalMode === "diff" ? "diff" : "edit";
|
|
18864
|
+
}
|
|
18865
|
+
if (mode === null) {
|
|
18866
|
+
screen.removeKey(key);
|
|
18867
|
+
return;
|
|
18868
|
+
}
|
|
18869
|
+
const diff = state.editDiff;
|
|
18870
|
+
renderedEditDiffs.set(toolCallId, diff);
|
|
18871
|
+
renderEditDiffBlock(toolCallId, diff, mode);
|
|
18327
18872
|
};
|
|
18328
18873
|
const reRenderAllEditDiffs = () => {
|
|
18329
18874
|
const globalMode = viewPrefs.showFileUpdates;
|
|
@@ -18335,12 +18880,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18335
18880
|
screen.removeKey(key);
|
|
18336
18881
|
continue;
|
|
18337
18882
|
}
|
|
18338
|
-
|
|
18339
|
-
if (out.length === 0) {
|
|
18340
|
-
screen.removeKey(key);
|
|
18341
|
-
} else {
|
|
18342
|
-
screen.upsertLines(key, out);
|
|
18343
|
-
}
|
|
18883
|
+
renderEditDiffBlock(toolCallId, diff, mode === "diff" ? "diff" : "edit");
|
|
18344
18884
|
}
|
|
18345
18885
|
};
|
|
18346
18886
|
const handleBlockClick = (key) => {
|
|
@@ -18353,12 +18893,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18353
18893
|
const current = editDiffOverrides.get(id) ?? viewPrefs.showFileUpdates === "diff";
|
|
18354
18894
|
editDiffOverrides.set(id, !current);
|
|
18355
18895
|
const next = !current ? "diff" : "edit";
|
|
18356
|
-
|
|
18357
|
-
if (out.length === 0) {
|
|
18358
|
-
screen.removeKey(key);
|
|
18359
|
-
} else {
|
|
18360
|
-
screen.upsertLines(key, out);
|
|
18361
|
-
}
|
|
18896
|
+
renderEditDiffBlock(id, diff, next);
|
|
18362
18897
|
screen.repaintNow();
|
|
18363
18898
|
return;
|
|
18364
18899
|
}
|
|
@@ -18512,7 +19047,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18512
19047
|
event.title,
|
|
18513
19048
|
event.status,
|
|
18514
19049
|
void 0,
|
|
18515
|
-
event.editDiff
|
|
19050
|
+
event.editDiff,
|
|
19051
|
+
event.detail
|
|
18516
19052
|
);
|
|
18517
19053
|
renderToolsBlock();
|
|
18518
19054
|
maybeRenderEditDiff(event.toolCallId);
|
|
@@ -18540,7 +19076,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18540
19076
|
event.title,
|
|
18541
19077
|
event.status,
|
|
18542
19078
|
event.errorText,
|
|
18543
|
-
event.editDiff
|
|
19079
|
+
event.editDiff,
|
|
19080
|
+
event.detail
|
|
18544
19081
|
);
|
|
18545
19082
|
if (event.upstreamInterrupted) {
|
|
18546
19083
|
upstreamInterruptedSeen = true;
|
|
@@ -18669,10 +19206,10 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18669
19206
|
}
|
|
18670
19207
|
const resetInFlightUiState = () => {
|
|
18671
19208
|
if (pendingPermission) {
|
|
18672
|
-
const
|
|
19209
|
+
const resolve9 = pendingPermission.resolve;
|
|
18673
19210
|
pendingPermission = null;
|
|
18674
19211
|
screen.setPermissionPrompt(null);
|
|
18675
|
-
|
|
19212
|
+
resolve9({ outcome: { outcome: "cancelled" } });
|
|
18676
19213
|
}
|
|
18677
19214
|
closeAgentText();
|
|
18678
19215
|
closeThought();
|
|
@@ -18724,17 +19261,20 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
18724
19261
|
historyPolicy: useAfterMessage ? "after_message" : "none",
|
|
18725
19262
|
...useAfterMessage ? { afterMessageId: lastSeenMessageId } : {},
|
|
18726
19263
|
clientInfo: { name: "hydra-acp-tui", version: HYDRA_VERSION },
|
|
18727
|
-
...
|
|
18728
|
-
|
|
18729
|
-
|
|
18730
|
-
|
|
18731
|
-
|
|
18732
|
-
|
|
18733
|
-
|
|
18734
|
-
|
|
18735
|
-
}
|
|
19264
|
+
...(() => {
|
|
19265
|
+
const meta = {};
|
|
19266
|
+
if (upstreamSessionId !== void 0) {
|
|
19267
|
+
meta.resume = {
|
|
19268
|
+
upstreamSessionId,
|
|
19269
|
+
agentId: resolvedAgentId,
|
|
19270
|
+
cwd: resolvedCwd
|
|
19271
|
+
};
|
|
18736
19272
|
}
|
|
18737
|
-
|
|
19273
|
+
if (config.tui.toolContent === "references") {
|
|
19274
|
+
meta.toolContent = "references";
|
|
19275
|
+
}
|
|
19276
|
+
return Object.keys(meta).length > 0 ? { _meta: { [HYDRA_META_KEY]: meta } } : {};
|
|
19277
|
+
})()
|
|
18738
19278
|
}
|
|
18739
19279
|
};
|
|
18740
19280
|
reconnectReplayBuffer = [];
|
|
@@ -19232,6 +19772,7 @@ var init_app = __esm({
|
|
|
19232
19772
|
init_attachments();
|
|
19233
19773
|
init_clipboard();
|
|
19234
19774
|
init_completion();
|
|
19775
|
+
init_file_completion();
|
|
19235
19776
|
init_reconnect_state();
|
|
19236
19777
|
init_render_update();
|
|
19237
19778
|
init_format();
|
|
@@ -19239,7 +19780,7 @@ var init_app = __esm({
|
|
|
19239
19780
|
HELP_ENTRIES_TAIL = [
|
|
19240
19781
|
["Alt+Enter", "newline in prompt"],
|
|
19241
19782
|
["Shift+Tab", "cycle agent modes (plan / accept-edits / etc.)"],
|
|
19242
|
-
["Tab", "indent \xB7 slash-command completion"],
|
|
19783
|
+
["Tab", "indent \xB7 slash-command / file-path completion"],
|
|
19243
19784
|
null,
|
|
19244
19785
|
["\u2191 / \u2193", "prompt history \xB7 queue navigation"],
|
|
19245
19786
|
["\u2190/\u2192 Home/End", "cursor movement"],
|
|
@@ -19284,7 +19825,7 @@ var init_tui = __esm({
|
|
|
19284
19825
|
// src/cli.ts
|
|
19285
19826
|
import { readFileSync as readFileSync2 } from "fs";
|
|
19286
19827
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
19287
|
-
import { dirname as dirname8, resolve as
|
|
19828
|
+
import { dirname as dirname8, resolve as resolve8 } from "path";
|
|
19288
19829
|
|
|
19289
19830
|
// src/cli/parse-args.ts
|
|
19290
19831
|
var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -19481,7 +20022,7 @@ import { setTimeout as sleep2 } from "timers/promises";
|
|
|
19481
20022
|
import chalk from "chalk";
|
|
19482
20023
|
|
|
19483
20024
|
// src/daemon/server.ts
|
|
19484
|
-
import * as
|
|
20025
|
+
import * as fs19 from "fs";
|
|
19485
20026
|
import * as fsp7 from "fs/promises";
|
|
19486
20027
|
import Fastify from "fastify";
|
|
19487
20028
|
import websocketPlugin from "@fastify/websocket";
|
|
@@ -19500,16 +20041,117 @@ init_config();
|
|
|
19500
20041
|
import pino from "pino";
|
|
19501
20042
|
import createPinoRoll from "pino-roll";
|
|
19502
20043
|
|
|
20044
|
+
// src/core/tool-store.ts
|
|
20045
|
+
init_paths();
|
|
20046
|
+
import * as fs5 from "fs/promises";
|
|
20047
|
+
import { createHash } from "crypto";
|
|
20048
|
+
import { gzip as gzipCb, gunzip as gunzipCb } from "zlib";
|
|
20049
|
+
import { promisify } from "util";
|
|
20050
|
+
var gzip = promisify(gzipCb);
|
|
20051
|
+
var gunzip = promisify(gunzipCb);
|
|
20052
|
+
var SESSION_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
20053
|
+
var HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
20054
|
+
var compressBlobs = true;
|
|
20055
|
+
function setToolBlobCompression(enabled) {
|
|
20056
|
+
compressBlobs = enabled;
|
|
20057
|
+
}
|
|
20058
|
+
function safe(sessionId, hash) {
|
|
20059
|
+
if (!SESSION_ID_PATTERN.test(sessionId)) {
|
|
20060
|
+
return false;
|
|
20061
|
+
}
|
|
20062
|
+
return hash === void 0 || HASH_PATTERN.test(hash);
|
|
20063
|
+
}
|
|
20064
|
+
function gzPath(sessionId, hash) {
|
|
20065
|
+
return `${paths.toolBlobFile(sessionId, hash)}.gz`;
|
|
20066
|
+
}
|
|
20067
|
+
async function putToolBlob(sessionId, text) {
|
|
20068
|
+
if (!safe(sessionId)) {
|
|
20069
|
+
return null;
|
|
20070
|
+
}
|
|
20071
|
+
const hash = createHash("sha256").update(text, "utf8").digest("hex");
|
|
20072
|
+
const gzFile = gzPath(sessionId, hash);
|
|
20073
|
+
const plainFile = paths.toolBlobFile(sessionId, hash);
|
|
20074
|
+
for (const existing of [gzFile, plainFile]) {
|
|
20075
|
+
try {
|
|
20076
|
+
await fs5.access(existing);
|
|
20077
|
+
return hash;
|
|
20078
|
+
} catch {
|
|
20079
|
+
}
|
|
20080
|
+
}
|
|
20081
|
+
await fs5.mkdir(paths.toolsDir(sessionId), { recursive: true });
|
|
20082
|
+
const file = compressBlobs ? gzFile : plainFile;
|
|
20083
|
+
const data = compressBlobs ? await gzip(Buffer.from(text, "utf8")) : Buffer.from(text, "utf8");
|
|
20084
|
+
await fs5.writeFile(file, data, { mode: 384, flag: "wx" }).catch(async (err) => {
|
|
20085
|
+
if (err.code !== "EEXIST") {
|
|
20086
|
+
throw err;
|
|
20087
|
+
}
|
|
20088
|
+
});
|
|
20089
|
+
return hash;
|
|
20090
|
+
}
|
|
20091
|
+
async function getToolBlob(sessionId, hash) {
|
|
20092
|
+
if (!safe(sessionId, hash)) {
|
|
20093
|
+
return null;
|
|
20094
|
+
}
|
|
20095
|
+
try {
|
|
20096
|
+
const buf = await fs5.readFile(gzPath(sessionId, hash));
|
|
20097
|
+
return (await gunzip(buf)).toString("utf8");
|
|
20098
|
+
} catch {
|
|
20099
|
+
}
|
|
20100
|
+
try {
|
|
20101
|
+
return await fs5.readFile(paths.toolBlobFile(sessionId, hash), "utf8");
|
|
20102
|
+
} catch {
|
|
20103
|
+
return null;
|
|
20104
|
+
}
|
|
20105
|
+
}
|
|
20106
|
+
async function readToolBlobGz(sessionId, hash) {
|
|
20107
|
+
if (!safe(sessionId, hash)) {
|
|
20108
|
+
return null;
|
|
20109
|
+
}
|
|
20110
|
+
try {
|
|
20111
|
+
return await fs5.readFile(gzPath(sessionId, hash));
|
|
20112
|
+
} catch {
|
|
20113
|
+
}
|
|
20114
|
+
try {
|
|
20115
|
+
const plain = await fs5.readFile(paths.toolBlobFile(sessionId, hash));
|
|
20116
|
+
return await gzip(plain);
|
|
20117
|
+
} catch {
|
|
20118
|
+
return null;
|
|
20119
|
+
}
|
|
20120
|
+
}
|
|
20121
|
+
async function writeToolBlobGz(sessionId, hash, gzBytes) {
|
|
20122
|
+
if (!safe(sessionId, hash)) {
|
|
20123
|
+
return;
|
|
20124
|
+
}
|
|
20125
|
+
const file = gzPath(sessionId, hash);
|
|
20126
|
+
try {
|
|
20127
|
+
await fs5.access(file);
|
|
20128
|
+
return;
|
|
20129
|
+
} catch {
|
|
20130
|
+
}
|
|
20131
|
+
await fs5.mkdir(paths.toolsDir(sessionId), { recursive: true });
|
|
20132
|
+
await fs5.writeFile(file, gzBytes, { mode: 384, flag: "wx" }).catch((err) => {
|
|
20133
|
+
if (err.code !== "EEXIST") {
|
|
20134
|
+
throw err;
|
|
20135
|
+
}
|
|
20136
|
+
});
|
|
20137
|
+
}
|
|
20138
|
+
async function deleteToolBlobs(sessionId) {
|
|
20139
|
+
if (!SESSION_ID_PATTERN.test(sessionId)) {
|
|
20140
|
+
return;
|
|
20141
|
+
}
|
|
20142
|
+
await fs5.rm(paths.toolsDir(sessionId), { recursive: true, force: true }).catch(() => void 0);
|
|
20143
|
+
}
|
|
20144
|
+
|
|
19503
20145
|
// src/core/registry.ts
|
|
19504
20146
|
init_paths();
|
|
19505
20147
|
init_json_store();
|
|
19506
|
-
import * as
|
|
20148
|
+
import * as fs7 from "fs/promises";
|
|
19507
20149
|
import * as path5 from "path";
|
|
19508
20150
|
import { z as z2 } from "zod";
|
|
19509
20151
|
|
|
19510
20152
|
// src/core/binary-install.ts
|
|
19511
20153
|
init_paths();
|
|
19512
|
-
import * as
|
|
20154
|
+
import * as fs6 from "fs";
|
|
19513
20155
|
import * as fsp from "fs/promises";
|
|
19514
20156
|
import * as path3 from "path";
|
|
19515
20157
|
import { spawn } from "child_process";
|
|
@@ -19642,7 +20284,7 @@ async function downloadTo(args) {
|
|
|
19642
20284
|
);
|
|
19643
20285
|
}
|
|
19644
20286
|
const total = Number(response.headers.get("content-length") ?? "0");
|
|
19645
|
-
const out =
|
|
20287
|
+
const out = fs6.createWriteStream(dest);
|
|
19646
20288
|
const nodeStream = Readable.fromWeb(response.body);
|
|
19647
20289
|
safeEmit(args.onProgress, {
|
|
19648
20290
|
phase: "download_start",
|
|
@@ -19673,10 +20315,10 @@ async function downloadTo(args) {
|
|
|
19673
20315
|
logSink(formatProgress(args.agentId, received, total));
|
|
19674
20316
|
}
|
|
19675
20317
|
});
|
|
19676
|
-
await new Promise((
|
|
20318
|
+
await new Promise((resolve9, reject) => {
|
|
19677
20319
|
nodeStream.on("error", reject);
|
|
19678
20320
|
out.on("error", reject);
|
|
19679
|
-
out.on("finish", () =>
|
|
20321
|
+
out.on("finish", () => resolve9());
|
|
19680
20322
|
nodeStream.pipe(out);
|
|
19681
20323
|
});
|
|
19682
20324
|
logSink(formatProgress(
|
|
@@ -19728,14 +20370,14 @@ async function extract(archivePath, dest) {
|
|
|
19728
20370
|
throw new Error(`Unsupported archive format: ${archivePath}`);
|
|
19729
20371
|
}
|
|
19730
20372
|
function run(cmd, args) {
|
|
19731
|
-
return new Promise((
|
|
20373
|
+
return new Promise((resolve9, reject) => {
|
|
19732
20374
|
const child = spawn(cmd, args, {
|
|
19733
20375
|
stdio: ["ignore", "ignore", "inherit"]
|
|
19734
20376
|
});
|
|
19735
20377
|
child.on("error", reject);
|
|
19736
20378
|
child.on("exit", (code, signal) => {
|
|
19737
20379
|
if (code === 0) {
|
|
19738
|
-
|
|
20380
|
+
resolve9();
|
|
19739
20381
|
return;
|
|
19740
20382
|
}
|
|
19741
20383
|
reject(
|
|
@@ -19747,11 +20389,11 @@ function run(cmd, args) {
|
|
|
19747
20389
|
});
|
|
19748
20390
|
}
|
|
19749
20391
|
async function hasCommand(name) {
|
|
19750
|
-
return new Promise((
|
|
20392
|
+
return new Promise((resolve9) => {
|
|
19751
20393
|
const finder = process.platform === "win32" ? "where" : "which";
|
|
19752
20394
|
const child = spawn(finder, [name], { stdio: "ignore" });
|
|
19753
|
-
child.on("error", () =>
|
|
19754
|
-
child.on("exit", (code) =>
|
|
20395
|
+
child.on("error", () => resolve9(false));
|
|
20396
|
+
child.on("exit", (code) => resolve9(code === 0));
|
|
19755
20397
|
});
|
|
19756
20398
|
}
|
|
19757
20399
|
async function fileExists(p) {
|
|
@@ -19879,7 +20521,7 @@ function runNpmInstall(args) {
|
|
|
19879
20521
|
}
|
|
19880
20522
|
async function runNpmInstallOnce(args, attempt) {
|
|
19881
20523
|
try {
|
|
19882
|
-
await new Promise((
|
|
20524
|
+
await new Promise((resolve9, reject) => {
|
|
19883
20525
|
const registryArgs = args.registry ? ["--registry", args.registry] : [];
|
|
19884
20526
|
let child;
|
|
19885
20527
|
try {
|
|
@@ -19921,7 +20563,7 @@ async function runNpmInstallOnce(args, attempt) {
|
|
|
19921
20563
|
});
|
|
19922
20564
|
child.on("exit", (code, signal) => {
|
|
19923
20565
|
if (code === 0) {
|
|
19924
|
-
|
|
20566
|
+
resolve9();
|
|
19925
20567
|
return;
|
|
19926
20568
|
}
|
|
19927
20569
|
const reason = code !== null ? `exit code ${code}` : `signal ${signal ?? "unknown"}`;
|
|
@@ -20297,7 +20939,7 @@ async function agentInstallState(agent) {
|
|
|
20297
20939
|
}
|
|
20298
20940
|
async function fileExists3(p) {
|
|
20299
20941
|
try {
|
|
20300
|
-
await
|
|
20942
|
+
await fs7.access(p);
|
|
20301
20943
|
return true;
|
|
20302
20944
|
} catch {
|
|
20303
20945
|
return false;
|
|
@@ -20380,7 +21022,7 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
|
|
|
20380
21022
|
|
|
20381
21023
|
// src/core/agent-instance.ts
|
|
20382
21024
|
import { spawn as spawn3 } from "child_process";
|
|
20383
|
-
import * as
|
|
21025
|
+
import * as fs8 from "fs";
|
|
20384
21026
|
import * as path6 from "path";
|
|
20385
21027
|
|
|
20386
21028
|
// src/acp/framing.ts
|
|
@@ -20437,13 +21079,13 @@ function ndjsonStreamFromStdio(stdout, stdin) {
|
|
|
20437
21079
|
throw new Error("stream is closed");
|
|
20438
21080
|
}
|
|
20439
21081
|
const line = JSON.stringify(message) + "\n";
|
|
20440
|
-
await new Promise((
|
|
21082
|
+
await new Promise((resolve9, reject) => {
|
|
20441
21083
|
stdin.write(line, (err) => {
|
|
20442
21084
|
if (err) {
|
|
20443
21085
|
reject(err);
|
|
20444
21086
|
return;
|
|
20445
21087
|
}
|
|
20446
|
-
|
|
21088
|
+
resolve9();
|
|
20447
21089
|
});
|
|
20448
21090
|
});
|
|
20449
21091
|
},
|
|
@@ -20602,8 +21244,8 @@ stderr: ${tail}` : reason;
|
|
|
20602
21244
|
function openAgentLog(agentId) {
|
|
20603
21245
|
try {
|
|
20604
21246
|
const logPath = paths.agentLogFile(agentId);
|
|
20605
|
-
|
|
20606
|
-
const stream =
|
|
21247
|
+
fs8.mkdirSync(path6.dirname(logPath), { recursive: true });
|
|
21248
|
+
const stream = fs8.createWriteStream(logPath, { flags: "a" });
|
|
20607
21249
|
stream.on("error", () => void 0);
|
|
20608
21250
|
return stream;
|
|
20609
21251
|
} catch {
|
|
@@ -20612,7 +21254,7 @@ function openAgentLog(agentId) {
|
|
|
20612
21254
|
}
|
|
20613
21255
|
|
|
20614
21256
|
// src/core/session-manager.ts
|
|
20615
|
-
import * as
|
|
21257
|
+
import * as fs16 from "fs/promises";
|
|
20616
21258
|
import * as os3 from "os";
|
|
20617
21259
|
import * as path10 from "path";
|
|
20618
21260
|
import { customAlphabet as customAlphabet3 } from "nanoid";
|
|
@@ -20621,7 +21263,7 @@ init_session();
|
|
|
20621
21263
|
// src/core/session-store.ts
|
|
20622
21264
|
init_paths();
|
|
20623
21265
|
init_json_store();
|
|
20624
|
-
import * as
|
|
21266
|
+
import * as fs10 from "fs/promises";
|
|
20625
21267
|
import * as path7 from "path";
|
|
20626
21268
|
import { customAlphabet as customAlphabet2 } from "nanoid";
|
|
20627
21269
|
import { z as z5 } from "zod";
|
|
@@ -20828,9 +21470,9 @@ var SessionRecord = z5.object({
|
|
|
20828
21470
|
createdAt: z5.string(),
|
|
20829
21471
|
updatedAt: z5.string()
|
|
20830
21472
|
});
|
|
20831
|
-
var
|
|
21473
|
+
var SESSION_ID_PATTERN2 = /^[A-Za-z0-9_-]+$/;
|
|
20832
21474
|
function assertSafeId(id) {
|
|
20833
|
-
if (!
|
|
21475
|
+
if (!SESSION_ID_PATTERN2.test(id)) {
|
|
20834
21476
|
throw new Error(`unsafe session id: ${id}`);
|
|
20835
21477
|
}
|
|
20836
21478
|
}
|
|
@@ -20843,7 +21485,7 @@ var SessionStore = class {
|
|
|
20843
21485
|
});
|
|
20844
21486
|
}
|
|
20845
21487
|
async read(sessionId) {
|
|
20846
|
-
if (!
|
|
21488
|
+
if (!SESSION_ID_PATTERN2.test(sessionId)) {
|
|
20847
21489
|
return void 0;
|
|
20848
21490
|
}
|
|
20849
21491
|
const parsed = await readJsonSafe(paths.sessionFile(sessionId));
|
|
@@ -20857,11 +21499,11 @@ var SessionStore = class {
|
|
|
20857
21499
|
}
|
|
20858
21500
|
}
|
|
20859
21501
|
async delete(sessionId) {
|
|
20860
|
-
if (!
|
|
21502
|
+
if (!SESSION_ID_PATTERN2.test(sessionId)) {
|
|
20861
21503
|
return;
|
|
20862
21504
|
}
|
|
20863
21505
|
try {
|
|
20864
|
-
await
|
|
21506
|
+
await fs10.unlink(paths.sessionFile(sessionId));
|
|
20865
21507
|
} catch (err) {
|
|
20866
21508
|
const e = err;
|
|
20867
21509
|
if (e.code !== "ENOENT") {
|
|
@@ -20869,7 +21511,7 @@ var SessionStore = class {
|
|
|
20869
21511
|
}
|
|
20870
21512
|
}
|
|
20871
21513
|
try {
|
|
20872
|
-
await
|
|
21514
|
+
await fs10.rmdir(paths.sessionDir(sessionId));
|
|
20873
21515
|
} catch (err) {
|
|
20874
21516
|
const e = err;
|
|
20875
21517
|
if (e.code !== "ENOENT" && e.code !== "ENOTEMPTY") {
|
|
@@ -20899,7 +21541,7 @@ var SessionStore = class {
|
|
|
20899
21541
|
async list() {
|
|
20900
21542
|
let entries;
|
|
20901
21543
|
try {
|
|
20902
|
-
entries = await
|
|
21544
|
+
entries = await fs10.readdir(paths.sessionsDir());
|
|
20903
21545
|
} catch (err) {
|
|
20904
21546
|
const e = err;
|
|
20905
21547
|
if (e.code === "ENOENT") {
|
|
@@ -20952,7 +21594,7 @@ function recordFromMemorySession(args) {
|
|
|
20952
21594
|
// src/core/tombstone-store.ts
|
|
20953
21595
|
init_paths();
|
|
20954
21596
|
init_json_store();
|
|
20955
|
-
import * as
|
|
21597
|
+
import * as fs11 from "fs/promises";
|
|
20956
21598
|
import { z as z6 } from "zod";
|
|
20957
21599
|
var Tombstone = z6.object({
|
|
20958
21600
|
version: z6.literal(1),
|
|
@@ -20981,7 +21623,7 @@ var TombstoneStore = class {
|
|
|
20981
21623
|
}
|
|
20982
21624
|
async has(agentId, upstreamSessionId) {
|
|
20983
21625
|
try {
|
|
20984
|
-
await
|
|
21626
|
+
await fs11.access(paths.tombstoneFile(agentId, upstreamSessionId));
|
|
20985
21627
|
return true;
|
|
20986
21628
|
} catch {
|
|
20987
21629
|
return false;
|
|
@@ -21019,7 +21661,7 @@ var TombstoneStore = class {
|
|
|
21019
21661
|
}
|
|
21020
21662
|
async remove(agentId, upstreamSessionId) {
|
|
21021
21663
|
try {
|
|
21022
|
-
await
|
|
21664
|
+
await fs11.unlink(paths.tombstoneFile(agentId, upstreamSessionId));
|
|
21023
21665
|
} catch (err) {
|
|
21024
21666
|
const e = err;
|
|
21025
21667
|
if (e.code !== "ENOENT") {
|
|
@@ -21027,7 +21669,7 @@ var TombstoneStore = class {
|
|
|
21027
21669
|
}
|
|
21028
21670
|
}
|
|
21029
21671
|
try {
|
|
21030
|
-
await
|
|
21672
|
+
await fs11.rmdir(paths.tombstoneAgentDir(agentId));
|
|
21031
21673
|
} catch (err) {
|
|
21032
21674
|
const e = err;
|
|
21033
21675
|
if (e.code !== "ENOENT" && e.code !== "ENOTEMPTY") {
|
|
@@ -21041,7 +21683,7 @@ var TombstoneStore = class {
|
|
|
21041
21683
|
}
|
|
21042
21684
|
let agents;
|
|
21043
21685
|
try {
|
|
21044
|
-
agents = await
|
|
21686
|
+
agents = await fs11.readdir(paths.tombstonesDir());
|
|
21045
21687
|
} catch (err) {
|
|
21046
21688
|
const e = err;
|
|
21047
21689
|
if (e.code === "ENOENT") {
|
|
@@ -21064,7 +21706,7 @@ var TombstoneStore = class {
|
|
|
21064
21706
|
async listForAgent(agentId) {
|
|
21065
21707
|
let files;
|
|
21066
21708
|
try {
|
|
21067
|
-
files = await
|
|
21709
|
+
files = await fs11.readdir(paths.tombstoneAgentDir(agentId));
|
|
21068
21710
|
} catch (err) {
|
|
21069
21711
|
const e = err;
|
|
21070
21712
|
if (e.code === "ENOENT") {
|
|
@@ -21099,7 +21741,7 @@ function shouldResurrectFromUpstream(tombstone, listingUpdatedAt) {
|
|
|
21099
21741
|
}
|
|
21100
21742
|
|
|
21101
21743
|
// src/core/synopsis-coordinator.ts
|
|
21102
|
-
import * as
|
|
21744
|
+
import * as fs13 from "fs/promises";
|
|
21103
21745
|
|
|
21104
21746
|
// src/core/synopsis-agent.ts
|
|
21105
21747
|
init_types();
|
|
@@ -21311,13 +21953,13 @@ ${SNAPSHOT_PROMPT}` : SNAPSHOT_PROMPT;
|
|
|
21311
21953
|
return parsed;
|
|
21312
21954
|
})();
|
|
21313
21955
|
return await new Promise(
|
|
21314
|
-
(
|
|
21956
|
+
(resolve9, reject) => {
|
|
21315
21957
|
timer = setTimeout(() => {
|
|
21316
21958
|
timedOut = true;
|
|
21317
21959
|
opts.logger?.warn(
|
|
21318
21960
|
`synopsis: agent ${opts.agentId} timed out after ${timeoutMs}ms`
|
|
21319
21961
|
);
|
|
21320
|
-
|
|
21962
|
+
resolve9(void 0);
|
|
21321
21963
|
}, timeoutMs);
|
|
21322
21964
|
timer.unref?.();
|
|
21323
21965
|
work.then(
|
|
@@ -21326,7 +21968,7 @@ ${SNAPSHOT_PROMPT}` : SNAPSHOT_PROMPT;
|
|
|
21326
21968
|
clearTimeout(timer);
|
|
21327
21969
|
}
|
|
21328
21970
|
if (!timedOut) {
|
|
21329
|
-
|
|
21971
|
+
resolve9(v);
|
|
21330
21972
|
}
|
|
21331
21973
|
},
|
|
21332
21974
|
(err) => {
|
|
@@ -21418,10 +22060,10 @@ function extractFilesTouchedDetailed(history) {
|
|
|
21418
22060
|
byTool.set(call.toolName, (byTool.get(call.toolName) ?? 0) + 1);
|
|
21419
22061
|
}
|
|
21420
22062
|
}
|
|
21421
|
-
return [...fileTouches.entries()].map(([
|
|
22063
|
+
return [...fileTouches.entries()].map(([path22, byTool]) => {
|
|
21422
22064
|
const perTool = [...byTool.entries()].map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
21423
22065
|
const total = perTool.reduce((s, t) => s + t.count, 0);
|
|
21424
|
-
return { path:
|
|
22066
|
+
return { path: path22, count: total, byTool: perTool };
|
|
21425
22067
|
}).sort((a, b) => b.count - a.count || a.path.localeCompare(b.path));
|
|
21426
22068
|
}
|
|
21427
22069
|
function extractFilesTouched(history) {
|
|
@@ -21622,7 +22264,7 @@ var SynopsisCoordinator = class {
|
|
|
21622
22264
|
});
|
|
21623
22265
|
const modelId = this.opts.synopsisModel;
|
|
21624
22266
|
const synopsisCwd = paths.sessionDir(sessionId);
|
|
21625
|
-
await
|
|
22267
|
+
await fs13.mkdir(synopsisCwd, { recursive: true }).catch(() => void 0);
|
|
21626
22268
|
this.opts.logger?.info(
|
|
21627
22269
|
`synopsis: start sessionId=${sessionId} agentId=${synopsisAgentId} historyLen=${history.length} model=${JSON.stringify(modelId ?? "(default)")} cwd=${synopsisCwd}`
|
|
21628
22270
|
);
|
|
@@ -21726,8 +22368,217 @@ function describeFields(s) {
|
|
|
21726
22368
|
|
|
21727
22369
|
// src/core/history-store.ts
|
|
21728
22370
|
init_paths();
|
|
21729
|
-
import * as
|
|
21730
|
-
|
|
22371
|
+
import * as fs14 from "fs/promises";
|
|
22372
|
+
|
|
22373
|
+
// src/core/tool-content.ts
|
|
22374
|
+
function parseToolContentMode(raw) {
|
|
22375
|
+
return raw === "summary" ? "summary" : "inline";
|
|
22376
|
+
}
|
|
22377
|
+
var SUMMARY_TEXT_CAP = 256;
|
|
22378
|
+
function applyToolContentMode(entries, mode) {
|
|
22379
|
+
if (mode !== "summary") {
|
|
22380
|
+
return entries;
|
|
22381
|
+
}
|
|
22382
|
+
return entries.map(summarizeEntry);
|
|
22383
|
+
}
|
|
22384
|
+
function summarizeEntry(entry) {
|
|
22385
|
+
if (entry.method !== "session/update") {
|
|
22386
|
+
return entry;
|
|
22387
|
+
}
|
|
22388
|
+
const params = entry.params;
|
|
22389
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
22390
|
+
return entry;
|
|
22391
|
+
}
|
|
22392
|
+
const p = params;
|
|
22393
|
+
const update = p.update;
|
|
22394
|
+
if (!update || typeof update !== "object" || Array.isArray(update)) {
|
|
22395
|
+
return entry;
|
|
22396
|
+
}
|
|
22397
|
+
const u = update;
|
|
22398
|
+
if (u.sessionUpdate !== "tool_call" && u.sessionUpdate !== "tool_call_update") {
|
|
22399
|
+
return entry;
|
|
22400
|
+
}
|
|
22401
|
+
const newUpdate = { ...u };
|
|
22402
|
+
if (Array.isArray(u.content)) {
|
|
22403
|
+
newUpdate.content = u.content.map(summarizeBlock);
|
|
22404
|
+
}
|
|
22405
|
+
const rawOutput = u.rawOutput;
|
|
22406
|
+
if (rawOutput && typeof rawOutput === "object" && !Array.isArray(rawOutput)) {
|
|
22407
|
+
const ro = rawOutput;
|
|
22408
|
+
const slim = {};
|
|
22409
|
+
if (ro.error !== void 0) {
|
|
22410
|
+
slim.error = clip(ro.error);
|
|
22411
|
+
}
|
|
22412
|
+
if (ro.metadata !== void 0) {
|
|
22413
|
+
slim.metadata = ro.metadata;
|
|
22414
|
+
}
|
|
22415
|
+
newUpdate.rawOutput = slim;
|
|
22416
|
+
}
|
|
22417
|
+
return { ...entry, params: { ...p, update: newUpdate } };
|
|
22418
|
+
}
|
|
22419
|
+
function isDiffBlock(block) {
|
|
22420
|
+
return !!block && typeof block === "object" && !Array.isArray(block) && block.type === "diff";
|
|
22421
|
+
}
|
|
22422
|
+
function summarizeBlock(block) {
|
|
22423
|
+
if (isDiffBlock(block)) {
|
|
22424
|
+
const b2 = block;
|
|
22425
|
+
const out2 = {
|
|
22426
|
+
type: "diff",
|
|
22427
|
+
oldText: "",
|
|
22428
|
+
newText: ""
|
|
22429
|
+
};
|
|
22430
|
+
if (typeof b2.path === "string") {
|
|
22431
|
+
out2.path = b2.path;
|
|
22432
|
+
}
|
|
22433
|
+
return out2;
|
|
22434
|
+
}
|
|
22435
|
+
if (!block || typeof block !== "object" || Array.isArray(block)) {
|
|
22436
|
+
return block;
|
|
22437
|
+
}
|
|
22438
|
+
const b = block;
|
|
22439
|
+
const out = { ...b };
|
|
22440
|
+
if (typeof b.text === "string") {
|
|
22441
|
+
out.text = clip(b.text);
|
|
22442
|
+
}
|
|
22443
|
+
if (typeof b.content === "string") {
|
|
22444
|
+
out.content = clip(b.content);
|
|
22445
|
+
} else if (b.content && typeof b.content === "object") {
|
|
22446
|
+
out.content = summarizeBlock(b.content);
|
|
22447
|
+
}
|
|
22448
|
+
return out;
|
|
22449
|
+
}
|
|
22450
|
+
function clip(value) {
|
|
22451
|
+
if (typeof value === "string" && value.length > SUMMARY_TEXT_CAP) {
|
|
22452
|
+
const elided = value.length - SUMMARY_TEXT_CAP;
|
|
22453
|
+
return `${value.slice(0, SUMMARY_TEXT_CAP)}\u2026[+${elided} chars omitted from summary export]`;
|
|
22454
|
+
}
|
|
22455
|
+
return value;
|
|
22456
|
+
}
|
|
22457
|
+
var TOOL_BLOB_THRESHOLD = 2048;
|
|
22458
|
+
function collectToolBlobHashes(entries) {
|
|
22459
|
+
const out = /* @__PURE__ */ new Set();
|
|
22460
|
+
const walk = (value) => {
|
|
22461
|
+
if (isToolBlobRef(value)) {
|
|
22462
|
+
out.add(value.__hydraBlob);
|
|
22463
|
+
return;
|
|
22464
|
+
}
|
|
22465
|
+
if (Array.isArray(value)) {
|
|
22466
|
+
for (const item of value) {
|
|
22467
|
+
walk(item);
|
|
22468
|
+
}
|
|
22469
|
+
return;
|
|
22470
|
+
}
|
|
22471
|
+
if (value && typeof value === "object") {
|
|
22472
|
+
for (const v of Object.values(value)) {
|
|
22473
|
+
walk(v);
|
|
22474
|
+
}
|
|
22475
|
+
}
|
|
22476
|
+
};
|
|
22477
|
+
for (const entry of entries) {
|
|
22478
|
+
walk(entry.params);
|
|
22479
|
+
}
|
|
22480
|
+
return out;
|
|
22481
|
+
}
|
|
22482
|
+
function isToolBlobRef(value) {
|
|
22483
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && typeof value.__hydraBlob === "string";
|
|
22484
|
+
}
|
|
22485
|
+
function isToolEntry(entry) {
|
|
22486
|
+
if (entry.method !== "session/update") {
|
|
22487
|
+
return false;
|
|
22488
|
+
}
|
|
22489
|
+
const params = entry.params;
|
|
22490
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
22491
|
+
return false;
|
|
22492
|
+
}
|
|
22493
|
+
const update = params.update;
|
|
22494
|
+
if (!update || typeof update !== "object" || Array.isArray(update)) {
|
|
22495
|
+
return false;
|
|
22496
|
+
}
|
|
22497
|
+
const kind = update.sessionUpdate;
|
|
22498
|
+
return kind === "tool_call" || kind === "tool_call_update";
|
|
22499
|
+
}
|
|
22500
|
+
async function externalizeToolEntry(entry, put) {
|
|
22501
|
+
if (!isToolEntry(entry)) {
|
|
22502
|
+
return entry;
|
|
22503
|
+
}
|
|
22504
|
+
const p = entry.params;
|
|
22505
|
+
const update = p.update;
|
|
22506
|
+
const newUpdate = await deepExternalize(update, put);
|
|
22507
|
+
return { ...entry, params: { ...p, update: newUpdate } };
|
|
22508
|
+
}
|
|
22509
|
+
async function deepExternalize(value, put) {
|
|
22510
|
+
if (typeof value === "string") {
|
|
22511
|
+
if (value.length <= TOOL_BLOB_THRESHOLD) {
|
|
22512
|
+
return value;
|
|
22513
|
+
}
|
|
22514
|
+
const hash = await put(value);
|
|
22515
|
+
if (hash === null) {
|
|
22516
|
+
return value;
|
|
22517
|
+
}
|
|
22518
|
+
const ref = { __hydraBlob: hash, bytes: value.length };
|
|
22519
|
+
return ref;
|
|
22520
|
+
}
|
|
22521
|
+
if (Array.isArray(value)) {
|
|
22522
|
+
const out = [];
|
|
22523
|
+
for (const item of value) {
|
|
22524
|
+
out.push(await deepExternalize(item, put));
|
|
22525
|
+
}
|
|
22526
|
+
return out;
|
|
22527
|
+
}
|
|
22528
|
+
if (value && typeof value === "object") {
|
|
22529
|
+
const out = {};
|
|
22530
|
+
for (const [k, v] of Object.entries(value)) {
|
|
22531
|
+
out[k] = await deepExternalize(v, put);
|
|
22532
|
+
}
|
|
22533
|
+
return out;
|
|
22534
|
+
}
|
|
22535
|
+
return value;
|
|
22536
|
+
}
|
|
22537
|
+
async function expandToolRefs(entry, get) {
|
|
22538
|
+
const params = entry.params;
|
|
22539
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
22540
|
+
return entry;
|
|
22541
|
+
}
|
|
22542
|
+
const expanded = await deepExpand(params, get);
|
|
22543
|
+
if (expanded === params) {
|
|
22544
|
+
return entry;
|
|
22545
|
+
}
|
|
22546
|
+
return { ...entry, params: expanded };
|
|
22547
|
+
}
|
|
22548
|
+
async function deepExpand(value, get) {
|
|
22549
|
+
if (isToolBlobRef(value)) {
|
|
22550
|
+
const text = await get(value.__hydraBlob);
|
|
22551
|
+
return text ?? "";
|
|
22552
|
+
}
|
|
22553
|
+
if (Array.isArray(value)) {
|
|
22554
|
+
let changed = false;
|
|
22555
|
+
const out = [];
|
|
22556
|
+
for (const item of value) {
|
|
22557
|
+
const next = await deepExpand(item, get);
|
|
22558
|
+
if (next !== item) {
|
|
22559
|
+
changed = true;
|
|
22560
|
+
}
|
|
22561
|
+
out.push(next);
|
|
22562
|
+
}
|
|
22563
|
+
return changed ? out : value;
|
|
22564
|
+
}
|
|
22565
|
+
if (value && typeof value === "object") {
|
|
22566
|
+
let changed = false;
|
|
22567
|
+
const out = {};
|
|
22568
|
+
for (const [k, v] of Object.entries(value)) {
|
|
22569
|
+
const next = await deepExpand(v, get);
|
|
22570
|
+
if (next !== v) {
|
|
22571
|
+
changed = true;
|
|
22572
|
+
}
|
|
22573
|
+
out[k] = next;
|
|
22574
|
+
}
|
|
22575
|
+
return changed ? out : value;
|
|
22576
|
+
}
|
|
22577
|
+
return value;
|
|
22578
|
+
}
|
|
22579
|
+
|
|
22580
|
+
// src/core/history-store.ts
|
|
22581
|
+
var SESSION_ID_PATTERN3 = /^[A-Za-z0-9_-]+$/;
|
|
21731
22582
|
var DEFAULT_MAX_ENTRIES = 1e3;
|
|
21732
22583
|
var HistoryStore = class {
|
|
21733
22584
|
// Serialize writes per session id so appends and rewrites don't
|
|
@@ -21739,26 +22590,34 @@ var HistoryStore = class {
|
|
|
21739
22590
|
this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
21740
22591
|
}
|
|
21741
22592
|
async append(sessionId, entry) {
|
|
21742
|
-
if (!
|
|
22593
|
+
if (!SESSION_ID_PATTERN3.test(sessionId)) {
|
|
21743
22594
|
return;
|
|
21744
22595
|
}
|
|
21745
22596
|
return this.enqueue(sessionId, async () => {
|
|
21746
|
-
await
|
|
21747
|
-
const
|
|
21748
|
-
|
|
22597
|
+
await fs14.mkdir(paths.sessionDir(sessionId), { recursive: true });
|
|
22598
|
+
const stored = await externalizeToolEntry(
|
|
22599
|
+
entry,
|
|
22600
|
+
(t) => putToolBlob(sessionId, t)
|
|
22601
|
+
);
|
|
22602
|
+
const line = JSON.stringify(stored) + "\n";
|
|
22603
|
+
await fs14.appendFile(paths.historyFile(sessionId), line, {
|
|
21749
22604
|
encoding: "utf8",
|
|
21750
22605
|
mode: 384
|
|
21751
22606
|
});
|
|
21752
22607
|
});
|
|
21753
22608
|
}
|
|
21754
22609
|
async rewrite(sessionId, entries) {
|
|
21755
|
-
if (!
|
|
22610
|
+
if (!SESSION_ID_PATTERN3.test(sessionId)) {
|
|
21756
22611
|
return;
|
|
21757
22612
|
}
|
|
21758
22613
|
return this.enqueue(sessionId, async () => {
|
|
21759
|
-
await
|
|
21760
|
-
const
|
|
21761
|
-
|
|
22614
|
+
await fs14.mkdir(paths.sessionDir(sessionId), { recursive: true });
|
|
22615
|
+
const stored = [];
|
|
22616
|
+
for (const e of entries) {
|
|
22617
|
+
stored.push(await externalizeToolEntry(e, (t) => putToolBlob(sessionId, t)));
|
|
22618
|
+
}
|
|
22619
|
+
const body = stored.length === 0 ? "" : stored.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
22620
|
+
await fs14.writeFile(paths.historyFile(sessionId), body, {
|
|
21762
22621
|
encoding: "utf8",
|
|
21763
22622
|
mode: 384
|
|
21764
22623
|
});
|
|
@@ -21769,13 +22628,13 @@ var HistoryStore = class {
|
|
|
21769
22628
|
// it's safe to invoke alongside ongoing writes; a no-op if the file is
|
|
21770
22629
|
// already at or below the cap.
|
|
21771
22630
|
async compact(sessionId, maxEntries) {
|
|
21772
|
-
if (!
|
|
22631
|
+
if (!SESSION_ID_PATTERN3.test(sessionId)) {
|
|
21773
22632
|
return;
|
|
21774
22633
|
}
|
|
21775
22634
|
return this.enqueue(sessionId, async () => {
|
|
21776
22635
|
let raw;
|
|
21777
22636
|
try {
|
|
21778
|
-
raw = await
|
|
22637
|
+
raw = await fs14.readFile(paths.historyFile(sessionId), "utf8");
|
|
21779
22638
|
} catch (err) {
|
|
21780
22639
|
const e = err;
|
|
21781
22640
|
if (e.code === "ENOENT") {
|
|
@@ -21788,23 +22647,29 @@ var HistoryStore = class {
|
|
|
21788
22647
|
return;
|
|
21789
22648
|
}
|
|
21790
22649
|
const trimmed = lines.slice(-maxEntries);
|
|
21791
|
-
await
|
|
22650
|
+
await fs14.writeFile(paths.historyFile(sessionId), trimmed.join("\n") + "\n", {
|
|
21792
22651
|
encoding: "utf8",
|
|
21793
22652
|
mode: 384
|
|
21794
22653
|
});
|
|
21795
22654
|
});
|
|
21796
22655
|
}
|
|
21797
|
-
|
|
21798
|
-
|
|
22656
|
+
// `tools` selects how externalized tool content is materialized:
|
|
22657
|
+
// "inline" (default) — expand blob refs back to full content, so every
|
|
22658
|
+
// consumer sees the original recorded shape.
|
|
22659
|
+
// "references" — leave references in place (the lean form) for
|
|
22660
|
+
// clients that fetch tool content on demand.
|
|
22661
|
+
async load(sessionId, opts = {}) {
|
|
22662
|
+
if (!SESSION_ID_PATTERN3.test(sessionId)) {
|
|
21799
22663
|
return [];
|
|
21800
22664
|
}
|
|
22665
|
+
const expand = (opts.tools ?? "inline") === "inline";
|
|
21801
22666
|
const pending = this.writeQueues.get(sessionId);
|
|
21802
22667
|
if (pending) {
|
|
21803
22668
|
await pending;
|
|
21804
22669
|
}
|
|
21805
22670
|
let raw;
|
|
21806
22671
|
try {
|
|
21807
|
-
raw = await
|
|
22672
|
+
raw = await fs14.readFile(paths.historyFile(sessionId), "utf8");
|
|
21808
22673
|
} catch (err) {
|
|
21809
22674
|
const e = err;
|
|
21810
22675
|
if (e.code === "ENOENT") {
|
|
@@ -21839,10 +22704,25 @@ var HistoryStore = class {
|
|
|
21839
22704
|
recordedAt: obj.recordedAt
|
|
21840
22705
|
});
|
|
21841
22706
|
}
|
|
21842
|
-
|
|
21843
|
-
|
|
22707
|
+
const kept = out.length > this.maxEntries ? out.slice(-this.maxEntries) : out;
|
|
22708
|
+
if (!expand) {
|
|
22709
|
+
return kept;
|
|
21844
22710
|
}
|
|
21845
|
-
|
|
22711
|
+
const blobCache = /* @__PURE__ */ new Map();
|
|
22712
|
+
const get = async (hash) => {
|
|
22713
|
+
const cached2 = blobCache.get(hash);
|
|
22714
|
+
if (cached2 !== void 0) {
|
|
22715
|
+
return cached2;
|
|
22716
|
+
}
|
|
22717
|
+
const value = await getToolBlob(sessionId, hash);
|
|
22718
|
+
blobCache.set(hash, value);
|
|
22719
|
+
return value;
|
|
22720
|
+
};
|
|
22721
|
+
const inlined = [];
|
|
22722
|
+
for (const entry of kept) {
|
|
22723
|
+
inlined.push(await expandToolRefs(entry, get));
|
|
22724
|
+
}
|
|
22725
|
+
return inlined;
|
|
21846
22726
|
}
|
|
21847
22727
|
// Wait for every pending append/rewrite/compact across all sessions to
|
|
21848
22728
|
// settle. Daemon shutdown calls this after closing sessions so the final
|
|
@@ -21858,20 +22738,21 @@ var HistoryStore = class {
|
|
|
21858
22738
|
await Promise.allSettled(pending);
|
|
21859
22739
|
}
|
|
21860
22740
|
async delete(sessionId) {
|
|
21861
|
-
if (!
|
|
22741
|
+
if (!SESSION_ID_PATTERN3.test(sessionId)) {
|
|
21862
22742
|
return;
|
|
21863
22743
|
}
|
|
21864
22744
|
return this.enqueue(sessionId, async () => {
|
|
21865
22745
|
try {
|
|
21866
|
-
await
|
|
22746
|
+
await fs14.unlink(paths.historyFile(sessionId));
|
|
21867
22747
|
} catch (err) {
|
|
21868
22748
|
const e = err;
|
|
21869
22749
|
if (e.code !== "ENOENT") {
|
|
21870
22750
|
throw err;
|
|
21871
22751
|
}
|
|
21872
22752
|
}
|
|
22753
|
+
await deleteToolBlobs(sessionId);
|
|
21873
22754
|
try {
|
|
21874
|
-
await
|
|
22755
|
+
await fs14.rmdir(paths.sessionDir(sessionId));
|
|
21875
22756
|
} catch (err) {
|
|
21876
22757
|
const e = err;
|
|
21877
22758
|
if (e.code !== "ENOENT" && e.code !== "ENOTEMPTY") {
|
|
@@ -21958,7 +22839,12 @@ var Bundle = z7.object({
|
|
|
21958
22839
|
}),
|
|
21959
22840
|
session: BundleSession,
|
|
21960
22841
|
history: z7.array(HistoryEntrySchema),
|
|
21961
|
-
promptHistory: z7.array(z7.string()).optional()
|
|
22842
|
+
promptHistory: z7.array(z7.string()).optional(),
|
|
22843
|
+
// Externalized tool-content blobs referenced by the history, present when
|
|
22844
|
+
// the bundle was exported with tools=references. Map of sha256 → base64
|
|
22845
|
+
// of the gzipped content. Restored to the session's tools/ store on
|
|
22846
|
+
// import; the ref-form history hydrates from them.
|
|
22847
|
+
toolBlobs: z7.record(z7.string()).optional()
|
|
21962
22848
|
});
|
|
21963
22849
|
function encodeBundle(params) {
|
|
21964
22850
|
const bundle = {
|
|
@@ -21993,6 +22879,9 @@ function encodeBundle(params) {
|
|
|
21993
22879
|
if (params.promptHistory !== void 0) {
|
|
21994
22880
|
bundle.promptHistory = params.promptHistory;
|
|
21995
22881
|
}
|
|
22882
|
+
if (params.toolBlobs !== void 0 && Object.keys(params.toolBlobs).length > 0) {
|
|
22883
|
+
bundle.toolBlobs = params.toolBlobs;
|
|
22884
|
+
}
|
|
21996
22885
|
return bundle;
|
|
21997
22886
|
}
|
|
21998
22887
|
function decodeBundle(raw) {
|
|
@@ -22000,6 +22889,7 @@ function decodeBundle(raw) {
|
|
|
22000
22889
|
}
|
|
22001
22890
|
|
|
22002
22891
|
// src/core/session-manager.ts
|
|
22892
|
+
init_model_resolve();
|
|
22003
22893
|
init_types();
|
|
22004
22894
|
init_hydra_version();
|
|
22005
22895
|
init_queue_store();
|
|
@@ -22410,7 +23300,7 @@ var SessionManager = class {
|
|
|
22410
23300
|
}
|
|
22411
23301
|
async dirExists(cwd) {
|
|
22412
23302
|
try {
|
|
22413
|
-
return (await
|
|
23303
|
+
return (await fs16.stat(cwd)).isDirectory();
|
|
22414
23304
|
} catch {
|
|
22415
23305
|
return false;
|
|
22416
23306
|
}
|
|
@@ -22600,6 +23490,25 @@ var SessionManager = class {
|
|
|
22600
23490
|
}
|
|
22601
23491
|
return out;
|
|
22602
23492
|
}
|
|
23493
|
+
// Issue session/set_model for a seed model (defaultModels / --model) at
|
|
23494
|
+
// bootstrap, logging success or a non-fatal rejection. `where` is the
|
|
23495
|
+
// human-readable provenance string used in log lines. A bad id in config
|
|
23496
|
+
// shouldn't break session creation, so a rejection is swallowed.
|
|
23497
|
+
async applySeedModel(agent, sessionId, modelId, where) {
|
|
23498
|
+
try {
|
|
23499
|
+
await agent.connection.request("session/set_model", {
|
|
23500
|
+
sessionId,
|
|
23501
|
+
modelId
|
|
23502
|
+
});
|
|
23503
|
+
this.logger?.info(`${where}: session/set_model accepted`);
|
|
23504
|
+
return true;
|
|
23505
|
+
} catch (err) {
|
|
23506
|
+
this.logger?.warn(
|
|
23507
|
+
`${where} rejected by agent (${err.message}); session will use the agent's own default`
|
|
23508
|
+
);
|
|
23509
|
+
return false;
|
|
23510
|
+
}
|
|
23511
|
+
}
|
|
22603
23512
|
// Bootstrap a fresh agent process: registry resolve → spawn → initialize
|
|
22604
23513
|
// → session/new. Shared by create() and the /hydra agent path so both
|
|
22605
23514
|
// go through the same env / capabilities / error-handling.
|
|
@@ -22648,23 +23557,31 @@ var SessionManager = class {
|
|
|
22648
23557
|
const initialModels = extractInitialModels(newResult);
|
|
22649
23558
|
const desired = params.model ?? this.defaultModels[params.agentId];
|
|
22650
23559
|
if (desired && desired !== initialModel) {
|
|
22651
|
-
const
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
sessionId: sessionIdRaw,
|
|
22656
|
-
modelId: desired
|
|
22657
|
-
});
|
|
23560
|
+
const resolution = resolveModelId(desired, initialModels);
|
|
23561
|
+
const where = params.model !== void 0 ? `model=${JSON.stringify(desired)}` : `defaultModels[${params.agentId}]=${JSON.stringify(desired)}`;
|
|
23562
|
+
if (resolution.kind === "exact" || resolution.kind === "none") {
|
|
23563
|
+
if (await this.applySeedModel(agent, sessionIdRaw, desired, where)) {
|
|
22658
23564
|
initialModel = desired;
|
|
22659
|
-
} catch (err) {
|
|
22660
|
-
this.logger?.warn(
|
|
22661
|
-
`defaultModels[${params.agentId}]=${JSON.stringify(desired)} rejected by agent (${err.message}); session will use ${JSON.stringify(initialModel)}`
|
|
22662
|
-
);
|
|
22663
23565
|
}
|
|
23566
|
+
} else if (resolution.kind === "resolved") {
|
|
23567
|
+
if (resolution.modelId === initialModel) {
|
|
23568
|
+
initialModel = resolution.modelId;
|
|
23569
|
+
} else if (await this.applySeedModel(
|
|
23570
|
+
agent,
|
|
23571
|
+
sessionIdRaw,
|
|
23572
|
+
resolution.modelId,
|
|
23573
|
+
`${where} resolved to ${JSON.stringify(resolution.modelId)}`
|
|
23574
|
+
)) {
|
|
23575
|
+
initialModel = resolution.modelId;
|
|
23576
|
+
}
|
|
23577
|
+
} else if (resolution.kind === "ambiguous") {
|
|
23578
|
+
this.logger?.warn(
|
|
23579
|
+
`${where} is ambiguous (trailing-segment matches [${resolution.candidates.join(", ")}]); skipping session/set_model, session will use ${JSON.stringify(initialModel)}`
|
|
23580
|
+
);
|
|
22664
23581
|
} else {
|
|
22665
23582
|
const known = initialModels.map((m) => m.modelId).join(", ");
|
|
22666
23583
|
this.logger?.warn(
|
|
22667
|
-
|
|
23584
|
+
`${where} not in agent's availableModels ([${known}]); skipping session/set_model, session will use ${JSON.stringify(initialModel)}`
|
|
22668
23585
|
);
|
|
22669
23586
|
}
|
|
22670
23587
|
}
|
|
@@ -22797,6 +23714,12 @@ var SessionManager = class {
|
|
|
22797
23714
|
async loadHistory(sessionId) {
|
|
22798
23715
|
return this.histories.load(sessionId);
|
|
22799
23716
|
}
|
|
23717
|
+
// Read a single externalized tool-content blob by sha256 (the lean
|
|
23718
|
+
// `tools: "references"` fetch-on-expand path). Null if the session id or
|
|
23719
|
+
// hash is malformed, or the blob isn't present.
|
|
23720
|
+
async loadToolBlob(sessionId, hash) {
|
|
23721
|
+
return getToolBlob(sessionId, hash);
|
|
23722
|
+
}
|
|
22800
23723
|
async loadFromDisk(sessionId) {
|
|
22801
23724
|
const record = await this.store.read(sessionId);
|
|
22802
23725
|
if (!record) {
|
|
@@ -23034,7 +23957,7 @@ var SessionManager = class {
|
|
|
23034
23957
|
// disk. Backfills lineageId if the on-disk record pre-dates that
|
|
23035
23958
|
// field. Returns undefined if the session doesn't exist. Callers
|
|
23036
23959
|
// populate the bundle's exportedFrom metadata themselves.
|
|
23037
|
-
async exportBundle(sessionId) {
|
|
23960
|
+
async exportBundle(sessionId, opts = {}) {
|
|
23038
23961
|
const record = await this.store.read(sessionId);
|
|
23039
23962
|
if (!record) {
|
|
23040
23963
|
return void 0;
|
|
@@ -23057,9 +23980,20 @@ var SessionManager = class {
|
|
|
23057
23980
|
}).catch(() => void 0);
|
|
23058
23981
|
withLineage = backfilled;
|
|
23059
23982
|
}
|
|
23060
|
-
const
|
|
23983
|
+
const tools = opts.tools ?? "inline";
|
|
23984
|
+
const history = await this.histories.load(sessionId, tools === "references" ? { tools: "references" } : {}).catch(() => []);
|
|
23061
23985
|
const promptHistory = await loadPromptHistorySafely(sessionId);
|
|
23062
|
-
|
|
23986
|
+
if (tools !== "references") {
|
|
23987
|
+
return { record: withLineage, history, promptHistory };
|
|
23988
|
+
}
|
|
23989
|
+
const toolBlobs = {};
|
|
23990
|
+
for (const hash of collectToolBlobHashes(history)) {
|
|
23991
|
+
const gz = await readToolBlobGz(sessionId, hash);
|
|
23992
|
+
if (gz) {
|
|
23993
|
+
toolBlobs[hash] = gz.toString("base64");
|
|
23994
|
+
}
|
|
23995
|
+
}
|
|
23996
|
+
return { record: withLineage, history, promptHistory, toolBlobs };
|
|
23063
23997
|
}
|
|
23064
23998
|
// Create a local session from an imported bundle. Without `replace`,
|
|
23065
23999
|
// a bundle with a lineageId we already have on disk throws
|
|
@@ -23220,9 +24154,18 @@ var SessionManager = class {
|
|
|
23220
24154
|
args.sessionId,
|
|
23221
24155
|
args.bundle.history
|
|
23222
24156
|
);
|
|
24157
|
+
if (args.bundle.toolBlobs) {
|
|
24158
|
+
for (const [hash, b64] of Object.entries(args.bundle.toolBlobs)) {
|
|
24159
|
+
await writeToolBlobGz(
|
|
24160
|
+
args.sessionId,
|
|
24161
|
+
hash,
|
|
24162
|
+
Buffer.from(b64, "base64")
|
|
24163
|
+
).catch(() => void 0);
|
|
24164
|
+
}
|
|
24165
|
+
}
|
|
23223
24166
|
const sourceMtime = new Date(args.bundle.session.updatedAt);
|
|
23224
24167
|
if (!Number.isNaN(sourceMtime.getTime())) {
|
|
23225
|
-
await
|
|
24168
|
+
await fs16.utimes(paths.historyFile(args.sessionId), sourceMtime, sourceMtime).catch(() => void 0);
|
|
23226
24169
|
}
|
|
23227
24170
|
if (args.bundle.promptHistory && args.bundle.promptHistory.length > 0) {
|
|
23228
24171
|
await saveHistory(
|
|
@@ -23869,7 +24812,7 @@ function findLastTurnComplete(history) {
|
|
|
23869
24812
|
}
|
|
23870
24813
|
async function loadPromptHistorySafely(sessionId) {
|
|
23871
24814
|
try {
|
|
23872
|
-
const raw = await
|
|
24815
|
+
const raw = await fs16.readFile(paths.tuiHistoryFile(sessionId), "utf8");
|
|
23873
24816
|
const out = [];
|
|
23874
24817
|
for (const line of raw.split("\n")) {
|
|
23875
24818
|
if (line.length === 0) {
|
|
@@ -23890,7 +24833,7 @@ async function loadPromptHistorySafely(sessionId) {
|
|
|
23890
24833
|
}
|
|
23891
24834
|
async function historyStatus(sessionId) {
|
|
23892
24835
|
try {
|
|
23893
|
-
const st = await
|
|
24836
|
+
const st = await fs16.stat(paths.historyFile(sessionId));
|
|
23894
24837
|
return {
|
|
23895
24838
|
mtime: new Date(st.mtimeMs).toISOString(),
|
|
23896
24839
|
hasContent: st.size > 0
|
|
@@ -23911,7 +24854,7 @@ function effectiveInteractive(record, hasContent) {
|
|
|
23911
24854
|
|
|
23912
24855
|
// src/core/child-supervisor.ts
|
|
23913
24856
|
import { spawn as spawn4 } from "child_process";
|
|
23914
|
-
import * as
|
|
24857
|
+
import * as fs17 from "fs";
|
|
23915
24858
|
import * as fsp5 from "fs/promises";
|
|
23916
24859
|
import * as path11 from "path";
|
|
23917
24860
|
|
|
@@ -24026,9 +24969,9 @@ var ChildSupervisor = class {
|
|
|
24026
24969
|
} catch {
|
|
24027
24970
|
}
|
|
24028
24971
|
tasks.push(
|
|
24029
|
-
new Promise((
|
|
24972
|
+
new Promise((resolve9) => {
|
|
24030
24973
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
24031
|
-
|
|
24974
|
+
resolve9();
|
|
24032
24975
|
return;
|
|
24033
24976
|
}
|
|
24034
24977
|
const timer = setTimeout(() => {
|
|
@@ -24036,11 +24979,11 @@ var ChildSupervisor = class {
|
|
|
24036
24979
|
child.kill("SIGKILL");
|
|
24037
24980
|
} catch {
|
|
24038
24981
|
}
|
|
24039
|
-
|
|
24982
|
+
resolve9();
|
|
24040
24983
|
}, STOP_GRACE_MS);
|
|
24041
24984
|
child.on("exit", () => {
|
|
24042
24985
|
clearTimeout(timer);
|
|
24043
|
-
|
|
24986
|
+
resolve9();
|
|
24044
24987
|
});
|
|
24045
24988
|
})
|
|
24046
24989
|
);
|
|
@@ -24164,8 +25107,8 @@ var ChildSupervisor = class {
|
|
|
24164
25107
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
24165
25108
|
return;
|
|
24166
25109
|
}
|
|
24167
|
-
const exited = new Promise((
|
|
24168
|
-
entry.exitWaiters.push(
|
|
25110
|
+
const exited = new Promise((resolve9) => {
|
|
25111
|
+
entry.exitWaiters.push(resolve9);
|
|
24169
25112
|
});
|
|
24170
25113
|
try {
|
|
24171
25114
|
child.kill("SIGTERM");
|
|
@@ -24284,7 +25227,7 @@ var ChildSupervisor = class {
|
|
|
24284
25227
|
}
|
|
24285
25228
|
const cfg = entry.config;
|
|
24286
25229
|
const command = cfg.command.length > 0 ? cfg.command : [cfg.name];
|
|
24287
|
-
const logStream =
|
|
25230
|
+
const logStream = fs17.createWriteStream(
|
|
24288
25231
|
this.adapter.paths.logFile(cfg.name),
|
|
24289
25232
|
{ flags: "a" }
|
|
24290
25233
|
);
|
|
@@ -24340,7 +25283,7 @@ var ChildSupervisor = class {
|
|
|
24340
25283
|
}
|
|
24341
25284
|
if (typeof child.pid === "number") {
|
|
24342
25285
|
try {
|
|
24343
|
-
|
|
25286
|
+
fs17.writeFileSync(
|
|
24344
25287
|
this.adapter.paths.pidFile(cfg.name),
|
|
24345
25288
|
`${child.pid}
|
|
24346
25289
|
`,
|
|
@@ -24366,7 +25309,7 @@ var ChildSupervisor = class {
|
|
|
24366
25309
|
});
|
|
24367
25310
|
child.on("exit", (code, signal) => {
|
|
24368
25311
|
try {
|
|
24369
|
-
|
|
25312
|
+
fs17.unlinkSync(this.adapter.paths.pidFile(cfg.name));
|
|
24370
25313
|
} catch {
|
|
24371
25314
|
}
|
|
24372
25315
|
logStream.write(
|
|
@@ -24381,8 +25324,8 @@ var ChildSupervisor = class {
|
|
|
24381
25324
|
entry.processToken = void 0;
|
|
24382
25325
|
}
|
|
24383
25326
|
const waiters = entry.exitWaiters.splice(0);
|
|
24384
|
-
for (const
|
|
24385
|
-
|
|
25327
|
+
for (const resolve9 of waiters) {
|
|
25328
|
+
resolve9();
|
|
24386
25329
|
}
|
|
24387
25330
|
if (this.stopping || entry.manuallyStopped) {
|
|
24388
25331
|
try {
|
|
@@ -24717,7 +25660,7 @@ init_hydra_version();
|
|
|
24717
25660
|
init_paths();
|
|
24718
25661
|
init_json_store();
|
|
24719
25662
|
import * as path13 from "path";
|
|
24720
|
-
import { createHash, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
25663
|
+
import { createHash as createHash2, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
24721
25664
|
var TOKEN_PREFIX = "hydra_session_";
|
|
24722
25665
|
var DEFAULT_TTL_SEC = 60 * 60 * 24 * 30;
|
|
24723
25666
|
var ID_LENGTH = 12;
|
|
@@ -24727,7 +25670,7 @@ function tokensFilePath() {
|
|
|
24727
25670
|
return path13.join(paths.home(), "session-tokens.json");
|
|
24728
25671
|
}
|
|
24729
25672
|
function sha256Hex(input) {
|
|
24730
|
-
return
|
|
25673
|
+
return createHash2("sha256").update(input).digest("hex");
|
|
24731
25674
|
}
|
|
24732
25675
|
function randomHex(bytes) {
|
|
24733
25676
|
return randomBytes2(bytes).toString("hex");
|
|
@@ -25844,15 +26787,21 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
25844
26787
|
app.get("/v1/sessions/:id/export", async (request, reply) => {
|
|
25845
26788
|
const raw = request.params.id;
|
|
25846
26789
|
const id = await manager.resolveCanonicalId(raw) ?? raw;
|
|
25847
|
-
const
|
|
26790
|
+
const toolsRaw = request.query?.tools;
|
|
26791
|
+
const toolMode = toolsRaw === "references" ? "references" : parseToolContentMode(toolsRaw);
|
|
26792
|
+
const exported = await manager.exportBundle(
|
|
26793
|
+
id,
|
|
26794
|
+
toolMode === "references" ? { tools: "references" } : {}
|
|
26795
|
+
);
|
|
25848
26796
|
if (!exported) {
|
|
25849
26797
|
reply.code(404).send({ error: "session not found" });
|
|
25850
26798
|
return;
|
|
25851
26799
|
}
|
|
25852
26800
|
const bundle = encodeBundle({
|
|
25853
26801
|
record: exported.record,
|
|
25854
|
-
history: exported.history,
|
|
26802
|
+
history: toolMode === "summary" ? applyToolContentMode(exported.history, "summary") : exported.history,
|
|
25855
26803
|
promptHistory: exported.promptHistory.length > 0 ? exported.promptHistory : void 0,
|
|
26804
|
+
...exported.toolBlobs !== void 0 ? { toolBlobs: exported.toolBlobs } : {},
|
|
25856
26805
|
hydraVersion: HYDRA_VERSION,
|
|
25857
26806
|
machine: os4.hostname(),
|
|
25858
26807
|
hydraHost: resolveHydraHost(defaults)
|
|
@@ -25864,6 +26813,17 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
25864
26813
|
);
|
|
25865
26814
|
reply.code(200).send(bundle);
|
|
25866
26815
|
});
|
|
26816
|
+
app.get("/v1/sessions/:id/tools/:hash", async (request, reply) => {
|
|
26817
|
+
const params = request.params;
|
|
26818
|
+
const id = await manager.resolveCanonicalId(params.id) ?? params.id;
|
|
26819
|
+
const blob = await manager.loadToolBlob(id, params.hash);
|
|
26820
|
+
if (blob === null) {
|
|
26821
|
+
reply.code(404).send({ error: "tool blob not found" });
|
|
26822
|
+
return;
|
|
26823
|
+
}
|
|
26824
|
+
reply.header("Content-Type", "text/plain; charset=utf-8");
|
|
26825
|
+
reply.code(200).send(blob);
|
|
26826
|
+
});
|
|
25867
26827
|
app.get("/v1/sessions/:id/transcript", async (request, reply) => {
|
|
25868
26828
|
const raw = request.params.id;
|
|
25869
26829
|
const id = await manager.resolveCanonicalId(raw) ?? raw;
|
|
@@ -26350,11 +27310,11 @@ import { z as z8 } from "zod";
|
|
|
26350
27310
|
|
|
26351
27311
|
// src/core/password.ts
|
|
26352
27312
|
init_paths();
|
|
26353
|
-
import * as
|
|
27313
|
+
import * as fs18 from "fs/promises";
|
|
26354
27314
|
import * as path15 from "path";
|
|
26355
27315
|
import { randomBytes as randomBytes3, scrypt, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
26356
|
-
import { promisify } from "util";
|
|
26357
|
-
var scryptAsync =
|
|
27316
|
+
import { promisify as promisify2 } from "util";
|
|
27317
|
+
var scryptAsync = promisify2(scrypt);
|
|
26358
27318
|
function passwordHashPath() {
|
|
26359
27319
|
return path15.join(paths.home(), "password-hash");
|
|
26360
27320
|
}
|
|
@@ -26377,15 +27337,15 @@ async function setPassword(plaintext) {
|
|
|
26377
27337
|
});
|
|
26378
27338
|
const encoded = `scrypt$${DEFAULT_N}$${DEFAULT_R}$${DEFAULT_P}$${salt.toString("hex")}$${key.toString("hex")}
|
|
26379
27339
|
`;
|
|
26380
|
-
await
|
|
26381
|
-
await
|
|
27340
|
+
await fs18.mkdir(paths.home(), { recursive: true });
|
|
27341
|
+
await fs18.writeFile(passwordHashPath(), encoded, {
|
|
26382
27342
|
encoding: "utf8",
|
|
26383
27343
|
mode: 384
|
|
26384
27344
|
});
|
|
26385
27345
|
}
|
|
26386
27346
|
async function hasPassword() {
|
|
26387
27347
|
try {
|
|
26388
|
-
const text = await
|
|
27348
|
+
const text = await fs18.readFile(passwordHashPath(), "utf8");
|
|
26389
27349
|
return text.trim().length > 0;
|
|
26390
27350
|
} catch (err) {
|
|
26391
27351
|
const e = err;
|
|
@@ -26401,7 +27361,7 @@ async function verifyPassword(plaintext) {
|
|
|
26401
27361
|
}
|
|
26402
27362
|
let line;
|
|
26403
27363
|
try {
|
|
26404
|
-
line = (await
|
|
27364
|
+
line = (await fs18.readFile(passwordHashPath(), "utf8")).trim();
|
|
26405
27365
|
} catch (err) {
|
|
26406
27366
|
const e = err;
|
|
26407
27367
|
if (e.code === "ENOENT") {
|
|
@@ -26523,6 +27483,7 @@ function remoteIp(request) {
|
|
|
26523
27483
|
// src/daemon/acp-ws.ts
|
|
26524
27484
|
init_connection();
|
|
26525
27485
|
init_ws_stream();
|
|
27486
|
+
init_model_resolve();
|
|
26526
27487
|
init_types();
|
|
26527
27488
|
import { nanoid as nanoid2 } from "nanoid";
|
|
26528
27489
|
init_hydra_version();
|
|
@@ -26778,13 +27739,13 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
26778
27739
|
{ code: JsonRpcErrorCodes.SessionNotFound }
|
|
26779
27740
|
);
|
|
26780
27741
|
}
|
|
26781
|
-
return new Promise((
|
|
27742
|
+
return new Promise((resolve9) => {
|
|
26782
27743
|
const entries = [];
|
|
26783
27744
|
let unsubscribe;
|
|
26784
27745
|
const finish = () => {
|
|
26785
27746
|
clearTimeout(timer);
|
|
26786
27747
|
unsubscribe?.();
|
|
26787
|
-
|
|
27748
|
+
resolve9({ entries });
|
|
26788
27749
|
};
|
|
26789
27750
|
unsubscribe = child.onBroadcast((entry) => {
|
|
26790
27751
|
entries.push(entry);
|
|
@@ -26826,6 +27787,24 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
26826
27787
|
return { ok: true };
|
|
26827
27788
|
});
|
|
26828
27789
|
}
|
|
27790
|
+
connection.onRequest("hydra-acp/session/tool_content", async (raw) => {
|
|
27791
|
+
const params = raw ?? {};
|
|
27792
|
+
if (typeof params.sessionId !== "string" || typeof params.hash !== "string") {
|
|
27793
|
+
throw Object.assign(
|
|
27794
|
+
new Error("hydra-acp/session/tool_content requires sessionId and hash"),
|
|
27795
|
+
{ code: JsonRpcErrorCodes.InvalidParams }
|
|
27796
|
+
);
|
|
27797
|
+
}
|
|
27798
|
+
const id = await deps.manager.resolveCanonicalId(params.sessionId) ?? params.sessionId;
|
|
27799
|
+
const content = await deps.manager.loadToolBlob(id, params.hash);
|
|
27800
|
+
if (content === null) {
|
|
27801
|
+
throw Object.assign(
|
|
27802
|
+
new Error("tool content not found"),
|
|
27803
|
+
{ code: JsonRpcErrorCodes.SessionNotFound }
|
|
27804
|
+
);
|
|
27805
|
+
}
|
|
27806
|
+
return { content };
|
|
27807
|
+
});
|
|
26829
27808
|
connection.onRequest("session/new", async (raw) => {
|
|
26830
27809
|
const params = SessionNewParams.parse(raw);
|
|
26831
27810
|
const hydraMeta = extractHydraMeta(
|
|
@@ -27032,7 +28011,13 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
27032
28011
|
const { entries: replay, appliedPolicy } = await session.attach(
|
|
27033
28012
|
client,
|
|
27034
28013
|
params.historyPolicy,
|
|
27035
|
-
{
|
|
28014
|
+
{
|
|
28015
|
+
afterMessageId: params.afterMessageId,
|
|
28016
|
+
raw: drip,
|
|
28017
|
+
// Lean clients opt into ref-form tool content via _meta; default
|
|
28018
|
+
// stays inline so existing/third-party clients are unaffected.
|
|
28019
|
+
...hydraAttach.toolContent !== void 0 ? { toolContent: hydraAttach.toolContent } : {}
|
|
28020
|
+
}
|
|
27036
28021
|
);
|
|
27037
28022
|
state.attached.set(session.sessionId, {
|
|
27038
28023
|
sessionId: session.sessionId,
|
|
@@ -27066,8 +28051,13 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
27066
28051
|
}
|
|
27067
28052
|
})();
|
|
27068
28053
|
} else {
|
|
27069
|
-
|
|
27070
|
-
|
|
28054
|
+
const REPLAY_FLUSH_EVERY = 200;
|
|
28055
|
+
for (let i = 0; i < replay.length; i++) {
|
|
28056
|
+
const note = replay[i];
|
|
28057
|
+
const pending = connection.notify(note.method, note.params).catch(() => void 0);
|
|
28058
|
+
if ((i + 1) % REPLAY_FLUSH_EVERY === 0) {
|
|
28059
|
+
await pending;
|
|
28060
|
+
}
|
|
27071
28061
|
}
|
|
27072
28062
|
}
|
|
27073
28063
|
session.replayPendingPermissions(client);
|
|
@@ -27324,8 +28314,11 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
27324
28314
|
return null;
|
|
27325
28315
|
}
|
|
27326
28316
|
app.log.info(decision.logMessage);
|
|
27327
|
-
const { modelId } =
|
|
27328
|
-
const result = await decision.session.forwardRequest("session/set_model",
|
|
28317
|
+
const { modelId } = decision;
|
|
28318
|
+
const result = await decision.session.forwardRequest("session/set_model", {
|
|
28319
|
+
...rawParams,
|
|
28320
|
+
modelId
|
|
28321
|
+
});
|
|
27329
28322
|
decision.session.applyModelChange(modelId);
|
|
27330
28323
|
return result;
|
|
27331
28324
|
});
|
|
@@ -27547,36 +28540,47 @@ function decideSetModel(rawParams, manager) {
|
|
|
27547
28540
|
};
|
|
27548
28541
|
}
|
|
27549
28542
|
const advertised = session.availableModels();
|
|
27550
|
-
|
|
28543
|
+
const resolution = resolveModelId(params.modelId, advertised);
|
|
28544
|
+
if (resolution.kind === "none") {
|
|
27551
28545
|
return {
|
|
27552
28546
|
kind: "ok",
|
|
27553
28547
|
session,
|
|
28548
|
+
modelId: params.modelId,
|
|
27554
28549
|
logMessage: `session/set_model passthrough (no availableModels) sessionId=${params.sessionId} modelId=${JSON.stringify(params.modelId)}`
|
|
27555
28550
|
};
|
|
27556
28551
|
}
|
|
27557
|
-
|
|
27558
|
-
if (!match) {
|
|
27559
|
-
const known = advertised.map((m) => m.modelId).join(", ");
|
|
27560
|
-
if (session.currentModel !== void 0 && session.currentModel.length > 0) {
|
|
27561
|
-
return {
|
|
27562
|
-
kind: "no_op",
|
|
27563
|
-
session,
|
|
27564
|
-
sessionId: params.sessionId,
|
|
27565
|
-
currentModel: session.currentModel,
|
|
27566
|
-
logMessage: `session/set_model no_op (resyncing client) sessionId=${params.sessionId} requested=${JSON.stringify(params.modelId)} actual=${JSON.stringify(session.currentModel)} agentId=${session.agentId} known=[${known}]`
|
|
27567
|
-
};
|
|
27568
|
-
}
|
|
28552
|
+
if (resolution.kind === "exact") {
|
|
27569
28553
|
return {
|
|
27570
|
-
kind: "
|
|
27571
|
-
|
|
27572
|
-
|
|
27573
|
-
logMessage: `session/set_model
|
|
28554
|
+
kind: "ok",
|
|
28555
|
+
session,
|
|
28556
|
+
modelId: params.modelId,
|
|
28557
|
+
logMessage: `session/set_model accepted sessionId=${params.sessionId} modelId=${JSON.stringify(params.modelId)}`
|
|
28558
|
+
};
|
|
28559
|
+
}
|
|
28560
|
+
if (resolution.kind === "resolved") {
|
|
28561
|
+
return {
|
|
28562
|
+
kind: "ok",
|
|
28563
|
+
session,
|
|
28564
|
+
modelId: resolution.modelId,
|
|
28565
|
+
logMessage: `session/set_model resolved sessionId=${params.sessionId} requested=${JSON.stringify(params.modelId)} \u2192 ${JSON.stringify(resolution.modelId)}`
|
|
28566
|
+
};
|
|
28567
|
+
}
|
|
28568
|
+
const known = advertised.map((m) => m.modelId).join(", ");
|
|
28569
|
+
const detail = resolution.kind === "ambiguous" ? `ambiguous (trailing-segment matches [${resolution.candidates.join(", ")}])` : `not in availableModels`;
|
|
28570
|
+
if (session.currentModel !== void 0 && session.currentModel.length > 0) {
|
|
28571
|
+
return {
|
|
28572
|
+
kind: "no_op",
|
|
28573
|
+
session,
|
|
28574
|
+
sessionId: params.sessionId,
|
|
28575
|
+
currentModel: session.currentModel,
|
|
28576
|
+
logMessage: `session/set_model no_op (resyncing client) sessionId=${params.sessionId} requested=${JSON.stringify(params.modelId)} ${detail} actual=${JSON.stringify(session.currentModel)} agentId=${session.agentId} known=[${known}]`
|
|
27574
28577
|
};
|
|
27575
28578
|
}
|
|
27576
28579
|
return {
|
|
27577
|
-
kind: "
|
|
27578
|
-
|
|
27579
|
-
|
|
28580
|
+
kind: "error",
|
|
28581
|
+
code: JsonRpcErrorCodes.InvalidParams,
|
|
28582
|
+
message: `model "${params.modelId}" is ${detail === "not in availableModels" ? "not in this session's availableModels" : detail} (agent ${session.agentId}); known models: ${known}`,
|
|
28583
|
+
logMessage: `session/set_model rejected sessionId=${params.sessionId} modelId=${JSON.stringify(params.modelId)} ${detail} agentId=${session.agentId} known=[${known}] (no current model to fall back to)`
|
|
27580
28584
|
};
|
|
27581
28585
|
}
|
|
27582
28586
|
function buildViewerResponseMeta(fromDisk) {
|
|
@@ -27714,8 +28718,8 @@ var McpTokenRegistry = class {
|
|
|
27714
28718
|
}
|
|
27715
28719
|
let resolveSession2;
|
|
27716
28720
|
let rejectSession;
|
|
27717
|
-
const sessionReady = new Promise((
|
|
27718
|
-
resolveSession2 =
|
|
28721
|
+
const sessionReady = new Promise((resolve9, reject) => {
|
|
28722
|
+
resolveSession2 = resolve9;
|
|
27719
28723
|
rejectSession = reject;
|
|
27720
28724
|
});
|
|
27721
28725
|
sessionReady.catch(() => void 0);
|
|
@@ -28016,8 +29020,8 @@ function registerStdinMcpRoutes(app, tokenRegistry) {
|
|
|
28016
29020
|
session = entry.session;
|
|
28017
29021
|
} else {
|
|
28018
29022
|
let timer;
|
|
28019
|
-
const timeout = new Promise((
|
|
28020
|
-
timer = setTimeout(() =>
|
|
29023
|
+
const timeout = new Promise((resolve9) => {
|
|
29024
|
+
timer = setTimeout(() => resolve9(void 0), SESSION_READY_TIMEOUT_MS);
|
|
28021
29025
|
});
|
|
28022
29026
|
const resolved = await Promise.race([
|
|
28023
29027
|
entry.sessionReady.catch(() => void 0),
|
|
@@ -28270,8 +29274,8 @@ function registerExtensionMcpRoutes(app, tokenRegistry, extensionMcp, options =
|
|
|
28270
29274
|
}
|
|
28271
29275
|
if (entry.session === void 0) {
|
|
28272
29276
|
let timer;
|
|
28273
|
-
const timeout = new Promise((
|
|
28274
|
-
timer = setTimeout(() =>
|
|
29277
|
+
const timeout = new Promise((resolve9) => {
|
|
29278
|
+
timer = setTimeout(() => resolve9(void 0), SESSION_READY_TIMEOUT_MS2);
|
|
28275
29279
|
});
|
|
28276
29280
|
const resolved = await Promise.race([
|
|
28277
29281
|
entry.sessionReady.catch(() => void 0),
|
|
@@ -28376,6 +29380,7 @@ async function startDaemon(config, serviceToken) {
|
|
|
28376
29380
|
stderrTailBytes: config.daemon.agentStderrTailBytes,
|
|
28377
29381
|
logger: agentLogger
|
|
28378
29382
|
});
|
|
29383
|
+
setToolBlobCompression(config.compressToolContent);
|
|
28379
29384
|
const extensionCommands = new ExtensionCommandRegistry();
|
|
28380
29385
|
const manager = new SessionManager(registry, spawner, void 0, {
|
|
28381
29386
|
idleTimeoutMs: config.daemon.sessionIdleTimeoutSeconds * 1e3,
|
|
@@ -28507,7 +29512,7 @@ async function startDaemon(config, serviceToken) {
|
|
|
28507
29512
|
setAgentPruneLogger(null);
|
|
28508
29513
|
await app.close();
|
|
28509
29514
|
try {
|
|
28510
|
-
|
|
29515
|
+
fs19.unlinkSync(paths.pidFile());
|
|
28511
29516
|
} catch {
|
|
28512
29517
|
}
|
|
28513
29518
|
try {
|
|
@@ -28557,7 +29562,7 @@ init_daemon_bootstrap();
|
|
|
28557
29562
|
init_hydra_version();
|
|
28558
29563
|
|
|
28559
29564
|
// src/cli/commands/log-tail.ts
|
|
28560
|
-
import * as
|
|
29565
|
+
import * as fs20 from "fs";
|
|
28561
29566
|
import * as fsp8 from "fs/promises";
|
|
28562
29567
|
async function runLogTail(logPath, argv, notFoundMessage) {
|
|
28563
29568
|
const opts = parseLogTailFlags(argv);
|
|
@@ -28581,7 +29586,7 @@ async function runLogTail(logPath, argv, notFoundMessage) {
|
|
|
28581
29586
|
process.stdout.write(`-- following ${logPath} --
|
|
28582
29587
|
`);
|
|
28583
29588
|
let pending = false;
|
|
28584
|
-
const watcher =
|
|
29589
|
+
const watcher = fs20.watch(logPath, () => {
|
|
28585
29590
|
if (pending) {
|
|
28586
29591
|
return;
|
|
28587
29592
|
}
|
|
@@ -28609,10 +29614,10 @@ async function runLogTail(logPath, argv, notFoundMessage) {
|
|
|
28609
29614
|
}
|
|
28610
29615
|
});
|
|
28611
29616
|
});
|
|
28612
|
-
await new Promise((
|
|
29617
|
+
await new Promise((resolve9) => {
|
|
28613
29618
|
const finish = () => {
|
|
28614
29619
|
watcher.close();
|
|
28615
|
-
|
|
29620
|
+
resolve9();
|
|
28616
29621
|
};
|
|
28617
29622
|
process.once("SIGINT", finish);
|
|
28618
29623
|
process.once("SIGTERM", finish);
|
|
@@ -28903,7 +29908,7 @@ init_remote_target();
|
|
|
28903
29908
|
init_remote_url();
|
|
28904
29909
|
init_session();
|
|
28905
29910
|
init_discovery();
|
|
28906
|
-
import * as
|
|
29911
|
+
import * as fs21 from "fs/promises";
|
|
28907
29912
|
import * as path16 from "path";
|
|
28908
29913
|
init_session_row();
|
|
28909
29914
|
async function runSessionsList(opts = {}) {
|
|
@@ -29026,18 +30031,19 @@ async function runSessionsRemove(id) {
|
|
|
29026
30031
|
process.stdout.write(`Removed ${id}
|
|
29027
30032
|
`);
|
|
29028
30033
|
}
|
|
29029
|
-
async function runSessionsExport(id, outPath) {
|
|
30034
|
+
async function runSessionsExport(id, outPath, tools) {
|
|
29030
30035
|
if (!id) {
|
|
29031
30036
|
process.stderr.write(
|
|
29032
|
-
"Usage: hydra-acp sessions export <session-id> [--out <file>]\n"
|
|
30037
|
+
"Usage: hydra-acp sessions export <session-id> [--out <file>] [--tools inline|summary]\n"
|
|
29033
30038
|
);
|
|
29034
30039
|
process.exit(2);
|
|
29035
30040
|
}
|
|
29036
30041
|
const config = await loadConfig();
|
|
29037
30042
|
const serviceToken = await loadServiceToken();
|
|
29038
30043
|
const baseUrl = httpBase(config.daemon.host, config.daemon.port, !!config.daemon.tls);
|
|
30044
|
+
const query = tools === "summary" ? "?tools=summary" : "";
|
|
29039
30045
|
const response = await fetch(
|
|
29040
|
-
`${baseUrl}/v1/sessions/${encodeURIComponent(id)}/export`,
|
|
30046
|
+
`${baseUrl}/v1/sessions/${encodeURIComponent(id)}/export${query}`,
|
|
29041
30047
|
{
|
|
29042
30048
|
headers: { Authorization: `Bearer ${serviceToken}` }
|
|
29043
30049
|
}
|
|
@@ -29057,8 +30063,8 @@ async function runSessionsExport(id, outPath) {
|
|
|
29057
30063
|
return;
|
|
29058
30064
|
}
|
|
29059
30065
|
const resolved = outPath === "." ? deriveFilenameFrom(response, id) : outPath;
|
|
29060
|
-
await
|
|
29061
|
-
await
|
|
30066
|
+
await fs21.mkdir(path16.dirname(path16.resolve(resolved)), { recursive: true });
|
|
30067
|
+
await fs21.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
|
|
29062
30068
|
process.stdout.write(`Wrote ${resolved}
|
|
29063
30069
|
`);
|
|
29064
30070
|
}
|
|
@@ -29105,21 +30111,21 @@ async function runSessionsTranscript(idOrFile, outPath) {
|
|
|
29105
30111
|
return;
|
|
29106
30112
|
}
|
|
29107
30113
|
const resolved = outPath === "." ? defaultName : outPath;
|
|
29108
|
-
await
|
|
29109
|
-
await
|
|
30114
|
+
await fs21.mkdir(path16.dirname(path16.resolve(resolved)), { recursive: true });
|
|
30115
|
+
await fs21.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
|
|
29110
30116
|
process.stdout.write(`Wrote ${resolved}
|
|
29111
30117
|
`);
|
|
29112
30118
|
}
|
|
29113
30119
|
async function readBundleFileIfExists(arg) {
|
|
29114
30120
|
try {
|
|
29115
|
-
const stat5 = await
|
|
30121
|
+
const stat5 = await fs21.stat(arg);
|
|
29116
30122
|
if (!stat5.isFile()) {
|
|
29117
30123
|
return null;
|
|
29118
30124
|
}
|
|
29119
30125
|
} catch {
|
|
29120
30126
|
return null;
|
|
29121
30127
|
}
|
|
29122
|
-
const text = await
|
|
30128
|
+
const text = await fs21.readFile(arg, "utf8");
|
|
29123
30129
|
try {
|
|
29124
30130
|
return { raw: JSON.parse(text) };
|
|
29125
30131
|
} catch (err) {
|
|
@@ -29148,7 +30154,7 @@ async function runSessionsImport(file, opts = {}) {
|
|
|
29148
30154
|
if (opts.cwd !== void 0) {
|
|
29149
30155
|
const resolved = path16.resolve(opts.cwd);
|
|
29150
30156
|
try {
|
|
29151
|
-
const stat5 = await
|
|
30157
|
+
const stat5 = await fs21.stat(resolved);
|
|
29152
30158
|
if (!stat5.isDirectory()) {
|
|
29153
30159
|
process.stderr.write(`--cwd ${resolved} is not a directory
|
|
29154
30160
|
`);
|
|
@@ -29165,7 +30171,7 @@ async function runSessionsImport(file, opts = {}) {
|
|
|
29165
30171
|
if (file === "-") {
|
|
29166
30172
|
body = await readStdin();
|
|
29167
30173
|
} else {
|
|
29168
|
-
body = await
|
|
30174
|
+
body = await fs21.readFile(file, "utf8");
|
|
29169
30175
|
}
|
|
29170
30176
|
let bundle;
|
|
29171
30177
|
try {
|
|
@@ -29349,7 +30355,7 @@ function splitHostPort(input, defaultPort) {
|
|
|
29349
30355
|
// src/cli/commands/sessions-info.ts
|
|
29350
30356
|
init_config();
|
|
29351
30357
|
init_service_token();
|
|
29352
|
-
import * as
|
|
30358
|
+
import * as fs22 from "fs/promises";
|
|
29353
30359
|
|
|
29354
30360
|
// src/core/history-edits.ts
|
|
29355
30361
|
init_render_update();
|
|
@@ -29393,8 +30399,8 @@ function aggregateFileEdits(history) {
|
|
|
29393
30399
|
}
|
|
29394
30400
|
}
|
|
29395
30401
|
const out = [];
|
|
29396
|
-
for (const [
|
|
29397
|
-
out.push({ path:
|
|
30402
|
+
for (const [path22, agg] of byPath) {
|
|
30403
|
+
out.push({ path: path22, hunks: agg.hunks, created: agg.created });
|
|
29398
30404
|
}
|
|
29399
30405
|
return out;
|
|
29400
30406
|
}
|
|
@@ -29464,13 +30470,13 @@ function extractRawEdits(update) {
|
|
|
29464
30470
|
if (b.type !== "diff") {
|
|
29465
30471
|
continue;
|
|
29466
30472
|
}
|
|
29467
|
-
const
|
|
29468
|
-
if (
|
|
30473
|
+
const path22 = typeof b.path === "string" ? b.path : void 0;
|
|
30474
|
+
if (path22 === void 0) {
|
|
29469
30475
|
continue;
|
|
29470
30476
|
}
|
|
29471
30477
|
const oldText = typeof b.oldText === "string" ? b.oldText : "";
|
|
29472
30478
|
const newText = typeof b.newText === "string" ? b.newText : "";
|
|
29473
|
-
out.push({ path:
|
|
30479
|
+
out.push({ path: path22, oldText, newText });
|
|
29474
30480
|
}
|
|
29475
30481
|
if (out.length > 0) {
|
|
29476
30482
|
return out;
|
|
@@ -29563,16 +30569,16 @@ function openPager(opts = {}) {
|
|
|
29563
30569
|
`);
|
|
29564
30570
|
}
|
|
29565
30571
|
});
|
|
29566
|
-
const childExited = new Promise((
|
|
29567
|
-
child.once("exit", () =>
|
|
29568
|
-
child.once("error", () =>
|
|
30572
|
+
const childExited = new Promise((resolve9) => {
|
|
30573
|
+
child.once("exit", () => resolve9());
|
|
30574
|
+
child.once("error", () => resolve9());
|
|
29569
30575
|
});
|
|
29570
30576
|
return {
|
|
29571
30577
|
stream: wrapper,
|
|
29572
30578
|
flush: async () => {
|
|
29573
30579
|
if (childStdin.writable) {
|
|
29574
|
-
await new Promise((
|
|
29575
|
-
wrapper.end(() =>
|
|
30580
|
+
await new Promise((resolve9) => {
|
|
30581
|
+
wrapper.end(() => resolve9());
|
|
29576
30582
|
});
|
|
29577
30583
|
}
|
|
29578
30584
|
await childExited;
|
|
@@ -31542,7 +32548,7 @@ function maxLen5(headerCell, values) {
|
|
|
31542
32548
|
}
|
|
31543
32549
|
|
|
31544
32550
|
// src/shim/proxy.ts
|
|
31545
|
-
import * as
|
|
32551
|
+
import * as fs23 from "fs";
|
|
31546
32552
|
init_config();
|
|
31547
32553
|
init_remote_target();
|
|
31548
32554
|
init_daemon_bootstrap();
|
|
@@ -32017,10 +33023,10 @@ function wireLog(direction, msg) {
|
|
|
32017
33023
|
wireLogChecked = true;
|
|
32018
33024
|
try {
|
|
32019
33025
|
wireLogPath = paths.shimWireLogFile();
|
|
32020
|
-
|
|
32021
|
-
const st =
|
|
33026
|
+
fs23.mkdirSync(paths.home(), { recursive: true });
|
|
33027
|
+
const st = fs23.statSync(wireLogPath, { throwIfNoEntry: false });
|
|
32022
33028
|
if (st && st.size > WIRE_LOG_MAX_BYTES) {
|
|
32023
|
-
|
|
33029
|
+
fs23.renameSync(wireLogPath, `${wireLogPath}.1`);
|
|
32024
33030
|
}
|
|
32025
33031
|
} catch {
|
|
32026
33032
|
wireLogPath = null;
|
|
@@ -32036,7 +33042,7 @@ function wireLog(direction, msg) {
|
|
|
32036
33042
|
dir: direction,
|
|
32037
33043
|
msg
|
|
32038
33044
|
}) + "\n";
|
|
32039
|
-
|
|
33045
|
+
fs23.appendFile(wireLogPath, line, () => void 0);
|
|
32040
33046
|
} catch {
|
|
32041
33047
|
}
|
|
32042
33048
|
}
|
|
@@ -32421,8 +33427,8 @@ async function runCatLoop(args) {
|
|
|
32421
33427
|
};
|
|
32422
33428
|
let exitCode = 0;
|
|
32423
33429
|
let resolveDone;
|
|
32424
|
-
const done = new Promise((
|
|
32425
|
-
resolveDone =
|
|
33430
|
+
const done = new Promise((resolve9) => {
|
|
33431
|
+
resolveDone = resolve9;
|
|
32426
33432
|
});
|
|
32427
33433
|
let settled = false;
|
|
32428
33434
|
const settle = async (code) => {
|
|
@@ -32729,11 +33735,11 @@ async function openOrAttachSession(conn, opts, useAutoStream) {
|
|
|
32729
33735
|
return created.sessionId;
|
|
32730
33736
|
}
|
|
32731
33737
|
async function openWs2(url, subprotocols) {
|
|
32732
|
-
return new Promise((
|
|
33738
|
+
return new Promise((resolve9, reject) => {
|
|
32733
33739
|
const ws = new WebSocket2(url, subprotocols);
|
|
32734
33740
|
const onOpen = () => {
|
|
32735
33741
|
ws.off("error", onError);
|
|
32736
|
-
|
|
33742
|
+
resolve9(ws);
|
|
32737
33743
|
};
|
|
32738
33744
|
const onError = (err) => {
|
|
32739
33745
|
ws.off("open", onOpen);
|
|
@@ -33010,7 +34016,8 @@ async function main() {
|
|
|
33010
34016
|
}
|
|
33011
34017
|
if (sub === "export") {
|
|
33012
34018
|
const out = resolveOption(flags, "out");
|
|
33013
|
-
|
|
34019
|
+
const tools = resolveOption(flags, "tools");
|
|
34020
|
+
await runSessionsExport(positional[2], out, tools);
|
|
33014
34021
|
return;
|
|
33015
34022
|
}
|
|
33016
34023
|
if (sub === "transcript") {
|
|
@@ -33341,7 +34348,7 @@ function readVersion() {
|
|
|
33341
34348
|
try {
|
|
33342
34349
|
const here = dirname8(fileURLToPath2(import.meta.url));
|
|
33343
34350
|
const pkg = JSON.parse(
|
|
33344
|
-
readFileSync2(
|
|
34351
|
+
readFileSync2(resolve8(here, "../package.json"), "utf8")
|
|
33345
34352
|
);
|
|
33346
34353
|
return pkg.version ?? "unknown";
|
|
33347
34354
|
} catch {
|