pi-ui-extend 1.0.42 → 1.0.45

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 (233) hide show
  1. package/README.md +41 -12
  2. package/dist/app/app.d.ts +10 -0
  3. package/dist/app/app.js +132 -8
  4. package/dist/app/cli/install.js +2 -2
  5. package/dist/app/commands/command-controller.d.ts +4 -0
  6. package/dist/app/commands/command-controller.js +11 -0
  7. package/dist/app/commands/command-git-actions.d.ts +25 -0
  8. package/dist/app/commands/command-git-actions.js +381 -0
  9. package/dist/app/commands/command-host.d.ts +3 -0
  10. package/dist/app/commands/command-host.js +5 -2
  11. package/dist/app/commands/command-model-actions.d.ts +1 -0
  12. package/dist/app/commands/command-model-actions.js +51 -34
  13. package/dist/app/commands/command-navigation-actions.d.ts +1 -0
  14. package/dist/app/commands/command-navigation-actions.js +16 -3
  15. package/dist/app/commands/command-registry.d.ts +2 -0
  16. package/dist/app/commands/command-registry.js +14 -0
  17. package/dist/app/commands/reload-context-inventory.js +12 -1
  18. package/dist/app/input/autocomplete-controller.js +61 -34
  19. package/dist/app/input/input-action-controller.d.ts +2 -0
  20. package/dist/app/input/input-action-controller.js +8 -1
  21. package/dist/app/input/input-controller.d.ts +2 -1
  22. package/dist/app/input/input-controller.js +7 -2
  23. package/dist/app/input/prompt-enhancer-controller.js +53 -35
  24. package/dist/app/input/voice-controller.d.ts +41 -45
  25. package/dist/app/input/voice-controller.js +351 -419
  26. package/dist/app/model/model-usage-controller.js +17 -7
  27. package/dist/app/model/model-usage-status.d.ts +4 -1
  28. package/dist/app/model/model-usage-status.js +270 -46
  29. package/dist/app/popup/menu-items-controller.d.ts +13 -3
  30. package/dist/app/popup/menu-items-controller.js +37 -21
  31. package/dist/app/popup/popup-action-controller.d.ts +12 -2
  32. package/dist/app/popup/popup-action-controller.js +77 -24
  33. package/dist/app/popup/popup-menu-controller.d.ts +36 -14
  34. package/dist/app/popup/popup-menu-controller.js +239 -69
  35. package/dist/app/rendering/dcp-stats.d.ts +9 -4
  36. package/dist/app/rendering/dcp-stats.js +40 -425
  37. package/dist/app/rendering/popup-menu-renderer.d.ts +3 -5
  38. package/dist/app/rendering/popup-menu-renderer.js +42 -37
  39. package/dist/app/rendering/render-controller.js +23 -2
  40. package/dist/app/rendering/status-line-renderer.d.ts +5 -0
  41. package/dist/app/rendering/status-line-renderer.js +41 -5
  42. package/dist/app/rendering/tab-line-renderer.js +26 -20
  43. package/dist/app/runtime.d.ts +12 -1
  44. package/dist/app/runtime.js +120 -12
  45. package/dist/app/screen/mouse-controller.d.ts +2 -0
  46. package/dist/app/screen/mouse-controller.js +17 -7
  47. package/dist/app/screen/status-controller.d.ts +4 -0
  48. package/dist/app/screen/status-controller.js +5 -0
  49. package/dist/app/session/lazy-session-manager.js +34 -0
  50. package/dist/app/session/session-event-controller.d.ts +1 -0
  51. package/dist/app/session/session-event-controller.js +10 -1
  52. package/dist/app/session/session-history.d.ts +1 -0
  53. package/dist/app/session/session-history.js +12 -1
  54. package/dist/app/session/session-lifecycle-controller.d.ts +6 -1
  55. package/dist/app/session/session-lifecycle-controller.js +10 -1
  56. package/dist/app/session/tabs-controller.d.ts +25 -1
  57. package/dist/app/session/tabs-controller.js +442 -153
  58. package/dist/app/subagents/subagents-widget-controller.d.ts +1 -0
  59. package/dist/app/subagents/subagents-widget-controller.js +6 -0
  60. package/dist/app/types.d.ts +11 -1
  61. package/dist/app/workspace/workspace-actions-controller.js +10 -2
  62. package/dist/app/workspace/workspace-undo.d.ts +1 -0
  63. package/dist/app/workspace/workspace-undo.js +1 -0
  64. package/dist/bundled-extensions/question/index.js +9 -1
  65. package/dist/bundled-extensions/question/remote.d.ts +4 -0
  66. package/dist/bundled-extensions/question/remote.js +33 -0
  67. package/dist/bundled-extensions/telegram-connector/bot.d.ts +43 -0
  68. package/dist/bundled-extensions/telegram-connector/bot.js +166 -0
  69. package/dist/bundled-extensions/telegram-connector/config.d.ts +8 -0
  70. package/dist/bundled-extensions/telegram-connector/config.js +87 -0
  71. package/dist/bundled-extensions/telegram-connector/coordinator.d.ts +66 -0
  72. package/dist/bundled-extensions/telegram-connector/coordinator.js +413 -0
  73. package/dist/bundled-extensions/telegram-connector/index.d.ts +3 -0
  74. package/dist/bundled-extensions/telegram-connector/index.js +195 -0
  75. package/dist/bundled-extensions/terminal-bell/index.d.ts +0 -8
  76. package/dist/bundled-extensions/terminal-bell/index.js +0 -76
  77. package/dist/bundled-extensions/workspace-undo/index.d.ts +26 -0
  78. package/dist/bundled-extensions/workspace-undo/index.js +191 -0
  79. package/dist/config.d.ts +21 -2
  80. package/dist/config.js +159 -32
  81. package/dist/default-pix-config.js +20 -10
  82. package/dist/schemas/pi-tools-suite-schema.d.ts +8 -4
  83. package/dist/schemas/pi-tools-suite-schema.js +11 -5
  84. package/dist/schemas/pix-schema.d.ts +12 -2
  85. package/dist/schemas/pix-schema.js +37 -9
  86. package/dist/schemas/tasks-schema.js +1 -1
  87. package/docs/concurrency.md +3 -1
  88. package/docs/desktop-mvp.md +10 -5
  89. package/docs/desktop-task-manager.md +81 -28
  90. package/docs/release.md +30 -6
  91. package/external/pi-tools-suite/README.md +165 -39
  92. package/external/pi-tools-suite/docs/session-recovery.md +7 -0
  93. package/external/pi-tools-suite/docs/subagent-model-pools.md +12 -5
  94. package/external/pi-tools-suite/docs/ui-qa-subagent.md +440 -0
  95. package/external/pi-tools-suite/package.json +6 -1
  96. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +2 -1
  97. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +9 -3
  98. package/external/pi-tools-suite/src/antigravity-auth/headers.ts +41 -3
  99. package/external/pi-tools-suite/src/antigravity-auth/models.ts +94 -21
  100. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +5 -17
  101. package/external/pi-tools-suite/src/antigravity-auth/payload.ts +72 -7
  102. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +13 -1
  103. package/external/pi-tools-suite/src/async-subagents/agents/implement.md +1 -1
  104. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/browser.mjs +346 -0
  105. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/desktop.mjs +1038 -0
  106. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/tui.mjs +759 -0
  107. package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/scripts/browser-qa-runner.mjs +32 -23
  108. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/chrome-devtools/chrome-devtools-provider.mjs +895 -0
  109. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/linux/linux-atspi.py +494 -0
  110. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/macos/macos-accessibility.swift +1085 -0
  111. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/bridge-client.mjs +50 -0
  112. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/native-terminal-host.mjs +501 -0
  113. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/windows/windows-uia.ps1 +454 -0
  114. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-auth.md +114 -0
  115. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-chrome-devtools.md +91 -0
  116. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-playwright.md +145 -0
  117. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser.md +82 -0
  118. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-linux.md +26 -0
  119. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-macos.md +29 -0
  120. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-windows.md +23 -0
  121. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop.md +54 -0
  122. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-native-terminal.md +59 -0
  123. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-pty.md +37 -0
  124. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui.md +60 -0
  125. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/scripts/ui-qa-runner.mjs +535 -0
  126. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa.md +75 -0
  127. package/external/pi-tools-suite/src/async-subagents/commands.ts +1 -1
  128. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
  129. package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +0 -2
  130. package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +16 -2
  131. package/external/pi-tools-suite/src/async-subagents/core/config.ts +40 -17
  132. package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +2 -1
  133. package/external/pi-tools-suite/src/async-subagents/core/registry.ts +158 -33
  134. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +7 -4
  135. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +34 -24
  136. package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +51 -45
  137. package/external/pi-tools-suite/src/async-subagents/lib.ts +9 -1
  138. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +0 -1
  139. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +2 -2
  140. package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
  141. package/external/pi-tools-suite/src/config.ts +14 -0
  142. package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
  143. package/external/pi-tools-suite/src/context-gateway/config.ts +66 -1
  144. package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
  145. package/external/pi-tools-suite/src/context-gateway/enforcement.ts +123 -3
  146. package/external/pi-tools-suite/src/context-gateway/index.ts +137 -4
  147. package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +5 -5
  148. package/external/pi-tools-suite/src/context-gateway/telemetry.ts +2 -2
  149. package/external/pi-tools-suite/src/context-gateway/types.ts +8 -1
  150. package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
  151. package/external/pi-tools-suite/src/dcp/auto-compress.ts +41 -14
  152. package/external/pi-tools-suite/src/dcp/commands.ts +14 -156
  153. package/external/pi-tools-suite/src/dcp/compress-tool.ts +8 -2
  154. package/external/pi-tools-suite/src/dcp/compression-progress.ts +22 -0
  155. package/external/pi-tools-suite/src/dcp/config.ts +58 -4
  156. package/external/pi-tools-suite/src/dcp/conversation-index.ts +36 -9
  157. package/external/pi-tools-suite/src/dcp/diagnostics.ts +41 -0
  158. package/external/pi-tools-suite/src/dcp/fresh-tool-results.ts +38 -0
  159. package/external/pi-tools-suite/src/dcp/index.ts +338 -66
  160. package/external/pi-tools-suite/src/dcp/journal.ts +126 -4
  161. package/external/pi-tools-suite/src/dcp/progress-controller.ts +2 -1
  162. package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +92 -2
  163. package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
  164. package/external/pi-tools-suite/src/dcp/pruner.ts +1 -0
  165. package/external/pi-tools-suite/src/dcp/routine-pressure.ts +86 -0
  166. package/external/pi-tools-suite/src/dcp/state.ts +9 -0
  167. package/external/pi-tools-suite/src/dcp/statistics.d.ts +8 -0
  168. package/external/pi-tools-suite/src/dcp/statistics.js +156 -0
  169. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +68 -16
  170. package/external/pi-tools-suite/src/lib/project.ts +36 -1
  171. package/external/pi-tools-suite/src/model-tools/index.ts +10 -7
  172. package/external/pi-tools-suite/src/repo-discovery/index.ts +304 -4
  173. package/external/pi-tools-suite/src/resource-registry/index.ts +282 -21
  174. package/external/pi-tools-suite/src/session-recovery/index.ts +17 -0
  175. package/external/pi-tools-suite/src/todo/index.ts +21 -0
  176. package/external/pi-tools-suite/src/todo/todo.ts +19 -1
  177. package/external/pi-tools-suite/src/tool-descriptions.ts +44 -15
  178. package/package.json +7 -8
  179. package/schemas/pi-tools-suite.json +44 -12
  180. package/schemas/pix.json +109 -15
  181. package/schemas/tasks.json +1 -2
  182. package/skills/simplify/SKILL.md +33 -5
  183. package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -196
  184. package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
  185. package/skills/playwright-cli/SKILL.md +0 -420
  186. package/skills/playwright-cli/references/element-attributes.md +0 -23
  187. package/skills/playwright-cli/references/playwright-tests.md +0 -50
  188. package/skills/playwright-cli/references/request-mocking.md +0 -87
  189. package/skills/playwright-cli/references/running-code.md +0 -241
  190. package/skills/playwright-cli/references/session-management.md +0 -273
  191. package/skills/playwright-cli/references/spec-driven-testing.md +0 -311
  192. package/skills/playwright-cli/references/storage-state.md +0 -290
  193. package/skills/playwright-cli/references/test-generation.md +0 -142
  194. package/skills/playwright-cli/references/tracing.md +0 -154
  195. package/skills/playwright-cli/references/video-recording.md +0 -147
  196. package/skills/spec-wiki/SKILL.md +0 -55
  197. package/skills/spec-wiki/evals/evals.json +0 -132
  198. package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +0 -14
  199. package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +0 -4
  200. package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +0 -9
  201. package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +0 -17
  202. package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +0 -12
  203. package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +0 -11
  204. package/skills/spec-wiki/evals/files/focused/src/session.ts +0 -5
  205. package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +0 -17
  206. package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +0 -2
  207. package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +0 -3
  208. package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +0 -18
  209. package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +0 -5
  210. package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +0 -24
  211. package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +0 -7
  212. package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +0 -7
  213. package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +0 -17
  214. package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +0 -3
  215. package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +0 -3
  216. package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +0 -4
  217. package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +0 -9
  218. package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +0 -15
  219. package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +0 -6
  220. package/skills/spec-wiki/evals/maintenance_regression.py +0 -311
  221. package/skills/spec-wiki/evals/retrieval-eval.md +0 -37
  222. package/skills/spec-wiki/evals/retrieval-evals.json +0 -37
  223. package/skills/spec-wiki/evals/trigger-evals.json +0 -94
  224. package/skills/spec-wiki/references/bootstrap.md +0 -100
  225. package/skills/spec-wiki/references/focused-spec.md +0 -117
  226. package/skills/spec-wiki/references/index-schema.md +0 -330
  227. package/skills/spec-wiki/references/maintenance.md +0 -134
  228. package/skills/spec-wiki/references/query.md +0 -74
  229. package/skills/spec-wiki/scripts/spec_wiki.py +0 -2076
  230. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-auth.example.jsonc +0 -0
  231. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-flow.example.jsonc +0 -0
  232. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.LICENSE +0 -0
  233. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.mjs +0 -0
@@ -1,439 +1,54 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import { parse as parseJsonc } from "jsonc-parser";
5
- import { normalizeToolName, parseArgsText } from "../../tool-renderers/utils.js";
6
- const NUDGE_TYPES = ["turn", "iteration", "context-soft", "context-strong"];
7
- export function formatDcpStatsToast(session, options = {}) {
8
- const branch = dcpStatsBranch(session);
9
- const latestState = resolveJournalDcpState(branch);
10
- const manualModeBaseline = options.manualModeBaseline ?? readDcpManualModeBaseline(session, options.configPath);
11
- const stats = collectDcpSessionStats(session, latestState, branch, manualModeBaseline);
12
- const nudgeStats = collectDcpNudgeStats(branch, latestState?.data);
13
- const activeBlocks = stats.activeBlocks ?? 0;
14
- const totalBlocks = stats.totalBlocks ?? stats.activeBlocks ?? 0;
15
- const totalNudgeEvents = nudgeStats.emitted + nudgeStats.reapplied + nudgeStats.upgraded;
16
- const activeAnchors = NUDGE_TYPES.reduce((sum, type) => sum + nudgeStats.activeByType[type], 0);
17
- const lines = [
18
- "DCP Session Statistics:",
19
- ` Tokens saved (estimated): ${fmt(stats.tokensSaved)}`,
20
- ` Total pruning operations: ${fmt(stats.totalPruneCount)}`,
21
- ` Compression blocks active: ${activeBlocks} / ${totalBlocks} total`,
22
- ` Manual mode: ${stats.manualMode === true ? "on" : stats.manualMode === false ? "off" : "unknown"}`,
23
- ` State source: ${formatStateSource(stats.stateSource)}`,
24
- "",
25
- "Nudge telemetry:",
26
- ` Sent: ${fmt(nudgeStats.emitted)} emitted, ${fmt(nudgeStats.reapplied)} reapplied, ${fmt(nudgeStats.upgraded)} upgraded`,
27
- ` By type: ${NUDGE_TYPES.map((type) => `${type}=${fmt(nudgeStats.byType[type])}`).join(", ")}`,
28
- ` Active anchors: ${fmt(activeAnchors)}${activeAnchors > 0 ? ` (${NUDGE_TYPES.map((type) => `${type}=${fmt(nudgeStats.activeByType[type])}`).join(", ")})` : ""}`,
29
- ` Cleared after compress: ${fmt(nudgeStats.clearedEvents)} time${nudgeStats.clearedEvents === 1 ? "" : "s"} (${fmt(nudgeStats.clearedAnchors)} anchor${nudgeStats.clearedAnchors === 1 ? "" : "s"})`,
30
- ` Compliance proxy: ${fmt(nudgeStats.clearedEvents)} compress-after-nudge / ${fmt(totalNudgeEvents)} nudge event${totalNudgeEvents === 1 ? "" : "s"} (${pct(nudgeStats.clearedEvents, totalNudgeEvents)})`,
31
- nudgeStats.last
32
- ? ` Last nudge: ${nudgeStats.last.type} ${nudgeStats.last.event} at ${formatDate(nudgeStats.last.createdAt)} (${formatContextPercent(nudgeStats.last.contextPercent)})`
33
- : " Last nudge: none recorded",
34
- "",
35
- `Context: ${formatContextUsage(stats)}`,
36
- ];
37
- return lines.join("\n");
38
- }
39
- function collectDcpSessionStats(session, latestState, branch, manualModeBaseline) {
40
- const usage = session.getContextUsage();
41
- const stats = {
42
- runs: 0,
43
- tokensSaved: 0,
44
- totalPruneCount: 0,
45
- items: 0,
46
- summaryTokens: 0,
47
- prunedTools: 0,
48
- stateSource: latestState?.source ?? "tool-results",
49
- ...(latestState ? { manualMode: manualModeBaseline } : {}),
50
- ...(usage?.tokens != null ? { contextTokens: usage.tokens } : {}),
51
- ...(usage?.contextWindow != null ? { contextWindow: usage.contextWindow } : {}),
52
- ...(usage?.percent != null ? { contextPercent: usage.percent } : {}),
53
- };
54
- if (latestState)
55
- applyDcpStateStats(stats, latestState.data);
56
- for (const entry of branch) {
57
- if (entry.type !== "message")
58
- continue;
59
- const message = entry.message;
60
- if (message.role !== "toolResult")
61
- continue;
62
- if (normalizeToolName(message.toolName) !== "compress")
63
- continue;
64
- if ("isError" in message && message.isError === true)
65
- continue;
66
- const result = parseToolResultText(message.content);
67
- if (!result)
68
- continue;
69
- if (result.idempotentReplay !== true) {
70
- stats.runs += 1;
71
- stats.tokensSaved += numberValue(result.tokensSaved) ?? 0;
72
- stats.items += numberValue(result.itemCount) ?? sumDefined(numberValue(result.ranges), numberValue(result.messages)) ?? 0;
73
- stats.summaryTokens += numberValue(result.totalSummaryTokens) ?? 0;
74
- }
75
- if (!latestState) {
76
- stats.totalPruneCount = Math.max(stats.totalPruneCount, numberValue(result.totalPruneCount) ?? 0);
77
- const prunedTools = numberValue(result.prunedTools);
78
- if (prunedTools != null)
79
- stats.prunedTools = prunedTools;
80
- const activeBlocks = numberValue(result.activeBlocks);
81
- const totalBlocks = numberValue(result.totalBlocks);
82
- if (activeBlocks != null)
83
- stats.activeBlocks = activeBlocks;
84
- if (totalBlocks != null)
85
- stats.totalBlocks = totalBlocks;
86
- }
87
- const contextTokens = numberValue(result.contextTokens);
88
- const contextWindow = numberValue(result.contextWindow);
89
- const contextPercent = numberValue(result.contextPercent);
90
- if (stats.contextTokens == null && contextTokens != null)
91
- stats.contextTokens = contextTokens;
92
- if (stats.contextWindow == null && contextWindow != null)
93
- stats.contextWindow = contextWindow;
94
- if (stats.contextPercent == null && contextPercent != null)
95
- stats.contextPercent = contextPercent;
96
- }
97
- if (latestState) {
98
- // Journal state owns durable block/prune counts; successful compress tool
99
- // results own operation gain because the journal intentionally stores only
100
- // projection decisions rather than derived accounting snapshots.
101
- stats.totalPruneCount = Math.max(stats.totalPruneCount, (stats.totalBlocks ?? 0) + stats.prunedTools);
102
- }
103
- return stats;
104
- }
105
- function applyDcpStateStats(stats, data) {
106
- stats.tokensSaved = numberValue(data.tokensSaved) ?? stats.tokensSaved;
107
- stats.tokensSaved += numberValue(data.prunedTokensSaved) ?? 0;
108
- stats.totalPruneCount = numberValue(data.totalPruneCount) ?? stats.totalPruneCount;
109
- const blocks = Array.isArray(data.compressionBlocks) ? data.compressionBlocks : undefined;
110
- if (blocks) {
111
- stats.totalBlocks = blocks.length;
112
- stats.activeBlocks = blocks.filter((block) => isRecord(block) && block.active !== false).length;
113
- }
114
- if (Array.isArray(data.prunedToolIds))
115
- stats.prunedTools = data.prunedToolIds.length;
116
- if (typeof data.manualMode === "boolean")
117
- stats.manualMode = data.manualMode;
118
- }
119
- function collectDcpNudgeStats(branch, latestState) {
120
- const stats = {
121
- emitted: 0,
122
- reapplied: 0,
123
- upgraded: 0,
124
- clearedEvents: 0,
125
- clearedAnchors: 0,
126
- byType: { "turn": 0, "iteration": 0, "context-soft": 0, "context-strong": 0 },
127
- activeByType: { "turn": 0, "iteration": 0, "context-soft": 0, "context-strong": 0 },
128
- };
129
- if (latestState)
130
- applyActiveAnchorStats(stats, latestState);
131
- for (const entry of branch) {
132
- const data = customEntryData(entry, "dcp-nudge");
133
- if (!data)
134
- continue;
135
- const event = data.event;
136
- if ((event === "emitted" || event === "reapplied" || event === "upgraded") && isNudgeType(data.type)) {
137
- if (event === "emitted")
138
- stats.emitted += 1;
139
- else if (event === "reapplied")
140
- stats.reapplied += 1;
141
- else
142
- stats.upgraded += 1;
143
- stats.byType[data.type] += 1;
144
- const createdAt = numberValue(data.createdAt);
145
- const contextPercent = typeof data.contextPercent === "number" || data.contextPercent === null ? data.contextPercent : undefined;
146
- if (!stats.last || (createdAt ?? 0) >= (stats.last.createdAt ?? 0)) {
147
- stats.last = {
148
- type: data.type,
149
- event,
150
- ...(createdAt !== undefined ? { createdAt } : {}),
151
- ...(contextPercent !== undefined ? { contextPercent } : {}),
152
- };
153
- }
154
- }
155
- else if (event === "cleared") {
156
- stats.clearedEvents += 1;
157
- stats.clearedAnchors += Math.max(0, numberValue(data.clearedAnchors) ?? 0);
158
- }
159
- }
160
- return stats;
161
- }
162
- function readDcpManualModeBaseline(session, configPath = join(homedir(), ".config", "pi", "pi-tools-suite.jsonc")) {
163
- let enabled = false;
164
- if (!existsSync(configPath))
165
- return enabled;
1
+ import { formatDcpStatistics } from "../../../external/pi-tools-suite/src/dcp/statistics.js";
2
+ /** On-demand TUI path: read the full branch asynchronously without hydrating
3
+ * the lazy presentation manager or blocking terminal input. ACP supplies a
4
+ * complete branch directly to the synchronous formatter instead. */
5
+ export async function loadDcpStatsToast(session) {
6
+ const manager = session.sessionManager;
7
+ if (!manager.readFullBranchEntries)
8
+ return formatDcpStatsToast(session);
9
+ const sessionId = manager.getSessionId?.(), leafId = manager.getLeafId?.();
10
+ const model = session.model;
166
11
  try {
167
- const parsed = parseJsonc(readFileSync(configPath, "utf8"));
168
- if (!isRecord(parsed))
169
- return enabled;
170
- const dcp = parsed.dcp;
171
- if (!isRecord(dcp))
172
- return enabled;
173
- const manualMode = dcp.manualMode;
174
- if (isRecord(manualMode) && typeof manualMode.enabled === "boolean")
175
- enabled = manualMode.enabled;
176
- const overrides = dcp.modelOverrides;
177
- if (!isRecord(overrides))
178
- return enabled;
179
- for (const override of matchingModelOverrides(overrides, dcpModelKeys(session))) {
180
- const overrideManualMode = override.manualMode;
181
- if (isRecord(overrideManualMode) && typeof overrideManualMode.enabled === "boolean") {
182
- enabled = overrideManualMode.enabled;
183
- }
12
+ const branch = await manager.readFullBranchEntries();
13
+ if (session.sessionManager !== manager || manager.getSessionId?.() !== sessionId || manager.getLeafId?.() !== leafId || session.model !== model) {
14
+ throw new Error("Statistics owner changed during read");
184
15
  }
16
+ return formatDcpStatsToast(session, { branch });
185
17
  }
186
18
  catch {
187
- return false;
188
- }
189
- return enabled;
190
- }
191
- function dcpModelKeys(session) {
192
- const model = session.model;
193
- const provider = normalizeModelKey(model?.provider);
194
- const id = normalizeModelKey(model?.id);
195
- return [provider && id ? `${provider}/${id}` : undefined, id].filter((key) => key !== undefined);
196
- }
197
- function matchingModelOverrides(overrides, modelKeys) {
198
- const candidates = modelKeys.map(normalizeModelKey).filter((key, index, all) => key !== undefined && all.indexOf(key) === index);
199
- const exact = new Map();
200
- const wildcard = [];
201
- for (const [rawKey, value] of Object.entries(overrides)) {
202
- const key = normalizeModelKey(rawKey);
203
- if (!key || !isRecord(value))
204
- continue;
205
- if (key.includes("*") || key.includes("?"))
206
- wildcard.push([key, value]);
207
- else
208
- exact.set(key, value);
209
- }
210
- const bare = candidates.filter((candidate) => !candidate.includes("/"));
211
- const full = candidates.filter((candidate) => candidate.includes("/"));
212
- const matches = [];
213
- for (const candidate of bare)
214
- for (const [pattern, value] of wildcard)
215
- if (!pattern.includes("/") && globMatches(pattern, candidate))
216
- matches.push(value);
217
- for (const candidate of bare) {
218
- const value = exact.get(candidate);
219
- if (value)
220
- matches.push(value);
221
- }
222
- for (const candidate of full)
223
- for (const [pattern, value] of wildcard)
224
- if (pattern.includes("/") && globMatches(pattern, candidate))
225
- matches.push(value);
226
- for (const candidate of full) {
227
- const value = exact.get(candidate);
228
- if (value)
229
- matches.push(value);
230
- }
231
- return matches;
232
- }
233
- function normalizeModelKey(value) {
234
- if (typeof value !== "string")
235
- return undefined;
236
- const trimmed = value.trim();
237
- return trimmed || undefined;
238
- }
239
- function globMatches(pattern, candidate) {
240
- let source = "^";
241
- for (const char of pattern) {
242
- if (char === "*")
243
- source += ".*";
244
- else if (char === "?")
245
- source += ".";
246
- else
247
- source += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
248
- }
249
- return new RegExp(`${source}$`).test(candidate);
250
- }
251
- function applyActiveAnchorStats(stats, data) {
252
- stats.activeByType = { "turn": 0, "iteration": 0, "context-soft": 0, "context-strong": 0 };
253
- const anchors = Array.isArray(data.nudgeAnchors) ? data.nudgeAnchors : [];
254
- for (const anchor of anchors) {
255
- if (isRecord(anchor) && isNudgeType(anchor.type))
256
- stats.activeByType[anchor.type] += 1;
257
- }
258
- const last = isRecord(data.lastNudge) ? data.lastNudge : undefined;
259
- if (last && isNudgeType(last.type) && !stats.last) {
260
- const contextPercent = numberValue(last.contextPercent);
261
- const createdAt = numberValue(last.createdAt);
262
- stats.last = {
263
- type: last.type,
264
- event: "emitted",
265
- ...(createdAt !== undefined ? { createdAt } : {}),
266
- ...(contextPercent !== undefined ? { contextPercent: contextPercent * 100 } : {}),
267
- };
268
- }
269
- }
270
- function resolveJournalDcpState(branch) {
271
- const blocks = new Map();
272
- const pruned = new Map();
273
- let initialized = false;
274
- let manualMode;
275
- let nudgeAnchors = [];
276
- let lastNudge;
277
- for (const entry of branch) {
278
- const operation = customEntryData(entry, "dcp-journal");
279
- if (!operation || operation.schemaVersion !== 1)
280
- continue;
281
- if (operation.kind === "init") {
282
- initialized = true;
283
- continue;
284
- }
285
- if (!initialized || operation.kind !== "delta")
286
- continue;
287
- if (Array.isArray(operation.blocks)) {
288
- for (const raw of operation.blocks) {
289
- if (!isRecord(raw) || typeof raw.id !== "number")
290
- continue;
291
- blocks.set(raw.id, { ...raw });
292
- }
293
- }
294
- if (Array.isArray(operation.blockStates)) {
295
- for (const raw of operation.blockStates) {
296
- if (!isRecord(raw) || typeof raw.id !== "number")
297
- continue;
298
- const block = blocks.get(raw.id);
299
- if (!block)
300
- continue;
301
- if (typeof raw.active === "boolean")
302
- block.active = raw.active;
303
- if (typeof raw.deactivatedReason === "string")
304
- block.deactivatedReason = raw.deactivatedReason;
305
- }
19
+ let usage;
20
+ try {
21
+ usage = session.getContextUsage();
306
22
  }
307
- if (Array.isArray(operation.prunedTools)) {
308
- for (const raw of operation.prunedTools) {
309
- if (!isRecord(raw) || typeof raw.toolCallId !== "string")
310
- continue;
311
- pruned.set(raw.toolCallId, {
312
- ...(typeof raw.reason === "string" ? { reason: raw.reason } : {}),
313
- tokenEstimate: Math.max(0, numberValue(raw.tokenEstimate) ?? 0),
314
- });
315
- }
316
- }
317
- if (typeof operation.manualMode === "boolean")
318
- manualMode = operation.manualMode;
319
- if (Array.isArray(operation.nudgeAnchors))
320
- nudgeAnchors = operation.nudgeAnchors;
321
- if (Object.prototype.hasOwnProperty.call(operation, "lastNudge"))
322
- lastNudge = operation.lastNudge ?? undefined;
23
+ catch { /* Unknown. */ }
24
+ return formatDcpStatistics({ model: session.model, usage, historyStatus: "unavailable" });
323
25
  }
324
- if (!initialized)
325
- return undefined;
326
- return {
327
- source: "journal",
328
- data: {
329
- compressionBlocks: [...blocks.values()],
330
- prunedToolIds: [...pruned.keys()],
331
- prunedTokensSaved: [...pruned.values()].reduce((sum, item) => sum + item.tokenEstimate, 0),
332
- manualMode,
333
- nudgeAnchors,
334
- lastNudge,
335
- },
336
- };
337
26
  }
338
- function dcpStatsBranch(session) {
339
- const manager = session.sessionManager;
27
+ /** TUI and ACP use the same passive report as /dcp stats. Opening this view
28
+ * never re-runs pruning, mutates history, or launches a model. */
29
+ export function formatDcpStatsToast(session, options = {}) {
30
+ let branch = [];
31
+ let historyStatus = "full";
340
32
  try {
341
- const full = manager.readFullBranchEntriesSync?.();
342
- if (Array.isArray(full))
343
- return full;
33
+ if (options.branch)
34
+ branch = options.branch;
35
+ else {
36
+ const manager = session.sessionManager;
37
+ branch = manager.readFullBranchEntriesSync ? manager.readFullBranchEntriesSync() : manager.getBranch();
38
+ }
39
+ if (!Array.isArray(branch) || branch[0]?.parentId != null) {
40
+ historyStatus = "unavailable";
41
+ branch = [];
42
+ }
344
43
  }
345
44
  catch {
346
- // Fall back to the manager's currently materialized branch below.
45
+ // A broken full reader is not permission to substitute the UI's lazy tail.
46
+ historyStatus = "unavailable";
347
47
  }
48
+ let usage;
348
49
  try {
349
- const branch = manager.getBranch();
350
- return Array.isArray(branch) ? branch : [];
351
- }
352
- catch {
353
- return [];
50
+ usage = session.getContextUsage();
354
51
  }
355
- }
356
- function formatStateSource(source) {
357
- if (source === "journal")
358
- return "session journal";
359
- return "compress tool results";
360
- }
361
- function customEntryData(entry, customType) {
362
- if (!isRecord(entry) || entry.type !== "custom" || entry.customType !== customType)
363
- return undefined;
364
- return isRecord(entry.data) ? entry.data : undefined;
365
- }
366
- function parseToolResultText(content) {
367
- const parsed = parseArgsText(textContent(content));
368
- return isRecord(parsed) ? parsed : undefined;
369
- }
370
- function textContent(content) {
371
- if (typeof content === "string")
372
- return content;
373
- if (!Array.isArray(content))
374
- return "";
375
- return content
376
- .map((part) => isRecord(part) && typeof part.text === "string" ? part.text : "")
377
- .filter(Boolean)
378
- .join("\n");
379
- }
380
- function formatContextUsage(stats) {
381
- const percent = stats.contextPercent;
382
- const tokens = stats.contextTokens;
383
- const window = stats.contextWindow;
384
- const percentText = percent != null ? formatPercent(percent) : "unknown";
385
- if (tokens != null && window != null)
386
- return `${percentText} (${formatCompactNumber(tokens)}/${formatCompactNumber(window)})`;
387
- if (tokens != null)
388
- return `${formatCompactNumber(tokens)} tokens`;
389
- if (window != null)
390
- return `${percentText} of ${formatCompactNumber(window)}`;
391
- return percentText;
392
- }
393
- function isNudgeType(value) {
394
- return typeof value === "string" && NUDGE_TYPES.includes(value);
395
- }
396
- function fmt(n) {
397
- return Math.round(n).toLocaleString();
398
- }
399
- function pct(numerator, denominator) {
400
- if (denominator <= 0)
401
- return "n/a";
402
- return `${((numerator / denominator) * 100).toFixed(1)}%`;
403
- }
404
- function formatDate(ts) {
405
- if (typeof ts !== "number" || !Number.isFinite(ts) || ts <= 0)
406
- return "unknown time";
407
- return new Date(ts).toLocaleString();
408
- }
409
- function formatContextPercent(value) {
410
- if (value === null)
411
- return "unknown context";
412
- if (typeof value !== "number" || !Number.isFinite(value))
413
- return "unknown context";
414
- return `${value.toFixed(1)}% context`;
415
- }
416
- function numberValue(value) {
417
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
418
- }
419
- function sumDefined(...values) {
420
- const defined = values.filter((value) => value != null);
421
- return defined.length > 0 ? defined.reduce((sum, value) => sum + value, 0) : undefined;
422
- }
423
- function formatCompactNumber(value) {
424
- const abs = Math.abs(value);
425
- if (abs >= 1_000_000)
426
- return `${trimDecimal(value / 1_000_000)}M`;
427
- if (abs >= 1_000)
428
- return `${trimDecimal(value / 1_000)}K`;
429
- return Math.round(value).toLocaleString();
430
- }
431
- function formatPercent(value) {
432
- return `${trimDecimal(value)}%`;
433
- }
434
- function trimDecimal(value) {
435
- return value.toFixed(1).replace(/\.0$/, "");
436
- }
437
- function isRecord(value) {
438
- return typeof value === "object" && value !== null && !Array.isArray(value);
52
+ catch { /* Explicitly unknown. */ }
53
+ return formatDcpStatistics({ branch, historyStatus, model: session.model, usage });
439
54
  }
@@ -2,7 +2,7 @@ import { type Theme } from "../../theme.js";
2
2
  import { type ModelColorsConfig } from "../../config.js";
3
3
  import type { PopupMenu } from "../../ui.js";
4
4
  import type { ScreenStyler } from "../screen/screen-styler.js";
5
- import type { Entry, ModelMenuValue, PixMenuItem, PixMenuOptions, QueueMessageMenuValue, RenderedLine, ResumeMenuValue, SlashCommand, ThinkingMenuValue, UserMessageJumpMenuValue, UserMessageMenuValue } from "../types.js";
5
+ import type { Entry, ModelMenuValue, ModelThinkingMenuState, PixMenuItem, PixMenuOptions, QueueMessageMenuValue, RenderedLine, ResumeMenuValue, SlashCommand, UserMessageJumpMenuValue, UserMessageMenuValue } from "../types.js";
6
6
  import type { AgentSession } from "@earendil-works/pi-coding-agent";
7
7
  export type PopupMenuRendererHost = {
8
8
  readonly theme: Theme;
@@ -24,12 +24,12 @@ export declare class PopupMenuRenderer {
24
24
  overlayPlainText(line: RenderedLine, width: number): string;
25
25
  renderUserMessageMenu(width: number, menu: PopupMenu<UserMessageMenuValue>): RenderedLine[];
26
26
  renderSlashCommandMenu(width: number, menu: PopupMenu<SlashCommand>): RenderedLine[];
27
- renderModelMenu(width: number, menu: PopupMenu<ModelMenuValue>): RenderedLine[];
28
- renderThinkingMenu(width: number, menu: PopupMenu<ThinkingMenuValue>): RenderedLine[];
27
+ renderModelMenu(width: number, menu: PopupMenu<ModelMenuValue>, state: ModelThinkingMenuState): RenderedLine[];
29
28
  renderResumeMenu(width: number, menu: PopupMenu<ResumeMenuValue>, state: {
30
29
  directQuery: string;
31
30
  allSessionsLoaded: boolean;
32
31
  loadedSessionCount: number;
32
+ draft: boolean;
33
33
  }): RenderedLine[];
34
34
  renderUserMessageJumpMenu(width: number, menu: PopupMenu<UserMessageJumpMenuValue>, directQuery: string): RenderedLine[];
35
35
  renderQueueMessageMenu(width: number, menu: PopupMenu<QueueMessageMenuValue>): RenderedLine[];
@@ -40,10 +40,8 @@ export declare class PopupMenuRenderer {
40
40
  private labelDescriptionText;
41
41
  private userMessageActionForeground;
42
42
  private selectableItemVariant;
43
- private thinkingMenuItemSegments;
44
43
  private modelMenuItemSegments;
45
44
  private modelMenuItemColor;
46
- private availableThinkingLevels;
47
45
  private queueMessageItemVariant;
48
46
  private sdkItemVariant;
49
47
  private sdkMenuItemSegments;
@@ -1,7 +1,7 @@
1
1
  import { colorLine } from "../../theme.js";
2
2
  import { stringDisplayWidth } from "../../terminal-width.js";
3
3
  import { resolveColor, resolveModelColor } from "../../config.js";
4
- import { SLASH_COMMAND_DESCRIPTION_COLUMN, THINKING_LEVELS, } from "../constants.js";
4
+ import { SLASH_COMMAND_DESCRIPTION_COLUMN, } from "../constants.js";
5
5
  import { APP_ICONS } from "../icons.js";
6
6
  import { ellipsizeDisplay, padOrTrimPlain, sanitizeText } from "./render-text.js";
7
7
  import { modelProviderThemeColor, thinkingLevelThemeColor } from "./status-line-renderer.js";
@@ -96,8 +96,8 @@ export class PopupMenuRenderer {
96
96
  }
97
97
  return lines;
98
98
  }
99
- renderModelMenu(width, menu) {
100
- const lines = [this.popupMenuHeader("Select model", width)];
99
+ renderModelMenu(width, menu, state) {
100
+ const lines = [this.popupMenuHeader(state.visibilityMode ? "Manage visible models" : "Select model & thinking", width)];
101
101
  const visibleItems = menu.visibleItems();
102
102
  if (!this.hasPopupActionItems(menu.items)) {
103
103
  lines.push({
@@ -106,37 +106,55 @@ export class PopupMenuRenderer {
106
106
  });
107
107
  }
108
108
  for (const item of visibleItems) {
109
- const marker = item.selected ? "▶ " : " ";
110
- const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
109
+ const selectionMarker = item.selected ? "▶ " : " ";
110
+ const visibilityMarker = state.visibilityMode ? `${item.value.visible === false ? "○" : APP_ICONS.check} ` : "";
111
+ const marker = `${selectionMarker}${visibilityMarker}`;
112
+ const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - marker.length)}`;
111
113
  lines.push({
112
114
  text,
113
115
  variant: this.selectableItemVariant(item.value),
114
- segments: [...this.modelMenuItemSegments(item.value), ...this.itemHighlightSegments(item, text)],
116
+ segments: [
117
+ ...this.modelMenuItemSegments(item.value, marker.length),
118
+ ...this.itemHighlightSegments(item, text, marker.length),
119
+ ],
115
120
  target: { kind: "popup-menu", index: item.index },
116
121
  });
117
122
  }
118
- return lines;
119
- }
120
- renderThinkingMenu(width, menu) {
121
- const lines = [this.popupMenuHeader("Thinking level", width)];
122
- const visibleItems = menu.visibleItems();
123
- if (!this.hasPopupActionItems(menu.items)) {
124
- lines.push({ text: " No matching thinking levels", variant: "muted" });
123
+ if (state.visibilityMode && menu.selectedItem()) {
124
+ const selected = menu.selectedItem().value;
125
+ lines.push({
126
+ text: selected.current
127
+ ? " Current model stays visible until another model is selected"
128
+ : " Enter toggles whether this model appears in model pickers",
129
+ variant: "muted",
130
+ });
131
+ lines.push({
132
+ text: " Clear all",
133
+ variant: "error",
134
+ target: { kind: "model-visibility-clear" },
135
+ });
136
+ lines.push({ text: " ↑/↓ model · Enter show/hide · Shift+Tab select", variant: "muted" });
125
137
  }
126
- for (const item of visibleItems) {
127
- const marker = item.selected ? "▶ " : " ";
128
- const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
138
+ else if (menu.selectedItem()) {
139
+ const thinkingPrefix = " Thinking ← ";
140
+ const thinkingSuffix = " →";
129
141
  lines.push({
130
- text,
131
- variant: this.selectableItemVariant(item.value),
132
- segments: this.thinkingMenuItemSegments(item.value),
133
- target: { kind: "popup-menu", index: item.index },
142
+ text: `${thinkingPrefix}${state.thinkingLevel}${thinkingSuffix}`,
143
+ variant: "normal",
144
+ segments: [{
145
+ start: thinkingPrefix.length,
146
+ end: thinkingPrefix.length + state.thinkingLevel.length,
147
+ foreground: thinkingLevelThemeColor(state.thinkingLevel, this.host.theme.colors, state.availableThinkingLevels),
148
+ bold: true,
149
+ }],
134
150
  });
151
+ lines.push({ text: " ↑/↓ model · ←/→ thinking · Enter apply · Shift+Tab manage", variant: "muted" });
135
152
  }
136
153
  return lines;
137
154
  }
138
155
  renderResumeMenu(width, menu, state) {
139
- const title = this.host.resumeLoading ? `Resume session ${APP_ICONS.timerSand}` : "Resume session";
156
+ const titleBase = state.draft ? "Open a conversation" : "Resume session";
157
+ const title = this.host.resumeLoading ? `${titleBase} ${APP_ICONS.timerSand}` : titleBase;
140
158
  const lines = [this.popupMenuHeader(title, width)];
141
159
  const visibleItems = menu.visibleItems();
142
160
  if (!this.host.resumeLoading && !this.hasPopupActionItems(menu.items)) {
@@ -261,16 +279,7 @@ export class PopupMenuRenderer {
261
279
  selectableItemVariant(value) {
262
280
  return value.current ? "muted" : "normal";
263
281
  }
264
- thinkingMenuItemSegments(value) {
265
- const markerOffset = 2; // "▶ " or " "
266
- return [{
267
- start: markerOffset,
268
- end: markerOffset + value.level.length,
269
- foreground: thinkingLevelThemeColor(value.level, this.host.theme.colors, this.availableThinkingLevels()),
270
- }];
271
- }
272
- modelMenuItemSegments(value) {
273
- const markerOffset = 2; // "▶ " or " "
282
+ modelMenuItemSegments(value, markerOffset = 2) {
274
283
  return [{
275
284
  start: markerOffset,
276
285
  end: markerOffset + value.ref.length,
@@ -285,10 +294,6 @@ export class PopupMenuRenderer {
285
294
  ? resolveColor(configuredColor, this.host.theme.colors)
286
295
  : modelProviderThemeColor(value.model.provider, this.host.theme.colors);
287
296
  }
288
- availableThinkingLevels() {
289
- const levels = this.host.session?.getAvailableThinkingLevels();
290
- return Array.isArray(levels) && levels.length > 0 ? levels.map(String) : [...THINKING_LEVELS];
291
- }
292
297
  queueMessageItemVariant(value) {
293
298
  return value === "cancel" ? "error" : "normal";
294
299
  }
@@ -301,8 +306,8 @@ export class PopupMenuRenderer {
301
306
  ...this.descriptionHighlightSegments(item.description, item.descriptionHighlightRanges ?? item.value.descriptionHighlightRanges ?? [], text),
302
307
  ];
303
308
  }
304
- itemHighlightSegments(item, text) {
305
- return this.highlightSegments(item.labelHighlightRanges ?? [], text, 2);
309
+ itemHighlightSegments(item, text, markerOffset = 2) {
310
+ return this.highlightSegments(item.labelHighlightRanges ?? [], text, markerOffset);
306
311
  }
307
312
  highlightSegments(ranges, text, markerOffset) {
308
313
  if (ranges.length === 0)