@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/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(1e3),
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
- currentUsage;
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.currentUsage = init.currentUsage;
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
- let envelope = params;
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(envelope)) {
2499
- this.recordAndBroadcast("session/update", this.injectCumulativeCost(envelope));
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 || this.cumulativeCost > 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" || this.cumulativeCost) {
2706
- cost.amount = this.cumulativeCost + (u.costAmount ?? 0);
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.currentUsage ?? {} };
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.currentUsage = next;
3669
+ this._currentUsage = next;
3670
+ const total = this.currentUsage ?? next;
3613
3671
  for (const handler of this.usageHandlers) {
3614
3672
  try {
3615
- handler(next);
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.currentUsage?.costAmount;
3683
+ const amount = this._currentUsage?.costAmount;
3626
3684
  if (!amount)
3627
3685
  return;
3628
3686
  this.cumulativeCost += amount;
3629
3687
  const next = {
3630
- ...this.currentUsage ?? {},
3688
+ ...this._currentUsage ?? {},
3631
3689
  cumulativeCost: this.cumulativeCost,
3632
3690
  costAmount: void 0
3633
3691
  };
3634
- this.currentUsage = next;
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
- // Total cost across all agent lives for this hydra session. Used by
3670
- // session/list so list rows show the accumulated figure.
3727
+ // Deprecated alias currentUsage already returns the total.
3671
3728
  get totalUsage() {
3672
- if (!this.currentUsage && !this.cumulativeCost)
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, GLOBAL_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
- return { kind: "model-changed", model: sanitizeSingleLine(model) };
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 > GLOBAL_HISTORY_CAP) {
12771
- globalHistory = globalHistory.slice(globalHistory.length - GLOBAL_HISTORY_CAP);
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, GLOBAL_HISTORY_CAP);
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
- const fields = parseReattachResponse(resp.result);
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: params.currentMode ?? extractInitialCurrentMode(loadResult ?? {}),
16589
+ currentMode: effectiveMode,
16359
16590
  currentUsage: params.currentUsage,
16360
16591
  agentCommands: params.agentCommands,
16361
- agentModes: params.agentModes ?? nonEmptyOrUndefined(extractInitialModes(loadResult ?? {})),
16362
- agentModels: params.agentModels ?? nonEmptyOrUndefined(extractInitialModels(loadResult ?? {})),
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: params.currentMode ?? fresh.initialMode,
16652
+ currentMode: effectiveMode,
16408
16653
  currentUsage: params.currentUsage,
16409
16654
  agentCommands: params.agentCommands,
16410
- agentModes: params.agentModes ?? fresh.initialModes,
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
- process.stderr.write(
16617
- `hydra-acp: defaultModels[${params.agentId}]=${JSON.stringify(desired)} is not in the agent's availableModels (${known}); skipping session/set_model
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.totalUsage,
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 ?? fromDisk?.title,
20150
- agentArgs: hydraHints.agentArgs ?? fromDisk?.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(name, argv) {
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 logs <name> [--tail N] [--follow]\n"
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, argv, "No log file (extension never ran?)");
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(name, argv) {
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 logs <name> [--tail N] [--follow]\n"
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, argv, "No log file (transformer never ran?)");
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(name2, rest);
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(name2, rest);
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 logs <name> [-f] [-n N] Tail or follow an extension's log",
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 logs <name> [-f] [-n N] Tail or follow a transformer's log",
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)",