@oh-my-pi/pi-coding-agent 15.10.9 → 15.10.11

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 (352) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/dist/cli.js +23087 -0
  3. package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
  4. package/dist/types/async/job-manager.d.ts +18 -0
  5. package/dist/types/cli/args.d.ts +1 -1
  6. package/dist/types/cli/dry-balance-cli.d.ts +1 -1
  7. package/dist/types/cli/gallery-cli.d.ts +1 -1
  8. package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
  9. package/dist/types/cli/usage-cli.d.ts +72 -0
  10. package/dist/types/commands/launch.d.ts +1 -1
  11. package/dist/types/commands/read.d.ts +1 -1
  12. package/dist/types/commands/usage.d.ts +25 -0
  13. package/dist/types/config/append-only-context-mode.d.ts +2 -1
  14. package/dist/types/config/model-discovery.d.ts +55 -0
  15. package/dist/types/config/model-registry.d.ts +20 -219
  16. package/dist/types/config/model-resolver.d.ts +16 -10
  17. package/dist/types/config/model-roles.d.ts +28 -0
  18. package/dist/types/config/models-config-schema.d.ts +523 -42
  19. package/dist/types/config/models-config.d.ts +385 -0
  20. package/dist/types/config/settings-schema.d.ts +12 -16
  21. package/dist/types/config/settings.d.ts +1 -1
  22. package/dist/types/debug/log-viewer.d.ts +1 -1
  23. package/dist/types/debug/raw-sse.d.ts +1 -1
  24. package/dist/types/debug/terminal-info.d.ts +0 -1
  25. package/dist/types/eval/backend.d.ts +0 -2
  26. package/dist/types/eval/idle-timeout.d.ts +0 -4
  27. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
  28. package/dist/types/export/html/template.generated.d.ts +1 -1
  29. package/dist/types/extensibility/extensions/types.d.ts +3 -3
  30. package/dist/types/hindsight/mental-models.d.ts +17 -8
  31. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  32. package/dist/types/internal-urls/types.d.ts +1 -1
  33. package/dist/types/lsp/edits.d.ts +9 -0
  34. package/dist/types/lsp/index.d.ts +2 -2
  35. package/dist/types/lsp/types.d.ts +2 -0
  36. package/dist/types/lsp/utils.d.ts +3 -0
  37. package/dist/types/mcp/json-rpc.d.ts +5 -0
  38. package/dist/types/mnemopi/state.d.ts +11 -1
  39. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  40. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  41. package/dist/types/modes/components/bash-execution.d.ts +1 -1
  42. package/dist/types/modes/components/copy-selector.d.ts +1 -1
  43. package/dist/types/modes/components/dynamic-border.d.ts +1 -1
  44. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
  45. package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
  46. package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
  47. package/dist/types/modes/components/footer.d.ts +1 -1
  48. package/dist/types/modes/components/hook-editor.d.ts +5 -0
  49. package/dist/types/modes/components/hook-input.d.ts +4 -0
  50. package/dist/types/modes/components/hook-selector.d.ts +1 -1
  51. package/dist/types/modes/components/model-selector.d.ts +1 -1
  52. package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
  53. package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
  54. package/dist/types/modes/components/session-selector.d.ts +1 -1
  55. package/dist/types/modes/components/status-line/component.d.ts +1 -1
  56. package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
  57. package/dist/types/modes/components/transcript-container.d.ts +31 -26
  58. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  59. package/dist/types/modes/components/user-message-selector.d.ts +1 -1
  60. package/dist/types/modes/components/user-message.d.ts +2 -1
  61. package/dist/types/modes/components/visual-truncate.d.ts +1 -1
  62. package/dist/types/modes/components/welcome.d.ts +19 -3
  63. package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
  64. package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
  65. package/dist/types/modes/interactive-mode.d.ts +1 -1
  66. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  67. package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
  68. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  69. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
  70. package/dist/types/modes/types.d.ts +2 -1
  71. package/dist/types/session/agent-session.d.ts +1 -1
  72. package/dist/types/session/auth-broker-config.d.ts +4 -0
  73. package/dist/types/session/session-manager.d.ts +1 -1
  74. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
  75. package/dist/types/ssh/connection-manager.d.ts +8 -0
  76. package/dist/types/task/discovery.d.ts +1 -2
  77. package/dist/types/task/parallel.d.ts +2 -2
  78. package/dist/types/task/worktree.d.ts +2 -0
  79. package/dist/types/tiny/title-client.d.ts +1 -1
  80. package/dist/types/tools/ask.d.ts +4 -0
  81. package/dist/types/tools/conflict-detect.d.ts +16 -0
  82. package/dist/types/tools/github-cache.d.ts +7 -0
  83. package/dist/types/tools/sqlite-reader.d.ts +3 -0
  84. package/dist/types/tools/todo.d.ts +2 -0
  85. package/dist/types/tui/output-block.d.ts +3 -3
  86. package/dist/types/utils/changelog.d.ts +8 -0
  87. package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
  88. package/dist/types/web/scrapers/types.d.ts +12 -0
  89. package/dist/types/web/search/providers/codex.d.ts +1 -1
  90. package/dist/types/web/search/providers/gemini.d.ts +1 -1
  91. package/examples/extensions/tools.ts +5 -4
  92. package/package.json +14 -11
  93. package/scripts/build-binary.ts +18 -23
  94. package/scripts/bundle-dist.ts +81 -0
  95. package/scripts/{dev-launch → omp} +1 -1
  96. package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
  97. package/src/async/job-manager.ts +57 -3
  98. package/src/autoresearch/dashboard.ts +1 -1
  99. package/src/autoresearch/prompt-setup.md +6 -6
  100. package/src/autoresearch/prompt.md +6 -6
  101. package/src/capability/fs.ts +10 -0
  102. package/src/cli/args.ts +1 -1
  103. package/src/cli/auth-gateway-cli.ts +1 -3
  104. package/src/cli/dry-balance-cli.ts +1 -1
  105. package/src/cli/gallery-cli.ts +1 -1
  106. package/src/cli/gallery-fixtures/fs.ts +1 -1
  107. package/src/cli/gallery-fixtures/types.ts +5 -1
  108. package/src/cli/list-models.ts +7 -12
  109. package/src/cli/usage-cli.ts +603 -0
  110. package/src/cli-commands.ts +1 -0
  111. package/src/cli.ts +69 -5
  112. package/src/commands/complete.ts +1 -1
  113. package/src/commands/launch.ts +1 -1
  114. package/src/commands/read.ts +6 -3
  115. package/src/commands/usage.ts +35 -0
  116. package/src/commit/agentic/agent.ts +1 -1
  117. package/src/commit/model-selection.ts +1 -1
  118. package/src/config/append-only-context-mode.ts +6 -12
  119. package/src/config/model-discovery.ts +554 -0
  120. package/src/config/model-registry.ts +308 -1025
  121. package/src/config/model-resolver.ts +113 -156
  122. package/src/config/model-roles.ts +74 -0
  123. package/src/config/models-config-schema.ts +57 -8
  124. package/src/config/models-config.ts +129 -0
  125. package/src/config/settings-schema.ts +18 -14
  126. package/src/config/settings.ts +37 -1
  127. package/src/dap/client.ts +124 -37
  128. package/src/dap/session.ts +259 -158
  129. package/src/debug/log-viewer.ts +1 -1
  130. package/src/debug/raw-sse.ts +1 -1
  131. package/src/debug/terminal-info.ts +0 -3
  132. package/src/edit/diff.ts +95 -18
  133. package/src/edit/hashline/block-resolver.ts +20 -1
  134. package/src/edit/hashline/diff.ts +36 -1
  135. package/src/edit/hashline/execute.ts +8 -2
  136. package/src/edit/index.ts +16 -1
  137. package/src/edit/modes/patch.ts +52 -0
  138. package/src/edit/modes/replace.ts +56 -22
  139. package/src/edit/notebook.ts +22 -2
  140. package/src/edit/renderer.ts +36 -10
  141. package/src/eval/__tests__/completion-bridge.test.ts +1 -1
  142. package/src/eval/backend.ts +0 -2
  143. package/src/eval/completion-bridge.ts +2 -1
  144. package/src/eval/idle-timeout.ts +2 -9
  145. package/src/eval/js/context-manager.ts +6 -8
  146. package/src/eval/js/executor.ts +6 -2
  147. package/src/eval/js/index.ts +0 -2
  148. package/src/eval/js/shared/helpers.ts +5 -6
  149. package/src/eval/js/shared/local-module-loader.ts +1 -1
  150. package/src/eval/js/shared/prelude.txt +62 -1
  151. package/src/eval/js/shared/rewrite-imports.ts +49 -23
  152. package/src/eval/js/shared/runtime.ts +1 -1
  153. package/src/eval/py/index.ts +0 -2
  154. package/src/eval/py/kernel.ts +19 -0
  155. package/src/eval/py/runner.py +107 -3
  156. package/src/exec/bash-executor.ts +3 -1
  157. package/src/export/html/template.generated.ts +1 -1
  158. package/src/export/html/template.js +3 -1
  159. package/src/extensibility/extensions/types.ts +3 -2
  160. package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
  161. package/src/hindsight/mental-models.ts +59 -12
  162. package/src/hindsight/state.ts +6 -1
  163. package/src/internal-urls/artifact-protocol.ts +11 -2
  164. package/src/internal-urls/docs-index.generated.ts +10 -10
  165. package/src/internal-urls/issue-pr-protocol.ts +12 -5
  166. package/src/internal-urls/router.ts +1 -1
  167. package/src/internal-urls/types.ts +1 -1
  168. package/src/lib/xai-http.ts +1 -1
  169. package/src/lsp/client.ts +118 -38
  170. package/src/lsp/clients/biome-client.ts +101 -39
  171. package/src/lsp/edits.ts +143 -95
  172. package/src/lsp/index.ts +31 -22
  173. package/src/lsp/render.ts +1 -1
  174. package/src/lsp/types.ts +2 -0
  175. package/src/lsp/utils.ts +28 -10
  176. package/src/main.ts +165 -17
  177. package/src/mcp/json-rpc.ts +35 -5
  178. package/src/mcp/transports/stdio.ts +7 -1
  179. package/src/memories/index.ts +2 -1
  180. package/src/mnemopi/backend.ts +25 -3
  181. package/src/mnemopi/state.ts +38 -2
  182. package/src/modes/components/agent-dashboard.ts +10 -7
  183. package/src/modes/components/assistant-message.ts +19 -13
  184. package/src/modes/components/bash-execution.ts +1 -1
  185. package/src/modes/components/copy-selector.ts +1 -1
  186. package/src/modes/components/diff.ts +13 -2
  187. package/src/modes/components/dynamic-border.ts +12 -3
  188. package/src/modes/components/extensions/extension-dashboard.ts +8 -5
  189. package/src/modes/components/extensions/extension-list.ts +1 -1
  190. package/src/modes/components/extensions/inspector-panel.ts +1 -1
  191. package/src/modes/components/footer.ts +1 -1
  192. package/src/modes/components/history-search.ts +1 -1
  193. package/src/modes/components/hook-editor.ts +8 -0
  194. package/src/modes/components/hook-input.ts +8 -0
  195. package/src/modes/components/hook-selector.ts +2 -2
  196. package/src/modes/components/model-selector.ts +66 -54
  197. package/src/modes/components/plan-review-overlay.ts +1 -1
  198. package/src/modes/components/session-observer-overlay.ts +2 -2
  199. package/src/modes/components/session-selector.ts +1 -1
  200. package/src/modes/components/settings-selector.ts +5 -1
  201. package/src/modes/components/status-line/component.ts +1 -1
  202. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  203. package/src/modes/components/transcript-container.ts +373 -141
  204. package/src/modes/components/tree-selector.ts +3 -3
  205. package/src/modes/components/user-message-selector.ts +1 -1
  206. package/src/modes/components/user-message.ts +17 -5
  207. package/src/modes/components/visual-truncate.ts +1 -1
  208. package/src/modes/components/welcome.ts +108 -26
  209. package/src/modes/controllers/command-controller.ts +10 -3
  210. package/src/modes/controllers/event-controller.ts +73 -49
  211. package/src/modes/controllers/input-controller.ts +5 -5
  212. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  213. package/src/modes/controllers/selector-controller.ts +1 -5
  214. package/src/modes/controllers/streaming-reveal.ts +85 -18
  215. package/src/modes/interactive-mode.ts +5 -19
  216. package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
  217. package/src/modes/setup-wizard/scenes/providers.ts +1 -1
  218. package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
  219. package/src/modes/setup-wizard/scenes/theme.ts +1 -1
  220. package/src/modes/setup-wizard/scenes/types.ts +1 -1
  221. package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
  222. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  223. package/src/modes/types.ts +2 -1
  224. package/src/prompts/agents/explore.md +2 -2
  225. package/src/prompts/agents/librarian.md +1 -2
  226. package/src/prompts/agents/oracle.md +1 -1
  227. package/src/prompts/agents/plan.md +5 -5
  228. package/src/prompts/agents/task.md +5 -5
  229. package/src/prompts/ci-green-request.md +5 -7
  230. package/src/prompts/goals/goal-budget-limit.md +2 -2
  231. package/src/prompts/goals/goal-continuation.md +4 -4
  232. package/src/prompts/goals/goal-mode-active.md +1 -1
  233. package/src/prompts/memories/read-path.md +1 -1
  234. package/src/prompts/memories/stage_one_system.md +2 -2
  235. package/src/prompts/review-custom-request.md +1 -1
  236. package/src/prompts/system/agent-creation-architect.md +2 -2
  237. package/src/prompts/system/auto-continue.md +1 -1
  238. package/src/prompts/system/background-tan-dispatch.md +1 -1
  239. package/src/prompts/system/btw-user.md +2 -2
  240. package/src/prompts/system/commit-message-system.md +13 -1
  241. package/src/prompts/system/custom-system-prompt.md +1 -1
  242. package/src/prompts/system/eager-todo.md +2 -2
  243. package/src/prompts/system/irc-incoming.md +1 -1
  244. package/src/prompts/system/manual-continue.md +1 -1
  245. package/src/prompts/system/omfg-user.md +3 -4
  246. package/src/prompts/system/orchestrate-notice.md +9 -9
  247. package/src/prompts/system/plan-mode-active.md +4 -4
  248. package/src/prompts/system/plan-mode-subagent.md +4 -5
  249. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  250. package/src/prompts/system/project-prompt.md +2 -2
  251. package/src/prompts/system/subagent-system-prompt.md +4 -4
  252. package/src/prompts/system/system-prompt.md +15 -26
  253. package/src/prompts/system/title-system.md +2 -2
  254. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  255. package/src/prompts/system/workflow-notice.md +1 -1
  256. package/src/prompts/tools/ast-edit.md +1 -1
  257. package/src/prompts/tools/ast-grep.md +2 -2
  258. package/src/prompts/tools/bash.md +8 -10
  259. package/src/prompts/tools/browser.md +7 -7
  260. package/src/prompts/tools/debug.md +1 -1
  261. package/src/prompts/tools/eval.md +3 -3
  262. package/src/prompts/tools/find.md +0 -1
  263. package/src/prompts/tools/github.md +8 -7
  264. package/src/prompts/tools/goal.md +1 -1
  265. package/src/prompts/tools/image-gen.md +1 -1
  266. package/src/prompts/tools/inspect-image-system.md +1 -1
  267. package/src/prompts/tools/irc.md +15 -15
  268. package/src/prompts/tools/lsp.md +2 -2
  269. package/src/prompts/tools/patch.md +2 -2
  270. package/src/prompts/tools/read.md +3 -4
  271. package/src/prompts/tools/recall.md +1 -1
  272. package/src/prompts/tools/reflect.md +1 -1
  273. package/src/prompts/tools/render-mermaid.md +2 -2
  274. package/src/prompts/tools/replace.md +4 -10
  275. package/src/prompts/tools/rewind.md +2 -2
  276. package/src/prompts/tools/search-tool-bm25.md +1 -9
  277. package/src/prompts/tools/search.md +0 -1
  278. package/src/prompts/tools/ssh.md +0 -4
  279. package/src/prompts/tools/task.md +2 -3
  280. package/src/prompts/tools/todo.md +6 -2
  281. package/src/sdk.ts +23 -10
  282. package/src/session/agent-session.ts +44 -10
  283. package/src/session/auth-broker-config.ts +30 -1
  284. package/src/session/session-manager.ts +2 -2
  285. package/src/session/streaming-output.ts +23 -2
  286. package/src/slash-commands/builtin-registry.ts +20 -0
  287. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  288. package/src/ssh/connection-manager.ts +27 -0
  289. package/src/task/commands.ts +2 -1
  290. package/src/task/discovery.ts +17 -24
  291. package/src/task/executor.ts +61 -53
  292. package/src/task/index.ts +137 -60
  293. package/src/task/parallel.ts +3 -3
  294. package/src/task/render.ts +2 -2
  295. package/src/task/worktree.ts +64 -56
  296. package/src/thinking.ts +2 -1
  297. package/src/tiny/title-client.ts +32 -14
  298. package/src/tools/archive-reader.ts +30 -2
  299. package/src/tools/ask.ts +104 -21
  300. package/src/tools/ast-edit.ts +25 -5
  301. package/src/tools/auto-generated-guard.ts +20 -3
  302. package/src/tools/bash-interactive.ts +27 -7
  303. package/src/tools/bash.ts +54 -13
  304. package/src/tools/browser/launch.ts +11 -2
  305. package/src/tools/browser/readable.ts +19 -2
  306. package/src/tools/browser/registry.ts +4 -1
  307. package/src/tools/browser/render.ts +2 -2
  308. package/src/tools/browser/tab-supervisor.ts +55 -16
  309. package/src/tools/conflict-detect.ts +50 -4
  310. package/src/tools/debug.ts +1 -1
  311. package/src/tools/eval-render.ts +5 -5
  312. package/src/tools/eval.ts +0 -2
  313. package/src/tools/fetch.ts +33 -10
  314. package/src/tools/gh-cache-invalidation.ts +63 -8
  315. package/src/tools/gh-renderer.ts +1 -1
  316. package/src/tools/gh.ts +172 -29
  317. package/src/tools/github-cache.ts +70 -6
  318. package/src/tools/image-gen.ts +3 -9
  319. package/src/tools/irc.ts +5 -1
  320. package/src/tools/job.ts +1 -1
  321. package/src/tools/read.ts +202 -61
  322. package/src/tools/render-utils.ts +3 -3
  323. package/src/tools/resolve.ts +1 -1
  324. package/src/tools/search.ts +92 -29
  325. package/src/tools/sqlite-reader.ts +17 -5
  326. package/src/tools/ssh.ts +8 -8
  327. package/src/tools/todo.ts +51 -12
  328. package/src/tools/write.ts +118 -18
  329. package/src/tui/output-block.ts +4 -4
  330. package/src/utils/changelog.ts +27 -1
  331. package/src/utils/file-mentions.ts +2 -1
  332. package/src/web/scrapers/arxiv.ts +1 -1
  333. package/src/web/scrapers/go-pkg.ts +1 -1
  334. package/src/web/scrapers/iacr.ts +1 -1
  335. package/src/web/scrapers/readthedocs.ts +1 -1
  336. package/src/web/scrapers/twitter.ts +2 -1
  337. package/src/web/scrapers/types.ts +87 -8
  338. package/src/web/scrapers/wikipedia.ts +1 -1
  339. package/src/web/scrapers/youtube.ts +6 -1
  340. package/src/web/search/index.ts +1 -1
  341. package/src/web/search/providers/anthropic.ts +8 -2
  342. package/src/web/search/providers/codex.ts +2 -1
  343. package/src/web/search/providers/gemini.ts +2 -3
  344. package/src/web/search/render.ts +8 -6
  345. package/dist/types/config/model-equivalence.d.ts +0 -24
  346. package/dist/types/config/model-id-affixes.d.ts +0 -12
  347. package/dist/types/config/model-provider-priority.d.ts +0 -1
  348. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  349. package/src/config/model-equivalence.ts +0 -875
  350. package/src/config/model-id-affixes.ts +0 -81
  351. package/src/config/model-provider-priority.ts +0 -56
  352. package/src/exec/idle-timeout-watchdog.ts +0 -126
@@ -0,0 +1,129 @@
1
+ /**
2
+ * models.json config file handle and provider configuration validation.
3
+ */
4
+
5
+ import type { Api, ModelSpec } from "@oh-my-pi/pi-ai/types";
6
+ import { ConfigFile } from "./config-file";
7
+ import {
8
+ type ModelsConfig,
9
+ ModelsConfigSchema,
10
+ type ProviderAuthMode,
11
+ type ProviderDiscovery,
12
+ } from "./models-config-schema";
13
+
14
+ export type ProviderValidationMode = "models-config" | "runtime-register";
15
+
16
+ export interface ProviderValidationModel {
17
+ id: string;
18
+ api?: Api;
19
+ contextWindow?: number;
20
+ maxTokens?: number;
21
+ }
22
+
23
+ export interface ProviderValidationConfig {
24
+ baseUrl?: string;
25
+ headers?: Record<string, string>;
26
+ apiKey?: string;
27
+ api?: Api;
28
+ auth?: ProviderAuthMode;
29
+ oauthConfigured?: boolean;
30
+ discovery?: ProviderDiscovery;
31
+ compat?: ModelSpec<Api>["compat"];
32
+ disableStrictTools?: boolean;
33
+ modelOverrides?: Record<string, unknown>;
34
+ models: ProviderValidationModel[];
35
+ }
36
+
37
+ export function validateProviderConfiguration(
38
+ providerName: string,
39
+ config: ProviderValidationConfig,
40
+ mode: ProviderValidationMode,
41
+ ): void {
42
+ const hasProviderApi = !!config.api;
43
+ const models = config.models;
44
+
45
+ if (models.length === 0) {
46
+ if (mode === "models-config") {
47
+ const hasModelOverrides = config.modelOverrides && Object.keys(config.modelOverrides).length > 0;
48
+ if (
49
+ !config.baseUrl &&
50
+ !config.headers &&
51
+ !config.compat &&
52
+ !config.apiKey &&
53
+ !config.disableStrictTools &&
54
+ !hasModelOverrides &&
55
+ !config.discovery
56
+ ) {
57
+ throw new Error(
58
+ `Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "compat", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
59
+ );
60
+ }
61
+ }
62
+ } else {
63
+ if (!config.baseUrl) {
64
+ throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
65
+ }
66
+ const requiresAuth =
67
+ mode === "runtime-register"
68
+ ? !config.apiKey && !config.oauthConfigured
69
+ : !config.apiKey && (config.auth ?? "apiKey") !== "none";
70
+ if (requiresAuth) {
71
+ throw new Error(
72
+ mode === "runtime-register"
73
+ ? `Provider ${providerName}: "apiKey" or "oauth" is required when defining models.`
74
+ : `Provider ${providerName}: "apiKey" is required when defining custom models unless auth is "none".`,
75
+ );
76
+ }
77
+ }
78
+
79
+ if (mode === "models-config" && config.discovery && !config.api && config.discovery.type !== "proxy") {
80
+ throw new Error(`Provider ${providerName}: "api" is required when discovery is enabled at provider level.`);
81
+ }
82
+
83
+ for (const modelDef of models) {
84
+ if (!hasProviderApi && !modelDef.api) {
85
+ throw new Error(
86
+ mode === "runtime-register"
87
+ ? `Provider ${providerName}, model ${modelDef.id}: no "api" specified.`
88
+ : `Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`,
89
+ );
90
+ }
91
+ if (!modelDef.id) {
92
+ throw new Error(`Provider ${providerName}: model missing "id"`);
93
+ }
94
+ if (mode === "models-config") {
95
+ if (modelDef.contextWindow !== undefined && modelDef.contextWindow <= 0) {
96
+ throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);
97
+ }
98
+ if (modelDef.maxTokens !== undefined && modelDef.maxTokens <= 0) {
99
+ throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsConfigSchema).withValidation(
106
+ "models",
107
+ config => {
108
+ const providers = config.providers ?? {};
109
+ for (const providerName in providers) {
110
+ const providerConfig = providers[providerName];
111
+ validateProviderConfiguration(
112
+ providerName,
113
+ {
114
+ baseUrl: providerConfig.baseUrl,
115
+ headers: providerConfig.headers,
116
+ apiKey: providerConfig.apiKey,
117
+ api: providerConfig.api as Api | undefined,
118
+ auth: (providerConfig.auth ?? "apiKey") as ProviderAuthMode,
119
+ discovery: providerConfig.discovery as ProviderDiscovery | undefined,
120
+ compat: providerConfig.compat,
121
+ disableStrictTools: providerConfig.disableStrictTools,
122
+ modelOverrides: providerConfig.modelOverrides,
123
+ models: (providerConfig.models ?? []) as ProviderValidationModel[],
124
+ },
125
+ "models-config",
126
+ );
127
+ }
128
+ },
129
+ );
@@ -246,7 +246,11 @@ export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [
246
246
  message: "Use the `edit` tool instead of awk -i inplace. It provides diff preview and fuzzy matching.",
247
247
  },
248
248
  {
249
- pattern: "^\\s*(echo|printf|cat\\s*<<)\\s+.*[^|]>\\s*\\S",
249
+ // `>` must sit outside quoted regions (so `echo "a -> b"` passes) and be
250
+ // followed by a plausible filename — including `$VAR` targets; `>|`
251
+ // (clobber) counts as a redirect; `>&2`/`2>&1` style fd duplication is
252
+ // not matched.
253
+ pattern: "^\\s*(echo|printf|cat\\s*<<)\\s+(?:[^\"'>]|\"[^\"]*\"|'[^']*')*(?<!\\|)>{1,2}\\|?\\s*[$\\w./~\"'-]",
250
254
  tool: "write",
251
255
  message: "Use the `write` tool instead of echo/cat redirection. It handles encoding and provides confirmation.",
252
256
  },
@@ -256,7 +260,6 @@ export const SETTINGS_SCHEMA = {
256
260
  // ────────────────────────────────────────────────────────────────────────
257
261
  // General settings (no UI)
258
262
  // ────────────────────────────────────────────────────────────────────────
259
- lastChangelogVersion: { type: "string", default: undefined },
260
263
  setupVersion: { type: "number", default: 0 },
261
264
 
262
265
  // Auth broker — credentials proxied through a remote `omp auth-broker serve`
@@ -686,16 +689,6 @@ export const SETTINGS_SCHEMA = {
686
689
  ui: { tab: "appearance", label: "Show Hardware Cursor", description: "Show terminal cursor for IME support" },
687
690
  },
688
691
 
689
- clearOnShrink: {
690
- type: "boolean",
691
- default: false,
692
- ui: {
693
- tab: "appearance",
694
- label: "Clear on Shrink",
695
- description: "Clear empty rows when content shrinks (may cause flicker)",
696
- },
697
- },
698
-
699
692
  // ────────────────────────────────────────────────────────────────────────
700
693
  // Model
701
694
  // ────────────────────────────────────────────────────────────────────────
@@ -886,7 +879,7 @@ export const SETTINGS_SCHEMA = {
886
879
 
887
880
  "retry.maxRetries": {
888
881
  type: "number",
889
- default: 3,
882
+ default: 10,
890
883
  ui: {
891
884
  tab: "model",
892
885
  label: "Retry Attempts",
@@ -901,7 +894,7 @@ export const SETTINGS_SCHEMA = {
901
894
  },
902
895
  },
903
896
 
904
- "retry.baseDelayMs": { type: "number", default: 2000 },
897
+ "retry.baseDelayMs": { type: "number", default: 500 },
905
898
  "retry.maxDelayMs": {
906
899
  type: "number",
907
900
  default: 5 * 60 * 1000,
@@ -1984,6 +1977,17 @@ export const SETTINGS_SCHEMA = {
1984
1977
  ui: { tab: "editing", label: "LSP", description: "Enable the lsp tool for language server protocol" },
1985
1978
  },
1986
1979
 
1980
+ "lsp.lazy": {
1981
+ type: "boolean",
1982
+ default: true,
1983
+ ui: {
1984
+ tab: "editing",
1985
+ label: "Lazy LSP Startup",
1986
+ description:
1987
+ "Start language servers on first use (lsp tool or editing a matching file type) instead of at session startup",
1988
+ },
1989
+ },
1990
+
1987
1991
  "lsp.formatOnWrite": {
1988
1992
  type: "boolean",
1989
1993
  default: false,
@@ -17,6 +17,7 @@ import * as path from "node:path";
17
17
  import {
18
18
  getAgentDbPath,
19
19
  getAgentDir,
20
+ getLastChangelogVersionPath,
20
21
  getProjectDir,
21
22
  isEnoent,
22
23
  logger,
@@ -25,7 +26,7 @@ import {
25
26
  } from "@oh-my-pi/pi-utils";
26
27
  import { YAML } from "bun";
27
28
  import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
28
- import type { ModelRole } from "../config/model-registry";
29
+ import type { ModelRole } from "../config/model-roles";
29
30
  import { loadCapability } from "../discovery";
30
31
  import { isLightTheme, setAutoThemeMapping, setColorBlindMode, setSymbolPreset } from "../modes/theme/theme";
31
32
  import { AgentStorage } from "../session/agent-storage";
@@ -206,6 +207,9 @@ export class Settings {
206
207
  /** Paths modified during this session (for partial save) */
207
208
  #modified = new Set<string>();
208
209
 
210
+ /** Legacy `lastChangelogVersion` captured from config.yml during migration (now a marker file). */
211
+ #legacyLastChangelogVersion?: string;
212
+
209
213
  /** Pending save (debounced) */
210
214
  #saveTimer?: NodeJS.Timeout;
211
215
  #savePromise?: Promise<void>;
@@ -549,6 +553,7 @@ export class Settings {
549
553
  this.#storage = await AgentStorage.open(getAgentDbPath(this.#agentDir));
550
554
  await this.#migrateFromLegacy();
551
555
  this.#global = await this.#loadYaml(this.#configPath!);
556
+ await this.#seedLastChangelogVersionMarker();
552
557
  }
553
558
 
554
559
  this.#project = await projectPromise;
@@ -642,6 +647,16 @@ export class Settings {
642
647
  delete raw.queueMode;
643
648
  }
644
649
 
650
+ // lastChangelogVersion moved out of config.yml into the
651
+ // <agentDir>/last-changelog-version marker file so version bumps no
652
+ // longer dirty user-tracked configs. Capture for marker seeding (see
653
+ // #seedLastChangelogVersionMarker), then strip the key — the next
654
+ // config save drops it from disk.
655
+ if (typeof raw.lastChangelogVersion === "string") {
656
+ this.#legacyLastChangelogVersion ??= raw.lastChangelogVersion;
657
+ }
658
+ delete raw.lastChangelogVersion;
659
+
645
660
  // ask.timeout: ms -> seconds (if value > 1000, it's old ms format)
646
661
  if (raw.ask && typeof (raw.ask as Record<string, unknown>).timeout === "number") {
647
662
  const oldValue = (raw.ask as Record<string, unknown>).timeout as number;
@@ -803,6 +818,27 @@ export class Settings {
803
818
  return raw;
804
819
  }
805
820
 
821
+ /**
822
+ * One-time migration: seed the last-changelog-version marker file from the
823
+ * legacy config.yml key. An existing marker always wins — it is the newer
824
+ * source of truth.
825
+ */
826
+ async #seedLastChangelogVersionMarker(): Promise<void> {
827
+ const legacy = this.#legacyLastChangelogVersion;
828
+ if (!legacy) return;
829
+ const markerPath = getLastChangelogVersionPath(this.#agentDir);
830
+ try {
831
+ if ((await Bun.file(markerPath).text()).trim()) return;
832
+ } catch (error) {
833
+ if (!isEnoent(error)) return;
834
+ }
835
+ try {
836
+ await Bun.write(markerPath, legacy);
837
+ } catch (error) {
838
+ logger.warn("Settings: failed to seed last-changelog-version marker", { error: String(error) });
839
+ }
840
+ }
841
+
806
842
  // ─────────────────────────────────────────────────────────────────────────
807
843
  // Saving
808
844
  // ─────────────────────────────────────────────────────────────────────────
package/src/dap/client.ts CHANGED
@@ -29,32 +29,67 @@ type DapReverseRequestHandler = (args: unknown) => unknown | Promise<unknown>;
29
29
 
30
30
  const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
31
31
 
32
- function findHeaderEnd(buffer: Uint8Array): number {
33
- for (let index = 0; index < buffer.length - 3; index += 1) {
34
- if (buffer[index] === 13 && buffer[index + 1] === 10 && buffer[index + 2] === 13 && buffer[index + 3] === 10) {
35
- return index;
32
+ // Reused for all full decodes; each decode() resets state, so a single
33
+ // instance is safe and avoids per-message TextDecoder allocation.
34
+ const MESSAGE_DECODER = new TextDecoder("utf-8");
35
+
36
+ /**
37
+ * Locate the `\r\n\r\n` header terminator across the pending chunk list.
38
+ * Returns the absolute byte index of the first `\r`, or -1 when not present.
39
+ * Equivalent to scanning the contiguous concatenation of the chunks.
40
+ */
41
+ function findHeaderEndInChunks(chunks: Buffer[]): number {
42
+ let global = 0;
43
+ let b0 = -1;
44
+ let b1 = -1;
45
+ let b2 = -1;
46
+ for (const chunk of chunks) {
47
+ for (let i = 0; i < chunk.length; i++) {
48
+ const b3 = chunk[i];
49
+ if (b0 === 13 && b1 === 10 && b2 === 13 && b3 === 10) {
50
+ return global - 3;
51
+ }
52
+ b0 = b1;
53
+ b1 = b2;
54
+ b2 = b3;
55
+ global++;
36
56
  }
37
57
  }
38
58
  return -1;
39
59
  }
40
60
 
41
- function parseMessage(
42
- buffer: Buffer,
43
- ): { message: DapResponseMessage | DapEventMessage | DapRequestMessage; remaining: Buffer } | null {
44
- const headerEndIndex = findHeaderEnd(buffer);
45
- if (headerEndIndex === -1) return null;
46
- const headerText = new TextDecoder().decode(buffer.slice(0, headerEndIndex));
47
- const contentLengthMatch = headerText.match(/Content-Length: (\d+)/i);
48
- if (!contentLengthMatch) return null;
49
- const contentLength = Number.parseInt(contentLengthMatch[1], 10);
50
- const messageStart = headerEndIndex + 4;
51
- const messageEnd = messageStart + contentLength;
52
- if (buffer.length < messageEnd) return null;
53
- const messageText = new TextDecoder().decode(buffer.subarray(messageStart, messageEnd));
54
- return {
55
- message: JSON.parse(messageText) as DapResponseMessage | DapEventMessage | DapRequestMessage,
56
- remaining: buffer.subarray(messageEnd),
57
- };
61
+ /** Copy the byte range [from, to) out of the pending chunk list into one Buffer. */
62
+ function copyChunkRange(chunks: Buffer[], from: number, to: number): Buffer {
63
+ const out = Buffer.allocUnsafe(to - from);
64
+ let global = 0;
65
+ let written = 0;
66
+ for (const chunk of chunks) {
67
+ const chunkEnd = global + chunk.length;
68
+ if (chunkEnd > from && global < to) {
69
+ const start = Math.max(from, global) - global;
70
+ const end = Math.min(to, chunkEnd) - global;
71
+ chunk.copy(out, written, start, end);
72
+ written += end - start;
73
+ }
74
+ global = chunkEnd;
75
+ if (global >= to) break;
76
+ }
77
+ return out;
78
+ }
79
+
80
+ /** Drop the first `count` bytes from the pending chunk list in place. */
81
+ function dropChunkFront(chunks: Buffer[], count: number): void {
82
+ let removed = 0;
83
+ while (chunks.length > 0) {
84
+ const head = chunks[0];
85
+ if (removed + head.length <= count) {
86
+ removed += head.length;
87
+ chunks.shift();
88
+ } else {
89
+ chunks[0] = head.subarray(count - removed);
90
+ break;
91
+ }
92
+ }
58
93
  }
59
94
 
60
95
  async function writeMessage(sink: DapWriteSink, message: DapRequestMessage | DapResponseMessage): Promise<void> {
@@ -81,7 +116,7 @@ export class DapClient {
81
116
  readonly #socket?: { end(): void };
82
117
  #requestSeq = 0;
83
118
  #pendingRequests = new Map<number, DapPendingRequest>();
84
- #messageBuffer = Buffer.alloc(0);
119
+ #messageBuffer: Buffer = Buffer.alloc(0);
85
120
  #isReading = false;
86
121
  #disposed = false;
87
122
  #lastActivity = Date.now();
@@ -416,32 +451,84 @@ export class DapClient {
416
451
  if (this.#isReading) return;
417
452
  this.#isReading = true;
418
453
  const reader = this.#readable.getReader();
454
+
455
+ // Incoming bytes are buffered as a list of chunks and only joined when a
456
+ // full message is framed (mirrors the LSP reader) — concatenating the
457
+ // accumulator on every read is O(n^2) for messages spanning many reads.
458
+ const pendingChunks: Buffer[] = [];
459
+ let pendingLen = 0;
460
+ if (this.#messageBuffer.length > 0) {
461
+ pendingChunks.push(this.#messageBuffer);
462
+ pendingLen = this.#messageBuffer.length;
463
+ }
464
+
419
465
  try {
420
466
  while (true) {
421
467
  const { done, value } = await reader.read();
422
468
  if (done) break;
423
- const currentBuffer = Buffer.concat([this.#messageBuffer, value]);
424
- this.#messageBuffer = currentBuffer;
425
- let workingBuffer = currentBuffer;
426
- let parsed = parseMessage(workingBuffer);
427
- while (parsed) {
428
- const { message, remaining } = parsed;
429
- workingBuffer = Buffer.from(remaining);
469
+
470
+ pendingChunks.push(Buffer.from(value));
471
+ pendingLen += value.length;
472
+
473
+ // Drain every complete message currently buffered.
474
+ while (true) {
475
+ const headerEnd = findHeaderEndInChunks(pendingChunks);
476
+ if (headerEnd === -1) break;
477
+
478
+ const headerText = MESSAGE_DECODER.decode(copyChunkRange(pendingChunks, 0, headerEnd));
479
+ const contentLengthMatch = headerText.match(/Content-Length: (\d+)/i);
480
+ if (!contentLengthMatch) {
481
+ // Non-protocol bytes (e.g. an adapter printing to stdout).
482
+ // Drop past the bogus terminator and resync instead of
483
+ // stalling on the same junk header forever.
484
+ logger.warn("DAP framing resync: header block without Content-Length", {
485
+ adapter: this.adapter.name,
486
+ header: headerText.slice(0, 200),
487
+ });
488
+ dropChunkFront(pendingChunks, headerEnd + 4);
489
+ pendingLen -= headerEnd + 4;
490
+ continue;
491
+ }
492
+
493
+ const contentLength = Number.parseInt(contentLengthMatch[1], 10);
494
+ const messageStart = headerEnd + 4; // Skip \r\n\r\n
495
+ const messageEnd = messageStart + contentLength;
496
+ if (pendingLen < messageEnd) break;
497
+
498
+ const messageText = MESSAGE_DECODER.decode(copyChunkRange(pendingChunks, messageStart, messageEnd));
499
+ dropChunkFront(pendingChunks, messageEnd);
500
+ pendingLen -= messageEnd;
430
501
  this.#lastActivity = Date.now();
431
- if (message.type === "response") {
432
- this.#handleResponse(message);
433
- } else if (message.type === "event") {
434
- await this.#dispatchEvent(message);
435
- } else {
436
- await this.#handleAdapterRequest(message);
502
+
503
+ // A malformed message must not kill the reader — later
504
+ // messages are still well-framed.
505
+ try {
506
+ const message = JSON.parse(messageText) as DapResponseMessage | DapEventMessage | DapRequestMessage;
507
+ if (message.type === "response") {
508
+ this.#handleResponse(message);
509
+ } else if (message.type === "event") {
510
+ await this.#dispatchEvent(message);
511
+ } else {
512
+ await this.#handleAdapterRequest(message);
513
+ }
514
+ } catch (error) {
515
+ logger.warn("DAP message handling failed", {
516
+ adapter: this.adapter.name,
517
+ error: toErrorMessage(error),
518
+ });
437
519
  }
438
- parsed = parseMessage(workingBuffer);
439
520
  }
440
- this.#messageBuffer = workingBuffer;
441
521
  }
442
522
  } catch (error) {
443
523
  this.#rejectPendingRequests(new Error(`DAP connection closed: ${toErrorMessage(error)}`));
444
524
  } finally {
525
+ // Persist any unparsed remainder so a restarted reader resumes mid-message.
526
+ this.#messageBuffer =
527
+ pendingChunks.length === 0
528
+ ? Buffer.alloc(0)
529
+ : pendingChunks.length === 1
530
+ ? pendingChunks[0]
531
+ : Buffer.concat(pendingChunks, pendingLen);
445
532
  reader.releaseLock();
446
533
  this.#isReading = false;
447
534
  }