@hydra-acp/cli 0.1.45 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/cli.js +468 -96
- package/dist/index.d.ts +22 -1
- package/dist/index.js +402 -44
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -271,7 +271,7 @@ var init_config = __esm({
|
|
|
271
271
|
// Compaction trims to this many on a periodic basis; reads also slice
|
|
272
272
|
// to the tail at this length as a defensive measure against older
|
|
273
273
|
// daemons that may have written unbounded files.
|
|
274
|
-
sessionHistoryMaxEntries: z.number().int().positive().default(
|
|
274
|
+
sessionHistoryMaxEntries: z.number().int().positive().default(1e4),
|
|
275
275
|
// Bytes of trailing agent stderr buffered per AgentInstance so the
|
|
276
276
|
// daemon can include it in the diagnostic message when a spawn fails.
|
|
277
277
|
// Bump if your agents emit large tracebacks you want surfaced.
|
|
@@ -331,7 +331,13 @@ var init_config = __esm({
|
|
|
331
331
|
// streaming lines beneath the live thinking block. Set false to
|
|
332
332
|
// suppress them — the TUI hotkey ^T toggles this at runtime without
|
|
333
333
|
// persisting back to config.
|
|
334
|
-
showThoughts: z.boolean().default(true)
|
|
334
|
+
showThoughts: z.boolean().default(true),
|
|
335
|
+
// Cap on entries kept in the cross-session global prompt-history file
|
|
336
|
+
// (~/.hydra-acp/prompt-history). This is the ^P / ^R recall list
|
|
337
|
+
// shared across all sessions; it's append-only on disk, so long-lived
|
|
338
|
+
// installs can grow past this — it's enforced at load time and per
|
|
339
|
+
// append in memory.
|
|
340
|
+
promptHistoryMaxEntries: z.number().int().positive().default(2e3)
|
|
335
341
|
});
|
|
336
342
|
ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
|
|
337
343
|
ExtensionBody = z.object({
|
|
@@ -385,7 +391,8 @@ var init_config = __esm({
|
|
|
385
391
|
cwdColumnMaxWidth: 24,
|
|
386
392
|
progressIndicator: true,
|
|
387
393
|
defaultEnterAction: "amend",
|
|
388
|
-
showThoughts: true
|
|
394
|
+
showThoughts: true,
|
|
395
|
+
promptHistoryMaxEntries: 2e3
|
|
389
396
|
})
|
|
390
397
|
});
|
|
391
398
|
}
|
|
@@ -1826,6 +1833,11 @@ var init_hydra_commands = __esm({
|
|
|
1826
1833
|
verb: "kill",
|
|
1827
1834
|
name: "hydra kill",
|
|
1828
1835
|
description: "Close this session (kills the agent; record is kept so it can be resumed later)"
|
|
1836
|
+
},
|
|
1837
|
+
{
|
|
1838
|
+
verb: "restart",
|
|
1839
|
+
name: "hydra restart",
|
|
1840
|
+
description: "Restart the agent with a fresh session/new while preserving conversation history (useful when the proxy has changed available models)"
|
|
1829
1841
|
}
|
|
1830
1842
|
];
|
|
1831
1843
|
VERB_INDEX = new Map(HYDRA_COMMANDS.map((c) => [c.verb, c]));
|
|
@@ -2182,7 +2194,9 @@ var init_session = __esm({
|
|
|
2182
2194
|
// stale-prone for snapshot-shaped events).
|
|
2183
2195
|
currentModel;
|
|
2184
2196
|
currentMode;
|
|
2185
|
-
|
|
2197
|
+
// Raw per-agent-life usage. Never read directly outside this class —
|
|
2198
|
+
// always access via the currentUsage getter which adds cumulativeCost.
|
|
2199
|
+
_currentUsage;
|
|
2186
2200
|
updatedAt;
|
|
2187
2201
|
createdAt;
|
|
2188
2202
|
clients = /* @__PURE__ */ new Map();
|
|
@@ -2248,6 +2262,7 @@ var init_session = __esm({
|
|
|
2248
2262
|
// and noisy state churn keep a quiet session alive forever.
|
|
2249
2263
|
lastRecordedAt;
|
|
2250
2264
|
spawnReplacementAgent;
|
|
2265
|
+
listSessions;
|
|
2251
2266
|
logger;
|
|
2252
2267
|
transformChain;
|
|
2253
2268
|
// Outstanding "processing" claims: token → claim waiting for respondsTo discharge.
|
|
@@ -2279,6 +2294,23 @@ var init_session = __esm({
|
|
|
2279
2294
|
modeHandlers = [];
|
|
2280
2295
|
usageHandlers = [];
|
|
2281
2296
|
cumulativeCost = 0;
|
|
2297
|
+
// Total cost across all agent lives. costAmount in the returned snapshot
|
|
2298
|
+
// is cumulativeCost + the current agent's raw amount so every consumer
|
|
2299
|
+
// gets the right figure without knowing about the internal split.
|
|
2300
|
+
// cumulativeCost is stripped from the return value so it never leaks
|
|
2301
|
+
// into persistence paths via session.currentUsage.
|
|
2302
|
+
get currentUsage() {
|
|
2303
|
+
if (!this._currentUsage && !this.cumulativeCost) {
|
|
2304
|
+
return void 0;
|
|
2305
|
+
}
|
|
2306
|
+
const base = this._currentUsage ?? {};
|
|
2307
|
+
const total = this.cumulativeCost + (base.costAmount ?? 0);
|
|
2308
|
+
return {
|
|
2309
|
+
...base,
|
|
2310
|
+
costAmount: total || void 0,
|
|
2311
|
+
cumulativeCost: void 0
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2282
2314
|
// Set by amendPrompt at the start of a cancel-and-resubmit dance.
|
|
2283
2315
|
// broadcastTurnComplete reads it to attach the _meta.amended marker
|
|
2284
2316
|
// to the cancelled turn's turn_complete notification, and to fire the
|
|
@@ -2311,7 +2343,7 @@ var init_session = __esm({
|
|
|
2311
2343
|
this.title = init.title;
|
|
2312
2344
|
this.currentModel = init.currentModel;
|
|
2313
2345
|
this.currentMode = init.currentMode;
|
|
2314
|
-
this.
|
|
2346
|
+
this._currentUsage = init.currentUsage;
|
|
2315
2347
|
this.cumulativeCost = init.currentUsage?.cumulativeCost ?? 0;
|
|
2316
2348
|
if (init.agentCommands && init.agentCommands.length > 0) {
|
|
2317
2349
|
this.agentAdvertisedCommands = [...init.agentCommands];
|
|
@@ -2325,6 +2357,7 @@ var init_session = __esm({
|
|
|
2325
2357
|
this.idleTimeoutMs = init.idleTimeoutMs ?? 0;
|
|
2326
2358
|
this.idleEventTimeoutMs = init.idleEventTimeoutMs ?? 3e4;
|
|
2327
2359
|
this.spawnReplacementAgent = init.spawnReplacementAgent;
|
|
2360
|
+
this.listSessions = init.listSessions;
|
|
2328
2361
|
this.logger = init.logger;
|
|
2329
2362
|
this.transformChain = init.transformChain ?? [];
|
|
2330
2363
|
if (init.firstPromptSeeded) {
|
|
@@ -2343,6 +2376,9 @@ var init_session = __esm({
|
|
|
2343
2376
|
broadcastMergedCommands() {
|
|
2344
2377
|
const merged = [
|
|
2345
2378
|
...hydraCommandsAsAdvertised(),
|
|
2379
|
+
{ name: "model <model-id>", description: "Switch model; omit arg to list available models" },
|
|
2380
|
+
{ name: "sessions", description: "List all sessions" },
|
|
2381
|
+
{ name: "help", description: "Show available commands" },
|
|
2346
2382
|
...this.agentAdvertisedCommands
|
|
2347
2383
|
];
|
|
2348
2384
|
this.recordAndBroadcast("session/update", {
|
|
@@ -2407,7 +2443,8 @@ var init_session = __esm({
|
|
|
2407
2443
|
// then recordAndBroadcast. All state mutation happens after the chain exits.
|
|
2408
2444
|
// See forwardRequest for originatedBy / startIdx semantics.
|
|
2409
2445
|
async runResponseChain(params, originatedBy = /* @__PURE__ */ new Set(), startIdx = 0) {
|
|
2410
|
-
|
|
2446
|
+
const rawParams = params;
|
|
2447
|
+
let envelope = this.injectCumulativeCost(params);
|
|
2411
2448
|
for (let i = startIdx; i < this.transformChain.length; i++) {
|
|
2412
2449
|
const t = this.transformChain[i];
|
|
2413
2450
|
if (originatedBy.has(t.name)) {
|
|
@@ -2495,8 +2532,8 @@ var init_session = __esm({
|
|
|
2495
2532
|
this.recordAndBroadcast("session/update", envelope);
|
|
2496
2533
|
return;
|
|
2497
2534
|
}
|
|
2498
|
-
if (this.maybeApplyAgentUsage(
|
|
2499
|
-
this.recordAndBroadcast("session/update",
|
|
2535
|
+
if (this.maybeApplyAgentUsage(rawParams)) {
|
|
2536
|
+
this.recordAndBroadcast("session/update", envelope);
|
|
2500
2537
|
return;
|
|
2501
2538
|
}
|
|
2502
2539
|
this.maybeApplyAgentSessionInfo(envelope);
|
|
@@ -2689,8 +2726,8 @@ var init_session = __esm({
|
|
|
2689
2726
|
recordedAt
|
|
2690
2727
|
});
|
|
2691
2728
|
}
|
|
2692
|
-
if (this.currentUsage !== void 0
|
|
2693
|
-
const u = this.currentUsage
|
|
2729
|
+
if (this.currentUsage !== void 0) {
|
|
2730
|
+
const u = this.currentUsage;
|
|
2694
2731
|
const update = {
|
|
2695
2732
|
sessionUpdate: "usage_update"
|
|
2696
2733
|
};
|
|
@@ -2702,8 +2739,8 @@ var init_session = __esm({
|
|
|
2702
2739
|
}
|
|
2703
2740
|
if (typeof u.costAmount === "number" || typeof u.costCurrency === "string") {
|
|
2704
2741
|
const cost = {};
|
|
2705
|
-
if (typeof u.costAmount === "number"
|
|
2706
|
-
cost.amount =
|
|
2742
|
+
if (typeof u.costAmount === "number") {
|
|
2743
|
+
cost.amount = u.costAmount;
|
|
2707
2744
|
}
|
|
2708
2745
|
if (typeof u.costCurrency === "string") {
|
|
2709
2746
|
cost.currency = u.costCurrency;
|
|
@@ -3242,6 +3279,23 @@ var init_session = __esm({
|
|
|
3242
3279
|
sessionId: this.upstreamSessionId
|
|
3243
3280
|
});
|
|
3244
3281
|
}
|
|
3282
|
+
// Add a transformer to this session's chain retroactively. No-ops if it's
|
|
3283
|
+
// already present. Fires session.opened on the new transformer so it gets
|
|
3284
|
+
// the same lifecycle signal it would have received at session creation.
|
|
3285
|
+
addTransformer(ref) {
|
|
3286
|
+
const existing = this.transformChain.findIndex((t) => t.name === ref.name);
|
|
3287
|
+
if (existing >= 0) {
|
|
3288
|
+
this.transformChain[existing] = ref;
|
|
3289
|
+
} else {
|
|
3290
|
+
this.transformChain.push(ref);
|
|
3291
|
+
}
|
|
3292
|
+
if (ref.intercepts.has("lifecycle:session.opened")) {
|
|
3293
|
+
void ref.connection.notify("transformer/session_event", {
|
|
3294
|
+
event: "session.opened",
|
|
3295
|
+
sessionId: this.sessionId
|
|
3296
|
+
}).catch(() => void 0);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3245
3299
|
// Walk the request-side chain then forward to the agent.
|
|
3246
3300
|
// originatedBy: transformer names already in the lineage — skipped for loop
|
|
3247
3301
|
// prevention and to implement resume-routing on re-entry from emit_message.
|
|
@@ -3566,6 +3620,9 @@ var init_session = __esm({
|
|
|
3566
3620
|
if (!trimmed || trimmed === this.currentMode) {
|
|
3567
3621
|
return true;
|
|
3568
3622
|
}
|
|
3623
|
+
this.logger?.info(
|
|
3624
|
+
`current_mode_update: sessionId=${this.sessionId} ${JSON.stringify(this.currentMode)} \u2192 ${JSON.stringify(trimmed)}`
|
|
3625
|
+
);
|
|
3569
3626
|
this.currentMode = trimmed;
|
|
3570
3627
|
for (const handler of this.modeHandlers) {
|
|
3571
3628
|
try {
|
|
@@ -3585,7 +3642,7 @@ var init_session = __esm({
|
|
|
3585
3642
|
if (update.sessionUpdate !== "usage_update") {
|
|
3586
3643
|
return false;
|
|
3587
3644
|
}
|
|
3588
|
-
const next = { ...this.
|
|
3645
|
+
const next = { ...this._currentUsage ?? {} };
|
|
3589
3646
|
let changed = false;
|
|
3590
3647
|
if (typeof update.used === "number" && next.used !== update.used) {
|
|
3591
3648
|
next.used = update.used;
|
|
@@ -3609,10 +3666,11 @@ var init_session = __esm({
|
|
|
3609
3666
|
if (!changed) {
|
|
3610
3667
|
return true;
|
|
3611
3668
|
}
|
|
3612
|
-
this.
|
|
3669
|
+
this._currentUsage = next;
|
|
3670
|
+
const total = this.currentUsage ?? next;
|
|
3613
3671
|
for (const handler of this.usageHandlers) {
|
|
3614
3672
|
try {
|
|
3615
|
-
handler(
|
|
3673
|
+
handler(total);
|
|
3616
3674
|
} catch {
|
|
3617
3675
|
}
|
|
3618
3676
|
}
|
|
@@ -3622,16 +3680,16 @@ var init_session = __esm({
|
|
|
3622
3680
|
// next agent life starts accumulating from $0. Fires usageHandlers so
|
|
3623
3681
|
// meta.json is updated before the new agent starts emitting.
|
|
3624
3682
|
accumulateAndResetCost() {
|
|
3625
|
-
const amount = this.
|
|
3683
|
+
const amount = this._currentUsage?.costAmount;
|
|
3626
3684
|
if (!amount)
|
|
3627
3685
|
return;
|
|
3628
3686
|
this.cumulativeCost += amount;
|
|
3629
3687
|
const next = {
|
|
3630
|
-
...this.
|
|
3688
|
+
...this._currentUsage ?? {},
|
|
3631
3689
|
cumulativeCost: this.cumulativeCost,
|
|
3632
3690
|
costAmount: void 0
|
|
3633
3691
|
};
|
|
3634
|
-
this.
|
|
3692
|
+
this._currentUsage = next;
|
|
3635
3693
|
for (const handler of this.usageHandlers) {
|
|
3636
3694
|
try {
|
|
3637
3695
|
handler(next);
|
|
@@ -3666,16 +3724,9 @@ var init_session = __esm({
|
|
|
3666
3724
|
}
|
|
3667
3725
|
};
|
|
3668
3726
|
}
|
|
3669
|
-
//
|
|
3670
|
-
// session/list so list rows show the accumulated figure.
|
|
3727
|
+
// Deprecated alias — currentUsage already returns the total.
|
|
3671
3728
|
get totalUsage() {
|
|
3672
|
-
|
|
3673
|
-
return void 0;
|
|
3674
|
-
const base = this.currentUsage ?? {};
|
|
3675
|
-
return {
|
|
3676
|
-
...base,
|
|
3677
|
-
costAmount: this.cumulativeCost + (this.currentUsage?.costAmount ?? 0)
|
|
3678
|
-
};
|
|
3729
|
+
return this.currentUsage;
|
|
3679
3730
|
}
|
|
3680
3731
|
// Update the cached agent command list, fire persist handlers, and
|
|
3681
3732
|
// broadcast the merged list to attached clients. Idempotent on a
|
|
@@ -3741,6 +3792,26 @@ var init_session = __esm({
|
|
|
3741
3792
|
onModeChange(handler) {
|
|
3742
3793
|
this.modeHandlers.push(handler);
|
|
3743
3794
|
}
|
|
3795
|
+
// Apply a mode change initiated by a client request (session/set_mode)
|
|
3796
|
+
// when the agent doesn't emit a current_mode_update notification on its
|
|
3797
|
+
// own. Fires modeHandlers so the persistence hook and any other listeners
|
|
3798
|
+
// see the change, identical to the agent-notification path.
|
|
3799
|
+
applyModeChange(modeId) {
|
|
3800
|
+
const trimmed = modeId.trim();
|
|
3801
|
+
if (!trimmed || trimmed === this.currentMode) {
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
this.logger?.info(
|
|
3805
|
+
`applyModeChange: sessionId=${this.sessionId} ${JSON.stringify(this.currentMode)} \u2192 ${JSON.stringify(trimmed)}`
|
|
3806
|
+
);
|
|
3807
|
+
this.currentMode = trimmed;
|
|
3808
|
+
for (const handler of this.modeHandlers) {
|
|
3809
|
+
try {
|
|
3810
|
+
handler(trimmed);
|
|
3811
|
+
} catch {
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3744
3815
|
onUsageChange(handler) {
|
|
3745
3816
|
this.usageHandlers.push(handler);
|
|
3746
3817
|
}
|
|
@@ -3823,6 +3894,8 @@ var init_session = __esm({
|
|
|
3823
3894
|
return this.runAgentCommand(arg);
|
|
3824
3895
|
case "kill":
|
|
3825
3896
|
return this.runKillCommand();
|
|
3897
|
+
case "restart":
|
|
3898
|
+
return this.runRestartCommand();
|
|
3826
3899
|
default: {
|
|
3827
3900
|
const err = new Error(
|
|
3828
3901
|
`no dispatcher for /hydra verb ${verb}`
|
|
@@ -3832,6 +3905,92 @@ var init_session = __esm({
|
|
|
3832
3905
|
}
|
|
3833
3906
|
}
|
|
3834
3907
|
}
|
|
3908
|
+
async handleSessionsCommand() {
|
|
3909
|
+
let text;
|
|
3910
|
+
if (!this.listSessions) {
|
|
3911
|
+
text = "_(session listing not available)_";
|
|
3912
|
+
} else {
|
|
3913
|
+
const sessions = await this.listSessions();
|
|
3914
|
+
if (sessions.length === 0) {
|
|
3915
|
+
text = "_(no sessions)_";
|
|
3916
|
+
} else {
|
|
3917
|
+
const lines = sessions.map((s) => {
|
|
3918
|
+
const id = s.sessionId.replace(/^hydra_session_/, "").slice(0, 12);
|
|
3919
|
+
const model = s.currentModel ? ` \xB7 ${s.currentModel}` : "";
|
|
3920
|
+
const marker = s.sessionId === this.sessionId ? " \u25C0" : "";
|
|
3921
|
+
const title = s.title ? ` ${s.title}` : "";
|
|
3922
|
+
return `\`${id}\` ${s.cwd}${model}${marker}${title}`;
|
|
3923
|
+
});
|
|
3924
|
+
text = `Sessions (${sessions.length}):
|
|
3925
|
+
${lines.join("\n")}`;
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
this.recordAndBroadcast("session/update", {
|
|
3929
|
+
sessionId: this.upstreamSessionId,
|
|
3930
|
+
update: {
|
|
3931
|
+
sessionUpdate: "agent_message_chunk",
|
|
3932
|
+
content: { type: "text", text: `
|
|
3933
|
+
${text}
|
|
3934
|
+
` },
|
|
3935
|
+
_meta: { "hydra-acp": { synthetic: true } }
|
|
3936
|
+
}
|
|
3937
|
+
});
|
|
3938
|
+
return { stopReason: "end_turn" };
|
|
3939
|
+
}
|
|
3940
|
+
handleHelpCommand() {
|
|
3941
|
+
const commands = this.mergedAvailableCommands();
|
|
3942
|
+
const lines = commands.map((c) => {
|
|
3943
|
+
const desc = c.description ? ` ${c.description}` : "";
|
|
3944
|
+
return `\`/${c.name}\`${desc}`;
|
|
3945
|
+
});
|
|
3946
|
+
const text = lines.length > 0 ? `Available commands:
|
|
3947
|
+
${lines.join("\n")}` : "_(no commands advertised)_";
|
|
3948
|
+
this.recordAndBroadcast("session/update", {
|
|
3949
|
+
sessionId: this.upstreamSessionId,
|
|
3950
|
+
update: {
|
|
3951
|
+
sessionUpdate: "agent_message_chunk",
|
|
3952
|
+
content: { type: "text", text: `
|
|
3953
|
+
${text}
|
|
3954
|
+
` },
|
|
3955
|
+
_meta: { "hydra-acp": { synthetic: true } }
|
|
3956
|
+
}
|
|
3957
|
+
});
|
|
3958
|
+
return Promise.resolve({ stopReason: "end_turn" });
|
|
3959
|
+
}
|
|
3960
|
+
async handleModelCommand(text) {
|
|
3961
|
+
const arg = text.slice("/model".length).trim();
|
|
3962
|
+
if (arg === "") {
|
|
3963
|
+
const models = this.agentAdvertisedModels;
|
|
3964
|
+
const current = this.currentModel;
|
|
3965
|
+
let body;
|
|
3966
|
+
if (models.length === 0) {
|
|
3967
|
+
body = current ? `Current model: ${current}` : "_(no models advertised yet)_";
|
|
3968
|
+
} else {
|
|
3969
|
+
const lines = models.map((m) => {
|
|
3970
|
+
const marker = m.modelId === current ? " \u25C0" : "";
|
|
3971
|
+
const desc = m.name && m.name !== m.modelId ? ` ${m.name}` : "";
|
|
3972
|
+
return `${m.modelId}${marker}${desc}`;
|
|
3973
|
+
});
|
|
3974
|
+
body = lines.join("\n");
|
|
3975
|
+
}
|
|
3976
|
+
this.recordAndBroadcast("session/update", {
|
|
3977
|
+
sessionId: this.upstreamSessionId,
|
|
3978
|
+
update: {
|
|
3979
|
+
sessionUpdate: "agent_message_chunk",
|
|
3980
|
+
content: { type: "text", text: `
|
|
3981
|
+
${body}
|
|
3982
|
+
` },
|
|
3983
|
+
_meta: { "hydra-acp": { synthetic: true } }
|
|
3984
|
+
}
|
|
3985
|
+
});
|
|
3986
|
+
return { stopReason: "end_turn" };
|
|
3987
|
+
}
|
|
3988
|
+
await this.forwardRequest("session/set_model", {
|
|
3989
|
+
sessionId: this.sessionId,
|
|
3990
|
+
modelId: arg
|
|
3991
|
+
});
|
|
3992
|
+
return { stopReason: "end_turn" };
|
|
3993
|
+
}
|
|
3835
3994
|
// Runs as a normal queued prompt (so it serializes after any in-flight
|
|
3836
3995
|
// turn). With an arg, sets the title directly. Without one, runs a
|
|
3837
3996
|
// suppressed sub-prompt to the agent and uses its reply as the title.
|
|
@@ -3953,6 +4112,57 @@ var init_session = __esm({
|
|
|
3953
4112
|
await this.close({ deleteRecord: false });
|
|
3954
4113
|
return { stopReason: "end_turn" };
|
|
3955
4114
|
}
|
|
4115
|
+
// Restart the underlying agent with a fresh session/new, preserving the
|
|
4116
|
+
// conversation history. Unlike /hydra agent, this allows the same agentId
|
|
4117
|
+
// — useful when the proxy has changed available models (e.g. opus became
|
|
4118
|
+
// available after a quota reset) and the resumed session is locked to a
|
|
4119
|
+
// stale model list.
|
|
4120
|
+
runRestartCommand() {
|
|
4121
|
+
if (!this.spawnReplacementAgent) {
|
|
4122
|
+
throw withCode(
|
|
4123
|
+
new Error("agent restart not configured for this session"),
|
|
4124
|
+
JsonRpcErrorCodes.InternalError
|
|
4125
|
+
);
|
|
4126
|
+
}
|
|
4127
|
+
const spawnAgent = this.spawnReplacementAgent;
|
|
4128
|
+
const agentId = this.agentId;
|
|
4129
|
+
return this.enqueuePrompt(async () => {
|
|
4130
|
+
const transcript = await this.buildSwitchTranscript(agentId);
|
|
4131
|
+
const fresh = await spawnAgent({
|
|
4132
|
+
agentId,
|
|
4133
|
+
cwd: this.cwd,
|
|
4134
|
+
agentArgs: this.agentArgs
|
|
4135
|
+
});
|
|
4136
|
+
this.accumulateAndResetCost();
|
|
4137
|
+
this.wireAgent(fresh.agent);
|
|
4138
|
+
const oldAgent = this.agent;
|
|
4139
|
+
this.agent = fresh.agent;
|
|
4140
|
+
this.upstreamSessionId = fresh.upstreamSessionId;
|
|
4141
|
+
this.agentMeta = fresh.agentMeta;
|
|
4142
|
+
this.agentCapabilities = fresh.agentCapabilities;
|
|
4143
|
+
this.agentAdvertisedCommands = [];
|
|
4144
|
+
this.broadcastMergedCommands();
|
|
4145
|
+
if (this.agentAdvertisedModels.length > 0) {
|
|
4146
|
+
this.setAgentAdvertisedModels([]);
|
|
4147
|
+
}
|
|
4148
|
+
if (this.agentAdvertisedModes.length > 0) {
|
|
4149
|
+
this.setAgentAdvertisedModes([]);
|
|
4150
|
+
}
|
|
4151
|
+
await oldAgent.kill().catch(() => void 0);
|
|
4152
|
+
if (transcript) {
|
|
4153
|
+
await this.runInternalPrompt(transcript).catch(() => void 0);
|
|
4154
|
+
}
|
|
4155
|
+
this.broadcastAgentSwitch(agentId, agentId);
|
|
4156
|
+
const info = { agentId, upstreamSessionId: this.upstreamSessionId };
|
|
4157
|
+
for (const handler of this.agentChangeHandlers) {
|
|
4158
|
+
try {
|
|
4159
|
+
handler(info);
|
|
4160
|
+
} catch {
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
return { stopReason: "end_turn" };
|
|
4164
|
+
});
|
|
4165
|
+
}
|
|
3956
4166
|
// Walk the persisted history and produce a labeled transcript suitable
|
|
3957
4167
|
// for handing to a fresh agent. Includes user prompts, agent replies,
|
|
3958
4168
|
// and tool-call outcomes; skips hydra-synthesized markers (so multi-hop
|
|
@@ -4569,6 +4779,22 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
4569
4779
|
return entry.task();
|
|
4570
4780
|
}
|
|
4571
4781
|
this.broadcastPromptReceived(entry);
|
|
4782
|
+
const promptText = extractPromptText(entry.prompt).trim();
|
|
4783
|
+
if (promptText === "/model" || promptText.startsWith("/model ") || promptText === "/sessions" || promptText === "/help") {
|
|
4784
|
+
let result;
|
|
4785
|
+
if (promptText === "/sessions") {
|
|
4786
|
+
result = await this.handleSessionsCommand();
|
|
4787
|
+
} else if (promptText === "/help") {
|
|
4788
|
+
result = await this.handleHelpCommand();
|
|
4789
|
+
} else {
|
|
4790
|
+
result = await this.handleModelCommand(promptText);
|
|
4791
|
+
}
|
|
4792
|
+
if (!this.closed) {
|
|
4793
|
+
this.broadcastTurnComplete(entry.clientId, result, entry.messageId, entry.wasAmend);
|
|
4794
|
+
}
|
|
4795
|
+
this.clearAmendIfMatches(entry.messageId);
|
|
4796
|
+
return result;
|
|
4797
|
+
}
|
|
4572
4798
|
let response;
|
|
4573
4799
|
try {
|
|
4574
4800
|
response = await this.agent.connection.request(
|
|
@@ -4690,12 +4916,11 @@ function buildCombinedHistory(global, session) {
|
|
|
4690
4916
|
const filteredGlobal = global.filter((e) => !sessionSet.has(e));
|
|
4691
4917
|
return [...filteredGlobal, ...session];
|
|
4692
4918
|
}
|
|
4693
|
-
var HISTORY_CAP
|
|
4919
|
+
var HISTORY_CAP;
|
|
4694
4920
|
var init_history = __esm({
|
|
4695
4921
|
"src/tui/history.ts"() {
|
|
4696
4922
|
"use strict";
|
|
4697
4923
|
HISTORY_CAP = 500;
|
|
4698
|
-
GLOBAL_HISTORY_CAP = 2e3;
|
|
4699
4924
|
}
|
|
4700
4925
|
});
|
|
4701
4926
|
|
|
@@ -5083,7 +5308,15 @@ function mapModel(u) {
|
|
|
5083
5308
|
if (!model) {
|
|
5084
5309
|
return null;
|
|
5085
5310
|
}
|
|
5086
|
-
|
|
5311
|
+
const raw = u.availableModels;
|
|
5312
|
+
const availableModels = Array.isArray(raw) ? raw.map(
|
|
5313
|
+
(m) => typeof m === "object" && m !== null ? m.modelId : typeof m === "string" ? m : void 0
|
|
5314
|
+
).filter((id) => typeof id === "string" && id.length > 0) : void 0;
|
|
5315
|
+
return {
|
|
5316
|
+
kind: "model-changed",
|
|
5317
|
+
model: sanitizeSingleLine(model),
|
|
5318
|
+
...availableModels && availableModels.length > 0 ? { availableModels } : {}
|
|
5319
|
+
};
|
|
5087
5320
|
}
|
|
5088
5321
|
function mapTurnComplete(u) {
|
|
5089
5322
|
const stopReason = readString(u, "stopReason");
|
|
@@ -11681,6 +11914,16 @@ function parseReattachResponse(result) {
|
|
|
11681
11914
|
if (typeof r.clientId === "string" && r.clientId.length > 0) {
|
|
11682
11915
|
out.clientId = r.clientId;
|
|
11683
11916
|
}
|
|
11917
|
+
const meta = r._meta;
|
|
11918
|
+
if (meta && typeof meta === "object") {
|
|
11919
|
+
const hydra = meta["hydra-acp"];
|
|
11920
|
+
if (hydra && typeof hydra === "object") {
|
|
11921
|
+
const ts = hydra.turnStartedAt;
|
|
11922
|
+
if (typeof ts === "number") {
|
|
11923
|
+
out.turnStartedAt = ts;
|
|
11924
|
+
}
|
|
11925
|
+
}
|
|
11926
|
+
}
|
|
11684
11927
|
return out;
|
|
11685
11928
|
}
|
|
11686
11929
|
var init_reconnect_state = __esm({
|
|
@@ -12767,8 +13010,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
12767
13010
|
const globalHistoryFile = paths.globalTuiHistoryFile();
|
|
12768
13011
|
let history = await loadHistory(historyFile).catch(() => []);
|
|
12769
13012
|
let globalHistory = await loadHistory(globalHistoryFile).catch(() => []);
|
|
12770
|
-
if (globalHistory.length >
|
|
12771
|
-
globalHistory = globalHistory.slice(globalHistory.length -
|
|
13013
|
+
if (globalHistory.length > config.tui.promptHistoryMaxEntries) {
|
|
13014
|
+
globalHistory = globalHistory.slice(globalHistory.length - config.tui.promptHistoryMaxEntries);
|
|
12772
13015
|
}
|
|
12773
13016
|
const dispatcher = new InputDispatcher({
|
|
12774
13017
|
history: buildCombinedHistory(globalHistory, history)
|
|
@@ -12782,7 +13025,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
12782
13025
|
const nextSession = appendEntry(history, trimmed);
|
|
12783
13026
|
const sessionChanged = nextSession !== history;
|
|
12784
13027
|
history = nextSession;
|
|
12785
|
-
const nextGlobal = appendEntry(globalHistory, trimmed,
|
|
13028
|
+
const nextGlobal = appendEntry(globalHistory, trimmed, config.tui.promptHistoryMaxEntries);
|
|
12786
13029
|
const globalChanged = nextGlobal !== globalHistory;
|
|
12787
13030
|
globalHistory = nextGlobal;
|
|
12788
13031
|
dispatcher.setHistory(buildCombinedHistory(globalHistory, history));
|
|
@@ -13754,40 +13997,6 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
13754
13997
|
}
|
|
13755
13998
|
]);
|
|
13756
13999
|
return true;
|
|
13757
|
-
case "/model": {
|
|
13758
|
-
const arg = space === -1 ? "" : trimmed.slice(space + 1).trim();
|
|
13759
|
-
if (arg === "") {
|
|
13760
|
-
screen.appendLines([
|
|
13761
|
-
{
|
|
13762
|
-
prefix: " ",
|
|
13763
|
-
body: "Usage: /model <model-id>",
|
|
13764
|
-
bodyStyle: "info"
|
|
13765
|
-
}
|
|
13766
|
-
]);
|
|
13767
|
-
return true;
|
|
13768
|
-
}
|
|
13769
|
-
conn.request("session/set_model", {
|
|
13770
|
-
sessionId: resolvedSessionId,
|
|
13771
|
-
modelId: arg
|
|
13772
|
-
}).then(() => {
|
|
13773
|
-
screen.appendLines([
|
|
13774
|
-
{
|
|
13775
|
-
prefix: " ",
|
|
13776
|
-
body: `model set to ${arg}`,
|
|
13777
|
-
bodyStyle: "system"
|
|
13778
|
-
}
|
|
13779
|
-
]);
|
|
13780
|
-
}).catch((err) => {
|
|
13781
|
-
screen.appendLines([
|
|
13782
|
-
{
|
|
13783
|
-
prefix: " ",
|
|
13784
|
-
body: `set_model failed: ${err.message}`,
|
|
13785
|
-
bodyStyle: "tool-status-fail"
|
|
13786
|
-
}
|
|
13787
|
-
]);
|
|
13788
|
-
});
|
|
13789
|
-
return true;
|
|
13790
|
-
}
|
|
13791
14000
|
default:
|
|
13792
14001
|
return false;
|
|
13793
14002
|
}
|
|
@@ -14279,12 +14488,13 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
14279
14488
|
reconnectReplayBuffer = [];
|
|
14280
14489
|
let appliedPolicy;
|
|
14281
14490
|
let attachErr;
|
|
14491
|
+
let fields;
|
|
14282
14492
|
try {
|
|
14283
14493
|
const resp = await stream.request(attachReq);
|
|
14284
14494
|
if (resp.error) {
|
|
14285
14495
|
throw new Error(resp.error.message);
|
|
14286
14496
|
}
|
|
14287
|
-
|
|
14497
|
+
fields = parseReattachResponse(resp.result);
|
|
14288
14498
|
appliedPolicy = fields.appliedPolicy;
|
|
14289
14499
|
if (fields.clientId !== void 0) {
|
|
14290
14500
|
ownClientId = fields.clientId;
|
|
@@ -14318,6 +14528,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
14318
14528
|
handleSessionUpdate(params);
|
|
14319
14529
|
}
|
|
14320
14530
|
}
|
|
14531
|
+
if (fields && fields.turnStartedAt === void 0 && pendingTurns > 0) {
|
|
14532
|
+
adjustPendingTurns(-pendingTurns);
|
|
14533
|
+
}
|
|
14321
14534
|
screen.setBanner({
|
|
14322
14535
|
status: pendingTurns > 0 ? "busy" : "ready",
|
|
14323
14536
|
elapsedMs: pendingTurns > 0 ? 0 : void 0
|
|
@@ -16234,6 +16447,7 @@ var SessionManager = class {
|
|
|
16234
16447
|
idleEventTimeoutMs: this.idleEventTimeoutMs,
|
|
16235
16448
|
logger: this.logger,
|
|
16236
16449
|
spawnReplacementAgent: (p) => this.bootstrapAgent({ ...p, mcpServers: [] }),
|
|
16450
|
+
listSessions: () => this.list(),
|
|
16237
16451
|
historyStore: this.histories,
|
|
16238
16452
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
16239
16453
|
currentModel: fresh.initialModel,
|
|
@@ -16335,6 +16549,22 @@ var SessionManager = class {
|
|
|
16335
16549
|
} else {
|
|
16336
16550
|
agent.connection.drainBuffered("session/update");
|
|
16337
16551
|
}
|
|
16552
|
+
const agentReportedMode = extractInitialCurrentMode(loadResult ?? {});
|
|
16553
|
+
const advertisedModes = params.agentModes ?? nonEmptyOrUndefined(extractInitialModes(loadResult ?? {}));
|
|
16554
|
+
this.logger?.info(
|
|
16555
|
+
`resurrect: sessionId=${params.hydraSessionId} persistedMode=${JSON.stringify(params.currentMode)} agentReportedMode=${JSON.stringify(agentReportedMode)} advertisedModes=${JSON.stringify(advertisedModes?.map((m) => m.id))}`
|
|
16556
|
+
);
|
|
16557
|
+
const effectiveMode = await restoreCurrentMode({
|
|
16558
|
+
agent,
|
|
16559
|
+
upstreamSessionId: params.upstreamSessionId,
|
|
16560
|
+
persistedMode: params.currentMode,
|
|
16561
|
+
agentReportedMode,
|
|
16562
|
+
advertisedModes,
|
|
16563
|
+
logger: this.logger
|
|
16564
|
+
});
|
|
16565
|
+
this.logger?.info(
|
|
16566
|
+
`resurrect: effectiveMode=${JSON.stringify(effectiveMode)} for sessionId=${params.hydraSessionId}`
|
|
16567
|
+
);
|
|
16338
16568
|
const session = new Session({
|
|
16339
16569
|
sessionId: params.hydraSessionId,
|
|
16340
16570
|
cwd: params.cwd,
|
|
@@ -16348,6 +16578,7 @@ var SessionManager = class {
|
|
|
16348
16578
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
16349
16579
|
logger: this.logger,
|
|
16350
16580
|
spawnReplacementAgent: (p) => this.bootstrapAgent({ ...p, mcpServers: [] }),
|
|
16581
|
+
listSessions: () => this.list(),
|
|
16351
16582
|
historyStore: this.histories,
|
|
16352
16583
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
16353
16584
|
// Prefer what we previously stored from a current_model_update; if
|
|
@@ -16355,11 +16586,15 @@ var SessionManager = class {
|
|
|
16355
16586
|
// this fix), fall back to the model the agent ships in its
|
|
16356
16587
|
// session/load response body.
|
|
16357
16588
|
currentModel: params.currentModel ?? extractInitialModel(loadResult ?? {}),
|
|
16358
|
-
currentMode:
|
|
16589
|
+
currentMode: effectiveMode,
|
|
16359
16590
|
currentUsage: params.currentUsage,
|
|
16360
16591
|
agentCommands: params.agentCommands,
|
|
16361
|
-
agentModes:
|
|
16362
|
-
|
|
16592
|
+
agentModes: advertisedModes,
|
|
16593
|
+
// Always prefer the fresh list from session/load over the persisted
|
|
16594
|
+
// snapshot — the proxy's available models can change between daemon
|
|
16595
|
+
// restarts (quota resets, rollouts), so meta.json is intentionally
|
|
16596
|
+
// treated as a cold fallback here, not the authoritative source.
|
|
16597
|
+
agentModels: nonEmptyOrUndefined(extractInitialModels(loadResult ?? {})) ?? params.agentModels,
|
|
16363
16598
|
// Only gate the first-prompt title heuristic when we actually have
|
|
16364
16599
|
// a title to preserve. A title-less session (lost to a write race
|
|
16365
16600
|
// or never seeded) should re-derive from the next prompt rather
|
|
@@ -16386,6 +16621,15 @@ var SessionManager = class {
|
|
|
16386
16621
|
mcpServers: [],
|
|
16387
16622
|
onInstallProgress: params.onInstallProgress
|
|
16388
16623
|
});
|
|
16624
|
+
const advertisedModes = params.agentModes ?? fresh.initialModes;
|
|
16625
|
+
const effectiveMode = await restoreCurrentMode({
|
|
16626
|
+
agent: fresh.agent,
|
|
16627
|
+
upstreamSessionId: fresh.upstreamSessionId,
|
|
16628
|
+
persistedMode: params.currentMode,
|
|
16629
|
+
agentReportedMode: fresh.initialMode,
|
|
16630
|
+
advertisedModes,
|
|
16631
|
+
logger: this.logger
|
|
16632
|
+
});
|
|
16389
16633
|
const session = new Session({
|
|
16390
16634
|
sessionId: params.hydraSessionId,
|
|
16391
16635
|
cwd,
|
|
@@ -16399,15 +16643,16 @@ var SessionManager = class {
|
|
|
16399
16643
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
16400
16644
|
logger: this.logger,
|
|
16401
16645
|
spawnReplacementAgent: (p) => this.bootstrapAgent({ ...p, mcpServers: [] }),
|
|
16646
|
+
listSessions: () => this.list(),
|
|
16402
16647
|
historyStore: this.histories,
|
|
16403
16648
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
16404
16649
|
// Prefer the stored value (set by a previous current_model_update);
|
|
16405
16650
|
// fall back to whatever the agent ships in its session/new response.
|
|
16406
16651
|
currentModel: params.currentModel ?? fresh.initialModel,
|
|
16407
|
-
currentMode:
|
|
16652
|
+
currentMode: effectiveMode,
|
|
16408
16653
|
currentUsage: params.currentUsage,
|
|
16409
16654
|
agentCommands: params.agentCommands,
|
|
16410
|
-
agentModes:
|
|
16655
|
+
agentModes: advertisedModes,
|
|
16411
16656
|
agentModels: params.agentModels ?? fresh.initialModels,
|
|
16412
16657
|
firstPromptSeeded: !!params.title,
|
|
16413
16658
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0
|
|
@@ -16609,13 +16854,15 @@ var SessionManager = class {
|
|
|
16609
16854
|
modelId: desired
|
|
16610
16855
|
});
|
|
16611
16856
|
initialModel = desired;
|
|
16612
|
-
} catch {
|
|
16857
|
+
} catch (err) {
|
|
16858
|
+
this.logger?.warn(
|
|
16859
|
+
`defaultModels[${params.agentId}]=${JSON.stringify(desired)} rejected by agent (${err.message}); session will use ${JSON.stringify(initialModel)}`
|
|
16860
|
+
);
|
|
16613
16861
|
}
|
|
16614
16862
|
} else {
|
|
16615
16863
|
const known = initialModels.map((m) => m.modelId).join(", ");
|
|
16616
|
-
|
|
16617
|
-
`
|
|
16618
|
-
`
|
|
16864
|
+
this.logger?.warn(
|
|
16865
|
+
`defaultModels[${params.agentId}]=${JSON.stringify(desired)} not in agent's availableModels ([${known}]); skipping session/set_model, session will use ${JSON.stringify(initialModel)}`
|
|
16619
16866
|
);
|
|
16620
16867
|
}
|
|
16621
16868
|
}
|
|
@@ -16792,6 +17039,9 @@ var SessionManager = class {
|
|
|
16792
17039
|
get(sessionId) {
|
|
16793
17040
|
return this.sessions.get(sessionId);
|
|
16794
17041
|
}
|
|
17042
|
+
liveSessions() {
|
|
17043
|
+
return this.sessions.values();
|
|
17044
|
+
}
|
|
16795
17045
|
// Snapshot of which agent versions are currently in use by live
|
|
16796
17046
|
// sessions, keyed by agentId. Read by the registry-fetch prune sweep
|
|
16797
17047
|
// so it can skip install dirs that still back a running process.
|
|
@@ -16853,7 +17103,7 @@ var SessionManager = class {
|
|
|
16853
17103
|
title: session.title,
|
|
16854
17104
|
agentId: session.agentId,
|
|
16855
17105
|
currentModel: session.currentModel,
|
|
16856
|
-
currentUsage: session.
|
|
17106
|
+
currentUsage: session.currentUsage,
|
|
16857
17107
|
parentSessionId: session.parentSessionId,
|
|
16858
17108
|
updatedAt: used,
|
|
16859
17109
|
attachedClients: session.attachedCount,
|
|
@@ -17005,6 +17255,7 @@ var SessionManager = class {
|
|
|
17005
17255
|
currentMode: args.bundle.session.currentMode,
|
|
17006
17256
|
currentUsage: args.bundle.session.currentUsage,
|
|
17007
17257
|
agentCommands: args.bundle.session.agentCommands,
|
|
17258
|
+
agentModes: args.bundle.session.agentModes,
|
|
17008
17259
|
createdAt: args.preservedCreatedAt ?? now,
|
|
17009
17260
|
// Fallback path for historyMtimeIso (used when the history file
|
|
17010
17261
|
// is missing). Keep this consistent with the utimes stamp above.
|
|
@@ -17394,6 +17645,47 @@ function extractInitialCurrentMode(result) {
|
|
|
17394
17645
|
}
|
|
17395
17646
|
return void 0;
|
|
17396
17647
|
}
|
|
17648
|
+
async function restoreCurrentMode(opts) {
|
|
17649
|
+
const {
|
|
17650
|
+
agent,
|
|
17651
|
+
upstreamSessionId,
|
|
17652
|
+
persistedMode,
|
|
17653
|
+
agentReportedMode,
|
|
17654
|
+
advertisedModes,
|
|
17655
|
+
logger
|
|
17656
|
+
} = opts;
|
|
17657
|
+
if (!persistedMode) {
|
|
17658
|
+
return agentReportedMode;
|
|
17659
|
+
}
|
|
17660
|
+
if (persistedMode === agentReportedMode) {
|
|
17661
|
+
return persistedMode;
|
|
17662
|
+
}
|
|
17663
|
+
if (advertisedModes && advertisedModes.length > 0 && !advertisedModes.some((m) => m.id === persistedMode)) {
|
|
17664
|
+
const known = advertisedModes.map((m) => m.id).join(", ");
|
|
17665
|
+
logger?.warn(
|
|
17666
|
+
`resurrect: persisted currentMode=${JSON.stringify(persistedMode)} not in agent's availableModes ([${known}]); skipping session/set_mode, session will use ${JSON.stringify(agentReportedMode)}`
|
|
17667
|
+
);
|
|
17668
|
+
return agentReportedMode;
|
|
17669
|
+
}
|
|
17670
|
+
try {
|
|
17671
|
+
logger?.info(
|
|
17672
|
+
`resurrect: pushing persisted modeId=${JSON.stringify(persistedMode)} to agent (agentReported=${JSON.stringify(agentReportedMode)})`
|
|
17673
|
+
);
|
|
17674
|
+
await agent.connection.request("session/set_mode", {
|
|
17675
|
+
sessionId: upstreamSessionId,
|
|
17676
|
+
modeId: persistedMode
|
|
17677
|
+
});
|
|
17678
|
+
logger?.info(
|
|
17679
|
+
`resurrect: session/set_mode accepted, effectiveMode=${JSON.stringify(persistedMode)}`
|
|
17680
|
+
);
|
|
17681
|
+
return persistedMode;
|
|
17682
|
+
} catch (err) {
|
|
17683
|
+
logger?.warn(
|
|
17684
|
+
`resurrect: session/set_mode rejected by agent for modeId=${JSON.stringify(persistedMode)} (${err.message}); session will use ${JSON.stringify(agentReportedMode)}`
|
|
17685
|
+
);
|
|
17686
|
+
return agentReportedMode;
|
|
17687
|
+
}
|
|
17688
|
+
}
|
|
17397
17689
|
function parseModesList(list) {
|
|
17398
17690
|
if (!Array.isArray(list)) {
|
|
17399
17691
|
return [];
|
|
@@ -19927,6 +20219,14 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
19927
20219
|
connection,
|
|
19928
20220
|
intercepts
|
|
19929
20221
|
);
|
|
20222
|
+
if (deps.manager?.defaultTransformers.includes(processIdentity.name)) {
|
|
20223
|
+
const ref = deps.transformers.resolveChain([processIdentity.name])[0];
|
|
20224
|
+
if (ref) {
|
|
20225
|
+
for (const session of deps.manager.liveSessions()) {
|
|
20226
|
+
session.addTransformer(ref);
|
|
20227
|
+
}
|
|
20228
|
+
}
|
|
20229
|
+
}
|
|
19930
20230
|
}
|
|
19931
20231
|
return { ack: true };
|
|
19932
20232
|
});
|
|
@@ -20142,16 +20442,13 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20142
20442
|
let resurrectParams = fromDisk;
|
|
20143
20443
|
if (hydraHints) {
|
|
20144
20444
|
resurrectParams = {
|
|
20445
|
+
...fromDisk,
|
|
20145
20446
|
hydraSessionId: params.sessionId,
|
|
20146
20447
|
upstreamSessionId: hydraHints.upstreamSessionId,
|
|
20147
20448
|
agentId: hydraHints.agentId,
|
|
20148
20449
|
cwd: hydraHints.cwd,
|
|
20149
|
-
title: hydraHints.title
|
|
20150
|
-
agentArgs: hydraHints.agentArgs
|
|
20151
|
-
currentModel: fromDisk?.currentModel,
|
|
20152
|
-
currentMode: fromDisk?.currentMode,
|
|
20153
|
-
agentCommands: fromDisk?.agentCommands,
|
|
20154
|
-
createdAt: fromDisk?.createdAt
|
|
20450
|
+
...hydraHints.title !== void 0 ? { title: hydraHints.title } : {},
|
|
20451
|
+
...hydraHints.agentArgs !== void 0 ? { agentArgs: hydraHints.agentArgs } : {}
|
|
20155
20452
|
};
|
|
20156
20453
|
}
|
|
20157
20454
|
if (!resurrectParams) {
|
|
@@ -20165,6 +20462,7 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20165
20462
|
...resurrectParams,
|
|
20166
20463
|
onInstallProgress: makeInstallProgressForwarder(connection)
|
|
20167
20464
|
});
|
|
20465
|
+
wireDefaultTransformers(session, deps);
|
|
20168
20466
|
}
|
|
20169
20467
|
const client = bindClientToSession(
|
|
20170
20468
|
connection,
|
|
@@ -20254,6 +20552,7 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20254
20552
|
`session/prompt auto-resurrecting cold sessionId=${params.sessionId}`
|
|
20255
20553
|
);
|
|
20256
20554
|
session = await deps.manager.resurrect(fromDisk);
|
|
20555
|
+
wireDefaultTransformers(session, deps);
|
|
20257
20556
|
const client = bindClientToSession(
|
|
20258
20557
|
connection,
|
|
20259
20558
|
session,
|
|
@@ -20406,6 +20705,7 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20406
20705
|
throw err;
|
|
20407
20706
|
}
|
|
20408
20707
|
session = await deps.manager.resurrect(fromDisk);
|
|
20708
|
+
wireDefaultTransformers(session, deps);
|
|
20409
20709
|
}
|
|
20410
20710
|
const client = bindClientToSession(connection, session, state);
|
|
20411
20711
|
const { entries: replay } = await session.attach(client, "pending_only");
|
|
@@ -20456,6 +20756,32 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20456
20756
|
app.log.info(decision.logMessage);
|
|
20457
20757
|
return decision.session.forwardRequest("session/set_model", rawParams);
|
|
20458
20758
|
});
|
|
20759
|
+
connection.onRequest("session/set_mode", async (rawParams) => {
|
|
20760
|
+
const params = rawParams;
|
|
20761
|
+
const sessionIdField = params?.sessionId;
|
|
20762
|
+
if (typeof sessionIdField === "string") {
|
|
20763
|
+
denyIfReadonly(sessionIdField, "session/set_mode");
|
|
20764
|
+
}
|
|
20765
|
+
if (!params || typeof params.sessionId !== "string") {
|
|
20766
|
+
const err = new Error("session/set_mode requires string sessionId");
|
|
20767
|
+
err.code = JsonRpcErrorCodes.InvalidParams;
|
|
20768
|
+
throw err;
|
|
20769
|
+
}
|
|
20770
|
+
if (typeof params.modeId !== "string") {
|
|
20771
|
+
const err = new Error("session/set_mode requires string modeId");
|
|
20772
|
+
err.code = JsonRpcErrorCodes.InvalidParams;
|
|
20773
|
+
throw err;
|
|
20774
|
+
}
|
|
20775
|
+
const session = deps.manager.get(params.sessionId);
|
|
20776
|
+
if (!session) {
|
|
20777
|
+
const err = new Error(`session ${params.sessionId} not found`);
|
|
20778
|
+
err.code = JsonRpcErrorCodes.SessionNotFound;
|
|
20779
|
+
throw err;
|
|
20780
|
+
}
|
|
20781
|
+
const result = await session.forwardRequest("session/set_mode", rawParams);
|
|
20782
|
+
session.applyModeChange(params.modeId);
|
|
20783
|
+
return result;
|
|
20784
|
+
});
|
|
20459
20785
|
connection.setDefaultHandler(async (rawParams, method) => {
|
|
20460
20786
|
if (!method.startsWith("session/") || rawParams === null || typeof rawParams !== "object") {
|
|
20461
20787
|
const err = new Error(`Method not found: ${method}`);
|
|
@@ -20728,6 +21054,17 @@ function buildInitializeResult() {
|
|
|
20728
21054
|
})
|
|
20729
21055
|
};
|
|
20730
21056
|
}
|
|
21057
|
+
function wireDefaultTransformers(session, deps) {
|
|
21058
|
+
if (!deps.transformers || !deps.manager) {
|
|
21059
|
+
return;
|
|
21060
|
+
}
|
|
21061
|
+
for (const name of deps.manager.defaultTransformers) {
|
|
21062
|
+
const ref = deps.transformers.resolveChain([name])[0];
|
|
21063
|
+
if (ref) {
|
|
21064
|
+
session.addTransformer(ref);
|
|
21065
|
+
}
|
|
21066
|
+
}
|
|
21067
|
+
}
|
|
20731
21068
|
function bindClientToSession(connection, session, state, clientInfo, callerClientId) {
|
|
20732
21069
|
void state;
|
|
20733
21070
|
void session;
|
|
@@ -21026,6 +21363,39 @@ async function printTail(logPath, fileSize, lines) {
|
|
|
21026
21363
|
}
|
|
21027
21364
|
return fileSize;
|
|
21028
21365
|
}
|
|
21366
|
+
function splitNameFromLogTailArgs(args) {
|
|
21367
|
+
const flagsWithValue = /* @__PURE__ */ new Set(["--tail", "-n"]);
|
|
21368
|
+
let name;
|
|
21369
|
+
const rest = [];
|
|
21370
|
+
let i = 0;
|
|
21371
|
+
while (i < args.length) {
|
|
21372
|
+
const tok = args[i];
|
|
21373
|
+
if (tok === void 0) {
|
|
21374
|
+
break;
|
|
21375
|
+
}
|
|
21376
|
+
if (tok.startsWith("-")) {
|
|
21377
|
+
rest.push(tok);
|
|
21378
|
+
if (flagsWithValue.has(tok) && i + 1 < args.length) {
|
|
21379
|
+
const v = args[i + 1];
|
|
21380
|
+
if (v !== void 0) {
|
|
21381
|
+
rest.push(v);
|
|
21382
|
+
}
|
|
21383
|
+
i += 2;
|
|
21384
|
+
continue;
|
|
21385
|
+
}
|
|
21386
|
+
i += 1;
|
|
21387
|
+
continue;
|
|
21388
|
+
}
|
|
21389
|
+
if (name === void 0) {
|
|
21390
|
+
name = tok;
|
|
21391
|
+
i += 1;
|
|
21392
|
+
continue;
|
|
21393
|
+
}
|
|
21394
|
+
rest.push(tok);
|
|
21395
|
+
i += 1;
|
|
21396
|
+
}
|
|
21397
|
+
return { name, rest };
|
|
21398
|
+
}
|
|
21029
21399
|
function parseLogTailFlags(argv) {
|
|
21030
21400
|
let tail = 50;
|
|
21031
21401
|
let follow = false;
|
|
@@ -22025,16 +22395,17 @@ async function postLifecycleAll(verb) {
|
|
|
22025
22395
|
process.exit(1);
|
|
22026
22396
|
}
|
|
22027
22397
|
}
|
|
22028
|
-
async function runExtensionsLogs(
|
|
22398
|
+
async function runExtensionsLogs(argv) {
|
|
22399
|
+
const { name, rest } = splitNameFromLogTailArgs(argv);
|
|
22029
22400
|
if (!name) {
|
|
22030
22401
|
process.stderr.write(
|
|
22031
|
-
"Usage: hydra-acp extensions
|
|
22402
|
+
"Usage: hydra-acp extensions log <name> [--tail N] [--follow]\n"
|
|
22032
22403
|
);
|
|
22033
22404
|
process.exit(2);
|
|
22034
22405
|
return;
|
|
22035
22406
|
}
|
|
22036
22407
|
const logPath = paths.extensionLogFile(name);
|
|
22037
|
-
await runLogTail(logPath,
|
|
22408
|
+
await runLogTail(logPath, rest, "No log file (extension never ran?)");
|
|
22038
22409
|
}
|
|
22039
22410
|
function parseAddFlags(argv) {
|
|
22040
22411
|
let command = [];
|
|
@@ -22480,16 +22851,17 @@ async function postLifecycleAll2(verb) {
|
|
|
22480
22851
|
process.exit(1);
|
|
22481
22852
|
}
|
|
22482
22853
|
}
|
|
22483
|
-
async function runTransformersLogs(
|
|
22854
|
+
async function runTransformersLogs(argv) {
|
|
22855
|
+
const { name, rest } = splitNameFromLogTailArgs(argv);
|
|
22484
22856
|
if (!name) {
|
|
22485
22857
|
process.stderr.write(
|
|
22486
|
-
"Usage: hydra-acp transformers
|
|
22858
|
+
"Usage: hydra-acp transformers log <name> [--tail N] [--follow]\n"
|
|
22487
22859
|
);
|
|
22488
22860
|
process.exit(2);
|
|
22489
22861
|
return;
|
|
22490
22862
|
}
|
|
22491
22863
|
const logPath = paths.transformerLogFile(name);
|
|
22492
|
-
await runLogTail(logPath,
|
|
22864
|
+
await runLogTail(logPath, rest, "No log file (transformer never ran?)");
|
|
22493
22865
|
}
|
|
22494
22866
|
async function readRawConfig2() {
|
|
22495
22867
|
const raw = await fsp12.readFile(paths.config(), "utf8");
|
|
@@ -24123,8 +24495,8 @@ async function main() {
|
|
|
24123
24495
|
await runExtensionsRestart(name2);
|
|
24124
24496
|
return;
|
|
24125
24497
|
}
|
|
24126
|
-
if (sub === "logs") {
|
|
24127
|
-
await runExtensionsLogs(
|
|
24498
|
+
if (sub === "log" || sub === "logs") {
|
|
24499
|
+
await runExtensionsLogs(tail.slice(1));
|
|
24128
24500
|
return;
|
|
24129
24501
|
}
|
|
24130
24502
|
process.stderr.write(`Unknown extension subcommand: ${sub}
|
|
@@ -24163,8 +24535,8 @@ async function main() {
|
|
|
24163
24535
|
await runTransformersRestart(name2);
|
|
24164
24536
|
return;
|
|
24165
24537
|
}
|
|
24166
|
-
if (sub === "logs") {
|
|
24167
|
-
await runTransformersLogs(
|
|
24538
|
+
if (sub === "log" || sub === "logs") {
|
|
24539
|
+
await runTransformersLogs(tail.slice(1));
|
|
24168
24540
|
return;
|
|
24169
24541
|
}
|
|
24170
24542
|
process.stderr.write(`Unknown transformer subcommand: ${sub}
|
|
@@ -24384,12 +24756,12 @@ function printHelp() {
|
|
|
24384
24756
|
" hydra-acp extension add <name> [opts] Add an extension to config",
|
|
24385
24757
|
" hydra-acp extension remove <name> Remove an extension from config",
|
|
24386
24758
|
" hydra-acp extension start|stop|restart <n>|all Lifecycle on one or all",
|
|
24387
|
-
" hydra-acp extension
|
|
24759
|
+
" hydra-acp extension log <name> [-f] [-n N] Tail or follow an extension's log",
|
|
24388
24760
|
" hydra-acp transformer list List configured transformers and live state",
|
|
24389
24761
|
" hydra-acp transformer add <name> [opts] Add a transformer to config (--command, --args, --env, --disabled)",
|
|
24390
24762
|
" hydra-acp transformer remove <name> Remove a transformer from config",
|
|
24391
24763
|
" hydra-acp transformer start|stop|restart <n>|all Lifecycle on one or all",
|
|
24392
|
-
" hydra-acp transformer
|
|
24764
|
+
" hydra-acp transformer log <name> [-f] [-n N] Tail or follow a transformer's log",
|
|
24393
24765
|
" hydra-acp agent [list] List agents in the cached registry",
|
|
24394
24766
|
" hydra-acp agent refresh Force a registry re-fetch",
|
|
24395
24767
|
" hydra-acp agent install <id> Pre-install <id> from the registry (else lazy on first session)",
|