@oh-my-pi/pi-coding-agent 17.2.4 → 17.2.6

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 (348) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/{CHANGELOG-bpmhv26t.md → CHANGELOG-gs76k6wc.md} +69 -0
  3. package/dist/cli.js +4438 -3962
  4. package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
  5. package/dist/types/cli/args.d.ts +4 -1
  6. package/dist/types/cli/browser-relay-cli.d.ts +15 -0
  7. package/dist/types/cli/command-help.d.ts +99 -0
  8. package/dist/types/cli/gc-cli.d.ts +1 -0
  9. package/dist/types/cli/help-extra.d.ts +2 -0
  10. package/dist/types/cli/thinking-levels.d.ts +5 -0
  11. package/dist/types/cli/update-cli.d.ts +13 -0
  12. package/dist/types/commands/browser-relay.d.ts +38 -0
  13. package/dist/types/commands/commit.d.ts +3 -0
  14. package/dist/types/commands/complete.d.ts +1 -1
  15. package/dist/types/commands/completions.d.ts +6 -0
  16. package/dist/types/commands/grep.d.ts +3 -0
  17. package/dist/types/commands/join.d.ts +4 -0
  18. package/dist/types/commands/launch-help.d.ts +171 -0
  19. package/dist/types/commands/launch.d.ts +5 -1
  20. package/dist/types/commands/models.d.ts +3 -0
  21. package/dist/types/commands/say.d.ts +10 -0
  22. package/dist/types/commands/worktree.d.ts +3 -0
  23. package/dist/types/config/keybindings.d.ts +5 -0
  24. package/dist/types/config/model-discovery.d.ts +2 -2
  25. package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
  26. package/dist/types/config/models-config-schema.d.ts +2 -0
  27. package/dist/types/config/models-config.d.ts +1 -0
  28. package/dist/types/config/service-tier.d.ts +2 -0
  29. package/dist/types/config/settings-schema.d.ts +69 -21
  30. package/dist/types/cursor-bridge-tools.d.ts +1 -1
  31. package/dist/types/cursor.d.ts +7 -4
  32. package/dist/types/edit/index.d.ts +3 -3
  33. package/dist/types/edit/modes/replace.d.ts +22 -15
  34. package/dist/types/edit/renderer.d.ts +1 -1
  35. package/dist/types/eval/py/kernel.d.ts +3 -1
  36. package/dist/types/eval/py/spawn-options.d.ts +12 -16
  37. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
  38. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  39. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  40. package/dist/types/launch/broker.d.ts +1 -1
  41. package/dist/types/launch/client.d.ts +15 -4
  42. package/dist/types/launch/ensure.d.ts +8 -0
  43. package/dist/types/launch/paths.d.ts +1 -1
  44. package/dist/types/launch/protocol.d.ts +19 -2
  45. package/dist/types/lsp/client.d.ts +2 -0
  46. package/dist/types/lsp/mux/daemon.d.ts +15 -0
  47. package/dist/types/lsp/mux/protocol.d.ts +57 -0
  48. package/dist/types/lsp/mux/server.d.ts +18 -0
  49. package/dist/types/lsp/types.d.ts +22 -2
  50. package/dist/types/main.d.ts +2 -0
  51. package/dist/types/mnemopi/embed-client.d.ts +1 -1
  52. package/dist/types/mnemopi/state.d.ts +0 -23
  53. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  54. package/dist/types/modes/components/btw-panel.d.ts +3 -0
  55. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  56. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  57. package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
  58. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  59. package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
  60. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  61. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  62. package/dist/types/modes/interactive-mode.d.ts +5 -1
  63. package/dist/types/modes/types.d.ts +4 -1
  64. package/dist/types/modes/workflow.d.ts +2 -1
  65. package/dist/types/sdk.d.ts +3 -1
  66. package/dist/types/secrets/index.d.ts +1 -1
  67. package/dist/types/security/contracts/schemas.d.ts +405 -403
  68. package/dist/types/session/agent-session-types.d.ts +8 -1
  69. package/dist/types/session/agent-session.d.ts +26 -2
  70. package/dist/types/session/indexed-session-storage.d.ts +1 -0
  71. package/dist/types/session/launch-completion.d.ts +10 -0
  72. package/dist/types/session/session-advisors.d.ts +1 -1
  73. package/dist/types/session/session-context.d.ts +2 -1
  74. package/dist/types/session/session-entries.d.ts +15 -1
  75. package/dist/types/session/session-maintenance.d.ts +1 -0
  76. package/dist/types/session/session-manager.d.ts +23 -11
  77. package/dist/types/session/session-stats.d.ts +10 -0
  78. package/dist/types/session/session-storage.d.ts +15 -3
  79. package/dist/types/session/yield-queue.d.ts +6 -1
  80. package/dist/types/system-prompt.d.ts +4 -1
  81. package/dist/types/task/index.d.ts +2 -1
  82. package/dist/types/task/spawn-policy.d.ts +5 -0
  83. package/dist/types/thinking.d.ts +1 -7
  84. package/dist/types/tools/ast-grep.d.ts +1 -1
  85. package/dist/types/tools/browser/registry.d.ts +2 -1
  86. package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
  87. package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
  88. package/dist/types/tools/browser/relay/kind.d.ts +19 -0
  89. package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
  90. package/dist/types/tools/browser/relay/server.d.ts +35 -0
  91. package/dist/types/tools/browser/render.d.ts +1 -0
  92. package/dist/types/tools/browser.d.ts +3 -0
  93. package/dist/types/tools/computer/exposure.d.ts +9 -7
  94. package/dist/types/tools/computer/protocol.d.ts +70 -17
  95. package/dist/types/tools/computer/supervisor.d.ts +23 -10
  96. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  97. package/dist/types/tools/computer/worker.d.ts +42 -7
  98. package/dist/types/tools/computer-renderer.d.ts +7 -5
  99. package/dist/types/tools/computer.d.ts +25 -44
  100. package/dist/types/tools/context.d.ts +3 -0
  101. package/dist/types/tools/glob.d.ts +1 -1
  102. package/dist/types/tools/grep.d.ts +1 -1
  103. package/dist/types/tools/index.d.ts +9 -0
  104. package/dist/types/tools/render-utils.d.ts +2 -0
  105. package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
  106. package/dist/types/tools/tool-timeouts.d.ts +5 -0
  107. package/dist/types/tools/xdev.d.ts +8 -0
  108. package/dist/types/utils/clipboard.d.ts +2 -2
  109. package/dist/types/web/kagi.d.ts +1 -0
  110. package/dist/types/web/search/index.d.ts +1 -1
  111. package/dist/types/web/search/providers/anthropic.d.ts +1 -0
  112. package/dist/types/web/search/providers/base.d.ts +2 -0
  113. package/dist/types/web/search/providers/brave.d.ts +1 -0
  114. package/dist/types/web/search/providers/browser-page.d.ts +1 -0
  115. package/dist/types/web/search/providers/codex.d.ts +1 -0
  116. package/dist/types/web/search/providers/exa.d.ts +1 -0
  117. package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
  118. package/dist/types/web/search/providers/gemini.d.ts +1 -0
  119. package/dist/types/web/search/providers/jina.d.ts +1 -0
  120. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  121. package/dist/types/web/search/providers/kimi.d.ts +1 -0
  122. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  123. package/dist/types/web/search/providers/perplexity.d.ts +1 -0
  124. package/dist/types/web/search/providers/searxng.d.ts +1 -0
  125. package/dist/types/web/search/providers/tavily.d.ts +1 -0
  126. package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
  127. package/dist/types/web/search/providers/utils.d.ts +6 -9
  128. package/dist/types/web/search/providers/zai.d.ts +1 -0
  129. package/dist/types/web/search/types.d.ts +4 -0
  130. package/package.json +12 -12
  131. package/src/cli/args.ts +7 -87
  132. package/src/cli/browser-relay-cli.ts +119 -0
  133. package/src/cli/command-help.ts +103 -0
  134. package/src/cli/completion-gen.ts +5 -5
  135. package/src/cli/flag-tables.ts +9 -0
  136. package/src/cli/gc-cli.ts +641 -21
  137. package/src/cli/help-extra.ts +89 -0
  138. package/src/cli/setup-cli.ts +13 -21
  139. package/src/cli/thinking-levels.ts +7 -0
  140. package/src/cli/update-cli.ts +26 -7
  141. package/src/cli-commands.ts +170 -33
  142. package/src/cli.ts +17 -5
  143. package/src/commands/acp.ts +3 -1
  144. package/src/commands/agents.ts +3 -2
  145. package/src/commands/auth-broker.ts +3 -2
  146. package/src/commands/auth-gateway.ts +3 -2
  147. package/src/commands/bench.ts +2 -3
  148. package/src/commands/browser-relay.ts +53 -0
  149. package/src/commands/cleanse.ts +2 -2
  150. package/src/commands/commit.ts +3 -2
  151. package/src/commands/complete.ts +2 -1
  152. package/src/commands/completions.ts +3 -2
  153. package/src/commands/config.ts +3 -2
  154. package/src/commands/dry-balance.ts +2 -2
  155. package/src/commands/gallery.ts +3 -2
  156. package/src/commands/gc.ts +3 -2
  157. package/src/commands/grep.ts +3 -2
  158. package/src/commands/grievances.ts +3 -2
  159. package/src/commands/install.ts +2 -2
  160. package/src/commands/join.ts +3 -2
  161. package/src/commands/launch-help.ts +116 -0
  162. package/src/commands/launch.ts +7 -191
  163. package/src/commands/models.ts +3 -2
  164. package/src/commands/plugin.ts +3 -2
  165. package/src/commands/read.ts +3 -2
  166. package/src/commands/say.ts +3 -2
  167. package/src/commands/setup.ts +3 -2
  168. package/src/commands/shell.ts +3 -2
  169. package/src/commands/ssh.ts +3 -2
  170. package/src/commands/stats.ts +3 -2
  171. package/src/commands/tiny-models.ts +2 -2
  172. package/src/commands/token.ts +2 -2
  173. package/src/commands/ttsr.ts +2 -2
  174. package/src/commands/update.ts +3 -2
  175. package/src/commands/usage.ts +3 -2
  176. package/src/commands/web-search.ts +3 -2
  177. package/src/commands/worktree.ts +3 -2
  178. package/src/config/keybindings.ts +5 -0
  179. package/src/config/model-discovery.ts +44 -27
  180. package/src/config/model-registry.ts +19 -13
  181. package/src/config/models-config-schema-bundle.ts +9 -0
  182. package/src/config/resolve-config-value.ts +2 -1
  183. package/src/config/service-tier.ts +5 -0
  184. package/src/config/settings-schema.ts +72 -20
  185. package/src/config/settings.ts +1 -1
  186. package/src/cursor-bridge-tools.ts +1 -1
  187. package/src/cursor.ts +10 -14
  188. package/src/edit/diff.ts +17 -8
  189. package/src/edit/index.ts +20 -7
  190. package/src/edit/modes/replace.ts +93 -40
  191. package/src/edit/renderer.ts +1 -1
  192. package/src/edit/streaming.ts +14 -17
  193. package/src/eval/py/kernel.ts +2 -1
  194. package/src/eval/py/spawn-options.ts +24 -29
  195. package/src/export/html/index.ts +10 -3
  196. package/src/export/html/template.js +8 -26
  197. package/src/export/share.ts +4 -0
  198. package/src/extensibility/extensions/wrapper.ts +1 -0
  199. package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
  200. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
  201. package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
  202. package/src/extensibility/skills.ts +11 -0
  203. package/src/extensibility/utils.ts +10 -2
  204. package/src/launch/broker.ts +223 -7
  205. package/src/launch/client.ts +192 -21
  206. package/src/launch/ensure.ts +73 -0
  207. package/src/launch/paths.ts +1 -1
  208. package/src/launch/protocol.ts +54 -2
  209. package/src/lsp/client.ts +23 -2
  210. package/src/lsp/index.ts +8 -0
  211. package/src/lsp/mux/daemon.ts +348 -0
  212. package/src/lsp/mux/protocol.ts +96 -0
  213. package/src/lsp/mux/server.ts +797 -0
  214. package/src/lsp/types.ts +28 -2
  215. package/src/main.ts +20 -8
  216. package/src/mcp/config-writer.ts +1 -1
  217. package/src/mcp/oauth-flow.ts +11 -1
  218. package/src/mnemopi/embed-client.ts +49 -2
  219. package/src/mnemopi/state.ts +57 -23
  220. package/src/modes/components/assistant-message.ts +11 -0
  221. package/src/modes/components/btw-panel.ts +41 -4
  222. package/src/modes/components/chat-transcript-builder.ts +4 -0
  223. package/src/modes/components/custom-editor.ts +9 -0
  224. package/src/modes/components/read-tool-group.ts +10 -0
  225. package/src/modes/components/status-line/component.ts +1 -1
  226. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  227. package/src/modes/components/tool-execution.ts +8 -0
  228. package/src/modes/components/tree-selector.ts +46 -48
  229. package/src/modes/controllers/btw-controller.ts +55 -7
  230. package/src/modes/controllers/command-controller.ts +31 -0
  231. package/src/modes/controllers/event-controller.ts +17 -0
  232. package/src/modes/controllers/input-controller.ts +63 -8
  233. package/src/modes/controllers/mcp-command-controller.ts +11 -5
  234. package/src/modes/controllers/selector-controller.ts +20 -0
  235. package/src/modes/interactive-mode.ts +18 -2
  236. package/src/modes/types.ts +9 -1
  237. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  238. package/src/modes/utils/interactive-context-helpers.ts +1 -0
  239. package/src/modes/utils/ui-helpers.ts +6 -10
  240. package/src/modes/workflow.ts +8 -2
  241. package/src/prompts/agents/init.md +1 -1
  242. package/src/prompts/session/launch-completion.md +1 -0
  243. package/src/prompts/system/plan-mode-active.md +2 -2
  244. package/src/prompts/system/system-prompt.md +26 -52
  245. package/src/prompts/system/workflow-notice.md +3 -3
  246. package/src/prompts/tools/ast-grep.md +1 -1
  247. package/src/prompts/tools/bash.md +1 -1
  248. package/src/prompts/tools/browser.md +1 -0
  249. package/src/prompts/tools/computer.md +21 -21
  250. package/src/prompts/tools/glob.md +1 -1
  251. package/src/prompts/tools/grep.md +1 -1
  252. package/src/prompts/tools/replace.md +5 -4
  253. package/src/prompts/tools/task.md +4 -4
  254. package/src/sdk.ts +49 -6
  255. package/src/secrets/index.ts +1 -1
  256. package/src/security/contracts/schemas.ts +205 -183
  257. package/src/security/contracts/validation.ts +5 -1
  258. package/src/security/store.ts +2 -2
  259. package/src/session/agent-session-types.ts +9 -1
  260. package/src/session/agent-session.ts +204 -15
  261. package/src/session/indexed-session-storage.ts +9 -0
  262. package/src/session/launch-completion.ts +37 -0
  263. package/src/session/prewalk.ts +24 -2
  264. package/src/session/session-advisors.ts +2 -2
  265. package/src/session/session-context.ts +42 -12
  266. package/src/session/session-dump-format.ts +1 -1
  267. package/src/session/session-entries.ts +17 -1
  268. package/src/session/session-maintenance.ts +38 -4
  269. package/src/session/session-manager.ts +123 -67
  270. package/src/session/session-paths.ts +118 -55
  271. package/src/session/session-stats.ts +48 -3
  272. package/src/session/session-storage.ts +30 -22
  273. package/src/session/stream-guards.ts +1 -1
  274. package/src/session/yield-queue.ts +121 -16
  275. package/src/slash-commands/builtin-registry.ts +37 -24
  276. package/src/system-prompt.ts +8 -3
  277. package/src/task/index.ts +23 -8
  278. package/src/task/spawn-policy.ts +14 -0
  279. package/src/thinking.ts +2 -8
  280. package/src/tools/ast-grep.ts +10 -4
  281. package/src/tools/browser/cmux/cmux-tab.ts +97 -14
  282. package/src/tools/browser/cmux/rpc.ts +2 -8
  283. package/src/tools/browser/registry.ts +53 -2
  284. package/src/tools/browser/relay/bridge.ts +945 -0
  285. package/src/tools/browser/relay/daemon.ts +117 -0
  286. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  287. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  288. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  289. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  290. package/src/tools/browser/relay/kind.ts +41 -0
  291. package/src/tools/browser/relay/protocol.ts +54 -0
  292. package/src/tools/browser/relay/server.ts +141 -0
  293. package/src/tools/browser/render.ts +4 -1
  294. package/src/tools/browser/shared-daemon.ts +7 -58
  295. package/src/tools/browser/tab-supervisor.ts +5 -4
  296. package/src/tools/browser/tab-worker.ts +33 -12
  297. package/src/tools/browser.ts +26 -1
  298. package/src/tools/computer/exposure.ts +11 -35
  299. package/src/tools/computer/protocol.ts +51 -10
  300. package/src/tools/computer/supervisor.ts +227 -124
  301. package/src/tools/computer/worker-entry.ts +11 -6
  302. package/src/tools/computer/worker.ts +695 -81
  303. package/src/tools/computer-renderer.ts +104 -65
  304. package/src/tools/computer.ts +134 -428
  305. package/src/tools/context.ts +3 -0
  306. package/src/tools/glob.ts +9 -2
  307. package/src/tools/grep.ts +12 -6
  308. package/src/tools/hub/launch.ts +122 -6
  309. package/src/tools/index.ts +9 -0
  310. package/src/tools/inspect-image.ts +32 -6
  311. package/src/tools/render-utils.ts +2 -0
  312. package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
  313. package/src/tools/tool-timeouts.ts +1 -0
  314. package/src/tools/xdev.ts +29 -2
  315. package/src/utils/clipboard.ts +40 -14
  316. package/src/utils/command-args.ts +26 -28
  317. package/src/utils/tool-choice.ts +0 -13
  318. package/src/web/kagi.ts +2 -1
  319. package/src/web/search/index.ts +18 -2
  320. package/src/web/search/providers/anthropic.ts +4 -1
  321. package/src/web/search/providers/base.ts +2 -0
  322. package/src/web/search/providers/brave.ts +3 -1
  323. package/src/web/search/providers/browser-page.ts +6 -4
  324. package/src/web/search/providers/codex.ts +4 -1
  325. package/src/web/search/providers/duckduckgo.ts +2 -1
  326. package/src/web/search/providers/ecosia.ts +2 -1
  327. package/src/web/search/providers/exa.ts +4 -2
  328. package/src/web/search/providers/firecrawl.ts +3 -1
  329. package/src/web/search/providers/gemini.ts +8 -2
  330. package/src/web/search/providers/google.ts +2 -1
  331. package/src/web/search/providers/jina.ts +12 -2
  332. package/src/web/search/providers/kagi.ts +3 -0
  333. package/src/web/search/providers/kimi.ts +5 -1
  334. package/src/web/search/providers/mojeek.ts +2 -1
  335. package/src/web/search/providers/parallel.ts +5 -1
  336. package/src/web/search/providers/perplexity.ts +6 -3
  337. package/src/web/search/providers/public.ts +1 -1
  338. package/src/web/search/providers/searxng.ts +11 -5
  339. package/src/web/search/providers/startpage.ts +10 -4
  340. package/src/web/search/providers/synthetic.ts +11 -5
  341. package/src/web/search/providers/tavily.ts +3 -1
  342. package/src/web/search/providers/tinyfish.ts +3 -1
  343. package/src/web/search/providers/utils.ts +12 -10
  344. package/src/web/search/providers/xai.ts +2 -2
  345. package/src/web/search/providers/zai.ts +18 -3
  346. package/src/web/search/types.ts +6 -0
  347. package/dist/types/config/file-lock.d.ts +0 -29
  348. package/src/config/file-lock.ts +0 -164
package/src/sdk.ts CHANGED
@@ -17,6 +17,7 @@ import type {
17
17
  Model,
18
18
  ModelUsageHealth,
19
19
  ProviderSessionState,
20
+ ServiceTier,
20
21
  SimpleStreamOptions,
21
22
  } from "@oh-my-pi/pi-ai";
22
23
  import { resolveApiKeyOnce } from "@oh-my-pi/pi-ai/auth-retry";
@@ -102,6 +103,7 @@ import {
102
103
  import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./extensibility/slash-commands";
103
104
  import type { HindsightSessionState } from "./hindsight/state";
104
105
  import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls";
106
+ import { setSharedLspEnabled } from "./lsp/client";
105
107
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
106
108
  import {
107
109
  deduplicateMCPToolsByName,
@@ -169,6 +171,7 @@ import {
169
171
  } from "./system-prompt";
170
172
  import { AgentOutputManager } from "./task/output-manager";
171
173
  import { wrapStreamFnWithProviderConcurrency } from "./task/provider-concurrency";
174
+ import { isScoutSpawnable } from "./task/spawn-policy";
172
175
  import type { StructuredSubagentSchemaMode } from "./task/types";
173
176
  import {
174
177
  AUTO_THINKING,
@@ -379,6 +382,8 @@ export interface CreateAgentSessionOptions {
379
382
  thinkingLevel?: ConfiguredThinkingLevel;
380
383
  /** Hard ceiling on the session's thinking effort (e.g. a task spawn's `task.maxEffort`-capped hint); retry-fallback recovery re-clamps to it. */
381
384
  thinkingLevelCeiling?: Effort;
385
+ /** OpenAI service-tier override for this session. `null` omits `service_tier`. */
386
+ openAIServiceTier?: ServiceTier | null;
382
387
  /** Models available for cycling (Ctrl+P in interactive mode) */
383
388
  scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
384
389
  /** Prewalk from the starting model to a fast/cheap target at the first edit/write once the todo list exists. */
@@ -1681,6 +1686,7 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
1681
1686
  // entries capture it at fetch time and are dropped at injection if a newer
1682
1687
  // mutation (any tool) bumped it in the meantime.
1683
1688
  const fileMutationVersions = new Map<string, number>();
1689
+ const disposeCallbacks = new Set<() => void>();
1684
1690
  const activeToolNames = new Set<string>();
1685
1691
  const toolRegistry = new Map<string, Tool>();
1686
1692
  const setActiveToolNames = (names: Iterable<string>): void => {
@@ -1734,6 +1740,7 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
1734
1740
  trackEvalExecution: (execution, abortController) =>
1735
1741
  session ? session.trackEvalExecution(execution, abortController) : execution,
1736
1742
  getSessionId: () => sessionManager.getSessionId?.() ?? null,
1743
+ isDisposed: () => session?.isDisposed ?? false,
1737
1744
  getHindsightSessionState: () => session?.getHindsightSessionState(),
1738
1745
  getMnemopiSessionState: () => session?.getMnemopiSessionState(),
1739
1746
  getAgentId: () => resolvedAgentId,
@@ -1760,6 +1767,14 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
1760
1767
  recordEvalSubagentUsage: output => sessionManager.recordEvalSubagentOutput(output),
1761
1768
  getClientBridge: () => session?.clientBridge,
1762
1769
  queueDeferredDiagnostics: entry => session?.yieldQueue.enqueue(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, entry),
1770
+ queueLaunchCompletion: notification =>
1771
+ session?.queueLaunchCompletion(notification) ??
1772
+ Promise.reject(new Error("Session unavailable for launch completion delivery")),
1773
+ registerDisposeCallback: callback => {
1774
+ disposeCallbacks.add(callback);
1775
+ return () => disposeCallbacks.delete(callback);
1776
+ },
1777
+ registerSessionChangeCallback: callback => session?.registerSessionChangeCallback(callback),
1763
1778
  bumpFileMutationVersion: path => {
1764
1779
  const next = (fileMutationVersions.get(path) ?? 0) + 1;
1765
1780
  fileMutationVersions.set(path, next);
@@ -2674,7 +2689,7 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
2674
2689
  // a real tool.
2675
2690
  //
2676
2691
  // It must be a `replace`-mode instance. `PiEditExecArgs` carries
2677
- // `old_text`/`new_text` pairs, which is exactly `replace`'s schema and
2692
+ // `old_string`/`new_string` replacements, which is exactly `replace`'s schema and
2678
2693
  // nothing else's — under the default `hashline` mode the frame's args do
2679
2694
  // not match the tool's parameters at all. The registry instance follows
2680
2695
  // the session's configured mode, so the bridge builds its own.
@@ -2875,8 +2890,8 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
2875
2890
  }
2876
2891
  }
2877
2892
  let appendPrompt: string | undefined = appendParts.length > 0 ? appendParts.join("\n\n") : undefined;
2878
- // Owned/in-band tool dialects (non-native) require the catalog as `# Tool:`
2879
- // sections; native tool calling lets the compact name list suffice.
2893
+ // Owned/in-band tool dialects (non-native) require the full functions-
2894
+ // namespace catalog; native tool calling lets the compact name list suffice.
2880
2895
  const nativeTools = resolveDialect(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
2881
2896
  const promptTools = projectSystemPromptToolMetadata(
2882
2897
  tools,
@@ -2910,6 +2925,10 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
2910
2925
  eagerTasksAlways,
2911
2926
  taskBatch: settings.get("task.batch"),
2912
2927
  taskMaxConcurrency: settings.get("task.maxConcurrency"),
2928
+ scoutAvailable: isScoutSpawnable(
2929
+ settings.get("task.disabledAgents") as string[] | undefined,
2930
+ options.spawns ?? "*",
2931
+ ),
2913
2932
  taskIrcEnabled: !restrictToolNames && isIrcEnabled(settings, options.taskDepth ?? 0),
2914
2933
  autoQaEnabled: !restrictToolNames && isAutoQaEnabled(settings),
2915
2934
  secretsEnabled,
@@ -3143,13 +3162,19 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
3143
3162
  const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
3144
3163
  const preferOpenAICodexWebsockets =
3145
3164
  openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
3146
- const initialServiceTierByFamily = hasServiceTierEntry
3165
+ const configuredServiceTierByFamily = hasServiceTierEntry
3147
3166
  ? (existingSession.serviceTier ?? {})
3148
3167
  : buildServiceTierByFamily(
3149
3168
  settings.get("tier.openai"),
3150
3169
  settings.get("tier.anthropic"),
3151
3170
  settings.get("tier.google"),
3152
3171
  );
3172
+ const initialServiceTierByFamily = { ...configuredServiceTierByFamily };
3173
+ if (options.openAIServiceTier === null) {
3174
+ delete initialServiceTierByFamily.openai;
3175
+ } else if (options.openAIServiceTier !== undefined) {
3176
+ initialServiceTierByFamily.openai = options.openAIServiceTier;
3177
+ }
3153
3178
 
3154
3179
  // One-shot launch-latency marker: fired the first time the loop dispatches
3155
3180
  // a chat request to the provider transport. See onFirstChatDispatch.
@@ -3252,6 +3277,11 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
3252
3277
  // Restore messages if session has existing data
3253
3278
  if (hasExistingSession) {
3254
3279
  agent.replaceMessages(existingSession.messages);
3280
+ if (options.openAIServiceTier !== undefined) {
3281
+ sessionManager.appendServiceTierChange(
3282
+ Object.keys(initialServiceTierByFamily).length > 0 ? initialServiceTierByFamily : null,
3283
+ );
3284
+ }
3255
3285
  } else {
3256
3286
  // Save initial model, thinking level, and service tier for new sessions so they can be restored on resume.
3257
3287
  if (model) {
@@ -3262,8 +3292,10 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
3262
3292
  // classification persists its concrete effort once a real user turn runs.
3263
3293
  sessionManager.appendThinkingLevelChange(effectiveThinkingLevel);
3264
3294
  }
3265
- if (Object.keys(initialServiceTierByFamily).length > 0) {
3266
- sessionManager.appendServiceTierChange(initialServiceTierByFamily);
3295
+ if (options.openAIServiceTier !== undefined || Object.keys(initialServiceTierByFamily).length > 0) {
3296
+ sessionManager.appendServiceTierChange(
3297
+ Object.keys(initialServiceTierByFamily).length > 0 ? initialServiceTierByFamily : null,
3298
+ );
3267
3299
  }
3268
3300
  }
3269
3301
 
@@ -3285,6 +3317,9 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
3285
3317
  const id = sessionManager.getSessionId?.();
3286
3318
  return id ? `${id}-advisor` : null;
3287
3319
  },
3320
+ queueLaunchCompletion: notification =>
3321
+ session?.queueLaunchCompletion(notification) ??
3322
+ Promise.reject(new Error("Session unavailable for launch completion delivery")),
3288
3323
  getAgentId: () => "advisor",
3289
3324
  // The primary's availability signals are wrong for advisors: their tool
3290
3325
  // slate is filtered separately at runtime (default read/grep/glob, no
@@ -3341,6 +3376,7 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
3341
3376
  initialAdvisorCosts,
3342
3377
  settings,
3343
3378
  autoApprove: options.autoApprove,
3379
+ scoutAllowedBySpawnPolicy: isScoutSpawnable(undefined, options.spawns ?? "*"),
3344
3380
  evalKernelOwnerId,
3345
3381
  // Defined only for top-level sessions (creation is gated above).
3346
3382
  // AgentSession uses this to decide whether it may dispose the global
@@ -3499,6 +3535,8 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
3499
3535
  unsubscribeCredentialDisabled?.();
3500
3536
  unsubscribeMcpNotifications?.();
3501
3537
  unregisterMcpPostmortem?.();
3538
+ for (const callback of disposeCallbacks) callback();
3539
+ disposeCallbacks.clear();
3502
3540
  // Drop refs so the process-global postmortem list doesn't retain
3503
3541
  // the bridge closure past explicit dispose.
3504
3542
  unsubscribeMcpNotifications = undefined;
@@ -3543,6 +3581,11 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
3543
3581
  }
3544
3582
  }
3545
3583
 
3584
+ // Broker-shared language servers: one server per project, multiplexed
3585
+ // across omp instances by the LSP mux daemon. Session-level because the
3586
+ // flag lives in module state consulted on every client cold-start.
3587
+ setSharedLspEnabled(enableLsp && settings.get("lsp.shared"));
3588
+
3546
3589
  // Start LSP warmup in the background so startup does not block on language server initialization.
3547
3590
  // With `lsp.lazy` (the default) the warmup is skipped: recognized servers are still discovered and
3548
3591
  // surfaced in the UI as "available", but cold-start on first use — the lsp tool or an edit/write
@@ -203,7 +203,7 @@ export function collectEnvSecrets(): SecretEntry[] {
203
203
  * API keys) that are NOT configured via secrets.yml or the environment. Without
204
204
  * these, such a token in a tool result falls through to pi-ai's irreversible
205
205
  * provider-boundary redaction (`[openai_token_redacted]`); the model then echoes
206
- * that placeholder into edit-tool `old_text`, which can never match the real
206
+ * that placeholder into edit-tool `old_string`, which can never match the real
207
207
  * bytes on disk (issue #6968). Routing the same shapes through the obfuscator
208
208
  * mints reversible keyed placeholders that `deobfuscateToolArguments` restores
209
209
  * before tool execution, keeping exact-match edits working while the credential
@@ -1,201 +1,223 @@
1
- import { type } from "arktype";
1
+ import { once } from "@oh-my-pi/pi-utils";
2
+ import { scope } from "arktype";
2
3
 
3
- const stringRecordSchema = type({ "[string]": "string" });
4
- const unknownRecordSchema = type({ "[string]": "unknown" });
4
+ export const getSecurityContractSchemas = once(() => {
5
+ // Security schemas validate only during security scans, so lazy construction
6
+ // with interpreted traversal avoids eager JIT startup tax without changing correctness.
7
+ const { type } = scope({}, { jitless: true });
5
8
 
6
- export const securityProducerSchema = type({
7
- kind: "'omp-native' | 'codex-security-bundle' | 'codex-security-cloud' | 'sarif-import'",
8
- name: "string > 0",
9
- "version?": "string",
10
- "vendor?": "string",
11
- "revision?": "string",
12
- "pluginVersion?": "string",
13
- });
9
+ const stringRecordSchema = type({ "[string]": "string" });
10
+ const unknownRecordSchema = type({ "[string]": "unknown" });
14
11
 
15
- export const securityProvenanceSchema = type({
16
- producer: securityProducerSchema,
17
- createdAt: "string > 0",
18
- "importedAt?": "string",
19
- "sourceIds?": stringRecordSchema,
20
- "vendorFingerprints?": stringRecordSchema,
21
- "upstream?": {
22
- "repository?": "string",
12
+ const securityProducerSchema = type({
13
+ kind: "'omp-native' | 'codex-security-bundle' | 'codex-security-cloud' | 'sarif-import'",
14
+ name: "string > 0",
15
+ "version?": "string",
16
+ "vendor?": "string",
23
17
  "revision?": "string",
24
- "packageVersion?": "string",
25
18
  "pluginVersion?": "string",
26
- "archiveSha256?": "string",
27
- },
28
- "metadata?": unknownRecordSchema,
29
- });
19
+ });
30
20
 
31
- export const securityLocationSchema = type({
32
- path: "string > 0",
33
- startLine: "number.integer >= 1",
34
- "endLine?": "number.integer >= 1",
35
- "startColumn?": "number.integer >= 1",
36
- "endColumn?": "number.integer >= 1",
37
- "role?": "string",
38
- });
21
+ const securityProvenanceSchema = type({
22
+ producer: securityProducerSchema,
23
+ createdAt: "string > 0",
24
+ "importedAt?": "string",
25
+ "sourceIds?": stringRecordSchema,
26
+ "vendorFingerprints?": stringRecordSchema,
27
+ "upstream?": {
28
+ "repository?": "string",
29
+ "revision?": "string",
30
+ "packageVersion?": "string",
31
+ "pluginVersion?": "string",
32
+ "archiveSha256?": "string",
33
+ },
34
+ "metadata?": unknownRecordSchema,
35
+ });
39
36
 
40
- export const securityEvidenceSchema = type({
41
- id: "string > 0",
42
- kind: "'code' | 'trace' | 'validation' | 'note'",
43
- label: "string > 0",
44
- explanation: "string",
45
- "location?": securityLocationSchema,
46
- "excerpt?": "string",
47
- });
37
+ const securityLocationSchema = type({
38
+ path: "string > 0",
39
+ startLine: "number.integer >= 1",
40
+ "endLine?": "number.integer >= 1",
41
+ "startColumn?": "number.integer >= 1",
42
+ "endColumn?": "number.integer >= 1",
43
+ "role?": "string",
44
+ });
48
45
 
49
- export const securityOccurrenceSchema = type({
50
- id: "string > 0",
51
- locations: securityLocationSchema.array().atLeastLength(1),
52
- evidenceIds: "string[]",
53
- });
46
+ const securityEvidenceSchema = type({
47
+ id: "string > 0",
48
+ kind: "'code' | 'trace' | 'validation' | 'note'",
49
+ label: "string > 0",
50
+ explanation: "string",
51
+ "location?": securityLocationSchema,
52
+ "excerpt?": "string",
53
+ });
54
54
 
55
- export const securityFindingSchema = type({
56
- id: "string > 0",
57
- scanId: "string > 0",
58
- fingerprint: "string > 0",
59
- ruleId: "string > 0",
60
- "anchor?": "string",
61
- title: "string > 0",
62
- summary: "string",
63
- severity: {
64
- level: "'critical' | 'high' | 'medium' | 'low' | 'informational'",
65
- "score?": "number",
66
- "scoringSystem?": "string",
67
- "vector?": "string",
68
- "rationale?": "string",
69
- },
70
- confidence: {
71
- level: "'high' | 'medium' | 'low'",
72
- "rationale?": "string",
73
- },
74
- taxonomy: {
75
- category: "string > 0",
76
- cwe: "string[]",
77
- "tags?": "string[]",
78
- },
79
- occurrences: securityOccurrenceSchema.array().atLeastLength(1),
80
- evidence: securityEvidenceSchema.array(),
81
- "remediation?": "string",
82
- validation: {
83
- status: "'unvalidated' | 'validated' | 'rejected' | 'partial' | 'error'",
84
- "summary?": "string",
55
+ const securityOccurrenceSchema = type({
56
+ id: "string > 0",
57
+ locations: securityLocationSchema.array().atLeastLength(1),
85
58
  evidenceIds: "string[]",
86
- "validatedAt?": "string",
87
- },
88
- disposition: {
89
- status: "'open' | 'false_positive' | 'accepted_risk' | 'fixed' | 'wont_fix'",
90
- "rationale?": "string",
91
- "updatedAt?": "string",
92
- "actor?": "string",
93
- },
94
- provenance: securityProvenanceSchema,
95
- "extensions?": unknownRecordSchema,
96
- });
59
+ });
97
60
 
98
- export const securityCoverageSchema = type({
99
- mode: "'repository' | 'scoped_path' | 'diff' | 'working_tree' | 'deep_repository' | 'imported'",
100
- completeness: "'complete' | 'partial' | 'unknown'",
101
- inventoryStrategy: "'repository' | 'scoped_path' | 'diff' | 'directory' | 'custom' | 'imported'",
102
- includePaths: "string[]",
103
- excludePaths: "string[]",
104
- surfaces: type({
105
- id: "string > 0",
106
- label: "string > 0",
107
- disposition: "'reported' | 'no_issue_found' | 'rejected' | 'not_applicable' | 'needs_follow_up'",
108
- receiptRefs: "string[]",
109
- "riskArea?": "string",
110
- "notes?": "string",
111
- }).array(),
112
- explicitExclusions: type({ pattern: "string", reason: "string" }).array(),
113
- deferred: type({
61
+ const securityFindingSchema = type({
114
62
  id: "string > 0",
115
- reason: "string > 0",
116
- "paths?": "string[]",
117
- "surfaceIds?": "string[]",
118
- }).array(),
119
- "openQuestions?": type({ question: "string > 0", "followUpPrompt?": "string" }).array(),
120
- });
63
+ scanId: "string > 0",
64
+ fingerprint: "string > 0",
65
+ ruleId: "string > 0",
66
+ "anchor?": "string",
67
+ title: "string > 0",
68
+ summary: "string",
69
+ severity: {
70
+ level: "'critical' | 'high' | 'medium' | 'low' | 'informational'",
71
+ "score?": "number",
72
+ "scoringSystem?": "string",
73
+ "vector?": "string",
74
+ "rationale?": "string",
75
+ },
76
+ confidence: {
77
+ level: "'high' | 'medium' | 'low'",
78
+ "rationale?": "string",
79
+ },
80
+ taxonomy: {
81
+ category: "string > 0",
82
+ cwe: "string[]",
83
+ "tags?": "string[]",
84
+ },
85
+ occurrences: securityOccurrenceSchema.array().atLeastLength(1),
86
+ evidence: securityEvidenceSchema.array(),
87
+ "remediation?": "string",
88
+ validation: {
89
+ status: "'unvalidated' | 'validated' | 'rejected' | 'partial' | 'error'",
90
+ "summary?": "string",
91
+ evidenceIds: "string[]",
92
+ "validatedAt?": "string",
93
+ },
94
+ disposition: {
95
+ status: "'open' | 'false_positive' | 'accepted_risk' | 'fixed' | 'wont_fix'",
96
+ "rationale?": "string",
97
+ "updatedAt?": "string",
98
+ "actor?": "string",
99
+ },
100
+ provenance: securityProvenanceSchema,
101
+ "extensions?": unknownRecordSchema,
102
+ });
121
103
 
122
- export const securityTargetSchema = type({
123
- kind: "'repository' | 'scoped_path' | 'ref_diff' | 'working_tree' | 'imported'",
124
- repositoryRoot: "string > 0",
125
- displayName: "string > 0",
126
- "revision?": "string",
127
- "baseRevision?": "string",
128
- "headRevision?": "string",
129
- includePaths: "string[]",
130
- excludePaths: "string[]",
131
- treeDigest: "string > 0",
132
- });
104
+ const securityCoverageSchema = type({
105
+ mode: "'repository' | 'scoped_path' | 'diff' | 'working_tree' | 'deep_repository' | 'imported'",
106
+ completeness: "'complete' | 'partial' | 'unknown'",
107
+ inventoryStrategy: "'repository' | 'scoped_path' | 'diff' | 'directory' | 'custom' | 'imported'",
108
+ includePaths: "string[]",
109
+ excludePaths: "string[]",
110
+ surfaces: type({
111
+ id: "string > 0",
112
+ label: "string > 0",
113
+ disposition: "'reported' | 'no_issue_found' | 'rejected' | 'not_applicable' | 'needs_follow_up'",
114
+ receiptRefs: "string[]",
115
+ "riskArea?": "string",
116
+ "notes?": "string",
117
+ }).array(),
118
+ explicitExclusions: type({ pattern: "string", reason: "string" }).array(),
119
+ deferred: type({
120
+ id: "string > 0",
121
+ reason: "string > 0",
122
+ "paths?": "string[]",
123
+ "surfaceIds?": "string[]",
124
+ }).array(),
125
+ "openQuestions?": type({ question: "string > 0", "followUpPrompt?": "string" }).array(),
126
+ });
133
127
 
134
- export const securityScanPlanSchema = type({
135
- documentType: "'omp-security.scan-plan'",
136
- schemaVersion: "'1.0'",
137
- id: "string > 0",
138
- createdAt: "string > 0",
139
- repositoryRoot: "string > 0",
140
- target: securityTargetSchema,
141
- knowledgeBases: type({ path: "string > 0", sha256: "string > 0", size: "number.integer >= 0" }).array(),
142
- output: {
143
- root: "string > 0",
144
- archiveExisting: "boolean",
145
- existingState: "'absent' | 'empty' | 'archivable'",
146
- },
147
- model: { provider: "string > 0", modelId: "string > 0", "thinkingLevel?": "string" },
148
- account: {
149
- provider: "string > 0",
150
- credentialId: "number.integer >= 1",
151
- "accountId?": "string",
152
- "email?": "string",
153
- "organizationId?": "string",
154
- "organizationName?": "string",
155
- },
156
- configFingerprint: "string > 0",
157
- workflowFingerprint: "string > 0",
158
- fingerprint: "string > 0",
159
- });
128
+ const securityTargetSchema = type({
129
+ kind: "'repository' | 'scoped_path' | 'ref_diff' | 'working_tree' | 'imported'",
130
+ repositoryRoot: "string > 0",
131
+ displayName: "string > 0",
132
+ "revision?": "string",
133
+ "baseRevision?": "string",
134
+ "headRevision?": "string",
135
+ includePaths: "string[]",
136
+ excludePaths: "string[]",
137
+ treeDigest: "string > 0",
138
+ });
160
139
 
161
- export const securityScanMetricsSchema = type({
162
- "runtimeMs?": "number >= 0",
163
- "tokenUsage?": {
164
- input: "number >= 0",
165
- output: "number >= 0",
166
- reasoning: "number >= 0",
167
- cacheRead: "number >= 0",
168
- cacheWrite: "number >= 0",
169
- total: "number >= 0",
170
- },
171
- "cost?": "number >= 0",
172
- "premiumRequests?": "number >= 0",
173
- });
140
+ const securityScanPlanSchema = type({
141
+ documentType: "'omp-security.scan-plan'",
142
+ schemaVersion: "'1.0'",
143
+ id: "string > 0",
144
+ createdAt: "string > 0",
145
+ repositoryRoot: "string > 0",
146
+ target: securityTargetSchema,
147
+ knowledgeBases: type({ path: "string > 0", sha256: "string > 0", size: "number.integer >= 0" }).array(),
148
+ output: {
149
+ root: "string > 0",
150
+ archiveExisting: "boolean",
151
+ existingState: "'absent' | 'empty' | 'archivable'",
152
+ },
153
+ model: { provider: "string > 0", modelId: "string > 0", "thinkingLevel?": "string" },
154
+ account: {
155
+ provider: "string > 0",
156
+ credentialId: "number.integer >= 1",
157
+ "accountId?": "string",
158
+ "email?": "string",
159
+ "organizationId?": "string",
160
+ "organizationName?": "string",
161
+ },
162
+ configFingerprint: "string > 0",
163
+ workflowFingerprint: "string > 0",
164
+ fingerprint: "string > 0",
165
+ });
174
166
 
175
- export const securityScanSchema = type({
176
- documentType: "'omp-security.scan'",
177
- schemaVersion: "'1.0'",
178
- id: "string > 0",
179
- projectKey: "string > 0",
180
- status: "'planned' | 'running' | 'completed' | 'partial' | 'cancelled' | 'failed'",
181
- createdAt: "string > 0",
182
- "startedAt?": "string",
183
- "completedAt?": "string",
184
- "plan?": securityScanPlanSchema,
185
- target: securityTargetSchema,
186
- producer: securityProducerSchema,
187
- provenance: securityProvenanceSchema,
188
- findingIds: "string[]",
189
- coverage: securityCoverageSchema,
190
- "reportRef?": "string",
191
- "sarifRef?": "string",
192
- "error?": "string",
193
- "metrics?": securityScanMetricsSchema,
194
- });
167
+ const securityScanMetricsSchema = type({
168
+ "runtimeMs?": "number >= 0",
169
+ "tokenUsage?": {
170
+ input: "number >= 0",
171
+ output: "number >= 0",
172
+ reasoning: "number >= 0",
173
+ cacheRead: "number >= 0",
174
+ cacheWrite: "number >= 0",
175
+ total: "number >= 0",
176
+ },
177
+ "cost?": "number >= 0",
178
+ "premiumRequests?": "number >= 0",
179
+ });
180
+
181
+ const securityScanSchema = type({
182
+ documentType: "'omp-security.scan'",
183
+ schemaVersion: "'1.0'",
184
+ id: "string > 0",
185
+ projectKey: "string > 0",
186
+ status: "'planned' | 'running' | 'completed' | 'partial' | 'cancelled' | 'failed'",
187
+ createdAt: "string > 0",
188
+ "startedAt?": "string",
189
+ "completedAt?": "string",
190
+ "plan?": securityScanPlanSchema,
191
+ target: securityTargetSchema,
192
+ producer: securityProducerSchema,
193
+ provenance: securityProvenanceSchema,
194
+ findingIds: "string[]",
195
+ coverage: securityCoverageSchema,
196
+ "reportRef?": "string",
197
+ "sarifRef?": "string",
198
+ "error?": "string",
199
+ "metrics?": securityScanMetricsSchema,
200
+ });
201
+
202
+ const securityScanBundleSchema = type({
203
+ scan: securityScanSchema,
204
+ findings: securityFindingSchema.array(),
205
+ "report?": "string",
206
+ "sarif?": unknownRecordSchema,
207
+ });
195
208
 
196
- export const securityScanBundleSchema = type({
197
- scan: securityScanSchema,
198
- findings: securityFindingSchema.array(),
199
- "report?": "string",
200
- "sarif?": unknownRecordSchema,
209
+ return {
210
+ securityProducerSchema,
211
+ securityProvenanceSchema,
212
+ securityLocationSchema,
213
+ securityEvidenceSchema,
214
+ securityOccurrenceSchema,
215
+ securityFindingSchema,
216
+ securityCoverageSchema,
217
+ securityTargetSchema,
218
+ securityScanPlanSchema,
219
+ securityScanMetricsSchema,
220
+ securityScanSchema,
221
+ securityScanBundleSchema,
222
+ };
201
223
  });
@@ -1,5 +1,5 @@
1
1
  import { type } from "arktype";
2
- import { securityFindingSchema, securityScanBundleSchema, securityScanPlanSchema, securityScanSchema } from "./schemas";
2
+ import { getSecurityContractSchemas } from "./schemas";
3
3
  import type { SecurityFinding, SecurityScan, SecurityScanBundle, SecurityScanPlan } from "./types";
4
4
 
5
5
  function schemaError(label: string, errors: type.errors): Error {
@@ -7,24 +7,28 @@ function schemaError(label: string, errors: type.errors): Error {
7
7
  }
8
8
 
9
9
  export function parseSecurityFinding(value: unknown): SecurityFinding {
10
+ const { securityFindingSchema } = getSecurityContractSchemas();
10
11
  const result = securityFindingSchema(value);
11
12
  if (result instanceof type.errors) throw schemaError("Security finding", result);
12
13
  return result as SecurityFinding;
13
14
  }
14
15
 
15
16
  export function parseSecurityScan(value: unknown): SecurityScan {
17
+ const { securityScanSchema } = getSecurityContractSchemas();
16
18
  const result = securityScanSchema(value);
17
19
  if (result instanceof type.errors) throw schemaError("Security scan", result);
18
20
  return result as SecurityScan;
19
21
  }
20
22
 
21
23
  export function parseSecurityScanPlan(value: unknown): SecurityScanPlan {
24
+ const { securityScanPlanSchema } = getSecurityContractSchemas();
22
25
  const result = securityScanPlanSchema(value);
23
26
  if (result instanceof type.errors) throw schemaError("Security scan plan", result);
24
27
  return result as SecurityScanPlan;
25
28
  }
26
29
 
27
30
  export function parseSecurityScanBundle(value: unknown): SecurityScanBundle {
31
+ const { securityScanBundleSchema } = getSecurityContractSchemas();
28
32
  const result = securityScanBundleSchema(value);
29
33
  if (result instanceof type.errors) throw schemaError("Security scan bundle", result);
30
34
  const bundle = result as SecurityScanBundle;
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { getSecurityProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
4
- import { withFileLock } from "../config/file-lock";
4
+ import { withFileLock } from "@oh-my-pi/pi-utils/file-lock";
5
5
  import * as git from "../utils/git";
6
6
  import { compareSecurityLineage } from "./comparison";
7
7
  import type {
@@ -34,7 +34,7 @@ const SECURITY_STORE_WRITE_CHAINS = new Map<string, Promise<unknown>>();
34
34
  async function withSecurityStoreWrite<T>(key: string, operation: () => Promise<T>): Promise<T> {
35
35
  const lockTarget = path.join(key, "index.json");
36
36
  const run = (SECURITY_STORE_WRITE_CHAINS.get(key) ?? Promise.resolve()).then(() =>
37
- withFileLock(lockTarget, operation, { staleMs: 60_000, retries: 200, retryDelayMs: 50 }),
37
+ withFileLock(lockTarget, operation, { retries: 200, retryDelayMs: 50 }),
38
38
  );
39
39
  const guarded = run.catch(() => undefined);
40
40
  SECURITY_STORE_WRITE_CHAINS.set(key, guarded);