@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/README.md +40 -51
- package/dist/cli.js +639 -299
- package/dist/index.d.ts +51 -1
- package/dist/index.js +217 -49
- package/package.json +1 -1
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.
|
|
1565
|
-
//
|
|
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
|
|
3383
|
+
const raw = await this.getHistorySnapshot();
|
|
3359
3384
|
const state = this.buildStateSnapshotReplay();
|
|
3360
3385
|
if (historyPolicy === "after_message") {
|
|
3361
|
-
const cutoff = opts.afterMessageId ? findMessageIdIndex(
|
|
3386
|
+
const cutoff = opts.afterMessageId ? findMessageIdIndex(raw, opts.afterMessageId) : -1;
|
|
3362
3387
|
if (cutoff < 0) {
|
|
3363
|
-
return {
|
|
3388
|
+
return {
|
|
3389
|
+
entries: [...state, ...coalesceReplay(raw)],
|
|
3390
|
+
appliedPolicy: "full"
|
|
3391
|
+
};
|
|
3364
3392
|
}
|
|
3365
3393
|
return {
|
|
3366
|
-
entries: [...state, ...
|
|
3394
|
+
entries: [...state, ...coalesceReplay(raw.slice(cutoff + 1))],
|
|
3367
3395
|
appliedPolicy: "after_message"
|
|
3368
3396
|
};
|
|
3369
3397
|
}
|
|
3370
|
-
return {
|
|
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.
|
|
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.
|
|
6241
|
-
//
|
|
6242
|
-
//
|
|
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.
|
|
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
|
|
7706
|
+
async dirExists(cwd) {
|
|
7641
7707
|
try {
|
|
7642
|
-
|
|
7643
|
-
if (stat2.isDirectory()) {
|
|
7644
|
-
return cwd;
|
|
7645
|
-
}
|
|
7708
|
+
return (await fs13.stat(cwd)).isDirectory();
|
|
7646
7709
|
} catch {
|
|
7710
|
+
return false;
|
|
7647
7711
|
}
|
|
7648
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9105
|
+
async function historyStatus(sessionId) {
|
|
8960
9106
|
try {
|
|
8961
9107
|
const st = await fs13.stat(paths.historyFile(sessionId));
|
|
8962
|
-
return
|
|
9108
|
+
return {
|
|
9109
|
+
mtime: new Date(st.mtimeMs).toISOString(),
|
|
9110
|
+
hasContent: st.size > 0
|
|
9111
|
+
};
|
|
8963
9112
|
} catch {
|
|
8964
|
-
return
|
|
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
|
|
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.
|
|
11196
|
-
const
|
|
11197
|
-
const 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 =
|
|
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,
|
|
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
|
-
...
|
|
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,
|