@hydra-acp/cli 0.1.57 → 0.1.58

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/index.js CHANGED
@@ -104,7 +104,11 @@ var paths = {
104
104
  // line, append-only so concurrent TUIs don't lose each other's
105
105
  // writes.
106
106
  globalTuiHistoryFile: () => path.join(hydraHome(), "prompt-history"),
107
- tuiLogFile: () => path.join(hydraHome(), "tui.log")
107
+ tuiLogFile: () => path.join(hydraHome(), "tui.log"),
108
+ // Diagnostic dump of every JSON-RPC message that crosses a `hydra-acp
109
+ // shim` process. Append-only NDJSON. One file shared by every shim;
110
+ // each line carries the writing process's pid for disambiguation.
111
+ shimWireLogFile: () => path.join(hydraHome(), "shim-wire.log")
108
112
  };
109
113
 
110
114
  // src/core/service-token.ts
@@ -1441,6 +1445,12 @@ function extractHydraMeta(meta) {
1441
1445
  if (typeof obj.mcpStdin === "boolean") {
1442
1446
  out.mcpStdin = obj.mcpStdin;
1443
1447
  }
1448
+ if (typeof obj.interactive === "boolean") {
1449
+ out.interactive = obj.interactive;
1450
+ }
1451
+ if (typeof obj.ancillary === "boolean") {
1452
+ out.ancillary = obj.ancillary;
1453
+ }
1444
1454
  if (typeof obj.promptAmending === "boolean") {
1445
1455
  out.promptAmending = obj.promptAmending;
1446
1456
  }
@@ -1561,10 +1571,14 @@ var SessionListEntry = z3.object({
1561
1571
  // local session, an import is a cross-machine takeover.
1562
1572
  forkedFromSessionId: z3.string().optional(),
1563
1573
  forkedFromMessageId: z3.string().optional(),
1564
- // clientInfo from the process that issued session/new. Lets list views
1565
- // hide cat-style ancillary sessions by default while letting an
1566
- // override flag surface them.
1574
+ // clientInfo from the process that issued session/new. Carried for
1575
+ // log/display; the effective filtering signal is `interactive` below.
1567
1576
  originatingClient: z3.object({ name: z3.string(), version: z3.string().optional() }).optional(),
1577
+ // Tristate filter signal computed by effectiveInteractive(): explicit
1578
+ // when the record stored a value, else inferred (legacy cat hint or
1579
+ // history-presence). Clients can use this to render a hint glyph
1580
+ // (e.g. dim non-interactive rows when the user toggles them in).
1581
+ interactive: z3.boolean().optional(),
1568
1582
  updatedAt: z3.string(),
1569
1583
  attachedClients: z3.number().int().nonnegative(),
1570
1584
  status: z3.enum(["live", "cold"]).default("live"),
@@ -1622,7 +1636,10 @@ function sessionListEntryToWire(entry) {
1622
1636
  }
1623
1637
  var SessionPromptParams = z3.object({
1624
1638
  sessionId: z3.string(),
1625
- prompt: z3.array(z3.unknown())
1639
+ prompt: z3.array(z3.unknown()),
1640
+ // Hydra extensions ride under _meta["hydra-acp"] (e.g. `ancillary` to
1641
+ // mark a non-promoting turn). Kept so Session.prompt can read them.
1642
+ _meta: z3.record(z3.unknown()).optional()
1626
1643
  });
1627
1644
  var SessionCancelParams = z3.object({
1628
1645
  sessionId: z3.string()
@@ -2882,6 +2899,12 @@ var Session = class {
2882
2899
  forkedFromSessionId;
2883
2900
  forkedFromMessageId;
2884
2901
  originatingClient;
2902
+ // Tristate. Mutates from undefined → true on first prompt (or directly
2903
+ // false if init.interactive === false). Persisted via interactiveHandlers.
2904
+ _interactive;
2905
+ get interactive() {
2906
+ return this._interactive;
2907
+ }
2885
2908
  title;
2886
2909
  // Snapshot state delivered to attaching clients via the attach
2887
2910
  // response _meta rather than via history replay (which would be
@@ -3010,6 +3033,7 @@ var Session = class {
3010
3033
  agentModelsHandlers = [];
3011
3034
  modelHandlers = [];
3012
3035
  modeHandlers = [];
3036
+ interactiveHandlers = [];
3013
3037
  usageHandlers = [];
3014
3038
  cumulativeCost = 0;
3015
3039
  // Total cost across all agent lives. costAmount in the returned snapshot
@@ -3093,6 +3117,7 @@ var Session = class {
3093
3117
  if (init.firstPromptSeeded) {
3094
3118
  this._firstPromptSeeded = true;
3095
3119
  }
3120
+ this._interactive = init.interactive;
3096
3121
  this.historyStore = init.historyStore;
3097
3122
  this.historyMaxEntries = init.historyMaxEntries ?? DEFAULT_HISTORY_MAX_ENTRIES;
3098
3123
  this.compactEvery = Math.max(1, Math.floor(this.historyMaxEntries * 0.2));
@@ -3355,19 +3380,25 @@ var Session = class {
3355
3380
  return this.loadReplay(historyPolicy, opts);
3356
3381
  }
3357
3382
  async loadReplay(historyPolicy, opts) {
3358
- const all = coalesceReplay(await this.getHistorySnapshot());
3383
+ const raw = await this.getHistorySnapshot();
3359
3384
  const state = this.buildStateSnapshotReplay();
3360
3385
  if (historyPolicy === "after_message") {
3361
- const cutoff = opts.afterMessageId ? findMessageIdIndex(all, opts.afterMessageId) : -1;
3386
+ const cutoff = opts.afterMessageId ? findMessageIdIndex(raw, opts.afterMessageId) : -1;
3362
3387
  if (cutoff < 0) {
3363
- return { entries: [...state, ...all], appliedPolicy: "full" };
3388
+ return {
3389
+ entries: [...state, ...coalesceReplay(raw)],
3390
+ appliedPolicy: "full"
3391
+ };
3364
3392
  }
3365
3393
  return {
3366
- entries: [...state, ...all.slice(cutoff + 1)],
3394
+ entries: [...state, ...coalesceReplay(raw.slice(cutoff + 1))],
3367
3395
  appliedPolicy: "after_message"
3368
3396
  };
3369
3397
  }
3370
- return { entries: [...state, ...all], appliedPolicy: "full" };
3398
+ return {
3399
+ entries: [...state, ...coalesceReplay(raw)],
3400
+ appliedPolicy: "full"
3401
+ };
3371
3402
  }
3372
3403
  // Synthesizes one session/update notification per cached STATE_UPDATE_KIND
3373
3404
  // so an attaching client receives the current snapshot through the
@@ -3548,6 +3579,18 @@ var Session = class {
3548
3579
  const messageId = generateMessageId();
3549
3580
  this.maybeSeedTitleFromPrompt(params);
3550
3581
  this._firstPromptSeeded = true;
3582
+ const ancillary = extractHydraMeta(
3583
+ (params ?? {})._meta
3584
+ ).ancillary === true;
3585
+ if (!ancillary && this._interactive === void 0) {
3586
+ this._interactive = true;
3587
+ for (const handler of this.interactiveHandlers) {
3588
+ try {
3589
+ handler(true);
3590
+ } catch {
3591
+ }
3592
+ }
3593
+ }
3551
3594
  return this.enqueueUserPrompt(client, params, messageId);
3552
3595
  }
3553
3596
  // DEVIATION FROM RFD #533: this broadcast is deliberately deferred
@@ -4330,13 +4373,7 @@ var Session = class {
4330
4373
  this.logger?.info(
4331
4374
  `live config_option_update(model): sessionId=${this.sessionId} ${JSON.stringify(this.currentModel)} \u2192 ${JSON.stringify(trimmed)}`
4332
4375
  );
4333
- this.currentModel = trimmed;
4334
- for (const handler of this.modelHandlers) {
4335
- try {
4336
- handler(trimmed);
4337
- } catch {
4338
- }
4339
- }
4376
+ this.applyModelChange(trimmed);
4340
4377
  }
4341
4378
  }
4342
4379
  break;
@@ -4532,6 +4569,9 @@ var Session = class {
4532
4569
  onModeChange(handler) {
4533
4570
  this.modeHandlers.push(handler);
4534
4571
  }
4572
+ onInteractiveChange(handler) {
4573
+ this.interactiveHandlers.push(handler);
4574
+ }
4535
4575
  // Apply a model change initiated by a client request (session/set_model)
4536
4576
  // when the agent doesn't emit a current_model_update notification, or
4537
4577
  // emits a non-spec shape (e.g. config_option_update). Fires modelHandlers
@@ -6237,10 +6277,17 @@ var SessionRecord = z5.object({
6237
6277
  // ended at. Kept so future UI can show "branched from turn N of session X".
6238
6278
  forkedFromSessionId: z5.string().optional(),
6239
6279
  forkedFromMessageId: z5.string().optional(),
6240
- // clientInfo from the process that issued session/new. Picker and
6241
- // `sessions list` use this to hide cat-style ancillary sessions by
6242
- // default; carried in meta.json so cold sessions filter the same way.
6280
+ // clientInfo from the process that issued session/new. Display only
6281
+ // since the `interactive` flag below; kept on the record for log
6282
+ // attribution and as the legacy hint inside effectiveInteractive
6283
+ // (pre-flag cat sessions can be recognised from this field).
6243
6284
  originatingClient: PersistedOriginatingClient.optional(),
6285
+ // Tristate: true once the session has had a real turn, false when
6286
+ // explicitly created as ancillary (e.g. `hydra cat`), undefined for
6287
+ // pre-flag records / freshly-created sessions that haven't decided
6288
+ // yet. effectiveInteractive() in session-manager.ts is the single
6289
+ // resolver — every filter site goes through it.
6290
+ interactive: z5.boolean().optional(),
6244
6291
  createdAt: z5.string(),
6245
6292
  updatedAt: z5.string()
6246
6293
  });
@@ -6359,6 +6406,7 @@ function recordFromMemorySession(args) {
6359
6406
  forkedFromSessionId: args.forkedFromSessionId,
6360
6407
  forkedFromMessageId: args.forkedFromMessageId,
6361
6408
  originatingClient: args.originatingClient,
6409
+ interactive: args.interactive,
6362
6410
  createdAt: args.createdAt ?? now,
6363
6411
  updatedAt: args.updatedAt ?? now
6364
6412
  };
@@ -7214,6 +7262,14 @@ var BundleSession = z6.object({
7214
7262
  currentUsage: PersistedUsage.optional(),
7215
7263
  agentCommands: z6.array(PersistedAgentCommand).optional(),
7216
7264
  agentModes: z6.array(PersistedAgentMode).optional(),
7265
+ // Raw interactive tristate (NOT the resolved effectiveInteractive) so
7266
+ // the value stays promotable on the destination: a cat/empty source
7267
+ // arrives as undefined and a real turn there can still flip it to
7268
+ // true. Carried alongside originatingClient so the importer's
7269
+ // effectiveInteractive can re-apply the cat-name hint at read time
7270
+ // without freezing a sticky `false` into the record.
7271
+ interactive: z6.boolean().optional(),
7272
+ originatingClient: PersistedOriginatingClient.optional(),
7217
7273
  createdAt: z6.string(),
7218
7274
  updatedAt: z6.string()
7219
7275
  });
@@ -7257,6 +7313,8 @@ function encodeBundle(params) {
7257
7313
  ...params.record.currentUsage !== void 0 ? { currentUsage: params.record.currentUsage } : {},
7258
7314
  ...params.record.agentCommands !== void 0 ? { agentCommands: params.record.agentCommands } : {},
7259
7315
  ...params.record.agentModes !== void 0 ? { agentModes: params.record.agentModes } : {},
7316
+ ...params.record.interactive !== void 0 ? { interactive: params.record.interactive } : {},
7317
+ ...params.record.originatingClient !== void 0 ? { originatingClient: params.record.originatingClient } : {},
7260
7318
  createdAt: params.record.createdAt,
7261
7319
  updatedAt: params.record.updatedAt
7262
7320
  },
@@ -7292,6 +7350,7 @@ var SessionManager = class {
7292
7350
  this.logger = options.logger;
7293
7351
  this.npmRegistry = options.npmRegistry;
7294
7352
  this.extensionCommands = options.extensionCommands;
7353
+ this.defaultCwd = options.defaultCwd ?? "~";
7295
7354
  this.synopsisCoordinator = new SynopsisCoordinator({
7296
7355
  registry: this.registry,
7297
7356
  store: this.store,
@@ -7325,6 +7384,7 @@ var SessionManager = class {
7325
7384
  logger;
7326
7385
  npmRegistry;
7327
7386
  extensionCommands;
7387
+ defaultCwd;
7328
7388
  // Background queue for ephemeral-agent synopsis generation. Runs
7329
7389
  // out-of-band so session close is instant; persists synopsis/title
7330
7390
  // via the same enqueueMetaWrite path the in-session handlers used.
@@ -7384,6 +7444,7 @@ var SessionManager = class {
7384
7444
  transformChain: params.transformChain,
7385
7445
  parentSessionId: params.parentSessionId,
7386
7446
  originatingClient: params.originatingClient,
7447
+ interactive: params.interactive,
7387
7448
  extensionCommands: this.extensionCommands,
7388
7449
  scheduleSynopsis: () => this.synopsisCoordinator.schedule(session.sessionId)
7389
7450
  });
@@ -7430,6 +7491,9 @@ var SessionManager = class {
7430
7491
  if (params.upstreamSessionId === "") {
7431
7492
  return this.doResurrectFromImport(params);
7432
7493
  }
7494
+ if (!await this.dirExists(params.cwd)) {
7495
+ return this.doResurrectFromImport(params);
7496
+ }
7433
7497
  const plan = await planSpawn(agentDef, params.agentArgs ?? [], {
7434
7498
  npmRegistry: this.npmRegistry,
7435
7499
  onInstallProgress: params.onInstallProgress
@@ -7557,6 +7621,7 @@ var SessionManager = class {
7557
7621
  firstPromptSeeded: !!params.title,
7558
7622
  createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
7559
7623
  originatingClient: params.originatingClient,
7624
+ interactive: params.interactive,
7560
7625
  forkedFromSessionId: params.forkedFromSessionId,
7561
7626
  forkedFromMessageId: params.forkedFromMessageId,
7562
7627
  extensionCommands: this.extensionCommands,
@@ -7573,7 +7638,7 @@ var SessionManager = class {
7573
7638
  // so subsequent resurrects of this session use the normal session/load
7574
7639
  // path.
7575
7640
  async doResurrectFromImport(params) {
7576
- const cwd = await this.resolveImportCwd(params.cwd);
7641
+ const cwd = await this.resolveResurrectCwd(params.cwd);
7577
7642
  const fresh = await this.bootstrapAgent({
7578
7643
  agentId: params.agentId,
7579
7644
  cwd,
@@ -7628,6 +7693,7 @@ var SessionManager = class {
7628
7693
  firstPromptSeeded: !!params.title,
7629
7694
  createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
7630
7695
  originatingClient: params.originatingClient,
7696
+ interactive: params.interactive,
7631
7697
  forkedFromSessionId: params.forkedFromSessionId,
7632
7698
  forkedFromMessageId: params.forkedFromMessageId,
7633
7699
  extensionCommands: this.extensionCommands,
@@ -7637,15 +7703,50 @@ var SessionManager = class {
7637
7703
  void session.seedFromImport().catch(() => void 0);
7638
7704
  return session;
7639
7705
  }
7640
- async resolveImportCwd(cwd) {
7706
+ async dirExists(cwd) {
7641
7707
  try {
7642
- const stat2 = await fs13.stat(cwd);
7643
- if (stat2.isDirectory()) {
7644
- return cwd;
7645
- }
7708
+ return (await fs13.stat(cwd)).isDirectory();
7646
7709
  } catch {
7710
+ return false;
7647
7711
  }
7648
- return os2.homedir();
7712
+ }
7713
+ // When the last client detaches from a session that resolves to
7714
+ // non-interactive — e.g. a `hydra cat` run, born interactive:undefined
7715
+ // with originatingClient hydra-acp-cat, whose every prompt is ancillary
7716
+ // — close it so its agent process doesn't linger until the (default 1h)
7717
+ // idle timeout fires. The cold record is kept, so the rare refine-in-TUI
7718
+ // still works via the resurrect/reseed path. Sessions promoted to
7719
+ // interactive (driven by a real, non-ancillary prompt) resolve to true
7720
+ // and are left running.
7721
+ async reapIfOrphanedNonInteractive(sessionId) {
7722
+ const session = this.sessions.get(sessionId);
7723
+ if (!session || session.attachedCount > 0) {
7724
+ return;
7725
+ }
7726
+ const interactive = effectiveInteractive(
7727
+ {
7728
+ interactive: session.interactive,
7729
+ ...session.originatingClient ? { originatingClient: session.originatingClient } : {}
7730
+ },
7731
+ true
7732
+ );
7733
+ if (interactive !== false) {
7734
+ return;
7735
+ }
7736
+ this.logger?.info(
7737
+ `reaping orphaned non-interactive session ${sessionId} (agent killed, cold record kept)`
7738
+ );
7739
+ await session.close({ deleteRecord: false }).catch(() => void 0);
7740
+ }
7741
+ // Resolve a recorded cwd for resurrect: use it if it still exists,
7742
+ // otherwise fall back to the configured defaultCwd. Covers both bundles
7743
+ // imported from another machine and local sessions (e.g. `cat`) whose
7744
+ // recorded dir was cleaned up, so the reseed spawn never ENOENTs.
7745
+ async resolveResurrectCwd(cwd) {
7746
+ if (await this.dirExists(cwd)) {
7747
+ return cwd;
7748
+ }
7749
+ return expandHome(this.defaultCwd);
7649
7750
  }
7650
7751
  // Pull every session the agent itself remembers (across all cwds) and
7651
7752
  // persist a cold hydra record for each one we don't already track.
@@ -7734,6 +7835,10 @@ var SessionManager = class {
7734
7835
  agentId,
7735
7836
  cwd: entry.cwd,
7736
7837
  pendingHistorySync: true,
7838
+ // `hydra agent sync` is a user-explicit "show me agent-side
7839
+ // sessions" action; the rows are meant to be visible immediately
7840
+ // even before the first resurrect populates history.jsonl.
7841
+ interactive: true,
7737
7842
  createdAt: ts,
7738
7843
  updatedAt: ts
7739
7844
  };
@@ -7900,6 +8005,11 @@ var SessionManager = class {
7900
8005
  () => void 0
7901
8006
  );
7902
8007
  });
8008
+ session.onInteractiveChange((interactive) => {
8009
+ void this.persistSnapshot(session.sessionId, { interactive }).catch(
8010
+ () => void 0
8011
+ );
8012
+ });
7903
8013
  session.onUsageChange((usage) => {
7904
8014
  void this.persistSnapshot(session.sessionId, {
7905
8015
  currentUsage: usageSnapshotToPersisted(usage)
@@ -7993,6 +8103,7 @@ var SessionManager = class {
7993
8103
  createdAt: record.createdAt,
7994
8104
  pendingHistorySync: record.pendingHistorySync,
7995
8105
  originatingClient: record.originatingClient,
8106
+ interactive: record.interactive,
7996
8107
  forkedFromSessionId: record.forkedFromSessionId,
7997
8108
  forkedFromMessageId: record.forkedFromMessageId
7998
8109
  };
@@ -8080,12 +8191,27 @@ var SessionManager = class {
8080
8191
  async list(filter = {}) {
8081
8192
  const entries = [];
8082
8193
  const liveIds = /* @__PURE__ */ new Set();
8194
+ const includeRow = (interactive) => {
8195
+ if (filter.includeNonInteractive) return true;
8196
+ return interactive === true;
8197
+ };
8083
8198
  for (const session of this.sessions.values()) {
8084
8199
  if (filter.cwd && session.cwd !== filter.cwd) {
8085
8200
  continue;
8086
8201
  }
8087
8202
  liveIds.add(session.sessionId);
8088
- const used = await historyMtimeIso(session.sessionId) ?? new Date(session.updatedAt).toISOString();
8203
+ const hist = await historyStatus(session.sessionId);
8204
+ const interactive = effectiveInteractive(
8205
+ {
8206
+ interactive: session.interactive,
8207
+ ...session.originatingClient ? { originatingClient: session.originatingClient } : {}
8208
+ },
8209
+ hist.hasContent
8210
+ );
8211
+ if (!includeRow(interactive)) {
8212
+ continue;
8213
+ }
8214
+ const used = hist.mtime ?? new Date(session.updatedAt).toISOString();
8089
8215
  entries.push({
8090
8216
  sessionId: session.sessionId,
8091
8217
  upstreamSessionId: session.upstreamSessionId,
@@ -8098,6 +8224,7 @@ var SessionManager = class {
8098
8224
  forkedFromSessionId: session.forkedFromSessionId,
8099
8225
  forkedFromMessageId: session.forkedFromMessageId,
8100
8226
  originatingClient: session.originatingClient,
8227
+ interactive,
8101
8228
  updatedAt: used,
8102
8229
  attachedClients: session.attachedCount,
8103
8230
  status: "live",
@@ -8112,7 +8239,12 @@ var SessionManager = class {
8112
8239
  if (filter.cwd && r.cwd !== filter.cwd) {
8113
8240
  continue;
8114
8241
  }
8115
- const used = await historyMtimeIso(r.sessionId) ?? r.updatedAt;
8242
+ const hist = await historyStatus(r.sessionId);
8243
+ const interactive = effectiveInteractive(r, hist.hasContent);
8244
+ if (!includeRow(interactive)) {
8245
+ continue;
8246
+ }
8247
+ const used = hist.mtime ?? r.updatedAt;
8116
8248
  entries.push({
8117
8249
  sessionId: r.sessionId,
8118
8250
  upstreamSessionId: r.upstreamSessionId,
@@ -8130,6 +8262,7 @@ var SessionManager = class {
8130
8262
  forkedFromSessionId: r.forkedFromSessionId,
8131
8263
  forkedFromMessageId: r.forkedFromMessageId,
8132
8264
  originatingClient: r.originatingClient,
8265
+ interactive,
8133
8266
  updatedAt: used,
8134
8267
  attachedClients: 0,
8135
8268
  status: "cold",
@@ -8364,8 +8497,18 @@ var SessionManager = class {
8364
8497
  currentUsage: args.bundle.session.currentUsage,
8365
8498
  agentCommands: args.bundle.session.agentCommands,
8366
8499
  agentModes: args.bundle.session.agentModes,
8500
+ // Carry the source's raw interactive tristate and originating
8501
+ // client rather than forcing true. A real conversation arrives
8502
+ // as true (visible immediately); an empty source arrives as
8503
+ // undefined (hidden until a turn lands here); a cat source
8504
+ // arrives as undefined + cat originatingClient, so
8505
+ // effectiveInteractive hides it via the hint while leaving it
8506
+ // promotable. Legacy bundles (pre-flag) carry neither and fall
8507
+ // back to effectiveInteractive's history-presence inference.
8508
+ interactive: args.bundle.session.interactive,
8509
+ originatingClient: args.bundle.session.originatingClient,
8367
8510
  createdAt: args.preservedCreatedAt ?? now,
8368
- // Fallback path for historyMtimeIso (used when the history file
8511
+ // Fallback path for historyStatus (used when the history file
8369
8512
  // is missing). Keep this consistent with the utimes stamp above.
8370
8513
  updatedAt: args.bundle.session.updatedAt
8371
8514
  });
@@ -8474,6 +8617,8 @@ var SessionManager = class {
8474
8617
  ...update.agentCommands !== void 0 ? { agentCommands: update.agentCommands } : {},
8475
8618
  ...update.agentModes !== void 0 ? { agentModes: update.agentModes } : {},
8476
8619
  ...update.agentModels !== void 0 ? { agentModels: update.agentModels } : {},
8620
+ ...update.interactive !== void 0 ? { interactive: update.interactive } : {},
8621
+ ...update.cwd !== void 0 ? { cwd: update.cwd } : {},
8477
8622
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
8478
8623
  });
8479
8624
  });
@@ -8650,6 +8795,7 @@ function mergeForPersistence(session, existing) {
8650
8795
  forkedFromSessionId: session.forkedFromSessionId ?? existing?.forkedFromSessionId,
8651
8796
  forkedFromMessageId: session.forkedFromMessageId ?? existing?.forkedFromMessageId,
8652
8797
  originatingClient: session.originatingClient ?? existing?.originatingClient,
8798
+ interactive: session.interactive ?? existing?.interactive,
8653
8799
  createdAt: existing?.createdAt ?? new Date(session.createdAt).toISOString()
8654
8800
  });
8655
8801
  }
@@ -8956,13 +9102,25 @@ async function loadPromptHistorySafely(sessionId) {
8956
9102
  return [];
8957
9103
  }
8958
9104
  }
8959
- async function historyMtimeIso(sessionId) {
9105
+ async function historyStatus(sessionId) {
8960
9106
  try {
8961
9107
  const st = await fs13.stat(paths.historyFile(sessionId));
8962
- return new Date(st.mtimeMs).toISOString();
9108
+ return {
9109
+ mtime: new Date(st.mtimeMs).toISOString(),
9110
+ hasContent: st.size > 0
9111
+ };
8963
9112
  } catch {
8964
- return void 0;
9113
+ return { hasContent: false };
9114
+ }
9115
+ }
9116
+ function effectiveInteractive(record, hasContent) {
9117
+ if (record.interactive !== void 0) {
9118
+ return record.interactive;
9119
+ }
9120
+ if (record.originatingClient?.name === HYDRA_CAT_CLIENT_NAME) {
9121
+ return false;
8965
9122
  }
9123
+ return hasContent ? true : void 0;
8966
9124
  }
8967
9125
 
8968
9126
  // src/core/child-supervisor.ts
@@ -11189,17 +11347,21 @@ function resolveHydraHost(defaults) {
11189
11347
  function registerSessionRoutes(app, manager, defaults) {
11190
11348
  app.get("/v1/sessions", async (request) => {
11191
11349
  const query = request.query;
11192
- const sessions = await manager.list({ cwd: query?.cwd });
11350
+ const includeNonInteractive = query?.includeNonInteractive === "1" || query?.includeNonInteractive === "true";
11351
+ const sessions = await manager.list({
11352
+ cwd: query?.cwd,
11353
+ includeNonInteractive
11354
+ });
11193
11355
  return { sessions };
11194
11356
  });
11195
- app.get("/v1/sessions/search", async (request, reply) => {
11196
- const query = request.query;
11197
- const q = query?.q ?? "";
11357
+ app.post("/v1/sessions/search", async (request, reply) => {
11358
+ const body = request.body ?? {};
11359
+ const q = typeof body.q === "string" ? body.q : "";
11198
11360
  if (q.trim().length === 0) {
11199
11361
  reply.code(400).send({ error: "q is required" });
11200
11362
  return reply;
11201
11363
  }
11202
- const ids = query?.sessionIds ? query.sessionIds.split(",").filter((s) => s.length > 0) : void 0;
11364
+ const ids = Array.isArray(body.sessionIds) ? body.sessionIds.filter((s) => typeof s === "string" && s.length > 0) : void 0;
11203
11365
  const out = await searchHistories(manager, q, { sessionIds: ids });
11204
11366
  return out;
11205
11367
  });
@@ -11793,13 +11955,8 @@ function parseRegisterBody2(body) {
11793
11955
  }
11794
11956
 
11795
11957
  // src/daemon/routes/config.ts
11796
- function registerConfigRoutes(app, defaults) {
11797
- app.get("/v1/config", async () => {
11798
- return {
11799
- defaultAgent: defaults.defaultAgent,
11800
- defaultCwd: defaults.defaultCwd
11801
- };
11802
- });
11958
+ function registerConfigRoutes(app, snapshot) {
11959
+ app.get("/v1/config", async () => snapshot);
11803
11960
  }
11804
11961
 
11805
11962
  // src/daemon/routes/auth.ts
@@ -12364,7 +12521,8 @@ function registerAcpWsEndpoint(app, deps) {
12364
12521
  model: hydraMeta.model,
12365
12522
  onInstallProgress: makeInstallProgressForwarder(connection),
12366
12523
  transformChain,
12367
- originatingClient: state.clientInfo
12524
+ originatingClient: state.clientInfo,
12525
+ ...hydraMeta.interactive !== void 0 ? { interactive: hydraMeta.interactive } : {}
12368
12526
  });
12369
12527
  } catch (err) {
12370
12528
  if (stdinReservation !== void 0) {
@@ -12491,8 +12649,9 @@ function registerAcpWsEndpoint(app, deps) {
12491
12649
  err.code = JsonRpcErrorCodes.SessionNotFound;
12492
12650
  throw err;
12493
12651
  }
12652
+ const resurrectWithOriginator = resurrectParams.originatingClient ? resurrectParams : { ...resurrectParams, originatingClient: state.clientInfo };
12494
12653
  session = await deps.manager.resurrect({
12495
- ...resurrectParams,
12654
+ ...resurrectWithOriginator,
12496
12655
  onInstallProgress: makeInstallProgressForwarder(connection)
12497
12656
  });
12498
12657
  wireDefaultTransformers(session, deps);
@@ -12549,6 +12708,9 @@ function registerAcpWsEndpoint(app, deps) {
12549
12708
  const session = deps.manager.get(params.sessionId);
12550
12709
  session?.detach(att.clientId);
12551
12710
  state.attached.delete(params.sessionId);
12711
+ if (session) {
12712
+ void deps.manager.reapIfOrphanedNonInteractive(params.sessionId);
12713
+ }
12552
12714
  return { sessionId: params.sessionId, status: "detached" };
12553
12715
  });
12554
12716
  connection.onRequest("session/list", async (raw) => {
@@ -13796,7 +13958,8 @@ async function startDaemon(config, serviceToken) {
13796
13958
  sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries,
13797
13959
  logger: agentLogger,
13798
13960
  npmRegistry: config.npmRegistry,
13799
- extensionCommands
13961
+ extensionCommands,
13962
+ defaultCwd: config.defaultCwd
13800
13963
  });
13801
13964
  const extensions = new ExtensionManager(extensionList(config), void 0, {
13802
13965
  tokenRegistry: processRegistry
@@ -13817,7 +13980,12 @@ async function startDaemon(config, serviceToken) {
13817
13980
  registerTransformerRoutes(app, transformers);
13818
13981
  registerConfigRoutes(app, {
13819
13982
  defaultAgent: config.defaultAgent,
13820
- defaultCwd: config.defaultCwd
13983
+ defaultCwd: config.defaultCwd,
13984
+ defaultModels: { ...config.defaultModels },
13985
+ ...config.synopsisAgent !== void 0 ? { synopsisAgent: config.synopsisAgent } : {},
13986
+ ...config.synopsisModel !== void 0 ? { synopsisModel: config.synopsisModel } : {},
13987
+ synopsisOnClose: config.synopsisOnClose,
13988
+ defaultTransformers: [...config.defaultTransformers]
13821
13989
  });
13822
13990
  registerAuthRoutes(app, {
13823
13991
  store: sessionTokenStore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
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",