@hydra-acp/cli 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +141 -10
- package/dist/index.d.ts +45 -0
- package/dist/index.js +95 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -264,7 +264,13 @@ var init_config = __esm({
|
|
|
264
264
|
// Width cap on the cwd column in the `sessions list` output and the
|
|
265
265
|
// TUI picker. Set higher if you keep deeply-nested working directories
|
|
266
266
|
// and want them visible; the elastic title column shrinks to make room.
|
|
267
|
-
cwdColumnMaxWidth: z.number().int().positive().default(24)
|
|
267
|
+
cwdColumnMaxWidth: z.number().int().positive().default(24),
|
|
268
|
+
// When true (default), emit OSC 9;4 progress-bar control codes so the
|
|
269
|
+
// host terminal can show an indeterminate busy indicator (taskbar pulse
|
|
270
|
+
// on Windows Terminal, dock badge on KDE/Konsole, etc.) while a turn is
|
|
271
|
+
// running. Set false if your terminal renders this obnoxiously or you
|
|
272
|
+
// just don't want it.
|
|
273
|
+
progressIndicator: z.boolean().default(true)
|
|
268
274
|
});
|
|
269
275
|
ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
|
|
270
276
|
ExtensionBody = z.object({
|
|
@@ -302,7 +308,8 @@ var init_config = __esm({
|
|
|
302
308
|
maxScrollbackLines: 1e4,
|
|
303
309
|
mouse: true,
|
|
304
310
|
logMaxBytes: 5 * 1024 * 1024,
|
|
305
|
-
cwdColumnMaxWidth: 24
|
|
311
|
+
cwdColumnMaxWidth: 24,
|
|
312
|
+
progressIndicator: true
|
|
306
313
|
})
|
|
307
314
|
});
|
|
308
315
|
HydraConfigReadOnly = HydraConfig.extend({
|
|
@@ -483,6 +490,11 @@ var init_types = __esm({
|
|
|
483
490
|
// Last-known usage snapshot so list views can show per-session cost
|
|
484
491
|
// (and tokens, in callers that care) without resurrecting cold sessions.
|
|
485
492
|
currentUsage: SessionListUsage.optional(),
|
|
493
|
+
// Origin host (and origin upstream id) for imported sessions. Picker
|
|
494
|
+
// uses the host to fill in the UPSTREAM cell pre-first-attach;
|
|
495
|
+
// future "connect back to origin" callers would dial both.
|
|
496
|
+
importedFromMachine: z3.string().optional(),
|
|
497
|
+
importedFromUpstreamSessionId: z3.string().optional(),
|
|
486
498
|
updatedAt: z3.string(),
|
|
487
499
|
attachedClients: z3.number().int().nonnegative(),
|
|
488
500
|
status: z3.enum(["live", "cold"]).default("live"),
|
|
@@ -1006,6 +1018,7 @@ var init_session = __esm({
|
|
|
1006
1018
|
// and noisy state churn keep a quiet session alive forever.
|
|
1007
1019
|
lastRecordedAt;
|
|
1008
1020
|
spawnReplacementAgent;
|
|
1021
|
+
logger;
|
|
1009
1022
|
agentChangeHandlers = [];
|
|
1010
1023
|
// Last available_commands_update we observed from the agent. Stored
|
|
1011
1024
|
// so we can re-broadcast a merged (hydra ∪ agent) list whenever
|
|
@@ -1037,6 +1050,7 @@ var init_session = __esm({
|
|
|
1037
1050
|
}
|
|
1038
1051
|
this.idleTimeoutMs = init.idleTimeoutMs ?? 0;
|
|
1039
1052
|
this.spawnReplacementAgent = init.spawnReplacementAgent;
|
|
1053
|
+
this.logger = init.logger;
|
|
1040
1054
|
if (init.firstPromptSeeded) {
|
|
1041
1055
|
this.firstPromptSeeded = true;
|
|
1042
1056
|
}
|
|
@@ -1366,6 +1380,9 @@ var init_session = __esm({
|
|
|
1366
1380
|
if (this.closed) {
|
|
1367
1381
|
return;
|
|
1368
1382
|
}
|
|
1383
|
+
this.logger?.info(
|
|
1384
|
+
`session ${this.sessionId} closing deleteRecord=${opts.deleteRecord ?? false} regenTitle=${opts.regenTitle ?? false}`
|
|
1385
|
+
);
|
|
1369
1386
|
this.cancelIdleTimer();
|
|
1370
1387
|
if (opts.regenTitle && this.firstPromptSeeded) {
|
|
1371
1388
|
const timeoutMs = opts.regenTitleTimeoutMs ?? 5e3;
|
|
@@ -1920,6 +1937,10 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
1920
1937
|
return;
|
|
1921
1938
|
}
|
|
1922
1939
|
const opts = this.firstPromptSeeded ? { deleteRecord: false, regenTitle: true } : { deleteRecord: true };
|
|
1940
|
+
const idleSec = Math.round(idle / 1e3);
|
|
1941
|
+
this.logger?.info(
|
|
1942
|
+
`session ${this.sessionId} idle timeout fired after ${idleSec}s (window=${Math.round(this.idleTimeoutMs / 1e3)}s) \u2014 closing`
|
|
1943
|
+
);
|
|
1923
1944
|
void this.close(opts).catch(() => void 0);
|
|
1924
1945
|
}
|
|
1925
1946
|
cancelIdleTimer() {
|
|
@@ -2096,6 +2117,8 @@ function recordFromMemorySession(args) {
|
|
|
2096
2117
|
lineageId: args.lineageId,
|
|
2097
2118
|
upstreamSessionId: args.upstreamSessionId,
|
|
2098
2119
|
importedFromSessionId: args.importedFromSessionId,
|
|
2120
|
+
importedFromUpstreamSessionId: args.importedFromUpstreamSessionId,
|
|
2121
|
+
importedFromMachine: args.importedFromMachine,
|
|
2099
2122
|
agentId: args.agentId,
|
|
2100
2123
|
cwd: args.cwd,
|
|
2101
2124
|
title: args.title,
|
|
@@ -2143,6 +2166,16 @@ var init_session_store = __esm({
|
|
|
2143
2166
|
// origin's local id at export time, kept for debuggability and as a
|
|
2144
2167
|
// breadcrumb in `sessions list` (informational, not used for routing).
|
|
2145
2168
|
importedFromSessionId: z4.string().optional(),
|
|
2169
|
+
// Origin's agent-side session id at export time. Carried as a
|
|
2170
|
+
// breadcrumb and as the handle a future "connect back to origin"
|
|
2171
|
+
// feature would dial. Absent when the origin record had no upstream
|
|
2172
|
+
// bound (re-export of an imported, not-yet-attached session).
|
|
2173
|
+
importedFromUpstreamSessionId: z4.string().optional(),
|
|
2174
|
+
// Hostname of the machine that exported the bundle we imported
|
|
2175
|
+
// (i.e. the most recent hop, not necessarily the true multi-hop
|
|
2176
|
+
// origin). Surfaced in the picker so imported rows don't look like
|
|
2177
|
+
// they materialized from nowhere.
|
|
2178
|
+
importedFromMachine: z4.string().optional(),
|
|
2146
2179
|
agentId: z4.string(),
|
|
2147
2180
|
cwd: z4.string(),
|
|
2148
2181
|
title: z4.string().optional(),
|
|
@@ -2358,6 +2391,7 @@ function encodeBundle(params) {
|
|
|
2358
2391
|
session: {
|
|
2359
2392
|
sessionId: params.record.sessionId,
|
|
2360
2393
|
lineageId: params.record.lineageId,
|
|
2394
|
+
...params.record.upstreamSessionId ? { upstreamSessionId: params.record.upstreamSessionId } : {},
|
|
2361
2395
|
agentId: params.record.agentId,
|
|
2362
2396
|
cwd: params.record.cwd,
|
|
2363
2397
|
...params.record.title !== void 0 ? { title: params.record.title } : {},
|
|
@@ -2395,6 +2429,12 @@ var init_bundle = __esm({
|
|
|
2395
2429
|
// Required on bundles — the export path backfills if the source
|
|
2396
2430
|
// record was written before lineageId existed.
|
|
2397
2431
|
lineageId: z5.string(),
|
|
2432
|
+
// The exporter's agent-side session id at export time. Carried so
|
|
2433
|
+
// importers can persist it as a breadcrumb (and, eventually, as the
|
|
2434
|
+
// handle a "connect back to origin" feature would need). Omitted on
|
|
2435
|
+
// bundles whose source record never bound to an agent (e.g. a
|
|
2436
|
+
// re-export of an imported, not-yet-attached session).
|
|
2437
|
+
upstreamSessionId: z5.string().optional(),
|
|
2398
2438
|
agentId: z5.string(),
|
|
2399
2439
|
cwd: z5.string(),
|
|
2400
2440
|
title: z5.string().optional(),
|
|
@@ -2608,7 +2648,7 @@ var init_agent_display = __esm({
|
|
|
2608
2648
|
function toRow(s, now = Date.now()) {
|
|
2609
2649
|
return {
|
|
2610
2650
|
session: stripHydraSessionPrefix(s.sessionId),
|
|
2611
|
-
upstream: s.upstreamSessionId
|
|
2651
|
+
upstream: formatUpstreamCell(s.upstreamSessionId, s.importedFromMachine),
|
|
2612
2652
|
state: formatState(s.status, s.attachedClients),
|
|
2613
2653
|
agent: formatAgentCell(s.agentId, s.currentUsage),
|
|
2614
2654
|
age: formatRelativeAge(s.updatedAt, now),
|
|
@@ -2616,6 +2656,15 @@ function toRow(s, now = Date.now()) {
|
|
|
2616
2656
|
cwd: shortenHomePath(s.cwd)
|
|
2617
2657
|
};
|
|
2618
2658
|
}
|
|
2659
|
+
function formatUpstreamCell(upstreamSessionId, importedFromMachine) {
|
|
2660
|
+
if (upstreamSessionId && upstreamSessionId.length > 0) {
|
|
2661
|
+
return upstreamSessionId;
|
|
2662
|
+
}
|
|
2663
|
+
if (importedFromMachine && importedFromMachine.length > 0) {
|
|
2664
|
+
return `\u2190 ${importedFromMachine}`;
|
|
2665
|
+
}
|
|
2666
|
+
return "-";
|
|
2667
|
+
}
|
|
2619
2668
|
function formatState(status, clients) {
|
|
2620
2669
|
if (status === "cold") {
|
|
2621
2670
|
return "COLD";
|
|
@@ -2955,11 +3004,11 @@ async function runSessionsImport(file, opts = {}) {
|
|
|
2955
3004
|
function bundleToSummary(parsed) {
|
|
2956
3005
|
return {
|
|
2957
3006
|
sessionId: parsed.session.sessionId,
|
|
2958
|
-
upstreamSessionId: "-",
|
|
2959
3007
|
cwd: parsed.session.cwd,
|
|
2960
3008
|
agentId: parsed.session.agentId,
|
|
2961
3009
|
currentUsage: parsed.session.currentUsage,
|
|
2962
3010
|
title: parsed.session.title,
|
|
3011
|
+
importedFromMachine: parsed.exportedFrom.machine,
|
|
2963
3012
|
attachedClients: 0,
|
|
2964
3013
|
updatedAt: parsed.session.updatedAt,
|
|
2965
3014
|
status: "cold"
|
|
@@ -2980,10 +3029,13 @@ function printBundleInfo(raw, cwdColumnMaxWidth) {
|
|
|
2980
3029
|
const maxWidth = process.stdout.isTTY ? process.stdout.columns : void 0;
|
|
2981
3030
|
process.stdout.write(formatRow(HEADER, widths, maxWidth, cwdColumnMaxWidth) + "\n");
|
|
2982
3031
|
process.stdout.write(formatRow(row, widths, maxWidth, cwdColumnMaxWidth) + "\n");
|
|
3032
|
+
const originUpstream = parsed.session.upstreamSessionId ?? "-";
|
|
2983
3033
|
process.stdout.write(
|
|
2984
3034
|
`
|
|
2985
3035
|
lineage: ${parsed.session.lineageId}
|
|
2986
3036
|
exported: ${parsed.exportedAt} from ${parsed.exportedFrom.machine} (hydra ${parsed.exportedFrom.hydraVersion})
|
|
3037
|
+
origin session: ${parsed.session.sessionId}
|
|
3038
|
+
origin upstream: ${originUpstream}
|
|
2987
3039
|
history entries: ${parsed.history.length}` + (parsed.promptHistory ? `, prompt history: ${parsed.promptHistory.length}
|
|
2988
3040
|
` : "\n")
|
|
2989
3041
|
);
|
|
@@ -3284,7 +3336,9 @@ async function listSessions(config, opts = {}, fetchImpl = fetch) {
|
|
|
3284
3336
|
agentId: s.agentId,
|
|
3285
3337
|
currentModel: s.currentModel,
|
|
3286
3338
|
currentUsage: s.currentUsage,
|
|
3287
|
-
title: s.title
|
|
3339
|
+
title: s.title,
|
|
3340
|
+
importedFromMachine: s.importedFromMachine,
|
|
3341
|
+
importedFromUpstreamSessionId: s.importedFromUpstreamSessionId
|
|
3288
3342
|
}));
|
|
3289
3343
|
}
|
|
3290
3344
|
async function killSession(config, id, fetchImpl = fetch) {
|
|
@@ -4436,6 +4490,12 @@ var init_screen = __esm({
|
|
|
4436
4490
|
pasteBuffer = "";
|
|
4437
4491
|
rawStdinHandler;
|
|
4438
4492
|
mouseEnabled;
|
|
4493
|
+
progressIndicatorEnabled;
|
|
4494
|
+
// Last OSC 9;4 state we wrote (3 = indeterminate, 0 = remove). Used to
|
|
4495
|
+
// suppress redundant writes when setBanner runs but `status` didn't
|
|
4496
|
+
// actually change, and to re-emit on start() if a picker round-trip
|
|
4497
|
+
// cleared the host terminal's indicator.
|
|
4498
|
+
lastProgressState = 0;
|
|
4439
4499
|
constructor(opts) {
|
|
4440
4500
|
this.term = opts.term;
|
|
4441
4501
|
this.dispatcher = opts.dispatcher;
|
|
@@ -4443,6 +4503,7 @@ var init_screen = __esm({
|
|
|
4443
4503
|
this.contentRepaintThrottleMs = opts.repaintThrottleMs ?? DEFAULT_CONTENT_REPAINT_THROTTLE_MS;
|
|
4444
4504
|
this.maxScrollbackLines = opts.maxScrollbackLines ?? DEFAULT_MAX_SCROLLBACK_LINES;
|
|
4445
4505
|
this.mouseEnabled = opts.mouse ?? true;
|
|
4506
|
+
this.progressIndicatorEnabled = opts.progressIndicator ?? true;
|
|
4446
4507
|
this.resizeHandler = () => this.repaint();
|
|
4447
4508
|
this.keyHandler = (name, _matches, data) => this.handleKey(name, data);
|
|
4448
4509
|
this.mouseHandler = (name) => this.handleMouse(name);
|
|
@@ -4471,6 +4532,8 @@ var init_screen = __esm({
|
|
|
4471
4532
|
}
|
|
4472
4533
|
this.term.on("resize", this.resizeHandler);
|
|
4473
4534
|
this.installBracketedPaste();
|
|
4535
|
+
this.lastProgressState = 0;
|
|
4536
|
+
this.writeProgressIndicator(this.banner.status === "busy" ? 3 : 0);
|
|
4474
4537
|
this.repaint();
|
|
4475
4538
|
}
|
|
4476
4539
|
stop() {
|
|
@@ -4491,6 +4554,7 @@ var init_screen = __esm({
|
|
|
4491
4554
|
this.term.grabInput(false);
|
|
4492
4555
|
this.term.hideCursor(false);
|
|
4493
4556
|
process.stdout.write("\x1B[?7h");
|
|
4557
|
+
this.writeProgressIndicator(0);
|
|
4494
4558
|
this.term.fullscreen(false);
|
|
4495
4559
|
this.term("\n");
|
|
4496
4560
|
}
|
|
@@ -4779,9 +4843,26 @@ var init_screen = __esm({
|
|
|
4779
4843
|
}
|
|
4780
4844
|
setBanner(banner) {
|
|
4781
4845
|
this.banner = { ...this.banner, ...banner };
|
|
4846
|
+
this.writeProgressIndicator(this.banner.status === "busy" ? 3 : 0);
|
|
4782
4847
|
this.drawBanner();
|
|
4783
4848
|
this.placeCursor();
|
|
4784
4849
|
}
|
|
4850
|
+
// OSC 9;4 progress-bar control. State 3 = indeterminate (pulsing
|
|
4851
|
+
// taskbar / dock badge while a turn is running); state 0 = remove.
|
|
4852
|
+
// ConEmu-flavor sequence — supported by Windows Terminal, WezTerm,
|
|
4853
|
+
// Ghostty, Konsole, Black Box, Rio, and others; ignored harmlessly
|
|
4854
|
+
// by terminals that don't implement it. Disabled entirely when
|
|
4855
|
+
// tui.progressIndicator is false.
|
|
4856
|
+
writeProgressIndicator(state) {
|
|
4857
|
+
if (!this.progressIndicatorEnabled) {
|
|
4858
|
+
return;
|
|
4859
|
+
}
|
|
4860
|
+
if (state === this.lastProgressState) {
|
|
4861
|
+
return;
|
|
4862
|
+
}
|
|
4863
|
+
this.lastProgressState = state;
|
|
4864
|
+
process.stdout.write(`\x1B]9;4;${state}\x1B\\`);
|
|
4865
|
+
}
|
|
4785
4866
|
// Transient right-side banner message. Cleared automatically after
|
|
4786
4867
|
// durationMs (default 4s). Each call resets the timer, so rapid
|
|
4787
4868
|
// successive notifications coalesce on the latest text. Active
|
|
@@ -7511,6 +7592,7 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
7511
7592
|
repaintThrottleMs: config.tui.repaintThrottleMs,
|
|
7512
7593
|
maxScrollbackLines: config.tui.maxScrollbackLines,
|
|
7513
7594
|
mouse: config.tui.mouse,
|
|
7595
|
+
progressIndicator: config.tui.progressIndicator,
|
|
7514
7596
|
onKey: (events) => {
|
|
7515
7597
|
for (const ev of events) {
|
|
7516
7598
|
if (pendingPermission && tryHandlePermissionKey(ev)) {
|
|
@@ -9408,12 +9490,14 @@ var AgentInstance = class _AgentInstance {
|
|
|
9408
9490
|
killed = false;
|
|
9409
9491
|
stderrTail = "";
|
|
9410
9492
|
stderrTailBytes;
|
|
9493
|
+
logger;
|
|
9411
9494
|
exitHandlers = [];
|
|
9412
9495
|
constructor(opts, child) {
|
|
9413
9496
|
this.agentId = opts.agentId;
|
|
9414
9497
|
this.cwd = opts.cwd;
|
|
9415
9498
|
this.child = child;
|
|
9416
9499
|
this.stderrTailBytes = opts.stderrTailBytes ?? DEFAULT_STDERR_TAIL_BYTES;
|
|
9500
|
+
this.logger = opts.logger;
|
|
9417
9501
|
if (!child.stdout || !child.stdin) {
|
|
9418
9502
|
throw new Error("agent subprocess missing stdio");
|
|
9419
9503
|
}
|
|
@@ -9422,7 +9506,15 @@ var AgentInstance = class _AgentInstance {
|
|
|
9422
9506
|
child.stderr?.setEncoding("utf8");
|
|
9423
9507
|
child.stderr?.on("data", (chunk) => {
|
|
9424
9508
|
this.stderrTail = (this.stderrTail + chunk).slice(-this.stderrTailBytes);
|
|
9425
|
-
|
|
9509
|
+
if (this.logger) {
|
|
9510
|
+
for (const line of chunk.split(/\r?\n/)) {
|
|
9511
|
+
if (line.length > 0) {
|
|
9512
|
+
this.logger.info(`[${opts.agentId}] ${line}`);
|
|
9513
|
+
}
|
|
9514
|
+
}
|
|
9515
|
+
} else {
|
|
9516
|
+
process.stderr.write(`[${opts.agentId}] ${chunk}`);
|
|
9517
|
+
}
|
|
9426
9518
|
});
|
|
9427
9519
|
child.on("error", (err) => {
|
|
9428
9520
|
const msg = this.formatFailure(err.message);
|
|
@@ -9430,9 +9522,16 @@ var AgentInstance = class _AgentInstance {
|
|
|
9430
9522
|
});
|
|
9431
9523
|
child.on("exit", (code, signal) => {
|
|
9432
9524
|
this.exited = true;
|
|
9433
|
-
if (
|
|
9525
|
+
if (this.killed) {
|
|
9526
|
+
this.logger?.info(
|
|
9527
|
+
`agent ${opts.agentId} pid=${child.pid} exited after kill code=${code} signal=${signal}`
|
|
9528
|
+
);
|
|
9529
|
+
} else {
|
|
9434
9530
|
const reason = `agent ${opts.agentId} exited before responding (code=${code} signal=${signal})`;
|
|
9435
9531
|
this.connection.fail(new Error(this.formatFailure(reason)));
|
|
9532
|
+
this.logger?.warn(
|
|
9533
|
+
`agent ${opts.agentId} pid=${child.pid} exited unexpectedly code=${code} signal=${signal}`
|
|
9534
|
+
);
|
|
9436
9535
|
}
|
|
9437
9536
|
for (const handler of this.exitHandlers) {
|
|
9438
9537
|
handler(code, signal);
|
|
@@ -9453,7 +9552,15 @@ stderr: ${tail}` : reason;
|
|
|
9453
9552
|
const child = spawn3(opts.plan.command, opts.plan.args, {
|
|
9454
9553
|
cwd: opts.cwd,
|
|
9455
9554
|
env,
|
|
9456
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
9555
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9556
|
+
// setsid the agent into its own session/process group. The daemon
|
|
9557
|
+
// already runs in its own setsid'd session, but macOS terminals
|
|
9558
|
+
// (iTerm2, Terminal.app) sometimes still reach inherited child
|
|
9559
|
+
// processes when the user closes a window — putting the agent
|
|
9560
|
+
// one more session-boundary away keeps it alive across terminal
|
|
9561
|
+
// restarts. The daemon still owns the pipes, so this.kill()
|
|
9562
|
+
// continues to terminate it cleanly on idle/close.
|
|
9563
|
+
detached: true
|
|
9457
9564
|
});
|
|
9458
9565
|
return new _AgentInstance(opts, child);
|
|
9459
9566
|
}
|
|
@@ -9468,6 +9575,9 @@ stderr: ${tail}` : reason;
|
|
|
9468
9575
|
return;
|
|
9469
9576
|
}
|
|
9470
9577
|
this.killed = true;
|
|
9578
|
+
this.logger?.info(
|
|
9579
|
+
`agent ${this.agentId} pid=${this.child.pid} kill requested signal=${signal}`
|
|
9580
|
+
);
|
|
9471
9581
|
await this.connection.close().catch(() => void 0);
|
|
9472
9582
|
this.child.kill(signal);
|
|
9473
9583
|
}
|
|
@@ -9653,6 +9763,7 @@ var SessionManager = class {
|
|
|
9653
9763
|
this.histories = new HistoryStore({ maxEntries: this.sessionHistoryMaxEntries });
|
|
9654
9764
|
this.idleTimeoutMs = options.idleTimeoutMs ?? 0;
|
|
9655
9765
|
this.defaultModels = options.defaultModels ?? {};
|
|
9766
|
+
this.logger = options.logger;
|
|
9656
9767
|
}
|
|
9657
9768
|
registry;
|
|
9658
9769
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -9667,6 +9778,7 @@ var SessionManager = class {
|
|
|
9667
9778
|
// concurrent snapshot updates (e.g. an agent emitting model + mode
|
|
9668
9779
|
// back-to-back) don't lose writes via interleaved reads.
|
|
9669
9780
|
metaWriteQueues = /* @__PURE__ */ new Map();
|
|
9781
|
+
logger;
|
|
9670
9782
|
async create(params) {
|
|
9671
9783
|
const fresh = await this.bootstrapAgent({
|
|
9672
9784
|
agentId: params.agentId,
|
|
@@ -9684,6 +9796,7 @@ var SessionManager = class {
|
|
|
9684
9796
|
title: params.title,
|
|
9685
9797
|
agentArgs: params.agentArgs,
|
|
9686
9798
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
9799
|
+
logger: this.logger,
|
|
9687
9800
|
spawnReplacementAgent: (p) => this.bootstrapAgent({ ...p, mcpServers: [] }),
|
|
9688
9801
|
historyStore: this.histories,
|
|
9689
9802
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
@@ -9777,6 +9890,7 @@ var SessionManager = class {
|
|
|
9777
9890
|
title: params.title,
|
|
9778
9891
|
agentArgs: params.agentArgs,
|
|
9779
9892
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
9893
|
+
logger: this.logger,
|
|
9780
9894
|
spawnReplacementAgent: (p) => this.bootstrapAgent({ ...p, mcpServers: [] }),
|
|
9781
9895
|
historyStore: this.histories,
|
|
9782
9896
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
@@ -9823,6 +9937,7 @@ var SessionManager = class {
|
|
|
9823
9937
|
title: params.title,
|
|
9824
9938
|
agentArgs: params.agentArgs,
|
|
9825
9939
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
9940
|
+
logger: this.logger,
|
|
9826
9941
|
spawnReplacementAgent: (p) => this.bootstrapAgent({ ...p, mcpServers: [] }),
|
|
9827
9942
|
historyStore: this.histories,
|
|
9828
9943
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
@@ -10087,6 +10202,8 @@ var SessionManager = class {
|
|
|
10087
10202
|
agentId: r.agentId,
|
|
10088
10203
|
currentModel: r.currentModel,
|
|
10089
10204
|
currentUsage: r.currentUsage,
|
|
10205
|
+
importedFromMachine: r.importedFromMachine,
|
|
10206
|
+
importedFromUpstreamSessionId: r.importedFromUpstreamSessionId,
|
|
10090
10207
|
updatedAt: used,
|
|
10091
10208
|
attachedClients: 0,
|
|
10092
10209
|
status: "cold"
|
|
@@ -10194,6 +10311,8 @@ var SessionManager = class {
|
|
|
10194
10311
|
lineageId: args.bundle.session.lineageId,
|
|
10195
10312
|
upstreamSessionId: "",
|
|
10196
10313
|
importedFromSessionId: args.bundle.session.sessionId,
|
|
10314
|
+
importedFromUpstreamSessionId: args.bundle.session.upstreamSessionId,
|
|
10315
|
+
importedFromMachine: args.bundle.exportedFrom.machine,
|
|
10197
10316
|
agentId: args.bundle.session.agentId,
|
|
10198
10317
|
cwd: args.cwd ?? args.bundle.session.cwd,
|
|
10199
10318
|
title: args.bundle.session.title,
|
|
@@ -10316,6 +10435,8 @@ function mergeForPersistence(session, existing) {
|
|
|
10316
10435
|
lineageId: existing?.lineageId ?? generateLineageId(),
|
|
10317
10436
|
upstreamSessionId: session.upstreamSessionId,
|
|
10318
10437
|
importedFromSessionId: existing?.importedFromSessionId,
|
|
10438
|
+
importedFromUpstreamSessionId: existing?.importedFromUpstreamSessionId,
|
|
10439
|
+
importedFromMachine: existing?.importedFromMachine,
|
|
10319
10440
|
agentId: session.agentId,
|
|
10320
10441
|
cwd: session.cwd,
|
|
10321
10442
|
title: session.title,
|
|
@@ -11595,11 +11716,20 @@ async function startDaemon(config) {
|
|
|
11595
11716
|
await auth(request, reply);
|
|
11596
11717
|
});
|
|
11597
11718
|
const registry = new Registry(config);
|
|
11598
|
-
const
|
|
11719
|
+
const agentLogger = {
|
|
11720
|
+
info: (msg) => app.log.info(msg),
|
|
11721
|
+
warn: (msg) => app.log.warn(msg)
|
|
11722
|
+
};
|
|
11723
|
+
const spawner = (opts) => AgentInstance.spawn({
|
|
11724
|
+
...opts,
|
|
11725
|
+
stderrTailBytes: config.daemon.agentStderrTailBytes,
|
|
11726
|
+
logger: agentLogger
|
|
11727
|
+
});
|
|
11599
11728
|
const manager = new SessionManager(registry, spawner, void 0, {
|
|
11600
11729
|
idleTimeoutMs: config.daemon.sessionIdleTimeoutSeconds * 1e3,
|
|
11601
11730
|
defaultModels: config.defaultModels,
|
|
11602
|
-
sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries
|
|
11731
|
+
sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries,
|
|
11732
|
+
logger: agentLogger
|
|
11603
11733
|
});
|
|
11604
11734
|
const extensions = new ExtensionManager(extensionList(config));
|
|
11605
11735
|
registerHealthRoutes(app, HYDRA_VERSION);
|
|
@@ -11836,6 +11966,7 @@ async function runDaemonStart(flags = {}) {
|
|
|
11836
11966
|
};
|
|
11837
11967
|
process.on("SIGINT", () => void shutdown());
|
|
11838
11968
|
process.on("SIGTERM", () => void shutdown());
|
|
11969
|
+
process.on("SIGHUP", () => void 0);
|
|
11839
11970
|
return;
|
|
11840
11971
|
}
|
|
11841
11972
|
spawnDaemonDetached();
|
package/dist/index.d.ts
CHANGED
|
@@ -103,18 +103,21 @@ declare const HydraConfig: z.ZodObject<{
|
|
|
103
103
|
mouse: z.ZodDefault<z.ZodBoolean>;
|
|
104
104
|
logMaxBytes: z.ZodDefault<z.ZodNumber>;
|
|
105
105
|
cwdColumnMaxWidth: z.ZodDefault<z.ZodNumber>;
|
|
106
|
+
progressIndicator: z.ZodDefault<z.ZodBoolean>;
|
|
106
107
|
}, "strip", z.ZodTypeAny, {
|
|
107
108
|
repaintThrottleMs: number;
|
|
108
109
|
maxScrollbackLines: number;
|
|
109
110
|
mouse: boolean;
|
|
110
111
|
logMaxBytes: number;
|
|
111
112
|
cwdColumnMaxWidth: number;
|
|
113
|
+
progressIndicator: boolean;
|
|
112
114
|
}, {
|
|
113
115
|
repaintThrottleMs?: number | undefined;
|
|
114
116
|
maxScrollbackLines?: number | undefined;
|
|
115
117
|
mouse?: boolean | undefined;
|
|
116
118
|
logMaxBytes?: number | undefined;
|
|
117
119
|
cwdColumnMaxWidth?: number | undefined;
|
|
120
|
+
progressIndicator?: boolean | undefined;
|
|
118
121
|
}>>;
|
|
119
122
|
}, "strip", z.ZodTypeAny, {
|
|
120
123
|
daemon: {
|
|
@@ -142,6 +145,7 @@ declare const HydraConfig: z.ZodObject<{
|
|
|
142
145
|
mouse: boolean;
|
|
143
146
|
logMaxBytes: number;
|
|
144
147
|
cwdColumnMaxWidth: number;
|
|
148
|
+
progressIndicator: boolean;
|
|
145
149
|
};
|
|
146
150
|
registry: {
|
|
147
151
|
url: string;
|
|
@@ -177,6 +181,7 @@ declare const HydraConfig: z.ZodObject<{
|
|
|
177
181
|
mouse?: boolean | undefined;
|
|
178
182
|
logMaxBytes?: number | undefined;
|
|
179
183
|
cwdColumnMaxWidth?: number | undefined;
|
|
184
|
+
progressIndicator?: boolean | undefined;
|
|
180
185
|
} | undefined;
|
|
181
186
|
registry?: {
|
|
182
187
|
url?: string | undefined;
|
|
@@ -1330,6 +1335,8 @@ declare const SessionListEntry: z.ZodObject<{
|
|
|
1330
1335
|
costAmount?: number | undefined;
|
|
1331
1336
|
costCurrency?: string | undefined;
|
|
1332
1337
|
}>>;
|
|
1338
|
+
importedFromMachine: z.ZodOptional<z.ZodString>;
|
|
1339
|
+
importedFromUpstreamSessionId: z.ZodOptional<z.ZodString>;
|
|
1333
1340
|
updatedAt: z.ZodString;
|
|
1334
1341
|
attachedClients: z.ZodNumber;
|
|
1335
1342
|
status: z.ZodDefault<z.ZodEnum<["live", "cold"]>>;
|
|
@@ -1351,6 +1358,8 @@ declare const SessionListEntry: z.ZodObject<{
|
|
|
1351
1358
|
costAmount?: number | undefined;
|
|
1352
1359
|
costCurrency?: string | undefined;
|
|
1353
1360
|
} | undefined;
|
|
1361
|
+
importedFromMachine?: string | undefined;
|
|
1362
|
+
importedFromUpstreamSessionId?: string | undefined;
|
|
1354
1363
|
}, {
|
|
1355
1364
|
cwd: string;
|
|
1356
1365
|
sessionId: string;
|
|
@@ -1368,6 +1377,8 @@ declare const SessionListEntry: z.ZodObject<{
|
|
|
1368
1377
|
costAmount?: number | undefined;
|
|
1369
1378
|
costCurrency?: string | undefined;
|
|
1370
1379
|
} | undefined;
|
|
1380
|
+
importedFromMachine?: string | undefined;
|
|
1381
|
+
importedFromUpstreamSessionId?: string | undefined;
|
|
1371
1382
|
}>;
|
|
1372
1383
|
type SessionListEntry = z.infer<typeof SessionListEntry>;
|
|
1373
1384
|
declare const SessionListResult: z.ZodObject<{
|
|
@@ -1394,6 +1405,8 @@ declare const SessionListResult: z.ZodObject<{
|
|
|
1394
1405
|
costAmount?: number | undefined;
|
|
1395
1406
|
costCurrency?: string | undefined;
|
|
1396
1407
|
}>>;
|
|
1408
|
+
importedFromMachine: z.ZodOptional<z.ZodString>;
|
|
1409
|
+
importedFromUpstreamSessionId: z.ZodOptional<z.ZodString>;
|
|
1397
1410
|
updatedAt: z.ZodString;
|
|
1398
1411
|
attachedClients: z.ZodNumber;
|
|
1399
1412
|
status: z.ZodDefault<z.ZodEnum<["live", "cold"]>>;
|
|
@@ -1415,6 +1428,8 @@ declare const SessionListResult: z.ZodObject<{
|
|
|
1415
1428
|
costAmount?: number | undefined;
|
|
1416
1429
|
costCurrency?: string | undefined;
|
|
1417
1430
|
} | undefined;
|
|
1431
|
+
importedFromMachine?: string | undefined;
|
|
1432
|
+
importedFromUpstreamSessionId?: string | undefined;
|
|
1418
1433
|
}, {
|
|
1419
1434
|
cwd: string;
|
|
1420
1435
|
sessionId: string;
|
|
@@ -1432,6 +1447,8 @@ declare const SessionListResult: z.ZodObject<{
|
|
|
1432
1447
|
costAmount?: number | undefined;
|
|
1433
1448
|
costCurrency?: string | undefined;
|
|
1434
1449
|
} | undefined;
|
|
1450
|
+
importedFromMachine?: string | undefined;
|
|
1451
|
+
importedFromUpstreamSessionId?: string | undefined;
|
|
1435
1452
|
}>, "many">;
|
|
1436
1453
|
nextCursor: z.ZodOptional<z.ZodString>;
|
|
1437
1454
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -1452,6 +1469,8 @@ declare const SessionListResult: z.ZodObject<{
|
|
|
1452
1469
|
costAmount?: number | undefined;
|
|
1453
1470
|
costCurrency?: string | undefined;
|
|
1454
1471
|
} | undefined;
|
|
1472
|
+
importedFromMachine?: string | undefined;
|
|
1473
|
+
importedFromUpstreamSessionId?: string | undefined;
|
|
1455
1474
|
}[];
|
|
1456
1475
|
nextCursor?: string | undefined;
|
|
1457
1476
|
}, {
|
|
@@ -1472,6 +1491,8 @@ declare const SessionListResult: z.ZodObject<{
|
|
|
1472
1491
|
costAmount?: number | undefined;
|
|
1473
1492
|
costCurrency?: string | undefined;
|
|
1474
1493
|
} | undefined;
|
|
1494
|
+
importedFromMachine?: string | undefined;
|
|
1495
|
+
importedFromUpstreamSessionId?: string | undefined;
|
|
1475
1496
|
}[];
|
|
1476
1497
|
nextCursor?: string | undefined;
|
|
1477
1498
|
}>;
|
|
@@ -1556,6 +1577,11 @@ interface AgentInstanceOptions {
|
|
|
1556
1577
|
plan: SpawnPlan;
|
|
1557
1578
|
extraEnv?: Record<string, string>;
|
|
1558
1579
|
stderrTailBytes?: number;
|
|
1580
|
+
logger?: AgentLogger;
|
|
1581
|
+
}
|
|
1582
|
+
interface AgentLogger {
|
|
1583
|
+
info: (msg: string) => void;
|
|
1584
|
+
warn: (msg: string) => void;
|
|
1559
1585
|
}
|
|
1560
1586
|
declare class AgentInstance {
|
|
1561
1587
|
readonly agentId: string;
|
|
@@ -1566,6 +1592,7 @@ declare class AgentInstance {
|
|
|
1566
1592
|
private killed;
|
|
1567
1593
|
private stderrTail;
|
|
1568
1594
|
private stderrTailBytes;
|
|
1595
|
+
private logger?;
|
|
1569
1596
|
private exitHandlers;
|
|
1570
1597
|
private constructor();
|
|
1571
1598
|
private formatFailure;
|
|
@@ -1636,6 +1663,10 @@ interface SessionInit {
|
|
|
1636
1663
|
agentMeta?: Record<string, unknown>;
|
|
1637
1664
|
agentArgs?: string[];
|
|
1638
1665
|
idleTimeoutMs?: number;
|
|
1666
|
+
logger?: {
|
|
1667
|
+
info: (msg: string) => void;
|
|
1668
|
+
warn: (msg: string) => void;
|
|
1669
|
+
};
|
|
1639
1670
|
spawnReplacementAgent?: SpawnReplacementAgent;
|
|
1640
1671
|
historyStore?: HistoryStore;
|
|
1641
1672
|
historyMaxEntries?: number;
|
|
@@ -1684,6 +1715,7 @@ declare class Session {
|
|
|
1684
1715
|
private idleTimer;
|
|
1685
1716
|
private lastRecordedAt;
|
|
1686
1717
|
private spawnReplacementAgent;
|
|
1718
|
+
private logger;
|
|
1687
1719
|
private agentChangeHandlers;
|
|
1688
1720
|
private agentAdvertisedCommands;
|
|
1689
1721
|
private agentCommandsHandlers;
|
|
@@ -1767,6 +1799,8 @@ declare const SessionRecord: z.ZodObject<{
|
|
|
1767
1799
|
lineageId: z.ZodOptional<z.ZodString>;
|
|
1768
1800
|
upstreamSessionId: z.ZodString;
|
|
1769
1801
|
importedFromSessionId: z.ZodOptional<z.ZodString>;
|
|
1802
|
+
importedFromUpstreamSessionId: z.ZodOptional<z.ZodString>;
|
|
1803
|
+
importedFromMachine: z.ZodOptional<z.ZodString>;
|
|
1770
1804
|
agentId: z.ZodString;
|
|
1771
1805
|
cwd: z.ZodString;
|
|
1772
1806
|
title: z.ZodOptional<z.ZodString>;
|
|
@@ -1819,6 +1853,8 @@ declare const SessionRecord: z.ZodObject<{
|
|
|
1819
1853
|
costAmount?: number | undefined;
|
|
1820
1854
|
costCurrency?: string | undefined;
|
|
1821
1855
|
} | undefined;
|
|
1856
|
+
importedFromMachine?: string | undefined;
|
|
1857
|
+
importedFromUpstreamSessionId?: string | undefined;
|
|
1822
1858
|
lineageId?: string | undefined;
|
|
1823
1859
|
importedFromSessionId?: string | undefined;
|
|
1824
1860
|
agentCommands?: {
|
|
@@ -1843,6 +1879,8 @@ declare const SessionRecord: z.ZodObject<{
|
|
|
1843
1879
|
costAmount?: number | undefined;
|
|
1844
1880
|
costCurrency?: string | undefined;
|
|
1845
1881
|
} | undefined;
|
|
1882
|
+
importedFromMachine?: string | undefined;
|
|
1883
|
+
importedFromUpstreamSessionId?: string | undefined;
|
|
1846
1884
|
lineageId?: string | undefined;
|
|
1847
1885
|
importedFromSessionId?: string | undefined;
|
|
1848
1886
|
agentCommands?: {
|
|
@@ -1875,6 +1913,7 @@ declare const Bundle: z.ZodObject<{
|
|
|
1875
1913
|
session: z.ZodObject<{
|
|
1876
1914
|
sessionId: z.ZodString;
|
|
1877
1915
|
lineageId: z.ZodString;
|
|
1916
|
+
upstreamSessionId: z.ZodOptional<z.ZodString>;
|
|
1878
1917
|
agentId: z.ZodString;
|
|
1879
1918
|
cwd: z.ZodString;
|
|
1880
1919
|
title: z.ZodOptional<z.ZodString>;
|
|
@@ -1915,6 +1954,7 @@ declare const Bundle: z.ZodObject<{
|
|
|
1915
1954
|
updatedAt: string;
|
|
1916
1955
|
lineageId: string;
|
|
1917
1956
|
createdAt: string;
|
|
1957
|
+
upstreamSessionId?: string | undefined;
|
|
1918
1958
|
title?: string | undefined;
|
|
1919
1959
|
currentModel?: string | undefined;
|
|
1920
1960
|
currentMode?: string | undefined;
|
|
@@ -1935,6 +1975,7 @@ declare const Bundle: z.ZodObject<{
|
|
|
1935
1975
|
updatedAt: string;
|
|
1936
1976
|
lineageId: string;
|
|
1937
1977
|
createdAt: string;
|
|
1978
|
+
upstreamSessionId?: string | undefined;
|
|
1938
1979
|
title?: string | undefined;
|
|
1939
1980
|
currentModel?: string | undefined;
|
|
1940
1981
|
currentMode?: string | undefined;
|
|
@@ -1977,6 +2018,7 @@ declare const Bundle: z.ZodObject<{
|
|
|
1977
2018
|
updatedAt: string;
|
|
1978
2019
|
lineageId: string;
|
|
1979
2020
|
createdAt: string;
|
|
2021
|
+
upstreamSessionId?: string | undefined;
|
|
1980
2022
|
title?: string | undefined;
|
|
1981
2023
|
currentModel?: string | undefined;
|
|
1982
2024
|
currentMode?: string | undefined;
|
|
@@ -2011,6 +2053,7 @@ declare const Bundle: z.ZodObject<{
|
|
|
2011
2053
|
updatedAt: string;
|
|
2012
2054
|
lineageId: string;
|
|
2013
2055
|
createdAt: string;
|
|
2056
|
+
upstreamSessionId?: string | undefined;
|
|
2014
2057
|
title?: string | undefined;
|
|
2015
2058
|
currentModel?: string | undefined;
|
|
2016
2059
|
currentMode?: string | undefined;
|
|
@@ -2060,6 +2103,7 @@ interface SessionManagerOptions {
|
|
|
2060
2103
|
idleTimeoutMs?: number;
|
|
2061
2104
|
defaultModels?: Record<string, string>;
|
|
2062
2105
|
sessionHistoryMaxEntries?: number;
|
|
2106
|
+
logger?: AgentLogger;
|
|
2063
2107
|
}
|
|
2064
2108
|
declare class SessionManager {
|
|
2065
2109
|
private registry;
|
|
@@ -2072,6 +2116,7 @@ declare class SessionManager {
|
|
|
2072
2116
|
private defaultModels;
|
|
2073
2117
|
private sessionHistoryMaxEntries;
|
|
2074
2118
|
private metaWriteQueues;
|
|
2119
|
+
private logger?;
|
|
2075
2120
|
constructor(registry: Registry, spawner?: AgentSpawner, store?: SessionStore, options?: SessionManagerOptions);
|
|
2076
2121
|
create(params: CreateSessionParams): Promise<Session>;
|
|
2077
2122
|
resurrect(params: ResurrectParams): Promise<Session>;
|
package/dist/index.js
CHANGED
|
@@ -120,7 +120,13 @@ var TuiConfig = z.object({
|
|
|
120
120
|
// Width cap on the cwd column in the `sessions list` output and the
|
|
121
121
|
// TUI picker. Set higher if you keep deeply-nested working directories
|
|
122
122
|
// and want them visible; the elastic title column shrinks to make room.
|
|
123
|
-
cwdColumnMaxWidth: z.number().int().positive().default(24)
|
|
123
|
+
cwdColumnMaxWidth: z.number().int().positive().default(24),
|
|
124
|
+
// When true (default), emit OSC 9;4 progress-bar control codes so the
|
|
125
|
+
// host terminal can show an indeterminate busy indicator (taskbar pulse
|
|
126
|
+
// on Windows Terminal, dock badge on KDE/Konsole, etc.) while a turn is
|
|
127
|
+
// running. Set false if your terminal renders this obnoxiously or you
|
|
128
|
+
// just don't want it.
|
|
129
|
+
progressIndicator: z.boolean().default(true)
|
|
124
130
|
});
|
|
125
131
|
var ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
|
|
126
132
|
var ExtensionBody = z.object({
|
|
@@ -158,7 +164,8 @@ var HydraConfig = z.object({
|
|
|
158
164
|
maxScrollbackLines: 1e4,
|
|
159
165
|
mouse: true,
|
|
160
166
|
logMaxBytes: 5 * 1024 * 1024,
|
|
161
|
-
cwdColumnMaxWidth: 24
|
|
167
|
+
cwdColumnMaxWidth: 24,
|
|
168
|
+
progressIndicator: true
|
|
162
169
|
})
|
|
163
170
|
});
|
|
164
171
|
var HydraConfigReadOnly = HydraConfig.extend({
|
|
@@ -1013,6 +1020,11 @@ var SessionListEntry = z3.object({
|
|
|
1013
1020
|
// Last-known usage snapshot so list views can show per-session cost
|
|
1014
1021
|
// (and tokens, in callers that care) without resurrecting cold sessions.
|
|
1015
1022
|
currentUsage: SessionListUsage.optional(),
|
|
1023
|
+
// Origin host (and origin upstream id) for imported sessions. Picker
|
|
1024
|
+
// uses the host to fill in the UPSTREAM cell pre-first-attach;
|
|
1025
|
+
// future "connect back to origin" callers would dial both.
|
|
1026
|
+
importedFromMachine: z3.string().optional(),
|
|
1027
|
+
importedFromUpstreamSessionId: z3.string().optional(),
|
|
1016
1028
|
updatedAt: z3.string(),
|
|
1017
1029
|
attachedClients: z3.number().int().nonnegative(),
|
|
1018
1030
|
status: z3.enum(["live", "cold"]).default("live"),
|
|
@@ -1323,12 +1335,14 @@ var AgentInstance = class _AgentInstance {
|
|
|
1323
1335
|
killed = false;
|
|
1324
1336
|
stderrTail = "";
|
|
1325
1337
|
stderrTailBytes;
|
|
1338
|
+
logger;
|
|
1326
1339
|
exitHandlers = [];
|
|
1327
1340
|
constructor(opts, child) {
|
|
1328
1341
|
this.agentId = opts.agentId;
|
|
1329
1342
|
this.cwd = opts.cwd;
|
|
1330
1343
|
this.child = child;
|
|
1331
1344
|
this.stderrTailBytes = opts.stderrTailBytes ?? DEFAULT_STDERR_TAIL_BYTES;
|
|
1345
|
+
this.logger = opts.logger;
|
|
1332
1346
|
if (!child.stdout || !child.stdin) {
|
|
1333
1347
|
throw new Error("agent subprocess missing stdio");
|
|
1334
1348
|
}
|
|
@@ -1337,7 +1351,15 @@ var AgentInstance = class _AgentInstance {
|
|
|
1337
1351
|
child.stderr?.setEncoding("utf8");
|
|
1338
1352
|
child.stderr?.on("data", (chunk) => {
|
|
1339
1353
|
this.stderrTail = (this.stderrTail + chunk).slice(-this.stderrTailBytes);
|
|
1340
|
-
|
|
1354
|
+
if (this.logger) {
|
|
1355
|
+
for (const line of chunk.split(/\r?\n/)) {
|
|
1356
|
+
if (line.length > 0) {
|
|
1357
|
+
this.logger.info(`[${opts.agentId}] ${line}`);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
} else {
|
|
1361
|
+
process.stderr.write(`[${opts.agentId}] ${chunk}`);
|
|
1362
|
+
}
|
|
1341
1363
|
});
|
|
1342
1364
|
child.on("error", (err) => {
|
|
1343
1365
|
const msg = this.formatFailure(err.message);
|
|
@@ -1345,9 +1367,16 @@ var AgentInstance = class _AgentInstance {
|
|
|
1345
1367
|
});
|
|
1346
1368
|
child.on("exit", (code, signal) => {
|
|
1347
1369
|
this.exited = true;
|
|
1348
|
-
if (
|
|
1370
|
+
if (this.killed) {
|
|
1371
|
+
this.logger?.info(
|
|
1372
|
+
`agent ${opts.agentId} pid=${child.pid} exited after kill code=${code} signal=${signal}`
|
|
1373
|
+
);
|
|
1374
|
+
} else {
|
|
1349
1375
|
const reason = `agent ${opts.agentId} exited before responding (code=${code} signal=${signal})`;
|
|
1350
1376
|
this.connection.fail(new Error(this.formatFailure(reason)));
|
|
1377
|
+
this.logger?.warn(
|
|
1378
|
+
`agent ${opts.agentId} pid=${child.pid} exited unexpectedly code=${code} signal=${signal}`
|
|
1379
|
+
);
|
|
1351
1380
|
}
|
|
1352
1381
|
for (const handler of this.exitHandlers) {
|
|
1353
1382
|
handler(code, signal);
|
|
@@ -1368,7 +1397,15 @@ stderr: ${tail}` : reason;
|
|
|
1368
1397
|
const child = spawn3(opts.plan.command, opts.plan.args, {
|
|
1369
1398
|
cwd: opts.cwd,
|
|
1370
1399
|
env,
|
|
1371
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1400
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1401
|
+
// setsid the agent into its own session/process group. The daemon
|
|
1402
|
+
// already runs in its own setsid'd session, but macOS terminals
|
|
1403
|
+
// (iTerm2, Terminal.app) sometimes still reach inherited child
|
|
1404
|
+
// processes when the user closes a window — putting the agent
|
|
1405
|
+
// one more session-boundary away keeps it alive across terminal
|
|
1406
|
+
// restarts. The daemon still owns the pipes, so this.kill()
|
|
1407
|
+
// continues to terminate it cleanly on idle/close.
|
|
1408
|
+
detached: true
|
|
1372
1409
|
});
|
|
1373
1410
|
return new _AgentInstance(opts, child);
|
|
1374
1411
|
}
|
|
@@ -1383,6 +1420,9 @@ stderr: ${tail}` : reason;
|
|
|
1383
1420
|
return;
|
|
1384
1421
|
}
|
|
1385
1422
|
this.killed = true;
|
|
1423
|
+
this.logger?.info(
|
|
1424
|
+
`agent ${this.agentId} pid=${this.child.pid} kill requested signal=${signal}`
|
|
1425
|
+
);
|
|
1386
1426
|
await this.connection.close().catch(() => void 0);
|
|
1387
1427
|
this.child.kill(signal);
|
|
1388
1428
|
}
|
|
@@ -1495,6 +1535,7 @@ var Session = class {
|
|
|
1495
1535
|
// and noisy state churn keep a quiet session alive forever.
|
|
1496
1536
|
lastRecordedAt;
|
|
1497
1537
|
spawnReplacementAgent;
|
|
1538
|
+
logger;
|
|
1498
1539
|
agentChangeHandlers = [];
|
|
1499
1540
|
// Last available_commands_update we observed from the agent. Stored
|
|
1500
1541
|
// so we can re-broadcast a merged (hydra ∪ agent) list whenever
|
|
@@ -1526,6 +1567,7 @@ var Session = class {
|
|
|
1526
1567
|
}
|
|
1527
1568
|
this.idleTimeoutMs = init.idleTimeoutMs ?? 0;
|
|
1528
1569
|
this.spawnReplacementAgent = init.spawnReplacementAgent;
|
|
1570
|
+
this.logger = init.logger;
|
|
1529
1571
|
if (init.firstPromptSeeded) {
|
|
1530
1572
|
this.firstPromptSeeded = true;
|
|
1531
1573
|
}
|
|
@@ -1855,6 +1897,9 @@ var Session = class {
|
|
|
1855
1897
|
if (this.closed) {
|
|
1856
1898
|
return;
|
|
1857
1899
|
}
|
|
1900
|
+
this.logger?.info(
|
|
1901
|
+
`session ${this.sessionId} closing deleteRecord=${opts.deleteRecord ?? false} regenTitle=${opts.regenTitle ?? false}`
|
|
1902
|
+
);
|
|
1858
1903
|
this.cancelIdleTimer();
|
|
1859
1904
|
if (opts.regenTitle && this.firstPromptSeeded) {
|
|
1860
1905
|
const timeoutMs = opts.regenTitleTimeoutMs ?? 5e3;
|
|
@@ -2409,6 +2454,10 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
2409
2454
|
return;
|
|
2410
2455
|
}
|
|
2411
2456
|
const opts = this.firstPromptSeeded ? { deleteRecord: false, regenTitle: true } : { deleteRecord: true };
|
|
2457
|
+
const idleSec = Math.round(idle / 1e3);
|
|
2458
|
+
this.logger?.info(
|
|
2459
|
+
`session ${this.sessionId} idle timeout fired after ${idleSec}s (window=${Math.round(this.idleTimeoutMs / 1e3)}s) \u2014 closing`
|
|
2460
|
+
);
|
|
2412
2461
|
void this.close(opts).catch(() => void 0);
|
|
2413
2462
|
}
|
|
2414
2463
|
cancelIdleTimer() {
|
|
@@ -2777,6 +2826,16 @@ var SessionRecord = z4.object({
|
|
|
2777
2826
|
// origin's local id at export time, kept for debuggability and as a
|
|
2778
2827
|
// breadcrumb in `sessions list` (informational, not used for routing).
|
|
2779
2828
|
importedFromSessionId: z4.string().optional(),
|
|
2829
|
+
// Origin's agent-side session id at export time. Carried as a
|
|
2830
|
+
// breadcrumb and as the handle a future "connect back to origin"
|
|
2831
|
+
// feature would dial. Absent when the origin record had no upstream
|
|
2832
|
+
// bound (re-export of an imported, not-yet-attached session).
|
|
2833
|
+
importedFromUpstreamSessionId: z4.string().optional(),
|
|
2834
|
+
// Hostname of the machine that exported the bundle we imported
|
|
2835
|
+
// (i.e. the most recent hop, not necessarily the true multi-hop
|
|
2836
|
+
// origin). Surfaced in the picker so imported rows don't look like
|
|
2837
|
+
// they materialized from nowhere.
|
|
2838
|
+
importedFromMachine: z4.string().optional(),
|
|
2780
2839
|
agentId: z4.string(),
|
|
2781
2840
|
cwd: z4.string(),
|
|
2782
2841
|
title: z4.string().optional(),
|
|
@@ -2897,6 +2956,8 @@ function recordFromMemorySession(args) {
|
|
|
2897
2956
|
lineageId: args.lineageId,
|
|
2898
2957
|
upstreamSessionId: args.upstreamSessionId,
|
|
2899
2958
|
importedFromSessionId: args.importedFromSessionId,
|
|
2959
|
+
importedFromUpstreamSessionId: args.importedFromUpstreamSessionId,
|
|
2960
|
+
importedFromMachine: args.importedFromMachine,
|
|
2900
2961
|
agentId: args.agentId,
|
|
2901
2962
|
cwd: args.cwd,
|
|
2902
2963
|
title: args.title,
|
|
@@ -3114,6 +3175,7 @@ var SessionManager = class {
|
|
|
3114
3175
|
this.histories = new HistoryStore({ maxEntries: this.sessionHistoryMaxEntries });
|
|
3115
3176
|
this.idleTimeoutMs = options.idleTimeoutMs ?? 0;
|
|
3116
3177
|
this.defaultModels = options.defaultModels ?? {};
|
|
3178
|
+
this.logger = options.logger;
|
|
3117
3179
|
}
|
|
3118
3180
|
registry;
|
|
3119
3181
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -3128,6 +3190,7 @@ var SessionManager = class {
|
|
|
3128
3190
|
// concurrent snapshot updates (e.g. an agent emitting model + mode
|
|
3129
3191
|
// back-to-back) don't lose writes via interleaved reads.
|
|
3130
3192
|
metaWriteQueues = /* @__PURE__ */ new Map();
|
|
3193
|
+
logger;
|
|
3131
3194
|
async create(params) {
|
|
3132
3195
|
const fresh = await this.bootstrapAgent({
|
|
3133
3196
|
agentId: params.agentId,
|
|
@@ -3145,6 +3208,7 @@ var SessionManager = class {
|
|
|
3145
3208
|
title: params.title,
|
|
3146
3209
|
agentArgs: params.agentArgs,
|
|
3147
3210
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
3211
|
+
logger: this.logger,
|
|
3148
3212
|
spawnReplacementAgent: (p) => this.bootstrapAgent({ ...p, mcpServers: [] }),
|
|
3149
3213
|
historyStore: this.histories,
|
|
3150
3214
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
@@ -3238,6 +3302,7 @@ var SessionManager = class {
|
|
|
3238
3302
|
title: params.title,
|
|
3239
3303
|
agentArgs: params.agentArgs,
|
|
3240
3304
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
3305
|
+
logger: this.logger,
|
|
3241
3306
|
spawnReplacementAgent: (p) => this.bootstrapAgent({ ...p, mcpServers: [] }),
|
|
3242
3307
|
historyStore: this.histories,
|
|
3243
3308
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
@@ -3284,6 +3349,7 @@ var SessionManager = class {
|
|
|
3284
3349
|
title: params.title,
|
|
3285
3350
|
agentArgs: params.agentArgs,
|
|
3286
3351
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
3352
|
+
logger: this.logger,
|
|
3287
3353
|
spawnReplacementAgent: (p) => this.bootstrapAgent({ ...p, mcpServers: [] }),
|
|
3288
3354
|
historyStore: this.histories,
|
|
3289
3355
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
@@ -3548,6 +3614,8 @@ var SessionManager = class {
|
|
|
3548
3614
|
agentId: r.agentId,
|
|
3549
3615
|
currentModel: r.currentModel,
|
|
3550
3616
|
currentUsage: r.currentUsage,
|
|
3617
|
+
importedFromMachine: r.importedFromMachine,
|
|
3618
|
+
importedFromUpstreamSessionId: r.importedFromUpstreamSessionId,
|
|
3551
3619
|
updatedAt: used,
|
|
3552
3620
|
attachedClients: 0,
|
|
3553
3621
|
status: "cold"
|
|
@@ -3655,6 +3723,8 @@ var SessionManager = class {
|
|
|
3655
3723
|
lineageId: args.bundle.session.lineageId,
|
|
3656
3724
|
upstreamSessionId: "",
|
|
3657
3725
|
importedFromSessionId: args.bundle.session.sessionId,
|
|
3726
|
+
importedFromUpstreamSessionId: args.bundle.session.upstreamSessionId,
|
|
3727
|
+
importedFromMachine: args.bundle.exportedFrom.machine,
|
|
3658
3728
|
agentId: args.bundle.session.agentId,
|
|
3659
3729
|
cwd: args.cwd ?? args.bundle.session.cwd,
|
|
3660
3730
|
title: args.bundle.session.title,
|
|
@@ -3777,6 +3847,8 @@ function mergeForPersistence(session, existing) {
|
|
|
3777
3847
|
lineageId: existing?.lineageId ?? generateLineageId(),
|
|
3778
3848
|
upstreamSessionId: session.upstreamSessionId,
|
|
3779
3849
|
importedFromSessionId: existing?.importedFromSessionId,
|
|
3850
|
+
importedFromUpstreamSessionId: existing?.importedFromUpstreamSessionId,
|
|
3851
|
+
importedFromMachine: existing?.importedFromMachine,
|
|
3780
3852
|
agentId: session.agentId,
|
|
3781
3853
|
cwd: session.cwd,
|
|
3782
3854
|
title: session.title,
|
|
@@ -4362,6 +4434,12 @@ var BundleSession = z5.object({
|
|
|
4362
4434
|
// Required on bundles — the export path backfills if the source
|
|
4363
4435
|
// record was written before lineageId existed.
|
|
4364
4436
|
lineageId: z5.string(),
|
|
4437
|
+
// The exporter's agent-side session id at export time. Carried so
|
|
4438
|
+
// importers can persist it as a breadcrumb (and, eventually, as the
|
|
4439
|
+
// handle a "connect back to origin" feature would need). Omitted on
|
|
4440
|
+
// bundles whose source record never bound to an agent (e.g. a
|
|
4441
|
+
// re-export of an imported, not-yet-attached session).
|
|
4442
|
+
upstreamSessionId: z5.string().optional(),
|
|
4365
4443
|
agentId: z5.string(),
|
|
4366
4444
|
cwd: z5.string(),
|
|
4367
4445
|
title: z5.string().optional(),
|
|
@@ -4394,6 +4472,7 @@ function encodeBundle(params) {
|
|
|
4394
4472
|
session: {
|
|
4395
4473
|
sessionId: params.record.sessionId,
|
|
4396
4474
|
lineageId: params.record.lineageId,
|
|
4475
|
+
...params.record.upstreamSessionId ? { upstreamSessionId: params.record.upstreamSessionId } : {},
|
|
4397
4476
|
agentId: params.record.agentId,
|
|
4398
4477
|
cwd: params.record.cwd,
|
|
4399
4478
|
...params.record.title !== void 0 ? { title: params.record.title } : {},
|
|
@@ -5185,11 +5264,20 @@ async function startDaemon(config) {
|
|
|
5185
5264
|
await auth(request, reply);
|
|
5186
5265
|
});
|
|
5187
5266
|
const registry = new Registry(config);
|
|
5188
|
-
const
|
|
5267
|
+
const agentLogger = {
|
|
5268
|
+
info: (msg) => app.log.info(msg),
|
|
5269
|
+
warn: (msg) => app.log.warn(msg)
|
|
5270
|
+
};
|
|
5271
|
+
const spawner = (opts) => AgentInstance.spawn({
|
|
5272
|
+
...opts,
|
|
5273
|
+
stderrTailBytes: config.daemon.agentStderrTailBytes,
|
|
5274
|
+
logger: agentLogger
|
|
5275
|
+
});
|
|
5189
5276
|
const manager = new SessionManager(registry, spawner, void 0, {
|
|
5190
5277
|
idleTimeoutMs: config.daemon.sessionIdleTimeoutSeconds * 1e3,
|
|
5191
5278
|
defaultModels: config.defaultModels,
|
|
5192
|
-
sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries
|
|
5279
|
+
sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries,
|
|
5280
|
+
logger: agentLogger
|
|
5193
5281
|
});
|
|
5194
5282
|
const extensions = new ExtensionManager(extensionList(config));
|
|
5195
5283
|
registerHealthRoutes(app, HYDRA_VERSION);
|