zidane 6.6.1 → 6.6.3

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.
Files changed (75) hide show
  1. package/dist/{acp-EFzMJPxa.js → acp-BQqhozmt.js} +2 -2
  2. package/dist/{acp-EFzMJPxa.js.map → acp-BQqhozmt.js.map} +1 -1
  3. package/dist/acp-cli.js +3 -3
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-ut6nQRnI.js → agent-DTxH7RHW.js} +40 -7
  7. package/dist/agent-DTxH7RHW.js.map +1 -0
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +1 -1
  10. package/dist/{auth-BaLBiz0r.js → auth-7rv6ZfWk.js} +2 -2
  11. package/dist/{auth-BaLBiz0r.js.map → auth-7rv6ZfWk.js.map} +1 -1
  12. package/dist/chat/pure.d.ts +3 -3
  13. package/dist/chat/pure.js +1 -1
  14. package/dist/chat.d.ts +88 -5
  15. package/dist/chat.d.ts.map +1 -1
  16. package/dist/chat.js +5 -5
  17. package/dist/contexts/daytona.d.ts +1 -1
  18. package/dist/contexts/e2b.d.ts +1 -1
  19. package/dist/eval.d.ts +1 -1
  20. package/dist/extensions.d.ts +1 -1
  21. package/dist/extensions.js +1 -1
  22. package/dist/headless.d.ts +1 -1
  23. package/dist/headless.js +1 -1
  24. package/dist/{index-BGiCne_W.d.ts → index-D0O-QO7Z.d.ts} +60 -1
  25. package/dist/index-D0O-QO7Z.d.ts.map +1 -0
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/mcp.d.ts +1 -1
  29. package/dist/output/stream-json.d.ts +1 -1
  30. package/dist/output/terminal.d.ts +1 -1
  31. package/dist/plugins.d.ts +1 -1
  32. package/dist/presets.d.ts +1 -1
  33. package/dist/presets.js +1 -1
  34. package/dist/providers/anthropic.d.ts +1 -1
  35. package/dist/providers/arcee.d.ts +1 -1
  36. package/dist/providers/baseten.d.ts +1 -1
  37. package/dist/providers/cerebras.d.ts +1 -1
  38. package/dist/providers/local.d.ts +1 -1
  39. package/dist/providers/openai-compat.d.ts +1 -1
  40. package/dist/providers/openai.d.ts +1 -1
  41. package/dist/providers/openrouter.d.ts +1 -1
  42. package/dist/providers/xai.d.ts +1 -1
  43. package/dist/providers.d.ts +1 -1
  44. package/dist/restate.d.ts +1 -1
  45. package/dist/restate.js +1 -1
  46. package/dist/session/sqlite.d.ts +1 -1
  47. package/dist/session.d.ts +1 -1
  48. package/dist/skills.d.ts +1 -1
  49. package/dist/{tool-formatters-5zq0YCK0.d.ts → tool-formatters-NrzD6Fkk.d.ts} +20 -3
  50. package/dist/{tool-formatters-5zq0YCK0.d.ts.map → tool-formatters-NrzD6Fkk.d.ts.map} +1 -1
  51. package/dist/tools/fetch-url.d.ts +1 -1
  52. package/dist/tools/web-search.d.ts +1 -1
  53. package/dist/tools.d.ts +1 -1
  54. package/dist/tools.js +1 -1
  55. package/dist/{transcript-anchors-BfV8T2us.js → transcript-anchors-BjH-68k2.js} +654 -35
  56. package/dist/transcript-anchors-BjH-68k2.js.map +1 -0
  57. package/dist/{transcript-anchors-BaAzmW1d.d.ts → transcript-anchors-UhC58Tmj.d.ts} +3 -3
  58. package/dist/transcript-anchors-UhC58Tmj.d.ts.map +1 -0
  59. package/dist/tui.d.ts +33 -5
  60. package/dist/tui.d.ts.map +1 -1
  61. package/dist/tui.js +859 -191
  62. package/dist/tui.js.map +1 -1
  63. package/dist/{turn-operations-DXiTV38z.d.ts → turn-operations-Cuc9pwiB.d.ts} +3 -3
  64. package/dist/{turn-operations-DXiTV38z.d.ts.map → turn-operations-Cuc9pwiB.d.ts.map} +1 -1
  65. package/dist/{turn-operations-7_Ff7INF.js → turn-operations-mMxDRgLY.js} +65 -3
  66. package/dist/{turn-operations-7_Ff7INF.js.map → turn-operations-mMxDRgLY.js.map} +1 -1
  67. package/dist/types.d.ts +1 -1
  68. package/docs/ARCHITECTURE.md +1 -1
  69. package/docs/CHAT.md +30 -5
  70. package/docs/TUI.md +22 -2
  71. package/package.json +1 -1
  72. package/dist/agent-ut6nQRnI.js.map +0 -1
  73. package/dist/index-BGiCne_W.d.ts.map +0 -1
  74. package/dist/transcript-anchors-BaAzmW1d.d.ts.map +0 -1
  75. package/dist/transcript-anchors-BfV8T2us.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { n as __require } from "./rolldown-runtime-BhDjJH2R.js";
2
2
  import { l as toolResultToText } from "./types-BIarq1qE.js";
3
- import { Ct as waitTask, Ft as readFile$1, Gt as edit, Lt as multiEdit, Nt as shell, Tt as createSpawnTool, Ut as grep, Vt as listFiles, Wt as glob, Y as EMPTY_EXTENSION_REGISTRY, _t as composePresets, an as readKeybindingsRaw, b as migrateSessionArtifactDirs, jt as shellKill, nn as mergeKeybindings, on as resolveExtensionKeybindings, st as buildExtensionPreset, vt as definePreset, xt as writeFile$1 } from "./agent-ut6nQRnI.js";
3
+ import { Ct as waitTask, Ft as readFile$1, Gt as edit, Lt as multiEdit, Nt as shell, Tt as createSpawnTool, Ut as grep, Vt as listFiles, Wt as glob, Y as EMPTY_EXTENSION_REGISTRY, _t as composePresets, an as readKeybindingsRaw, b as migrateSessionArtifactDirs, jt as shellKill, nn as mergeKeybindings, on as resolveExtensionKeybindings, st as buildExtensionPreset, vt as definePreset, xt as writeFile$1 } from "./agent-DTxH7RHW.js";
4
4
  import { a as formatDuration, c as previewLine, i as fmtTokens, l as shortId } from "./format-BF745pLL.js";
5
5
  import { u as errorMessage } from "./errors-DKiMrOKq.js";
6
6
  import { R as projectUserPaths, x as discoverSkills, z as findGitRoot$1 } from "./skills-BMGH_KK1.js";
@@ -12,8 +12,8 @@ import { r as formatTokenUsage } from "./stats-DAKBEKjc.js";
12
12
  import { i as findActiveTriggers, n as collectReferences, o as mergeSuggestions, t as applyInsert } from "./completion-core-BRo86R_L.js";
13
13
  import { r as fetchUrl } from "./fetch-url-ma-5_p0p.js";
14
14
  import { webSearch } from "./tools/web-search.js";
15
- import { T as restoreModelOptions, b as modelSupportsReasoning, d as BUILTIN_PROVIDERS, n as applyApiKeyEnv, r as credentialsPath, t as detectAuth, x as modelsForDescriptor } from "./auth-BaLBiz0r.js";
16
- import { C as chunkMarkdownForRender, it as extractEditPayload, m as ownerOf, pt as parseEditOutcomesFromResult, u as capTranscriptEvents, yt as chatReferenceTokensFromText } from "./turn-operations-7_Ff7INF.js";
15
+ import { T as restoreModelOptions, b as modelSupportsReasoning, d as BUILTIN_PROVIDERS, i as readCredentials, m as credKeyOf, n as applyApiKeyEnv, r as credentialsPath, t as detectAuth, x as modelsForDescriptor } from "./auth-7rv6ZfWk.js";
16
+ import { at as extractEditPayload, bt as chatReferenceTokensFromText, m as ownerOf, mt as parseEditOutcomesFromResult, u as capTranscriptEvents, w as chunkMarkdownForRender } from "./turn-operations-mMxDRgLY.js";
17
17
  import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
18
18
  import { Buffer } from "node:buffer";
19
19
  import { chmodSync, closeSync, cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -189,6 +189,64 @@ function base64DecodedSize(data) {
189
189
  const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
190
190
  return Math.floor(data.length * 3 / 4) - padding;
191
191
  }
192
+ /** Severity values a persisted notice may carry. Anything else is ignored. */
193
+ const NOTICE_SEVERITIES = /* @__PURE__ */ new Set([
194
+ "info",
195
+ "warn",
196
+ "error"
197
+ ]);
198
+ /**
199
+ * Read a persisted `metadata.command.notice` back into the event shape, or
200
+ * `null` when the block is an ordinary command detail. Defensive because the
201
+ * value round-tripped through JSON written by a possibly older build.
202
+ */
203
+ function readNoticeMeta(value) {
204
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
205
+ const { severity, label, childId, depth } = value;
206
+ if (typeof severity !== "string" || !NOTICE_SEVERITIES.has(severity)) return null;
207
+ if (typeof label !== "string" || label === "") return null;
208
+ return {
209
+ notice: {
210
+ severity,
211
+ label
212
+ },
213
+ ...typeof childId === "string" && childId !== "" ? { childId } : {},
214
+ ...typeof depth === "number" && Number.isFinite(depth) && depth >= 0 ? { depth: Math.floor(depth) } : {}
215
+ };
216
+ }
217
+ /** The notice a command block carries, or `null` for an ordinary detail block. */
218
+ function noticeFromBlock(block) {
219
+ if (block.type !== "tool_result") return null;
220
+ const meta = block.metadata?.command;
221
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return null;
222
+ return readNoticeMeta(meta.notice);
223
+ }
224
+ /**
225
+ * Persisted form of a harness notice: a display-only `role: 'command'` turn,
226
+ * which is the existing carrier for content that must survive a reload without
227
+ * ever reaching the model.
228
+ *
229
+ * Pairs with the `readNoticeMeta` branch in {@link eventsFromTurns}. Callers
230
+ * pre-mint `turnId` and emit the live event with the same id so the live row
231
+ * and the reloaded row are the same row.
232
+ *
233
+ * `origin` carries the live event's `childId` / `depth`. A subagent notice is
234
+ * persisted by the PARENT's depth-0 run, so without it the run-derived tag in
235
+ * `eventsFromTurns` would reload the row un-indented and unattributed.
236
+ */
237
+ function noticeTurnContent(notice, text, origin = {}) {
238
+ return [{
239
+ type: "tool_result",
240
+ callId: `notice_${crypto.randomUUID()}`,
241
+ output: text,
242
+ metadata: { command: { notice: {
243
+ severity: notice.severity,
244
+ label: notice.label,
245
+ ...origin.childId ? { childId: origin.childId } : {},
246
+ ...origin.depth !== void 0 ? { depth: origin.depth } : {}
247
+ } } }
248
+ }];
249
+ }
192
250
  function eventsFromTurns(turns, runs = []) {
193
251
  const runById = /* @__PURE__ */ new Map();
194
252
  for (const run of runs) if (!runById.has(run.id)) runById.set(run.id, run);
@@ -426,7 +484,8 @@ function eventsFromTurns(turns, runs = []) {
426
484
  continue;
427
485
  }
428
486
  if (turn.role === "command") {
429
- if (lastDepthAtEmission === 0) events.push({
487
+ const noticeOnly = turn.content.length > 0 && turn.content.every((block) => noticeFromBlock(block) !== null);
488
+ if (lastDepthAtEmission === 0 && !noticeOnly) events.push({
430
489
  kind: "separator",
431
490
  text: ""
432
491
  });
@@ -439,6 +498,18 @@ function eventsFromTurns(turns, runs = []) {
439
498
  else if (block.type === "tool_result") {
440
499
  const meta = block.metadata?.command;
441
500
  const commandMeta = meta && typeof meta === "object" && !Array.isArray(meta) ? meta : void 0;
501
+ const persistedNotice = readNoticeMeta(commandMeta?.notice);
502
+ if (persistedNotice) {
503
+ events.push({
504
+ kind: persistedNotice.notice.severity === "error" ? "error" : "info",
505
+ text: toolResultText(block.output),
506
+ notice: persistedNotice.notice,
507
+ ...tag,
508
+ ...persistedNotice.childId ? { childId: persistedNotice.childId } : {},
509
+ ...persistedNotice.depth !== void 0 ? { depth: persistedNotice.depth } : {}
510
+ });
511
+ continue;
512
+ }
442
513
  const customMeta = commandMeta?.custom;
443
514
  if (customMeta && typeof customMeta === "object" && !Array.isArray(customMeta) && typeof customMeta.renderer === "string") {
444
515
  const bound = customMeta;
@@ -733,6 +804,7 @@ function marginTopFor(event, previous) {
733
804
  if (TOOL_KINDS.has(event.kind) && previous && TOOL_KINDS.has(previous.kind)) return 0;
734
805
  if (event.kind === "spawn-start" && previous && (TOOL_KINDS.has(previous.kind) || previous.kind === "spawn-start" || previous.kind === "spawn-end")) return 0;
735
806
  if (event.kind === "task-notification" && previous?.kind === "task-notification") return 0;
807
+ if (event.notice && previous?.notice) return 0;
736
808
  if (event.kind === "thinking" && previous?.kind === "thinking" && previous.childId === event.childId) return 0;
737
809
  return MARGIN_TOP[event.kind] ?? 0;
738
810
  }
@@ -2074,7 +2146,10 @@ const SHARED_BEHAVIOR = {
2074
2146
  grep: discoveryDedupHash,
2075
2147
  list_files: discoveryDedupHash
2076
2148
  },
2077
- repeatGuard: {},
2149
+ repeatGuard: {
2150
+ mode: "both",
2151
+ windowSize: 40
2152
+ },
2078
2153
  thinkingDecay: {
2079
2154
  afterTurn: 10,
2080
2155
  floor: 1024
@@ -2524,13 +2599,13 @@ function chatAutoCompactBehavior(opts) {
2524
2599
  }
2525
2600
  //#endregion
2526
2601
  //#region src/chat/auto-update.ts
2527
- const OPT_OUT_VARS = [
2602
+ const OPT_OUT_VARS$1 = [
2528
2603
  "ZIDANE_NO_UPDATE",
2529
2604
  "NO_UPDATE_NOTIFIER",
2530
2605
  "CI"
2531
2606
  ];
2532
2607
  function isOptedOut(env = process.env) {
2533
- for (const key of OPT_OUT_VARS) {
2608
+ for (const key of OPT_OUT_VARS$1) {
2534
2609
  const v = env[key];
2535
2610
  if (v !== void 0 && v !== "" && v !== "0" && v.toLowerCase() !== "false") return true;
2536
2611
  }
@@ -6572,8 +6647,10 @@ const DEFAULT_SETTINGS = {
6572
6647
  smoothStreaming: true,
6573
6648
  fileLogging: false,
6574
6649
  showTodoIndicator: true,
6650
+ showUsageIndicator: true,
6575
6651
  showThrobber: false,
6576
6652
  checkForUpdates: true,
6653
+ subscriptionUsage: true,
6577
6654
  uiMode: "full",
6578
6655
  userInstructionsScope: "both",
6579
6656
  ctrlCBehavior: "quit",
@@ -6651,7 +6728,7 @@ function useSettings() {
6651
6728
  const SETTINGS_CATEGORIES = [{
6652
6729
  id: "agent",
6653
6730
  label: "Agent",
6654
- description: "agent behaviour, capabilities, persistence, auth"
6731
+ description: "behavior, capabilities, persistence, auth"
6655
6732
  }, {
6656
6733
  id: "ui",
6657
6734
  label: "UI",
@@ -6680,37 +6757,37 @@ const SETTINGS_TOGGLES = [
6680
6757
  category: "agent",
6681
6758
  key: "loadProjectExtensions",
6682
6759
  label: "Load project extensions",
6683
- description: "run extensions under <project>/.zidane/extensions — off by default (they execute arbitrary local code)"
6760
+ description: "run extensions from <project>/.zidane/extensions (off by default, they execute arbitrary local code)"
6684
6761
  },
6685
6762
  {
6686
6763
  category: "agent",
6687
6764
  key: "importForeignConfigs",
6688
6765
  label: "Import configurations from other agents",
6689
- description: "discover MCP servers + skills from Claude Code, Codex, and Pi configs (project + user) — imported stdio servers execute commands from files zidane does not own"
6766
+ description: "discover MCP servers and skills from Claude Code, Codex and Pi configs (imported stdio servers run commands from files zidane does not own)"
6690
6767
  },
6691
6768
  {
6692
6769
  category: "agent",
6693
6770
  key: "importRulerConfigs",
6694
6771
  label: "Support Ruler configuration (.ruler)",
6695
- description: "discover MCP servers, skills + instructions from .ruler/ (project) and ~/.config/ruler (user) — read-only, zidane's own configs win collisions; stdio servers additionally need \"Import configurations from other agents\" (they execute local commands)"
6772
+ description: "discover MCP servers, skills and instructions from .ruler/ and ~/.config/ruler (read-only, zidane's own configs win collisions; stdio servers also need \"Import configurations from other agents\")"
6696
6773
  },
6697
6774
  {
6698
6775
  category: "agent",
6699
6776
  key: "attachmentsEnabled",
6700
6777
  label: "Paste attachments",
6701
- description: "fold dragged files + multiline pastes into prompt attachments (off = everything pastes inline)"
6778
+ description: "turn dragged files and multiline pastes into attachments (off = everything pastes inline)"
6702
6779
  },
6703
6780
  {
6704
6781
  category: "agent",
6705
6782
  key: "externalEditor",
6706
6783
  label: "Edit prompt in $EDITOR",
6707
- description: "open the current prompt in $EDITOR / $VISUAL on the openExternalEditor key (ctrl+g), reloading it when you save & quit (git-style)"
6784
+ description: "edit the current prompt in $EDITOR / $VISUAL with ctrl+g, reloaded when you save and quit"
6708
6785
  },
6709
6786
  {
6710
6787
  category: "agent",
6711
6788
  key: "inlineAutocomplete",
6712
6789
  label: "Inline autocomplete",
6713
- description: "ghost-text suggestions in the prompt (file paths + extension providers) — Tab accepts"
6790
+ description: "ghost-text suggestions in the prompt, tab accepts (file paths and extension providers)"
6714
6791
  },
6715
6792
  {
6716
6793
  category: "agent",
@@ -6722,19 +6799,25 @@ const SETTINGS_TOGGLES = [
6722
6799
  category: "agent",
6723
6800
  key: "subagentModels",
6724
6801
  label: "Allow subagents to use different models",
6725
- description: "spawn gains a `model` field + `model_search` so the agent can delegate to cheaper models (off = children inherit your model)"
6802
+ description: "let the agent delegate to cheaper models with `model_search` (off = children inherit your model)"
6726
6803
  },
6727
6804
  {
6728
6805
  category: "agent",
6729
6806
  key: "resumeOnMaxTokens",
6730
6807
  label: "Auto-resume on output limit",
6731
- description: "when a turn hits the model’s output-token ceiling mid-answer, continue it instead of stopping (off = stop at the limit)"
6808
+ description: "when a turn hits the model's output-token ceiling mid-answer, continue instead of stopping (off = stop at the limit)"
6732
6809
  },
6733
6810
  {
6734
6811
  category: "agent",
6735
6812
  key: "checkForUpdates",
6736
6813
  label: "Check for updates",
6737
- description: "quietly check npm once a day; footer chip surfaces newer releases. `zidane upgrade` always works regardless."
6814
+ description: "check npm once a day and flag new releases in the footer (zidane upgrade works either way)"
6815
+ },
6816
+ {
6817
+ category: "agent",
6818
+ key: "subscriptionUsage",
6819
+ label: "Read subscription usage",
6820
+ description: "ask signed-in providers for rate-limit windows and credits (off = no automatic requests, ctrl+r in the panel still fetches once)"
6738
6821
  },
6739
6822
  {
6740
6823
  category: "ui",
@@ -6764,13 +6847,13 @@ const SETTINGS_TOGGLES = [
6764
6847
  category: "ui",
6765
6848
  key: "groupToolCalls",
6766
6849
  label: "Group tool calls",
6767
- description: "collapse each exchange's tool calls into one row — click to expand the individual calls"
6850
+ description: "collapse each exchange's tool calls into one row, click to expand"
6768
6851
  },
6769
6852
  {
6770
6853
  category: "ui",
6771
6854
  key: "showFullShellOutput",
6772
6855
  label: "Full shell output",
6773
- description: "show every line of ! shell output instead of the last 10 (display only — the model still gets the trimmed result)"
6856
+ description: "show every line of ! shell output instead of the last 10 (display only, the model still gets the trimmed result)"
6774
6857
  },
6775
6858
  {
6776
6859
  category: "ui",
@@ -6788,7 +6871,7 @@ const SETTINGS_TOGGLES = [
6788
6871
  category: "ui",
6789
6872
  key: "smoothStreaming",
6790
6873
  label: "Smooth streaming",
6791
- description: "drip-feed streamed text character-by-character at a steady cadence (typewriter effect)"
6874
+ description: "reveal streamed text character by character (typewriter effect)"
6792
6875
  },
6793
6876
  {
6794
6877
  category: "ui",
@@ -6796,11 +6879,17 @@ const SETTINGS_TOGGLES = [
6796
6879
  label: "Todo indicator",
6797
6880
  description: "show the `n/total todos` chip in the footer (ctrl+t opens the list regardless)"
6798
6881
  },
6882
+ {
6883
+ category: "ui",
6884
+ key: "showUsageIndicator",
6885
+ label: "Usage indicator",
6886
+ description: "show the subscription gauge (▰ 5h 43%) in the footer (ctrl+p opens the panel regardless)"
6887
+ },
6799
6888
  {
6800
6889
  category: "ui",
6801
6890
  key: "showThrobber",
6802
6891
  label: "Streaming throbber",
6803
- description: "animated gradient glyphs at the transcript tail while the assistant is working"
6892
+ description: "animated glyphs under the last message while the agent works"
6804
6893
  },
6805
6894
  {
6806
6895
  category: "ui",
@@ -6812,7 +6901,7 @@ const SETTINGS_TOGGLES = [
6812
6901
  category: "ui",
6813
6902
  key: "mouseInteractions",
6814
6903
  label: "Clickable UI",
6815
- description: "mouse clicks, hovers & wheel scrolling on hints and rows (off = terminal-native text selection and scrollback)"
6904
+ description: "mouse clicks, hovers and wheel scrolling on hints and rows (off = terminal-native text selection and scrollback)"
6816
6905
  }
6817
6906
  ];
6818
6907
  const SETTINGS_CHOICES = [
@@ -6820,7 +6909,7 @@ const SETTINGS_CHOICES = [
6820
6909
  category: "agent",
6821
6910
  key: "interactionMode",
6822
6911
  label: "Interaction mode",
6823
- description: "ask normally, skip UI with repository-aware automatic responses, or disable interaction tools",
6912
+ description: "ask normally, answer automatically from the repository, or turn the interaction tools off",
6824
6913
  options: [
6825
6914
  {
6826
6915
  value: "ask",
@@ -6916,7 +7005,7 @@ const SETTINGS_CHOICES = [
6916
7005
  category: "agent",
6917
7006
  key: "retryMaxAttempts",
6918
7007
  label: "Provider retry attempts",
6919
- description: "max attempts per provider call (retryable errors + silent stream stalls) — applies to the session and its subagents",
7008
+ description: "max attempts per provider call, for retryable errors and silent stream stalls (applies to the session and its subagents)",
6920
7009
  options: [
6921
7010
  {
6922
7011
  value: 0,
@@ -7060,7 +7149,7 @@ const SETTINGS_CHOICES = [
7060
7149
  category: "ui",
7061
7150
  key: "toolCallDisplay",
7062
7151
  label: "Tool calls display",
7063
- description: "how `↳ <tool>` lines render — hidden, clean per-tool summary, or full JSON",
7152
+ description: "how `↳ <tool>` lines render: hidden, per-tool summary, or full JSON",
7064
7153
  options: [
7065
7154
  {
7066
7155
  value: "formatted",
@@ -7080,7 +7169,7 @@ const SETTINGS_CHOICES = [
7080
7169
  category: "ui",
7081
7170
  key: "editDiffDisplay",
7082
7171
  label: "Edit diff density",
7083
- description: "full unified diff with line numbers · or compact per-hunk summary list",
7172
+ description: "full unified diff with line numbers, or a compact per-hunk summary",
7084
7173
  options: [{
7085
7174
  value: "full",
7086
7175
  label: "Full"
@@ -7103,7 +7192,7 @@ const SETTINGS_CHOICES = [
7103
7192
  category: "ui",
7104
7193
  key: "uiMode",
7105
7194
  label: "UI mode",
7106
- description: "chat-screen chrome density — full shows every shortcut · minimal hides them (`ctrl+y` to peek)",
7195
+ description: "chat screen density: full shows every shortcut, minimal hides them (ctrl+y to peek)",
7107
7196
  options: [{
7108
7197
  value: "full",
7109
7198
  label: "Full"
@@ -7116,7 +7205,7 @@ const SETTINGS_CHOICES = [
7116
7205
  category: "ui",
7117
7206
  key: "targetFps",
7118
7207
  label: "Renderer FPS",
7119
- description: "30 saves CPU · 60 recommended · 120 for ProMotion + modern terminals",
7208
+ description: "30 saves CPU, 60 recommended, 120 for ProMotion and modern terminals",
7120
7209
  options: [
7121
7210
  {
7122
7211
  value: 30,
@@ -7136,7 +7225,7 @@ const SETTINGS_CHOICES = [
7136
7225
  category: "ui",
7137
7226
  key: "ctrlCBehavior",
7138
7227
  label: "Ctrl+C behavior",
7139
- description: "quit immediately · confirm first · or stop current action then quit on double-press",
7228
+ description: "quit immediately, confirm first, or stop the current action and quit on a second press",
7140
7229
  options: [
7141
7230
  {
7142
7231
  value: "quit",
@@ -7156,7 +7245,7 @@ const SETTINGS_CHOICES = [
7156
7245
  category: "ui",
7157
7246
  key: "terminalTitle",
7158
7247
  label: "Terminal title",
7159
- description: "what the window / tab title shows — off leaves it alone · zidane pins the app name · session tracks the active session title · icon + session prefixes it with your session icon",
7248
+ description: "what the window or tab title shows: off leaves it alone, zidane pins the app name, session tracks the session title, icon + session adds your icon",
7160
7249
  options: [
7161
7250
  {
7162
7251
  value: "off",
@@ -7182,21 +7271,21 @@ const SETTINGS_INPUTS = [
7182
7271
  category: "ui",
7183
7272
  key: "sessionIcon",
7184
7273
  label: "Session icon",
7185
- description: "icon prefixed before the session title when \"Terminal title\" is set to icon + session (empty = ⚽)",
7274
+ description: "shown before the session title when \"Terminal title\" is set to icon + session (empty = ⚽)",
7186
7275
  placeholder: "⚽"
7187
7276
  },
7188
7277
  {
7189
7278
  category: "ui",
7190
7279
  key: "attentionIcon",
7191
7280
  label: "Attention icon",
7192
- description: "replaces the session icon while zidane waits on your input — tool approvals and question forms (empty = ⚠️)",
7281
+ description: "replaces the session icon while zidane waits on you, like tool approvals (empty = ⚠️)",
7193
7282
  placeholder: "⚠️"
7194
7283
  },
7195
7284
  {
7196
7285
  category: "ui",
7197
7286
  key: "activeIcon",
7198
7287
  label: "Active icon",
7199
- description: "replaces the session icon while zidane is running/streaming (empty = ⚡); attention wins when input is needed",
7288
+ description: "replaces the session icon while zidane is running (empty = ⚡), attention wins when input is needed",
7200
7289
  placeholder: "⚡"
7201
7290
  }
7202
7291
  ];
@@ -9025,6 +9114,536 @@ function formatPathForCwd(projectRelativePath, projectRoot, cwd) {
9025
9114
  return sep === "/" ? rel : rel.split(sep).join(posix.sep);
9026
9115
  }
9027
9116
  //#endregion
9117
+ //#region src/chat/provider-usage.ts
9118
+ /** Per-request timeout. Generous, since these endpoints are occasionally slow. */
9119
+ const FETCH_TIMEOUT_MS = 15e3;
9120
+ /** How long a snapshot stays fresh. Usage windows move in minutes, not seconds. */
9121
+ const USAGE_CACHE_TTL_MS = 6e4;
9122
+ /**
9123
+ * Credential-file key to probe. Keyed by credential key rather than provider
9124
+ * key so a custom descriptor reusing a builtin credential slot
9125
+ * (`credentialFileKey: 'anthropic'`) gets usage for free.
9126
+ */
9127
+ const USAGE_SOURCE_BY_CRED_KEY = {
9128
+ "anthropic": "claude",
9129
+ "openai-codex": "codex",
9130
+ "xai-oauth": "grok",
9131
+ "openrouter": "openrouter"
9132
+ };
9133
+ /** Credential kind each probe authenticates with. */
9134
+ const SOURCE_CRED_KIND = {
9135
+ claude: "oauth",
9136
+ codex: "oauth",
9137
+ grok: "oauth",
9138
+ openrouter: "apikey"
9139
+ };
9140
+ /** The probe for a descriptor, or `null` when it has no usage surface. */
9141
+ function usageSourceFor(descriptor) {
9142
+ return USAGE_SOURCE_BY_CRED_KEY[credKeyOf(descriptor)] ?? null;
9143
+ }
9144
+ /** Whether this provider can report subscription usage at all. */
9145
+ function providerUsageSupported(descriptor) {
9146
+ return usageSourceFor(descriptor) !== null;
9147
+ }
9148
+ /**
9149
+ * The window closest to its ceiling, which is the one that will bite first and
9150
+ * the only one a single-line indicator has room for. `null` when the snapshot
9151
+ * carries no windows.
9152
+ */
9153
+ function mostConstrainedWindow(snapshot) {
9154
+ let worst = null;
9155
+ for (const win of snapshot.windows) if (!worst || win.usedPercent > worst.usedPercent) worst = win;
9156
+ return worst;
9157
+ }
9158
+ /** Keyed by credential key: two descriptors sharing tokens share a snapshot. */
9159
+ const cache = /* @__PURE__ */ new Map();
9160
+ /** In-flight probes, so a footer read and a panel open share one request. */
9161
+ const inFlight = /* @__PURE__ */ new Map();
9162
+ /**
9163
+ * Newest probe issued per credential key, as a monotonic ticket. `refresh`
9164
+ * skips both maps above, so two probes for one key can be outstanding at once
9165
+ * (panel refresh while the footer's post-run refresh is still in flight); only
9166
+ * the newest may write the cache, or a slow older probe would pin stale
9167
+ * numbers for a full TTL. Clearing the map also invalidates every outstanding
9168
+ * ticket, so a probe that spans `clearProviderUsageCache()` cannot repopulate
9169
+ * the cache it was meant to drop.
9170
+ */
9171
+ const latestProbe = /* @__PURE__ */ new Map();
9172
+ let probeTicket = 0;
9173
+ /**
9174
+ * Drop every cached snapshot. Entries are keyed by credential KEY, not by the
9175
+ * token itself, so signing out and back in (or re-authing to another account)
9176
+ * would otherwise keep serving the previous account's numbers for the rest of
9177
+ * the TTL. The TUI calls this wherever it re-reads credentials.
9178
+ */
9179
+ function clearProviderUsageCache() {
9180
+ cache.clear();
9181
+ inFlight.clear();
9182
+ latestProbe.clear();
9183
+ }
9184
+ const OPT_OUT_VARS = ["ZIDANE_NO_USAGE_CHECK", "CI"];
9185
+ /** Whether the environment forbids usage probes outright. */
9186
+ function usageChecksOptedOut(env = process.env) {
9187
+ for (const key of OPT_OUT_VARS) {
9188
+ const v = env[key];
9189
+ if (v !== void 0 && v !== "" && v !== "0" && v.toLowerCase() !== "false") return true;
9190
+ }
9191
+ return false;
9192
+ }
9193
+ /**
9194
+ * Usage for one provider. `null` means there is nothing to show: the provider
9195
+ * has no usage surface, no credential of the kind its probe needs (file or,
9196
+ * for API keys, env), or the environment opted out (see
9197
+ * {@link usageChecksOptedOut}).
9198
+ *
9199
+ * Never rejects. Transport and parse failures land in the snapshot.
9200
+ */
9201
+ async function fetchProviderUsage(dataDir, descriptor, opts = {}) {
9202
+ const source = usageSourceFor(descriptor);
9203
+ if (!source || usageChecksOptedOut()) return null;
9204
+ const credKey = credKeyOf(descriptor);
9205
+ const cred = resolveUsageCredential(dataDir, descriptor, source);
9206
+ if (!cred) return null;
9207
+ const identify = (snapshot) => ({
9208
+ ...snapshot,
9209
+ providerKey: descriptor.key,
9210
+ label: descriptor.label
9211
+ });
9212
+ if (!opts.refresh) {
9213
+ const cached = cache.get(credKey);
9214
+ if (cached && cached.expiresAt > Date.now()) return identify(cached.snapshot);
9215
+ const pending = inFlight.get(credKey);
9216
+ if (pending) return pending.then(identify);
9217
+ }
9218
+ const ticket = ++probeTicket;
9219
+ latestProbe.set(credKey, ticket);
9220
+ const probe = runProbe(source, descriptor, cred).then((snapshot) => {
9221
+ if (latestProbe.get(credKey) === ticket) cache.set(credKey, {
9222
+ expiresAt: Date.now() + USAGE_CACHE_TTL_MS,
9223
+ snapshot
9224
+ });
9225
+ return snapshot;
9226
+ }).finally(() => {
9227
+ if (inFlight.get(credKey) === probe) inFlight.delete(credKey);
9228
+ });
9229
+ inFlight.set(credKey, probe);
9230
+ return probe.then(identify);
9231
+ }
9232
+ /**
9233
+ * The credential a probe authenticates with, or `null` when the provider isn't
9234
+ * authenticated in a way that probe can use.
9235
+ *
9236
+ * An API key is taken from the environment when the file has none: `detectAuth`
9237
+ * marks a provider available on a shell-set `envKey` alone, and the launcher
9238
+ * copies stored keys INTO env, so the two are the same signal. Reading only the
9239
+ * file would leave an env-authed user with no row at all, under an empty state
9240
+ * telling them they aren't signed in. OAuth tokens have no env equivalent, so
9241
+ * those probes stay file-only.
9242
+ */
9243
+ function resolveUsageCredential(dataDir, descriptor, source) {
9244
+ const kind = SOURCE_CRED_KIND[source];
9245
+ const stored = readCredentials(dataDir)[credKeyOf(descriptor)];
9246
+ if (stored?.kind === kind && (stored.kind !== "apikey" || stored.value)) return stored;
9247
+ if (kind === "apikey" && descriptor.envKey) {
9248
+ const fromEnv = process.env[descriptor.envKey];
9249
+ if (fromEnv) return {
9250
+ kind: "apikey",
9251
+ value: fromEnv
9252
+ };
9253
+ }
9254
+ return null;
9255
+ }
9256
+ /**
9257
+ * Usage for every registered provider that has both a usage surface and a
9258
+ * usable credential, in registry order. What the panel renders.
9259
+ */
9260
+ async function fetchAllProviderUsage(dataDir, registry, opts = {}) {
9261
+ const candidates = Object.values(registry).filter(providerUsageSupported);
9262
+ return (await Promise.all(candidates.map((descriptor) => fetchProviderUsage(dataDir, descriptor, opts)))).filter((snapshot) => snapshot !== null);
9263
+ }
9264
+ async function runProbe(source, descriptor, cred) {
9265
+ const base = {
9266
+ providerKey: descriptor.key,
9267
+ label: descriptor.label
9268
+ };
9269
+ try {
9270
+ switch (source) {
9271
+ case "claude": return await fetchClaudeUsage(base, cred);
9272
+ case "codex": return await fetchCodexUsage(base, cred);
9273
+ case "grok": return await fetchGrokUsage(base, cred);
9274
+ case "openrouter": return await fetchOpenRouterUsage(base, cred);
9275
+ }
9276
+ } catch (err) {
9277
+ return snapshot(base, { error: err instanceof Error ? err.message : "request failed" });
9278
+ }
9279
+ }
9280
+ /** Snapshot factory. Defaults to `unavailable` so failure paths stay one-liners. */
9281
+ function snapshot(base, partial = {}) {
9282
+ return {
9283
+ ...base,
9284
+ status: "unavailable",
9285
+ plan: null,
9286
+ windows: [],
9287
+ credits: null,
9288
+ fetchedAt: Date.now(),
9289
+ error: null,
9290
+ ...partial
9291
+ };
9292
+ }
9293
+ /**
9294
+ * OAuth access token, or an `unauthorized` snapshot explaining why there isn't
9295
+ * one. Expiry is checked without refreshing (see the module header); the next
9296
+ * run refreshes it.
9297
+ */
9298
+ function accessTokenOr(base, cred) {
9299
+ if (cred.kind !== "oauth" || !cred.access) return { fail: snapshot(base, {
9300
+ status: "unauthorized",
9301
+ error: "not signed in"
9302
+ }) };
9303
+ if (typeof cred.expires === "number" && cred.expires <= Date.now()) return { fail: snapshot(base, {
9304
+ status: "unauthorized",
9305
+ error: "token expired"
9306
+ }) };
9307
+ return { token: cred.access };
9308
+ }
9309
+ /**
9310
+ * 401/403 to unauthorized, other non-2xx to unavailable. `null` when the
9311
+ * response is fine.
9312
+ *
9313
+ * A rejected token reads in the same register as the local checks (`not signed
9314
+ * in`, `token expired`): it is the row a real user hits most, and the status
9315
+ * code tells them nothing. Other codes keep the number, where it is the useful
9316
+ * detail.
9317
+ */
9318
+ function httpFailure(base, res) {
9319
+ if (res.status === 401 || res.status === 403) return snapshot(base, {
9320
+ status: "unauthorized",
9321
+ error: "token rejected"
9322
+ });
9323
+ if (!res.ok) return snapshot(base, { error: `HTTP ${res.status}` });
9324
+ return null;
9325
+ }
9326
+ const CLAUDE_WINDOWS = [
9327
+ {
9328
+ key: "five_hour",
9329
+ id: "five_hour",
9330
+ label: "5h"
9331
+ },
9332
+ {
9333
+ key: "seven_day",
9334
+ id: "seven_day",
9335
+ label: "weekly"
9336
+ },
9337
+ {
9338
+ key: "seven_day_opus",
9339
+ id: "seven_day_opus",
9340
+ label: "opus weekly"
9341
+ },
9342
+ {
9343
+ key: "seven_day_sonnet",
9344
+ id: "seven_day_sonnet",
9345
+ label: "sonnet weekly"
9346
+ }
9347
+ ];
9348
+ async function fetchClaudeUsage(base, cred) {
9349
+ const auth = accessTokenOr(base, cred);
9350
+ if ("fail" in auth) return auth.fail;
9351
+ const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
9352
+ headers: {
9353
+ "Authorization": `Bearer ${auth.token}`,
9354
+ "anthropic-beta": "oauth-2025-04-20",
9355
+ "Accept": "application/json"
9356
+ },
9357
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
9358
+ });
9359
+ const failure = httpFailure(base, res);
9360
+ if (failure) return failure;
9361
+ const data = await res.json().catch(() => null);
9362
+ if (!data) return snapshot(base, { error: "invalid response" });
9363
+ const windows = [];
9364
+ for (const { key, id, label } of CLAUDE_WINDOWS) {
9365
+ const win = data[key];
9366
+ if (win && typeof win.utilization === "number") windows.push({
9367
+ id,
9368
+ label,
9369
+ usedPercent: clampPercent(win.utilization),
9370
+ resetsAt: isoToEpoch(win.resets_at)
9371
+ });
9372
+ }
9373
+ if (windows.length === 0) return snapshot(base, { error: "no usage data" });
9374
+ return snapshot(base, {
9375
+ status: "ok",
9376
+ windows
9377
+ });
9378
+ }
9379
+ /** Codex reports window length, not a name, so derive `5h` / `weekly` / `Nd`. */
9380
+ function codexWindowLabel(win, fallback) {
9381
+ const seconds = win.limit_window_seconds;
9382
+ if (!seconds) return fallback;
9383
+ if (seconds < 86400) return `${Math.round(seconds / 3600)}h`;
9384
+ const days = Math.round(seconds / 86400);
9385
+ return days === 7 ? "weekly" : `${days}d`;
9386
+ }
9387
+ async function fetchCodexUsage(base, cred) {
9388
+ const auth = accessTokenOr(base, cred);
9389
+ if ("fail" in auth) return auth.fail;
9390
+ const accountId = cred.kind === "oauth" && typeof cred.accountId === "string" ? cred.accountId : null;
9391
+ const res = await fetch("https://chatgpt.com/backend-api/wham/usage", {
9392
+ headers: {
9393
+ "Authorization": `Bearer ${auth.token}`,
9394
+ ...accountId ? { "ChatGPT-Account-Id": accountId } : {},
9395
+ "User-Agent": "codex-cli",
9396
+ "Accept": "application/json"
9397
+ },
9398
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
9399
+ });
9400
+ const failure = httpFailure(base, res);
9401
+ if (failure) return failure;
9402
+ const data = await res.json().catch(() => null);
9403
+ if (!data) return snapshot(base, { error: "invalid response" });
9404
+ const rateLimit = data.rate_limit ?? data.rate_limits;
9405
+ const windows = [];
9406
+ const push = (win, id, fallbackLabel) => {
9407
+ if (win && typeof win.used_percent === "number") windows.push({
9408
+ id,
9409
+ label: codexWindowLabel(win, fallbackLabel),
9410
+ usedPercent: clampPercent(win.used_percent),
9411
+ resetsAt: typeof win.reset_at === "number" ? win.reset_at * 1e3 : null
9412
+ });
9413
+ };
9414
+ push(rateLimit?.primary_window, "primary", "5h");
9415
+ push(rateLimit?.secondary_window, "secondary", "weekly");
9416
+ const credits = data.credits && typeof data.credits.balance === "number" && !data.credits.unlimited ? {
9417
+ remaining: data.credits.balance,
9418
+ total: null
9419
+ } : null;
9420
+ if (windows.length === 0 && !credits) return snapshot(base, { error: "no usage data" });
9421
+ return snapshot(base, {
9422
+ status: "ok",
9423
+ plan: typeof data.plan_type === "string" ? data.plan_type : null,
9424
+ windows,
9425
+ credits
9426
+ });
9427
+ }
9428
+ async function fetchOpenRouterUsage(base, cred) {
9429
+ if (cred.kind !== "apikey" || !cred.value) return snapshot(base, {
9430
+ status: "unauthorized",
9431
+ error: "no API key stored"
9432
+ });
9433
+ const res = await fetch("https://openrouter.ai/api/v1/credits", {
9434
+ headers: {
9435
+ Authorization: `Bearer ${cred.value}`,
9436
+ Accept: "application/json"
9437
+ },
9438
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
9439
+ });
9440
+ const failure = httpFailure(base, res);
9441
+ if (failure) return failure;
9442
+ const totals = (await res.json().catch(() => null))?.data;
9443
+ if (!totals || typeof totals.total_credits !== "number" || typeof totals.total_usage !== "number") return snapshot(base, { error: "invalid response" });
9444
+ return snapshot(base, {
9445
+ status: "ok",
9446
+ windows: [{
9447
+ id: "credits",
9448
+ label: "credits",
9449
+ usedPercent: totals.total_credits > 0 ? clampPercent(totals.total_usage / totals.total_credits * 100) : 0,
9450
+ resetsAt: null
9451
+ }],
9452
+ credits: {
9453
+ remaining: totals.total_credits - totals.total_usage,
9454
+ total: totals.total_credits
9455
+ }
9456
+ });
9457
+ }
9458
+ const GROK_BILLING_URL = "https://grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig";
9459
+ const GROK_AUTH_FAILURE_RE = /access token|unauthenticated|expired/i;
9460
+ async function fetchGrokUsage(base, cred) {
9461
+ const auth = accessTokenOr(base, cred);
9462
+ if ("fail" in auth) return auth.fail;
9463
+ const res = await fetch(GROK_BILLING_URL, {
9464
+ method: "POST",
9465
+ headers: {
9466
+ "Authorization": `Bearer ${auth.token}`,
9467
+ "Content-Type": "application/grpc-web+proto",
9468
+ "x-grpc-web": "1",
9469
+ "x-user-agent": "connect-es/2.1.1",
9470
+ "Origin": "https://grok.com",
9471
+ "Referer": "https://grok.com/?_s=usage",
9472
+ "Accept": "*/*"
9473
+ },
9474
+ body: new Uint8Array([
9475
+ 0,
9476
+ 0,
9477
+ 0,
9478
+ 0,
9479
+ 0
9480
+ ]),
9481
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
9482
+ });
9483
+ const failure = httpFailure(base, res);
9484
+ if (failure) return failure;
9485
+ const grpcStatus = res.headers.get("grpc-status");
9486
+ if (grpcStatus && grpcStatus !== "0") {
9487
+ const message = res.headers.get("grpc-message") ?? "";
9488
+ return grpcStatus === "16" || grpcStatus === "7" && GROK_AUTH_FAILURE_RE.test(message) ? snapshot(base, {
9489
+ status: "unauthorized",
9490
+ error: "token not accepted"
9491
+ }) : snapshot(base, { error: `gRPC ${grpcStatus}${message ? `: ${message}` : ""}` });
9492
+ }
9493
+ const parsed = parseGrokBillingResponse(new Uint8Array(await res.arrayBuffer()));
9494
+ if (!parsed) return snapshot(base, { error: "unreadable response" });
9495
+ return snapshot(base, {
9496
+ status: "ok",
9497
+ windows: [{
9498
+ id: "credits",
9499
+ label: "credits",
9500
+ usedPercent: clampPercent(parsed.usedPercent),
9501
+ resetsAt: parsed.resetsAt ? parsed.resetsAt.getTime() : null
9502
+ }]
9503
+ });
9504
+ }
9505
+ /** Split a gRPC-web body into data frames. Flag bit 0x80 marks trailers. */
9506
+ function grpcWebDataFrames(bytes) {
9507
+ const frames = [];
9508
+ let index = 0;
9509
+ while (index < bytes.length) {
9510
+ if (index + 5 > bytes.length) return [];
9511
+ const flags = bytes[index];
9512
+ const length = bytes[index + 1] << 24 | bytes[index + 2] << 16 | bytes[index + 3] << 8 | bytes[index + 4];
9513
+ const start = index + 5;
9514
+ const end = start + length;
9515
+ if (length < 0 || end > bytes.length) return [];
9516
+ if ((flags & 128) === 0) frames.push(bytes.subarray(start, end));
9517
+ index = end;
9518
+ }
9519
+ return frames;
9520
+ }
9521
+ function looksLikeProtobuf(bytes) {
9522
+ if (bytes.length === 0) return false;
9523
+ const fieldNumber = bytes[0] >> 3;
9524
+ const wireType = bytes[0] & 7;
9525
+ return fieldNumber > 0 && (wireType === 0 || wireType === 1 || wireType === 2 || wireType === 5);
9526
+ }
9527
+ function readVarint(bytes, offset) {
9528
+ let value = 0;
9529
+ let shift = 0;
9530
+ let index = offset;
9531
+ while (index < bytes.length && shift < 64) {
9532
+ const byte = bytes[index];
9533
+ value += (byte & 127) * 2 ** shift;
9534
+ index += 1;
9535
+ if ((byte & 128) === 0) return {
9536
+ value,
9537
+ next: index
9538
+ };
9539
+ shift += 7;
9540
+ }
9541
+ return null;
9542
+ }
9543
+ function scanProtobuf(bytes, path, scan, orderRef, depth) {
9544
+ if (depth > 6) return;
9545
+ let index = 0;
9546
+ while (index < bytes.length) {
9547
+ const tag = readVarint(bytes, index);
9548
+ if (!tag) return;
9549
+ const fieldNumber = Math.floor(tag.value / 8);
9550
+ const wireType = tag.value % 8;
9551
+ if (fieldNumber === 0) return;
9552
+ index = tag.next;
9553
+ const fieldPath = [...path, fieldNumber];
9554
+ switch (wireType) {
9555
+ case 0: {
9556
+ const v = readVarint(bytes, index);
9557
+ if (!v) return;
9558
+ scan.varint.push({
9559
+ path: fieldPath,
9560
+ order: orderRef.n++,
9561
+ value: v.value
9562
+ });
9563
+ index = v.next;
9564
+ break;
9565
+ }
9566
+ case 1:
9567
+ if (index + 8 > bytes.length) return;
9568
+ index += 8;
9569
+ break;
9570
+ case 5: {
9571
+ if (index + 4 > bytes.length) return;
9572
+ const view = new DataView(bytes.buffer, bytes.byteOffset + index, 4);
9573
+ scan.fixed32.push({
9574
+ path: fieldPath,
9575
+ order: orderRef.n++,
9576
+ value: view.getFloat32(0, true)
9577
+ });
9578
+ index += 4;
9579
+ break;
9580
+ }
9581
+ case 2: {
9582
+ const len = readVarint(bytes, index);
9583
+ if (!len || len.next + len.value > bytes.length) return;
9584
+ const sub = bytes.subarray(len.next, len.next + len.value);
9585
+ if (looksLikeProtobuf(sub)) scanProtobuf(sub, fieldPath, scan, orderRef, depth + 1);
9586
+ index = len.next + len.value;
9587
+ break;
9588
+ }
9589
+ default: return;
9590
+ }
9591
+ }
9592
+ }
9593
+ /**
9594
+ * Recover `{ usedPercent, resetsAt }` from the billing protobuf without a
9595
+ * schema. The used percent is a fixed32 float in 0-100 whose path ends in field
9596
+ * 1 (shallowest, earliest wins); the reset is the nearest future epoch-second
9597
+ * varint, preferring path `[1,5,1]` (billing-cycle end).
9598
+ */
9599
+ function parseGrokBillingResponse(bytes, now = /* @__PURE__ */ new Date()) {
9600
+ let frames = grpcWebDataFrames(bytes);
9601
+ if (frames.length === 0 && looksLikeProtobuf(bytes)) frames = [bytes];
9602
+ if (frames.length === 0) return null;
9603
+ const scan = {
9604
+ fixed32: [],
9605
+ varint: []
9606
+ };
9607
+ const orderRef = { n: 0 };
9608
+ for (const frame of frames) scanProtobuf(frame, [], scan, orderRef, 0);
9609
+ const percentField = scan.fixed32.filter((f) => f.path.at(-1) === 1 && Number.isFinite(f.value) && f.value >= 0 && f.value <= 100).sort((a, b) => a.path.length === b.path.length ? a.order - b.order : a.path.length - b.path.length)[0];
9610
+ const epochFields = scan.varint.filter((f) => f.value >= 17e8 && f.value <= 21e8).map((f) => ({
9611
+ path: f.path,
9612
+ date: /* @__PURE__ */ new Date(f.value * 1e3)
9613
+ })).filter((f) => f.date > now);
9614
+ const preferred = epochFields.filter((f) => f.path.length === 3 && f.path[0] === 1 && f.path[1] === 5 && f.path[2] === 1);
9615
+ const resetsAt = (preferred.length > 0 ? preferred : epochFields).map((f) => f.date).sort((a, b) => a.getTime() - b.getTime())[0] ?? null;
9616
+ const hasUsagePeriod = scan.varint.some((f) => f.path.length >= 2 && f.path[0] === 1 && f.path[1] === 6 || f.path.length === 3 && f.path[0] === 1 && f.path[1] === 8 && f.path[2] === 1 && (f.value === 1 || f.value === 2));
9617
+ if (!percentField) {
9618
+ if (scan.fixed32.length === 0 && resetsAt && hasUsagePeriod) return {
9619
+ usedPercent: 0,
9620
+ resetsAt
9621
+ };
9622
+ return null;
9623
+ }
9624
+ return {
9625
+ usedPercent: percentField.value,
9626
+ resetsAt
9627
+ };
9628
+ }
9629
+ /**
9630
+ * Percent without trailing noise: `43`, `7.5`, `100`. Used by the panel. The
9631
+ * footer floors to a whole percent instead (`footerUsagePercent` in
9632
+ * `tui/components.tsx`), so a status bar never reads `100%` for `99.9`.
9633
+ */
9634
+ function formatUsagePercent(usedPercent) {
9635
+ return Number.isInteger(usedPercent) ? String(usedPercent) : usedPercent.toFixed(1);
9636
+ }
9637
+ function clampPercent(value) {
9638
+ if (!Number.isFinite(value)) return 0;
9639
+ return Math.min(100, Math.max(0, Math.round(value * 10) / 10));
9640
+ }
9641
+ function isoToEpoch(value) {
9642
+ if (typeof value !== "string") return null;
9643
+ const ts = new Date(value).getTime();
9644
+ return Number.isFinite(ts) ? ts : null;
9645
+ }
9646
+ //#endregion
9028
9647
  //#region src/chat/shell-parse.ts
9029
9648
  /**
9030
9649
  * Lightweight shell command parser for safelist enforcement.
@@ -10555,6 +11174,6 @@ function computeTurnAnchors(items) {
10555
11174
  };
10556
11175
  }
10557
11176
  //#endregion
10558
- export { McpAuthProvider as $, isTodoTool as $n, resolveStoragePaths as $t, writeProjects as A, performInPlaceSelfUpdate as An, saveState as Ar, SETTINGS_INPUTS as At, mcpToolsCachePath as B, DEFAULT_PERSIST_EXCLUDE_TOOLS as Bn, GRUVBOX_DARK as Bt, addToSafelist as C, buildUpdateHint as Cn, createStateStore as Cr, buildHints as Ct, projectsFilePath as D, detectLibc as Dn, listSessionMeta as Dr, DEFAULT_SETTINGS as Dt, matchesSafelistEntry as E, compareSemver as En, lastContextSizeFromTurns as Er, useEnabledToggleSet as Et, oauthUsesManualCodePaste as F, renderAgentsMdBlock as Fn, toolCallPreview as Fr, BUILTIN_THEMES as Ft, useMcpToolToggleMap as G, TODOREAD_TOOL as Gn, CATPPUCCIN_MOCHA as Gt, saveMcpToolsCache as H, accentColor as Hn, CATPPUCCIN_FRAPPE as Ht, runOAuthLogin as I, BUILD_AGENT as In, toolResultText as Ir, DEFAULT_THEME as It, indexOfServerRow as J, TODO_STATUS_GLYPHS as Jn, useDiscovery as Jt, useMcpToolToggleSet as K, TODOS_METADATA_KEY as Kn, createDiscoverySlot as Kt, supportsOAuth as L, BUILTIN_AGENTS as Ln, updateToolEventOutcomes as Lr, resolveChipColor as Lt, fetchOAuthRedirect as M, resolvePlatformPackage as Mn, stripSpawnTokensLine as Mr, SettingsProvider as Mt, normalizeRedirectPaste as N, chatAutoCompactBehavior as Nn, sumRunCosts as Nr, clampFps as Nt, readProjects as O, detectPackageManager as On, loadState as Or, SETTINGS_CATEGORIES as Ot, redirectUriFromAuthUrl as P, discoverAgentsMd as Pn, titleFromTurns as Pr, useSettings as Pt, patchMcpCredential as Q, getTodosForRun as Qn, resolveConfig as Qt, clearMcpToolsCache as R, DEFAULT_AGENT_ID as Rn, usageByModel as Rr, resolveTheme as Rt, IMPLICITLY_SAFE_TOOLS as S, bootTick as Sn, compactSummaryEvent as Sr, truncateTrailing as St, isOnSafelist as T, checkForUpdate as Tn, eventsFromTurns as Tr, listProjectFiles as Tt, subscribeMcpToolsCache as U, resolveAgentId as Un, CATPPUCCIN_LATTE as Ut, refreshMcpToolsCatalog as V, PLAN_AGENT as Vn, GRUVBOX_LIGHT as Vt, buildToolToggle as W, singleAgentRegistry as Wn, CATPPUCCIN_MACCHIATO as Wt, createFileMcpCredentialStore as X, createTodoTools as Xn, ConfigProvider as Xt, parentServerName as Y, TODO_WRITE_COUNTS_METADATA_KEY as Yn, useDiscoveryOptional as Yt, mcpCredentialsPath as Z, getArchivedTodosForRun as Zn, useConfig as Zt, resolveSessionExportTarget as _, defaultMcpsConfigPaths as _n, buildPlanSystem as _r, useInteractionsActions as _t, useSurfaces as a, restoreChainFallbacks as an, useActiveTodos as ar, AUTOMATIC_INTERACTION_INSTRUCTION as at, useSafeModeActions as b, tryOpenBrowser as bn, createChatTurnsReadTool as br, clipHintsToWidth as bt, discoverSystemMd as c, useCompletion as cn, DOING_TASKS_DOCTRINE as cr, PRESENT_PLAN_TOOL as ct, userSystemMdPath as d, importFromClaudeCode as dn, INTERACTION_GUIDANCE_NO_PROMPTS as dr, createInteractionTools as dt, buildRunModel as en, pickActiveRunId as er, useMcpAuthDispatch as et, useStreamBuffer as f, mcpsWritePath as fn, PLAN_MODE_DOCTRINE as fr, isInteractionTool as ft, renderSession as g, dedupMcpServersByName as gn, buildBuildSystem as gr, serializeInteractionResponse as gt, discoverProjectSkills as h, buildMcpServers as hn, TOKEN_DISCIPLINE_DOCTRINE as hr, pendingInteractionsFromTurns as ht, useSelectStyle as i, providerKeySetChanged as in, todoProgressLabel as ir, ASK_USER_TOOL as it, formatPathForCwd as j, performSelfUpdate as jn, serverToolResultSummary as jr, SETTINGS_TOGGLES as jt, suggestSafelistEntries as k, parseSemver as kn, marginTopFor as kr, SETTINGS_CHOICES as kt, renderSystemMdSeed as l, countClaudeSessionFiles as ln, IDENTITY_PREFIX as lr, buildResumedToolResultsTurn as lt, defaultSkillScanPaths as m, upsertMcpServer as mn, SUBAGENT_GUIDANCE as mr, makeRequestInteraction as mt, ThemeProvider as n, chainsEqual as nn, selectActiveTodos as nr, getMcpAuthStatus as nt, useSyntaxStyles as o, sanitizeModelChain as on, ACTIONS_WITH_CARE_DOCTRINE as or, HOST_INTERACTION_ID_PREFIX as ot, buildSkillsConfig as p, removeMcpServer as pn, PLAN_MODE_DOCTRINE_NO_PROMPTS as pr, isModelInteraction as pt, buildVisibleMcpRows as q, TODOWRITE_TOOL as qn, DiscoveryProvider as qt, useColors as r, filterChainFallbacks as rn, setTodosForRun as rr, reduceMcpAuth as rt, useTheme as s, toggleChainEntry as sn, COMMUNICATION_DOCTRINE as sr, InteractionsProvider as st, computeTurnAnchors as t, chainPositionOf as tn, pruneTodosByRun as tr, useMcpAuthState as tt, userScopeOverridesLoad as u, hasClaudeCodeSetup as un, INTERACTION_GUIDANCE as ur, createAutomaticInteractionResponse as ut, writeSessionExport as v, discoverProjectMcps as vn, envSection as vr, useInteractionsQueue as vt, getSafelist as w, useUpdateCheck as wn, deriveSessionTitle as wr, shortChord as wt, useSafeModeQueue as x, bootProfileEnabled as xn, DEFAULT_SESSION_TITLE as xr, hintsLength as xt, SafeModeProvider as y, parseMcpsFile as yn, chatReferenceTokensForPrompt as yr, EMPTY_HINTS as yt, loadMcpToolsCache as z, DEFAULT_BUDGET_EXCLUDE_TOOLS as zn, VAPORWAVE_THEME as zt };
11177
+ export { buildToolToggle as $, singleAgentRegistry as $n, CATPPUCCIN_MACCHIATO as $t, writeProjects as A, bootProfileEnabled as An, DEFAULT_SESSION_TITLE as Ar, hintsLength as At, formatPathForCwd as B, performSelfUpdate as Bn, saveState as Br, SETTINGS_TOGGLES as Bt, addToSafelist as C, upsertMcpServer as Cn, SUBAGENT_GUIDANCE as Cr, makeRequestInteraction as Ct, projectsFilePath as D, discoverProjectMcps as Dn, envSection as Dr, useInteractionsQueue as Dt, matchesSafelistEntry as E, defaultMcpsConfigPaths as En, buildPlanSystem as Er, useInteractionsActions as Et, formatUsagePercent as F, compareSemver as Fn, lastContextSizeFromTurns as Fr, useEnabledToggleSet as Ft, runOAuthLogin as G, BUILD_AGENT as Gn, toolCallPreview as Gr, DEFAULT_THEME as Gt, normalizeRedirectPaste as H, chatAutoCompactBehavior as Hn, stripSpawnTokensLine as Hr, clampFps as Ht, mostConstrainedWindow as I, detectLibc as In, listSessionMeta as Ir, DEFAULT_SETTINGS as It, loadMcpToolsCache as J, DEFAULT_BUDGET_EXCLUDE_TOOLS as Jn, usageByModel as Jr, VAPORWAVE_THEME as Jt, supportsOAuth as K, BUILTIN_AGENTS as Kn, toolResultText as Kr, resolveChipColor as Kt, providerUsageSupported as L, detectPackageManager as Ln, loadState as Lr, SETTINGS_CATEGORIES as Lt, clearProviderUsageCache as M, buildUpdateHint as Mn, createStateStore as Mr, buildHints as Mt, fetchAllProviderUsage as N, useUpdateCheck as Nn, deriveSessionTitle as Nr, shortChord as Nt, readProjects as O, parseMcpsFile as On, chatReferenceTokensForPrompt as Or, EMPTY_HINTS as Ot, fetchProviderUsage as P, checkForUpdate as Pn, eventsFromTurns as Pr, listProjectFiles as Pt, subscribeMcpToolsCache as Q, resolveAgentId as Qn, CATPPUCCIN_LATTE as Qt, usageChecksOptedOut as R, parseSemver as Rn, marginTopFor as Rr, SETTINGS_CHOICES as Rt, IMPLICITLY_SAFE_TOOLS as S, removeMcpServer as Sn, PLAN_MODE_DOCTRINE_NO_PROMPTS as Sr, isModelInteraction as St, isOnSafelist as T, dedupMcpServersByName as Tn, buildBuildSystem as Tr, serializeInteractionResponse as Tt, redirectUriFromAuthUrl as U, discoverAgentsMd as Un, sumRunCosts as Ur, useSettings as Ut, fetchOAuthRedirect as V, resolvePlatformPackage as Vn, serverToolResultSummary as Vr, SettingsProvider as Vt, oauthUsesManualCodePaste as W, renderAgentsMdBlock as Wn, titleFromTurns as Wr, BUILTIN_THEMES as Wt, refreshMcpToolsCatalog as X, PLAN_AGENT as Xn, GRUVBOX_LIGHT as Xt, mcpToolsCachePath as Y, DEFAULT_PERSIST_EXCLUDE_TOOLS as Yn, GRUVBOX_DARK as Yt, saveMcpToolsCache as Z, accentColor as Zn, CATPPUCCIN_FRAPPE as Zt, resolveSessionExportTarget as _, useCompletion as _n, DOING_TASKS_DOCTRINE as _r, PRESENT_PLAN_TOOL as _t, useSurfaces as a, ConfigProvider as an, createTodoTools as ar, createFileMcpCredentialStore as at, useSafeModeActions as b, importFromClaudeCode as bn, INTERACTION_GUIDANCE_NO_PROMPTS as br, createInteractionTools as bt, discoverSystemMd as c, resolveStoragePaths as cn, isTodoTool as cr, McpAuthProvider as ct, userSystemMdPath as d, chainsEqual as dn, selectActiveTodos as dr, getMcpAuthStatus as dt, CATPPUCCIN_MOCHA as en, TODOREAD_TOOL as er, useMcpToolToggleMap as et, useStreamBuffer as f, filterChainFallbacks as fn, setTodosForRun as fr, reduceMcpAuth as ft, renderSession as g, toggleChainEntry as gn, COMMUNICATION_DOCTRINE as gr, InteractionsProvider as gt, discoverProjectSkills as h, sanitizeModelChain as hn, ACTIONS_WITH_CARE_DOCTRINE as hr, HOST_INTERACTION_ID_PREFIX as ht, useSelectStyle as i, useDiscoveryOptional as in, TODO_WRITE_COUNTS_METADATA_KEY as ir, parentServerName as it, USAGE_CACHE_TTL_MS as j, bootTick as jn, compactSummaryEvent as jr, truncateTrailing as jt, suggestSafelistEntries as k, tryOpenBrowser as kn, createChatTurnsReadTool as kr, clipHintsToWidth as kt, renderSystemMdSeed as l, buildRunModel as ln, pickActiveRunId as lr, useMcpAuthDispatch as lt, defaultSkillScanPaths as m, restoreChainFallbacks as mn, useActiveTodos as mr, AUTOMATIC_INTERACTION_INSTRUCTION as mt, ThemeProvider as n, DiscoveryProvider as nn, TODOWRITE_TOOL as nr, buildVisibleMcpRows as nt, useSyntaxStyles as o, useConfig as on, getArchivedTodosForRun as or, mcpCredentialsPath as ot, buildSkillsConfig as p, providerKeySetChanged as pn, todoProgressLabel as pr, ASK_USER_TOOL as pt, clearMcpToolsCache as q, DEFAULT_AGENT_ID as qn, updateToolEventOutcomes as qr, resolveTheme as qt, useColors as r, useDiscovery as rn, TODO_STATUS_GLYPHS as rr, indexOfServerRow as rt, useTheme as s, resolveConfig as sn, getTodosForRun as sr, patchMcpCredential as st, computeTurnAnchors as t, createDiscoverySlot as tn, TODOS_METADATA_KEY as tr, useMcpToolToggleSet as tt, userScopeOverridesLoad as u, chainPositionOf as un, pruneTodosByRun as ur, useMcpAuthState as ut, writeSessionExport as v, countClaudeSessionFiles as vn, IDENTITY_PREFIX as vr, buildResumedToolResultsTurn as vt, getSafelist as w, buildMcpServers as wn, TOKEN_DISCIPLINE_DOCTRINE as wr, pendingInteractionsFromTurns as wt, useSafeModeQueue as x, mcpsWritePath as xn, PLAN_MODE_DOCTRINE as xr, isInteractionTool as xt, SafeModeProvider as y, hasClaudeCodeSetup as yn, INTERACTION_GUIDANCE as yr, createAutomaticInteractionResponse as yt, usageSourceFor as z, performInPlaceSelfUpdate as zn, noticeTurnContent as zr, SETTINGS_INPUTS as zt };
10559
11178
 
10560
- //# sourceMappingURL=transcript-anchors-BfV8T2us.js.map
11179
+ //# sourceMappingURL=transcript-anchors-BjH-68k2.js.map