@hydra-acp/cli 0.1.52 → 0.1.53
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/README.md +4 -2
- package/dist/cli.js +1460 -385
- package/dist/index.d.ts +37 -2
- package/dist/index.js +558 -239
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -168,8 +168,77 @@ var init_service_token = __esm({
|
|
|
168
168
|
}
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
-
// src/core/
|
|
171
|
+
// src/core/json-store.ts
|
|
172
172
|
import * as fs2 from "fs/promises";
|
|
173
|
+
import * as fsSync from "fs";
|
|
174
|
+
import { randomBytes } from "crypto";
|
|
175
|
+
async function writeJsonAtomic(filePath, data, opts = {}) {
|
|
176
|
+
const pretty = opts.pretty ?? true;
|
|
177
|
+
const body = (pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data)) + "\n";
|
|
178
|
+
await writeFileAtomic(filePath, body, opts);
|
|
179
|
+
}
|
|
180
|
+
async function writeFileAtomic(filePath, body, opts = {}) {
|
|
181
|
+
const dir = dirname(filePath);
|
|
182
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
183
|
+
const tmp = `${filePath}.tmp-${process.pid}-${randSuffix()}`;
|
|
184
|
+
try {
|
|
185
|
+
const writeOpts = {
|
|
186
|
+
encoding: "utf8"
|
|
187
|
+
};
|
|
188
|
+
if (opts.mode !== void 0) {
|
|
189
|
+
writeOpts.mode = opts.mode;
|
|
190
|
+
}
|
|
191
|
+
await fs2.writeFile(tmp, body, writeOpts);
|
|
192
|
+
await fs2.rename(tmp, filePath);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
await fs2.unlink(tmp).catch(() => void 0);
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
if (opts.mode !== void 0) {
|
|
198
|
+
try {
|
|
199
|
+
fsSync.chmodSync(filePath, opts.mode);
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function readJsonSafe(filePath) {
|
|
205
|
+
let raw;
|
|
206
|
+
try {
|
|
207
|
+
raw = await fs2.readFile(filePath, "utf8");
|
|
208
|
+
} catch (err) {
|
|
209
|
+
const e = err;
|
|
210
|
+
if (e.code === "ENOENT") {
|
|
211
|
+
return void 0;
|
|
212
|
+
}
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
if (raw.trim().length === 0) {
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
return JSON.parse(raw);
|
|
220
|
+
} catch {
|
|
221
|
+
return void 0;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function dirname(p) {
|
|
225
|
+
const slash = p.lastIndexOf("/");
|
|
226
|
+
if (slash <= 0) {
|
|
227
|
+
return ".";
|
|
228
|
+
}
|
|
229
|
+
return p.slice(0, slash);
|
|
230
|
+
}
|
|
231
|
+
function randSuffix() {
|
|
232
|
+
return randomBytes(4).toString("hex");
|
|
233
|
+
}
|
|
234
|
+
var init_json_store = __esm({
|
|
235
|
+
"src/core/json-store.ts"() {
|
|
236
|
+
"use strict";
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// src/core/config.ts
|
|
241
|
+
import * as fs3 from "fs/promises";
|
|
173
242
|
import { homedir as homedir2 } from "os";
|
|
174
243
|
import { z } from "zod";
|
|
175
244
|
function extensionList(config) {
|
|
@@ -185,17 +254,8 @@ function transformerList(config) {
|
|
|
185
254
|
}));
|
|
186
255
|
}
|
|
187
256
|
async function readConfigFile() {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
raw = await fs2.readFile(paths.config(), "utf8");
|
|
191
|
-
} catch (err) {
|
|
192
|
-
const e = err;
|
|
193
|
-
if (e.code === "ENOENT") {
|
|
194
|
-
return {};
|
|
195
|
-
}
|
|
196
|
-
throw err;
|
|
197
|
-
}
|
|
198
|
-
return JSON.parse(raw);
|
|
257
|
+
const parsed = await readJsonSafe(paths.config());
|
|
258
|
+
return parsed ?? {};
|
|
199
259
|
}
|
|
200
260
|
async function migrateLegacyAuthToken() {
|
|
201
261
|
const raw = await readConfigFile();
|
|
@@ -206,7 +266,7 @@ async function migrateLegacyAuthToken() {
|
|
|
206
266
|
}
|
|
207
267
|
let tokenFileExists = false;
|
|
208
268
|
try {
|
|
209
|
-
await
|
|
269
|
+
await fs3.access(paths.authToken());
|
|
210
270
|
tokenFileExists = true;
|
|
211
271
|
} catch (err) {
|
|
212
272
|
const e = err;
|
|
@@ -224,10 +284,7 @@ async function migrateLegacyAuthToken() {
|
|
|
224
284
|
if (Object.keys(daemon).length === 0) {
|
|
225
285
|
delete raw.daemon;
|
|
226
286
|
}
|
|
227
|
-
await
|
|
228
|
-
encoding: "utf8",
|
|
229
|
-
mode: 384
|
|
230
|
-
});
|
|
287
|
+
await writeJsonAtomic(paths.config(), raw, { mode: 384 });
|
|
231
288
|
process.stderr.write(
|
|
232
289
|
`hydra-acp: migrated auth token from ${paths.config()} to ${paths.authToken()}.
|
|
233
290
|
`
|
|
@@ -255,6 +312,7 @@ var init_config = __esm({
|
|
|
255
312
|
"use strict";
|
|
256
313
|
init_paths();
|
|
257
314
|
init_service_token();
|
|
315
|
+
init_json_store();
|
|
258
316
|
REGISTRY_URL_DEFAULT = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
|
|
259
317
|
TlsConfig = z.object({
|
|
260
318
|
cert: z.string(),
|
|
@@ -343,7 +401,22 @@ var init_config = __esm({
|
|
|
343
401
|
// shared across all sessions; it's append-only on disk, so long-lived
|
|
344
402
|
// installs can grow past this — it's enforced at load time and per
|
|
345
403
|
// append in memory.
|
|
346
|
-
promptHistoryMaxEntries: z.number().int().positive().default(2e3)
|
|
404
|
+
promptHistoryMaxEntries: z.number().int().positive().default(2e3),
|
|
405
|
+
// How edit-style tool calls (Edit, Write, str_replace) render in
|
|
406
|
+
// scrollback, *in addition to* the normal tool row inside the tools
|
|
407
|
+
// block.
|
|
408
|
+
// "none" — nothing extra; the collapsed tool row is the only signal.
|
|
409
|
+
// "edit" (default) — a one-line scrollback mark naming the file
|
|
410
|
+
// that was touched, so the user can scroll back and see which
|
|
411
|
+
// files moved without expanding the tools block. Suppressed on
|
|
412
|
+
// tool-only turns (no agent prose) since the marks would only
|
|
413
|
+
// duplicate the still-visible tool rows.
|
|
414
|
+
// "diff" — same mark plus a syntax-highlighted unified diff body,
|
|
415
|
+
// Claude Code's Update(file) look.
|
|
416
|
+
// The diff payload is extracted from the ACP wire (content[]
|
|
417
|
+
// type:"diff" entries, falling back to rawInput shapes), so any agent
|
|
418
|
+
// that emits one of those shapes gets the treatment.
|
|
419
|
+
showFileUpdates: z.enum(["none", "edit", "diff"]).default("edit")
|
|
347
420
|
});
|
|
348
421
|
ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
|
|
349
422
|
ExtensionBody = z.object({
|
|
@@ -398,7 +471,8 @@ var init_config = __esm({
|
|
|
398
471
|
progressIndicator: true,
|
|
399
472
|
defaultEnterAction: "amend",
|
|
400
473
|
showThoughts: true,
|
|
401
|
-
promptHistoryMaxEntries: 2e3
|
|
474
|
+
promptHistoryMaxEntries: 2e3,
|
|
475
|
+
showFileUpdates: "edit"
|
|
402
476
|
})
|
|
403
477
|
});
|
|
404
478
|
}
|
|
@@ -480,8 +554,6 @@ var init_remote_url = __esm({
|
|
|
480
554
|
});
|
|
481
555
|
|
|
482
556
|
// src/core/remotes-store.ts
|
|
483
|
-
import * as fs3 from "fs/promises";
|
|
484
|
-
import * as fsSync from "fs";
|
|
485
557
|
function hostKey(host, port) {
|
|
486
558
|
return `${host}:${port}`;
|
|
487
559
|
}
|
|
@@ -504,21 +576,9 @@ function splitKey(key) {
|
|
|
504
576
|
}
|
|
505
577
|
return { host, port };
|
|
506
578
|
}
|
|
507
|
-
async function
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
raw = await fs3.readFile(paths.remotes(), "utf8");
|
|
511
|
-
} catch (err) {
|
|
512
|
-
const e = err;
|
|
513
|
-
if (e.code === "ENOENT") {
|
|
514
|
-
return { version: 1, entries: {} };
|
|
515
|
-
}
|
|
516
|
-
throw err;
|
|
517
|
-
}
|
|
518
|
-
let parsed;
|
|
519
|
-
try {
|
|
520
|
-
parsed = JSON.parse(raw);
|
|
521
|
-
} catch {
|
|
579
|
+
async function readFile3() {
|
|
580
|
+
const parsed = await readJsonSafe(paths.remotes());
|
|
581
|
+
if (parsed === void 0) {
|
|
522
582
|
return { version: 1, entries: {} };
|
|
523
583
|
}
|
|
524
584
|
return normalise(parsed);
|
|
@@ -549,32 +609,22 @@ function normalise(raw) {
|
|
|
549
609
|
}
|
|
550
610
|
return { version: 1, entries: out };
|
|
551
611
|
}
|
|
552
|
-
async function
|
|
553
|
-
|
|
554
|
-
await fs3.mkdir(dir, { recursive: true });
|
|
555
|
-
const tmp = paths.remotes() + ".tmp";
|
|
556
|
-
await fs3.writeFile(tmp, JSON.stringify(data, null, 2) + "\n", {
|
|
557
|
-
encoding: "utf8",
|
|
558
|
-
mode: 384
|
|
559
|
-
});
|
|
560
|
-
await fs3.rename(tmp, paths.remotes());
|
|
561
|
-
try {
|
|
562
|
-
fsSync.chmodSync(paths.remotes(), 384);
|
|
563
|
-
} catch {
|
|
564
|
-
}
|
|
612
|
+
async function writeFile3(data) {
|
|
613
|
+
await writeJsonAtomic(paths.remotes(), data, { mode: 384 });
|
|
565
614
|
}
|
|
566
615
|
var RemotesStore;
|
|
567
616
|
var init_remotes_store = __esm({
|
|
568
617
|
"src/core/remotes-store.ts"() {
|
|
569
618
|
"use strict";
|
|
570
619
|
init_paths();
|
|
620
|
+
init_json_store();
|
|
571
621
|
RemotesStore = class _RemotesStore {
|
|
572
622
|
data;
|
|
573
623
|
constructor(data) {
|
|
574
624
|
this.data = data;
|
|
575
625
|
}
|
|
576
626
|
static async load() {
|
|
577
|
-
const data = await
|
|
627
|
+
const data = await readFile3();
|
|
578
628
|
const now = Date.now();
|
|
579
629
|
const filtered = {};
|
|
580
630
|
let dropped = false;
|
|
@@ -587,7 +637,7 @@ var init_remotes_store = __esm({
|
|
|
587
637
|
}
|
|
588
638
|
const final = { version: 1, entries: filtered };
|
|
589
639
|
if (dropped) {
|
|
590
|
-
await
|
|
640
|
+
await writeFile3(final);
|
|
591
641
|
}
|
|
592
642
|
return new _RemotesStore(final);
|
|
593
643
|
}
|
|
@@ -603,7 +653,7 @@ var init_remotes_store = __esm({
|
|
|
603
653
|
}
|
|
604
654
|
async set(host, port, credential) {
|
|
605
655
|
this.data.entries[hostKey(host, port)] = credential;
|
|
606
|
-
await
|
|
656
|
+
await writeFile3(this.data);
|
|
607
657
|
}
|
|
608
658
|
async delete(host, port) {
|
|
609
659
|
const key = hostKey(host, port);
|
|
@@ -611,7 +661,7 @@ var init_remotes_store = __esm({
|
|
|
611
661
|
return false;
|
|
612
662
|
}
|
|
613
663
|
delete this.data.entries[key];
|
|
614
|
-
await
|
|
664
|
+
await writeFile3(this.data);
|
|
615
665
|
return true;
|
|
616
666
|
}
|
|
617
667
|
list() {
|
|
@@ -1172,6 +1222,11 @@ var init_types = __esm({
|
|
|
1172
1222
|
importedFromUpstreamSessionId: z3.string().optional(),
|
|
1173
1223
|
// Set when this session was spawned as a child by a transformer.
|
|
1174
1224
|
parentSessionId: z3.string().optional(),
|
|
1225
|
+
// Local-fork breadcrumbs set by hydra-acp/fork_session. Distinct from
|
|
1226
|
+
// the imported* family above: a fork is a local branch off another
|
|
1227
|
+
// local session, an import is a cross-machine takeover.
|
|
1228
|
+
forkedFromSessionId: z3.string().optional(),
|
|
1229
|
+
forkedFromMessageId: z3.string().optional(),
|
|
1175
1230
|
// clientInfo from the process that issued session/new. Lets list views
|
|
1176
1231
|
// hide cat-style ancillary sessions by default while letting an
|
|
1177
1232
|
// override flag surface them.
|
|
@@ -2386,6 +2441,8 @@ var init_session = __esm({
|
|
|
2386
2441
|
agentCapabilities;
|
|
2387
2442
|
agentArgs;
|
|
2388
2443
|
parentSessionId;
|
|
2444
|
+
forkedFromSessionId;
|
|
2445
|
+
forkedFromMessageId;
|
|
2389
2446
|
originatingClient;
|
|
2390
2447
|
title;
|
|
2391
2448
|
// Snapshot state delivered to attaching clients via the attach
|
|
@@ -2414,6 +2471,13 @@ var init_session = __esm({
|
|
|
2414
2471
|
// enqueue) and leave the file out of sync with in-memory state.
|
|
2415
2472
|
queueWriteChain = Promise.resolve();
|
|
2416
2473
|
closed = false;
|
|
2474
|
+
// Set true at the start of close() / markClosed before any await yields.
|
|
2475
|
+
// drainQueue checks this between iterations and bails out, so a queued
|
|
2476
|
+
// entry can't be promoted to currentEntry (with its prompt_received and
|
|
2477
|
+
// synthesized turn_complete(interrupted)) while the session is tearing
|
|
2478
|
+
// down. markClosed sweeps the remaining queue with the normal abandoned
|
|
2479
|
+
// / cancelled handling.
|
|
2480
|
+
closing = false;
|
|
2417
2481
|
closeHandlers = [];
|
|
2418
2482
|
titleHandlers = [];
|
|
2419
2483
|
// Subscribers notified after every entry that's actually persisted to
|
|
@@ -2541,6 +2605,8 @@ var init_session = __esm({
|
|
|
2541
2605
|
this.agentCapabilities = init.agentCapabilities;
|
|
2542
2606
|
this.agentArgs = init.agentArgs;
|
|
2543
2607
|
this.parentSessionId = init.parentSessionId;
|
|
2608
|
+
this.forkedFromSessionId = init.forkedFromSessionId;
|
|
2609
|
+
this.forkedFromMessageId = init.forkedFromMessageId;
|
|
2544
2610
|
this.originatingClient = init.originatingClient;
|
|
2545
2611
|
this.title = init.title;
|
|
2546
2612
|
this.currentModel = init.currentModel;
|
|
@@ -3649,6 +3715,7 @@ var init_session = __esm({
|
|
|
3649
3715
|
if (this.closed) {
|
|
3650
3716
|
return;
|
|
3651
3717
|
}
|
|
3718
|
+
this.closing = true;
|
|
3652
3719
|
this.logger?.info(
|
|
3653
3720
|
`session ${this.sessionId} closing deleteRecord=${opts.deleteRecord ?? false} regenTitle=${opts.regenTitle ?? false}`
|
|
3654
3721
|
);
|
|
@@ -4780,21 +4847,22 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
4780
4847
|
if (this.closed) {
|
|
4781
4848
|
return;
|
|
4782
4849
|
}
|
|
4850
|
+
this.closing = true;
|
|
4783
4851
|
this.closed = true;
|
|
4784
4852
|
this.cancelIdleTimer();
|
|
4785
4853
|
if (this.extensionCommandsUnsub) {
|
|
4786
4854
|
this.extensionCommandsUnsub();
|
|
4787
4855
|
this.extensionCommandsUnsub = void 0;
|
|
4788
4856
|
}
|
|
4789
|
-
if (this.currentEntry?.kind === "user") {
|
|
4857
|
+
if (this.currentEntry?.kind === "user" && !this.recentlyTerminal.has(this.currentEntry.messageId)) {
|
|
4790
4858
|
this.broadcastTurnComplete(
|
|
4791
4859
|
this.currentEntry.clientId,
|
|
4792
4860
|
{ stopReason: "interrupted" },
|
|
4793
4861
|
this.currentEntry.messageId,
|
|
4794
4862
|
this.currentEntry.wasAmend
|
|
4795
4863
|
);
|
|
4796
|
-
this.currentEntry = void 0;
|
|
4797
4864
|
}
|
|
4865
|
+
this.currentEntry = void 0;
|
|
4798
4866
|
const stranded = this.promptQueue;
|
|
4799
4867
|
this.promptQueue = [];
|
|
4800
4868
|
for (const entry of stranded) {
|
|
@@ -5123,6 +5191,9 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
5123
5191
|
await new Promise((r) => setImmediate(r));
|
|
5124
5192
|
try {
|
|
5125
5193
|
while (this.promptQueue.length > 0) {
|
|
5194
|
+
if (this.closing) {
|
|
5195
|
+
break;
|
|
5196
|
+
}
|
|
5126
5197
|
const next = this.promptQueue.shift();
|
|
5127
5198
|
if (!next) {
|
|
5128
5199
|
break;
|
|
@@ -5557,6 +5628,51 @@ function isExitPlanModeTool(name) {
|
|
|
5557
5628
|
const normalised = name.toLowerCase().replace(/[_\s-]/g, "");
|
|
5558
5629
|
return normalised === "exitplanmode";
|
|
5559
5630
|
}
|
|
5631
|
+
function extractEditDiff(u) {
|
|
5632
|
+
const content = u.content;
|
|
5633
|
+
if (Array.isArray(content)) {
|
|
5634
|
+
for (const block of content) {
|
|
5635
|
+
if (!block || typeof block !== "object") {
|
|
5636
|
+
continue;
|
|
5637
|
+
}
|
|
5638
|
+
const b = block;
|
|
5639
|
+
if (b.type !== "diff") {
|
|
5640
|
+
continue;
|
|
5641
|
+
}
|
|
5642
|
+
const oldText = typeof b.oldText === "string" ? b.oldText : void 0;
|
|
5643
|
+
const newText = typeof b.newText === "string" ? b.newText : void 0;
|
|
5644
|
+
if (oldText === void 0 && newText === void 0) {
|
|
5645
|
+
continue;
|
|
5646
|
+
}
|
|
5647
|
+
const path20 = typeof b.path === "string" ? b.path : void 0;
|
|
5648
|
+
return {
|
|
5649
|
+
...path20 !== void 0 ? { path: path20 } : {},
|
|
5650
|
+
oldText: oldText ?? "",
|
|
5651
|
+
newText: newText ?? ""
|
|
5652
|
+
};
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
const rawInput = u.rawInput;
|
|
5656
|
+
if (rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)) {
|
|
5657
|
+
const r = rawInput;
|
|
5658
|
+
const filePath = typeof r.file_path === "string" ? r.file_path : typeof r.path === "string" ? r.path : void 0;
|
|
5659
|
+
if (typeof r.old_string === "string" && typeof r.new_string === "string") {
|
|
5660
|
+
return {
|
|
5661
|
+
...filePath !== void 0 ? { path: filePath } : {},
|
|
5662
|
+
oldText: r.old_string,
|
|
5663
|
+
newText: r.new_string
|
|
5664
|
+
};
|
|
5665
|
+
}
|
|
5666
|
+
if (typeof r.content === "string") {
|
|
5667
|
+
return {
|
|
5668
|
+
...filePath !== void 0 ? { path: filePath } : {},
|
|
5669
|
+
oldText: "",
|
|
5670
|
+
newText: r.content
|
|
5671
|
+
};
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
return null;
|
|
5675
|
+
}
|
|
5560
5676
|
function readExitPlanMarkdown(u) {
|
|
5561
5677
|
const rawInput = u.rawInput;
|
|
5562
5678
|
if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) {
|
|
@@ -5596,6 +5712,10 @@ function mapToolCall(u) {
|
|
|
5596
5712
|
if (rawKind !== void 0) {
|
|
5597
5713
|
event.rawKind = rawKind;
|
|
5598
5714
|
}
|
|
5715
|
+
const diff = extractEditDiff(u);
|
|
5716
|
+
if (diff !== null) {
|
|
5717
|
+
event.editDiff = diff;
|
|
5718
|
+
}
|
|
5599
5719
|
return event;
|
|
5600
5720
|
}
|
|
5601
5721
|
function mapToolCallUpdate(u) {
|
|
@@ -5606,7 +5726,8 @@ function mapToolCallUpdate(u) {
|
|
|
5606
5726
|
const rawTitle = readString(u, "title");
|
|
5607
5727
|
const title = rawTitle !== void 0 ? sanitizeSingleLine(rawTitle) : void 0;
|
|
5608
5728
|
const status = readString(u, "status");
|
|
5609
|
-
const
|
|
5729
|
+
const diff = extractEditDiff(u);
|
|
5730
|
+
const meaningful = title !== void 0 || diff !== null || status === "completed" || status === "failed" || status === "rejected" || status === "cancelled";
|
|
5610
5731
|
if (!meaningful) {
|
|
5611
5732
|
return null;
|
|
5612
5733
|
}
|
|
@@ -5629,6 +5750,9 @@ function mapToolCallUpdate(u) {
|
|
|
5629
5750
|
if (status !== void 0) {
|
|
5630
5751
|
event.status = status;
|
|
5631
5752
|
}
|
|
5753
|
+
if (diff !== null) {
|
|
5754
|
+
event.editDiff = diff;
|
|
5755
|
+
}
|
|
5632
5756
|
if (status === "failed") {
|
|
5633
5757
|
const errorText = extractToolFailureText(u);
|
|
5634
5758
|
if (errorText !== null) {
|
|
@@ -5949,10 +6073,37 @@ async function listSessions(target, opts = {}, fetchImpl = fetch) {
|
|
|
5949
6073
|
title: s.title,
|
|
5950
6074
|
importedFromMachine: s.importedFromMachine,
|
|
5951
6075
|
importedFromUpstreamSessionId: s.importedFromUpstreamSessionId,
|
|
6076
|
+
forkedFromSessionId: s.forkedFromSessionId,
|
|
6077
|
+
forkedFromMessageId: s.forkedFromMessageId,
|
|
5952
6078
|
busy: s.busy,
|
|
5953
6079
|
originatingClient: s.originatingClient
|
|
5954
6080
|
}));
|
|
5955
6081
|
}
|
|
6082
|
+
async function forkSession(target, id, opts = {}, fetchImpl = fetch) {
|
|
6083
|
+
const response = await fetchImpl(
|
|
6084
|
+
`${target.baseUrl}/v1/sessions/${id}/fork`,
|
|
6085
|
+
{
|
|
6086
|
+
method: "POST",
|
|
6087
|
+
headers: {
|
|
6088
|
+
Authorization: `Bearer ${target.token}`,
|
|
6089
|
+
"Content-Type": "application/json"
|
|
6090
|
+
},
|
|
6091
|
+
body: JSON.stringify(opts)
|
|
6092
|
+
}
|
|
6093
|
+
);
|
|
6094
|
+
if (!response.ok) {
|
|
6095
|
+
let detail = "";
|
|
6096
|
+
try {
|
|
6097
|
+
const body = await response.json();
|
|
6098
|
+
if (typeof body.error === "string") {
|
|
6099
|
+
detail = `: ${body.error}`;
|
|
6100
|
+
}
|
|
6101
|
+
} catch {
|
|
6102
|
+
}
|
|
6103
|
+
throw new Error(`fork failed (HTTP ${response.status})${detail}`);
|
|
6104
|
+
}
|
|
6105
|
+
return await response.json();
|
|
6106
|
+
}
|
|
5956
6107
|
async function killSession(target, id, fetchImpl = fetch) {
|
|
5957
6108
|
const response = await fetchImpl(`${target.baseUrl}/v1/sessions/${id}/kill`, {
|
|
5958
6109
|
method: "POST",
|
|
@@ -6584,13 +6735,25 @@ function applyInlineMarkup(text, opts) {
|
|
|
6584
6735
|
s = s.replace(/`([^`]+)`/g, `${codeOpen}$1${codeReset}`);
|
|
6585
6736
|
return s;
|
|
6586
6737
|
}
|
|
6738
|
+
function headingInlineOptsFor(style) {
|
|
6739
|
+
switch (style) {
|
|
6740
|
+
case "heading-1":
|
|
6741
|
+
return { codeOpen: "^C", boldReset: "^+^Y", codeReset: "^+^Y" };
|
|
6742
|
+
case "heading-2":
|
|
6743
|
+
return { codeOpen: "^Y", boldReset: "^+^C", codeReset: "^+^C" };
|
|
6744
|
+
case "heading-3":
|
|
6745
|
+
default:
|
|
6746
|
+
return { codeOpen: "^C", boldReset: "^:^+", codeReset: "^:^+" };
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6587
6749
|
function parseMarkdown(text, opts) {
|
|
6588
6750
|
const {
|
|
6589
6751
|
proseStyle,
|
|
6590
6752
|
highlightCode,
|
|
6591
6753
|
prefixStyle,
|
|
6592
6754
|
firstPrefix = " ",
|
|
6593
|
-
inlineOpts
|
|
6755
|
+
inlineOpts,
|
|
6756
|
+
maxWidth
|
|
6594
6757
|
} = opts;
|
|
6595
6758
|
const out = [];
|
|
6596
6759
|
const lines = text.split("\n");
|
|
@@ -6657,7 +6820,12 @@ function parseMarkdown(text, opts) {
|
|
|
6657
6820
|
const level = heading[1].length;
|
|
6658
6821
|
const headingText = heading[2] ?? "";
|
|
6659
6822
|
const headingStyle = highlightCode ? level === 1 ? "heading-1" : level === 2 ? "heading-2" : "heading-3" : proseStyle;
|
|
6660
|
-
|
|
6823
|
+
const headingInlineOpts = highlightCode ? headingInlineOptsFor(headingStyle) : inlineOpts;
|
|
6824
|
+
line(
|
|
6825
|
+
applyInlineMarkup(headingText, headingInlineOpts),
|
|
6826
|
+
headingStyle,
|
|
6827
|
+
nextPrefix()
|
|
6828
|
+
);
|
|
6661
6829
|
continue;
|
|
6662
6830
|
}
|
|
6663
6831
|
const next = lines[i + 1];
|
|
@@ -6669,7 +6837,7 @@ function parseMarkdown(text, opts) {
|
|
|
6669
6837
|
body.push(parseTableRow(lines[j]));
|
|
6670
6838
|
j++;
|
|
6671
6839
|
}
|
|
6672
|
-
const tableLines = formatTable(header, body);
|
|
6840
|
+
const tableLines = formatTable(header, body, maxWidth);
|
|
6673
6841
|
for (const tl of tableLines) {
|
|
6674
6842
|
if (prefixStyle !== void 0)
|
|
6675
6843
|
tl.prefixStyle = prefixStyle;
|
|
@@ -6712,8 +6880,12 @@ function parseMarkdown(text, opts) {
|
|
|
6712
6880
|
flushCode();
|
|
6713
6881
|
return out;
|
|
6714
6882
|
}
|
|
6715
|
-
function parseAgentMarkdown(text) {
|
|
6716
|
-
return parseMarkdown(text, {
|
|
6883
|
+
function parseAgentMarkdown(text, opts) {
|
|
6884
|
+
return parseMarkdown(text, {
|
|
6885
|
+
proseStyle: "agent",
|
|
6886
|
+
highlightCode: true,
|
|
6887
|
+
maxWidth: opts?.maxWidth
|
|
6888
|
+
});
|
|
6717
6889
|
}
|
|
6718
6890
|
function parseThoughtMarkdown(text) {
|
|
6719
6891
|
return parseMarkdown(text, {
|
|
@@ -6744,45 +6916,238 @@ function isTableSeparatorLine(line) {
|
|
|
6744
6916
|
}
|
|
6745
6917
|
return cells.every((c) => /^:?-+:?$/.test(c));
|
|
6746
6918
|
}
|
|
6747
|
-
function cellVisibleWidth(cell
|
|
6748
|
-
if (asLiteral) {
|
|
6749
|
-
return stringWidth(cell);
|
|
6750
|
-
}
|
|
6919
|
+
function cellVisibleWidth(cell) {
|
|
6751
6920
|
const visible = cell.replace(/\*\*(.+?)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1");
|
|
6752
6921
|
return stringWidth(visible);
|
|
6753
6922
|
}
|
|
6754
|
-
function
|
|
6923
|
+
function tokenizeCell(cell) {
|
|
6924
|
+
const atoms = [];
|
|
6925
|
+
let i = 0;
|
|
6926
|
+
while (i < cell.length) {
|
|
6927
|
+
const ch = cell[i];
|
|
6928
|
+
if (ch === " " || ch === " ") {
|
|
6929
|
+
let j2 = i;
|
|
6930
|
+
while (j2 < cell.length && (cell[j2] === " " || cell[j2] === " ")) {
|
|
6931
|
+
j2++;
|
|
6932
|
+
}
|
|
6933
|
+
const text = cell.slice(i, j2);
|
|
6934
|
+
atoms.push({ text, isWS: true, width: stringWidth(text) });
|
|
6935
|
+
i = j2;
|
|
6936
|
+
continue;
|
|
6937
|
+
}
|
|
6938
|
+
let word = "";
|
|
6939
|
+
let j = i;
|
|
6940
|
+
while (j < cell.length) {
|
|
6941
|
+
const c = cell[j];
|
|
6942
|
+
if (c === " " || c === " ") {
|
|
6943
|
+
break;
|
|
6944
|
+
}
|
|
6945
|
+
if (cell[j] === "*" && cell[j + 1] === "*") {
|
|
6946
|
+
const close = cell.indexOf("**", j + 2);
|
|
6947
|
+
if (close === -1) {
|
|
6948
|
+
word += "**";
|
|
6949
|
+
j += 2;
|
|
6950
|
+
} else {
|
|
6951
|
+
word += cell.slice(j, close + 2);
|
|
6952
|
+
j = close + 2;
|
|
6953
|
+
}
|
|
6954
|
+
continue;
|
|
6955
|
+
}
|
|
6956
|
+
if (c === "`") {
|
|
6957
|
+
const close = cell.indexOf("`", j + 1);
|
|
6958
|
+
if (close === -1) {
|
|
6959
|
+
word += "`";
|
|
6960
|
+
j += 1;
|
|
6961
|
+
} else {
|
|
6962
|
+
word += cell.slice(j, close + 1);
|
|
6963
|
+
j = close + 1;
|
|
6964
|
+
}
|
|
6965
|
+
continue;
|
|
6966
|
+
}
|
|
6967
|
+
word += c;
|
|
6968
|
+
j += 1;
|
|
6969
|
+
}
|
|
6970
|
+
atoms.push({ text: word, isWS: false, width: cellVisibleWidth(word) });
|
|
6971
|
+
i = j;
|
|
6972
|
+
}
|
|
6973
|
+
return atoms;
|
|
6974
|
+
}
|
|
6975
|
+
function hardBreak(text, width) {
|
|
6976
|
+
const out = [];
|
|
6977
|
+
let current = "";
|
|
6978
|
+
let currentWidth = 0;
|
|
6979
|
+
for (const ch of text) {
|
|
6980
|
+
const w = stringWidth(ch);
|
|
6981
|
+
if (currentWidth > 0 && currentWidth + w > width) {
|
|
6982
|
+
out.push(current);
|
|
6983
|
+
current = ch;
|
|
6984
|
+
currentWidth = w;
|
|
6985
|
+
} else {
|
|
6986
|
+
current += ch;
|
|
6987
|
+
currentWidth += w;
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6990
|
+
if (current.length > 0) {
|
|
6991
|
+
out.push(current);
|
|
6992
|
+
}
|
|
6993
|
+
return out;
|
|
6994
|
+
}
|
|
6995
|
+
function wrapCellAtoms(atoms, width) {
|
|
6996
|
+
if (width <= 0) {
|
|
6997
|
+
return atoms.length === 0 ? [""] : [atoms.map((a) => a.text).join("")];
|
|
6998
|
+
}
|
|
6999
|
+
const lines = [];
|
|
7000
|
+
let current = "";
|
|
7001
|
+
let currentWidth = 0;
|
|
7002
|
+
const flush = () => {
|
|
7003
|
+
lines.push(current.replace(/[ \t]+$/, ""));
|
|
7004
|
+
current = "";
|
|
7005
|
+
currentWidth = 0;
|
|
7006
|
+
};
|
|
7007
|
+
for (const atom of atoms) {
|
|
7008
|
+
if (atom.isWS) {
|
|
7009
|
+
if (currentWidth === 0) {
|
|
7010
|
+
continue;
|
|
7011
|
+
}
|
|
7012
|
+
current += atom.text;
|
|
7013
|
+
currentWidth += atom.width;
|
|
7014
|
+
continue;
|
|
7015
|
+
}
|
|
7016
|
+
if (atom.width > width) {
|
|
7017
|
+
if (currentWidth > 0) {
|
|
7018
|
+
flush();
|
|
7019
|
+
}
|
|
7020
|
+
const hasMarkup = atom.text.includes("**") || atom.text.includes("`");
|
|
7021
|
+
if (hasMarkup) {
|
|
7022
|
+
lines.push(atom.text);
|
|
7023
|
+
} else {
|
|
7024
|
+
const fragments = hardBreak(atom.text, width);
|
|
7025
|
+
for (let k = 0; k < fragments.length - 1; k++) {
|
|
7026
|
+
lines.push(fragments[k]);
|
|
7027
|
+
}
|
|
7028
|
+
const last = fragments[fragments.length - 1] ?? "";
|
|
7029
|
+
current = last;
|
|
7030
|
+
currentWidth = stringWidth(last);
|
|
7031
|
+
}
|
|
7032
|
+
continue;
|
|
7033
|
+
}
|
|
7034
|
+
if (currentWidth === 0) {
|
|
7035
|
+
current = atom.text;
|
|
7036
|
+
currentWidth = atom.width;
|
|
7037
|
+
continue;
|
|
7038
|
+
}
|
|
7039
|
+
if (currentWidth + atom.width > width) {
|
|
7040
|
+
flush();
|
|
7041
|
+
current = atom.text;
|
|
7042
|
+
currentWidth = atom.width;
|
|
7043
|
+
} else {
|
|
7044
|
+
current += atom.text;
|
|
7045
|
+
currentWidth += atom.width;
|
|
7046
|
+
}
|
|
7047
|
+
}
|
|
7048
|
+
if (current.length > 0 || lines.length === 0) {
|
|
7049
|
+
flush();
|
|
7050
|
+
}
|
|
7051
|
+
return lines;
|
|
7052
|
+
}
|
|
7053
|
+
function distributeColumnWidths(natural, budget) {
|
|
7054
|
+
const cols = natural.length;
|
|
7055
|
+
const total = natural.reduce((a, b) => a + b, 0);
|
|
7056
|
+
if (total <= budget) {
|
|
7057
|
+
return natural.slice();
|
|
7058
|
+
}
|
|
7059
|
+
const widths = natural.map((n) => Math.min(n, TABLE_MIN_COL));
|
|
7060
|
+
let used = widths.reduce((a, b) => a + b, 0);
|
|
7061
|
+
if (used >= budget) {
|
|
7062
|
+
return widths;
|
|
7063
|
+
}
|
|
7064
|
+
const remaining = budget - used;
|
|
7065
|
+
const shrinkable = natural.map((n, i) => ({ i, slack: Math.max(0, n - widths[i]) })).filter((e) => e.slack > 0);
|
|
7066
|
+
const shrinkableTotal = shrinkable.reduce((a, b) => a + b.slack, 0);
|
|
7067
|
+
if (shrinkableTotal === 0) {
|
|
7068
|
+
return widths;
|
|
7069
|
+
}
|
|
7070
|
+
for (const e of shrinkable) {
|
|
7071
|
+
const add = Math.floor(remaining * e.slack / shrinkableTotal);
|
|
7072
|
+
widths[e.i] = widths[e.i] + Math.min(add, e.slack);
|
|
7073
|
+
}
|
|
7074
|
+
used = widths.reduce((a, b) => a + b, 0);
|
|
7075
|
+
let leftover = budget - used;
|
|
7076
|
+
while (leftover > 0) {
|
|
7077
|
+
let bestIdx = -1;
|
|
7078
|
+
let bestDeficit = 0;
|
|
7079
|
+
for (let i = 0; i < cols; i++) {
|
|
7080
|
+
const deficit = natural[i] - widths[i];
|
|
7081
|
+
if (deficit > bestDeficit) {
|
|
7082
|
+
bestDeficit = deficit;
|
|
7083
|
+
bestIdx = i;
|
|
7084
|
+
}
|
|
7085
|
+
}
|
|
7086
|
+
if (bestIdx < 0) {
|
|
7087
|
+
break;
|
|
7088
|
+
}
|
|
7089
|
+
widths[bestIdx] = widths[bestIdx] + 1;
|
|
7090
|
+
leftover--;
|
|
7091
|
+
}
|
|
7092
|
+
return widths;
|
|
7093
|
+
}
|
|
7094
|
+
function formatTable(header, body, maxWidth) {
|
|
6755
7095
|
const cols = header.length;
|
|
6756
|
-
const
|
|
7096
|
+
const natural = new Array(cols).fill(0);
|
|
6757
7097
|
for (let c = 0; c < cols; c++) {
|
|
6758
|
-
|
|
7098
|
+
natural[c] = cellVisibleWidth(header[c] ?? "");
|
|
6759
7099
|
}
|
|
6760
7100
|
for (const row of body) {
|
|
6761
7101
|
for (let c = 0; c < cols; c++) {
|
|
6762
7102
|
const cell = row[c] ?? "";
|
|
6763
|
-
const w = cellVisibleWidth(cell
|
|
6764
|
-
if (w >
|
|
6765
|
-
|
|
7103
|
+
const w = cellVisibleWidth(cell);
|
|
7104
|
+
if (w > natural[c]) {
|
|
7105
|
+
natural[c] = w;
|
|
6766
7106
|
}
|
|
6767
7107
|
}
|
|
6768
7108
|
}
|
|
6769
|
-
|
|
6770
|
-
|
|
7109
|
+
let widths = natural.slice();
|
|
7110
|
+
if (maxWidth !== void 0) {
|
|
7111
|
+
const budget = Math.max(
|
|
7112
|
+
cols * TABLE_MIN_COL,
|
|
7113
|
+
maxWidth - TABLE_PREFIX_WIDTH - (cols - 1) * TABLE_SEP_WIDTH
|
|
7114
|
+
);
|
|
7115
|
+
widths = distributeColumnWidths(natural, budget);
|
|
7116
|
+
}
|
|
7117
|
+
const renderRow = (cells, style, inlineOpts) => {
|
|
7118
|
+
const wrapped = [];
|
|
7119
|
+
let rowHeight = 1;
|
|
6771
7120
|
for (let c = 0; c < cols; c++) {
|
|
6772
7121
|
const cell = cells[c] ?? "";
|
|
6773
7122
|
const w = widths[c];
|
|
6774
|
-
const
|
|
6775
|
-
|
|
6776
|
-
|
|
7123
|
+
const lines = wrapCellAtoms(tokenizeCell(cell), w);
|
|
7124
|
+
wrapped.push(lines);
|
|
7125
|
+
if (lines.length > rowHeight) {
|
|
7126
|
+
rowHeight = lines.length;
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
7129
|
+
const out2 = [];
|
|
7130
|
+
for (let r = 0; r < rowHeight; r++) {
|
|
7131
|
+
const padded = [];
|
|
7132
|
+
for (let c = 0; c < cols; c++) {
|
|
7133
|
+
const cellLine = wrapped[c][r] ?? "";
|
|
7134
|
+
const w = widths[c];
|
|
7135
|
+
const visible = cellVisibleWidth(cellLine);
|
|
7136
|
+
const rendered = applyInlineMarkup(cellLine, inlineOpts);
|
|
7137
|
+
padded.push(rendered + " ".repeat(Math.max(0, w - visible)));
|
|
7138
|
+
}
|
|
7139
|
+
out2.push({
|
|
7140
|
+
prefix: " ",
|
|
7141
|
+
body: padded.join(" \u2502 "),
|
|
7142
|
+
bodyStyle: style
|
|
7143
|
+
});
|
|
6777
7144
|
}
|
|
6778
|
-
return
|
|
6779
|
-
prefix: " ",
|
|
6780
|
-
body: padded.join(" \u2502 "),
|
|
6781
|
-
bodyStyle: style
|
|
6782
|
-
};
|
|
7145
|
+
return out2;
|
|
6783
7146
|
};
|
|
6784
7147
|
const out = [];
|
|
6785
|
-
out.push(
|
|
7148
|
+
out.push(
|
|
7149
|
+
...renderRow(header, "heading-3", headingInlineOptsFor("heading-3"))
|
|
7150
|
+
);
|
|
6786
7151
|
const rules = [];
|
|
6787
7152
|
for (let c = 0; c < cols; c++) {
|
|
6788
7153
|
rules.push("\u2500".repeat(widths[c]));
|
|
@@ -6793,7 +7158,7 @@ function formatTable(header, body) {
|
|
|
6793
7158
|
bodyStyle: "dim"
|
|
6794
7159
|
});
|
|
6795
7160
|
for (const row of body) {
|
|
6796
|
-
out.push(renderRow(row, "agent"
|
|
7161
|
+
out.push(...renderRow(row, "agent"));
|
|
6797
7162
|
}
|
|
6798
7163
|
return out;
|
|
6799
7164
|
}
|
|
@@ -6811,6 +7176,7 @@ function highlightFencedBlock(lang, lines) {
|
|
|
6811
7176
|
} catch {
|
|
6812
7177
|
return lines.map((body) => ({ body, ansi: false }));
|
|
6813
7178
|
}
|
|
7179
|
+
highlighted = highlighted.replace(/\x1b\[39m/g, "\x1B[37m");
|
|
6814
7180
|
const out = highlighted.split("\n");
|
|
6815
7181
|
if (out.length !== lines.length) {
|
|
6816
7182
|
return lines.map((body) => ({ body, ansi: false }));
|
|
@@ -6875,6 +7241,97 @@ function formatToolLine2(state) {
|
|
|
6875
7241
|
}
|
|
6876
7242
|
return lines;
|
|
6877
7243
|
}
|
|
7244
|
+
function formatEditDiffBlock(diff, mode) {
|
|
7245
|
+
const lines = [];
|
|
7246
|
+
if (diff.path) {
|
|
7247
|
+
lines.push({
|
|
7248
|
+
prefix: " ",
|
|
7249
|
+
body: `\u25B8 Edited ${sanitizeSingleLine(shortenHomePath(diff.path))}`,
|
|
7250
|
+
bodyStyle: "dim"
|
|
7251
|
+
});
|
|
7252
|
+
}
|
|
7253
|
+
if (mode === "edit") {
|
|
7254
|
+
return lines;
|
|
7255
|
+
}
|
|
7256
|
+
const body = buildUnifiedDiff(diff);
|
|
7257
|
+
if (body.length === 0) {
|
|
7258
|
+
return lines;
|
|
7259
|
+
}
|
|
7260
|
+
const fenced = "```diff\n" + body + "\n```";
|
|
7261
|
+
lines.push(...parseAgentMarkdown(fenced));
|
|
7262
|
+
return lines;
|
|
7263
|
+
}
|
|
7264
|
+
function buildUnifiedDiff(diff) {
|
|
7265
|
+
const oldLines = sanitizeWireText(diff.oldText).split("\n");
|
|
7266
|
+
const newLines = sanitizeWireText(diff.newText).split("\n");
|
|
7267
|
+
if (oldLines.length > 0 && oldLines[oldLines.length - 1] === "") {
|
|
7268
|
+
oldLines.pop();
|
|
7269
|
+
}
|
|
7270
|
+
if (newLines.length > 0 && newLines[newLines.length - 1] === "") {
|
|
7271
|
+
newLines.pop();
|
|
7272
|
+
}
|
|
7273
|
+
const ops = diffLines(oldLines, newLines);
|
|
7274
|
+
const rendered = [];
|
|
7275
|
+
for (let idx = 0; idx < ops.length; idx++) {
|
|
7276
|
+
const op = ops[idx];
|
|
7277
|
+
const wouldTruncate = rendered.length >= EDIT_DIFF_MAX_LINES - 1 && idx < ops.length - 1;
|
|
7278
|
+
if (wouldTruncate) {
|
|
7279
|
+
const remaining = ops.length - idx;
|
|
7280
|
+
rendered.push(`\u2026 ${remaining} more line${remaining === 1 ? "" : "s"}`);
|
|
7281
|
+
break;
|
|
7282
|
+
}
|
|
7283
|
+
if (op.op === "=") {
|
|
7284
|
+
rendered.push(` ${op.text}`);
|
|
7285
|
+
} else if (op.op === "-") {
|
|
7286
|
+
rendered.push(`- ${op.text}`);
|
|
7287
|
+
} else {
|
|
7288
|
+
rendered.push(`+ ${op.text}`);
|
|
7289
|
+
}
|
|
7290
|
+
}
|
|
7291
|
+
return rendered.join("\n");
|
|
7292
|
+
}
|
|
7293
|
+
function diffLines(a, b) {
|
|
7294
|
+
const m = a.length;
|
|
7295
|
+
const n = b.length;
|
|
7296
|
+
const dp = Array.from(
|
|
7297
|
+
{ length: m + 1 },
|
|
7298
|
+
() => new Array(n + 1).fill(0)
|
|
7299
|
+
);
|
|
7300
|
+
for (let i2 = m - 1; i2 >= 0; i2--) {
|
|
7301
|
+
for (let j2 = n - 1; j2 >= 0; j2--) {
|
|
7302
|
+
if (a[i2] === b[j2]) {
|
|
7303
|
+
dp[i2][j2] = dp[i2 + 1][j2 + 1] + 1;
|
|
7304
|
+
} else {
|
|
7305
|
+
dp[i2][j2] = Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7309
|
+
const out = [];
|
|
7310
|
+
let i = 0;
|
|
7311
|
+
let j = 0;
|
|
7312
|
+
while (i < m && j < n) {
|
|
7313
|
+
if (a[i] === b[j]) {
|
|
7314
|
+
out.push({ op: "=", text: a[i] });
|
|
7315
|
+
i++;
|
|
7316
|
+
j++;
|
|
7317
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
7318
|
+
out.push({ op: "-", text: a[i] });
|
|
7319
|
+
i++;
|
|
7320
|
+
} else {
|
|
7321
|
+
out.push({ op: "+", text: b[j] });
|
|
7322
|
+
j++;
|
|
7323
|
+
}
|
|
7324
|
+
}
|
|
7325
|
+
while (i < m) {
|
|
7326
|
+
out.push({ op: "-", text: a[i] });
|
|
7327
|
+
i++;
|
|
7328
|
+
}
|
|
7329
|
+
while (j < n) {
|
|
7330
|
+
out.push({ op: "+", text: b[j] });
|
|
7331
|
+
j++;
|
|
7332
|
+
}
|
|
7333
|
+
return out;
|
|
7334
|
+
}
|
|
6878
7335
|
function toolStatusIcon(status) {
|
|
6879
7336
|
switch (status) {
|
|
6880
7337
|
case "completed":
|
|
@@ -7016,11 +7473,15 @@ function toolStatusStyle(status) {
|
|
|
7016
7473
|
return "tool-status-pending";
|
|
7017
7474
|
}
|
|
7018
7475
|
}
|
|
7019
|
-
var highlightChalk, HIGHLIGHT_THEME;
|
|
7476
|
+
var TABLE_MIN_COL, TABLE_PREFIX_WIDTH, TABLE_SEP_WIDTH, highlightChalk, HIGHLIGHT_THEME, EDIT_DIFF_MAX_LINES;
|
|
7020
7477
|
var init_format = __esm({
|
|
7021
7478
|
"src/tui/format.ts"() {
|
|
7022
7479
|
"use strict";
|
|
7480
|
+
init_paths();
|
|
7023
7481
|
init_render_update();
|
|
7482
|
+
TABLE_MIN_COL = 6;
|
|
7483
|
+
TABLE_PREFIX_WIDTH = 2;
|
|
7484
|
+
TABLE_SEP_WIDTH = 3;
|
|
7024
7485
|
highlightChalk = new chalk2.Instance({ level: 3 });
|
|
7025
7486
|
HIGHLIGHT_THEME = {
|
|
7026
7487
|
keyword: highlightChalk.blueBright,
|
|
@@ -7046,6 +7507,7 @@ var init_format = __esm({
|
|
|
7046
7507
|
tag: highlightChalk.cyan,
|
|
7047
7508
|
name: highlightChalk.cyanBright
|
|
7048
7509
|
};
|
|
7510
|
+
EDIT_DIFF_MAX_LINES = 40;
|
|
7049
7511
|
}
|
|
7050
7512
|
});
|
|
7051
7513
|
|
|
@@ -7140,10 +7602,17 @@ var init_update_check = __esm({
|
|
|
7140
7602
|
});
|
|
7141
7603
|
|
|
7142
7604
|
// src/tui/input.ts
|
|
7143
|
-
|
|
7605
|
+
function formatPasteToken(id, lineCount) {
|
|
7606
|
+
return `[pasted #${id} +${lineCount} lines]`;
|
|
7607
|
+
}
|
|
7608
|
+
var PASTE_LINE_THRESHOLD, PASTE_TOKEN_RE, PASTE_TOKEN_LEFT_RE, PASTE_TOKEN_RIGHT_RE, InputDispatcher;
|
|
7144
7609
|
var init_input = __esm({
|
|
7145
7610
|
"src/tui/input.ts"() {
|
|
7146
7611
|
"use strict";
|
|
7612
|
+
PASTE_LINE_THRESHOLD = 10;
|
|
7613
|
+
PASTE_TOKEN_RE = /\[pasted #(\d+) \+\d+ lines\]/g;
|
|
7614
|
+
PASTE_TOKEN_LEFT_RE = /\[pasted #(\d+) \+\d+ lines\]$/;
|
|
7615
|
+
PASTE_TOKEN_RIGHT_RE = /^\[pasted #(\d+) \+\d+ lines\]/;
|
|
7147
7616
|
InputDispatcher = class {
|
|
7148
7617
|
buffer = [""];
|
|
7149
7618
|
row = 0;
|
|
@@ -7185,9 +7654,23 @@ var init_input = __esm({
|
|
|
7185
7654
|
// queue slots (which may carry their own attachments — though we
|
|
7186
7655
|
// don't surface that yet) shouldn't blend with the current draft's.
|
|
7187
7656
|
savedAttachments = null;
|
|
7657
|
+
// Map of paste id → original text for placeholder tokens currently in
|
|
7658
|
+
// the buffer (or recoverable via history walks within this session).
|
|
7659
|
+
// Persists across sends — never cleared by clearBuffer/setBuffer, so
|
|
7660
|
+
// up-arrow recall of a placeholder can still reanimate on resubmit.
|
|
7661
|
+
pastes = /* @__PURE__ */ new Map();
|
|
7662
|
+
nextPasteId = 1;
|
|
7663
|
+
collapsePastes;
|
|
7188
7664
|
constructor(opts = {}) {
|
|
7189
7665
|
this.history = [...opts.history ?? []];
|
|
7190
7666
|
this.planMode = opts.planMode ?? false;
|
|
7667
|
+
this.collapsePastes = opts.collapsePastes ?? true;
|
|
7668
|
+
}
|
|
7669
|
+
// Buffer text with paste placeholders expanded back to their original
|
|
7670
|
+
// content. Used by callers that bypass the send/amend effects (e.g.
|
|
7671
|
+
// picker.ts reads composer text directly).
|
|
7672
|
+
expandedText() {
|
|
7673
|
+
return this.expandPastes(this.bufferText());
|
|
7191
7674
|
}
|
|
7192
7675
|
state() {
|
|
7193
7676
|
return {
|
|
@@ -7296,7 +7779,14 @@ var init_input = __esm({
|
|
|
7296
7779
|
return [];
|
|
7297
7780
|
}
|
|
7298
7781
|
if (event.type === "paste") {
|
|
7299
|
-
|
|
7782
|
+
const lineCount = event.text.split("\n").length;
|
|
7783
|
+
if (this.collapsePastes && lineCount > PASTE_LINE_THRESHOLD) {
|
|
7784
|
+
const id = this.nextPasteId++;
|
|
7785
|
+
this.pastes.set(id, event.text);
|
|
7786
|
+
this.insertText(formatPasteToken(id, lineCount));
|
|
7787
|
+
} else {
|
|
7788
|
+
this.insertText(event.text);
|
|
7789
|
+
}
|
|
7300
7790
|
return [];
|
|
7301
7791
|
}
|
|
7302
7792
|
if (event.type === "attachment-paths") {
|
|
@@ -7420,6 +7910,16 @@ var init_input = __esm({
|
|
|
7420
7910
|
bufferText() {
|
|
7421
7911
|
return this.buffer.join("\n");
|
|
7422
7912
|
}
|
|
7913
|
+
// Substitute every [pasted #N +M lines] token with its stored original
|
|
7914
|
+
// text. Unknown ids (orphaned placeholders from outside this process)
|
|
7915
|
+
// are left as the literal token string — the safe fallback.
|
|
7916
|
+
expandPastes(text) {
|
|
7917
|
+
return text.replace(PASTE_TOKEN_RE, (match, idStr) => {
|
|
7918
|
+
const id = parseInt(idStr, 10);
|
|
7919
|
+
const stored = this.pastes.get(id);
|
|
7920
|
+
return stored !== void 0 ? stored : match;
|
|
7921
|
+
});
|
|
7922
|
+
}
|
|
7423
7923
|
bufferIsEmpty() {
|
|
7424
7924
|
return this.buffer.length === 1 && this.buffer[0] === "";
|
|
7425
7925
|
}
|
|
@@ -7476,6 +7976,16 @@ var init_input = __esm({
|
|
|
7476
7976
|
backspace() {
|
|
7477
7977
|
if (this.col > 0) {
|
|
7478
7978
|
const line = this.currentLine();
|
|
7979
|
+
const before = line.slice(0, this.col);
|
|
7980
|
+
const m = before.match(PASTE_TOKEN_LEFT_RE);
|
|
7981
|
+
if (m !== null) {
|
|
7982
|
+
this.pastes.delete(parseInt(m[1], 10));
|
|
7983
|
+
this.setCurrentLine(
|
|
7984
|
+
line.slice(0, this.col - m[0].length) + line.slice(this.col)
|
|
7985
|
+
);
|
|
7986
|
+
this.col -= m[0].length;
|
|
7987
|
+
return;
|
|
7988
|
+
}
|
|
7479
7989
|
this.setCurrentLine(line.slice(0, this.col - 1) + line.slice(this.col));
|
|
7480
7990
|
this.col -= 1;
|
|
7481
7991
|
return;
|
|
@@ -7493,6 +8003,15 @@ var init_input = __esm({
|
|
|
7493
8003
|
deleteForward() {
|
|
7494
8004
|
const line = this.currentLine();
|
|
7495
8005
|
if (this.col < line.length) {
|
|
8006
|
+
const after = line.slice(this.col);
|
|
8007
|
+
const m = after.match(PASTE_TOKEN_RIGHT_RE);
|
|
8008
|
+
if (m !== null) {
|
|
8009
|
+
this.pastes.delete(parseInt(m[1], 10));
|
|
8010
|
+
this.setCurrentLine(
|
|
8011
|
+
line.slice(0, this.col) + line.slice(this.col + m[0].length)
|
|
8012
|
+
);
|
|
8013
|
+
return;
|
|
8014
|
+
}
|
|
7496
8015
|
this.setCurrentLine(line.slice(0, this.col) + line.slice(this.col + 1));
|
|
7497
8016
|
return;
|
|
7498
8017
|
}
|
|
@@ -7567,6 +8086,15 @@ var init_input = __esm({
|
|
|
7567
8086
|
this.backspace();
|
|
7568
8087
|
return;
|
|
7569
8088
|
}
|
|
8089
|
+
const before = line.slice(0, this.col);
|
|
8090
|
+
const m = before.match(PASTE_TOKEN_LEFT_RE);
|
|
8091
|
+
if (m !== null) {
|
|
8092
|
+
this.killBuffer = m[0];
|
|
8093
|
+
const i2 = this.col - m[0].length;
|
|
8094
|
+
this.setCurrentLine(line.slice(0, i2) + line.slice(this.col));
|
|
8095
|
+
this.col = i2;
|
|
8096
|
+
return;
|
|
8097
|
+
}
|
|
7570
8098
|
let i = this.col;
|
|
7571
8099
|
while (i > 0 && /\s/.test(line[i - 1] ?? "")) {
|
|
7572
8100
|
i -= 1;
|
|
@@ -7589,6 +8117,12 @@ var init_input = __esm({
|
|
|
7589
8117
|
}
|
|
7590
8118
|
moveLeft() {
|
|
7591
8119
|
if (this.col > 0) {
|
|
8120
|
+
const before = this.currentLine().slice(0, this.col);
|
|
8121
|
+
const m = before.match(PASTE_TOKEN_LEFT_RE);
|
|
8122
|
+
if (m !== null) {
|
|
8123
|
+
this.col -= m[0].length;
|
|
8124
|
+
return;
|
|
8125
|
+
}
|
|
7592
8126
|
this.col -= 1;
|
|
7593
8127
|
return;
|
|
7594
8128
|
}
|
|
@@ -7598,7 +8132,14 @@ var init_input = __esm({
|
|
|
7598
8132
|
}
|
|
7599
8133
|
}
|
|
7600
8134
|
moveRight() {
|
|
7601
|
-
|
|
8135
|
+
const line = this.currentLine();
|
|
8136
|
+
if (this.col < line.length) {
|
|
8137
|
+
const after = line.slice(this.col);
|
|
8138
|
+
const m = after.match(PASTE_TOKEN_RIGHT_RE);
|
|
8139
|
+
if (m !== null) {
|
|
8140
|
+
this.col += m[0].length;
|
|
8141
|
+
return;
|
|
8142
|
+
}
|
|
7602
8143
|
this.col += 1;
|
|
7603
8144
|
return;
|
|
7604
8145
|
}
|
|
@@ -7621,6 +8162,12 @@ var init_input = __esm({
|
|
|
7621
8162
|
while (i > 0 && /\s/.test(line[i - 1] ?? "")) {
|
|
7622
8163
|
i -= 1;
|
|
7623
8164
|
}
|
|
8165
|
+
const before = line.slice(0, i);
|
|
8166
|
+
const m = before.match(PASTE_TOKEN_LEFT_RE);
|
|
8167
|
+
if (m !== null) {
|
|
8168
|
+
this.col = i - m[0].length;
|
|
8169
|
+
return;
|
|
8170
|
+
}
|
|
7624
8171
|
while (i > 0 && !/\s/.test(line[i - 1] ?? "")) {
|
|
7625
8172
|
i -= 1;
|
|
7626
8173
|
}
|
|
@@ -7640,6 +8187,12 @@ var init_input = __esm({
|
|
|
7640
8187
|
while (i < line.length && /\s/.test(line[i] ?? "")) {
|
|
7641
8188
|
i += 1;
|
|
7642
8189
|
}
|
|
8190
|
+
const after = line.slice(i);
|
|
8191
|
+
const m = after.match(PASTE_TOKEN_RIGHT_RE);
|
|
8192
|
+
if (m !== null) {
|
|
8193
|
+
this.col = i + m[0].length;
|
|
8194
|
+
return;
|
|
8195
|
+
}
|
|
7643
8196
|
while (i < line.length && !/\s/.test(line[i] ?? "")) {
|
|
7644
8197
|
i += 1;
|
|
7645
8198
|
}
|
|
@@ -7890,7 +8443,8 @@ var init_input = __esm({
|
|
|
7890
8443
|
this.col = (this.buffer[this.row] ?? "").length;
|
|
7891
8444
|
}
|
|
7892
8445
|
send() {
|
|
7893
|
-
const
|
|
8446
|
+
const displayText = this.bufferText();
|
|
8447
|
+
const text = this.expandPastes(displayText);
|
|
7894
8448
|
if (this.queueIndex >= 0 && this.queueIndex < this.queue.length) {
|
|
7895
8449
|
const index = this.queueIndex;
|
|
7896
8450
|
const attachments2 = [...this.attachments];
|
|
@@ -7898,7 +8452,7 @@ var init_input = __esm({
|
|
|
7898
8452
|
if (text.trim().length === 0) {
|
|
7899
8453
|
return [{ type: "queue-remove", index }];
|
|
7900
8454
|
}
|
|
7901
|
-
return [{ type: "queue-edit", index, text, attachments: attachments2 }];
|
|
8455
|
+
return [{ type: "queue-edit", index, text, displayText, attachments: attachments2 }];
|
|
7902
8456
|
}
|
|
7903
8457
|
if (text.trim().length === 0 && this.attachments.length === 0) {
|
|
7904
8458
|
return [];
|
|
@@ -7906,7 +8460,7 @@ var init_input = __esm({
|
|
|
7906
8460
|
const planMode = this.planMode;
|
|
7907
8461
|
const attachments = [...this.attachments];
|
|
7908
8462
|
this.clearBuffer();
|
|
7909
|
-
return [{ type: "send", text, planMode, attachments }];
|
|
8463
|
+
return [{ type: "send", text, displayText, planMode, attachments }];
|
|
7910
8464
|
}
|
|
7911
8465
|
// Shift+Enter (also Ctrl+Enter / ^S): amend the in-flight turn.
|
|
7912
8466
|
// While editing a queued slot, this is the "drop and amend" chord:
|
|
@@ -7918,7 +8472,8 @@ var init_input = __esm({
|
|
|
7918
8472
|
// whether to route the amend through amend_prompt or fall through to
|
|
7919
8473
|
// a regular send when no turn is in flight.
|
|
7920
8474
|
amend() {
|
|
7921
|
-
const
|
|
8475
|
+
const displayText = this.bufferText();
|
|
8476
|
+
const text = this.expandPastes(displayText);
|
|
7922
8477
|
if (this.queueIndex >= 0 && this.queueIndex < this.queue.length) {
|
|
7923
8478
|
const index = this.queueIndex;
|
|
7924
8479
|
const planMode2 = this.planMode;
|
|
@@ -7930,7 +8485,7 @@ var init_input = __esm({
|
|
|
7930
8485
|
}
|
|
7931
8486
|
return [
|
|
7932
8487
|
{ type: "queue-remove", index },
|
|
7933
|
-
{ type: "amend", text, planMode: planMode2, attachments: attachments2 }
|
|
8488
|
+
{ type: "amend", text, displayText, planMode: planMode2, attachments: attachments2 }
|
|
7934
8489
|
];
|
|
7935
8490
|
}
|
|
7936
8491
|
if (text.trim().length === 0 && this.attachments.length === 0) {
|
|
@@ -7939,7 +8494,7 @@ var init_input = __esm({
|
|
|
7939
8494
|
const planMode = this.planMode;
|
|
7940
8495
|
const attachments = [...this.attachments];
|
|
7941
8496
|
this.clearBuffer();
|
|
7942
|
-
return [{ type: "amend", text, planMode, attachments }];
|
|
8497
|
+
return [{ type: "amend", text, displayText, planMode, attachments }];
|
|
7943
8498
|
}
|
|
7944
8499
|
// Home: jump to the very start of the prompt buffer. If we're already
|
|
7945
8500
|
// there, fall through to scrolling the scrollback to its top.
|
|
@@ -8248,6 +8803,9 @@ function writeBodyWithHighlight(termObj, text, style, term, activeCol = null, _a
|
|
|
8248
8803
|
i = next + term.length;
|
|
8249
8804
|
}
|
|
8250
8805
|
}
|
|
8806
|
+
function bodyStyleUsesMarkup(style) {
|
|
8807
|
+
return style === "agent" || style === "heading-1" || style === "heading-2" || style === "heading-3";
|
|
8808
|
+
}
|
|
8251
8809
|
function writeStyled(term, text, style) {
|
|
8252
8810
|
if (text.length === 0) {
|
|
8253
8811
|
return;
|
|
@@ -8299,16 +8857,16 @@ function writeStyled(term, text, style) {
|
|
|
8299
8857
|
term.dim.noFormat(text);
|
|
8300
8858
|
return;
|
|
8301
8859
|
case "code":
|
|
8302
|
-
term.bgColorGrayscale.
|
|
8860
|
+
term.bgColorGrayscale.white.noFormat(28, text);
|
|
8303
8861
|
return;
|
|
8304
8862
|
case "heading-1":
|
|
8305
|
-
term.bold.brightYellow
|
|
8863
|
+
term.bold.brightYellow(text);
|
|
8306
8864
|
return;
|
|
8307
8865
|
case "heading-2":
|
|
8308
|
-
term.bold.brightCyan
|
|
8866
|
+
term.bold.brightCyan(text);
|
|
8309
8867
|
return;
|
|
8310
8868
|
case "heading-3":
|
|
8311
|
-
term.bold
|
|
8869
|
+
term.bold(text);
|
|
8312
8870
|
return;
|
|
8313
8871
|
case "search-highlight":
|
|
8314
8872
|
term.bgBrightYellow.black.noFormat(text);
|
|
@@ -9343,6 +9901,14 @@ uncaught: ${err.stack ?? err.message}
|
|
|
9343
9901
|
this.pasteActive = true;
|
|
9344
9902
|
}
|
|
9345
9903
|
}
|
|
9904
|
+
// Current terminal column count. Markdown rendering (parseAgentMarkdown,
|
|
9905
|
+
// tables in particular) consults this so a too-wide block lays out
|
|
9906
|
+
// narrowly enough that the screen-layer wrap is a no-op. Returns 0 if the
|
|
9907
|
+
// terminal hasn't reported a width yet, in which case callers should fall
|
|
9908
|
+
// back to natural-width formatting.
|
|
9909
|
+
width() {
|
|
9910
|
+
return this.term.width || 0;
|
|
9911
|
+
}
|
|
9346
9912
|
appendLines(lines) {
|
|
9347
9913
|
if (lines.length === 0) {
|
|
9348
9914
|
return;
|
|
@@ -10880,7 +11446,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10880
11446
|
}
|
|
10881
11447
|
const prefix = line.prefix ?? "";
|
|
10882
11448
|
const room = Math.max(1, width - prefix.length);
|
|
10883
|
-
const stripMarkup = line.bodyStyle
|
|
11449
|
+
const stripMarkup = bodyStyleUsesMarkup(line.bodyStyle);
|
|
10884
11450
|
const chunks = line.ansi ? wrapAnsiBody(line.body, room) : wrap(line.body, room, { stripMarkup });
|
|
10885
11451
|
const wrapped = [];
|
|
10886
11452
|
let scanPos = 0;
|
|
@@ -10926,7 +11492,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10926
11492
|
writeStyled(this.term, line.prefix, line.prefixStyle ?? line.bodyStyle);
|
|
10927
11493
|
}
|
|
10928
11494
|
const remaining = Math.max(0, width - (line.prefix?.length ?? 0));
|
|
10929
|
-
const stripMarkup = line.bodyStyle
|
|
11495
|
+
const stripMarkup = bodyStyleUsesMarkup(line.bodyStyle);
|
|
10930
11496
|
const bodyText = line.ansi ? line.body : truncate(line.body, remaining, { stripMarkup });
|
|
10931
11497
|
if (this.scrollbackHighlight !== null && !line.ansi) {
|
|
10932
11498
|
writeBodyWithHighlight(
|
|
@@ -11466,7 +12032,10 @@ async function pickSession(term, opts) {
|
|
|
11466
12032
|
let mode = "normal";
|
|
11467
12033
|
let pendingAction = null;
|
|
11468
12034
|
let findSubMode = "input";
|
|
11469
|
-
let findComposer = new InputDispatcher({
|
|
12035
|
+
let findComposer = new InputDispatcher({
|
|
12036
|
+
history: [],
|
|
12037
|
+
collapsePastes: false
|
|
12038
|
+
});
|
|
11470
12039
|
let findResults = [];
|
|
11471
12040
|
let findTruncated = false;
|
|
11472
12041
|
let findSelectedIdx = 0;
|
|
@@ -12214,7 +12783,10 @@ async function pickSession(term, opts) {
|
|
|
12214
12783
|
};
|
|
12215
12784
|
const focus = { push: pushLayer, pop: popLayer };
|
|
12216
12785
|
exitFind = () => {
|
|
12217
|
-
findComposer = new InputDispatcher({
|
|
12786
|
+
findComposer = new InputDispatcher({
|
|
12787
|
+
history: [],
|
|
12788
|
+
collapsePastes: false
|
|
12789
|
+
});
|
|
12218
12790
|
findResults = [];
|
|
12219
12791
|
findTruncated = false;
|
|
12220
12792
|
findSelectedIdx = 0;
|
|
@@ -12699,7 +13271,7 @@ ${cells}`;
|
|
|
12699
13271
|
}
|
|
12700
13272
|
if (name === "ENTER" || name === "KP_ENTER") {
|
|
12701
13273
|
cleanup();
|
|
12702
|
-
const text = composer.
|
|
13274
|
+
const text = composer.expandedText();
|
|
12703
13275
|
if (text.trim().length === 0) {
|
|
12704
13276
|
resolve7({ kind: "new" });
|
|
12705
13277
|
} else {
|
|
@@ -12867,12 +13439,35 @@ ${cells}`;
|
|
|
12867
13439
|
resolve7(result);
|
|
12868
13440
|
return;
|
|
12869
13441
|
}
|
|
12870
|
-
if ((name === "
|
|
13442
|
+
if ((name === "f" || name === "F") && selectedIdx > 0) {
|
|
12871
13443
|
const session = visible[selectedIdx - 1];
|
|
12872
13444
|
if (!session) {
|
|
12873
13445
|
return;
|
|
12874
13446
|
}
|
|
12875
|
-
|
|
13447
|
+
cleanup();
|
|
13448
|
+
const result = {
|
|
13449
|
+
kind: "fork",
|
|
13450
|
+
sourceSessionId: session.sessionId,
|
|
13451
|
+
sourceCwd: session.cwd
|
|
13452
|
+
};
|
|
13453
|
+
if (session.agentId !== void 0) {
|
|
13454
|
+
result.sourceAgentId = session.agentId;
|
|
13455
|
+
}
|
|
13456
|
+
if (session.importedFromMachine !== void 0) {
|
|
13457
|
+
result.sourceImportedFromMachine = session.importedFromMachine;
|
|
13458
|
+
}
|
|
13459
|
+
if (session.upstreamSessionId !== void 0) {
|
|
13460
|
+
result.sourceUpstreamSessionId = session.upstreamSessionId;
|
|
13461
|
+
}
|
|
13462
|
+
resolve7(result);
|
|
13463
|
+
return;
|
|
13464
|
+
}
|
|
13465
|
+
if ((name === "k" || name === "K") && selectedIdx > 0) {
|
|
13466
|
+
const session = visible[selectedIdx - 1];
|
|
13467
|
+
if (!session) {
|
|
13468
|
+
return;
|
|
13469
|
+
}
|
|
13470
|
+
pendingAction = {
|
|
12876
13471
|
sessionId: session.sessionId,
|
|
12877
13472
|
cwd: session.cwd,
|
|
12878
13473
|
status: session.status
|
|
@@ -13111,7 +13706,7 @@ var init_picker = __esm({
|
|
|
13111
13706
|
});
|
|
13112
13707
|
|
|
13113
13708
|
// src/core/cwd.ts
|
|
13114
|
-
import * as
|
|
13709
|
+
import * as fs19 from "fs/promises";
|
|
13115
13710
|
import * as path17 from "path";
|
|
13116
13711
|
async function validateLocalCwd(input) {
|
|
13117
13712
|
const trimmed = input.trim();
|
|
@@ -13121,7 +13716,7 @@ async function validateLocalCwd(input) {
|
|
|
13121
13716
|
const resolved = path17.resolve(expandHome(trimmed));
|
|
13122
13717
|
let stat5;
|
|
13123
13718
|
try {
|
|
13124
|
-
stat5 = await
|
|
13719
|
+
stat5 = await fs19.stat(resolved);
|
|
13125
13720
|
} catch {
|
|
13126
13721
|
return { ok: false, reason: `${resolved} does not exist` };
|
|
13127
13722
|
}
|
|
@@ -13130,6 +13725,58 @@ async function validateLocalCwd(input) {
|
|
|
13130
13725
|
}
|
|
13131
13726
|
return { ok: true, path: resolved };
|
|
13132
13727
|
}
|
|
13728
|
+
async function pickInitialLocalCwd(sessionCwd) {
|
|
13729
|
+
const candidates = [];
|
|
13730
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13731
|
+
const push = (p) => {
|
|
13732
|
+
if (!seen.has(p)) {
|
|
13733
|
+
seen.add(p);
|
|
13734
|
+
candidates.push(p);
|
|
13735
|
+
}
|
|
13736
|
+
};
|
|
13737
|
+
push(sessionCwd);
|
|
13738
|
+
if (sessionCwd.startsWith("/Users/")) {
|
|
13739
|
+
push("/home/" + sessionCwd.slice("/Users/".length));
|
|
13740
|
+
} else if (sessionCwd.startsWith("/home/")) {
|
|
13741
|
+
push("/Users/" + sessionCwd.slice("/home/".length));
|
|
13742
|
+
}
|
|
13743
|
+
for (const candidate of candidates) {
|
|
13744
|
+
try {
|
|
13745
|
+
const stat5 = await fs19.stat(candidate);
|
|
13746
|
+
if (stat5.isDirectory()) {
|
|
13747
|
+
return candidate;
|
|
13748
|
+
}
|
|
13749
|
+
} catch {
|
|
13750
|
+
}
|
|
13751
|
+
}
|
|
13752
|
+
return null;
|
|
13753
|
+
}
|
|
13754
|
+
async function completeLocalPath(input) {
|
|
13755
|
+
const lastSlash = input.lastIndexOf("/");
|
|
13756
|
+
let prefix;
|
|
13757
|
+
let basePrefix;
|
|
13758
|
+
let dirForRead;
|
|
13759
|
+
if (lastSlash === -1) {
|
|
13760
|
+
prefix = "";
|
|
13761
|
+
basePrefix = input;
|
|
13762
|
+
dirForRead = ".";
|
|
13763
|
+
} else {
|
|
13764
|
+
prefix = input.slice(0, lastSlash + 1);
|
|
13765
|
+
basePrefix = input.slice(lastSlash + 1);
|
|
13766
|
+
dirForRead = lastSlash === 0 ? "/" : prefix;
|
|
13767
|
+
}
|
|
13768
|
+
const resolvedDir = path17.resolve(expandHome(dirForRead));
|
|
13769
|
+
let entries;
|
|
13770
|
+
try {
|
|
13771
|
+
const list = await fs19.readdir(resolvedDir, { withFileTypes: true });
|
|
13772
|
+
entries = list.map((e) => ({ name: e.name, isDir: e.isDirectory() }));
|
|
13773
|
+
} catch {
|
|
13774
|
+
return { prefix, basePrefix, matches: [] };
|
|
13775
|
+
}
|
|
13776
|
+
const showHidden = basePrefix.startsWith(".");
|
|
13777
|
+
const matches = entries.filter((e) => e.name.startsWith(basePrefix)).filter((e) => showHidden || !e.name.startsWith(".")).map((e) => e.isDir ? `${e.name}/` : e.name).sort();
|
|
13778
|
+
return { prefix, basePrefix, matches };
|
|
13779
|
+
}
|
|
13133
13780
|
var init_cwd = __esm({
|
|
13134
13781
|
"src/core/cwd.ts"() {
|
|
13135
13782
|
"use strict";
|
|
@@ -13137,10 +13784,54 @@ var init_cwd = __esm({
|
|
|
13137
13784
|
}
|
|
13138
13785
|
});
|
|
13139
13786
|
|
|
13787
|
+
// src/tui/completion.ts
|
|
13788
|
+
function longestCommonPrefix(names) {
|
|
13789
|
+
if (names.length === 0) {
|
|
13790
|
+
return "";
|
|
13791
|
+
}
|
|
13792
|
+
let prefix = names[0] ?? "";
|
|
13793
|
+
for (let i = 1; i < names.length; i++) {
|
|
13794
|
+
const n = names[i] ?? "";
|
|
13795
|
+
let j = 0;
|
|
13796
|
+
while (j < prefix.length && j < n.length && prefix[j] === n[j]) {
|
|
13797
|
+
j += 1;
|
|
13798
|
+
}
|
|
13799
|
+
prefix = prefix.slice(0, j);
|
|
13800
|
+
if (prefix.length === 0) {
|
|
13801
|
+
break;
|
|
13802
|
+
}
|
|
13803
|
+
}
|
|
13804
|
+
return prefix;
|
|
13805
|
+
}
|
|
13806
|
+
function computeTabCompletion(args) {
|
|
13807
|
+
const { matches, firstLine: firstLine3 } = args;
|
|
13808
|
+
if (matches.length === 0) {
|
|
13809
|
+
return null;
|
|
13810
|
+
}
|
|
13811
|
+
const space = firstLine3.indexOf(" ");
|
|
13812
|
+
const typedPrefix = space === -1 ? firstLine3 : firstLine3.slice(0, space);
|
|
13813
|
+
const tail = space === -1 ? "" : firstLine3.slice(space);
|
|
13814
|
+
if (matches.length === 1) {
|
|
13815
|
+
const name = matches[0] ?? "";
|
|
13816
|
+
const suffix = tail.startsWith(" ") ? "" : " ";
|
|
13817
|
+
return name + suffix + tail;
|
|
13818
|
+
}
|
|
13819
|
+
const commonPrefix = longestCommonPrefix(matches);
|
|
13820
|
+
if (commonPrefix.length <= typedPrefix.length) {
|
|
13821
|
+
return null;
|
|
13822
|
+
}
|
|
13823
|
+
return commonPrefix + tail;
|
|
13824
|
+
}
|
|
13825
|
+
var init_completion = __esm({
|
|
13826
|
+
"src/tui/completion.ts"() {
|
|
13827
|
+
"use strict";
|
|
13828
|
+
}
|
|
13829
|
+
});
|
|
13830
|
+
|
|
13140
13831
|
// src/tui/import-cwd-prompt.ts
|
|
13141
13832
|
import * as os6 from "os";
|
|
13142
13833
|
async function promptForImportCwd(term, session, opts = {}) {
|
|
13143
|
-
const defaultCwd = opts.defaultCwd ?? os6.homedir();
|
|
13834
|
+
const defaultCwd = opts.defaultCwd ?? await pickInitialLocalCwd(session.cwd) ?? os6.homedir();
|
|
13144
13835
|
resetTerminalModes();
|
|
13145
13836
|
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
13146
13837
|
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
@@ -13180,11 +13871,11 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
13180
13871
|
} else {
|
|
13181
13872
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
13182
13873
|
term.dim.noFormat(
|
|
13183
|
-
" Enter accept \xB7
|
|
13874
|
+
" Enter accept \xB7 Tab complete \xB7 Esc back \xB7 ^U clear"
|
|
13184
13875
|
);
|
|
13185
13876
|
}
|
|
13186
13877
|
};
|
|
13187
|
-
const inputRow = () =>
|
|
13878
|
+
const inputRow = () => 6;
|
|
13188
13879
|
const paintInputRow = (rowOffset) => {
|
|
13189
13880
|
if (!layout) {
|
|
13190
13881
|
return;
|
|
@@ -13215,7 +13906,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
13215
13906
|
term.red.noFormat(` ${truncate3(errorLine, layout.contentW - 2)}`);
|
|
13216
13907
|
} else {
|
|
13217
13908
|
term.dim.noFormat(
|
|
13218
|
-
" Enter accept \xB7
|
|
13909
|
+
" Enter accept \xB7 Tab complete \xB7 Esc back \xB7 ^U clear"
|
|
13219
13910
|
);
|
|
13220
13911
|
}
|
|
13221
13912
|
};
|
|
@@ -13271,6 +13962,32 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
13271
13962
|
finish({ kind: "cancel" });
|
|
13272
13963
|
return;
|
|
13273
13964
|
}
|
|
13965
|
+
if (name === "TAB") {
|
|
13966
|
+
busy = true;
|
|
13967
|
+
void completeLocalPath(buffer).then((result) => {
|
|
13968
|
+
busy = false;
|
|
13969
|
+
if (result.matches.length === 0) {
|
|
13970
|
+
return;
|
|
13971
|
+
}
|
|
13972
|
+
let next;
|
|
13973
|
+
if (result.matches.length === 1) {
|
|
13974
|
+
next = result.prefix + result.matches[0];
|
|
13975
|
+
} else {
|
|
13976
|
+
const lcp = longestCommonPrefix(result.matches);
|
|
13977
|
+
if (lcp.length <= result.basePrefix.length) {
|
|
13978
|
+
return;
|
|
13979
|
+
}
|
|
13980
|
+
next = result.prefix + lcp;
|
|
13981
|
+
}
|
|
13982
|
+
if (next === buffer) {
|
|
13983
|
+
return;
|
|
13984
|
+
}
|
|
13985
|
+
buffer = next;
|
|
13986
|
+
errorLine = null;
|
|
13987
|
+
repaintInput();
|
|
13988
|
+
});
|
|
13989
|
+
return;
|
|
13990
|
+
}
|
|
13274
13991
|
if (name === "BACKSPACE") {
|
|
13275
13992
|
if (buffer.length > 0) {
|
|
13276
13993
|
buffer = buffer.slice(0, -1);
|
|
@@ -13332,13 +14049,14 @@ var init_import_cwd_prompt = __esm({
|
|
|
13332
14049
|
init_paths();
|
|
13333
14050
|
init_session();
|
|
13334
14051
|
init_cwd();
|
|
14052
|
+
init_completion();
|
|
13335
14053
|
init_prompt_utils();
|
|
13336
14054
|
}
|
|
13337
14055
|
});
|
|
13338
14056
|
|
|
13339
14057
|
// src/tui/clipboard.ts
|
|
13340
14058
|
import { spawn as nodeSpawn } from "child_process";
|
|
13341
|
-
import
|
|
14059
|
+
import fs20 from "fs/promises";
|
|
13342
14060
|
import os7 from "os";
|
|
13343
14061
|
import path18 from "path";
|
|
13344
14062
|
async function readClipboard(envIn = {}) {
|
|
@@ -13379,7 +14097,7 @@ async function readMacOS(env) {
|
|
|
13379
14097
|
return img;
|
|
13380
14098
|
}
|
|
13381
14099
|
} catch {
|
|
13382
|
-
await
|
|
14100
|
+
await fs20.unlink(tmpPath).catch(() => void 0);
|
|
13383
14101
|
}
|
|
13384
14102
|
try {
|
|
13385
14103
|
const buf = await runCapture(env.spawn, "pbpaste", []);
|
|
@@ -13494,9 +14212,9 @@ async function which(env, cmd) {
|
|
|
13494
14212
|
}
|
|
13495
14213
|
async function readFileAsAttachment(p, unlinkAfter) {
|
|
13496
14214
|
try {
|
|
13497
|
-
const buf = await
|
|
14215
|
+
const buf = await fs20.readFile(p);
|
|
13498
14216
|
if (unlinkAfter) {
|
|
13499
|
-
await
|
|
14217
|
+
await fs20.unlink(p).catch(() => void 0);
|
|
13500
14218
|
}
|
|
13501
14219
|
if (buf.length === 0) {
|
|
13502
14220
|
return { ok: false, reason: "no image on clipboard" };
|
|
@@ -13595,50 +14313,6 @@ var init_clipboard = __esm({
|
|
|
13595
14313
|
}
|
|
13596
14314
|
});
|
|
13597
14315
|
|
|
13598
|
-
// src/tui/completion.ts
|
|
13599
|
-
function longestCommonPrefix(names) {
|
|
13600
|
-
if (names.length === 0) {
|
|
13601
|
-
return "";
|
|
13602
|
-
}
|
|
13603
|
-
let prefix = names[0] ?? "";
|
|
13604
|
-
for (let i = 1; i < names.length; i++) {
|
|
13605
|
-
const n = names[i] ?? "";
|
|
13606
|
-
let j = 0;
|
|
13607
|
-
while (j < prefix.length && j < n.length && prefix[j] === n[j]) {
|
|
13608
|
-
j += 1;
|
|
13609
|
-
}
|
|
13610
|
-
prefix = prefix.slice(0, j);
|
|
13611
|
-
if (prefix.length === 0) {
|
|
13612
|
-
break;
|
|
13613
|
-
}
|
|
13614
|
-
}
|
|
13615
|
-
return prefix;
|
|
13616
|
-
}
|
|
13617
|
-
function computeTabCompletion(args) {
|
|
13618
|
-
const { matches, firstLine: firstLine3 } = args;
|
|
13619
|
-
if (matches.length === 0) {
|
|
13620
|
-
return null;
|
|
13621
|
-
}
|
|
13622
|
-
const space = firstLine3.indexOf(" ");
|
|
13623
|
-
const typedPrefix = space === -1 ? firstLine3 : firstLine3.slice(0, space);
|
|
13624
|
-
const tail = space === -1 ? "" : firstLine3.slice(space);
|
|
13625
|
-
if (matches.length === 1) {
|
|
13626
|
-
const name = matches[0] ?? "";
|
|
13627
|
-
const suffix = tail.startsWith(" ") ? "" : " ";
|
|
13628
|
-
return name + suffix + tail;
|
|
13629
|
-
}
|
|
13630
|
-
const commonPrefix = longestCommonPrefix(matches);
|
|
13631
|
-
if (commonPrefix.length <= typedPrefix.length) {
|
|
13632
|
-
return null;
|
|
13633
|
-
}
|
|
13634
|
-
return commonPrefix + tail;
|
|
13635
|
-
}
|
|
13636
|
-
var init_completion = __esm({
|
|
13637
|
-
"src/tui/completion.ts"() {
|
|
13638
|
-
"use strict";
|
|
13639
|
-
}
|
|
13640
|
-
});
|
|
13641
|
-
|
|
13642
14316
|
// src/tui/reconnect-state.ts
|
|
13643
14317
|
function parseReattachResponse(result) {
|
|
13644
14318
|
const out = {};
|
|
@@ -13664,6 +14338,21 @@ function parseReattachResponse(result) {
|
|
|
13664
14338
|
}
|
|
13665
14339
|
return out;
|
|
13666
14340
|
}
|
|
14341
|
+
function shouldDriftSnap(args) {
|
|
14342
|
+
return !args.replayDraining && args.pendingTurns > 0 && args.queueSize === 0 && !args.ownTurnInFlight && !args.hasInFlightHead;
|
|
14343
|
+
}
|
|
14344
|
+
function computeAttachReconcile(args) {
|
|
14345
|
+
if (args.daemonTurnStartedAt !== void 0) {
|
|
14346
|
+
const delta2 = args.pendingTurns === 0 ? 1 : 0;
|
|
14347
|
+
return {
|
|
14348
|
+
pendingTurnsDelta: delta2,
|
|
14349
|
+
banner: "busy",
|
|
14350
|
+
busySince: args.daemonTurnStartedAt
|
|
14351
|
+
};
|
|
14352
|
+
}
|
|
14353
|
+
const delta = args.pendingTurns > 0 ? -args.pendingTurns : 0;
|
|
14354
|
+
return { pendingTurnsDelta: delta, banner: "ready" };
|
|
14355
|
+
}
|
|
13667
14356
|
var init_reconnect_state = __esm({
|
|
13668
14357
|
"src/tui/reconnect-state.ts"() {
|
|
13669
14358
|
"use strict";
|
|
@@ -13674,7 +14363,7 @@ var init_reconnect_state = __esm({
|
|
|
13674
14363
|
import { appendFileSync, statSync, renameSync } from "fs";
|
|
13675
14364
|
import { nanoid as nanoid3 } from "nanoid";
|
|
13676
14365
|
import termkit from "terminal-kit";
|
|
13677
|
-
import
|
|
14366
|
+
import fs21 from "fs/promises";
|
|
13678
14367
|
import path19 from "path";
|
|
13679
14368
|
function isReadonlyForbiddenEffect(effect) {
|
|
13680
14369
|
switch (effect.type) {
|
|
@@ -13800,6 +14489,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
13800
14489
|
let bufferedEvents = [];
|
|
13801
14490
|
let applyRenderEvent = null;
|
|
13802
14491
|
let teardownStarted = false;
|
|
14492
|
+
let replayDraining = false;
|
|
13803
14493
|
const appendRender = (event) => {
|
|
13804
14494
|
if (!event) {
|
|
13805
14495
|
return;
|
|
@@ -14002,7 +14692,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14002
14692
|
echo.flushed = true;
|
|
14003
14693
|
appendRender({
|
|
14004
14694
|
kind: "user-text",
|
|
14005
|
-
text: echo.
|
|
14695
|
+
text: echo.displayText,
|
|
14006
14696
|
attachments: echo.attachments
|
|
14007
14697
|
});
|
|
14008
14698
|
currentTurnEcho = echo;
|
|
@@ -14266,23 +14956,26 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14266
14956
|
if (globalHistory.length > config.tui.promptHistoryMaxEntries) {
|
|
14267
14957
|
globalHistory = globalHistory.slice(globalHistory.length - config.tui.promptHistoryMaxEntries);
|
|
14268
14958
|
}
|
|
14959
|
+
let displayHistory = [...history];
|
|
14269
14960
|
const dispatcher = new InputDispatcher({
|
|
14270
|
-
history: buildCombinedHistory(globalHistory,
|
|
14961
|
+
history: buildCombinedHistory(globalHistory, displayHistory)
|
|
14271
14962
|
});
|
|
14272
14963
|
dispatcherRef = dispatcher;
|
|
14273
14964
|
let livePeerHistoryRecording = false;
|
|
14274
|
-
const recordHistoryEntry = (entry) => {
|
|
14965
|
+
const recordHistoryEntry = (entry, displayEntry) => {
|
|
14275
14966
|
const trimmed = entry.replace(/\n+$/, "");
|
|
14276
14967
|
if (trimmed.length === 0) {
|
|
14277
14968
|
return;
|
|
14278
14969
|
}
|
|
14970
|
+
const trimmedDisplay = (displayEntry ?? entry).replace(/\n+$/, "");
|
|
14279
14971
|
const nextSession = appendEntry(history, trimmed);
|
|
14280
14972
|
const sessionChanged = nextSession !== history;
|
|
14281
14973
|
history = nextSession;
|
|
14974
|
+
displayHistory = appendEntry(displayHistory, trimmedDisplay);
|
|
14282
14975
|
const nextGlobal = appendEntry(globalHistory, trimmed, config.tui.promptHistoryMaxEntries);
|
|
14283
14976
|
const globalChanged = nextGlobal !== globalHistory;
|
|
14284
14977
|
globalHistory = nextGlobal;
|
|
14285
|
-
dispatcher.setHistory(buildCombinedHistory(globalHistory,
|
|
14978
|
+
dispatcher.setHistory(buildCombinedHistory(globalHistory, displayHistory));
|
|
14286
14979
|
if (sessionChanged) {
|
|
14287
14980
|
saveHistory(historyFile, history).catch(() => void 0);
|
|
14288
14981
|
}
|
|
@@ -14692,6 +15385,31 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14692
15385
|
resolvedChoice = { choice: choice2, sessions };
|
|
14693
15386
|
break;
|
|
14694
15387
|
}
|
|
15388
|
+
if (choice2.kind === "fork") {
|
|
15389
|
+
const decided2 = await runForkFlow(term, target, choice2, sessions);
|
|
15390
|
+
if (decided2.kind === "cancel") {
|
|
15391
|
+
screen.start({ skipFullscreen: true });
|
|
15392
|
+
screen.resumeRepaint();
|
|
15393
|
+
return;
|
|
15394
|
+
}
|
|
15395
|
+
if (decided2.kind === "back") {
|
|
15396
|
+
continue;
|
|
15397
|
+
}
|
|
15398
|
+
const synthetic = {
|
|
15399
|
+
kind: "attach",
|
|
15400
|
+
sessionId: decided2.ctx.sessionId,
|
|
15401
|
+
...decided2.ctx.agentId ? { agentId: decided2.ctx.agentId } : {}
|
|
15402
|
+
};
|
|
15403
|
+
resolvedChoice = { choice: synthetic, sessions };
|
|
15404
|
+
attachOverrides = {
|
|
15405
|
+
readonly: false,
|
|
15406
|
+
cwd: decided2.ctx.cwd
|
|
15407
|
+
};
|
|
15408
|
+
if (decided2.ctx.importAttachHint !== void 0) {
|
|
15409
|
+
attachOverrides.importAttachHint = decided2.ctx.importAttachHint;
|
|
15410
|
+
}
|
|
15411
|
+
break;
|
|
15412
|
+
}
|
|
14695
15413
|
const chosen = sessions.find((s) => s.sessionId === choice2.sessionId);
|
|
14696
15414
|
const isImportedFirstLaunch = chosen !== void 0 && !!chosen.importedFromMachine && !chosen.upstreamSessionId && choice2.readonly !== true;
|
|
14697
15415
|
if (!isImportedFirstLaunch) {
|
|
@@ -14783,16 +15501,16 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14783
15501
|
switch (effect.type) {
|
|
14784
15502
|
case "send":
|
|
14785
15503
|
if (config.tui.defaultEnterAction === "amend") {
|
|
14786
|
-
amendPrompt(effect.text, effect.attachments);
|
|
15504
|
+
amendPrompt(effect.text, effect.attachments, effect.displayText);
|
|
14787
15505
|
} else {
|
|
14788
|
-
enqueuePrompt(effect.text, effect.attachments);
|
|
15506
|
+
enqueuePrompt(effect.text, effect.attachments, effect.displayText);
|
|
14789
15507
|
}
|
|
14790
15508
|
return;
|
|
14791
15509
|
case "amend":
|
|
14792
15510
|
if (config.tui.defaultEnterAction === "amend") {
|
|
14793
|
-
enqueuePrompt(effect.text, effect.attachments);
|
|
15511
|
+
enqueuePrompt(effect.text, effect.attachments, effect.displayText);
|
|
14794
15512
|
} else {
|
|
14795
|
-
amendPrompt(effect.text, effect.attachments);
|
|
15513
|
+
amendPrompt(effect.text, effect.attachments, effect.displayText);
|
|
14796
15514
|
}
|
|
14797
15515
|
return;
|
|
14798
15516
|
case "queue-edit": {
|
|
@@ -14946,7 +15664,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14946
15664
|
continue;
|
|
14947
15665
|
}
|
|
14948
15666
|
try {
|
|
14949
|
-
const buf = await
|
|
15667
|
+
const buf = await fs21.readFile(token);
|
|
14950
15668
|
if (buf.length > MAX_ATTACHMENT_BYTES) {
|
|
14951
15669
|
screen.notify(
|
|
14952
15670
|
`image too large (${formatSize(buf.length)}, max ${formatSize(MAX_ATTACHMENT_BYTES)})`
|
|
@@ -15044,22 +15762,22 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15044
15762
|
refreshQueueDisplay();
|
|
15045
15763
|
}
|
|
15046
15764
|
}
|
|
15047
|
-
const enqueuePrompt = (text, attachments) => {
|
|
15765
|
+
const enqueuePrompt = (text, attachments, displayText) => {
|
|
15048
15766
|
screen.scrollToBottom();
|
|
15049
15767
|
if (handleBuiltinCommand(text)) {
|
|
15050
15768
|
return;
|
|
15051
15769
|
}
|
|
15052
|
-
recordHistoryEntry(text);
|
|
15053
|
-
void runPrompt(text, attachments);
|
|
15770
|
+
recordHistoryEntry(text, displayText);
|
|
15771
|
+
void runPrompt(text, attachments, displayText);
|
|
15054
15772
|
};
|
|
15055
|
-
const amendPrompt = (text, attachments) => {
|
|
15773
|
+
const amendPrompt = (text, attachments, displayText) => {
|
|
15056
15774
|
screen.scrollToBottom();
|
|
15057
15775
|
if (handleBuiltinCommand(text)) {
|
|
15058
15776
|
return;
|
|
15059
15777
|
}
|
|
15060
|
-
recordHistoryEntry(text);
|
|
15778
|
+
recordHistoryEntry(text, displayText);
|
|
15061
15779
|
if (!daemonSupportsAmend || currentHeadMessageId === void 0) {
|
|
15062
|
-
void runPrompt(text, attachments);
|
|
15780
|
+
void runPrompt(text, attachments, displayText);
|
|
15063
15781
|
return;
|
|
15064
15782
|
}
|
|
15065
15783
|
const target2 = currentHeadMessageId;
|
|
@@ -15070,7 +15788,12 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15070
15788
|
for (const a of attachments) {
|
|
15071
15789
|
blocks.push({ type: "image", data: a.data, mimeType: a.mimeType });
|
|
15072
15790
|
}
|
|
15073
|
-
const echo = {
|
|
15791
|
+
const echo = {
|
|
15792
|
+
text,
|
|
15793
|
+
displayText: displayText ?? text,
|
|
15794
|
+
attachments,
|
|
15795
|
+
flushed: false
|
|
15796
|
+
};
|
|
15074
15797
|
pendingEchoes.push(echo);
|
|
15075
15798
|
const popEcho = () => {
|
|
15076
15799
|
const idx = pendingEchoes.indexOf(echo);
|
|
@@ -15161,6 +15884,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15161
15884
|
toolsBlockEndedAt = null;
|
|
15162
15885
|
toolsBlockStopReason = null;
|
|
15163
15886
|
toolsExpanded = false;
|
|
15887
|
+
pendingEditMarks = [];
|
|
15164
15888
|
screen.clearScrollback();
|
|
15165
15889
|
return true;
|
|
15166
15890
|
case "/demo-plan": {
|
|
@@ -15256,7 +15980,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15256
15980
|
return false;
|
|
15257
15981
|
}
|
|
15258
15982
|
};
|
|
15259
|
-
const runPrompt = async (text, attachments) => {
|
|
15983
|
+
const runPrompt = async (text, attachments, displayText) => {
|
|
15260
15984
|
const userBlocks = [];
|
|
15261
15985
|
if (text.length > 0) {
|
|
15262
15986
|
userBlocks.push({ type: "text", text });
|
|
@@ -15265,7 +15989,12 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15265
15989
|
userBlocks.push({ type: "image", data: a.data, mimeType: a.mimeType });
|
|
15266
15990
|
}
|
|
15267
15991
|
adjustPendingTurns(1);
|
|
15268
|
-
const echo = {
|
|
15992
|
+
const echo = {
|
|
15993
|
+
text,
|
|
15994
|
+
displayText: displayText ?? text,
|
|
15995
|
+
attachments,
|
|
15996
|
+
flushed: false
|
|
15997
|
+
};
|
|
15269
15998
|
pendingEchoes.push(echo);
|
|
15270
15999
|
let cancelled = false;
|
|
15271
16000
|
turnInFlight = {
|
|
@@ -15351,7 +16080,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15351
16080
|
if (agentKey === null) {
|
|
15352
16081
|
return;
|
|
15353
16082
|
}
|
|
15354
|
-
const
|
|
16083
|
+
const w = screen.width();
|
|
16084
|
+
const lines = parseAgentMarkdown(
|
|
16085
|
+
agentBuffer,
|
|
16086
|
+
w > 0 ? { maxWidth: w } : void 0
|
|
16087
|
+
);
|
|
15355
16088
|
if (lines.length === 0) {
|
|
15356
16089
|
return;
|
|
15357
16090
|
}
|
|
@@ -15460,7 +16193,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15460
16193
|
toolsBlockStopReason = null;
|
|
15461
16194
|
renderToolsBlock();
|
|
15462
16195
|
};
|
|
15463
|
-
const recordToolCall = (id, title, status, errorText) => {
|
|
16196
|
+
const recordToolCall = (id, title, status, errorText, editDiff) => {
|
|
15464
16197
|
const wasNew = !toolStates.has(id);
|
|
15465
16198
|
const existing = toolStates.get(id);
|
|
15466
16199
|
const state = existing ?? {
|
|
@@ -15480,6 +16213,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15480
16213
|
if (errorText !== void 0) {
|
|
15481
16214
|
state.errorText = errorText;
|
|
15482
16215
|
}
|
|
16216
|
+
if (editDiff !== void 0) {
|
|
16217
|
+
state.editDiff = editDiff;
|
|
16218
|
+
}
|
|
15483
16219
|
toolStates.set(id, state);
|
|
15484
16220
|
if (wasNew) {
|
|
15485
16221
|
if (toolsBlockStartedAt === null) {
|
|
@@ -15490,6 +16226,45 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15490
16226
|
toolCallOrder.push(id);
|
|
15491
16227
|
}
|
|
15492
16228
|
};
|
|
16229
|
+
let pendingEditMarks = [];
|
|
16230
|
+
const maybeRenderEditDiff = (toolCallId) => {
|
|
16231
|
+
const mode = config.tui.showFileUpdates;
|
|
16232
|
+
if (mode === "none") {
|
|
16233
|
+
return;
|
|
16234
|
+
}
|
|
16235
|
+
const state = toolStates.get(toolCallId);
|
|
16236
|
+
if (!state?.editDiff || state.status !== "completed") {
|
|
16237
|
+
return;
|
|
16238
|
+
}
|
|
16239
|
+
if (mode === "diff") {
|
|
16240
|
+
const lines = formatEditDiffBlock(state.editDiff, "diff");
|
|
16241
|
+
if (lines.length > 0) {
|
|
16242
|
+
screen.upsertLines(`editdiff:${toolCallId}`, lines);
|
|
16243
|
+
}
|
|
16244
|
+
return;
|
|
16245
|
+
}
|
|
16246
|
+
pendingEditMarks.push({ toolCallId, diff: state.editDiff });
|
|
16247
|
+
};
|
|
16248
|
+
const flushPendingEditMarks = () => {
|
|
16249
|
+
if (pendingEditMarks.length === 0) {
|
|
16250
|
+
return;
|
|
16251
|
+
}
|
|
16252
|
+
let lastPath = null;
|
|
16253
|
+
for (const { toolCallId, diff } of pendingEditMarks) {
|
|
16254
|
+
if (diff.path && diff.path === lastPath) {
|
|
16255
|
+
continue;
|
|
16256
|
+
}
|
|
16257
|
+
const lines = formatEditDiffBlock(diff, "edit");
|
|
16258
|
+
if (lines.length === 0) {
|
|
16259
|
+
continue;
|
|
16260
|
+
}
|
|
16261
|
+
screen.upsertLines(`editdiff:${toolCallId}`, lines);
|
|
16262
|
+
if (diff.path) {
|
|
16263
|
+
lastPath = diff.path;
|
|
16264
|
+
}
|
|
16265
|
+
}
|
|
16266
|
+
pendingEditMarks = [];
|
|
16267
|
+
};
|
|
15493
16268
|
applyRenderEvent = (event) => {
|
|
15494
16269
|
if (event.kind === "available-commands") {
|
|
15495
16270
|
agentCommands = event.commands;
|
|
@@ -15561,17 +16336,22 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15561
16336
|
toolCallOrder.length = 0;
|
|
15562
16337
|
toolsExpanded = false;
|
|
15563
16338
|
toolsBlockEndedAt = null;
|
|
16339
|
+
pendingEditMarks = [];
|
|
15564
16340
|
startToolsBlock();
|
|
15565
16341
|
screen.redraw();
|
|
15566
16342
|
return;
|
|
15567
16343
|
}
|
|
15568
16344
|
if (event.kind === "agent-text") {
|
|
15569
16345
|
closeThought();
|
|
16346
|
+
flushPendingEditMarks();
|
|
15570
16347
|
appendAgentText(event.text);
|
|
15571
16348
|
return;
|
|
15572
16349
|
}
|
|
15573
16350
|
if (event.kind === "agent-thought") {
|
|
15574
16351
|
closeAgentText();
|
|
16352
|
+
if (viewPrefs.showThoughts) {
|
|
16353
|
+
flushPendingEditMarks();
|
|
16354
|
+
}
|
|
15575
16355
|
appendThought(event.text);
|
|
15576
16356
|
return;
|
|
15577
16357
|
}
|
|
@@ -15596,8 +16376,15 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15596
16376
|
if (event.kind === "tool-call") {
|
|
15597
16377
|
closeAgentText();
|
|
15598
16378
|
closeThought();
|
|
15599
|
-
recordToolCall(
|
|
16379
|
+
recordToolCall(
|
|
16380
|
+
event.toolCallId,
|
|
16381
|
+
event.title,
|
|
16382
|
+
event.status,
|
|
16383
|
+
void 0,
|
|
16384
|
+
event.editDiff
|
|
16385
|
+
);
|
|
15600
16386
|
renderToolsBlock();
|
|
16387
|
+
maybeRenderEditDiff(event.toolCallId);
|
|
15601
16388
|
return;
|
|
15602
16389
|
}
|
|
15603
16390
|
if (event.kind === "plan") {
|
|
@@ -15617,12 +16404,14 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15617
16404
|
event.toolCallId,
|
|
15618
16405
|
event.title,
|
|
15619
16406
|
event.status,
|
|
15620
|
-
event.errorText
|
|
16407
|
+
event.errorText,
|
|
16408
|
+
event.editDiff
|
|
15621
16409
|
);
|
|
15622
16410
|
if (event.upstreamInterrupted) {
|
|
15623
16411
|
upstreamInterruptedSeen = true;
|
|
15624
16412
|
}
|
|
15625
16413
|
renderToolsBlock();
|
|
16414
|
+
maybeRenderEditDiff(event.toolCallId);
|
|
15626
16415
|
return;
|
|
15627
16416
|
}
|
|
15628
16417
|
if (event.kind === "model-changed") {
|
|
@@ -15675,7 +16464,17 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15675
16464
|
toolsBlockStopReason = null;
|
|
15676
16465
|
toolsExpanded = false;
|
|
15677
16466
|
upstreamInterruptedSeen = false;
|
|
16467
|
+
pendingEditMarks = [];
|
|
15678
16468
|
screen.ensureSeparator();
|
|
16469
|
+
if (shouldDriftSnap({
|
|
16470
|
+
pendingTurns,
|
|
16471
|
+
queueSize: queueCache.size,
|
|
16472
|
+
ownTurnInFlight: turnInFlight !== null,
|
|
16473
|
+
hasInFlightHead: currentHeadMessageId !== void 0,
|
|
16474
|
+
replayDraining
|
|
16475
|
+
})) {
|
|
16476
|
+
adjustPendingTurns(-pendingTurns);
|
|
16477
|
+
}
|
|
15679
16478
|
}
|
|
15680
16479
|
};
|
|
15681
16480
|
const buffered = bufferedEvents;
|
|
@@ -15687,18 +16486,21 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15687
16486
|
}
|
|
15688
16487
|
}
|
|
15689
16488
|
screen.pauseRepaint();
|
|
16489
|
+
replayDraining = true;
|
|
15690
16490
|
try {
|
|
15691
16491
|
for (const event of buffered) {
|
|
15692
16492
|
applyRenderEvent(event);
|
|
15693
16493
|
}
|
|
15694
16494
|
} finally {
|
|
16495
|
+
replayDraining = false;
|
|
15695
16496
|
screen.resumeRepaint();
|
|
15696
16497
|
}
|
|
15697
16498
|
if (replayedPromptTexts.length > 0) {
|
|
15698
16499
|
const merged = mergeReplayedEntries(history, replayedPromptTexts);
|
|
15699
16500
|
if (merged !== history) {
|
|
15700
16501
|
history = merged;
|
|
15701
|
-
|
|
16502
|
+
displayHistory = mergeReplayedEntries(displayHistory, replayedPromptTexts);
|
|
16503
|
+
dispatcher.setHistory(buildCombinedHistory(globalHistory, displayHistory));
|
|
15702
16504
|
saveHistory(historyFile, history).catch(() => void 0);
|
|
15703
16505
|
}
|
|
15704
16506
|
}
|
|
@@ -15747,6 +16549,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15747
16549
|
toolsBlockEndedAt = null;
|
|
15748
16550
|
toolsBlockStopReason = null;
|
|
15749
16551
|
toolsExpanded = false;
|
|
16552
|
+
pendingEditMarks = [];
|
|
15750
16553
|
};
|
|
15751
16554
|
onDisconnectHook = () => {
|
|
15752
16555
|
screen.setBanner({ status: "disconnected", elapsedMs: void 0 });
|
|
@@ -15832,17 +16635,47 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15832
16635
|
}
|
|
15833
16636
|
]);
|
|
15834
16637
|
} else {
|
|
15835
|
-
|
|
15836
|
-
|
|
16638
|
+
replayDraining = true;
|
|
16639
|
+
try {
|
|
16640
|
+
for (const params of buffered2) {
|
|
16641
|
+
handleSessionUpdate(params);
|
|
16642
|
+
}
|
|
16643
|
+
} finally {
|
|
16644
|
+
replayDraining = false;
|
|
15837
16645
|
}
|
|
15838
16646
|
}
|
|
15839
|
-
if (fields
|
|
15840
|
-
|
|
16647
|
+
if (fields) {
|
|
16648
|
+
const reconcile = computeAttachReconcile({
|
|
16649
|
+
daemonTurnStartedAt: fields.turnStartedAt,
|
|
16650
|
+
pendingTurns
|
|
16651
|
+
});
|
|
16652
|
+
if (reconcile.pendingTurnsDelta !== 0) {
|
|
16653
|
+
adjustPendingTurns(reconcile.pendingTurnsDelta);
|
|
16654
|
+
}
|
|
16655
|
+
if (reconcile.banner === "busy" && reconcile.busySince !== void 0) {
|
|
16656
|
+
sessionBusySince = reconcile.busySince;
|
|
16657
|
+
screen.setBanner({
|
|
16658
|
+
status: "busy",
|
|
16659
|
+
elapsedMs: Date.now() - reconcile.busySince
|
|
16660
|
+
});
|
|
16661
|
+
if (sessionElapsedTimer === null) {
|
|
16662
|
+
sessionElapsedTimer = setInterval(() => {
|
|
16663
|
+
if (sessionBusySince === null || screenRef === null) {
|
|
16664
|
+
return;
|
|
16665
|
+
}
|
|
16666
|
+
screenRef.setBanner({ elapsedMs: Date.now() - sessionBusySince });
|
|
16667
|
+
renderToolsBlock();
|
|
16668
|
+
}, 1e3);
|
|
16669
|
+
}
|
|
16670
|
+
} else {
|
|
16671
|
+
screen.setBanner({ status: "ready", elapsedMs: void 0 });
|
|
16672
|
+
}
|
|
16673
|
+
} else {
|
|
16674
|
+
screen.setBanner({
|
|
16675
|
+
status: pendingTurns > 0 ? "busy" : "ready",
|
|
16676
|
+
elapsedMs: pendingTurns > 0 ? 0 : void 0
|
|
16677
|
+
});
|
|
15841
16678
|
}
|
|
15842
|
-
screen.setBanner({
|
|
15843
|
-
status: pendingTurns > 0 ? "busy" : "ready",
|
|
15844
|
-
elapsedMs: pendingTurns > 0 ? 0 : void 0
|
|
15845
|
-
});
|
|
15846
16679
|
};
|
|
15847
16680
|
conn.onClose((err) => {
|
|
15848
16681
|
if (err) {
|
|
@@ -15906,6 +16739,16 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
15906
16739
|
}
|
|
15907
16740
|
return newCtx(opts, cwd, config);
|
|
15908
16741
|
}
|
|
16742
|
+
if (choice.kind === "fork") {
|
|
16743
|
+
const decided = await runForkFlow(term, target, choice, sessions);
|
|
16744
|
+
if (decided.kind === "cancel") {
|
|
16745
|
+
return null;
|
|
16746
|
+
}
|
|
16747
|
+
if (decided.kind === "back") {
|
|
16748
|
+
continue;
|
|
16749
|
+
}
|
|
16750
|
+
return decided.ctx;
|
|
16751
|
+
}
|
|
15909
16752
|
opts.readonly = choice.readonly === true;
|
|
15910
16753
|
const chosen = sessions.find((s) => s.sessionId === choice.sessionId);
|
|
15911
16754
|
const isImportedFirstLaunch = chosen !== void 0 && !!chosen.importedFromMachine && !chosen.upstreamSessionId && !opts.readonly;
|
|
@@ -15966,6 +16809,51 @@ async function runImportedFirstLaunchFlow(term, chosen, choice, opts) {
|
|
|
15966
16809
|
};
|
|
15967
16810
|
}
|
|
15968
16811
|
}
|
|
16812
|
+
async function runForkFlow(term, target, choice, sessions) {
|
|
16813
|
+
const source = sessions.find((s) => s.sessionId === choice.sourceSessionId);
|
|
16814
|
+
const isForeignNeverLaunched = !!choice.sourceImportedFromMachine && !choice.sourceUpstreamSessionId;
|
|
16815
|
+
let cwd = choice.sourceCwd;
|
|
16816
|
+
if (isForeignNeverLaunched) {
|
|
16817
|
+
if (!source) {
|
|
16818
|
+
return { kind: "back" };
|
|
16819
|
+
}
|
|
16820
|
+
const cwdResult = await promptForImportCwd(term, source);
|
|
16821
|
+
if (cwdResult.kind === "cancel") {
|
|
16822
|
+
return { kind: "cancel" };
|
|
16823
|
+
}
|
|
16824
|
+
if (cwdResult.kind === "back") {
|
|
16825
|
+
return { kind: "back" };
|
|
16826
|
+
}
|
|
16827
|
+
cwd = cwdResult.path;
|
|
16828
|
+
}
|
|
16829
|
+
let result;
|
|
16830
|
+
try {
|
|
16831
|
+
result = await forkSession(
|
|
16832
|
+
target,
|
|
16833
|
+
choice.sourceSessionId,
|
|
16834
|
+
isForeignNeverLaunched ? { cwd } : {}
|
|
16835
|
+
);
|
|
16836
|
+
} catch (err) {
|
|
16837
|
+
term.red(`
|
|
16838
|
+
fork failed: ${err.message}
|
|
16839
|
+
`);
|
|
16840
|
+
return { kind: "cancel" };
|
|
16841
|
+
}
|
|
16842
|
+
return {
|
|
16843
|
+
kind: "ctx",
|
|
16844
|
+
ctx: {
|
|
16845
|
+
sessionId: result.sessionId,
|
|
16846
|
+
agentId: choice.sourceAgentId ?? "",
|
|
16847
|
+
cwd,
|
|
16848
|
+
// For foreign-never-launched forks, the daemon stamped the chosen
|
|
16849
|
+
// cwd onto meta.json via the POST body, but the very first attach
|
|
16850
|
+
// still goes through the import-reseed path (upstreamSessionId=""),
|
|
16851
|
+
// and importAttachHint is what makes attachManagerHooks persist
|
|
16852
|
+
// the local cwd over the bundle's recorded one.
|
|
16853
|
+
...isForeignNeverLaunched ? { importAttachHint: { agentId: choice.sourceAgentId ?? "", cwd } } : {}
|
|
16854
|
+
}
|
|
16855
|
+
};
|
|
16856
|
+
}
|
|
15969
16857
|
function newCtx(opts, cwd, config) {
|
|
15970
16858
|
return {
|
|
15971
16859
|
sessionId: "__new__",
|
|
@@ -16174,7 +17062,7 @@ var init_tui = __esm({
|
|
|
16174
17062
|
// src/cli.ts
|
|
16175
17063
|
import { readFileSync as readFileSync2 } from "fs";
|
|
16176
17064
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16177
|
-
import { dirname as
|
|
17065
|
+
import { dirname as dirname7, resolve as resolve6 } from "path";
|
|
16178
17066
|
|
|
16179
17067
|
// src/cli/parse-args.ts
|
|
16180
17068
|
var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -16363,7 +17251,7 @@ import { setTimeout as sleep2 } from "timers/promises";
|
|
|
16363
17251
|
import chalk from "chalk";
|
|
16364
17252
|
|
|
16365
17253
|
// src/daemon/server.ts
|
|
16366
|
-
import * as
|
|
17254
|
+
import * as fs16 from "fs";
|
|
16367
17255
|
import * as fsp8 from "fs/promises";
|
|
16368
17256
|
import Fastify from "fastify";
|
|
16369
17257
|
import websocketPlugin from "@fastify/websocket";
|
|
@@ -16384,6 +17272,7 @@ import createPinoRoll from "pino-roll";
|
|
|
16384
17272
|
|
|
16385
17273
|
// src/core/registry.ts
|
|
16386
17274
|
init_paths();
|
|
17275
|
+
init_json_store();
|
|
16387
17276
|
import * as fs6 from "fs/promises";
|
|
16388
17277
|
import * as path5 from "path";
|
|
16389
17278
|
import { z as z2 } from "zod";
|
|
@@ -17024,54 +17913,26 @@ var Registry = class {
|
|
|
17024
17913
|
return cached2;
|
|
17025
17914
|
}
|
|
17026
17915
|
async readDiskCache() {
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
if (e.code === "ENOENT") {
|
|
17033
|
-
return void 0;
|
|
17034
|
-
}
|
|
17035
|
-
throw err;
|
|
17916
|
+
const parsed = await readJsonSafe(
|
|
17917
|
+
paths.registryCache()
|
|
17918
|
+
);
|
|
17919
|
+
if (!parsed || typeof parsed.fetchedAt !== "number" || parsed.data === void 0) {
|
|
17920
|
+
return void 0;
|
|
17036
17921
|
}
|
|
17037
17922
|
try {
|
|
17038
|
-
const parsed = JSON.parse(text);
|
|
17039
|
-
if (typeof parsed.fetchedAt !== "number" || parsed.data === void 0) {
|
|
17040
|
-
return void 0;
|
|
17041
|
-
}
|
|
17042
17923
|
const data = RegistryDocument.parse(parsed.data);
|
|
17043
17924
|
return { fetchedAt: parsed.fetchedAt, raw: parsed.data, data };
|
|
17044
17925
|
} catch {
|
|
17045
17926
|
return void 0;
|
|
17046
17927
|
}
|
|
17047
17928
|
}
|
|
17048
|
-
// Atomic write: dump to a sibling temp path, then rename onto the
|
|
17049
|
-
// target. POSIX rename is atomic within a filesystem, so readers
|
|
17050
|
-
// either see the old file or the fully-written new file — never a
|
|
17051
|
-
// truncated middle. This also makes simultaneous writers safe
|
|
17052
|
-
// without a lock file: the loser of the rename race just gets its
|
|
17053
|
-
// version replaced by the winner's.
|
|
17054
17929
|
async writeDiskCache(cache) {
|
|
17055
|
-
await
|
|
17056
|
-
|
|
17057
|
-
|
|
17058
|
-
|
|
17059
|
-
{ fetchedAt: cache.fetchedAt, data: cache.raw },
|
|
17060
|
-
null,
|
|
17061
|
-
2
|
|
17062
|
-
) + "\n";
|
|
17063
|
-
try {
|
|
17064
|
-
await fs6.writeFile(tmp, body, "utf8");
|
|
17065
|
-
await fs6.rename(tmp, final);
|
|
17066
|
-
} catch (err) {
|
|
17067
|
-
await fs6.unlink(tmp).catch(() => void 0);
|
|
17068
|
-
throw err;
|
|
17069
|
-
}
|
|
17930
|
+
await writeJsonAtomic(paths.registryCache(), {
|
|
17931
|
+
fetchedAt: cache.fetchedAt,
|
|
17932
|
+
data: cache.raw
|
|
17933
|
+
});
|
|
17070
17934
|
}
|
|
17071
17935
|
};
|
|
17072
|
-
function randSuffix() {
|
|
17073
|
-
return Math.random().toString(36).slice(2, 10);
|
|
17074
|
-
}
|
|
17075
17936
|
function npxPackageBasename(agent) {
|
|
17076
17937
|
const pkg = agent.distribution.npx?.package;
|
|
17077
17938
|
if (!pkg) {
|
|
@@ -17384,6 +18245,7 @@ init_session();
|
|
|
17384
18245
|
|
|
17385
18246
|
// src/core/session-store.ts
|
|
17386
18247
|
init_paths();
|
|
18248
|
+
init_json_store();
|
|
17387
18249
|
import * as fs8 from "fs/promises";
|
|
17388
18250
|
import * as path6 from "path";
|
|
17389
18251
|
import { customAlphabet as customAlphabet2 } from "nanoid";
|
|
@@ -17469,6 +18331,12 @@ var SessionRecord = z4.object({
|
|
|
17469
18331
|
// Set when this session was spawned as a child by a transformer via
|
|
17470
18332
|
// hydra-acp/spawn_child_session. Points to the spawning session's id.
|
|
17471
18333
|
parentSessionId: z4.string().optional(),
|
|
18334
|
+
// Set when this session was created by hydra-acp/fork_session.
|
|
18335
|
+
// forkedFromSessionId points to the local source session; forkedFromMessageId
|
|
18336
|
+
// is the resolved forkAt — the messageId of the turn_complete the slice
|
|
18337
|
+
// ended at. Kept so future UI can show "branched from turn N of session X".
|
|
18338
|
+
forkedFromSessionId: z4.string().optional(),
|
|
18339
|
+
forkedFromMessageId: z4.string().optional(),
|
|
17472
18340
|
// clientInfo from the process that issued session/new. Picker and
|
|
17473
18341
|
// `sessions list` use this to hide cat-style ancillary sessions by
|
|
17474
18342
|
// default; carried in meta.json so cold sessions filter the same way.
|
|
@@ -17485,30 +18353,21 @@ function assertSafeId(id) {
|
|
|
17485
18353
|
var SessionStore = class {
|
|
17486
18354
|
async write(record) {
|
|
17487
18355
|
assertSafeId(record.sessionId);
|
|
17488
|
-
await fs8.mkdir(paths.sessionDir(record.sessionId), { recursive: true });
|
|
17489
18356
|
const full = { version: 1, ...record };
|
|
17490
|
-
await
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
{ encoding: "utf8", mode: 384 }
|
|
17494
|
-
);
|
|
18357
|
+
await writeJsonAtomic(paths.sessionFile(record.sessionId), full, {
|
|
18358
|
+
mode: 384
|
|
18359
|
+
});
|
|
17495
18360
|
}
|
|
17496
18361
|
async read(sessionId) {
|
|
17497
18362
|
if (!SESSION_ID_PATTERN.test(sessionId)) {
|
|
17498
18363
|
return void 0;
|
|
17499
18364
|
}
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
} catch (err) {
|
|
17504
|
-
const e = err;
|
|
17505
|
-
if (e.code === "ENOENT") {
|
|
17506
|
-
return void 0;
|
|
17507
|
-
}
|
|
17508
|
-
throw err;
|
|
18365
|
+
const parsed = await readJsonSafe(paths.sessionFile(sessionId));
|
|
18366
|
+
if (parsed === void 0) {
|
|
18367
|
+
return void 0;
|
|
17509
18368
|
}
|
|
17510
18369
|
try {
|
|
17511
|
-
return SessionRecord.parse(
|
|
18370
|
+
return SessionRecord.parse(parsed);
|
|
17512
18371
|
} catch {
|
|
17513
18372
|
return void 0;
|
|
17514
18373
|
}
|
|
@@ -17595,6 +18454,8 @@ function recordFromMemorySession(args) {
|
|
|
17595
18454
|
agentModels: args.agentModels,
|
|
17596
18455
|
pendingHistorySync: args.pendingHistorySync,
|
|
17597
18456
|
parentSessionId: args.parentSessionId,
|
|
18457
|
+
forkedFromSessionId: args.forkedFromSessionId,
|
|
18458
|
+
forkedFromMessageId: args.forkedFromMessageId,
|
|
17598
18459
|
originatingClient: args.originatingClient,
|
|
17599
18460
|
createdAt: args.createdAt ?? now,
|
|
17600
18461
|
updatedAt: args.updatedAt ?? now
|
|
@@ -17721,6 +18582,19 @@ var HistoryStore = class {
|
|
|
17721
18582
|
}
|
|
17722
18583
|
return out;
|
|
17723
18584
|
}
|
|
18585
|
+
// Wait for every pending append/rewrite/compact across all sessions to
|
|
18586
|
+
// settle. Daemon shutdown calls this after closing sessions so the final
|
|
18587
|
+
// turn_complete(interrupted) emitted by markClosed reaches disk before
|
|
18588
|
+
// the process exits — without this, history-replay attaches after a
|
|
18589
|
+
// restart see an unmatched prompt_received and leak pendingTurns on
|
|
18590
|
+
// every client.
|
|
18591
|
+
async flushAll() {
|
|
18592
|
+
const pending = [...this.writeQueues.values()];
|
|
18593
|
+
if (pending.length === 0) {
|
|
18594
|
+
return;
|
|
18595
|
+
}
|
|
18596
|
+
await Promise.allSettled(pending);
|
|
18597
|
+
}
|
|
17724
18598
|
async delete(sessionId) {
|
|
17725
18599
|
if (!SESSION_ID_PATTERN2.test(sessionId)) {
|
|
17726
18600
|
return;
|
|
@@ -17756,11 +18630,96 @@ var HistoryStore = class {
|
|
|
17756
18630
|
});
|
|
17757
18631
|
return task$;
|
|
17758
18632
|
}
|
|
17759
|
-
};
|
|
18633
|
+
};
|
|
18634
|
+
|
|
18635
|
+
// src/core/session-manager.ts
|
|
18636
|
+
init_paths();
|
|
18637
|
+
init_history();
|
|
18638
|
+
|
|
18639
|
+
// src/core/bundle.ts
|
|
18640
|
+
import { z as z5 } from "zod";
|
|
18641
|
+
var HistoryEntrySchema = z5.object({
|
|
18642
|
+
method: z5.string(),
|
|
18643
|
+
params: z5.unknown(),
|
|
18644
|
+
recordedAt: z5.number()
|
|
18645
|
+
});
|
|
18646
|
+
var BundleSession = z5.object({
|
|
18647
|
+
// The exporter's local id. Regenerated fresh on import (sessionId is
|
|
18648
|
+
// the local namespace; lineageId is what survives across hops).
|
|
18649
|
+
sessionId: z5.string(),
|
|
18650
|
+
// Required on bundles — the export path backfills if the source
|
|
18651
|
+
// record was written before lineageId existed.
|
|
18652
|
+
lineageId: z5.string(),
|
|
18653
|
+
// The exporter's agent-side session id at export time. Carried so
|
|
18654
|
+
// importers can persist it as a breadcrumb (and, eventually, as the
|
|
18655
|
+
// handle a "connect back to origin" feature would need). Omitted on
|
|
18656
|
+
// bundles whose source record never bound to an agent (e.g. a
|
|
18657
|
+
// re-export of an imported, not-yet-attached session).
|
|
18658
|
+
upstreamSessionId: z5.string().optional(),
|
|
18659
|
+
agentId: z5.string(),
|
|
18660
|
+
cwd: z5.string(),
|
|
18661
|
+
title: z5.string().optional(),
|
|
18662
|
+
currentModel: z5.string().optional(),
|
|
18663
|
+
currentMode: z5.string().optional(),
|
|
18664
|
+
currentUsage: PersistedUsage.optional(),
|
|
18665
|
+
agentCommands: z5.array(PersistedAgentCommand).optional(),
|
|
18666
|
+
agentModes: z5.array(PersistedAgentMode).optional(),
|
|
18667
|
+
createdAt: z5.string(),
|
|
18668
|
+
updatedAt: z5.string()
|
|
18669
|
+
});
|
|
18670
|
+
var Bundle = z5.object({
|
|
18671
|
+
version: z5.literal(1),
|
|
18672
|
+
exportedAt: z5.string(),
|
|
18673
|
+
exportedFrom: z5.object({
|
|
18674
|
+
hydraVersion: z5.string(),
|
|
18675
|
+
machine: z5.string(),
|
|
18676
|
+
// Externally-reachable name (and optional ":port") for the exporting
|
|
18677
|
+
// daemon, sourced from config.daemon.publicHost (or daemon.host when
|
|
18678
|
+
// non-loopback). Carried so an importer can construct a hydra:// URL
|
|
18679
|
+
// that dials back to the origin — e.g. over Tailscale. Omitted when
|
|
18680
|
+
// the exporter has no routable address; never falls back to loopback.
|
|
18681
|
+
hydraHost: z5.string().optional()
|
|
18682
|
+
}),
|
|
18683
|
+
session: BundleSession,
|
|
18684
|
+
history: z5.array(HistoryEntrySchema),
|
|
18685
|
+
promptHistory: z5.array(z5.string()).optional()
|
|
18686
|
+
});
|
|
18687
|
+
function encodeBundle(params) {
|
|
18688
|
+
const bundle = {
|
|
18689
|
+
version: 1,
|
|
18690
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18691
|
+
exportedFrom: {
|
|
18692
|
+
hydraVersion: params.hydraVersion,
|
|
18693
|
+
machine: params.machine,
|
|
18694
|
+
...params.hydraHost !== void 0 && params.hydraHost.length > 0 ? { hydraHost: params.hydraHost } : {}
|
|
18695
|
+
},
|
|
18696
|
+
session: {
|
|
18697
|
+
sessionId: params.record.sessionId,
|
|
18698
|
+
lineageId: params.record.lineageId,
|
|
18699
|
+
...params.record.upstreamSessionId ? { upstreamSessionId: params.record.upstreamSessionId } : {},
|
|
18700
|
+
agentId: params.record.agentId,
|
|
18701
|
+
cwd: params.record.cwd,
|
|
18702
|
+
...params.record.title !== void 0 ? { title: params.record.title } : {},
|
|
18703
|
+
...params.record.currentModel !== void 0 ? { currentModel: params.record.currentModel } : {},
|
|
18704
|
+
...params.record.currentMode !== void 0 ? { currentMode: params.record.currentMode } : {},
|
|
18705
|
+
...params.record.currentUsage !== void 0 ? { currentUsage: params.record.currentUsage } : {},
|
|
18706
|
+
...params.record.agentCommands !== void 0 ? { agentCommands: params.record.agentCommands } : {},
|
|
18707
|
+
...params.record.agentModes !== void 0 ? { agentModes: params.record.agentModes } : {},
|
|
18708
|
+
createdAt: params.record.createdAt,
|
|
18709
|
+
updatedAt: params.record.updatedAt
|
|
18710
|
+
},
|
|
18711
|
+
history: params.history
|
|
18712
|
+
};
|
|
18713
|
+
if (params.promptHistory !== void 0) {
|
|
18714
|
+
bundle.promptHistory = params.promptHistory;
|
|
18715
|
+
}
|
|
18716
|
+
return bundle;
|
|
18717
|
+
}
|
|
18718
|
+
function decodeBundle(raw) {
|
|
18719
|
+
return Bundle.parse(raw);
|
|
18720
|
+
}
|
|
17760
18721
|
|
|
17761
18722
|
// src/core/session-manager.ts
|
|
17762
|
-
init_paths();
|
|
17763
|
-
init_history();
|
|
17764
18723
|
init_types();
|
|
17765
18724
|
init_hydra_version();
|
|
17766
18725
|
init_queue_store();
|
|
@@ -18027,6 +18986,8 @@ var SessionManager = class {
|
|
|
18027
18986
|
firstPromptSeeded: !!params.title,
|
|
18028
18987
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
18029
18988
|
originatingClient: params.originatingClient,
|
|
18989
|
+
forkedFromSessionId: params.forkedFromSessionId,
|
|
18990
|
+
forkedFromMessageId: params.forkedFromMessageId,
|
|
18030
18991
|
extensionCommands: this.extensionCommands
|
|
18031
18992
|
});
|
|
18032
18993
|
await this.attachManagerHooks(session);
|
|
@@ -18095,6 +19056,8 @@ var SessionManager = class {
|
|
|
18095
19056
|
firstPromptSeeded: !!params.title,
|
|
18096
19057
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
18097
19058
|
originatingClient: params.originatingClient,
|
|
19059
|
+
forkedFromSessionId: params.forkedFromSessionId,
|
|
19060
|
+
forkedFromMessageId: params.forkedFromMessageId,
|
|
18098
19061
|
extensionCommands: this.extensionCommands
|
|
18099
19062
|
});
|
|
18100
19063
|
await this.attachManagerHooks(session);
|
|
@@ -18445,7 +19408,9 @@ var SessionManager = class {
|
|
|
18445
19408
|
agentModels: record.agentModels,
|
|
18446
19409
|
createdAt: record.createdAt,
|
|
18447
19410
|
pendingHistorySync: record.pendingHistorySync,
|
|
18448
|
-
originatingClient: record.originatingClient
|
|
19411
|
+
originatingClient: record.originatingClient,
|
|
19412
|
+
forkedFromSessionId: record.forkedFromSessionId,
|
|
19413
|
+
forkedFromMessageId: record.forkedFromMessageId
|
|
18449
19414
|
};
|
|
18450
19415
|
}
|
|
18451
19416
|
async clearPendingHistorySync(sessionId) {
|
|
@@ -18546,6 +19511,8 @@ var SessionManager = class {
|
|
|
18546
19511
|
currentModel: session.currentModel,
|
|
18547
19512
|
currentUsage: session.currentUsage,
|
|
18548
19513
|
parentSessionId: session.parentSessionId,
|
|
19514
|
+
forkedFromSessionId: session.forkedFromSessionId,
|
|
19515
|
+
forkedFromMessageId: session.forkedFromMessageId,
|
|
18549
19516
|
originatingClient: session.originatingClient,
|
|
18550
19517
|
updatedAt: used,
|
|
18551
19518
|
attachedClients: session.attachedCount,
|
|
@@ -18576,6 +19543,8 @@ var SessionManager = class {
|
|
|
18576
19543
|
importedFromMachine: r.importedFromMachine,
|
|
18577
19544
|
importedFromUpstreamSessionId: r.importedFromUpstreamSessionId,
|
|
18578
19545
|
parentSessionId: r.parentSessionId,
|
|
19546
|
+
forkedFromSessionId: r.forkedFromSessionId,
|
|
19547
|
+
forkedFromMessageId: r.forkedFromMessageId,
|
|
18579
19548
|
originatingClient: r.originatingClient,
|
|
18580
19549
|
updatedAt: used,
|
|
18581
19550
|
attachedClients: 0,
|
|
@@ -18663,10 +19632,114 @@ var SessionManager = class {
|
|
|
18663
19632
|
replaced: false
|
|
18664
19633
|
};
|
|
18665
19634
|
}
|
|
18666
|
-
//
|
|
18667
|
-
//
|
|
19635
|
+
// Branch an existing local session into a new one that shares context
|
|
19636
|
+
// up to the chosen turn boundary and diverges from there. Composes the
|
|
19637
|
+
// import pipeline: synthesizes a Bundle from the source's record and
|
|
19638
|
+
// sliced history, mints a fresh lineageId, then writes the new record
|
|
19639
|
+
// via writeImportedRecord with forked* breadcrumbs instead of
|
|
19640
|
+
// imported*. The fork carries upstreamSessionId="" so the first attach
|
|
19641
|
+
// triggers seedFromImport — same wire shape as an imported session.
|
|
19642
|
+
//
|
|
19643
|
+
// forkAt defaults to the messageId of the source's most recent
|
|
19644
|
+
// turn_complete; explicit forkAt must reference a session/update
|
|
19645
|
+
// entry that's present in the source's history.jsonl. Cutting at a
|
|
19646
|
+
// completed turn excludes any in-flight prompt by construction
|
|
19647
|
+
// (history.jsonl is appended serially per session), so no locking
|
|
19648
|
+
// against the live source is needed.
|
|
19649
|
+
//
|
|
19650
|
+
// agentId defaults to the source's agent. Overriding to a different
|
|
19651
|
+
// agent scrubs agent-specific state from the fork (model, mode,
|
|
19652
|
+
// usage, agent-emitted commands/modes/models) so the new agent boots
|
|
19653
|
+
// clean — title and conversation transcript are agent-agnostic and
|
|
19654
|
+
// are kept.
|
|
19655
|
+
async forkSession(sourceSessionId, opts = {}) {
|
|
19656
|
+
const sourceRecord = await this.store.read(sourceSessionId);
|
|
19657
|
+
if (!sourceRecord) {
|
|
19658
|
+
const err = new Error(`source session not found: ${sourceSessionId}`);
|
|
19659
|
+
err.code = JsonRpcErrorCodes.SessionNotFound;
|
|
19660
|
+
throw err;
|
|
19661
|
+
}
|
|
19662
|
+
const targetAgentId = opts.agentId ?? sourceRecord.agentId;
|
|
19663
|
+
const crossAgent = targetAgentId !== sourceRecord.agentId;
|
|
19664
|
+
if (crossAgent) {
|
|
19665
|
+
const def = await this.registry.getAgent(targetAgentId);
|
|
19666
|
+
if (!def) {
|
|
19667
|
+
const err = new Error(
|
|
19668
|
+
`agent ${targetAgentId} not found in registry`
|
|
19669
|
+
);
|
|
19670
|
+
err.code = JsonRpcErrorCodes.AgentNotInstalled;
|
|
19671
|
+
throw err;
|
|
19672
|
+
}
|
|
19673
|
+
}
|
|
19674
|
+
const sourceHistory = await this.histories.load(sourceSessionId).catch(() => []);
|
|
19675
|
+
let cutoffIndex;
|
|
19676
|
+
let forkedAt;
|
|
19677
|
+
if (opts.forkAt !== void 0) {
|
|
19678
|
+
cutoffIndex = findMessageIdIndex(sourceHistory, opts.forkAt);
|
|
19679
|
+
if (cutoffIndex < 0) {
|
|
19680
|
+
const err = new Error(
|
|
19681
|
+
`forkAt messageId not found in source history: ${opts.forkAt}`
|
|
19682
|
+
);
|
|
19683
|
+
err.code = JsonRpcErrorCodes.InvalidParams;
|
|
19684
|
+
throw err;
|
|
19685
|
+
}
|
|
19686
|
+
forkedAt = opts.forkAt;
|
|
19687
|
+
} else {
|
|
19688
|
+
const found = findLastTurnComplete(sourceHistory);
|
|
19689
|
+
if (!found) {
|
|
19690
|
+
const err = new Error(
|
|
19691
|
+
`source session ${sourceSessionId} has no completed turns to fork from`
|
|
19692
|
+
);
|
|
19693
|
+
err.code = JsonRpcErrorCodes.InvalidParams;
|
|
19694
|
+
throw err;
|
|
19695
|
+
}
|
|
19696
|
+
cutoffIndex = found.index;
|
|
19697
|
+
forkedAt = found.messageId;
|
|
19698
|
+
}
|
|
19699
|
+
const slicedHistory = sourceHistory.slice(0, cutoffIndex + 1);
|
|
19700
|
+
const promptHistory = await loadPromptHistorySafely(sourceSessionId);
|
|
19701
|
+
const recordForBundle = {
|
|
19702
|
+
...sourceRecord,
|
|
19703
|
+
lineageId: generateLineageId(),
|
|
19704
|
+
agentId: targetAgentId,
|
|
19705
|
+
...crossAgent ? {
|
|
19706
|
+
currentModel: void 0,
|
|
19707
|
+
currentMode: void 0,
|
|
19708
|
+
currentUsage: void 0,
|
|
19709
|
+
agentCommands: void 0,
|
|
19710
|
+
agentModes: void 0,
|
|
19711
|
+
agentModels: void 0
|
|
19712
|
+
} : {}
|
|
19713
|
+
};
|
|
19714
|
+
const bundle = encodeBundle({
|
|
19715
|
+
record: recordForBundle,
|
|
19716
|
+
history: slicedHistory,
|
|
19717
|
+
promptHistory: promptHistory.length > 0 ? promptHistory : void 0,
|
|
19718
|
+
hydraVersion: HYDRA_VERSION,
|
|
19719
|
+
machine: os3.hostname()
|
|
19720
|
+
});
|
|
19721
|
+
const newId = `${HYDRA_SESSION_PREFIX}${generateRawSessionId()}`;
|
|
19722
|
+
await this.writeImportedRecord({
|
|
19723
|
+
sessionId: newId,
|
|
19724
|
+
bundle,
|
|
19725
|
+
cwd: opts.cwd,
|
|
19726
|
+
forkedFromSessionId: sourceSessionId,
|
|
19727
|
+
forkedFromMessageId: forkedAt
|
|
19728
|
+
});
|
|
19729
|
+
return {
|
|
19730
|
+
sessionId: newId,
|
|
19731
|
+
forkedFromSessionId: sourceSessionId,
|
|
19732
|
+
forkedAt
|
|
19733
|
+
};
|
|
19734
|
+
}
|
|
19735
|
+
// Write the imported (or forked) bundle's history.jsonl, prompt-history
|
|
19736
|
+
// (if present), and meta.json. upstreamSessionId is left empty as the
|
|
18668
19737
|
// marker that the first attach should bootstrap a fresh agent and
|
|
18669
|
-
// run seedFromImport rather than calling session/load.
|
|
19738
|
+
// run seedFromImport rather than calling session/load. When
|
|
19739
|
+
// forkedFromSessionId is set, the record is marked as a local fork
|
|
19740
|
+
// (forked* fields populated) instead of a cross-machine import
|
|
19741
|
+
// (imported* fields populated) — both share the seed-on-first-attach
|
|
19742
|
+
// wire shape but trace differently in list views.
|
|
18670
19743
|
async writeImportedRecord(args) {
|
|
18671
19744
|
await this.histories.rewrite(
|
|
18672
19745
|
args.sessionId,
|
|
@@ -18683,14 +19756,20 @@ var SessionManager = class {
|
|
|
18683
19756
|
).catch(() => void 0);
|
|
18684
19757
|
}
|
|
18685
19758
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19759
|
+
const isFork = args.forkedFromSessionId !== void 0;
|
|
18686
19760
|
await this.enqueueMetaWrite(args.sessionId, async () => {
|
|
18687
19761
|
await this.store.write({
|
|
18688
19762
|
sessionId: args.sessionId,
|
|
18689
19763
|
lineageId: args.bundle.session.lineageId,
|
|
18690
19764
|
upstreamSessionId: "",
|
|
18691
|
-
|
|
18692
|
-
|
|
18693
|
-
|
|
19765
|
+
...isFork ? {
|
|
19766
|
+
forkedFromSessionId: args.forkedFromSessionId,
|
|
19767
|
+
forkedFromMessageId: args.forkedFromMessageId
|
|
19768
|
+
} : {
|
|
19769
|
+
importedFromSessionId: args.bundle.session.sessionId,
|
|
19770
|
+
importedFromUpstreamSessionId: args.bundle.session.upstreamSessionId,
|
|
19771
|
+
importedFromMachine: args.bundle.exportedFrom.machine
|
|
19772
|
+
},
|
|
18694
19773
|
agentId: args.bundle.session.agentId,
|
|
18695
19774
|
cwd: args.cwd ?? args.bundle.session.cwd,
|
|
18696
19775
|
title: args.bundle.session.title,
|
|
@@ -18824,6 +19903,14 @@ var SessionManager = class {
|
|
|
18824
19903
|
}
|
|
18825
19904
|
await Promise.allSettled(pending);
|
|
18826
19905
|
}
|
|
19906
|
+
// Wait for every pending history.jsonl write to settle. markClosed
|
|
19907
|
+
// broadcasts turn_complete(interrupted) for the in-flight turn via a
|
|
19908
|
+
// fire-and-forget store.append; without flushing, a SIGTERM can exit
|
|
19909
|
+
// before that append hits disk, leaving an unmatched prompt_received
|
|
19910
|
+
// in history that leaks pendingTurns on every client that replays it.
|
|
19911
|
+
async flushHistoryWrites() {
|
|
19912
|
+
await this.histories.flushAll();
|
|
19913
|
+
}
|
|
18827
19914
|
// Startup hook: scan persisted sessions for non-empty queue files,
|
|
18828
19915
|
// apply the TTL, resurrect anything with surviving entries, and
|
|
18829
19916
|
// replay them through the normal queue path. Called from the daemon
|
|
@@ -18922,6 +20009,8 @@ function mergeForPersistence(session, existing) {
|
|
|
18922
20009
|
agentModes,
|
|
18923
20010
|
agentModels,
|
|
18924
20011
|
parentSessionId: session.parentSessionId ?? existing?.parentSessionId,
|
|
20012
|
+
forkedFromSessionId: session.forkedFromSessionId ?? existing?.forkedFromSessionId,
|
|
20013
|
+
forkedFromMessageId: session.forkedFromMessageId ?? existing?.forkedFromMessageId,
|
|
18925
20014
|
originatingClient: session.originatingClient ?? existing?.originatingClient,
|
|
18926
20015
|
createdAt: existing?.createdAt ?? new Date(session.createdAt).toISOString()
|
|
18927
20016
|
});
|
|
@@ -19191,6 +20280,23 @@ function parseModesList(list) {
|
|
|
19191
20280
|
}
|
|
19192
20281
|
return out;
|
|
19193
20282
|
}
|
|
20283
|
+
function findLastTurnComplete(history) {
|
|
20284
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
20285
|
+
const entry = history[i];
|
|
20286
|
+
if (!entry || entry.method !== "session/update") {
|
|
20287
|
+
continue;
|
|
20288
|
+
}
|
|
20289
|
+
const update = entry.params?.update;
|
|
20290
|
+
if (update?.sessionUpdate !== "turn_complete") {
|
|
20291
|
+
continue;
|
|
20292
|
+
}
|
|
20293
|
+
if (typeof update.messageId !== "string" || update.messageId.length === 0) {
|
|
20294
|
+
continue;
|
|
20295
|
+
}
|
|
20296
|
+
return { index: i, messageId: update.messageId };
|
|
20297
|
+
}
|
|
20298
|
+
return void 0;
|
|
20299
|
+
}
|
|
19194
20300
|
async function loadPromptHistorySafely(sessionId) {
|
|
19195
20301
|
try {
|
|
19196
20302
|
const raw = await fs12.readFile(paths.tuiHistoryFile(sessionId), "utf8");
|
|
@@ -20322,9 +21428,9 @@ init_hydra_version();
|
|
|
20322
21428
|
|
|
20323
21429
|
// src/core/session-tokens.ts
|
|
20324
21430
|
init_paths();
|
|
20325
|
-
|
|
21431
|
+
init_json_store();
|
|
20326
21432
|
import * as path12 from "path";
|
|
20327
|
-
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
21433
|
+
import { createHash, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
20328
21434
|
var TOKEN_PREFIX = "hydra_session_";
|
|
20329
21435
|
var DEFAULT_TTL_SEC = 60 * 60 * 24 * 30;
|
|
20330
21436
|
var ID_LENGTH = 12;
|
|
@@ -20337,7 +21443,7 @@ function sha256Hex(input) {
|
|
|
20337
21443
|
return createHash("sha256").update(input).digest("hex");
|
|
20338
21444
|
}
|
|
20339
21445
|
function randomHex(bytes) {
|
|
20340
|
-
return
|
|
21446
|
+
return randomBytes2(bytes).toString("hex");
|
|
20341
21447
|
}
|
|
20342
21448
|
function generateId() {
|
|
20343
21449
|
return randomHex(ID_LENGTH).slice(0, ID_LENGTH * 2);
|
|
@@ -20357,17 +21463,11 @@ var SessionTokenStore = class _SessionTokenStore {
|
|
|
20357
21463
|
}
|
|
20358
21464
|
static async load() {
|
|
20359
21465
|
let records = [];
|
|
20360
|
-
|
|
20361
|
-
|
|
20362
|
-
|
|
20363
|
-
|
|
20364
|
-
|
|
20365
|
-
}
|
|
20366
|
-
} catch (err) {
|
|
20367
|
-
const e = err;
|
|
20368
|
-
if (e.code !== "ENOENT") {
|
|
20369
|
-
throw err;
|
|
20370
|
-
}
|
|
21466
|
+
const parsed = await readJsonSafe(
|
|
21467
|
+
tokensFilePath()
|
|
21468
|
+
);
|
|
21469
|
+
if (parsed && Array.isArray(parsed.records)) {
|
|
21470
|
+
records = parsed.records.filter(isRecord);
|
|
20371
21471
|
}
|
|
20372
21472
|
const store = new _SessionTokenStore(records);
|
|
20373
21473
|
const removed = store.sweepExpired(/* @__PURE__ */ new Date());
|
|
@@ -20484,14 +21584,11 @@ var SessionTokenStore = class _SessionTokenStore {
|
|
|
20484
21584
|
await this.writeInflight;
|
|
20485
21585
|
}
|
|
20486
21586
|
const records = Array.from(this.records.values());
|
|
20487
|
-
|
|
20488
|
-
|
|
20489
|
-
|
|
20490
|
-
|
|
20491
|
-
|
|
20492
|
-
mode: 384
|
|
20493
|
-
});
|
|
20494
|
-
})();
|
|
21587
|
+
this.writeInflight = writeJsonAtomic(
|
|
21588
|
+
tokensFilePath(),
|
|
21589
|
+
{ records },
|
|
21590
|
+
{ mode: 384 }
|
|
21591
|
+
);
|
|
20495
21592
|
try {
|
|
20496
21593
|
await this.writeInflight;
|
|
20497
21594
|
} finally {
|
|
@@ -20661,89 +21758,6 @@ var AuthRateLimiter = class {
|
|
|
20661
21758
|
init_config();
|
|
20662
21759
|
import * as os4 from "os";
|
|
20663
21760
|
|
|
20664
|
-
// src/core/bundle.ts
|
|
20665
|
-
import { z as z5 } from "zod";
|
|
20666
|
-
var HistoryEntrySchema = z5.object({
|
|
20667
|
-
method: z5.string(),
|
|
20668
|
-
params: z5.unknown(),
|
|
20669
|
-
recordedAt: z5.number()
|
|
20670
|
-
});
|
|
20671
|
-
var BundleSession = z5.object({
|
|
20672
|
-
// The exporter's local id. Regenerated fresh on import (sessionId is
|
|
20673
|
-
// the local namespace; lineageId is what survives across hops).
|
|
20674
|
-
sessionId: z5.string(),
|
|
20675
|
-
// Required on bundles — the export path backfills if the source
|
|
20676
|
-
// record was written before lineageId existed.
|
|
20677
|
-
lineageId: z5.string(),
|
|
20678
|
-
// The exporter's agent-side session id at export time. Carried so
|
|
20679
|
-
// importers can persist it as a breadcrumb (and, eventually, as the
|
|
20680
|
-
// handle a "connect back to origin" feature would need). Omitted on
|
|
20681
|
-
// bundles whose source record never bound to an agent (e.g. a
|
|
20682
|
-
// re-export of an imported, not-yet-attached session).
|
|
20683
|
-
upstreamSessionId: z5.string().optional(),
|
|
20684
|
-
agentId: z5.string(),
|
|
20685
|
-
cwd: z5.string(),
|
|
20686
|
-
title: z5.string().optional(),
|
|
20687
|
-
currentModel: z5.string().optional(),
|
|
20688
|
-
currentMode: z5.string().optional(),
|
|
20689
|
-
currentUsage: PersistedUsage.optional(),
|
|
20690
|
-
agentCommands: z5.array(PersistedAgentCommand).optional(),
|
|
20691
|
-
agentModes: z5.array(PersistedAgentMode).optional(),
|
|
20692
|
-
createdAt: z5.string(),
|
|
20693
|
-
updatedAt: z5.string()
|
|
20694
|
-
});
|
|
20695
|
-
var Bundle = z5.object({
|
|
20696
|
-
version: z5.literal(1),
|
|
20697
|
-
exportedAt: z5.string(),
|
|
20698
|
-
exportedFrom: z5.object({
|
|
20699
|
-
hydraVersion: z5.string(),
|
|
20700
|
-
machine: z5.string(),
|
|
20701
|
-
// Externally-reachable name (and optional ":port") for the exporting
|
|
20702
|
-
// daemon, sourced from config.daemon.publicHost (or daemon.host when
|
|
20703
|
-
// non-loopback). Carried so an importer can construct a hydra:// URL
|
|
20704
|
-
// that dials back to the origin — e.g. over Tailscale. Omitted when
|
|
20705
|
-
// the exporter has no routable address; never falls back to loopback.
|
|
20706
|
-
hydraHost: z5.string().optional()
|
|
20707
|
-
}),
|
|
20708
|
-
session: BundleSession,
|
|
20709
|
-
history: z5.array(HistoryEntrySchema),
|
|
20710
|
-
promptHistory: z5.array(z5.string()).optional()
|
|
20711
|
-
});
|
|
20712
|
-
function encodeBundle(params) {
|
|
20713
|
-
const bundle = {
|
|
20714
|
-
version: 1,
|
|
20715
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20716
|
-
exportedFrom: {
|
|
20717
|
-
hydraVersion: params.hydraVersion,
|
|
20718
|
-
machine: params.machine,
|
|
20719
|
-
...params.hydraHost !== void 0 && params.hydraHost.length > 0 ? { hydraHost: params.hydraHost } : {}
|
|
20720
|
-
},
|
|
20721
|
-
session: {
|
|
20722
|
-
sessionId: params.record.sessionId,
|
|
20723
|
-
lineageId: params.record.lineageId,
|
|
20724
|
-
...params.record.upstreamSessionId ? { upstreamSessionId: params.record.upstreamSessionId } : {},
|
|
20725
|
-
agentId: params.record.agentId,
|
|
20726
|
-
cwd: params.record.cwd,
|
|
20727
|
-
...params.record.title !== void 0 ? { title: params.record.title } : {},
|
|
20728
|
-
...params.record.currentModel !== void 0 ? { currentModel: params.record.currentModel } : {},
|
|
20729
|
-
...params.record.currentMode !== void 0 ? { currentMode: params.record.currentMode } : {},
|
|
20730
|
-
...params.record.currentUsage !== void 0 ? { currentUsage: params.record.currentUsage } : {},
|
|
20731
|
-
...params.record.agentCommands !== void 0 ? { agentCommands: params.record.agentCommands } : {},
|
|
20732
|
-
...params.record.agentModes !== void 0 ? { agentModes: params.record.agentModes } : {},
|
|
20733
|
-
createdAt: params.record.createdAt,
|
|
20734
|
-
updatedAt: params.record.updatedAt
|
|
20735
|
-
},
|
|
20736
|
-
history: params.history
|
|
20737
|
-
};
|
|
20738
|
-
if (params.promptHistory !== void 0) {
|
|
20739
|
-
bundle.promptHistory = params.promptHistory;
|
|
20740
|
-
}
|
|
20741
|
-
return bundle;
|
|
20742
|
-
}
|
|
20743
|
-
function decodeBundle(raw) {
|
|
20744
|
-
return Bundle.parse(raw);
|
|
20745
|
-
}
|
|
20746
|
-
|
|
20747
21761
|
// src/core/transcript.ts
|
|
20748
21762
|
init_render_update();
|
|
20749
21763
|
init_session();
|
|
@@ -21519,6 +22533,48 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
21519
22533
|
reply.header("Content-Type", "text/markdown; charset=utf-8");
|
|
21520
22534
|
reply.code(200).send(bundleToMarkdown(bundle));
|
|
21521
22535
|
});
|
|
22536
|
+
app.post("/v1/sessions/:id/fork", async (request, reply) => {
|
|
22537
|
+
const raw = request.params.id;
|
|
22538
|
+
const id = await manager.resolveCanonicalId(raw) ?? raw;
|
|
22539
|
+
const body = request.body ?? {};
|
|
22540
|
+
const opts = {};
|
|
22541
|
+
if (body.forkAt !== void 0) {
|
|
22542
|
+
if (typeof body.forkAt !== "string" || body.forkAt.length === 0) {
|
|
22543
|
+
reply.code(400).send({ error: "forkAt must be a non-empty string" });
|
|
22544
|
+
return;
|
|
22545
|
+
}
|
|
22546
|
+
opts.forkAt = body.forkAt;
|
|
22547
|
+
}
|
|
22548
|
+
if (body.cwd !== void 0) {
|
|
22549
|
+
if (typeof body.cwd !== "string" || body.cwd.length === 0) {
|
|
22550
|
+
reply.code(400).send({ error: "cwd must be a non-empty string" });
|
|
22551
|
+
return;
|
|
22552
|
+
}
|
|
22553
|
+
opts.cwd = expandHome(body.cwd);
|
|
22554
|
+
}
|
|
22555
|
+
if (body.agentId !== void 0) {
|
|
22556
|
+
if (typeof body.agentId !== "string" || body.agentId.length === 0) {
|
|
22557
|
+
reply.code(400).send({ error: "agentId must be a non-empty string" });
|
|
22558
|
+
return;
|
|
22559
|
+
}
|
|
22560
|
+
opts.agentId = body.agentId;
|
|
22561
|
+
}
|
|
22562
|
+
try {
|
|
22563
|
+
const result = await manager.forkSession(id, opts);
|
|
22564
|
+
reply.code(201).send(result);
|
|
22565
|
+
} catch (err) {
|
|
22566
|
+
const e = err;
|
|
22567
|
+
if (e.code === JsonRpcErrorCodes.SessionNotFound) {
|
|
22568
|
+
reply.code(404).send({ error: e.message });
|
|
22569
|
+
return;
|
|
22570
|
+
}
|
|
22571
|
+
if (e.code === JsonRpcErrorCodes.InvalidParams || e.code === JsonRpcErrorCodes.AgentNotInstalled) {
|
|
22572
|
+
reply.code(400).send({ error: e.message });
|
|
22573
|
+
return;
|
|
22574
|
+
}
|
|
22575
|
+
reply.code(500).send({ error: e.message });
|
|
22576
|
+
}
|
|
22577
|
+
});
|
|
21522
22578
|
app.post("/v1/sessions/import", async (request, reply) => {
|
|
21523
22579
|
const body = request.body ?? {};
|
|
21524
22580
|
if (body.bundle === void 0) {
|
|
@@ -21964,9 +23020,9 @@ import { z as z6 } from "zod";
|
|
|
21964
23020
|
|
|
21965
23021
|
// src/core/password.ts
|
|
21966
23022
|
init_paths();
|
|
21967
|
-
import * as
|
|
23023
|
+
import * as fs15 from "fs/promises";
|
|
21968
23024
|
import * as path13 from "path";
|
|
21969
|
-
import { randomBytes as
|
|
23025
|
+
import { randomBytes as randomBytes3, scrypt, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
21970
23026
|
import { promisify } from "util";
|
|
21971
23027
|
var scryptAsync = promisify(scrypt);
|
|
21972
23028
|
function passwordHashPath() {
|
|
@@ -21982,7 +23038,7 @@ async function setPassword(plaintext) {
|
|
|
21982
23038
|
if (typeof plaintext !== "string" || plaintext.length === 0) {
|
|
21983
23039
|
throw new Error("password must be a non-empty string");
|
|
21984
23040
|
}
|
|
21985
|
-
const salt =
|
|
23041
|
+
const salt = randomBytes3(SALT_LEN);
|
|
21986
23042
|
const key = await scryptAsync(plaintext, salt, KEY_LEN, {
|
|
21987
23043
|
N: DEFAULT_N,
|
|
21988
23044
|
r: DEFAULT_R,
|
|
@@ -21991,15 +23047,15 @@ async function setPassword(plaintext) {
|
|
|
21991
23047
|
});
|
|
21992
23048
|
const encoded = `scrypt$${DEFAULT_N}$${DEFAULT_R}$${DEFAULT_P}$${salt.toString("hex")}$${key.toString("hex")}
|
|
21993
23049
|
`;
|
|
21994
|
-
await
|
|
21995
|
-
await
|
|
23050
|
+
await fs15.mkdir(paths.home(), { recursive: true });
|
|
23051
|
+
await fs15.writeFile(passwordHashPath(), encoded, {
|
|
21996
23052
|
encoding: "utf8",
|
|
21997
23053
|
mode: 384
|
|
21998
23054
|
});
|
|
21999
23055
|
}
|
|
22000
23056
|
async function hasPassword() {
|
|
22001
23057
|
try {
|
|
22002
|
-
const text = await
|
|
23058
|
+
const text = await fs15.readFile(passwordHashPath(), "utf8");
|
|
22003
23059
|
return text.trim().length > 0;
|
|
22004
23060
|
} catch (err) {
|
|
22005
23061
|
const e = err;
|
|
@@ -22015,7 +23071,7 @@ async function verifyPassword(plaintext) {
|
|
|
22015
23071
|
}
|
|
22016
23072
|
let line;
|
|
22017
23073
|
try {
|
|
22018
|
-
line = (await
|
|
23074
|
+
line = (await fs15.readFile(passwordHashPath(), "utf8")).trim();
|
|
22019
23075
|
} catch (err) {
|
|
22020
23076
|
const e = err;
|
|
22021
23077
|
if (e.code === "ENOENT") {
|
|
@@ -22142,7 +23198,7 @@ import { nanoid as nanoid2 } from "nanoid";
|
|
|
22142
23198
|
import * as os5 from "os";
|
|
22143
23199
|
import * as path14 from "path";
|
|
22144
23200
|
init_hydra_version();
|
|
22145
|
-
import { randomBytes as
|
|
23201
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
22146
23202
|
function registerAcpWsEndpoint(app, deps) {
|
|
22147
23203
|
app.get("/acp", { websocket: true }, async (socket, request) => {
|
|
22148
23204
|
const token = tokenFromUpgradeRequest({
|
|
@@ -22339,6 +23395,23 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
22339
23395
|
});
|
|
22340
23396
|
return { childSessionId: child.sessionId };
|
|
22341
23397
|
});
|
|
23398
|
+
connection.onRequest("hydra-acp/fork_session", async (raw) => {
|
|
23399
|
+
const params = raw ?? {};
|
|
23400
|
+
if (typeof params.sessionId !== "string") {
|
|
23401
|
+
throw Object.assign(
|
|
23402
|
+
new Error("fork_session requires sessionId"),
|
|
23403
|
+
{ code: JsonRpcErrorCodes.InvalidParams }
|
|
23404
|
+
);
|
|
23405
|
+
}
|
|
23406
|
+
const forkAt = typeof params.forkAt === "string" ? params.forkAt : void 0;
|
|
23407
|
+
const cwd = typeof params.cwd === "string" ? params.cwd : void 0;
|
|
23408
|
+
const agentId = typeof params.agentId === "string" ? params.agentId : void 0;
|
|
23409
|
+
return await deps.manager.forkSession(params.sessionId, {
|
|
23410
|
+
...forkAt !== void 0 ? { forkAt } : {},
|
|
23411
|
+
...cwd !== void 0 ? { cwd } : {},
|
|
23412
|
+
...agentId !== void 0 ? { agentId } : {}
|
|
23413
|
+
});
|
|
23414
|
+
});
|
|
22342
23415
|
connection.onRequest("hydra-acp/await_child", async (raw) => {
|
|
22343
23416
|
const params = raw ?? {};
|
|
22344
23417
|
const childSessionId = typeof params.childSessionId === "string" ? params.childSessionId : void 0;
|
|
@@ -22413,7 +23486,7 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
22413
23486
|
let stdinReservation;
|
|
22414
23487
|
let augmentedMcpServers = params.mcpServers;
|
|
22415
23488
|
if (hydraMeta.mcpStdin === true && deps.mcpTokenRegistry !== void 0 && deps.getDaemonOrigin !== void 0) {
|
|
22416
|
-
stdinToken =
|
|
23489
|
+
stdinToken = randomBytes4(32).toString("hex");
|
|
22417
23490
|
stdinReservation = deps.mcpTokenRegistry.reserve(stdinToken);
|
|
22418
23491
|
const url = `${deps.getDaemonOrigin()}/mcp/hydra-acp-stdin`;
|
|
22419
23492
|
const descriptor = {
|
|
@@ -22431,7 +23504,7 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
22431
23504
|
if (deps.extensionMcp !== void 0 && deps.mcpTokenRegistry !== void 0 && deps.getDaemonOrigin !== void 0) {
|
|
22432
23505
|
const extNames = deps.extensionMcp.list();
|
|
22433
23506
|
if (extNames.length > 0) {
|
|
22434
|
-
extMcpToken =
|
|
23507
|
+
extMcpToken = randomBytes4(32).toString("hex");
|
|
22435
23508
|
extMcpReservation = deps.mcpTokenRegistry.reserve(extMcpToken);
|
|
22436
23509
|
const origin = deps.getDaemonOrigin();
|
|
22437
23510
|
const descriptors = extNames.map((name) => ({
|
|
@@ -23990,12 +25063,13 @@ async function startDaemon(config, serviceToken) {
|
|
|
23990
25063
|
await transformers.stop();
|
|
23991
25064
|
await manager.closeAll();
|
|
23992
25065
|
await manager.flushMetaWrites();
|
|
25066
|
+
await manager.flushHistoryWrites();
|
|
23993
25067
|
setBinaryInstallLogger(null);
|
|
23994
25068
|
setNpmInstallLogger(null);
|
|
23995
25069
|
setAgentPruneLogger(null);
|
|
23996
25070
|
await app.close();
|
|
23997
25071
|
try {
|
|
23998
|
-
|
|
25072
|
+
fs16.unlinkSync(paths.pidFile());
|
|
23999
25073
|
} catch {
|
|
24000
25074
|
}
|
|
24001
25075
|
try {
|
|
@@ -24045,7 +25119,7 @@ init_daemon_bootstrap();
|
|
|
24045
25119
|
init_hydra_version();
|
|
24046
25120
|
|
|
24047
25121
|
// src/cli/commands/log-tail.ts
|
|
24048
|
-
import * as
|
|
25122
|
+
import * as fs17 from "fs";
|
|
24049
25123
|
import * as fsp9 from "fs/promises";
|
|
24050
25124
|
async function runLogTail(logPath, argv, notFoundMessage) {
|
|
24051
25125
|
const opts = parseLogTailFlags(argv);
|
|
@@ -24069,7 +25143,7 @@ async function runLogTail(logPath, argv, notFoundMessage) {
|
|
|
24069
25143
|
process.stdout.write(`-- following ${logPath} --
|
|
24070
25144
|
`);
|
|
24071
25145
|
let pending = false;
|
|
24072
|
-
const watcher =
|
|
25146
|
+
const watcher = fs17.watch(logPath, () => {
|
|
24073
25147
|
if (pending) {
|
|
24074
25148
|
return;
|
|
24075
25149
|
}
|
|
@@ -24392,7 +25466,7 @@ init_remote_url();
|
|
|
24392
25466
|
init_session();
|
|
24393
25467
|
init_discovery();
|
|
24394
25468
|
init_hydra_version();
|
|
24395
|
-
import * as
|
|
25469
|
+
import * as fs18 from "fs/promises";
|
|
24396
25470
|
import * as path15 from "path";
|
|
24397
25471
|
init_session_row();
|
|
24398
25472
|
async function runSessionsList(opts = {}) {
|
|
@@ -24542,8 +25616,8 @@ async function runSessionsExport(id, outPath) {
|
|
|
24542
25616
|
return;
|
|
24543
25617
|
}
|
|
24544
25618
|
const resolved = outPath === "." ? deriveFilenameFrom(response, id) : outPath;
|
|
24545
|
-
await
|
|
24546
|
-
await
|
|
25619
|
+
await fs18.mkdir(path15.dirname(path15.resolve(resolved)), { recursive: true });
|
|
25620
|
+
await fs18.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
|
|
24547
25621
|
process.stdout.write(`Wrote ${resolved}
|
|
24548
25622
|
`);
|
|
24549
25623
|
}
|
|
@@ -24590,21 +25664,21 @@ async function runSessionsTranscript(idOrFile, outPath) {
|
|
|
24590
25664
|
return;
|
|
24591
25665
|
}
|
|
24592
25666
|
const resolved = outPath === "." ? defaultName : outPath;
|
|
24593
|
-
await
|
|
24594
|
-
await
|
|
25667
|
+
await fs18.mkdir(path15.dirname(path15.resolve(resolved)), { recursive: true });
|
|
25668
|
+
await fs18.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
|
|
24595
25669
|
process.stdout.write(`Wrote ${resolved}
|
|
24596
25670
|
`);
|
|
24597
25671
|
}
|
|
24598
25672
|
async function readBundleFileIfExists(arg) {
|
|
24599
25673
|
try {
|
|
24600
|
-
const stat5 = await
|
|
25674
|
+
const stat5 = await fs18.stat(arg);
|
|
24601
25675
|
if (!stat5.isFile()) {
|
|
24602
25676
|
return null;
|
|
24603
25677
|
}
|
|
24604
25678
|
} catch {
|
|
24605
25679
|
return null;
|
|
24606
25680
|
}
|
|
24607
|
-
const text = await
|
|
25681
|
+
const text = await fs18.readFile(arg, "utf8");
|
|
24608
25682
|
try {
|
|
24609
25683
|
return { raw: JSON.parse(text) };
|
|
24610
25684
|
} catch (err) {
|
|
@@ -24633,7 +25707,7 @@ async function runSessionsImport(file, opts = {}) {
|
|
|
24633
25707
|
if (opts.cwd !== void 0) {
|
|
24634
25708
|
const resolved = path15.resolve(opts.cwd);
|
|
24635
25709
|
try {
|
|
24636
|
-
const stat5 = await
|
|
25710
|
+
const stat5 = await fs18.stat(resolved);
|
|
24637
25711
|
if (!stat5.isDirectory()) {
|
|
24638
25712
|
process.stderr.write(`--cwd ${resolved} is not a directory
|
|
24639
25713
|
`);
|
|
@@ -24650,7 +25724,7 @@ async function runSessionsImport(file, opts = {}) {
|
|
|
24650
25724
|
if (file === "-") {
|
|
24651
25725
|
body = await readStdin();
|
|
24652
25726
|
} else {
|
|
24653
|
-
body = await
|
|
25727
|
+
body = await fs18.readFile(file, "utf8");
|
|
24654
25728
|
}
|
|
24655
25729
|
let bundle;
|
|
24656
25730
|
try {
|
|
@@ -26676,14 +27750,15 @@ function renderLine(line, mode) {
|
|
|
26676
27750
|
}
|
|
26677
27751
|
var ANSI_BOLD = "\x1B[1m";
|
|
26678
27752
|
var ANSI_CODE = "\x1B[96m";
|
|
27753
|
+
var ANSI_BRIGHT_YELLOW = "\x1B[93m";
|
|
26679
27754
|
var ANSI_RESET = "\x1B[0m";
|
|
26680
27755
|
var CARET_SENTINEL = "\0";
|
|
26681
27756
|
function translateMarkup(text, mode) {
|
|
26682
27757
|
let s = text.replace(/\^\^/g, CARET_SENTINEL);
|
|
26683
27758
|
if (mode === "ansi") {
|
|
26684
|
-
s = s.replace(/\^\+/g, ANSI_BOLD).replace(/\^C/g, ANSI_CODE).replace(/\^:/g, ANSI_RESET);
|
|
27759
|
+
s = s.replace(/\^\+/g, ANSI_BOLD).replace(/\^C/g, ANSI_CODE).replace(/\^Y/g, ANSI_BRIGHT_YELLOW).replace(/\^:/g, ANSI_RESET);
|
|
26685
27760
|
}
|
|
26686
|
-
s = s.replace(/\^[+\-:
|
|
27761
|
+
s = s.replace(/\^[+\-:CcKY]/g, "");
|
|
26687
27762
|
s = s.replace(/\x00/g, "^");
|
|
26688
27763
|
return s;
|
|
26689
27764
|
}
|
|
@@ -27730,7 +28805,7 @@ async function resolveSessionFlagOrExit(input, opts) {
|
|
|
27730
28805
|
}
|
|
27731
28806
|
function readVersion() {
|
|
27732
28807
|
try {
|
|
27733
|
-
const here =
|
|
28808
|
+
const here = dirname7(fileURLToPath2(import.meta.url));
|
|
27734
28809
|
const pkg = JSON.parse(
|
|
27735
28810
|
readFileSync2(resolve6(here, "../package.json"), "utf8")
|
|
27736
28811
|
);
|