@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 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
- process.stderr.write(`[${opts.agentId}] ${chunk}`);
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 (!this.killed) {
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 spawner = (opts) => AgentInstance.spawn({ ...opts, stderrTailBytes: config.daemon.agentStderrTailBytes });
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
- process.stderr.write(`[${opts.agentId}] ${chunk}`);
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 (!this.killed) {
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 spawner = (opts) => AgentInstance.spawn({ ...opts, stderrTailBytes: config.daemon.agentStderrTailBytes });
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Multi-client ACP session daemon: spawn agents, attach over WSS, multiplex sessions across editors.",
5
5
  "license": "MIT",
6
6
  "type": "module",