@oh-my-pi/pi-coding-agent 0.1.0

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 (337) hide show
  1. package/CHANGELOG.md +1629 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/config-usage.md +113 -0
  5. package/docs/custom-tools.md +541 -0
  6. package/docs/extension-loading.md +1004 -0
  7. package/docs/hooks.md +867 -0
  8. package/docs/rpc.md +1040 -0
  9. package/docs/sdk.md +994 -0
  10. package/docs/session-tree-plan.md +441 -0
  11. package/docs/session.md +240 -0
  12. package/docs/skills.md +290 -0
  13. package/docs/theme.md +670 -0
  14. package/docs/tree.md +197 -0
  15. package/docs/tui.md +341 -0
  16. package/examples/README.md +21 -0
  17. package/examples/custom-tools/README.md +124 -0
  18. package/examples/custom-tools/hello/index.ts +20 -0
  19. package/examples/custom-tools/question/index.ts +84 -0
  20. package/examples/custom-tools/subagent/README.md +172 -0
  21. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  22. package/examples/custom-tools/subagent/agents/scout.md +50 -0
  23. package/examples/custom-tools/subagent/agents/worker.md +24 -0
  24. package/examples/custom-tools/subagent/agents.ts +156 -0
  25. package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
  26. package/examples/custom-tools/subagent/commands/implement.md +10 -0
  27. package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
  28. package/examples/custom-tools/subagent/index.ts +1002 -0
  29. package/examples/custom-tools/todo/index.ts +212 -0
  30. package/examples/hooks/README.md +56 -0
  31. package/examples/hooks/auto-commit-on-exit.ts +49 -0
  32. package/examples/hooks/confirm-destructive.ts +59 -0
  33. package/examples/hooks/custom-compaction.ts +116 -0
  34. package/examples/hooks/dirty-repo-guard.ts +52 -0
  35. package/examples/hooks/file-trigger.ts +41 -0
  36. package/examples/hooks/git-checkpoint.ts +53 -0
  37. package/examples/hooks/handoff.ts +150 -0
  38. package/examples/hooks/permission-gate.ts +34 -0
  39. package/examples/hooks/protected-paths.ts +30 -0
  40. package/examples/hooks/qna.ts +119 -0
  41. package/examples/hooks/snake.ts +343 -0
  42. package/examples/hooks/status-line.ts +40 -0
  43. package/examples/sdk/01-minimal.ts +22 -0
  44. package/examples/sdk/02-custom-model.ts +49 -0
  45. package/examples/sdk/03-custom-prompt.ts +44 -0
  46. package/examples/sdk/04-skills.ts +44 -0
  47. package/examples/sdk/05-tools.ts +90 -0
  48. package/examples/sdk/06-hooks.ts +61 -0
  49. package/examples/sdk/07-context-files.ts +36 -0
  50. package/examples/sdk/08-slash-commands.ts +42 -0
  51. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  52. package/examples/sdk/10-settings.ts +38 -0
  53. package/examples/sdk/11-sessions.ts +48 -0
  54. package/examples/sdk/12-full-control.ts +95 -0
  55. package/examples/sdk/README.md +154 -0
  56. package/package.json +89 -0
  57. package/src/bun-imports.d.ts +16 -0
  58. package/src/capability/context-file.ts +40 -0
  59. package/src/capability/extension.ts +48 -0
  60. package/src/capability/hook.ts +40 -0
  61. package/src/capability/index.ts +616 -0
  62. package/src/capability/instruction.ts +37 -0
  63. package/src/capability/mcp.ts +52 -0
  64. package/src/capability/prompt.ts +35 -0
  65. package/src/capability/rule.ts +56 -0
  66. package/src/capability/settings.ts +35 -0
  67. package/src/capability/skill.ts +49 -0
  68. package/src/capability/slash-command.ts +40 -0
  69. package/src/capability/system-prompt.ts +35 -0
  70. package/src/capability/tool.ts +38 -0
  71. package/src/capability/types.ts +166 -0
  72. package/src/cli/args.ts +259 -0
  73. package/src/cli/file-processor.ts +121 -0
  74. package/src/cli/list-models.ts +104 -0
  75. package/src/cli/plugin-cli.ts +661 -0
  76. package/src/cli/session-picker.ts +41 -0
  77. package/src/cli/update-cli.ts +274 -0
  78. package/src/cli.ts +10 -0
  79. package/src/config.ts +391 -0
  80. package/src/core/agent-session.ts +2178 -0
  81. package/src/core/auth-storage.ts +258 -0
  82. package/src/core/bash-executor.ts +197 -0
  83. package/src/core/compaction/branch-summarization.ts +315 -0
  84. package/src/core/compaction/compaction.ts +664 -0
  85. package/src/core/compaction/index.ts +7 -0
  86. package/src/core/compaction/utils.ts +153 -0
  87. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  88. package/src/core/custom-commands/index.ts +15 -0
  89. package/src/core/custom-commands/loader.ts +226 -0
  90. package/src/core/custom-commands/types.ts +112 -0
  91. package/src/core/custom-tools/index.ts +22 -0
  92. package/src/core/custom-tools/loader.ts +248 -0
  93. package/src/core/custom-tools/types.ts +185 -0
  94. package/src/core/custom-tools/wrapper.ts +29 -0
  95. package/src/core/exec.ts +139 -0
  96. package/src/core/export-html/index.ts +159 -0
  97. package/src/core/export-html/template.css +774 -0
  98. package/src/core/export-html/template.generated.ts +2 -0
  99. package/src/core/export-html/template.html +45 -0
  100. package/src/core/export-html/template.js +1185 -0
  101. package/src/core/export-html/template.macro.ts +24 -0
  102. package/src/core/file-mentions.ts +54 -0
  103. package/src/core/hooks/index.ts +16 -0
  104. package/src/core/hooks/loader.ts +288 -0
  105. package/src/core/hooks/runner.ts +434 -0
  106. package/src/core/hooks/tool-wrapper.ts +98 -0
  107. package/src/core/hooks/types.ts +770 -0
  108. package/src/core/index.ts +53 -0
  109. package/src/core/logger.ts +112 -0
  110. package/src/core/mcp/client.ts +185 -0
  111. package/src/core/mcp/config.ts +248 -0
  112. package/src/core/mcp/index.ts +45 -0
  113. package/src/core/mcp/loader.ts +99 -0
  114. package/src/core/mcp/manager.ts +235 -0
  115. package/src/core/mcp/tool-bridge.ts +156 -0
  116. package/src/core/mcp/transports/http.ts +316 -0
  117. package/src/core/mcp/transports/index.ts +6 -0
  118. package/src/core/mcp/transports/stdio.ts +252 -0
  119. package/src/core/mcp/types.ts +228 -0
  120. package/src/core/messages.ts +211 -0
  121. package/src/core/model-registry.ts +334 -0
  122. package/src/core/model-resolver.ts +494 -0
  123. package/src/core/plugins/doctor.ts +67 -0
  124. package/src/core/plugins/index.ts +38 -0
  125. package/src/core/plugins/installer.ts +189 -0
  126. package/src/core/plugins/loader.ts +339 -0
  127. package/src/core/plugins/manager.ts +672 -0
  128. package/src/core/plugins/parser.ts +105 -0
  129. package/src/core/plugins/paths.ts +37 -0
  130. package/src/core/plugins/types.ts +190 -0
  131. package/src/core/sdk.ts +900 -0
  132. package/src/core/session-manager.ts +1837 -0
  133. package/src/core/settings-manager.ts +860 -0
  134. package/src/core/skills.ts +352 -0
  135. package/src/core/slash-commands.ts +132 -0
  136. package/src/core/system-prompt.ts +442 -0
  137. package/src/core/timings.ts +25 -0
  138. package/src/core/title-generator.ts +110 -0
  139. package/src/core/tools/ask.ts +193 -0
  140. package/src/core/tools/bash-interceptor.ts +120 -0
  141. package/src/core/tools/bash.ts +91 -0
  142. package/src/core/tools/context.ts +32 -0
  143. package/src/core/tools/edit-diff.ts +487 -0
  144. package/src/core/tools/edit.ts +140 -0
  145. package/src/core/tools/exa/company.ts +59 -0
  146. package/src/core/tools/exa/index.ts +63 -0
  147. package/src/core/tools/exa/linkedin.ts +59 -0
  148. package/src/core/tools/exa/mcp-client.ts +368 -0
  149. package/src/core/tools/exa/render.ts +200 -0
  150. package/src/core/tools/exa/researcher.ts +90 -0
  151. package/src/core/tools/exa/search.ts +338 -0
  152. package/src/core/tools/exa/types.ts +167 -0
  153. package/src/core/tools/exa/websets.ts +248 -0
  154. package/src/core/tools/find.ts +244 -0
  155. package/src/core/tools/grep.ts +584 -0
  156. package/src/core/tools/index.ts +283 -0
  157. package/src/core/tools/ls.ts +142 -0
  158. package/src/core/tools/lsp/client.ts +767 -0
  159. package/src/core/tools/lsp/clients/biome-client.ts +207 -0
  160. package/src/core/tools/lsp/clients/index.ts +49 -0
  161. package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
  162. package/src/core/tools/lsp/config.ts +845 -0
  163. package/src/core/tools/lsp/edits.ts +110 -0
  164. package/src/core/tools/lsp/index.ts +1364 -0
  165. package/src/core/tools/lsp/render.ts +560 -0
  166. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  167. package/src/core/tools/lsp/types.ts +495 -0
  168. package/src/core/tools/lsp/utils.ts +526 -0
  169. package/src/core/tools/notebook.ts +182 -0
  170. package/src/core/tools/output.ts +198 -0
  171. package/src/core/tools/path-utils.ts +61 -0
  172. package/src/core/tools/read.ts +507 -0
  173. package/src/core/tools/renderers.ts +820 -0
  174. package/src/core/tools/review.ts +275 -0
  175. package/src/core/tools/rulebook.ts +124 -0
  176. package/src/core/tools/task/agents.ts +158 -0
  177. package/src/core/tools/task/artifacts.ts +114 -0
  178. package/src/core/tools/task/commands.ts +157 -0
  179. package/src/core/tools/task/discovery.ts +217 -0
  180. package/src/core/tools/task/executor.ts +531 -0
  181. package/src/core/tools/task/index.ts +548 -0
  182. package/src/core/tools/task/model-resolver.ts +176 -0
  183. package/src/core/tools/task/parallel.ts +38 -0
  184. package/src/core/tools/task/render.ts +502 -0
  185. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  186. package/src/core/tools/task/types.ts +142 -0
  187. package/src/core/tools/truncate.ts +265 -0
  188. package/src/core/tools/web-fetch.ts +2511 -0
  189. package/src/core/tools/web-search/auth.ts +199 -0
  190. package/src/core/tools/web-search/index.ts +583 -0
  191. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  192. package/src/core/tools/web-search/providers/exa.ts +196 -0
  193. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  194. package/src/core/tools/web-search/render.ts +372 -0
  195. package/src/core/tools/web-search/types.ts +180 -0
  196. package/src/core/tools/write.ts +63 -0
  197. package/src/core/ttsr.ts +211 -0
  198. package/src/core/utils.ts +187 -0
  199. package/src/discovery/agents-md.ts +75 -0
  200. package/src/discovery/builtin.ts +647 -0
  201. package/src/discovery/claude.ts +623 -0
  202. package/src/discovery/cline.ts +104 -0
  203. package/src/discovery/codex.ts +571 -0
  204. package/src/discovery/cursor.ts +266 -0
  205. package/src/discovery/gemini.ts +368 -0
  206. package/src/discovery/github.ts +120 -0
  207. package/src/discovery/helpers.test.ts +127 -0
  208. package/src/discovery/helpers.ts +249 -0
  209. package/src/discovery/index.ts +84 -0
  210. package/src/discovery/mcp-json.ts +127 -0
  211. package/src/discovery/vscode.ts +99 -0
  212. package/src/discovery/windsurf.ts +219 -0
  213. package/src/index.ts +192 -0
  214. package/src/main.ts +507 -0
  215. package/src/migrations.ts +156 -0
  216. package/src/modes/cleanup.ts +23 -0
  217. package/src/modes/index.ts +48 -0
  218. package/src/modes/interactive/components/armin.ts +382 -0
  219. package/src/modes/interactive/components/assistant-message.ts +86 -0
  220. package/src/modes/interactive/components/bash-execution.ts +199 -0
  221. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  222. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  223. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  224. package/src/modes/interactive/components/custom-editor.ts +122 -0
  225. package/src/modes/interactive/components/diff.ts +147 -0
  226. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  227. package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
  228. package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
  229. package/src/modes/interactive/components/extensions/index.ts +9 -0
  230. package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
  231. package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
  232. package/src/modes/interactive/components/extensions/types.ts +191 -0
  233. package/src/modes/interactive/components/hook-editor.ts +117 -0
  234. package/src/modes/interactive/components/hook-input.ts +64 -0
  235. package/src/modes/interactive/components/hook-message.ts +96 -0
  236. package/src/modes/interactive/components/hook-selector.ts +91 -0
  237. package/src/modes/interactive/components/model-selector.ts +560 -0
  238. package/src/modes/interactive/components/oauth-selector.ts +136 -0
  239. package/src/modes/interactive/components/plugin-settings.ts +481 -0
  240. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  241. package/src/modes/interactive/components/session-selector.ts +220 -0
  242. package/src/modes/interactive/components/settings-defs.ts +597 -0
  243. package/src/modes/interactive/components/settings-selector.ts +545 -0
  244. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  245. package/src/modes/interactive/components/status-line/index.ts +4 -0
  246. package/src/modes/interactive/components/status-line/presets.ts +94 -0
  247. package/src/modes/interactive/components/status-line/segments.ts +350 -0
  248. package/src/modes/interactive/components/status-line/separators.ts +55 -0
  249. package/src/modes/interactive/components/status-line/types.ts +81 -0
  250. package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
  251. package/src/modes/interactive/components/status-line.ts +384 -0
  252. package/src/modes/interactive/components/theme-selector.ts +62 -0
  253. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  254. package/src/modes/interactive/components/tool-execution.ts +946 -0
  255. package/src/modes/interactive/components/tree-selector.ts +877 -0
  256. package/src/modes/interactive/components/ttsr-notification.ts +82 -0
  257. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  258. package/src/modes/interactive/components/user-message.ts +18 -0
  259. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  260. package/src/modes/interactive/components/welcome.ts +228 -0
  261. package/src/modes/interactive/interactive-mode.ts +2669 -0
  262. package/src/modes/interactive/theme/dark.json +102 -0
  263. package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
  264. package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
  265. package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
  266. package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
  267. package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
  268. package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
  269. package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
  270. package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
  271. package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
  272. package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
  273. package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
  274. package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
  275. package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
  276. package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
  277. package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
  278. package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
  279. package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
  280. package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
  281. package/src/modes/interactive/theme/defaults/index.ts +67 -0
  282. package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
  283. package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
  284. package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
  285. package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
  286. package/src/modes/interactive/theme/defaults/light-github.json +114 -0
  287. package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
  288. package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
  289. package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
  290. package/src/modes/interactive/theme/defaults/light-one.json +105 -0
  291. package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
  292. package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
  293. package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
  294. package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
  295. package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
  296. package/src/modes/interactive/theme/light.json +99 -0
  297. package/src/modes/interactive/theme/theme-schema.json +424 -0
  298. package/src/modes/interactive/theme/theme.ts +2211 -0
  299. package/src/modes/print-mode.ts +163 -0
  300. package/src/modes/rpc/rpc-client.ts +527 -0
  301. package/src/modes/rpc/rpc-mode.ts +494 -0
  302. package/src/modes/rpc/rpc-types.ts +203 -0
  303. package/src/prompts/architect-plan.md +10 -0
  304. package/src/prompts/branch-summary-preamble.md +3 -0
  305. package/src/prompts/branch-summary.md +28 -0
  306. package/src/prompts/browser.md +71 -0
  307. package/src/prompts/compaction-summary.md +34 -0
  308. package/src/prompts/compaction-turn-prefix.md +16 -0
  309. package/src/prompts/compaction-update-summary.md +41 -0
  310. package/src/prompts/explore.md +82 -0
  311. package/src/prompts/implement-with-critic.md +11 -0
  312. package/src/prompts/implement.md +11 -0
  313. package/src/prompts/init.md +30 -0
  314. package/src/prompts/plan.md +54 -0
  315. package/src/prompts/reviewer.md +81 -0
  316. package/src/prompts/summarization-system.md +3 -0
  317. package/src/prompts/system-prompt.md +27 -0
  318. package/src/prompts/task.md +56 -0
  319. package/src/prompts/title-system.md +8 -0
  320. package/src/prompts/tools/ask.md +24 -0
  321. package/src/prompts/tools/bash.md +23 -0
  322. package/src/prompts/tools/edit.md +9 -0
  323. package/src/prompts/tools/find.md +6 -0
  324. package/src/prompts/tools/grep.md +12 -0
  325. package/src/prompts/tools/lsp.md +14 -0
  326. package/src/prompts/tools/output.md +23 -0
  327. package/src/prompts/tools/read.md +25 -0
  328. package/src/prompts/tools/web-fetch.md +8 -0
  329. package/src/prompts/tools/web-search.md +10 -0
  330. package/src/prompts/tools/write.md +10 -0
  331. package/src/utils/changelog.ts +99 -0
  332. package/src/utils/clipboard.ts +265 -0
  333. package/src/utils/fuzzy.ts +108 -0
  334. package/src/utils/mime.ts +30 -0
  335. package/src/utils/shell-snapshot.ts +218 -0
  336. package/src/utils/shell.ts +364 -0
  337. package/src/utils/tools-manager.ts +265 -0
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Review tools - report_finding and submit_review
3
+ *
4
+ * Used by the reviewer agent to report findings in a structured way.
5
+ * Both tools are hidden by default - only enabled when explicitly listed in agent's tools.
6
+ */
7
+
8
+ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
9
+ import type { Component } from "@oh-my-pi/pi-tui";
10
+ import { Container, Spacer, Text } from "@oh-my-pi/pi-tui";
11
+ import { Type } from "@sinclair/typebox";
12
+ import type { Theme } from "../../modes/interactive/theme/theme";
13
+ import { theme } from "../../modes/interactive/theme/theme";
14
+
15
+ const PRIORITY_LABELS: Record<number, string> = {
16
+ 0: "P0",
17
+ 1: "P1",
18
+ 2: "P2",
19
+ 3: "P3",
20
+ };
21
+
22
+ const _PRIORITY_DESCRIPTIONS: Record<number, string> = {
23
+ 0: "Drop everything to fix. Blocking release, operations, or major usage.",
24
+ 1: "Urgent. Should be addressed in the next cycle.",
25
+ 2: "Normal. To be fixed eventually.",
26
+ 3: "Low. Nice to have.",
27
+ };
28
+
29
+ // report_finding schema
30
+ const ReportFindingParams = Type.Object({
31
+ title: Type.String({
32
+ description: "≤80 chars, imperative, prefixed with [P0-P3]. E.g., '[P1] Un-padding slices along wrong dimension'",
33
+ }),
34
+ body: Type.String({
35
+ description: "Markdown explaining why this is a problem. One paragraph max.",
36
+ }),
37
+ priority: Type.Union([Type.Literal(0), Type.Literal(1), Type.Literal(2), Type.Literal(3)], {
38
+ description: "0=P0 (critical), 1=P1 (urgent), 2=P2 (normal), 3=P3 (low)",
39
+ }),
40
+ confidence: Type.Number({
41
+ minimum: 0,
42
+ maximum: 1,
43
+ description: "Confidence score 0.0-1.0",
44
+ }),
45
+ file_path: Type.String({ description: "Absolute path to the file" }),
46
+ line_start: Type.Number({ description: "Start line of the issue" }),
47
+ line_end: Type.Number({ description: "End line of the issue" }),
48
+ });
49
+
50
+ interface ReportFindingDetails {
51
+ title: string;
52
+ body: string;
53
+ priority: number;
54
+ confidence: number;
55
+ file_path: string;
56
+ line_start: number;
57
+ line_end: number;
58
+ }
59
+
60
+ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> = {
61
+ name: "report_finding",
62
+ label: "Report Finding",
63
+ description: "Report a code review finding. Use this for each issue found. Call submit_review when done.",
64
+ parameters: ReportFindingParams,
65
+ hidden: true,
66
+
67
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
68
+ const { title, body, priority, confidence, file_path, line_start, line_end } = params;
69
+ const location = `${file_path}:${line_start}${line_end !== line_start ? `-${line_end}` : ""}`;
70
+
71
+ return {
72
+ content: [
73
+ {
74
+ type: "text",
75
+ text: `Finding recorded: ${PRIORITY_LABELS[priority]} ${title}\nLocation: ${location}\nConfidence: ${(confidence * 100).toFixed(0)}%`,
76
+ },
77
+ ],
78
+ details: { title, body, priority, confidence, file_path, line_start, line_end },
79
+ };
80
+ },
81
+
82
+ renderCall(args, theme): Component {
83
+ const priority = PRIORITY_LABELS[args.priority as number] ?? "P?";
84
+ const color = args.priority === 0 ? "error" : args.priority === 1 ? "warning" : "muted";
85
+ const titleText = String(args.title).replace(/^\[P\d\]\s*/, "");
86
+ return new Text(
87
+ `${theme.fg("toolTitle", theme.bold("report_finding "))}${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", titleText)}`,
88
+ 0,
89
+ 0,
90
+ );
91
+ },
92
+
93
+ renderResult(result, _options, theme): Component {
94
+ const { details } = result;
95
+ if (!details) {
96
+ const text = result.content[0];
97
+ return new Text(text?.type === "text" ? text.text : "", 0, 0);
98
+ }
99
+
100
+ const priority = PRIORITY_LABELS[details.priority] ?? "P?";
101
+ const color = details.priority === 0 ? "error" : details.priority === 1 ? "warning" : "muted";
102
+ const location = `${details.file_path}:${details.line_start}${details.line_end !== details.line_start ? `-${details.line_end}` : ""}`;
103
+
104
+ return new Text(
105
+ `${theme.fg("success", theme.status.success)} ${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", location)}`,
106
+ 0,
107
+ 0,
108
+ );
109
+ },
110
+ };
111
+
112
+ // submit_review schema
113
+ const SubmitReviewParams = Type.Object({
114
+ overall_correctness: Type.Union([Type.Literal("correct"), Type.Literal("incorrect")], {
115
+ description: "Whether the patch is correct (no bugs, tests won't break)",
116
+ }),
117
+ explanation: Type.String({
118
+ description: "1-3 sentence explanation justifying the verdict",
119
+ }),
120
+ confidence: Type.Number({
121
+ minimum: 0,
122
+ maximum: 1,
123
+ description: "Overall confidence score 0.0-1.0",
124
+ }),
125
+ });
126
+
127
+ interface SubmitReviewDetails {
128
+ overall_correctness: "correct" | "incorrect";
129
+ explanation: string;
130
+ confidence: number;
131
+ }
132
+
133
+ export const submitReviewTool: AgentTool<typeof SubmitReviewParams, SubmitReviewDetails, Theme> = {
134
+ name: "submit_review",
135
+ label: "Submit Review",
136
+ description: "Submit the final review verdict. Call this after all findings have been reported.",
137
+ parameters: SubmitReviewParams,
138
+ hidden: true,
139
+
140
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
141
+ const { overall_correctness, explanation, confidence } = params;
142
+
143
+ let summary = `## Review Summary\n\n`;
144
+ summary += `**Verdict:** ${overall_correctness === "correct" ? `${theme.status.success} Patch is correct` : `${theme.status.error} Patch is incorrect`}\n`;
145
+ summary += `**Confidence:** ${(confidence * 100).toFixed(0)}%\n\n`;
146
+ summary += explanation;
147
+
148
+ return {
149
+ content: [{ type: "text", text: summary }],
150
+ details: { overall_correctness, explanation, confidence },
151
+ };
152
+ },
153
+
154
+ renderCall(args, theme): Component {
155
+ const verdict = args.overall_correctness === "correct" ? "correct" : "incorrect";
156
+ const color = args.overall_correctness === "correct" ? "success" : "error";
157
+ return new Text(
158
+ `${theme.fg("toolTitle", theme.bold("submit_review "))}${theme.fg(color, verdict)} ${theme.fg("dim", `(${((args.confidence as number) * 100).toFixed(0)}%)`)}`,
159
+ 0,
160
+ 0,
161
+ );
162
+ },
163
+
164
+ renderResult(result, { expanded }, theme): Component {
165
+ const { details } = result;
166
+ if (!details) {
167
+ const text = result.content[0];
168
+ return new Text(text?.type === "text" ? text.text : "", 0, 0);
169
+ }
170
+
171
+ const container = new Container();
172
+ const verdictColor = details.overall_correctness === "correct" ? "success" : "error";
173
+ const verdictIcon = details.overall_correctness === "correct" ? theme.status.success : theme.status.error;
174
+
175
+ container.addChild(
176
+ new Text(
177
+ `${theme.fg(verdictColor, verdictIcon)} Patch is ${theme.fg(verdictColor, details.overall_correctness)} ${theme.fg("dim", `(${(details.confidence * 100).toFixed(0)}% confidence)`)}`,
178
+ 0,
179
+ 0,
180
+ ),
181
+ );
182
+
183
+ if (expanded) {
184
+ container.addChild(new Spacer(1));
185
+ container.addChild(new Text(theme.fg("dim", details.explanation), 0, 0));
186
+ }
187
+
188
+ return container;
189
+ },
190
+ };
191
+
192
+ export function createReportFindingTool(): AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> {
193
+ return reportFindingTool;
194
+ }
195
+
196
+ export function createSubmitReviewTool(): AgentTool<typeof SubmitReviewParams, SubmitReviewDetails, Theme> {
197
+ return submitReviewTool;
198
+ }
199
+
200
+ // Re-export types for external use
201
+ export type { ReportFindingDetails, SubmitReviewDetails };
202
+
203
+ // ─────────────────────────────────────────────────────────────────────────────
204
+ // Subprocess tool handlers - registered for extraction/rendering in task tool
205
+ // ─────────────────────────────────────────────────────────────────────────────
206
+
207
+ import path from "node:path";
208
+ import { subprocessToolRegistry } from "./task/subprocess-tool-registry";
209
+
210
+ // Register report_finding handler
211
+ subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
212
+ extractData: (event) => event.result?.details as ReportFindingDetails | undefined,
213
+
214
+ renderInline: (data, theme) => {
215
+ const priority = PRIORITY_LABELS[data.priority] ?? "P?";
216
+ const color = data.priority === 0 ? "error" : data.priority === 1 ? "warning" : "muted";
217
+ const titleText = data.title.replace(/^\[P\d\]\s*/, "");
218
+ const loc = `${path.basename(data.file_path)}:${data.line_start}`;
219
+ return new Text(`${theme.fg(color, `[${priority}]`)} ${titleText} ${theme.fg("dim", loc)}`, 0, 0);
220
+ },
221
+
222
+ renderFinal: (allData, theme, expanded) => {
223
+ const container = new Container();
224
+ const displayCount = expanded ? allData.length : Math.min(3, allData.length);
225
+
226
+ for (let i = 0; i < displayCount; i++) {
227
+ const data = allData[i];
228
+ const priority = PRIORITY_LABELS[data.priority] ?? "P?";
229
+ const color = data.priority === 0 ? "error" : data.priority === 1 ? "warning" : "muted";
230
+ const titleText = data.title.replace(/^\[P\d\]\s*/, "");
231
+ const loc = `${path.basename(data.file_path)}:${data.line_start}`;
232
+
233
+ container.addChild(
234
+ new Text(` ${theme.fg(color, `[${priority}]`)} ${titleText} ${theme.fg("dim", loc)}`, 0, 0),
235
+ );
236
+
237
+ if (expanded && data.body) {
238
+ container.addChild(new Text(` ${theme.fg("dim", data.body)}`, 0, 0));
239
+ }
240
+ }
241
+
242
+ if (allData.length > displayCount) {
243
+ container.addChild(
244
+ new Text(
245
+ theme.fg("dim", ` ${theme.format.ellipsis} ${allData.length - displayCount} more findings`),
246
+ 0,
247
+ 0,
248
+ ),
249
+ );
250
+ }
251
+
252
+ return container;
253
+ },
254
+ });
255
+
256
+ // Register submit_review handler
257
+ subprocessToolRegistry.register<SubmitReviewDetails>("submit_review", {
258
+ extractData: (event) => event.result?.details as SubmitReviewDetails | undefined,
259
+
260
+ // Terminate subprocess after review is submitted
261
+ shouldTerminate: () => true,
262
+
263
+ renderInline: (data, theme) => {
264
+ const verdictColor = data.overall_correctness === "correct" ? "success" : "error";
265
+ const verdictIcon = data.overall_correctness === "correct" ? theme.status.success : theme.status.error;
266
+ return new Text(
267
+ `${theme.fg(verdictColor, verdictIcon)} Review: ${theme.fg(verdictColor, data.overall_correctness)} (${(data.confidence * 100).toFixed(0)}%)`,
268
+ 0,
269
+ 0,
270
+ );
271
+ },
272
+
273
+ // Note: renderFinal is NOT used for submit_review - we use the combined
274
+ // renderReviewResult in render.ts to show verdict + findings together
275
+ });
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Rulebook Tool
3
+ *
4
+ * Allows the agent to fetch full content of rules that have descriptions.
5
+ * Rules are listed in the system prompt with name + description; this tool
6
+ * retrieves the complete rule content on demand.
7
+ */
8
+
9
+ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
10
+ import { Type } from "@sinclair/typebox";
11
+ import type { Rule } from "../../capability/rule";
12
+
13
+ export interface RulebookToolDetails {
14
+ type: "rulebook";
15
+ ruleName: string;
16
+ found: boolean;
17
+ content?: string;
18
+ }
19
+
20
+ const rulebookSchema = Type.Object({
21
+ name: Type.String({ description: "The name of the rule to fetch" }),
22
+ });
23
+
24
+ /**
25
+ * Create a rulebook tool with access to discovered rules.
26
+ * @param rules - Array of discovered rules (non-TTSR rules with descriptions)
27
+ */
28
+ export function createRulebookTool(rules: Rule[]): AgentTool<typeof rulebookSchema> {
29
+ // Build lookup map for O(1) access
30
+ const ruleMap = new Map<string, Rule>();
31
+ for (const rule of rules) {
32
+ ruleMap.set(rule.name, rule);
33
+ }
34
+
35
+ const ruleNames = rules.map((r) => r.name);
36
+
37
+ return {
38
+ name: "rulebook",
39
+ label: "Rulebook",
40
+ description: `Fetch the full content of a project rule by name. Use this when a rule listed in <available_rules> is relevant to your current task. Available: ${ruleNames.join(", ") || "(none)"}`,
41
+ parameters: rulebookSchema,
42
+ execute: async (_toolCallId: string, { name }: { name: string }) => {
43
+ const rule = ruleMap.get(name);
44
+
45
+ if (!rule) {
46
+ const available = ruleNames.join(", ");
47
+ return {
48
+ content: [{ type: "text", text: `Rule "${name}" not found. Available rules: ${available || "(none)"}` }],
49
+ details: {
50
+ type: "rulebook",
51
+ ruleName: name,
52
+ found: false,
53
+ } satisfies RulebookToolDetails,
54
+ };
55
+ }
56
+
57
+ return {
58
+ content: [{ type: "text", text: `# Rule: ${rule.name}\n\n${rule.content}` }],
59
+ details: {
60
+ type: "rulebook",
61
+ ruleName: name,
62
+ found: true,
63
+ content: rule.content,
64
+ } satisfies RulebookToolDetails,
65
+ };
66
+ },
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Filter rules to only those suitable for the rulebook (have descriptions, no TTSR trigger).
72
+ */
73
+ export function filterRulebookRules(rules: Rule[]): Rule[] {
74
+ return rules.filter((rule) => {
75
+ // Exclude TTSR rules (handled separately by streaming)
76
+ if (rule.ttsrTrigger) return false;
77
+ // Exclude always-apply rules (already in context)
78
+ if (rule.alwaysApply) return false;
79
+ // Must have a description for agent to know when to fetch
80
+ if (!rule.description) return false;
81
+ return true;
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Format rules for inclusion in the system prompt.
87
+ * Lists rule names and descriptions so the agent knows what's available.
88
+ */
89
+ export function formatRulesForPrompt(rules: Rule[]): string {
90
+ if (rules.length === 0) {
91
+ return "";
92
+ }
93
+
94
+ const lines = [
95
+ "\n\n## Available Rules",
96
+ "",
97
+ "The following project rules are available. Use the `rulebook` tool to fetch a rule's full content when it's relevant to your task.",
98
+ "",
99
+ "<available_rules>",
100
+ ];
101
+
102
+ for (const rule of rules) {
103
+ lines.push(" <rule>");
104
+ lines.push(` <name>${escapeXml(rule.name)}</name>`);
105
+ lines.push(` <description>${escapeXml(rule.description || "")}</description>`);
106
+ if (rule.globs && rule.globs.length > 0) {
107
+ lines.push(` <globs>${escapeXml(rule.globs.join(", "))}</globs>`);
108
+ }
109
+ lines.push(" </rule>");
110
+ }
111
+
112
+ lines.push("</available_rules>");
113
+
114
+ return lines.join("\n");
115
+ }
116
+
117
+ function escapeXml(str: string): string {
118
+ return str
119
+ .replace(/&/g, "&amp;")
120
+ .replace(/</g, "&lt;")
121
+ .replace(/>/g, "&gt;")
122
+ .replace(/"/g, "&quot;")
123
+ .replace(/'/g, "&apos;");
124
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Bundled agent definitions.
3
+ *
4
+ * Agents are embedded at build time via Bun's import with { type: "text" }.
5
+ */
6
+
7
+ // Embed agent markdown files at build time
8
+ import browserMd from "../../../prompts/browser.md" with { type: "text" };
9
+ import exploreMd from "../../../prompts/explore.md" with { type: "text" };
10
+ import planMd from "../../../prompts/plan.md" with { type: "text" };
11
+ import reviewerMd from "../../../prompts/reviewer.md" with { type: "text" };
12
+ import taskMd from "../../../prompts/task.md" with { type: "text" };
13
+ import type { AgentDefinition, AgentSource } from "./types";
14
+
15
+ const EMBEDDED_AGENTS: { name: string; content: string }[] = [
16
+ { name: "browser.md", content: browserMd },
17
+ { name: "explore.md", content: exploreMd },
18
+ { name: "plan.md", content: planMd },
19
+ { name: "reviewer.md", content: reviewerMd },
20
+ { name: "task.md", content: taskMd },
21
+ ];
22
+
23
+ /**
24
+ * Parse YAML frontmatter from markdown content.
25
+ */
26
+ function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
27
+ const frontmatter: Record<string, string> = {};
28
+ const normalized = content.replace(/\r\n/g, "\n");
29
+
30
+ if (!normalized.startsWith("---")) {
31
+ return { frontmatter, body: normalized };
32
+ }
33
+
34
+ const endIndex = normalized.indexOf("\n---", 3);
35
+ if (endIndex === -1) {
36
+ return { frontmatter, body: normalized };
37
+ }
38
+
39
+ const frontmatterBlock = normalized.slice(4, endIndex);
40
+ const body = normalized.slice(endIndex + 4).trim();
41
+
42
+ for (const line of frontmatterBlock.split("\n")) {
43
+ const match = line.match(/^([\w-]+):\s*(.*)$/);
44
+ if (match) {
45
+ let value = match[2].trim();
46
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
47
+ value = value.slice(1, -1);
48
+ }
49
+ frontmatter[match[1]] = value;
50
+ }
51
+ }
52
+
53
+ return { frontmatter, body };
54
+ }
55
+
56
+ /**
57
+ * Parse an agent from embedded content.
58
+ */
59
+ function parseAgent(fileName: string, content: string, source: AgentSource): AgentDefinition | null {
60
+ const { frontmatter, body } = parseFrontmatter(content);
61
+
62
+ if (!frontmatter.name || !frontmatter.description) {
63
+ return null;
64
+ }
65
+
66
+ const tools = frontmatter.tools
67
+ ?.split(",")
68
+ .map((t) => t.trim())
69
+ .filter(Boolean);
70
+
71
+ // Parse spawns field
72
+ let spawns: string[] | "*" | undefined;
73
+ if (frontmatter.spawns !== undefined) {
74
+ const spawnsRaw = frontmatter.spawns.trim();
75
+ if (spawnsRaw === "*") {
76
+ spawns = "*";
77
+ } else if (spawnsRaw) {
78
+ spawns = spawnsRaw
79
+ .split(",")
80
+ .map((s) => s.trim())
81
+ .filter(Boolean);
82
+ if (spawns.length === 0) spawns = undefined;
83
+ }
84
+ }
85
+
86
+ // Backward compat: infer spawns: "*" when tools includes "task"
87
+ if (spawns === undefined && tools?.includes("task")) {
88
+ spawns = "*";
89
+ }
90
+
91
+ const recursive =
92
+ frontmatter.recursive === undefined ? false : frontmatter.recursive === "true" || frontmatter.recursive === "1";
93
+
94
+ return {
95
+ name: frontmatter.name,
96
+ description: frontmatter.description,
97
+ tools: tools && tools.length > 0 ? tools : undefined,
98
+ spawns,
99
+ model: frontmatter.model,
100
+ recursive,
101
+ systemPrompt: body,
102
+ source,
103
+ filePath: `embedded:${fileName}`,
104
+ };
105
+ }
106
+
107
+ /** Cache for bundled agents */
108
+ let bundledAgentsCache: AgentDefinition[] | null = null;
109
+
110
+ /**
111
+ * Load all bundled agents from embedded content.
112
+ * Results are cached after first load.
113
+ */
114
+ export function loadBundledAgents(): AgentDefinition[] {
115
+ if (bundledAgentsCache !== null) {
116
+ return bundledAgentsCache;
117
+ }
118
+
119
+ const agents: AgentDefinition[] = [];
120
+
121
+ for (const { name, content } of EMBEDDED_AGENTS) {
122
+ const agent = parseAgent(name, content, "bundled");
123
+ if (agent) {
124
+ agents.push(agent);
125
+ }
126
+ }
127
+
128
+ bundledAgentsCache = agents;
129
+ return agents;
130
+ }
131
+
132
+ /**
133
+ * Get a bundled agent by name.
134
+ */
135
+ export function getBundledAgent(name: string): AgentDefinition | undefined {
136
+ return loadBundledAgents().find((a) => a.name === name);
137
+ }
138
+
139
+ /**
140
+ * Get all bundled agents as a map keyed by name.
141
+ */
142
+ export function getBundledAgentsMap(): Map<string, AgentDefinition> {
143
+ const map = new Map<string, AgentDefinition>();
144
+ for (const agent of loadBundledAgents()) {
145
+ map.set(agent.name, agent);
146
+ }
147
+ return map;
148
+ }
149
+
150
+ /**
151
+ * Clear the bundled agents cache (for testing).
152
+ */
153
+ export function clearBundledAgentsCache(): void {
154
+ bundledAgentsCache = null;
155
+ }
156
+
157
+ // Re-export for backward compatibility
158
+ export const BUNDLED_AGENTS = loadBundledAgents;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Session artifacts for subagent outputs.
3
+ *
4
+ * When a session exists, writes agent outputs to a sibling directory.
5
+ * Otherwise uses temp files that are cleaned up after execution.
6
+ */
7
+
8
+ import * as fs from "node:fs";
9
+ import * as os from "node:os";
10
+ import * as path from "node:path";
11
+
12
+ /**
13
+ * Derive artifacts directory from session file path.
14
+ *
15
+ * /path/to/sessions/project/2026-01-01T14-28-11-636Z_uuid.jsonl
16
+ * → /path/to/sessions/project/2026-01-01T14-28-11-636Z_uuid/
17
+ */
18
+ export function getArtifactsDir(sessionFile: string | null): string | null {
19
+ if (!sessionFile) return null;
20
+ // Strip .jsonl extension to get directory path
21
+ if (sessionFile.endsWith(".jsonl")) {
22
+ return sessionFile.slice(0, -6);
23
+ }
24
+ return sessionFile;
25
+ }
26
+
27
+ /**
28
+ * Ensure artifacts directory exists.
29
+ */
30
+ export function ensureArtifactsDir(dir: string): void {
31
+ if (!fs.existsSync(dir)) {
32
+ fs.mkdirSync(dir, { recursive: true });
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Generate artifact file paths for an agent run.
38
+ */
39
+ export function getArtifactPaths(
40
+ dir: string,
41
+ agentName: string,
42
+ index: number,
43
+ ): { inputPath: string; outputPath: string; jsonlPath: string } {
44
+ const base = `${agentName}_${index}`;
45
+ return {
46
+ inputPath: path.join(dir, `${base}.in.md`),
47
+ outputPath: path.join(dir, `${base}.out.md`),
48
+ jsonlPath: path.join(dir, `${base}.jsonl`),
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Write artifacts for an agent run.
54
+ */
55
+ export async function writeArtifacts(
56
+ dir: string,
57
+ agentName: string,
58
+ index: number,
59
+ input: string,
60
+ output: string,
61
+ jsonlEvents?: string[],
62
+ ): Promise<{ inputPath: string; outputPath: string; jsonlPath?: string }> {
63
+ ensureArtifactsDir(dir);
64
+
65
+ const paths = getArtifactPaths(dir, agentName, index);
66
+
67
+ // Write input
68
+ await fs.promises.writeFile(paths.inputPath, input, "utf-8");
69
+
70
+ // Write output
71
+ await fs.promises.writeFile(paths.outputPath, output, "utf-8");
72
+
73
+ // Write JSONL if events provided
74
+ if (jsonlEvents && jsonlEvents.length > 0) {
75
+ await fs.promises.writeFile(paths.jsonlPath, jsonlEvents.join("\n"), "utf-8");
76
+ return paths;
77
+ }
78
+
79
+ return { inputPath: paths.inputPath, outputPath: paths.outputPath };
80
+ }
81
+
82
+ /**
83
+ * Create a temporary artifacts directory.
84
+ */
85
+ export function createTempArtifactsDir(runId?: string): string {
86
+ const id = runId || `${Date.now()}-${Math.random().toString(36).slice(2)}`;
87
+ const dir = path.join(os.tmpdir(), `omp-task-${id}`);
88
+ ensureArtifactsDir(dir);
89
+ return dir;
90
+ }
91
+
92
+ /**
93
+ * Clean up temporary artifacts.
94
+ */
95
+ export async function cleanupTempArtifacts(paths: string[]): Promise<void> {
96
+ for (const p of paths) {
97
+ try {
98
+ await fs.promises.unlink(p);
99
+ } catch {
100
+ // Ignore cleanup errors
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Clean up a temporary directory and its contents.
107
+ */
108
+ export async function cleanupTempDir(dir: string): Promise<void> {
109
+ try {
110
+ await fs.promises.rm(dir, { recursive: true, force: true });
111
+ } catch {
112
+ // Ignore cleanup errors
113
+ }
114
+ }