@oh-my-pi/pi-coding-agent 17.2.10 → 17.2.12

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 (247) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-k9ghy5sn.md} +53 -0
  3. package/dist/cli.js +10775 -10762
  4. package/dist/types/advisor/runtime.d.ts +1 -1
  5. package/dist/types/capability/mcp.d.ts +11 -0
  6. package/dist/types/capability/skill.d.ts +6 -0
  7. package/dist/types/cli/command-help.d.ts +3 -0
  8. package/dist/types/commands/share.d.ts +25 -0
  9. package/dist/types/commit/agentic/index.d.ts +3 -1
  10. package/dist/types/commit/execute.d.ts +32 -0
  11. package/dist/types/commit/index.d.ts +2 -1
  12. package/dist/types/commit/pipeline.d.ts +7 -1
  13. package/dist/types/config/custom-models.d.ts +31 -0
  14. package/dist/types/config/model-config-values.d.ts +19 -0
  15. package/dist/types/config/model-patch.d.ts +93 -0
  16. package/dist/types/config/model-provider-discovery.d.ts +51 -0
  17. package/dist/types/config/model-registry.d.ts +9 -52
  18. package/dist/types/config/settings-schema.d.ts +1 -34
  19. package/dist/types/config/settings.d.ts +5 -3
  20. package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
  21. package/dist/types/discovery/agent-plugin-format.d.ts +136 -0
  22. package/dist/types/discovery/agent-plugins.d.ts +1 -0
  23. package/dist/types/discovery/contained-path.d.ts +33 -0
  24. package/dist/types/discovery/index.d.ts +1 -0
  25. package/dist/types/eval/executor-base.d.ts +8 -2
  26. package/dist/types/eval/kernel-session-registry.d.ts +60 -0
  27. package/dist/types/export/share.d.ts +1 -1
  28. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  29. package/dist/types/extensibility/extensions/loader.d.ts +7 -0
  30. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -2
  32. package/dist/types/extensibility/skills.d.ts +5 -0
  33. package/dist/types/lsp/diagnostics.d.ts +96 -0
  34. package/dist/types/lsp/index.d.ts +7 -128
  35. package/dist/types/lsp/servers.d.ts +72 -0
  36. package/dist/types/lsp/tool.d.ts +42 -0
  37. package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
  38. package/dist/types/lsp/writethrough.d.ts +33 -0
  39. package/dist/types/mcp/transports/header-policy.d.ts +46 -0
  40. package/dist/types/mcp/types.d.ts +15 -0
  41. package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
  42. package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
  43. package/dist/types/modes/components/agent-hub.d.ts +2 -0
  44. package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
  45. package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
  46. package/dist/types/modes/components/custom-editor.d.ts +1 -2
  47. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  48. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  49. package/dist/types/modes/theme/color.d.ts +19 -0
  50. package/dist/types/modes/theme/loader.d.ts +19 -0
  51. package/dist/types/modes/theme/schema.d.ts +175 -0
  52. package/dist/types/modes/theme/symbols.d.ts +28 -0
  53. package/dist/types/modes/theme/theme-class.d.ts +244 -0
  54. package/dist/types/modes/theme/theme.d.ts +9 -280
  55. package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
  56. package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
  57. package/dist/types/registry/agent-registry.d.ts +1 -3
  58. package/dist/types/secrets/index.d.ts +26 -2
  59. package/dist/types/secrets/message-transform.d.ts +40 -0
  60. package/dist/types/secrets/obfuscator.d.ts +0 -108
  61. package/dist/types/secrets/placeholder-scan.d.ts +95 -0
  62. package/dist/types/secrets/placeholder.d.ts +94 -0
  63. package/dist/types/secrets/replacement.d.ts +82 -0
  64. package/dist/types/session/agent-session-events.d.ts +2 -2
  65. package/dist/types/session/agent-session-types.d.ts +6 -0
  66. package/dist/types/session/agent-session.d.ts +2 -2
  67. package/dist/types/session/messages.d.ts +1 -0
  68. package/dist/types/session/prewalk.d.ts +3 -1
  69. package/dist/types/session/session-handoff.d.ts +3 -3
  70. package/dist/types/session/session-manager.d.ts +32 -0
  71. package/dist/types/session/session-provider-boundary.d.ts +1 -1
  72. package/dist/types/session/session-tools.d.ts +8 -0
  73. package/dist/types/session/turn-recovery.d.ts +9 -4
  74. package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
  75. package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
  76. package/dist/types/slash-commands/builtin-control.d.ts +2 -0
  77. package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
  78. package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
  79. package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
  80. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  81. package/dist/types/slash-commands/builtin-session.d.ts +2 -0
  82. package/dist/types/task/index.d.ts +2 -0
  83. package/dist/types/tools/gh-common.d.ts +69 -0
  84. package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
  85. package/dist/types/tools/gh-pr-diff.d.ts +102 -0
  86. package/dist/types/tools/gh-run-watch.d.ts +78 -0
  87. package/dist/types/tools/gh-search.d.ts +61 -0
  88. package/dist/types/tools/gh-types.d.ts +340 -0
  89. package/dist/types/tools/gh-view.d.ts +88 -0
  90. package/dist/types/tools/gh.d.ts +4 -201
  91. package/dist/types/tools/read-archive.d.ts +16 -0
  92. package/dist/types/tools/read-format.d.ts +104 -0
  93. package/dist/types/tools/read-path-resolution.d.ts +19 -0
  94. package/dist/types/tools/read-pdf-images.d.ts +12 -0
  95. package/dist/types/tools/read-renderer.d.ts +24 -0
  96. package/dist/types/tools/read-selector.d.ts +27 -0
  97. package/dist/types/tools/read-sqlite.d.ts +16 -0
  98. package/dist/types/tools/read-summary.d.ts +19 -0
  99. package/dist/types/tools/read.d.ts +1 -23
  100. package/dist/types/tools/shell-tokenize.d.ts +18 -1
  101. package/dist/types/utils/changelog.d.ts +0 -5
  102. package/package.json +13 -13
  103. package/scripts/legacy-pi-virtual-module.ts +4 -1
  104. package/src/advisor/runtime.ts +12 -7
  105. package/src/capability/mcp.ts +11 -0
  106. package/src/capability/skill.ts +6 -0
  107. package/src/cli/command-help.ts +4 -0
  108. package/src/cli/gallery-cli.ts +1 -1
  109. package/src/cli-commands.ts +5 -0
  110. package/src/commands/commit.ts +16 -3
  111. package/src/commands/share.ts +71 -0
  112. package/src/commit/agentic/index.ts +39 -21
  113. package/src/commit/execute.ts +56 -0
  114. package/src/commit/index.ts +2 -1
  115. package/src/commit/pipeline.ts +20 -7
  116. package/src/config/custom-models.ts +188 -0
  117. package/src/config/model-config-values.ts +128 -0
  118. package/src/config/model-discovery.ts +1 -1
  119. package/src/config/model-patch.ts +252 -0
  120. package/src/config/model-provider-discovery.ts +132 -0
  121. package/src/config/model-registry.ts +84 -704
  122. package/src/config/settings-schema.ts +2 -33
  123. package/src/config/settings.ts +53 -3
  124. package/src/debug/raw-sse-buffer.ts +20 -0
  125. package/src/discovery/agent-plugin-format.ts +551 -0
  126. package/src/discovery/agent-plugins.ts +341 -0
  127. package/src/discovery/claude-plugins.ts +21 -5
  128. package/src/discovery/contained-path.ts +75 -0
  129. package/src/discovery/helpers.ts +23 -9
  130. package/src/discovery/index.ts +1 -0
  131. package/src/discovery/omp-plugins.ts +20 -7
  132. package/src/edit/hashline/diff.ts +5 -1
  133. package/src/eval/executor-base.ts +39 -6
  134. package/src/eval/jl/executor.ts +82 -371
  135. package/src/eval/kernel-session-registry.ts +398 -0
  136. package/src/eval/py/executor.ts +53 -261
  137. package/src/eval/rb/executor.ts +36 -279
  138. package/src/exec/non-interactive-env.ts +1 -0
  139. package/src/export/share.ts +2 -1
  140. package/src/extensibility/custom-tools/types.ts +2 -2
  141. package/src/extensibility/extensions/loader.ts +78 -14
  142. package/src/extensibility/extensions/runner.ts +6 -0
  143. package/src/extensibility/extensions/types.ts +12 -0
  144. package/src/extensibility/plugins/marketplace/manager.ts +2 -1
  145. package/src/extensibility/shared-events.ts +3 -2
  146. package/src/extensibility/skills.ts +9 -0
  147. package/src/internal-urls/skill-protocol.ts +14 -0
  148. package/src/internal-urls/vault-protocol.ts +2 -2
  149. package/src/launch/client.ts +11 -1
  150. package/src/launch/protocol.ts +7 -2
  151. package/src/lsp/diagnostics.ts +516 -0
  152. package/src/lsp/index.ts +20 -2819
  153. package/src/lsp/servers.ts +296 -0
  154. package/src/lsp/tool.ts +1352 -0
  155. package/src/lsp/workspace-diagnostics.ts +170 -0
  156. package/src/lsp/writethrough.ts +561 -0
  157. package/src/main.ts +3 -2
  158. package/src/mcp/config.ts +3 -0
  159. package/src/mcp/manager.ts +12 -9
  160. package/src/mcp/transports/header-policy.ts +95 -0
  161. package/src/mcp/transports/http.ts +35 -52
  162. package/src/mcp/transports/sse.ts +20 -27
  163. package/src/mcp/types.ts +15 -0
  164. package/src/modes/acp/acp-agent.ts +15 -4
  165. package/src/modes/components/agent-dashboard.ts +2 -0
  166. package/src/modes/components/agent-hub-projection.ts +2 -2
  167. package/src/modes/components/agent-hub-renderer.ts +3 -7
  168. package/src/modes/components/agent-hub.ts +5 -0
  169. package/src/modes/components/agent-transcript-viewer.ts +3 -0
  170. package/src/modes/components/chat-transcript-builder.ts +3 -0
  171. package/src/modes/components/custom-editor.ts +0 -9
  172. package/src/modes/components/status-line/component.ts +1 -0
  173. package/src/modes/components/status-line/segments.ts +6 -4
  174. package/src/modes/components/status-line/types.ts +2 -0
  175. package/src/modes/components/tool-execution.ts +13 -16
  176. package/src/modes/components/tree-selector.ts +62 -3
  177. package/src/modes/controllers/command-controller.ts +8 -2
  178. package/src/modes/controllers/event-controller.ts +15 -15
  179. package/src/modes/controllers/input-controller.ts +20 -2
  180. package/src/modes/controllers/selector-controller.ts +1 -0
  181. package/src/modes/theme/color.ts +133 -0
  182. package/src/modes/theme/loader.ts +178 -0
  183. package/src/modes/theme/schema.ts +263 -0
  184. package/src/modes/theme/symbols.ts +1000 -0
  185. package/src/modes/theme/theme-class.ts +611 -0
  186. package/src/modes/theme/theme.ts +27 -2453
  187. package/src/modes/theme/tui-adapters.ts +279 -0
  188. package/src/modes/utils/transcript-render-helpers.ts +4 -2
  189. package/src/modes/utils/ui-helpers.ts +1 -0
  190. package/src/registry/agent-registry.ts +2 -2
  191. package/src/sdk.ts +6 -49
  192. package/src/secrets/index.ts +51 -6
  193. package/src/secrets/message-transform.ts +287 -0
  194. package/src/secrets/obfuscator.ts +39 -1303
  195. package/src/secrets/placeholder-scan.ts +506 -0
  196. package/src/secrets/placeholder.ts +309 -0
  197. package/src/secrets/replacement.ts +216 -0
  198. package/src/session/agent-session-events.ts +2 -2
  199. package/src/session/agent-session-types.ts +6 -0
  200. package/src/session/agent-session.ts +146 -21
  201. package/src/session/indexed-session-storage.ts +7 -2
  202. package/src/session/messages.ts +1 -0
  203. package/src/session/prewalk.ts +57 -17
  204. package/src/session/session-advisors.ts +32 -34
  205. package/src/session/session-context.ts +3 -5
  206. package/src/session/session-handoff.ts +39 -16
  207. package/src/session/session-manager.ts +67 -3
  208. package/src/session/session-provider-boundary.ts +3 -6
  209. package/src/session/session-tools.ts +37 -3
  210. package/src/session/todo-tracker.ts +11 -1
  211. package/src/session/turn-recovery.ts +115 -84
  212. package/src/slash-commands/builtin-collaboration.ts +504 -0
  213. package/src/slash-commands/builtin-completions.ts +291 -0
  214. package/src/slash-commands/builtin-control.ts +78 -0
  215. package/src/slash-commands/builtin-lifecycle.ts +506 -0
  216. package/src/slash-commands/builtin-marketplace.ts +567 -0
  217. package/src/slash-commands/builtin-modes.ts +518 -0
  218. package/src/slash-commands/builtin-registry.ts +21 -2996
  219. package/src/slash-commands/builtin-session.ts +592 -0
  220. package/src/task/executor.ts +17 -14
  221. package/src/task/index.ts +21 -6
  222. package/src/tools/bash-skill-urls.ts +15 -0
  223. package/src/tools/bash.ts +10 -11
  224. package/src/tools/debug.ts +1 -1
  225. package/src/tools/gh-common.ts +288 -0
  226. package/src/tools/gh-pr-checkout.ts +679 -0
  227. package/src/tools/gh-pr-diff.ts +473 -0
  228. package/src/tools/gh-run-watch.ts +1015 -0
  229. package/src/tools/gh-search.ts +500 -0
  230. package/src/tools/gh-types.ts +379 -0
  231. package/src/tools/gh-view.ts +612 -0
  232. package/src/tools/gh.ts +109 -3821
  233. package/src/tools/jtd-to-json-schema.ts +123 -21
  234. package/src/tools/read-archive.ts +209 -0
  235. package/src/tools/read-format.ts +593 -0
  236. package/src/tools/read-path-resolution.ts +36 -0
  237. package/src/tools/read-pdf-images.ts +250 -0
  238. package/src/tools/read-renderer.ts +289 -0
  239. package/src/tools/read-selector.ts +84 -0
  240. package/src/tools/read-sqlite.ts +215 -0
  241. package/src/tools/read-summary.ts +199 -0
  242. package/src/tools/read.ts +82 -1824
  243. package/src/tools/shell-tokenize.ts +99 -1
  244. package/src/utils/changelog.ts +1 -1
  245. package/src/utils/title-generator.ts +3 -1
  246. package/src/web/search/providers/exa.ts +1 -1
  247. package/src/web/search/providers/zai.ts +12 -3
@@ -0,0 +1,279 @@
1
+ import {
2
+ type HighlightColors as NativeHighlightColors,
3
+ highlightCode as nativeHighlightCode,
4
+ supportsLanguage as nativeSupportsLanguage,
5
+ } from "@oh-my-pi/pi-natives";
6
+ import type { EditorTheme, MarkdownTheme, SelectListTheme, SettingsListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
7
+ import chalk from "@oh-my-pi/pi-utils/chalk";
8
+ import { LRUCache } from "@oh-my-pi/pi-utils/lru";
9
+ import { resolveMermaidAscii } from "./mermaid-cache";
10
+ import { theme } from "./theme";
11
+ import type { Theme } from "./theme-class";
12
+
13
+ // ============================================================================
14
+ // TUI Helpers
15
+ // ============================================================================
16
+
17
+ let cachedHighlightColorsFor: Theme | undefined;
18
+ let cachedHighlightColors: NativeHighlightColors | undefined;
19
+
20
+ function getHighlightColors(t: Theme): NativeHighlightColors {
21
+ if (cachedHighlightColorsFor !== t || !cachedHighlightColors) {
22
+ cachedHighlightColorsFor = t;
23
+ cachedHighlightColors = {
24
+ comment: t.getFgAnsi("syntaxComment"),
25
+ keyword: t.getFgAnsi("syntaxKeyword"),
26
+ function: t.getFgAnsi("syntaxFunction"),
27
+ variable: t.getFgAnsi("syntaxVariable"),
28
+ string: t.getFgAnsi("syntaxString"),
29
+ number: t.getFgAnsi("syntaxNumber"),
30
+ type: t.getFgAnsi("syntaxType"),
31
+ operator: t.getFgAnsi("syntaxOperator"),
32
+ punctuation: t.getFgAnsi("syntaxPunctuation"),
33
+ inserted: t.getFgAnsi("toolDiffAdded"),
34
+ deleted: t.getFgAnsi("toolDiffRemoved"),
35
+ };
36
+ }
37
+ return cachedHighlightColors;
38
+ }
39
+
40
+ /**
41
+ * Memoized native syntax highlight. Returns the joined ANSI string, or `null`
42
+ * when the native tokenizer throws so callers can apply their own fallback.
43
+ *
44
+ * Keyed on `(lang, code)` and reset whenever the active `theme` instance
45
+ * changes — the ANSI colors are baked into the highlighted output, so a theme
46
+ * switch (which always reassigns `theme`) must invalidate every entry.
47
+ *
48
+ * Why this exists: animated tool blocks (eval/bash) repaint their box on every
49
+ * ~33ms border-shimmer frame, and markdown re-lexes on every streamed delta.
50
+ * Without memoization each frame can re-tokenize an unchanged code body through
51
+ * the Rust FFI — ~26ms for 100 lines, ~40ms for 150 — consuming or overrunning
52
+ * the 33ms frame budget and starving the spinner/render timers (the "TUI freeze").
53
+ */
54
+ const HIGHLIGHT_CACHE_MAX = 256;
55
+ const highlightCache = new LRUCache<string, string>({ max: HIGHLIGHT_CACHE_MAX });
56
+ let highlightCacheTheme: Theme | undefined;
57
+
58
+ function highlightCached(code: string, validLang: string | undefined, highlightTheme: Theme): string | null {
59
+ if (highlightCacheTheme !== highlightTheme) {
60
+ highlightCache.clear();
61
+ highlightCacheTheme = highlightTheme;
62
+ }
63
+ const key = `${validLang ?? ""}\x00${code}`;
64
+ const hit = highlightCache.get(key);
65
+ if (hit !== undefined) {
66
+ return hit;
67
+ }
68
+ let highlighted: string;
69
+ try {
70
+ highlighted = nativeHighlightCode(code, validLang, getHighlightColors(highlightTheme));
71
+ } catch {
72
+ return null;
73
+ }
74
+ highlightCache.set(key, highlighted);
75
+ return highlighted;
76
+ }
77
+
78
+ /**
79
+ * Highlight code with syntax coloring based on file extension or language.
80
+ * Returns array of highlighted lines.
81
+ */
82
+ export function highlightCode(code: string, lang?: string, highlightTheme: Theme = theme): string[] {
83
+ const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
84
+ const highlighted = highlightCached(code, validLang, highlightTheme);
85
+ // Always return a fresh array: callers (e.g. renderCodeCell) push extra lines
86
+ // onto the result, which would corrupt the cached string otherwise.
87
+ return (highlighted ?? code).split("\n");
88
+ }
89
+
90
+ export function getSymbolTheme(): SymbolTheme {
91
+ // Guard against `theme` being undefined (pre-init or cross-module-instance
92
+ // plugin calls). Fall back to the ASCII preset so the returned symbols are
93
+ // usable instead of crashing. See #2998.
94
+ if (typeof theme === "undefined") {
95
+ const box = {
96
+ topLeft: "+",
97
+ topRight: "+",
98
+ bottomLeft: "+",
99
+ bottomRight: "+",
100
+ horizontal: "-",
101
+ vertical: "|",
102
+ cross: "+",
103
+ teeDown: "+",
104
+ teeUp: "+",
105
+ teeLeft: "+",
106
+ teeRight: "+",
107
+ };
108
+ return {
109
+ cursor: ">",
110
+ inputCursor: "|",
111
+ boxRound: box,
112
+ boxSharp: box,
113
+ table: box,
114
+ quoteBorder: "|",
115
+ hrChar: "-",
116
+ colorSwatch: "[]",
117
+ spinnerFrames: ["-", "\\", "|", "/"],
118
+ };
119
+ }
120
+ const preset = theme.getSymbolPreset();
121
+
122
+ return {
123
+ cursor: theme.nav.cursor,
124
+ inputCursor: preset === "ascii" ? "|" : "▏",
125
+ boxRound: theme.boxRound,
126
+ boxSharp: theme.boxSharp,
127
+ table: theme.boxSharp,
128
+ quoteBorder: theme.md.quoteBorder,
129
+ hrChar: theme.md.hrChar,
130
+ colorSwatch: theme.md.colorSwatch,
131
+ spinnerFrames: theme.getSpinnerFrames("activity"),
132
+ };
133
+ }
134
+
135
+ let cachedMarkdownTheme: MarkdownTheme | undefined;
136
+ let cachedMarkdownThemeRef: Theme | undefined;
137
+ let markdownMermaidRendering = true;
138
+
139
+ export function setMarkdownMermaidRendering(enabled: boolean): void {
140
+ if (markdownMermaidRendering === enabled) return;
141
+ markdownMermaidRendering = enabled;
142
+ cachedMarkdownTheme = undefined;
143
+ }
144
+
145
+ export function getMarkdownTheme(): MarkdownTheme {
146
+ if (cachedMarkdownTheme !== undefined && cachedMarkdownThemeRef === theme) {
147
+ return cachedMarkdownTheme;
148
+ }
149
+ const mermaid = markdownMermaidRendering
150
+ ? (() => {
151
+ // Mermaid ASCII diagrams render with the active palette so they read as
152
+ // content rather than raw monochrome. Roles mirror the SVG renderer's
153
+ // mapping; `text`/`muted`/`border`/`borderMuted`/`accent` exist in every theme.
154
+ const mermaidColorMode =
155
+ theme.getColorMode() === "truecolor" ? ("truecolor" as const) : ("ansi256" as const);
156
+ const mermaidTheme = {
157
+ fg: theme.getColorHex("text"),
158
+ border: theme.getColorHex("border"),
159
+ line: theme.getColorHex("muted"),
160
+ arrow: theme.getColorHex("accent"),
161
+ corner: theme.getColorHex("muted"),
162
+ junction: theme.getColorHex("borderMuted"),
163
+ };
164
+ return { mermaidColorMode, mermaidTheme };
165
+ })()
166
+ : undefined;
167
+ const markdownTheme: MarkdownTheme = {
168
+ heading: (text: string) => theme.fg("mdHeading", text),
169
+ link: (text: string) => theme.fg("mdLink", text),
170
+ linkUrl: (text: string) => theme.fg("mdLinkUrl", text),
171
+ code: (text: string) => theme.fg("mdCode", text),
172
+ codeBlock: (text: string) => theme.fg("mdCodeBlock", text),
173
+ codeBlockBorder: (text: string) => theme.fg("mdCodeBlockBorder", text),
174
+ quote: (text: string) => theme.fg("mdQuote", text),
175
+ quoteBorder: (text: string) => theme.fg("mdQuoteBorder", text),
176
+ hr: (text: string) => theme.fg("mdHr", text),
177
+ listBullet: (text: string) => theme.fg("mdListBullet", text),
178
+ bold: (text: string) => theme.bold(text),
179
+ italic: (text: string) => theme.italic(text),
180
+ underline: (text: string) => theme.underline(text),
181
+ strikethrough: (text: string) => chalk.strikethrough(text),
182
+ symbols: getSymbolTheme(),
183
+ resolveMermaidAscii: mermaid
184
+ ? (source, maxWidth) =>
185
+ resolveMermaidAscii(source, {
186
+ maxWidth,
187
+ theme: mermaid.mermaidTheme,
188
+ colorMode: mermaid.mermaidColorMode,
189
+ })
190
+ : undefined,
191
+ highlightCode: (code: string, lang?: string): string[] => {
192
+ const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
193
+ const highlighted = highlightCached(code, validLang, theme);
194
+ if (highlighted !== null) return highlighted.split("\n");
195
+ return code.split("\n").map(line => theme.fg("mdCodeBlock", line));
196
+ },
197
+ };
198
+ cachedMarkdownTheme = markdownTheme;
199
+ cachedMarkdownThemeRef = theme;
200
+ return markdownTheme;
201
+ }
202
+
203
+ export function getSelectListTheme(): SelectListTheme {
204
+ // Guard against `theme` being undefined (pre-init or cross-module-instance
205
+ // plugin calls). See #2998.
206
+ if (typeof theme === "undefined") {
207
+ return {
208
+ selectedPrefix: (text: string) => text,
209
+ selectedText: (text: string) => text,
210
+ description: (text: string) => text,
211
+ scrollInfo: (text: string) => text,
212
+ noMatch: (text: string) => text,
213
+ symbols: getSymbolTheme(),
214
+ hovered: (text: string) => text,
215
+ };
216
+ }
217
+ return {
218
+ selectedPrefix: (text: string) => theme.fg("accent", text),
219
+ selectedText: (text: string) => theme.fg("accent", text),
220
+ description: (text: string) => theme.fg("muted", text),
221
+ scrollInfo: (text: string) => theme.fg("muted", text),
222
+ noMatch: (text: string) => theme.fg("muted", text),
223
+ symbols: getSymbolTheme(),
224
+ hovered: (text: string) => theme.bg("selectedBg", text),
225
+ };
226
+ }
227
+
228
+ export function getEditorTheme(): EditorTheme {
229
+ // Guard against `theme` being undefined (pre-init or cross-module-instance
230
+ // plugin calls). See #2998.
231
+ if (typeof theme === "undefined") {
232
+ return {
233
+ borderColor: (text: string) => text,
234
+ selectList: getSelectListTheme(),
235
+ symbols: getSymbolTheme(),
236
+ hintStyle: (text: string) => text,
237
+ };
238
+ }
239
+ return {
240
+ borderColor: (text: string) => theme.fg("borderMuted", text),
241
+ selectList: getSelectListTheme(),
242
+ symbols: getSymbolTheme(),
243
+ hintStyle: (text: string) => theme.fg("dim", text),
244
+ };
245
+ }
246
+
247
+ export function getSettingsListTheme(): SettingsListTheme {
248
+ // Plugins (e.g. pi-rtk-optimizer) may call this before `initTheme()` assigns
249
+ // the global `theme`, or from a separate module instance under npm-global
250
+ // installs where the live binding was never initialized. Fall back to plain
251
+ // text so the call returns a usable (unstyled) theme instead of crashing with
252
+ // "undefined is not an object (evaluating 'theme.fg')". See #2998.
253
+ if (typeof theme === "undefined") {
254
+ return {
255
+ label: (text: string) => text,
256
+ value: (text: string) => text,
257
+ description: (text: string) => text,
258
+ cursor: "> ",
259
+ hint: (text: string) => text,
260
+ heading: (text: string) => text,
261
+ section: (text: string) => text,
262
+ hovered: (text: string) => text,
263
+ };
264
+ }
265
+ return {
266
+ label: (text: string, selected: boolean, changed: boolean) =>
267
+ changed ? theme.fg("statusLineGitDirty", text) : selected ? theme.fg("accent", text) : text,
268
+ value: (text: string, selected: boolean, changed: boolean) =>
269
+ changed ? theme.fg("statusLineGitDirty", text) : selected ? theme.fg("accent", text) : theme.fg("muted", text),
270
+ description: (text: string) => theme.fg("dim", text),
271
+ cursor: theme.fg("accent", `${theme.nav.cursor} `),
272
+ hint: (text: string) => theme.fg("dim", text),
273
+ heading: (text: string, dimmed: boolean) =>
274
+ dimmed ? theme.fg("dim", theme.underline(text)) : theme.fg("muted", theme.bold(theme.underline(text))),
275
+ section: (text: string, active: boolean) =>
276
+ active ? theme.fg("accent", theme.bold(text)) : theme.fg("muted", text),
277
+ hovered: (text: string) => theme.bg("selectedBg", text),
278
+ };
279
+ }
@@ -211,13 +211,15 @@ function sanitizeRecoveredRetryNote(note: string): string {
211
211
 
212
212
  /**
213
213
  * Resolve the turn-ending assistant error presentation, if any.
214
- * Silent and user-interrupt aborts yield no label. Recovered auto-retry errors
215
- * collapse to a single non-error note; terminal errors keep the full red presentation.
214
+ * Silent and user-interrupt aborts yield no label. Recovered retry attempts
215
+ * render a compact note; attempts superseded by an exhausted budget are hidden
216
+ * while the final terminal error keeps its full presentation.
216
217
  */
217
218
  export function resolveAssistantErrorPresentation(
218
219
  message: AssistantAgentMessage,
219
220
  retryAttempt = 0,
220
221
  ): AssistantErrorPresentation {
222
+ if (message.retryRecovery?.status === "superseded") return { kind: "none" };
221
223
  if (message.retryRecovery?.status === "recovered") {
222
224
  return {
223
225
  kind: "compact-recovered",
@@ -500,6 +500,7 @@ export class UiHelpers {
500
500
  content.name,
501
501
  renderArgs,
502
502
  {
503
+ useBuiltInRenderer: this.ctx.viewSession.hasBuiltInTool(content.name),
503
504
  snapshots: getFileSnapshotStore(this.ctx.viewSession),
504
505
  clipboard: getEditClipboard(this.ctx.viewSession),
505
506
  showImages: settings.get("terminal.showImages"),
@@ -15,7 +15,7 @@ import { oneLineLabel } from "../task/types";
15
15
  export const MAIN_AGENT_ID = "Main";
16
16
 
17
17
  /** Sidecar marker retained beside a child transcript after an explicit kill. */
18
- export const AGENT_TOMBSTONE_SUFFIX = ".tombstone";
18
+ const AGENT_TOMBSTONE_SUFFIX = ".tombstone";
19
19
 
20
20
  export function getAgentTombstonePath(sessionFile: string): string {
21
21
  return `${sessionFile}${AGENT_TOMBSTONE_SUFFIX}`;
@@ -30,7 +30,7 @@ export function getAgentTombstonePath(sessionFile: string): string {
30
30
  */
31
31
  export type AgentStatus = "running" | "idle" | "parked" | "aborted";
32
32
  /** Provenance of a displayed duration: active runtime, transcript span, or unavailable. */
33
- export type AgentDurationKind = "active" | "span" | "unknown";
33
+ type AgentDurationKind = "active" | "span" | "unknown";
34
34
  /**
35
35
  * - `main`/`sub`: the user-facing agent tree (driving agent + task subagents).
36
36
  * - `advisor`: a passive review transcript persisted like a subagent for usage
package/src/sdk.ts CHANGED
@@ -123,18 +123,12 @@ import lateDiagnosticTemplate from "./prompts/tools/lsp-late-diagnostic.md" with
123
123
  import { AgentLifecycleManager } from "./registry/agent-lifecycle";
124
124
  import { type AgentRef, AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
125
125
  import {
126
- builtinCredentialSecretEntries,
127
- collectEnvSecrets,
126
+ buildSecretObfuscator,
128
127
  deobfuscateSessionContext,
129
128
  deobfuscateToolArguments,
130
- getExistingSecretPlaceholderKey,
131
- getSecretPlaceholderKey,
132
- getSecretPlaceholderKeySync,
133
- loadSecrets,
134
129
  obfuscateMessages,
135
130
  obfuscateProviderContext,
136
- SecretObfuscator,
137
- secretEntriesNeedPlaceholderKey,
131
+ type SecretObfuscator,
138
132
  } from "./secrets";
139
133
  import { AgentSession, type InitialRetryFallbackState, type PlanYolo, type Prewalk } from "./session/agent-session";
140
134
  import { discoverAuthStorage as discoverAuthStorageFromConfig } from "./session/auth-broker-config";
@@ -1039,7 +1033,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
1039
1033
  success: event.success,
1040
1034
  attempt: event.attempt,
1041
1035
  finalError: event.finalError,
1042
- recoveredErrors: event.recoveredErrors,
1036
+ retryErrors: event.retryErrors,
1043
1037
  },
1044
1038
  ctx,
1045
1039
  ),
@@ -1377,46 +1371,9 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
1377
1371
 
1378
1372
  // Load and create secret obfuscator early so resumed session state and prompt warnings
1379
1373
  // reflect actual loaded secrets, not just the setting toggle.
1380
- let obfuscator: SecretObfuscator | undefined;
1381
- if (settings.get("secrets.enabled")) {
1382
- const fileEntries = await logger.time("loadSecrets", loadSecrets, cwd, agentDir);
1383
- const envEntries = collectEnvSecrets();
1384
- // Built-in credential-pattern entries come last so user-configured entries
1385
- // (plain literals, custom regexes) take precedence in the scan order.
1386
- const allEntries = [...envEntries, ...fileEntries, ...builtinCredentialSecretEntries()];
1387
- // Only CONFIGURED entries force startup key creation: a configured
1388
- // obfuscate-mode secret — or a default (no custom `replacement`)
1389
- // replace-mode regex whose key-derived idempotent fallback marker needs a
1390
- // stable key across restarts (see `secretEntryNeedsPlaceholderKey`) —
1391
- // mints placeholders as soon as the obfuscator is built. The built-in
1392
- // credential-pattern entry matches dynamically, so it resolves the
1393
- // persisted key lazily on first match instead of creating the key file
1394
- // for every secrets.enabled session; a session whose content never
1395
- // contains a credential-shaped token must not require the key, otherwise a
1396
- // headless run on an unwritable default config root pays for a feature it
1397
- // does not use.
1398
- const needsPlaceholderKey = secretEntriesNeedPlaceholderKey([...envEntries, ...fileEntries]);
1399
- const explicitAgentDir = options.agentDir;
1400
- const placeholderKey = needsPlaceholderKey
1401
- ? await getSecretPlaceholderKey(explicitAgentDir)
1402
- : await getExistingSecretPlaceholderKey(explicitAgentDir);
1403
- if (allEntries.length > 0) {
1404
- obfuscator = new SecretObfuscator(
1405
- allEntries,
1406
- placeholderKey ?? (() => getSecretPlaceholderKeySync(explicitAgentDir)),
1407
- );
1408
- }
1409
- if (obfuscator?.hasSecrets() !== true && placeholderKey !== undefined) {
1410
- // No configured entry produced an active secret (e.g. only ignored short
1411
- // plain entries, or no entries at all), but a persisted key exists. Build a
1412
- // redaction-only obfuscator so a tool read of the key file does not ship the
1413
- // reusable HMAC key to the provider.
1414
- obfuscator = new SecretObfuscator(
1415
- [{ type: "plain", mode: "replace", content: placeholderKey }],
1416
- placeholderKey,
1417
- );
1418
- }
1419
- }
1374
+ const obfuscator: SecretObfuscator | undefined = settings.get("secrets.enabled")
1375
+ ? await buildSecretObfuscator(cwd, agentDir, options.agentDir)
1376
+ : undefined;
1420
1377
  const secretsEnabled = obfuscator?.hasSecrets() === true;
1421
1378
 
1422
1379
  // An abnormal process exit after a non-terminal message tail is durable
@@ -4,8 +4,10 @@ import * as path from "node:path";
4
4
  import { SENSITIVE_TOKEN_RE } from "@oh-my-pi/pi-ai/providers/transform-messages";
5
5
  import { getSecretPlaceholderKeyPath, isEnoent, logger } from "@oh-my-pi/pi-utils";
6
6
  import { YAML } from "bun";
7
- import { regexHasUnresolvableShortMatchFallback, type SecretEntry, sanitizeSecretFriendlyName } from "./obfuscator";
7
+ import { type SecretEntry, SecretObfuscator } from "./obfuscator";
8
+ import { sanitizeSecretFriendlyName, secretEntriesNeedPlaceholderKey } from "./placeholder";
8
9
  import { compileSecretRegex } from "./regex";
10
+ import { regexHasUnresolvableShortMatchFallback } from "./replacement";
9
11
 
10
12
  const PLACEHOLDER_KEY_RE = /^[A-Za-z0-9_-]{43}$/;
11
13
  const cachedPlaceholderKeys = new Map<string, string>();
@@ -152,11 +154,9 @@ export {
152
154
  deobfuscateToolArguments,
153
155
  obfuscateMessages,
154
156
  obfuscateProviderContext,
155
- type SecretEntry,
156
- SecretObfuscator,
157
- secretEntriesNeedPlaceholderKey,
158
- secretEntryNeedsPlaceholderKey,
159
- } from "./obfuscator";
157
+ } from "./message-transform";
158
+ export { type SecretEntry, SecretObfuscator } from "./obfuscator";
159
+ export { secretEntriesNeedPlaceholderKey, secretEntryNeedsPlaceholderKey } from "./placeholder";
160
160
 
161
161
  /**
162
162
  * Load secrets from project-local and global secrets.yml files.
@@ -223,6 +223,51 @@ export function builtinCredentialSecretEntries(): SecretEntry[] {
223
223
  ];
224
224
  }
225
225
 
226
+ /**
227
+ * Build the session secret obfuscator from every configured source: secrets.yml
228
+ * (project + global), secret-shaped environment variables, and the built-in
229
+ * credential patterns. Callers gate on `secrets.enabled`.
230
+ *
231
+ * Only CONFIGURED entries force startup key creation: a configured
232
+ * obfuscate-mode secret — or a default (no custom `replacement`) replace-mode
233
+ * regex whose key-derived idempotent fallback marker needs a stable key across
234
+ * restarts (see `secretEntryNeedsPlaceholderKey`) — mints placeholders as soon
235
+ * as the obfuscator is built. The built-in credential-pattern entry matches
236
+ * dynamically, so it resolves the persisted key lazily on first match instead
237
+ * of creating the key file for every secrets-enabled session.
238
+ *
239
+ * When no configured entry produced an active secret but a persisted key
240
+ * exists, returns a redaction-only obfuscator so a tool read of the key file
241
+ * does not ship the reusable HMAC key to the provider. Returns undefined when
242
+ * there is nothing to protect.
243
+ *
244
+ * `keyDir` is the explicit agent dir override for the placeholder-key file
245
+ * (default XDG/agent location when omitted).
246
+ */
247
+ export async function buildSecretObfuscator(
248
+ cwd: string,
249
+ agentDir: string,
250
+ keyDir?: string,
251
+ ): Promise<SecretObfuscator | undefined> {
252
+ const fileEntries = await logger.time("loadSecrets", loadSecrets, cwd, agentDir);
253
+ const envEntries = collectEnvSecrets();
254
+ // Built-in credential-pattern entries come last so user-configured entries
255
+ // (plain literals, custom regexes) take precedence in the scan order.
256
+ const allEntries = [...envEntries, ...fileEntries, ...builtinCredentialSecretEntries()];
257
+ const needsPlaceholderKey = secretEntriesNeedPlaceholderKey([...envEntries, ...fileEntries]);
258
+ const placeholderKey = needsPlaceholderKey
259
+ ? await getSecretPlaceholderKey(keyDir)
260
+ : await getExistingSecretPlaceholderKey(keyDir);
261
+ let obfuscator: SecretObfuscator | undefined;
262
+ if (allEntries.length > 0) {
263
+ obfuscator = new SecretObfuscator(allEntries, placeholderKey ?? (() => getSecretPlaceholderKeySync(keyDir)));
264
+ }
265
+ if (obfuscator?.hasSecrets() !== true && placeholderKey !== undefined) {
266
+ obfuscator = new SecretObfuscator([{ type: "plain", mode: "replace", content: placeholderKey }], placeholderKey);
267
+ }
268
+ return obfuscator;
269
+ }
270
+
226
271
  async function loadSecretsFile(filePath: string): Promise<SecretEntry[]> {
227
272
  try {
228
273
  const text = await Bun.file(filePath).text();