pi-usereq 0.6.0 → 0.10.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 (774) hide show
  1. package/.gitignore +2 -0
  2. package/CHANGELOG.md +44 -0
  3. package/README.md +1 -1
  4. package/docs/pi.dev/coding-agent-docs/custom-provider.md +42 -1
  5. package/docs/pi.dev/coding-agent-docs/extensions.md +302 -28
  6. package/docs/pi.dev/coding-agent-docs/keybindings.md +23 -1
  7. package/docs/pi.dev/coding-agent-docs/models.md +3 -0
  8. package/docs/pi.dev/coding-agent-docs/packages.md +2 -2
  9. package/docs/pi.dev/coding-agent-docs/prompt-templates.md +21 -0
  10. package/docs/pi.dev/coding-agent-docs/providers.md +1 -0
  11. package/docs/pi.dev/coding-agent-docs/rpc.md +36 -6
  12. package/docs/pi.dev/coding-agent-docs/sdk.md +30 -5
  13. package/docs/pi.dev/coding-agent-docs/session.md +1 -1
  14. package/docs/pi.dev/coding-agent-docs/settings.md +2 -1
  15. package/docs/pi.dev/coding-agent-docs/skills.md +12 -12
  16. package/docs/pi.dev/coding-agent-docs/tree.md +2 -0
  17. package/docs/pi.dev/coding-agent-docs/tui.md +31 -0
  18. package/docs/pi.dev/mom-docs/new.md +4 -4
  19. package/package.json +1 -1
  20. package/pi-usereq/docs/REFERENCES.md +2658 -2035
  21. package/pi-usereq/docs/REQUIREMENTS.md +345 -191
  22. package/pi-usereq/docs/WORKFLOW.md +387 -363
  23. package/pi.dev-src/pi-mono/agent/src/agent-loop.ts +683 -0
  24. package/pi.dev-src/pi-mono/agent/src/agent.ts +543 -0
  25. package/pi.dev-src/pi-mono/agent/src/index.ts +8 -0
  26. package/pi.dev-src/pi-mono/agent/src/proxy.ts +367 -0
  27. package/pi.dev-src/pi-mono/agent/src/types.ts +364 -0
  28. package/pi.dev-src/pi-mono/agent/test/agent-loop.test.ts +1181 -0
  29. package/pi.dev-src/pi-mono/agent/test/agent.test.ts +463 -0
  30. package/pi.dev-src/pi-mono/agent/test/e2e.test.ts +404 -0
  31. package/pi.dev-src/pi-mono/agent/test/utils/calculate.ts +32 -0
  32. package/pi.dev-src/pi-mono/agent/test/utils/get-current-time.ts +46 -0
  33. package/pi.dev-src/pi-mono/agent/vitest.config.ts +9 -0
  34. package/pi.dev-src/pi-mono/ai/bedrock-provider.d.ts +1 -0
  35. package/pi.dev-src/pi-mono/ai/bedrock-provider.js +1 -0
  36. package/pi.dev-src/pi-mono/ai/scripts/generate-models.ts +1635 -0
  37. package/pi.dev-src/pi-mono/ai/scripts/generate-test-image.ts +34 -0
  38. package/pi.dev-src/pi-mono/ai/src/api-registry.ts +98 -0
  39. package/pi.dev-src/pi-mono/ai/src/bedrock-provider.ts +6 -0
  40. package/pi.dev-src/pi-mono/ai/src/cli.ts +133 -0
  41. package/pi.dev-src/pi-mono/ai/src/env-api-keys.ts +134 -0
  42. package/pi.dev-src/pi-mono/ai/src/index.ts +35 -0
  43. package/pi.dev-src/pi-mono/ai/src/models.generated.ts +14928 -0
  44. package/pi.dev-src/pi-mono/ai/src/models.ts +82 -0
  45. package/pi.dev-src/pi-mono/ai/src/oauth.ts +1 -0
  46. package/pi.dev-src/pi-mono/ai/src/providers/amazon-bedrock.ts +929 -0
  47. package/pi.dev-src/pi-mono/ai/src/providers/anthropic.ts +1125 -0
  48. package/pi.dev-src/pi-mono/ai/src/providers/azure-openai-responses.ts +253 -0
  49. package/pi.dev-src/pi-mono/ai/src/providers/faux.ts +499 -0
  50. package/pi.dev-src/pi-mono/ai/src/providers/github-copilot-headers.ts +37 -0
  51. package/pi.dev-src/pi-mono/ai/src/providers/google-gemini-cli.ts +996 -0
  52. package/pi.dev-src/pi-mono/ai/src/providers/google-shared.ts +354 -0
  53. package/pi.dev-src/pi-mono/ai/src/providers/google-vertex.ts +543 -0
  54. package/pi.dev-src/pi-mono/ai/src/providers/google.ts +500 -0
  55. package/pi.dev-src/pi-mono/ai/src/providers/mistral.ts +629 -0
  56. package/pi.dev-src/pi-mono/ai/src/providers/openai-codex-responses.ts +984 -0
  57. package/pi.dev-src/pi-mono/ai/src/providers/openai-completions.ts +1063 -0
  58. package/pi.dev-src/pi-mono/ai/src/providers/openai-responses-shared.ts +539 -0
  59. package/pi.dev-src/pi-mono/ai/src/providers/openai-responses.ts +264 -0
  60. package/pi.dev-src/pi-mono/ai/src/providers/register-builtins.ts +433 -0
  61. package/pi.dev-src/pi-mono/ai/src/providers/simple-options.ts +47 -0
  62. package/pi.dev-src/pi-mono/ai/src/providers/transform-messages.ts +220 -0
  63. package/pi.dev-src/pi-mono/ai/src/stream.ts +59 -0
  64. package/pi.dev-src/pi-mono/ai/src/types.ts +417 -0
  65. package/pi.dev-src/pi-mono/ai/src/utils/event-stream.ts +87 -0
  66. package/pi.dev-src/pi-mono/ai/src/utils/hash.ts +13 -0
  67. package/pi.dev-src/pi-mono/ai/src/utils/headers.ts +7 -0
  68. package/pi.dev-src/pi-mono/ai/src/utils/json-parse.ts +124 -0
  69. package/pi.dev-src/pi-mono/ai/src/utils/overflow.ts +138 -0
  70. package/pi.dev-src/pi-mono/ai/src/utils/sanitize-unicode.ts +25 -0
  71. package/pi.dev-src/pi-mono/ai/src/utils/typebox-helpers.ts +24 -0
  72. package/pi.dev-src/pi-mono/ai/src/utils/validation.ts +324 -0
  73. package/pi.dev-src/pi-mono/ai/test/abort.test.ts +260 -0
  74. package/pi.dev-src/pi-mono/ai/test/anthropic-oauth.test.ts +99 -0
  75. package/pi.dev-src/pi-mono/ai/test/anthropic-opus-4-7-smoke.test.ts +72 -0
  76. package/pi.dev-src/pi-mono/ai/test/anthropic-sse-parsing.test.ts +113 -0
  77. package/pi.dev-src/pi-mono/ai/test/anthropic-thinking-disable.test.ts +152 -0
  78. package/pi.dev-src/pi-mono/ai/test/anthropic-tool-name-normalization.test.ts +205 -0
  79. package/pi.dev-src/pi-mono/ai/test/azure-utils.ts +28 -0
  80. package/pi.dev-src/pi-mono/ai/test/bedrock-endpoint-resolution.test.ts +131 -0
  81. package/pi.dev-src/pi-mono/ai/test/bedrock-models.test.ts +66 -0
  82. package/pi.dev-src/pi-mono/ai/test/bedrock-thinking-payload.test.ts +107 -0
  83. package/pi.dev-src/pi-mono/ai/test/bedrock-utils.ts +18 -0
  84. package/pi.dev-src/pi-mono/ai/test/cache-retention.test.ts +335 -0
  85. package/pi.dev-src/pi-mono/ai/test/context-overflow.test.ts +714 -0
  86. package/pi.dev-src/pi-mono/ai/test/cross-provider-handoff.test.ts +458 -0
  87. package/pi.dev-src/pi-mono/ai/test/data/red-circle.png +0 -0
  88. package/pi.dev-src/pi-mono/ai/test/empty.test.ts +742 -0
  89. package/pi.dev-src/pi-mono/ai/test/faux-provider.test.ts +597 -0
  90. package/pi.dev-src/pi-mono/ai/test/fireworks-models.test.ts +49 -0
  91. package/pi.dev-src/pi-mono/ai/test/github-copilot-anthropic.test.ts +108 -0
  92. package/pi.dev-src/pi-mono/ai/test/github-copilot-oauth.test.ts +196 -0
  93. package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-claude-thinking-header.test.ts +103 -0
  94. package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-empty-stream.test.ts +108 -0
  95. package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-retry-delay.test.ts +53 -0
  96. package/pi.dev-src/pi-mono/ai/test/google-shared-convert-tools.test.ts +187 -0
  97. package/pi.dev-src/pi-mono/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts +172 -0
  98. package/pi.dev-src/pi-mono/ai/test/google-shared-image-tool-result-routing.test.ts +124 -0
  99. package/pi.dev-src/pi-mono/ai/test/google-thinking-disable.test.ts +183 -0
  100. package/pi.dev-src/pi-mono/ai/test/google-thinking-signature.test.ts +38 -0
  101. package/pi.dev-src/pi-mono/ai/test/google-tool-call-missing-args.test.ts +105 -0
  102. package/pi.dev-src/pi-mono/ai/test/google-vertex-api-key-resolution.test.ts +144 -0
  103. package/pi.dev-src/pi-mono/ai/test/image-tool-result.test.ts +481 -0
  104. package/pi.dev-src/pi-mono/ai/test/interleaved-thinking.test.ts +145 -0
  105. package/pi.dev-src/pi-mono/ai/test/lazy-module-load.test.ts +102 -0
  106. package/pi.dev-src/pi-mono/ai/test/mistral-reasoning-mode.test.ts +66 -0
  107. package/pi.dev-src/pi-mono/ai/test/mistral-tool-schema.test.ts +61 -0
  108. package/pi.dev-src/pi-mono/ai/test/oauth.ts +89 -0
  109. package/pi.dev-src/pi-mono/ai/test/openai-codex-cache-affinity-e2e.test.ts +36 -0
  110. package/pi.dev-src/pi-mono/ai/test/openai-codex-stream.test.ts +686 -0
  111. package/pi.dev-src/pi-mono/ai/test/openai-completions-cache-control-format.test.ts +189 -0
  112. package/pi.dev-src/pi-mono/ai/test/openai-completions-prompt-cache.test.ts +198 -0
  113. package/pi.dev-src/pi-mono/ai/test/openai-completions-thinking-as-text.test.ts +214 -0
  114. package/pi.dev-src/pi-mono/ai/test/openai-completions-tool-choice.test.ts +560 -0
  115. package/pi.dev-src/pi-mono/ai/test/openai-completions-tool-result-images.test.ts +101 -0
  116. package/pi.dev-src/pi-mono/ai/test/openai-responses-cache-affinity-e2e.test.ts +32 -0
  117. package/pi.dev-src/pi-mono/ai/test/openai-responses-copilot-provider.test.ts +128 -0
  118. package/pi.dev-src/pi-mono/ai/test/openai-responses-foreign-toolcall-id.test.ts +66 -0
  119. package/pi.dev-src/pi-mono/ai/test/openai-responses-partial-json-cleanup.test.ts +101 -0
  120. package/pi.dev-src/pi-mono/ai/test/openai-responses-reasoning-replay-e2e.test.ts +292 -0
  121. package/pi.dev-src/pi-mono/ai/test/openai-responses-tool-result-images.test.ts +200 -0
  122. package/pi.dev-src/pi-mono/ai/test/openrouter-cache-write-repro.test.ts +78 -0
  123. package/pi.dev-src/pi-mono/ai/test/overflow.test.ts +64 -0
  124. package/pi.dev-src/pi-mono/ai/test/responseid.test.ts +145 -0
  125. package/pi.dev-src/pi-mono/ai/test/stream.test.ts +1405 -0
  126. package/pi.dev-src/pi-mono/ai/test/supports-xhigh.test.ts +34 -0
  127. package/pi.dev-src/pi-mono/ai/test/tokens.test.ts +304 -0
  128. package/pi.dev-src/pi-mono/ai/test/tool-call-id-normalization.test.ts +291 -0
  129. package/pi.dev-src/pi-mono/ai/test/tool-call-without-result.test.ts +313 -0
  130. package/pi.dev-src/pi-mono/ai/test/total-tokens.test.ts +695 -0
  131. package/pi.dev-src/pi-mono/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts +191 -0
  132. package/pi.dev-src/pi-mono/ai/test/unicode-surrogate.test.ts +754 -0
  133. package/pi.dev-src/pi-mono/ai/test/validation.test.ts +116 -0
  134. package/pi.dev-src/pi-mono/ai/test/xhigh.test.ts +71 -0
  135. package/pi.dev-src/pi-mono/ai/test/zen.test.ts +25 -0
  136. package/pi.dev-src/pi-mono/ai/vitest.config.ts +9 -0
  137. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/antigravity-image-gen.ts +418 -0
  138. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/auto-commit-on-exit.ts +49 -0
  139. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/bash-spawn-hook.ts +30 -0
  140. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/bookmark.ts +50 -0
  141. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/built-in-tool-renderer.ts +249 -0
  142. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/claude-rules.ts +86 -0
  143. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/commands.ts +72 -0
  144. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/confirm-destructive.ts +59 -0
  145. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-compaction.ts +127 -0
  146. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-footer.ts +64 -0
  147. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-header.ts +73 -0
  148. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  149. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  150. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  151. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  152. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-qwen-cli/index.ts +345 -0
  153. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dirty-repo-guard.ts +56 -0
  154. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom/build.sh +152 -0
  155. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  156. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-component.ts +132 -0
  157. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  158. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  159. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/index.ts +74 -0
  160. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  161. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/SKILL.md +8 -0
  162. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/dynamic.json +79 -0
  163. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/dynamic.md +5 -0
  164. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/index.ts +15 -0
  165. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-tools.ts +74 -0
  166. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/event-bus.ts +43 -0
  167. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/file-trigger.ts +41 -0
  168. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/git-checkpoint.ts +53 -0
  169. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/github-issue-autocomplete.ts +185 -0
  170. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/handoff.ts +153 -0
  171. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/hello.ts +26 -0
  172. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/hidden-thinking-label.ts +53 -0
  173. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/inline-bash.ts +94 -0
  174. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/input-transform.ts +43 -0
  175. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/interactive-shell.ts +196 -0
  176. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/mac-system-theme.ts +47 -0
  177. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/message-renderer.ts +59 -0
  178. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/minimal-mode.ts +426 -0
  179. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/modal-editor.ts +85 -0
  180. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/model-status.ts +31 -0
  181. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/notify.ts +55 -0
  182. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/overlay-qa-tests.ts +1348 -0
  183. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/overlay-test.ts +150 -0
  184. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/permission-gate.ts +34 -0
  185. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/pirate.ts +47 -0
  186. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/plan-mode/index.ts +340 -0
  187. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/plan-mode/utils.ts +168 -0
  188. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/preset.ts +430 -0
  189. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/prompt-customizer.ts +97 -0
  190. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/protected-paths.ts +30 -0
  191. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/provider-payload.ts +18 -0
  192. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/qna.ts +122 -0
  193. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/question.ts +264 -0
  194. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/questionnaire.ts +427 -0
  195. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/rainbow-editor.ts +88 -0
  196. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/reload-runtime.ts +37 -0
  197. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/rpc-demo.ts +118 -0
  198. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/sandbox/index.ts +321 -0
  199. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/sandbox/package-lock.json +92 -0
  200. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/send-user-message.ts +97 -0
  201. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/session-name.ts +27 -0
  202. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/shutdown-command.ts +63 -0
  203. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/snake.ts +343 -0
  204. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/space-invaders.ts +560 -0
  205. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/ssh.ts +220 -0
  206. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/status-line.ts +32 -0
  207. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/structured-output.ts +65 -0
  208. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/planner.md +37 -0
  209. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/reviewer.md +35 -0
  210. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/scout.md +50 -0
  211. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/worker.md +24 -0
  212. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents.ts +126 -0
  213. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/index.ts +987 -0
  214. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  215. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/implement.md +10 -0
  216. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  217. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/summarize.ts +206 -0
  218. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/system-prompt-header.ts +17 -0
  219. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tic-tac-toe.ts +1008 -0
  220. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/timed-confirm.ts +70 -0
  221. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/titlebar-spinner.ts +58 -0
  222. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/todo.ts +297 -0
  223. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tool-override.ts +144 -0
  224. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tools.ts +141 -0
  225. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/trigger-compact.ts +50 -0
  226. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/truncated-tool.ts +195 -0
  227. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/widget-placement.ts +9 -0
  228. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/with-deps/index.ts +32 -0
  229. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/with-deps/package-lock.json +31 -0
  230. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/working-indicator.ts +123 -0
  231. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/working-message-test.ts +25 -0
  232. package/pi.dev-src/pi-mono/coding-agent/examples/rpc-extension-ui.ts +632 -0
  233. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/01-minimal.ts +22 -0
  234. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/02-custom-model.ts +49 -0
  235. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/03-custom-prompt.ts +62 -0
  236. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/04-skills.ts +55 -0
  237. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/05-tools.ts +44 -0
  238. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/06-extensions.ts +90 -0
  239. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/07-context-files.ts +42 -0
  240. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/08-prompt-templates.ts +51 -0
  241. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  242. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/10-settings.ts +53 -0
  243. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/11-sessions.ts +48 -0
  244. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/12-full-control.ts +73 -0
  245. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/13-session-runtime.ts +67 -0
  246. package/pi.dev-src/pi-mono/coding-agent/scripts/migrate-sessions.sh +93 -0
  247. package/pi.dev-src/pi-mono/coding-agent/src/bun/cli.ts +6 -0
  248. package/pi.dev-src/pi-mono/coding-agent/src/bun/register-bedrock.ts +4 -0
  249. package/pi.dev-src/pi-mono/coding-agent/src/cli/args.ts +335 -0
  250. package/pi.dev-src/pi-mono/coding-agent/src/cli/config-selector.ts +52 -0
  251. package/pi.dev-src/pi-mono/coding-agent/src/cli/file-processor.ts +100 -0
  252. package/pi.dev-src/pi-mono/coding-agent/src/cli/initial-message.ts +43 -0
  253. package/pi.dev-src/pi-mono/coding-agent/src/cli/list-models.ts +110 -0
  254. package/pi.dev-src/pi-mono/coding-agent/src/cli/session-picker.ts +52 -0
  255. package/pi.dev-src/pi-mono/coding-agent/src/cli.ts +17 -0
  256. package/pi.dev-src/pi-mono/coding-agent/src/config.ts +261 -0
  257. package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session-runtime.ts +394 -0
  258. package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session-services.ts +196 -0
  259. package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session.ts +3096 -0
  260. package/pi.dev-src/pi-mono/coding-agent/src/core/auth-storage.ts +493 -0
  261. package/pi.dev-src/pi-mono/coding-agent/src/core/bash-executor.ts +154 -0
  262. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/branch-summarization.ts +355 -0
  263. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/compaction.ts +839 -0
  264. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/index.ts +7 -0
  265. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/utils.ts +170 -0
  266. package/pi.dev-src/pi-mono/coding-agent/src/core/defaults.ts +3 -0
  267. package/pi.dev-src/pi-mono/coding-agent/src/core/diagnostics.ts +15 -0
  268. package/pi.dev-src/pi-mono/coding-agent/src/core/event-bus.ts +33 -0
  269. package/pi.dev-src/pi-mono/coding-agent/src/core/exec.ts +107 -0
  270. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/ansi-to-html.ts +258 -0
  271. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/index.ts +314 -0
  272. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.css +1017 -0
  273. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.html +55 -0
  274. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.js +1770 -0
  275. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/tool-renderer.ts +158 -0
  276. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/vendor/highlight.min.js +1213 -0
  277. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/vendor/marked.min.js +6 -0
  278. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/index.ts +171 -0
  279. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/loader.ts +606 -0
  280. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/runner.ts +1019 -0
  281. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/types.ts +1542 -0
  282. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/wrapper.ts +30 -0
  283. package/pi.dev-src/pi-mono/coding-agent/src/core/footer-data-provider.ts +354 -0
  284. package/pi.dev-src/pi-mono/coding-agent/src/core/index.ts +77 -0
  285. package/pi.dev-src/pi-mono/coding-agent/src/core/keybindings.ts +370 -0
  286. package/pi.dev-src/pi-mono/coding-agent/src/core/messages.ts +195 -0
  287. package/pi.dev-src/pi-mono/coding-agent/src/core/model-registry.ts +858 -0
  288. package/pi.dev-src/pi-mono/coding-agent/src/core/model-resolver.ts +629 -0
  289. package/pi.dev-src/pi-mono/coding-agent/src/core/output-guard.ts +74 -0
  290. package/pi.dev-src/pi-mono/coding-agent/src/core/package-manager.ts +2408 -0
  291. package/pi.dev-src/pi-mono/coding-agent/src/core/prompt-templates.ts +296 -0
  292. package/pi.dev-src/pi-mono/coding-agent/src/core/resolve-config-value.ts +142 -0
  293. package/pi.dev-src/pi-mono/coding-agent/src/core/resource-loader.ts +917 -0
  294. package/pi.dev-src/pi-mono/coding-agent/src/core/sdk.ts +380 -0
  295. package/pi.dev-src/pi-mono/coding-agent/src/core/session-cwd.ts +59 -0
  296. package/pi.dev-src/pi-mono/coding-agent/src/core/session-manager.ts +1425 -0
  297. package/pi.dev-src/pi-mono/coding-agent/src/core/settings-manager.ts +999 -0
  298. package/pi.dev-src/pi-mono/coding-agent/src/core/skills.ts +508 -0
  299. package/pi.dev-src/pi-mono/coding-agent/src/core/slash-commands.ts +39 -0
  300. package/pi.dev-src/pi-mono/coding-agent/src/core/source-info.ts +40 -0
  301. package/pi.dev-src/pi-mono/coding-agent/src/core/system-prompt.ts +172 -0
  302. package/pi.dev-src/pi-mono/coding-agent/src/core/telemetry.ts +13 -0
  303. package/pi.dev-src/pi-mono/coding-agent/src/core/timings.ts +31 -0
  304. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/bash.ts +448 -0
  305. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/edit-diff.ts +445 -0
  306. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/edit.ts +487 -0
  307. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/file-mutation-queue.ts +39 -0
  308. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/find.ts +370 -0
  309. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/grep.ts +384 -0
  310. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/index.ts +196 -0
  311. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/ls.ts +229 -0
  312. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/path-utils.ts +94 -0
  313. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/read.ts +273 -0
  314. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/render-utils.ts +64 -0
  315. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/tool-definition-wrapper.ts +45 -0
  316. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/truncate.ts +265 -0
  317. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/write.ts +281 -0
  318. package/pi.dev-src/pi-mono/coding-agent/src/index.ts +352 -0
  319. package/pi.dev-src/pi-mono/coding-agent/src/main.ts +731 -0
  320. package/pi.dev-src/pi-mono/coding-agent/src/migrations.ts +314 -0
  321. package/pi.dev-src/pi-mono/coding-agent/src/modes/index.ts +9 -0
  322. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/assets/clankolas.png +0 -0
  323. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/armin.ts +382 -0
  324. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/assistant-message.ts +147 -0
  325. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/bash-execution.ts +218 -0
  326. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/bordered-loader.ts +68 -0
  327. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/branch-summary-message.ts +58 -0
  328. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/compaction-summary-message.ts +59 -0
  329. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/config-selector.ts +592 -0
  330. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/countdown-timer.ts +38 -0
  331. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/custom-editor.ts +80 -0
  332. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/custom-message.ts +99 -0
  333. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/daxnuts.ts +164 -0
  334. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/diff.ts +147 -0
  335. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/dynamic-border.ts +25 -0
  336. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/earendil-announcement.ts +53 -0
  337. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-editor.ts +147 -0
  338. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-input.ts +87 -0
  339. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-selector.ts +107 -0
  340. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/footer.ts +220 -0
  341. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/index.ts +32 -0
  342. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/keybinding-hints.ts +24 -0
  343. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/login-dialog.ts +178 -0
  344. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/model-selector.ts +338 -0
  345. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/oauth-selector.ts +121 -0
  346. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/scoped-models-selector.ts +355 -0
  347. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/session-selector-search.ts +194 -0
  348. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/session-selector.ts +1026 -0
  349. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/settings-selector.ts +456 -0
  350. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/show-images-selector.ts +50 -0
  351. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/skill-invocation-message.ts +55 -0
  352. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/theme-selector.ts +67 -0
  353. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/thinking-selector.ts +74 -0
  354. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/tool-execution.ts +352 -0
  355. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/tree-selector.ts +1246 -0
  356. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/user-message-selector.ts +155 -0
  357. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/user-message.ts +35 -0
  358. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/visual-truncate.ts +50 -0
  359. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/interactive-mode.ts +5155 -0
  360. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/dark.json +85 -0
  361. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/light.json +84 -0
  362. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/theme-schema.json +335 -0
  363. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/theme.ts +1141 -0
  364. package/pi.dev-src/pi-mono/coding-agent/src/modes/print-mode.ts +158 -0
  365. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/jsonl.ts +58 -0
  366. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-client.ts +515 -0
  367. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-mode.ts +745 -0
  368. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-types.ts +264 -0
  369. package/pi.dev-src/pi-mono/coding-agent/src/package-manager-cli.ts +270 -0
  370. package/pi.dev-src/pi-mono/coding-agent/src/utils/changelog.ts +99 -0
  371. package/pi.dev-src/pi-mono/coding-agent/src/utils/child-process.ts +86 -0
  372. package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard-image.ts +300 -0
  373. package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard-native.ts +22 -0
  374. package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard.ts +81 -0
  375. package/pi.dev-src/pi-mono/coding-agent/src/utils/exif-orientation.ts +183 -0
  376. package/pi.dev-src/pi-mono/coding-agent/src/utils/frontmatter.ts +39 -0
  377. package/pi.dev-src/pi-mono/coding-agent/src/utils/fs-watch.ts +30 -0
  378. package/pi.dev-src/pi-mono/coding-agent/src/utils/git.ts +192 -0
  379. package/pi.dev-src/pi-mono/coding-agent/src/utils/image-convert.ts +41 -0
  380. package/pi.dev-src/pi-mono/coding-agent/src/utils/image-resize.ts +176 -0
  381. package/pi.dev-src/pi-mono/coding-agent/src/utils/mime.ts +30 -0
  382. package/pi.dev-src/pi-mono/coding-agent/src/utils/paths.ts +20 -0
  383. package/pi.dev-src/pi-mono/coding-agent/src/utils/photon.ts +139 -0
  384. package/pi.dev-src/pi-mono/coding-agent/src/utils/shell.ts +210 -0
  385. package/pi.dev-src/pi-mono/coding-agent/src/utils/sleep.ts +18 -0
  386. package/pi.dev-src/pi-mono/coding-agent/src/utils/tools-manager.ts +287 -0
  387. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-auto-compaction-queue.test.ts +437 -0
  388. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-branching.test.ts +156 -0
  389. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-compaction.test.ts +208 -0
  390. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-concurrent.test.ts +618 -0
  391. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-dynamic-provider.test.ts +117 -0
  392. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-dynamic-tools.test.ts +182 -0
  393. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-retry.test.ts +318 -0
  394. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-runtime-events.test.ts +208 -0
  395. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-stats.test.ts +143 -0
  396. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-tree-navigation.test.ts +323 -0
  397. package/pi.dev-src/pi-mono/coding-agent/test/args.test.ts +328 -0
  398. package/pi.dev-src/pi-mono/coding-agent/test/assistant-message.test.ts +57 -0
  399. package/pi.dev-src/pi-mono/coding-agent/test/auth-storage.test.ts +462 -0
  400. package/pi.dev-src/pi-mono/coding-agent/test/bash-close-hang-windows.test.ts +126 -0
  401. package/pi.dev-src/pi-mono/coding-agent/test/bash-execution-width.test.ts +80 -0
  402. package/pi.dev-src/pi-mono/coding-agent/test/block-images.test.ts +120 -0
  403. package/pi.dev-src/pi-mono/coding-agent/test/clipboard-image-bmp-conversion.test.ts +88 -0
  404. package/pi.dev-src/pi-mono/coding-agent/test/clipboard-image.test.ts +136 -0
  405. package/pi.dev-src/pi-mono/coding-agent/test/compaction-extensions-example.test.ts +66 -0
  406. package/pi.dev-src/pi-mono/coding-agent/test/compaction-extensions.test.ts +415 -0
  407. package/pi.dev-src/pi-mono/coding-agent/test/compaction-serialization.test.ts +79 -0
  408. package/pi.dev-src/pi-mono/coding-agent/test/compaction-summary-reasoning.test.ts +118 -0
  409. package/pi.dev-src/pi-mono/coding-agent/test/compaction-thinking-model.test.ts +223 -0
  410. package/pi.dev-src/pi-mono/coding-agent/test/compaction.test.ts +550 -0
  411. package/pi.dev-src/pi-mono/coding-agent/test/config.test.ts +30 -0
  412. package/pi.dev-src/pi-mono/coding-agent/test/edit-tool-legacy-input.test.ts +116 -0
  413. package/pi.dev-src/pi-mono/coding-agent/test/edit-tool-no-full-redraw.test.ts +235 -0
  414. package/pi.dev-src/pi-mono/coding-agent/test/export-html-whitespace.test.ts +12 -0
  415. package/pi.dev-src/pi-mono/coding-agent/test/export-html-xss.test.ts +28 -0
  416. package/pi.dev-src/pi-mono/coding-agent/test/extensions-discovery.test.ts +463 -0
  417. package/pi.dev-src/pi-mono/coding-agent/test/extensions-input-event.test.ts +112 -0
  418. package/pi.dev-src/pi-mono/coding-agent/test/extensions-runner.test.ts +805 -0
  419. package/pi.dev-src/pi-mono/coding-agent/test/file-mutation-queue.test.ts +163 -0
  420. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/assistant-message-with-thinking-code.json +33 -0
  421. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/before-compaction.jsonl +1003 -0
  422. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/empty-agent/.gitkeep +0 -0
  423. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/empty-cwd/.gitkeep +0 -0
  424. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/large-session.jsonl +1019 -0
  425. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/consecutive-hyphens/SKILL.md +8 -0
  426. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/disable-model-invocation/SKILL.md +9 -0
  427. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/invalid-name-chars/SKILL.md +8 -0
  428. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/invalid-yaml/SKILL.md +8 -0
  429. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/long-name/SKILL.md +8 -0
  430. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/missing-description/SKILL.md +7 -0
  431. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/multiline-description/SKILL.md +11 -0
  432. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/name-mismatch/SKILL.md +8 -0
  433. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/nested/child-skill/SKILL.md +8 -0
  434. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/no-frontmatter/SKILL.md +3 -0
  435. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/root-skill-preferred/SKILL.md +3 -0
  436. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/root-skill-preferred/nested-child/SKILL.md +3 -0
  437. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/unknown-field/SKILL.md +10 -0
  438. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/valid-skill/SKILL.md +8 -0
  439. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills-collision/first/calendar/SKILL.md +8 -0
  440. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills-collision/second/calendar/SKILL.md +8 -0
  441. package/pi.dev-src/pi-mono/coding-agent/test/footer-data-provider.test.ts +265 -0
  442. package/pi.dev-src/pi-mono/coding-agent/test/footer-width.test.ts +115 -0
  443. package/pi.dev-src/pi-mono/coding-agent/test/frontmatter.test.ts +60 -0
  444. package/pi.dev-src/pi-mono/coding-agent/test/git-ssh-url.test.ts +78 -0
  445. package/pi.dev-src/pi-mono/coding-agent/test/git-update.test.ts +411 -0
  446. package/pi.dev-src/pi-mono/coding-agent/test/image-processing.test.ts +145 -0
  447. package/pi.dev-src/pi-mono/coding-agent/test/image-resize-callers.test.ts +53 -0
  448. package/pi.dev-src/pi-mono/coding-agent/test/initial-message.test.ts +48 -0
  449. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-anthropic-warning.test.ts +73 -0
  450. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-clone-command.test.ts +77 -0
  451. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-compaction.test.ts +57 -0
  452. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-import-command.test.ts +144 -0
  453. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-status.test.ts +635 -0
  454. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-suspend.test.ts +149 -0
  455. package/pi.dev-src/pi-mono/coding-agent/test/keybindings-migration.test.ts +88 -0
  456. package/pi.dev-src/pi-mono/coding-agent/test/model-registry.test.ts +1209 -0
  457. package/pi.dev-src/pi-mono/coding-agent/test/model-resolver.test.ts +436 -0
  458. package/pi.dev-src/pi-mono/coding-agent/test/package-command-paths.test.ts +144 -0
  459. package/pi.dev-src/pi-mono/coding-agent/test/package-manager-ssh.test.ts +97 -0
  460. package/pi.dev-src/pi-mono/coding-agent/test/package-manager.test.ts +1795 -0
  461. package/pi.dev-src/pi-mono/coding-agent/test/path-utils.test.ts +162 -0
  462. package/pi.dev-src/pi-mono/coding-agent/test/plan-mode-utils.test.ts +261 -0
  463. package/pi.dev-src/pi-mono/coding-agent/test/print-mode.test.ts +140 -0
  464. package/pi.dev-src/pi-mono/coding-agent/test/prompt-templates.test.ts +515 -0
  465. package/pi.dev-src/pi-mono/coding-agent/test/resource-loader.test.ts +576 -0
  466. package/pi.dev-src/pi-mono/coding-agent/test/rpc-client-clone.test.ts +29 -0
  467. package/pi.dev-src/pi-mono/coding-agent/test/rpc-example.ts +86 -0
  468. package/pi.dev-src/pi-mono/coding-agent/test/rpc-jsonl.test.ts +65 -0
  469. package/pi.dev-src/pi-mono/coding-agent/test/rpc-prompt-response-semantics.test.ts +285 -0
  470. package/pi.dev-src/pi-mono/coding-agent/test/rpc.test.ts +321 -0
  471. package/pi.dev-src/pi-mono/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts +364 -0
  472. package/pi.dev-src/pi-mono/coding-agent/test/sdk-openrouter-attribution.test.ts +181 -0
  473. package/pi.dev-src/pi-mono/coding-agent/test/sdk-session-manager.test.ts +95 -0
  474. package/pi.dev-src/pi-mono/coding-agent/test/sdk-skills.test.ts +109 -0
  475. package/pi.dev-src/pi-mono/coding-agent/test/session-cwd.test.ts +91 -0
  476. package/pi.dev-src/pi-mono/coding-agent/test/session-info-modified-timestamp.test.ts +83 -0
  477. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/build-context.test.ts +268 -0
  478. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/custom-session-id.test.ts +109 -0
  479. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/file-operations.test.ts +208 -0
  480. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/labels.test.ts +198 -0
  481. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/migration.test.ts +78 -0
  482. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/save-entry.test.ts +55 -0
  483. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/tree-traversal.test.ts +533 -0
  484. package/pi.dev-src/pi-mono/coding-agent/test/session-selector-path-delete.test.ts +315 -0
  485. package/pi.dev-src/pi-mono/coding-agent/test/session-selector-rename.test.ts +111 -0
  486. package/pi.dev-src/pi-mono/coding-agent/test/session-selector-search.test.ts +195 -0
  487. package/pi.dev-src/pi-mono/coding-agent/test/settings-manager-bug.test.ts +147 -0
  488. package/pi.dev-src/pi-mono/coding-agent/test/settings-manager.test.ts +319 -0
  489. package/pi.dev-src/pi-mono/coding-agent/test/skills.test.ts +432 -0
  490. package/pi.dev-src/pi-mono/coding-agent/test/stdout-cleanliness.test.ts +103 -0
  491. package/pi.dev-src/pi-mono/coding-agent/test/streaming-render-debug.ts +97 -0
  492. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-bash-persistence.test.ts +242 -0
  493. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-compaction.test.ts +343 -0
  494. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-model-extension.test.ts +325 -0
  495. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-prompt.test.ts +323 -0
  496. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-queue.test.ts +422 -0
  497. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-retry-events.test.ts +358 -0
  498. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-runtime.test.ts +553 -0
  499. package/pi.dev-src/pi-mono/coding-agent/test/suite/harness.ts +208 -0
  500. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2023-queued-slash-command-followup.test.ts +80 -0
  501. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2753-reload-stale-resource-settings.test.ts +100 -0
  502. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2781-skill-collision-precedence.test.ts +120 -0
  503. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2791-fswatch-error-crash.test.ts +106 -0
  504. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2835-tools-allowlist-filters-extension-tools.test.ts +94 -0
  505. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2860-replaced-session-context.test.ts +274 -0
  506. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3217-scoped-model-order.test.ts +104 -0
  507. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3302-find-path-glob.test.ts +72 -0
  508. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3303-find-nested-gitignore.test.ts +83 -0
  509. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3317-network-connection-lost-retry.test.ts +33 -0
  510. package/pi.dev-src/pi-mono/coding-agent/test/system-prompt.test.ts +102 -0
  511. package/pi.dev-src/pi-mono/coding-agent/test/test-harness.test.ts +321 -0
  512. package/pi.dev-src/pi-mono/coding-agent/test/test-harness.ts +446 -0
  513. package/pi.dev-src/pi-mono/coding-agent/test/test-theme-colors.ts +246 -0
  514. package/pi.dev-src/pi-mono/coding-agent/test/theme-export.test.ts +104 -0
  515. package/pi.dev-src/pi-mono/coding-agent/test/tool-execution-component.test.ts +331 -0
  516. package/pi.dev-src/pi-mono/coding-agent/test/tools.test.ts +889 -0
  517. package/pi.dev-src/pi-mono/coding-agent/test/tree-selector.test.ts +655 -0
  518. package/pi.dev-src/pi-mono/coding-agent/test/trigger-compact-extension.test.ts +57 -0
  519. package/pi.dev-src/pi-mono/coding-agent/test/truncate-to-width.test.ts +81 -0
  520. package/pi.dev-src/pi-mono/coding-agent/test/user-message.test.ts +25 -0
  521. package/pi.dev-src/pi-mono/coding-agent/test/utilities.ts +316 -0
  522. package/pi.dev-src/pi-mono/coding-agent/tsconfig.examples.json +16 -0
  523. package/pi.dev-src/pi-mono/coding-agent/vitest.config.ts +26 -0
  524. package/pi.dev-src/pi-mono/mom/dev.sh +30 -0
  525. package/pi.dev-src/pi-mono/mom/docker.sh +95 -0
  526. package/pi.dev-src/pi-mono/mom/scripts/migrate-timestamps.ts +121 -0
  527. package/pi.dev-src/pi-mono/mom/src/agent.ts +882 -0
  528. package/pi.dev-src/pi-mono/mom/src/context.ts +180 -0
  529. package/pi.dev-src/pi-mono/mom/src/download.ts +117 -0
  530. package/pi.dev-src/pi-mono/mom/src/events.ts +442 -0
  531. package/pi.dev-src/pi-mono/mom/src/fs-watch.ts +30 -0
  532. package/pi.dev-src/pi-mono/mom/src/log.ts +271 -0
  533. package/pi.dev-src/pi-mono/mom/src/main.ts +367 -0
  534. package/pi.dev-src/pi-mono/mom/src/sandbox.ts +221 -0
  535. package/pi.dev-src/pi-mono/mom/src/slack.ts +623 -0
  536. package/pi.dev-src/pi-mono/mom/src/store.ts +234 -0
  537. package/pi.dev-src/pi-mono/mom/src/tools/attach.ts +47 -0
  538. package/pi.dev-src/pi-mono/mom/src/tools/bash.ts +97 -0
  539. package/pi.dev-src/pi-mono/mom/src/tools/edit.ts +165 -0
  540. package/pi.dev-src/pi-mono/mom/src/tools/index.ts +19 -0
  541. package/pi.dev-src/pi-mono/mom/src/tools/read.ts +159 -0
  542. package/pi.dev-src/pi-mono/mom/src/tools/truncate.ts +236 -0
  543. package/pi.dev-src/pi-mono/mom/src/tools/write.ts +45 -0
  544. package/pi.dev-src/pi-mono/mom/test/events.test.ts +50 -0
  545. package/pi.dev-src/pi-mono/pods/scripts/model_run.sh +83 -0
  546. package/pi.dev-src/pi-mono/pods/scripts/pod_setup.sh +336 -0
  547. package/pi.dev-src/pi-mono/pods/src/cli.ts +360 -0
  548. package/pi.dev-src/pi-mono/pods/src/commands/models.ts +753 -0
  549. package/pi.dev-src/pi-mono/pods/src/commands/pods.ts +205 -0
  550. package/pi.dev-src/pi-mono/pods/src/commands/prompt.ts +84 -0
  551. package/pi.dev-src/pi-mono/pods/src/config.ts +80 -0
  552. package/pi.dev-src/pi-mono/pods/src/index.ts +2 -0
  553. package/pi.dev-src/pi-mono/pods/src/model-configs.ts +111 -0
  554. package/pi.dev-src/pi-mono/pods/src/models.json +295 -0
  555. package/pi.dev-src/pi-mono/pods/src/ssh.ts +151 -0
  556. package/pi.dev-src/pi-mono/pods/src/types.ts +27 -0
  557. package/pi.dev-src/pi-mono/tui/src/autocomplete.ts +783 -0
  558. package/pi.dev-src/pi-mono/tui/src/components/box.ts +137 -0
  559. package/pi.dev-src/pi-mono/tui/src/components/cancellable-loader.ts +40 -0
  560. package/pi.dev-src/pi-mono/tui/src/components/editor.ts +2280 -0
  561. package/pi.dev-src/pi-mono/tui/src/components/image.ts +104 -0
  562. package/pi.dev-src/pi-mono/tui/src/components/input.ts +503 -0
  563. package/pi.dev-src/pi-mono/tui/src/components/loader.ts +86 -0
  564. package/pi.dev-src/pi-mono/tui/src/components/markdown.ts +852 -0
  565. package/pi.dev-src/pi-mono/tui/src/components/select-list.ts +229 -0
  566. package/pi.dev-src/pi-mono/tui/src/components/settings-list.ts +250 -0
  567. package/pi.dev-src/pi-mono/tui/src/components/spacer.ts +28 -0
  568. package/pi.dev-src/pi-mono/tui/src/components/text.ts +106 -0
  569. package/pi.dev-src/pi-mono/tui/src/components/truncated-text.ts +65 -0
  570. package/pi.dev-src/pi-mono/tui/src/editor-component.ts +74 -0
  571. package/pi.dev-src/pi-mono/tui/src/fuzzy.ts +133 -0
  572. package/pi.dev-src/pi-mono/tui/src/index.ts +106 -0
  573. package/pi.dev-src/pi-mono/tui/src/keybindings.ts +244 -0
  574. package/pi.dev-src/pi-mono/tui/src/keys.ts +1400 -0
  575. package/pi.dev-src/pi-mono/tui/src/kill-ring.ts +46 -0
  576. package/pi.dev-src/pi-mono/tui/src/stdin-buffer.ts +386 -0
  577. package/pi.dev-src/pi-mono/tui/src/terminal-image.ts +414 -0
  578. package/pi.dev-src/pi-mono/tui/src/terminal.ts +373 -0
  579. package/pi.dev-src/pi-mono/tui/src/tui.ts +1243 -0
  580. package/pi.dev-src/pi-mono/tui/src/undo-stack.ts +28 -0
  581. package/pi.dev-src/pi-mono/tui/src/utils.ts +1085 -0
  582. package/pi.dev-src/pi-mono/tui/test/autocomplete.test.ts +542 -0
  583. package/pi.dev-src/pi-mono/tui/test/bug-regression-isimageline-startswith-bug.test.ts +237 -0
  584. package/pi.dev-src/pi-mono/tui/test/chat-simple.ts +129 -0
  585. package/pi.dev-src/pi-mono/tui/test/editor.test.ts +3812 -0
  586. package/pi.dev-src/pi-mono/tui/test/fuzzy.test.ts +98 -0
  587. package/pi.dev-src/pi-mono/tui/test/image-test.ts +56 -0
  588. package/pi.dev-src/pi-mono/tui/test/input.test.ts +580 -0
  589. package/pi.dev-src/pi-mono/tui/test/key-tester.ts +105 -0
  590. package/pi.dev-src/pi-mono/tui/test/keybindings.test.ts +38 -0
  591. package/pi.dev-src/pi-mono/tui/test/keys.test.ts +614 -0
  592. package/pi.dev-src/pi-mono/tui/test/markdown.test.ts +1236 -0
  593. package/pi.dev-src/pi-mono/tui/test/overlay-non-capturing.test.ts +609 -0
  594. package/pi.dev-src/pi-mono/tui/test/overlay-options.test.ts +538 -0
  595. package/pi.dev-src/pi-mono/tui/test/overlay-short-content.test.ts +56 -0
  596. package/pi.dev-src/pi-mono/tui/test/regression-regional-indicator-width.test.ts +52 -0
  597. package/pi.dev-src/pi-mono/tui/test/select-list.test.ts +116 -0
  598. package/pi.dev-src/pi-mono/tui/test/stdin-buffer.test.ts +422 -0
  599. package/pi.dev-src/pi-mono/tui/test/terminal-image.test.ts +279 -0
  600. package/pi.dev-src/pi-mono/tui/test/test-themes.ts +38 -0
  601. package/pi.dev-src/pi-mono/tui/test/truncate-to-width.test.ts +62 -0
  602. package/pi.dev-src/pi-mono/tui/test/truncated-text.test.ts +129 -0
  603. package/pi.dev-src/pi-mono/tui/test/tui-cell-size-input.test.ts +81 -0
  604. package/pi.dev-src/pi-mono/tui/test/tui-overlay-style-leak.test.ts +72 -0
  605. package/pi.dev-src/pi-mono/tui/test/tui-render.test.ts +509 -0
  606. package/pi.dev-src/pi-mono/tui/test/viewport-overwrite-repro.ts +106 -0
  607. package/pi.dev-src/pi-mono/tui/test/virtual-terminal.ts +218 -0
  608. package/pi.dev-src/pi-mono/tui/test/wrap-ansi.test.ts +207 -0
  609. package/pi.dev-src/pi-mono/tui/vitest.config.ts +7 -0
  610. package/pi.dev-src/pi-mono/web-ui/example/index.html +13 -0
  611. package/pi.dev-src/pi-mono/web-ui/example/src/app.css +1 -0
  612. package/pi.dev-src/pi-mono/web-ui/example/src/custom-messages.ts +99 -0
  613. package/pi.dev-src/pi-mono/web-ui/example/src/main.ts +421 -0
  614. package/pi.dev-src/pi-mono/web-ui/example/tsconfig.json +23 -0
  615. package/pi.dev-src/pi-mono/web-ui/example/vite.config.ts +6 -0
  616. package/pi.dev-src/pi-mono/web-ui/scripts/count-prompt-tokens.ts +88 -0
  617. package/pi.dev-src/pi-mono/web-ui/src/ChatPanel.ts +209 -0
  618. package/pi.dev-src/pi-mono/web-ui/src/app.css +68 -0
  619. package/pi.dev-src/pi-mono/web-ui/src/components/AgentInterface.ts +403 -0
  620. package/pi.dev-src/pi-mono/web-ui/src/components/AttachmentTile.ts +107 -0
  621. package/pi.dev-src/pi-mono/web-ui/src/components/ConsoleBlock.ts +72 -0
  622. package/pi.dev-src/pi-mono/web-ui/src/components/CustomProviderCard.ts +100 -0
  623. package/pi.dev-src/pi-mono/web-ui/src/components/ExpandableSection.ts +46 -0
  624. package/pi.dev-src/pi-mono/web-ui/src/components/Input.ts +113 -0
  625. package/pi.dev-src/pi-mono/web-ui/src/components/MessageEditor.ts +405 -0
  626. package/pi.dev-src/pi-mono/web-ui/src/components/MessageList.ts +98 -0
  627. package/pi.dev-src/pi-mono/web-ui/src/components/Messages.ts +383 -0
  628. package/pi.dev-src/pi-mono/web-ui/src/components/ProviderKeyInput.ts +153 -0
  629. package/pi.dev-src/pi-mono/web-ui/src/components/SandboxedIframe.ts +626 -0
  630. package/pi.dev-src/pi-mono/web-ui/src/components/StreamingMessageContainer.ts +103 -0
  631. package/pi.dev-src/pi-mono/web-ui/src/components/ThinkingBlock.ts +43 -0
  632. package/pi.dev-src/pi-mono/web-ui/src/components/message-renderer-registry.ts +28 -0
  633. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/ArtifactsRuntimeProvider.ts +220 -0
  634. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
  635. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/ConsoleRuntimeProvider.ts +186 -0
  636. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
  637. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
  638. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
  639. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/SandboxRuntimeProvider.ts +52 -0
  640. package/pi.dev-src/pi-mono/web-ui/src/dialogs/ApiKeyPromptDialog.ts +75 -0
  641. package/pi.dev-src/pi-mono/web-ui/src/dialogs/AttachmentOverlay.ts +636 -0
  642. package/pi.dev-src/pi-mono/web-ui/src/dialogs/CustomProviderDialog.ts +274 -0
  643. package/pi.dev-src/pi-mono/web-ui/src/dialogs/ModelSelector.ts +370 -0
  644. package/pi.dev-src/pi-mono/web-ui/src/dialogs/PersistentStorageDialog.ts +144 -0
  645. package/pi.dev-src/pi-mono/web-ui/src/dialogs/ProvidersModelsTab.ts +212 -0
  646. package/pi.dev-src/pi-mono/web-ui/src/dialogs/SessionListDialog.ts +150 -0
  647. package/pi.dev-src/pi-mono/web-ui/src/dialogs/SettingsDialog.ts +218 -0
  648. package/pi.dev-src/pi-mono/web-ui/src/index.ts +120 -0
  649. package/pi.dev-src/pi-mono/web-ui/src/prompts/prompts.ts +282 -0
  650. package/pi.dev-src/pi-mono/web-ui/src/storage/app-storage.ts +60 -0
  651. package/pi.dev-src/pi-mono/web-ui/src/storage/backends/indexeddb-storage-backend.ts +193 -0
  652. package/pi.dev-src/pi-mono/web-ui/src/storage/store.ts +33 -0
  653. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/custom-providers-store.ts +62 -0
  654. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/provider-keys-store.ts +33 -0
  655. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/sessions-store.ts +136 -0
  656. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/settings-store.ts +34 -0
  657. package/pi.dev-src/pi-mono/web-ui/src/storage/types.ts +206 -0
  658. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ArtifactElement.ts +14 -0
  659. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ArtifactPill.ts +26 -0
  660. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/Console.ts +93 -0
  661. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/DocxArtifact.ts +213 -0
  662. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ExcelArtifact.ts +231 -0
  663. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/GenericArtifact.ts +117 -0
  664. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/HtmlArtifact.ts +195 -0
  665. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ImageArtifact.ts +116 -0
  666. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/MarkdownArtifact.ts +82 -0
  667. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/PdfArtifact.ts +201 -0
  668. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/SvgArtifact.ts +119 -0
  669. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/TextArtifact.ts +148 -0
  670. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/artifacts-tool-renderer.ts +310 -0
  671. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/artifacts.ts +713 -0
  672. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/index.ts +7 -0
  673. package/pi.dev-src/pi-mono/web-ui/src/tools/extract-document.ts +275 -0
  674. package/pi.dev-src/pi-mono/web-ui/src/tools/index.ts +46 -0
  675. package/pi.dev-src/pi-mono/web-ui/src/tools/javascript-repl.ts +293 -0
  676. package/pi.dev-src/pi-mono/web-ui/src/tools/renderer-registry.ts +130 -0
  677. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/BashRenderer.ts +52 -0
  678. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/CalculateRenderer.ts +58 -0
  679. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/DefaultRenderer.ts +103 -0
  680. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/GetCurrentTimeRenderer.ts +92 -0
  681. package/pi.dev-src/pi-mono/web-ui/src/tools/types.ts +15 -0
  682. package/pi.dev-src/pi-mono/web-ui/src/utils/attachment-utils.ts +472 -0
  683. package/pi.dev-src/pi-mono/web-ui/src/utils/auth-token.ts +22 -0
  684. package/pi.dev-src/pi-mono/web-ui/src/utils/format.ts +42 -0
  685. package/pi.dev-src/pi-mono/web-ui/src/utils/i18n.ts +653 -0
  686. package/pi.dev-src/pi-mono/web-ui/src/utils/model-discovery.ts +277 -0
  687. package/pi.dev-src/pi-mono/web-ui/src/utils/proxy-utils.ts +139 -0
  688. package/pi.dev-src/pi-mono/web-ui/src/utils/test-sessions.ts +2357 -0
  689. package/pi.dev-src/pi-mono/web-ui/tsconfig.json +7 -0
  690. package/scripts/lib/extension-debug-harness.ts +3 -10
  691. package/scripts/lib/recording-extension-api.ts +71 -2
  692. package/scripts/pi-usereq-debug.sh +1 -1
  693. package/scripts/tool-args-to-params.ts +4 -15
  694. package/src/cli.ts +24 -68
  695. package/src/core/agent-tool-json.ts +53 -239
  696. package/src/core/compress-payload.ts +4 -74
  697. package/src/core/compress.ts +26 -15
  698. package/src/core/config.ts +546 -65
  699. package/src/core/debug-runtime.ts +542 -0
  700. package/src/core/extension-status.ts +324 -202
  701. package/src/core/find-constructs.ts +3 -3
  702. package/src/core/find-payload.ts +26 -132
  703. package/src/core/path-context.ts +346 -81
  704. package/src/core/pi-notify.ts +585 -315
  705. package/src/core/pi-usereq-tools.ts +57 -19
  706. package/src/core/prompt-command-catalog.ts +46 -0
  707. package/src/core/prompt-command-runtime.ts +1650 -0
  708. package/src/core/prompt-command-state.ts +211 -0
  709. package/src/core/prompts.ts +173 -41
  710. package/src/core/reference-payload.ts +5 -63
  711. package/src/core/resources.ts +61 -5
  712. package/src/core/settings-menu.ts +33 -7
  713. package/src/core/source-analyzer.ts +27 -7
  714. package/src/core/token-counter.ts +61 -79
  715. package/src/core/tool-runner.ts +38 -317
  716. package/src/index.ts +2629 -1220
  717. package/src/resources/images/favicon.svg +21 -0
  718. package/src/resources/images/pi.dev.png +0 -0
  719. package/src/resources/instructions/git_commit.md +16 -0
  720. package/src/resources/instructions/git_read-only.md +2 -0
  721. package/src/resources/prompts/analyze.md +12 -13
  722. package/src/resources/prompts/change.md +21 -59
  723. package/src/resources/prompts/check.md +14 -16
  724. package/src/resources/prompts/cover.md +21 -59
  725. package/src/resources/prompts/create.md +5 -4
  726. package/src/resources/prompts/fix.md +20 -58
  727. package/src/resources/prompts/flowchart.md +14 -49
  728. package/src/resources/prompts/implement.md +14 -50
  729. package/src/resources/prompts/new.md +20 -58
  730. package/src/resources/prompts/readme.md +14 -50
  731. package/src/resources/prompts/recreate.md +14 -45
  732. package/src/resources/prompts/refactor.md +20 -58
  733. package/src/resources/prompts/references.md +7 -43
  734. package/src/resources/prompts/renumber.md +8 -39
  735. package/src/resources/prompts/workflow.md +14 -50
  736. package/src/resources/prompts/write.md +5 -4
  737. package/tests/attended-results-scenarios.ts +30 -136
  738. package/tests/cli-command-option-parity.test.ts +148 -192
  739. package/tests/debug-extension-harness.test.ts +74 -73
  740. package/tests/extension-registration.test.ts +3626 -685
  741. package/tests/fixtures_attended_results/project/compress-line-numbers.json +1 -1
  742. package/tests/fixtures_attended_results/project/compress.json +1 -1
  743. package/tests/fixtures_attended_results/project/find-line-numbers.json +1 -1
  744. package/tests/fixtures_attended_results/project/find.json +1 -1
  745. package/tests/fixtures_attended_results/project/references.json +2 -2
  746. package/tests/fixtures_attended_results/standalone/files-compress/fixture_go.go.json +1 -1
  747. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_go.go.json +1 -1
  748. package/tests/fixtures_attended_results/standalone/files-find/fixture_go.go.json +1 -1
  749. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_go.go.json +1 -1
  750. package/tests/fixtures_attended_results/standalone/files-references/fixture_c.c.json +1 -1
  751. package/tests/fixtures_attended_results/standalone/files-references/fixture_cpp.cpp.json +1 -1
  752. package/tests/fixtures_attended_results/standalone/files-references/fixture_csharp.cs.json +1 -1
  753. package/tests/fixtures_attended_results/standalone/files-references/fixture_elixir.ex.json +1 -1
  754. package/tests/fixtures_attended_results/standalone/files-references/fixture_go.go.json +1 -1
  755. package/tests/fixtures_attended_results/standalone/files-references/fixture_haskell.hs.json +1 -1
  756. package/tests/fixtures_attended_results/standalone/files-references/fixture_java.java.json +1 -1
  757. package/tests/fixtures_attended_results/standalone/files-references/fixture_javascript.js.json +1 -1
  758. package/tests/fixtures_attended_results/standalone/files-references/fixture_kotlin.kt.json +1 -1
  759. package/tests/fixtures_attended_results/standalone/files-references/fixture_lua.lua.json +1 -1
  760. package/tests/fixtures_attended_results/standalone/files-references/fixture_perl.pl.json +1 -1
  761. package/tests/fixtures_attended_results/standalone/files-references/fixture_php.php.json +1 -1
  762. package/tests/fixtures_attended_results/standalone/files-references/fixture_python.py.json +1 -1
  763. package/tests/fixtures_attended_results/standalone/files-references/fixture_ruby.rb.json +1 -1
  764. package/tests/fixtures_attended_results/standalone/files-references/fixture_rust.rs.json +1 -1
  765. package/tests/fixtures_attended_results/standalone/files-references/fixture_scala.scala.json +1 -1
  766. package/tests/fixtures_attended_results/standalone/files-references/fixture_shell.sh.json +1 -1
  767. package/tests/fixtures_attended_results/standalone/files-references/fixture_swift.swift.json +1 -1
  768. package/tests/fixtures_attended_results/standalone/files-references/fixture_typescript.ts.json +1 -1
  769. package/tests/fixtures_attended_results/standalone/files-references/fixture_zig.zig.json +1 -1
  770. package/tests/helpers.ts +24 -15
  771. package/tests/oracle-project.test.ts +29 -30
  772. package/tests/oracle-standalone.test.ts +27 -1
  773. package/tests/prompt-rendering.test.ts +40 -9
  774. package/tests/python_oracle_stubs/tiktoken.py +76 -0
@@ -1,70 +1,93 @@
1
1
  /**
2
2
  * @file
3
- * @brief Implements pi-usereq terminal-beep, external sound-hook, and Pushover notification helpers.
4
- * @details Centralizes configuration defaults, status serialization, agent-end outcome classification, terminal notification dispatch, successful-run external sound-command execution, and successful-run Pushover delivery. Runtime is O(m + c + b) in `agent_end` message count plus command length and Pushover payload size. Side effects include stdout writes, detached child-process spawning, and outbound HTTPS requests.
3
+ * @brief Implements pi-usereq command-notify, sound, and Pushover prompt-end helpers.
4
+ * @details Centralizes configuration defaults, status serialization, prompt-end outcome classification, placeholder substitution, detached shell-command execution, and native Pushover delivery. Runtime is O(m + c + b) in `agent_end` message count plus command length and Pushover payload size. Side effects include detached child-process spawning and outbound HTTPS requests.
5
5
  */
6
6
 
7
- import { execFile, spawn } from "node:child_process";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ import { spawn } from "node:child_process";
8
10
  import * as https from "node:https";
9
11
  import type { AgentEndEvent } from "@mariozechner/pi-coding-agent";
10
- import { getInstallationPath } from "./path-context.js";
12
+ import { getInstallationPath, normalizePathSlashes } from "./path-context.js";
11
13
  import type { UseReqConfig } from "./config.js";
12
14
 
13
15
  /**
14
- * @brief Enumerates supported successful-run sound levels.
15
- * @details Defines the canonical persisted order used by config normalization, shortcut cycling, status rendering, and external command selection. Access complexity is O(1).
16
+ * @brief Enumerates supported sound levels.
17
+ * @details Defines the canonical persisted order used by config normalization, shortcut cycling, status rendering, and sound-command selection. Access complexity is O(1).
16
18
  */
17
19
  export const PI_NOTIFY_SOUND_LEVELS = ["none", "low", "mid", "high"] as const;
18
20
 
19
21
  /**
20
- * @brief Represents one supported successful-run sound level.
21
- * @details Narrows configuration parsing and runtime command dispatch to the canonical four-state sound-hook domain. Compile-time only and introduces no runtime cost.
22
+ * @brief Represents one supported sound level.
23
+ * @details Narrows configuration parsing and runtime sound-command dispatch to the canonical four-state domain. Compile-time only and introduces no runtime cost.
22
24
  */
23
25
  export type PiNotifySoundLevel = (typeof PI_NOTIFY_SOUND_LEVELS)[number];
24
26
 
25
27
  /**
26
- * @brief Enumerates supported prompt-end notification outcomes.
27
- * @details Distinguishes successful completion, escape-triggered abortion, and error termination for independent beep routing. Access complexity is O(1).
28
+ * @brief Enumerates supported prompt-end outcomes.
29
+ * @details Distinguishes completed, interrupted, and failed prompt termination states for cross-channel notification routing. Access complexity is O(1).
28
30
  */
29
- export const PI_NOTIFY_OUTCOMES = ["end", "esc", "err"] as const;
31
+ export const PI_NOTIFY_OUTCOMES = ["completed", "interrupted", "failed"] as const;
30
32
 
31
33
  /**
32
- * @brief Represents one supported prompt-end notification outcome.
33
- * @details Narrows prompt-end event classification and status serialization to the canonical three-outcome domain. Compile-time only and introduces no runtime cost.
34
+ * @brief Represents one supported prompt-end outcome.
35
+ * @details Narrows prompt-end event classification and per-feature toggle routing to the canonical completed/interrupted/failed domain. Compile-time only and introduces no runtime cost.
34
36
  */
35
37
  export type PiNotifyOutcome = (typeof PI_NOTIFY_OUTCOMES)[number];
36
38
 
37
39
  /**
38
- * @brief Defines the default successful-run sound toggle shortcut.
40
+ * @brief Defines the default sound toggle shortcut.
39
41
  * @details Seeds persisted configuration when no project-specific shortcut exists. Access complexity is O(1).
40
42
  * @satisfies REQ-134
41
43
  */
42
44
  export const DEFAULT_PI_NOTIFY_SOUND_TOGGLE_SHORTCUT = "alt+s";
43
45
 
44
46
  /**
45
- * @brief Defines the default low-level successful-run sound command.
46
- * @details Uses the bundled soft notification asset with a low playback volume and defers `%%INSTALLATION_PATH%%` substitution until runtime execution. Access complexity is O(1).
47
- * @satisfies REQ-133, REQ-142
47
+ * @brief Defines the default command-notify shell command.
48
+ * @details Uses `notify-send`, the bundled icon, the fixed `PI-useReq` application name, and runtime placeholder substitution for prompt name, base path, elapsed time, and terminal outcome text. Access complexity is O(1).
49
+ * @satisfies REQ-175
50
+ */
51
+ export const DEFAULT_PI_NOTIFY_CMD = "notify-send -i %%INSTALLATION_PATH%%/resources/images/pi.dev.png -a \"PI-useReq\" \"%%PROMT%% @ %%BASE%% [%%TIME%%]\" \"%%RESULT%%\"";
52
+
53
+ /**
54
+ * @brief Defines the default low-volume sound command.
55
+ * @details Uses the bundled notification asset with a low playback volume and defers `%%INSTALLATION_PATH%%` substitution until runtime execution. Access complexity is O(1).
56
+ * @satisfies REQ-133
48
57
  */
49
58
  export const DEFAULT_PI_NOTIFY_SOUND_LOW_CMD = "paplay --volume=21845 %%INSTALLATION_PATH%%/resources/sounds/Soft-high-tech-notification-sound-effect.mp3";
50
59
 
51
60
  /**
52
- * @brief Defines the default mid-level successful-run sound command.
53
- * @details Uses the bundled soft notification asset with a mid playback volume and defers `%%INSTALLATION_PATH%%` substitution until runtime execution. Access complexity is O(1).
54
- * @satisfies REQ-133, REQ-143
61
+ * @brief Defines the default mid-volume sound command.
62
+ * @details Uses the bundled notification asset with a mid playback volume and defers `%%INSTALLATION_PATH%%` substitution until runtime execution. Access complexity is O(1).
63
+ * @satisfies REQ-133
55
64
  */
56
65
  export const DEFAULT_PI_NOTIFY_SOUND_MID_CMD = "paplay --volume=43690 %%INSTALLATION_PATH%%/resources/sounds/Soft-high-tech-notification-sound-effect.mp3";
57
66
 
58
67
  /**
59
- * @brief Defines the default high-level successful-run sound command.
60
- * @details Uses the bundled soft notification asset with a high playback volume and defers `%%INSTALLATION_PATH%%` substitution until runtime execution. Access complexity is O(1).
61
- * @satisfies REQ-133, REQ-144
68
+ * @brief Defines the default high-volume sound command.
69
+ * @details Uses the bundled notification asset with a high playback volume and defers `%%INSTALLATION_PATH%%` substitution until runtime execution. Access complexity is O(1).
70
+ * @satisfies REQ-133
62
71
  */
63
72
  export const DEFAULT_PI_NOTIFY_SOUND_HIGH_CMD = "paplay --volume=65535 %%INSTALLATION_PATH%%/resources/sounds/Soft-high-tech-notification-sound-effect.mp3";
64
73
 
74
+ /**
75
+ * @brief Defines the default Pushover title template.
76
+ * @details Reuses the same runtime placeholder contract required by `PI_NOTIFY_CMD` except for `%%INSTALLATION_PATH%%`. Access complexity is O(1).
77
+ * @satisfies REQ-185
78
+ */
79
+ export const DEFAULT_PI_NOTIFY_PUSHOVER_TITLE = "%%PROMT%% @ %%BASE%% [%%TIME%%]";
80
+
81
+ /**
82
+ * @brief Defines the default Pushover text template.
83
+ * @details Prefixes outbound Pushover messages with the terminal outcome token and preserves the raw prompt arguments on the following line so notifications remain traceable to the invoked prompt command. Access complexity is O(1).
84
+ * @satisfies REQ-185
85
+ */
86
+ export const DEFAULT_PI_NOTIFY_PUSHOVER_TEXT = "%%RESULT%%\n%%ARGS%%";
87
+
65
88
  /**
66
89
  * @brief Enumerates supported Pushover priorities.
67
- * @details Restricts persisted Pushover delivery to the canonical normal and high-priority values accepted by the user-facing configuration menu. Access complexity is O(1).
90
+ * @details Restricts persisted Pushover delivery to the canonical normal and high-priority values accepted by the configuration menu. Access complexity is O(1).
68
91
  */
69
92
  export const PI_NOTIFY_PUSHOVER_PRIORITIES = [0, 1] as const;
70
93
 
@@ -75,10 +98,10 @@ export const PI_NOTIFY_PUSHOVER_PRIORITIES = [0, 1] as const;
75
98
  export type PiNotifyPushoverPriority = (typeof PI_NOTIFY_PUSHOVER_PRIORITIES)[number];
76
99
 
77
100
  /**
78
- * @brief Describes one successful prompt-completion payload routed to Pushover.
79
- * @details Stores the prompt command name, substituted prompt arguments, runtime base path, and successful completion duration used to build one Pushover API request. The interface is compile-time only and introduces no runtime side effects.
101
+ * @brief Describes one prompt-end request used for command-notify and Pushover substitution.
102
+ * @details Stores the prompt command name, raw prompt arguments, runtime base path, and final prompt duration required to resolve runtime placeholders. The interface is compile-time only and introduces no runtime cost.
80
103
  */
81
- export interface PiNotifyPushoverRequest {
104
+ export interface PiNotifyEventRequest {
82
105
  promptName: string;
83
106
  promptArgs: string;
84
107
  basePath: string;
@@ -87,31 +110,52 @@ export interface PiNotifyPushoverRequest {
87
110
 
88
111
  /**
89
112
  * @brief Describes the configuration fields consumed by pi-notify helpers.
90
- * @details Narrows the full project config to the persisted notification, sound-hook, and Pushover fields used by status rendering, prompt-end routing, and shortcut toggles. Compile-time only and introduces no runtime cost.
113
+ * @details Narrows the full project config to the persisted notify, sound, and Pushover fields used by status rendering, prompt-end routing, and shortcut toggles. Compile-time only and introduces no runtime cost.
91
114
  */
92
115
  export type PiNotifyConfigFields = Pick<
93
116
  UseReqConfig,
94
- | "notify-beep-on-end"
95
- | "notify-beep-on-esc"
96
- | "notify-beep-on-error"
117
+ | "notify-enabled"
118
+ | "notify-on-completed"
119
+ | "notify-on-interrupted"
120
+ | "notify-on-failed"
97
121
  | "notify-sound"
122
+ | "notify-sound-on-completed"
123
+ | "notify-sound-on-interrupted"
124
+ | "notify-sound-on-failed"
98
125
  | "notify-sound-toggle-shortcut"
99
- | "notify-pushover-global-disable"
100
- | "notify-pushover-on-success"
126
+ | "notify-pushover-enabled"
127
+ | "notify-pushover-on-completed"
128
+ | "notify-pushover-on-interrupted"
129
+ | "notify-pushover-on-failed"
101
130
  | "notify-pushover-user-key"
102
131
  | "notify-pushover-api-token"
103
132
  | "notify-pushover-priority"
133
+ | "notify-pushover-title"
134
+ | "notify-pushover-text"
135
+ | "PI_NOTIFY_CMD"
104
136
  | "PI_NOTIFY_SOUND_LOW_CMD"
105
137
  | "PI_NOTIFY_SOUND_MID_CMD"
106
138
  | "PI_NOTIFY_SOUND_HIGH_CMD"
107
139
  >;
108
140
 
141
+ /**
142
+ * @brief Describes the shell-spawn callback used by prompt-end command dispatch.
143
+ * @details Narrows the injected spawn surface so deterministic tests can capture detached shell invocations without patching global module state externally. Compile-time only and introduces no runtime cost.
144
+ */
145
+ type PiNotifySpawn = typeof spawn;
146
+
109
147
  /**
110
148
  * @brief Stores the currently configured native HTTPS request function used for Pushover delivery.
111
149
  * @details Defaults to `node:https.request` and can be replaced by deterministic tests so Pushover dispatch remains observable without real network I/O. Access complexity is O(1). Side effect: mutated only through the dedicated test hook.
112
150
  */
113
151
  let piNotifyHttpsRequest: typeof https.request = https.request;
114
152
 
153
+ /**
154
+ * @brief Stores the currently configured shell-spawn function used for notify and sound commands.
155
+ * @details Defaults to `node:child_process.spawn` and can be replaced by deterministic tests so detached shell-command execution remains observable without launching real child processes. Access complexity is O(1). Side effect: mutated only through the dedicated test hook.
156
+ */
157
+ let piNotifySpawn: PiNotifySpawn = spawn;
158
+
115
159
  /**
116
160
  * @brief Normalizes one persisted sound level.
117
161
  * @details Accepts only canonical `none|low|mid|high` values and falls back to `none` for missing or invalid payloads. Runtime is O(1). No external state is mutated.
@@ -139,17 +183,140 @@ export function normalizePiNotifyShortcut(value: unknown): string {
139
183
  }
140
184
 
141
185
  /**
142
- * @brief Normalizes one persisted sound command string.
143
- * @details Accepts any non-empty string so project config can override bundled commands verbatim and falls back to the supplied default when the payload is empty or invalid. Runtime is O(n) in command length. No external state is mutated.
186
+ * @brief Normalizes one persisted shell-command string.
187
+ * @details Accepts any non-empty string so project config can override bundled command templates verbatim and falls back to the supplied default when the payload is empty or invalid. Runtime is O(n) in command length. No external state is mutated.
144
188
  * @param[in] value {unknown} Raw persisted command payload.
145
189
  * @param[in] fallback {string} Canonical fallback command.
146
190
  * @return {string} Canonical non-empty command string.
147
- * @satisfies REQ-133
191
+ * @satisfies REQ-133, REQ-175
148
192
  */
149
193
  export function normalizePiNotifyCommand(value: unknown, fallback: string): string {
150
194
  return typeof value === "string" && value.trim() ? value.trim() : fallback;
151
195
  }
152
196
 
197
+ /**
198
+ * @brief Normalizes one persisted template string.
199
+ * @details Accepts any non-empty string so Pushover title and text templates can be user-configured verbatim, preserves internal and edge control characters, and falls back to the supplied default when the payload is empty or invalid. Runtime is O(n) in template length. No external state is mutated.
200
+ * @param[in] value {unknown} Raw persisted template payload.
201
+ * @param[in] fallback {string} Canonical fallback template.
202
+ * @return {string} Canonical non-empty template string.
203
+ * @satisfies REQ-185, REQ-235
204
+ */
205
+ export function normalizePiNotifyTemplateValue(value: unknown, fallback: string): string {
206
+ return typeof value === "string" && value.trim() ? value : fallback;
207
+ }
208
+
209
+ /**
210
+ * @brief Formats control characters for single-line menu rendering.
211
+ * @details Rewrites supported control bytes into escaped display sequences so configuration rows can render `Pushover text` inside a one-line settings list without executing the embedded control effects. Runtime is O(n) in text length. No external state is mutated.
212
+ * @param[in] value {string} Raw template text.
213
+ * @return {string} Escaped single-line display text.
214
+ * @satisfies REQ-235
215
+ */
216
+ export function formatPiNotifyControlSequenceText(value: string): string {
217
+ let result = "";
218
+ for (const character of value) {
219
+ switch (character) {
220
+ case "\\":
221
+ result += "\\\\";
222
+ break;
223
+ case "\n":
224
+ result += "\\n";
225
+ break;
226
+ case "\r":
227
+ result += "\\r";
228
+ break;
229
+ case "\t":
230
+ result += "\\t";
231
+ break;
232
+ case "\0":
233
+ result += "\\0";
234
+ break;
235
+ case "\b":
236
+ result += "\\b";
237
+ break;
238
+ case "\f":
239
+ result += "\\f";
240
+ break;
241
+ case "\v":
242
+ result += "\\v";
243
+ break;
244
+ default:
245
+ result += character;
246
+ break;
247
+ }
248
+ }
249
+ return result;
250
+ }
251
+
252
+ /**
253
+ * @brief Decodes supported escaped control sequences from one menu input string.
254
+ * @details Converts the documented `\n`, `\r`, `\t`, `\\`, `\0`, `\b`, `\f`, and `\v` escape sequences into their raw byte forms while preserving unknown escapes verbatim so user-entered text is not lossy. Runtime is O(n) in text length. No external state is mutated.
255
+ * @param[in] value {string} Escaped menu input text.
256
+ * @return {string} Decoded raw template text.
257
+ * @satisfies REQ-235
258
+ */
259
+ export function parsePiNotifyControlSequenceText(value: string): string {
260
+ let result = "";
261
+ for (let index = 0; index < value.length; index += 1) {
262
+ const character = value[index];
263
+ if (character !== "\\") {
264
+ result += character;
265
+ continue;
266
+ }
267
+ const nextCharacter = value[index + 1];
268
+ if (nextCharacter === undefined) {
269
+ result += "\\";
270
+ break;
271
+ }
272
+ switch (nextCharacter) {
273
+ case "\\":
274
+ result += "\\";
275
+ break;
276
+ case "n":
277
+ result += "\n";
278
+ break;
279
+ case "r":
280
+ result += "\r";
281
+ break;
282
+ case "t":
283
+ result += "\t";
284
+ break;
285
+ case "0":
286
+ result += "\0";
287
+ break;
288
+ case "b":
289
+ result += "\b";
290
+ break;
291
+ case "f":
292
+ result += "\f";
293
+ break;
294
+ case "v":
295
+ result += "\v";
296
+ break;
297
+ default:
298
+ result += `\\${nextCharacter}`;
299
+ break;
300
+ }
301
+ index += 1;
302
+ }
303
+ return result;
304
+ }
305
+
306
+ /**
307
+ * @brief Tests whether both persisted Pushover credentials are non-empty.
308
+ * @details Performs exact empty-string checks against the normalized user and token fields so UI locking, config persistence, and prompt-end delivery can share one readiness rule. Runtime is O(1). No external state is mutated.
309
+ * @param[in] config {Pick<UseReqConfig, "notify-pushover-user-key" | "notify-pushover-api-token">} Effective Pushover credential subset.
310
+ * @return {boolean} `true` when both Pushover credential fields are non-empty.
311
+ * @satisfies REQ-168, REQ-234
312
+ */
313
+ export function hasPiNotifyPushoverCredentials(
314
+ config: Pick<UseReqConfig, "notify-pushover-user-key" | "notify-pushover-api-token">,
315
+ ): boolean {
316
+ return config["notify-pushover-user-key"] !== ""
317
+ && config["notify-pushover-api-token"] !== "";
318
+ }
319
+
153
320
  /**
154
321
  * @brief Normalizes one persisted Pushover credential string.
155
322
  * @details Accepts any trimmed string so the project config can store raw Pushover user and token values verbatim and falls back to the empty string for missing or invalid payloads. Runtime is O(n) in credential length. No external state is mutated.
@@ -166,42 +333,32 @@ export function normalizePiNotifyPushoverCredential(value: unknown): string {
166
333
  * @details Accepts only canonical `0` and `1` values, treating numeric-string `"1"` as high priority and every other payload as normal priority. Runtime is O(1). No external state is mutated.
167
334
  * @param[in] value {unknown} Raw persisted priority payload.
168
335
  * @return {PiNotifyPushoverPriority} Canonical priority value.
169
- * @satisfies REQ-163
336
+ * @satisfies REQ-172
170
337
  */
171
338
  export function normalizePiNotifyPushoverPriority(value: unknown): PiNotifyPushoverPriority {
172
339
  return value === 1 || value === "1" ? 1 : 0;
173
340
  }
174
341
 
175
342
  /**
176
- * @brief Formats enabled terminal-beep flags for status rendering.
177
- * @details Emits the canonical comma-ordered enabled outcome tokens `end`, `esc`, and `err`, or `none` when all prompt-end beep flags are disabled. Runtime is O(1). No external state is mutated.
178
- * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
179
- * @return {string} Status-bar beep payload.
180
- * @satisfies REQ-135
343
+ * @brief Formats the global command-notify flag for UI value rendering.
344
+ * @details Serializes only the persisted global command-notify enable state so configuration menus and summaries can report `on|off` independently from per-event toggles. Runtime is O(1). No external state is mutated.
345
+ * @param[in] config {Pick<UseReqConfig, "notify-enabled">} Effective command-notify configuration subset.
346
+ * @return {string} `on` when command-notify is globally enabled; otherwise `off`.
347
+ * @satisfies REQ-196
181
348
  */
182
- export function formatPiNotifyBeepStatus(config: PiNotifyConfigFields): string {
183
- const enabledOutcomes: PiNotifyOutcome[] = [];
184
- if (config["notify-beep-on-end"]) {
185
- enabledOutcomes.push("end");
186
- }
187
- if (config["notify-beep-on-esc"]) {
188
- enabledOutcomes.push("esc");
189
- }
190
- if (config["notify-beep-on-error"]) {
191
- enabledOutcomes.push("err");
192
- }
193
- return enabledOutcomes.length > 0 ? enabledOutcomes.join(",") : "none";
349
+ export function formatPiNotifyStatus(config: Pick<UseReqConfig, "notify-enabled">): string {
350
+ return config["notify-enabled"] ? "on" : "off";
194
351
  }
195
352
 
196
353
  /**
197
- * @brief Formats the Pushover enable flag for status rendering.
198
- * @details Serializes only the persisted successful-prompt enable setting so the status bar reports the dedicated Pushover toggle independently from any global-disable override. Runtime is O(1). No external state is mutated.
199
- * @param[in] config {Pick<UseReqConfig, "notify-pushover-on-success">} Effective Pushover configuration subset.
200
- * @return {string} `on` when successful-prompt Pushover delivery is enabled; otherwise `off`.
201
- * @satisfies REQ-171
354
+ * @brief Formats the global Pushover flag for UI value rendering.
355
+ * @details Serializes only the persisted global Pushover enable state so configuration menus and summaries can report `on|off` independently from per-event toggles. Runtime is O(1). No external state is mutated.
356
+ * @param[in] config {Pick<UseReqConfig, "notify-pushover-enabled">} Effective Pushover configuration subset.
357
+ * @return {string} `on` when Pushover is globally enabled; otherwise `off`.
358
+ * @satisfies REQ-163
202
359
  */
203
- export function formatPiNotifyPushoverStatus(config: Pick<UseReqConfig, "notify-pushover-on-success">): string {
204
- return config["notify-pushover-on-success"] ? "on" : "off";
360
+ export function formatPiNotifyPushoverStatus(config: Pick<UseReqConfig, "notify-pushover-enabled">): string {
361
+ return config["notify-pushover-enabled"] ? "on" : "off";
205
362
  }
206
363
 
207
364
  /**
@@ -220,114 +377,28 @@ export function cyclePiNotifySoundLevel(currentLevel: PiNotifySoundLevel): PiNot
220
377
  }
221
378
 
222
379
  /**
223
- * @brief Escapes one string for single-quoted PowerShell embedding.
224
- * @details Doubles embedded apostrophes so the generated Windows toast script preserves literal title and body text. Runtime is O(n) in text length. No external state is mutated.
225
- * @param[in] value {string} Raw literal text.
226
- * @return {string} PowerShell-safe single-quoted literal content.
227
- */
228
- function escapePowerShellLiteral(value: string): string {
229
- return value.replace(/'/g, "''");
230
- }
231
-
232
- /**
233
- * @brief Builds the PowerShell script used for Windows toast notifications.
234
- * @details Reuses the pi notify example contract, escapes title and body literals, and emits a one-shot script that shows a toast notification through the Windows Runtime API. Runtime is O(n) in payload length. No external state is mutated.
235
- * @param[in] title {string} Notification title.
236
- * @param[in] body {string} Notification body.
237
- * @return {string} PowerShell script passed to `powershell.exe`.
238
- */
239
- function windowsToastScript(title: string, body: string): string {
240
- const type = "Windows.UI.Notifications";
241
- const manager = `[${type}.ToastNotificationManager, ${type}, ContentType = WindowsRuntime]`;
242
- const template = `[${type}.ToastTemplateType]::ToastText02`;
243
- const escapedTitle = escapePowerShellLiteral(title);
244
- const escapedBody = escapePowerShellLiteral(body);
245
- return [
246
- `${manager} > $null`,
247
- `$xml = [${type}.ToastNotificationManager]::GetTemplateContent(${template})`,
248
- `$texts = $xml.GetElementsByTagName('text')`,
249
- `$texts[0].AppendChild($xml.CreateTextNode('${escapedTitle}')) > $null`,
250
- `$texts[1].AppendChild($xml.CreateTextNode('${escapedBody}')) > $null`,
251
- `$toast = [${type}.ToastNotification]::new($xml)`,
252
- `[${type}.ToastNotificationManager]::CreateToastNotifier('pi-usereq').Show($toast)`,
253
- ].join("; ");
254
- }
255
-
256
- /**
257
- * @brief Emits one OSC 777 terminal notification.
258
- * @details Writes the Ghostty/iTerm2/WezTerm-compatible OSC 777 escape sequence directly to stdout using the supplied title and body payloads. Runtime is O(n) in payload length. Side effect: writes to stdout.
259
- * @param[in] title {string} Notification title.
260
- * @param[in] body {string} Notification body.
261
- * @return {void} No return value.
262
- * @satisfies REQ-130
380
+ * @brief Tests whether one outcome-specific toggle is enabled.
381
+ * @details Reuses the canonical completed/interrupted/failed routing order shared by notify, sound, and Pushover event toggles. Runtime is O(1). No external state is mutated.
382
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
383
+ * @param[in] completedEnabled {boolean} Enabled state for completed prompt termination.
384
+ * @param[in] interruptedEnabled {boolean} Enabled state for interrupted prompt termination.
385
+ * @param[in] failedEnabled {boolean} Enabled state for failed prompt termination.
386
+ * @return {boolean} `true` when the selected outcome flag is enabled.
263
387
  */
264
- export function notifyOSC777(title: string, body: string): void {
265
- process.stdout.write(`\u001b]777;notify;${title};${body}\u0007`);
266
- }
267
-
268
- /**
269
- * @brief Emits one Kitty OSC 99 terminal notification.
270
- * @details Writes the two-part Kitty OSC 99 sequence that carries the title and body payloads under one notification identifier. Runtime is O(n) in payload length. Side effect: writes to stdout.
271
- * @param[in] title {string} Notification title.
272
- * @param[in] body {string} Notification body.
273
- * @return {void} No return value.
274
- * @satisfies REQ-130
275
- */
276
- export function notifyOSC99(title: string, body: string): void {
277
- process.stdout.write(`\u001b]99;i=1:d=0;${title}\u001b\\`);
278
- process.stdout.write(`\u001b]99;i=1:p=body;${body}\u001b\\`);
279
- }
280
-
281
- /**
282
- * @brief Emits one OSC 9 terminal notification.
283
- * @details Writes the single-part OSC 9 sequence commonly used by terminals that accept message-only desktop notifications. Runtime is O(n) in payload length. Side effect: writes to stdout.
284
- * @param[in] title {string} Notification title.
285
- * @param[in] body {string} Notification body.
286
- * @return {void} No return value.
287
- * @satisfies REQ-130
288
- */
289
- export function notifyOSC9(title: string, body: string): void {
290
- process.stdout.write(`\u001b]9;${title}: ${body}\u0007`);
291
- }
292
-
293
- /**
294
- * @brief Emits one Windows toast notification.
295
- * @details Spawns `powershell.exe` without waiting, delegates payload rendering to `windowsToastScript(...)`, and ignores transport failures so prompt-end handling remains non-blocking. Runtime is dominated by process spawn. Side effects include child-process execution.
296
- * @param[in] title {string} Notification title.
297
- * @param[in] body {string} Notification body.
298
- * @return {void} No return value.
299
- * @satisfies REQ-130
300
- */
301
- export function notifyWindows(title: string, body: string): void {
302
- void execFile(
303
- "powershell.exe",
304
- ["-NoProfile", "-Command", windowsToastScript(title, body)],
305
- () => undefined,
306
- );
307
- }
308
-
309
- /**
310
- * @brief Routes one prompt-end terminal notification through the detected terminal protocol.
311
- * @details Prefers Windows toast delivery inside Windows Terminal, Kitty OSC 99 inside Kitty, OSC 9 inside iTerm-like terminals, and OSC 777 as the fallback path. Runtime is O(n) in payload length plus optional process spawn cost. Side effects include stdout writes or child-process execution.
312
- * @param[in] title {string} Notification title.
313
- * @param[in] body {string} Notification body.
314
- * @return {void} No return value.
315
- * @satisfies REQ-130
316
- */
317
- export function notifyPiTerminal(title: string, body: string): void {
318
- if (process.env.WT_SESSION) {
319
- notifyWindows(title, body);
320
- return;
321
- }
322
- if (process.env.KITTY_WINDOW_ID) {
323
- notifyOSC99(title, body);
324
- return;
325
- }
326
- if (process.env.TERM_PROGRAM === "iTerm.app" || process.env.TERM_PROGRAM === "Apple_Terminal") {
327
- notifyOSC9(title, body);
328
- return;
388
+ function isPiNotifyOutcomeEnabled(
389
+ outcome: PiNotifyOutcome,
390
+ completedEnabled: boolean,
391
+ interruptedEnabled: boolean,
392
+ failedEnabled: boolean,
393
+ ): boolean {
394
+ switch (outcome) {
395
+ case "interrupted":
396
+ return interruptedEnabled;
397
+ case "failed":
398
+ return failedEnabled;
399
+ default:
400
+ return completedEnabled;
329
401
  }
330
- notifyOSC777(title, body);
331
402
  }
332
403
 
333
404
  /**
@@ -351,120 +422,387 @@ function hasAgentEndStopReason(
351
422
 
352
423
  /**
353
424
  * @brief Classifies one agent-end payload into the canonical pi-notify outcome.
354
- * @details Treats assistant `stopReason=error` as `err`, `stopReason=aborted` as `esc`, and every remaining terminal state as successful `end`. Runtime is O(m) in message count. No external state is mutated.
425
+ * @details Treats assistant `stopReason=error` as `failed`, `stopReason=aborted` as `interrupted`, and every remaining terminal state as `completed`. Runtime is O(m) in message count. No external state is mutated.
355
426
  * @param[in] event {Pick<AgentEndEvent, "messages">} Agent-end payload subset.
356
427
  * @return {PiNotifyOutcome} Canonical prompt-end outcome.
357
- * @satisfies REQ-129
358
428
  */
359
429
  export function classifyPiNotifyOutcome(event: Pick<AgentEndEvent, "messages">): PiNotifyOutcome {
360
430
  if (hasAgentEndStopReason(event.messages, "error")) {
361
- return "err";
431
+ return "failed";
362
432
  }
363
433
  if (hasAgentEndStopReason(event.messages, "aborted")) {
364
- return "esc";
434
+ return "interrupted";
365
435
  }
366
- return "end";
436
+ return "completed";
437
+ }
438
+
439
+ /**
440
+ * @brief Formats one prompt-end duration for runtime placeholders.
441
+ * @details Floors the supplied duration to whole seconds, keeps minutes unbounded above 59, and zero-pads seconds to two digits so `%%TIME%%` aligns with status-bar elapsed formatting. Runtime is O(1). No external state is mutated.
442
+ * @param[in] durationMs {number} Prompt-end duration in milliseconds.
443
+ * @return {string} Duration rendered as `M:SS`.
444
+ * @satisfies REQ-187
445
+ */
446
+ function formatPiNotifyDuration(durationMs: number): string {
447
+ const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
448
+ const minutes = Math.floor(totalSeconds / 60);
449
+ const seconds = totalSeconds % 60;
450
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
367
451
  }
368
452
 
369
453
  /**
370
- * @brief Builds the user-visible prompt-end notification title.
371
- * @details Keeps a stable `pi-usereq` title across outcomes so terminal notification stacks remain easy to correlate with this extension. Runtime is O(1). No external state is mutated.
372
- * @return {string} Notification title.
454
+ * @brief Formats one runtime base path for placeholder substitution.
455
+ * @details Emits `~` or `~/...` when the supplied path equals or descends from the current user home directory; otherwise emits the slash-normalized absolute path. Runtime is O(p) in path length. No external state is mutated.
456
+ * @param[in] basePath {string} Runtime base path.
457
+ * @return {string} Placeholder-ready base path string.
458
+ * @satisfies REQ-187
373
459
  */
374
- function buildPiNotifyTitle(): string {
375
- return "pi-usereq";
460
+ function formatPiNotifyBasePath(basePath: string): string {
461
+ const normalizedBasePath = path.resolve(basePath);
462
+ const homePath = path.resolve(os.homedir());
463
+ if (normalizedBasePath === homePath) {
464
+ return "~";
465
+ }
466
+ const relativePath = path.relative(homePath, normalizedBasePath);
467
+ if (relativePath !== "" && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
468
+ return `~/${normalizePathSlashes(relativePath)}`;
469
+ }
470
+ return normalizePathSlashes(normalizedBasePath);
376
471
  }
377
472
 
378
473
  /**
379
- * @brief Builds the user-visible prompt-end notification body.
380
- * @details Maps each canonical outcome to one deterministic English phrase so downstream tests and users can distinguish success, abort, and error notifications. Runtime is O(1). No external state is mutated.
381
- * @param[in] outcome {PiNotifyOutcome} Canonical prompt-end outcome.
382
- * @return {string} Notification body.
474
+ * @brief Formats one prompt-end outcome for `%%RESULT%%` substitution.
475
+ * @details Maps canonical prompt-end outcomes to the persisted human-readable result tokens reused by default notify-command and Pushover templates. Runtime is O(1). No external state is mutated.
476
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
477
+ * @return {string} `successed`, `aborted`, or `failed`.
478
+ * @satisfies REQ-169, REQ-186, REQ-199
383
479
  */
384
- function buildPiNotifyBody(outcome: PiNotifyOutcome): string {
480
+ function formatPiNotifyResult(outcome: PiNotifyOutcome): string {
385
481
  switch (outcome) {
386
- case "err":
387
- return "Prompt ended with error";
388
- case "esc":
389
- return "Prompt aborted by escape";
482
+ case "interrupted":
483
+ return "aborted";
484
+ case "failed":
485
+ return "failed";
390
486
  default:
391
- return "Prompt completed";
487
+ return "successed";
392
488
  }
393
489
  }
394
490
 
395
491
  /**
396
- * @brief Formats one successful prompt duration for Pushover titles.
397
- * @details Floors the supplied completion duration to whole seconds, keeps minutes unbounded above 59, and zero-pads seconds to two digits so Pushover titles align with status-bar elapsed formatting. Runtime is O(1). No external state is mutated.
398
- * @param[in] durationMs {number} Successful prompt duration in milliseconds.
399
- * @return {string} Duration rendered as `M:SS`.
400
- * @satisfies REQ-169
492
+ * @brief Builds the raw runtime placeholder map for one prompt-end request.
493
+ * @details Resolves every placeholder value exactly once so notify-command and Pushover template substitution reuse the same prompt name, base path, elapsed time, argument string, and terminal outcome token. Runtime is O(p) in path length. No external state is mutated.
494
+ * @param[in] request {PiNotifyEventRequest} Prompt-end request metadata.
495
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
496
+ * @return {{ "%%PROMT%%": string; "%%BASE%%": string; "%%TIME%%": string; "%%ARGS%%": string; "%%RESULT%%": string }} Raw placeholder-value map.
401
497
  */
402
- function formatPiNotifyPushoverDuration(durationMs: number): string {
403
- const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
404
- const minutes = Math.floor(totalSeconds / 60);
405
- const seconds = totalSeconds % 60;
406
- return `${minutes}:${String(seconds).padStart(2, "0")}`;
498
+ function buildPiNotifyRuntimeTemplateValues(
499
+ request: PiNotifyEventRequest,
500
+ outcome: PiNotifyOutcome,
501
+ ): Record<string, string> {
502
+ return {
503
+ "%%PROMT%%": request.promptName,
504
+ "%%BASE%%": formatPiNotifyBasePath(request.basePath),
505
+ "%%TIME%%": formatPiNotifyDuration(request.completionTimeMs),
506
+ "%%ARGS%%": request.promptArgs,
507
+ "%%RESULT%%": formatPiNotifyResult(outcome),
508
+ };
407
509
  }
408
510
 
409
511
  /**
410
- * @brief Builds the Pushover notification title for one successful prompt.
411
- * @details Serializes the prompt name, absolute runtime base path, and successful completion duration into the canonical `<prompt> @ <base-path> [<time>]` title required by the repository feature contract. Runtime is O(n) in path length. No external state is mutated.
412
- * @param[in] request {PiNotifyPushoverRequest} Successful prompt metadata.
413
- * @return {string} Pushover title string.
414
- * @satisfies REQ-169
512
+ * @brief Quotes one installation path for shell substitution.
513
+ * @details Emits POSIX single-quoted literals for `sh -lc` execution and CMD double-quoted literals for `cmd.exe /c` execution so `%%INSTALLATION_PATH%%` substitutions preserve whitespace safely. Runtime is O(n) in path length. No external state is mutated.
514
+ * @param[in] installationPath {string} Absolute extension installation path.
515
+ * @return {string} Shell-quoted installation path fragment.
415
516
  */
416
- function buildPiNotifyPushoverTitle(request: PiNotifyPushoverRequest): string {
417
- return `${request.promptName} @ ${request.basePath} [${formatPiNotifyPushoverDuration(request.completionTimeMs)}]`;
517
+ function quotePiNotifyInstallPath(installationPath: string): string {
518
+ if (process.platform === "win32") {
519
+ return `"${installationPath.replace(/"/g, '""')}"`;
520
+ }
521
+ return `'${installationPath.replace(/'/g, `'\\''`)}'`;
418
522
  }
419
523
 
420
524
  /**
421
- * @brief Builds the Pushover message body for one successful prompt.
422
- * @details Reuses the raw prompt argument string substituted into `%%ARGS%%` so the pushed body remains traceable to the executed prompt invocation. Runtime is O(1). No external state is mutated.
423
- * @param[in] request {PiNotifyPushoverRequest} Successful prompt metadata.
424
- * @return {string} Pushover message body.
525
+ * @brief Escapes one placeholder value for double-quoted shell insertion.
526
+ * @details Escapes the characters interpreted specially by POSIX or CMD double-quoted strings so default notify-command templates remain safe when placeholders are embedded inside double quotes. Runtime is O(n) in value length. No external state is mutated.
527
+ * @param[in] value {string} Raw placeholder value.
528
+ * @return {string} Shell-escaped placeholder fragment without surrounding quotes.
529
+ */
530
+ function escapePiNotifyShellTemplateValue(value: string): string {
531
+ if (process.platform === "win32") {
532
+ return value.replace(/%/g, "%%").replace(/"/g, '""');
533
+ }
534
+ return value
535
+ .replace(/\\/g, "\\\\")
536
+ .replace(/\$/g, "\\$")
537
+ .replace(/`/g, "\\`")
538
+ .replace(/"/g, '\\"');
539
+ }
540
+
541
+ /**
542
+ * @brief Substitutes `%%INSTALLATION_PATH%%` inside one shell command.
543
+ * @details Replaces every `%%INSTALLATION_PATH%%` token with a shell-quoted runtime installation path so bundled assets can be addressed safely from external commands. Runtime is O(n) in command length. No external state is mutated.
544
+ * @param[in] command {string} Raw configured shell command.
545
+ * @param[in] installationPath {string} Absolute extension installation path.
546
+ * @return {string} Runtime-ready command string.
425
547
  * @satisfies REQ-169
426
548
  */
427
- function buildPiNotifyPushoverBody(request: PiNotifyPushoverRequest): string {
428
- return request.promptArgs;
549
+ export function substitutePiNotifyInstallPath(command: string, installationPath: string): string {
550
+ return command.replaceAll(
551
+ "%%INSTALLATION_PATH%%",
552
+ quotePiNotifyInstallPath(installationPath),
553
+ );
554
+ }
555
+
556
+ /**
557
+ * @brief Substitutes runtime placeholders inside one shell-command template.
558
+ * @details Applies shell-quoted installation-path substitution plus shell-escaped prompt, base-path, elapsed-time, raw-argument, and outcome-result substitution expected by `PI_NOTIFY_CMD`. Runtime is O(n) in template length. No external state is mutated.
559
+ * @param[in] template {string} Raw shell-command template.
560
+ * @param[in] request {PiNotifyEventRequest} Prompt-end request metadata.
561
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
562
+ * @param[in] installationPath {string} Absolute extension installation path.
563
+ * @return {string} Runtime-ready shell command.
564
+ * @satisfies REQ-169, REQ-199
565
+ */
566
+ function substitutePiNotifyShellTemplate(
567
+ template: string,
568
+ request: PiNotifyEventRequest,
569
+ outcome: PiNotifyOutcome,
570
+ installationPath: string,
571
+ ): string {
572
+ const runtimeValues = buildPiNotifyRuntimeTemplateValues(request, outcome);
573
+ let result = substitutePiNotifyInstallPath(template, installationPath);
574
+ for (const [token, value] of Object.entries(runtimeValues)) {
575
+ result = result.replaceAll(token, escapePiNotifyShellTemplateValue(value));
576
+ }
577
+ return result;
578
+ }
579
+
580
+ /**
581
+ * @brief Substitutes runtime placeholders inside one text template.
582
+ * @details Applies raw prompt name, home-relative base path, elapsed time, raw prompt arguments, and terminal outcome text without shell escaping so Pushover payloads preserve literal text. Runtime is O(n) in template length. No external state is mutated.
583
+ * @param[in] template {string} Raw text template.
584
+ * @param[in] request {PiNotifyEventRequest} Prompt-end request metadata.
585
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
586
+ * @return {string} Placeholder-resolved text string.
587
+ * @satisfies REQ-186, REQ-187, REQ-199
588
+ */
589
+ function substitutePiNotifyTextTemplate(
590
+ template: string,
591
+ request: PiNotifyEventRequest,
592
+ outcome: PiNotifyOutcome,
593
+ ): string {
594
+ let result = template;
595
+ for (const [token, value] of Object.entries(buildPiNotifyRuntimeTemplateValues(request, outcome))) {
596
+ result = result.replaceAll(token, value);
597
+ }
598
+ return result;
599
+ }
600
+
601
+ /**
602
+ * @brief Executes one detached shell command without waiting for completion.
603
+ * @details Uses the platform-default shell contract already employed by sound-command execution and ignores transport failures so prompt-end handling remains non-blocking. Runtime is dominated by process spawn. Side effects include detached child-process execution.
604
+ * @param[in] command {string} Runtime-ready shell command.
605
+ * @return {void} No return value.
606
+ */
607
+ function runPiNotifyShellCommand(command: string): void {
608
+ const shell = process.platform === "win32" ? "cmd.exe" : (process.env.SHELL ?? "sh");
609
+ const shellArgs = process.platform === "win32"
610
+ ? ["/d", "/s", "/c", command]
611
+ : ["-lc", command];
612
+ const child = piNotifySpawn(shell, shellArgs, {
613
+ detached: true,
614
+ stdio: "ignore",
615
+ });
616
+ child.once("error", () => undefined);
617
+ child.unref();
618
+ }
619
+
620
+ /**
621
+ * @brief Determines whether one prompt-end outcome should trigger command-notify.
622
+ * @details Requires a prompt-end request, the global command-notify enable flag, and the corresponding per-event notify toggle. Runtime is O(1). No external state is mutated.
623
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
624
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
625
+ * @param[in] request {PiNotifyEventRequest | undefined} Prompt-end request metadata.
626
+ * @return {boolean} `true` when command-notify prerequisites are satisfied.
627
+ * @satisfies REQ-174, REQ-176
628
+ */
629
+ function shouldRunPiNotifyCommand(
630
+ config: PiNotifyConfigFields,
631
+ outcome: PiNotifyOutcome,
632
+ request: PiNotifyEventRequest | undefined,
633
+ ): boolean {
634
+ return request !== undefined
635
+ && config["notify-enabled"]
636
+ && isPiNotifyOutcomeEnabled(
637
+ outcome,
638
+ config["notify-on-completed"],
639
+ config["notify-on-interrupted"],
640
+ config["notify-on-failed"],
641
+ );
642
+ }
643
+
644
+ /**
645
+ * @brief Executes the configured command-notify shell command.
646
+ * @details Resolves the runtime installation path, substitutes runtime placeholders into `PI_NOTIFY_CMD`, and spawns the resulting command without waiting for completion. Runtime is dominated by process spawn. Side effects include detached child-process execution.
647
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
648
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
649
+ * @param[in] request {PiNotifyEventRequest} Prompt-end request metadata.
650
+ * @return {void} No return value.
651
+ * @satisfies REQ-169, REQ-175, REQ-176, REQ-199
652
+ */
653
+ function runPiNotifyCommand(
654
+ config: PiNotifyConfigFields,
655
+ outcome: PiNotifyOutcome,
656
+ request: PiNotifyEventRequest,
657
+ ): void {
658
+ const installationPath = getInstallationPath();
659
+ const command = substitutePiNotifyShellTemplate(
660
+ config.PI_NOTIFY_CMD,
661
+ request,
662
+ outcome,
663
+ installationPath,
664
+ );
665
+ runPiNotifyShellCommand(command);
666
+ }
667
+
668
+ /**
669
+ * @brief Resolves the configured command for one non-`none` sound level.
670
+ * @details Selects the matching persisted command string from config without performing runtime substitution or shell execution. Runtime is O(1). No external state is mutated.
671
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
672
+ * @param[in] soundLevel {Exclude<PiNotifySoundLevel, "none">} Non-disabled sound level.
673
+ * @return {string} Configured command string for the requested level.
674
+ */
675
+ function resolvePiNotifySoundCommand(
676
+ config: PiNotifyConfigFields,
677
+ soundLevel: Exclude<PiNotifySoundLevel, "none">,
678
+ ): string {
679
+ switch (soundLevel) {
680
+ case "low":
681
+ return config.PI_NOTIFY_SOUND_LOW_CMD;
682
+ case "mid":
683
+ return config.PI_NOTIFY_SOUND_MID_CMD;
684
+ case "high":
685
+ return config.PI_NOTIFY_SOUND_HIGH_CMD;
686
+ }
687
+ }
688
+
689
+ /**
690
+ * @brief Determines whether one prompt-end outcome should trigger sound-command execution.
691
+ * @details Requires a non-`none` sound level and the corresponding per-event sound toggle. Runtime is O(1). No external state is mutated.
692
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
693
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
694
+ * @return {boolean} `true` when sound-command prerequisites are satisfied.
695
+ * @satisfies REQ-178
696
+ */
697
+ function shouldRunPiNotifySound(
698
+ config: PiNotifyConfigFields,
699
+ outcome: PiNotifyOutcome,
700
+ ): boolean {
701
+ return config["notify-sound"] !== "none"
702
+ && isPiNotifyOutcomeEnabled(
703
+ outcome,
704
+ config["notify-sound-on-completed"],
705
+ config["notify-sound-on-interrupted"],
706
+ config["notify-sound-on-failed"],
707
+ );
429
708
  }
430
709
 
431
710
  /**
432
- * @brief Determines whether one successful prompt should trigger Pushover delivery.
433
- * @details Requires a captured prompt request, the successful-prompt enable flag, the Pushover global-disable flag to remain false, and non-empty user plus token credentials. Runtime is O(1). No external state is mutated.
711
+ * @brief Executes the configured sound command on an external shell.
712
+ * @details Resolves the runtime installation path, substitutes `%%INSTALLATION_PATH%%`, and spawns the configured command without waiting for completion. Runtime is dominated by process spawn. Side effects include detached child-process execution.
434
713
  * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
435
- * @param[in] request {PiNotifyPushoverRequest | undefined} Successful prompt metadata.
714
+ * @param[in] soundLevel {Exclude<PiNotifySoundLevel, "none">} Requested non-disabled sound level.
715
+ * @return {void} No return value.
716
+ * @satisfies REQ-132, REQ-133
717
+ */
718
+ export function runPiNotifySoundCommand(
719
+ config: PiNotifyConfigFields,
720
+ soundLevel: Exclude<PiNotifySoundLevel, "none">,
721
+ ): void {
722
+ const rawCommand = resolvePiNotifySoundCommand(config, soundLevel);
723
+ const command = substitutePiNotifyInstallPath(rawCommand, getInstallationPath());
724
+ runPiNotifyShellCommand(command);
725
+ }
726
+
727
+ /**
728
+ * @brief Determines whether one prompt-end outcome should trigger Pushover delivery.
729
+ * @details Requires a prompt-end request, the global Pushover enable flag, the corresponding per-event Pushover toggle, and non-empty user plus token credentials. Runtime is O(1). No external state is mutated.
730
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
731
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
732
+ * @param[in] request {PiNotifyEventRequest | undefined} Prompt-end request metadata.
436
733
  * @return {boolean} `true` when Pushover delivery prerequisites are satisfied.
437
- * @satisfies REQ-166, REQ-168, REQ-172
734
+ * @satisfies REQ-166, REQ-168, REQ-184
438
735
  */
439
736
  function shouldRunPiNotifyPushover(
440
737
  config: PiNotifyConfigFields,
441
- request: PiNotifyPushoverRequest | undefined,
738
+ outcome: PiNotifyOutcome,
739
+ request: PiNotifyEventRequest | undefined,
442
740
  ): boolean {
443
741
  return request !== undefined
444
- && config["notify-pushover-on-success"]
445
- && !config["notify-pushover-global-disable"]
446
- && config["notify-pushover-user-key"] !== ""
447
- && config["notify-pushover-api-token"] !== "";
742
+ && config["notify-pushover-enabled"]
743
+ && isPiNotifyOutcomeEnabled(
744
+ outcome,
745
+ config["notify-pushover-on-completed"],
746
+ config["notify-pushover-on-interrupted"],
747
+ config["notify-pushover-on-failed"],
748
+ )
749
+ && hasPiNotifyPushoverCredentials(config);
750
+ }
751
+
752
+ /**
753
+ * @brief Builds the Pushover notification title for one prompt-end request.
754
+ * @details Resolves the configured `notify-pushover-title` template with raw runtime placeholder substitution so the pushed title remains configurable and deterministic. Runtime is O(n) in template length. No external state is mutated.
755
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
756
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
757
+ * @param[in] request {PiNotifyEventRequest} Prompt-end request metadata.
758
+ * @return {string} Pushover title string.
759
+ * @satisfies REQ-185, REQ-186, REQ-187, REQ-199
760
+ */
761
+ function buildPiNotifyPushoverTitle(
762
+ config: PiNotifyConfigFields,
763
+ outcome: PiNotifyOutcome,
764
+ request: PiNotifyEventRequest,
765
+ ): string {
766
+ return substitutePiNotifyTextTemplate(config["notify-pushover-title"], request, outcome);
767
+ }
768
+
769
+ /**
770
+ * @brief Builds the Pushover message body for one prompt-end request.
771
+ * @details Resolves the configured `notify-pushover-text` template with raw runtime placeholder substitution so the pushed text remains configurable and deterministic. Runtime is O(n) in template length. No external state is mutated.
772
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
773
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
774
+ * @param[in] request {PiNotifyEventRequest} Prompt-end request metadata.
775
+ * @return {string} Pushover message body.
776
+ * @satisfies REQ-185, REQ-186, REQ-187, REQ-199
777
+ */
778
+ function buildPiNotifyPushoverBody(
779
+ config: PiNotifyConfigFields,
780
+ outcome: PiNotifyOutcome,
781
+ request: PiNotifyEventRequest,
782
+ ): string {
783
+ return substitutePiNotifyTextTemplate(config["notify-pushover-text"], request, outcome);
448
784
  }
449
785
 
450
786
  /**
451
- * @brief Builds the Pushover API payload for one successful prompt.
452
- * @details Encodes the configured token, user key, canonical title, priority, and substituted prompt-argument body as `application/x-www-form-urlencoded` fields accepted by the Pushover Message API. Runtime is O(n) in payload size. No external state is mutated.
787
+ * @brief Builds the Pushover API payload for one prompt-end request.
788
+ * @details Encodes the configured token, user key, substituted title, priority, and substituted text as `application/x-www-form-urlencoded` fields accepted by the Pushover Message API. Runtime is O(n) in payload size. No external state is mutated.
453
789
  * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
454
- * @param[in] request {PiNotifyPushoverRequest} Successful prompt metadata.
790
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
791
+ * @param[in] request {PiNotifyEventRequest} Prompt-end request metadata.
455
792
  * @return {URLSearchParams} Encoded Pushover request payload.
456
- * @satisfies REQ-167, REQ-169
793
+ * @satisfies REQ-167, REQ-172, REQ-185, REQ-186, REQ-199
457
794
  */
458
795
  function buildPiNotifyPushoverPayload(
459
796
  config: PiNotifyConfigFields,
460
- request: PiNotifyPushoverRequest,
797
+ outcome: PiNotifyOutcome,
798
+ request: PiNotifyEventRequest,
461
799
  ): URLSearchParams {
462
800
  return new URLSearchParams({
463
801
  token: config["notify-pushover-api-token"],
464
802
  user: config["notify-pushover-user-key"],
465
- title: buildPiNotifyPushoverTitle(request),
803
+ title: buildPiNotifyPushoverTitle(config, outcome, request),
466
804
  priority: String(config["notify-pushover-priority"]),
467
- message: buildPiNotifyPushoverBody(request),
805
+ message: buildPiNotifyPushoverBody(config, outcome, request),
468
806
  });
469
807
  }
470
808
 
@@ -472,16 +810,18 @@ function buildPiNotifyPushoverPayload(
472
810
  * @brief Dispatches one native HTTPS request to the Pushover Message API.
473
811
  * @details Serializes the request body as URL-encoded form data, posts it to `https://api.pushover.net/1/messages.json`, drains the response, and ignores transport failures so prompt-end handling remains non-blocking. Runtime is dominated by outbound I/O. Side effects include one HTTPS request.
474
812
  * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
475
- * @param[in] request {PiNotifyPushoverRequest} Successful prompt metadata.
813
+ * @param[in] outcome {PiNotifyOutcome} Classified prompt-end outcome.
814
+ * @param[in] request {PiNotifyEventRequest} Prompt-end request metadata.
476
815
  * @return {void} No return value.
477
- * @satisfies REQ-167, REQ-169
816
+ * @satisfies REQ-167, REQ-199
478
817
  */
479
818
  function runPiNotifyPushoverRequest(
480
819
  config: PiNotifyConfigFields,
481
- request: PiNotifyPushoverRequest,
820
+ outcome: PiNotifyOutcome,
821
+ request: PiNotifyEventRequest,
482
822
  ): void {
483
823
  const url = new URL("https://api.pushover.net/1/messages.json");
484
- const body = buildPiNotifyPushoverPayload(config, request).toString();
824
+ const body = buildPiNotifyPushoverPayload(config, outcome, request).toString();
485
825
  const httpRequest = piNotifyHttpsRequest(url, {
486
826
  method: "POST",
487
827
  headers: {
@@ -507,111 +847,41 @@ export function setPiNotifyHttpsRequestForTests(requestImpl: typeof https.reques
507
847
  }
508
848
 
509
849
  /**
510
- * @brief Quotes one installation path for shell substitution.
511
- * @details Emits POSIX single-quoted literals for `sh -lc` execution and CMD double-quoted literals for `cmd.exe /c` execution so `%%INSTALLATION_PATH%%` substitutions preserve whitespace safely. Runtime is O(n) in path length. No external state is mutated.
512
- * @param[in] installationPath {string} Absolute extension installation path.
513
- * @return {string} Shell-quoted installation path fragment.
514
- */
515
- function quotePiNotifyInstallPath(installationPath: string): string {
516
- if (process.platform === "win32") {
517
- return `"${installationPath.replace(/"/g, '""')}"`;
518
- }
519
- return `'${installationPath.replace(/'/g, `'\\''`)}'`;
520
- }
521
-
522
- /**
523
- * @brief Substitutes `%%INSTALLATION_PATH%%` inside one sound command.
524
- * @details Replaces every `%%INSTALLATION_PATH%%` token with a shell-quoted runtime installation path so bundled sound assets can be addressed safely from external commands. Runtime is O(n) in command length. No external state is mutated.
525
- * @param[in] command {string} Raw configured sound command.
526
- * @param[in] installationPath {string} Absolute extension installation path.
527
- * @return {string} Runtime-ready command string.
528
- * @satisfies REQ-133
529
- */
530
- export function substitutePiNotifyInstallPath(command: string, installationPath: string): string {
531
- return command.replaceAll("%%INSTALLATION_PATH%%", quotePiNotifyInstallPath(installationPath));
532
- }
533
-
534
- /**
535
- * @brief Resolves the configured command for one non-`none` sound level.
536
- * @details Selects the matching persisted command string from config without performing runtime substitution or shell execution. Runtime is O(1). No external state is mutated.
537
- * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
538
- * @param[in] soundLevel {Exclude<PiNotifySoundLevel, "none">} Non-disabled sound level.
539
- * @return {string} Configured command string for the requested level.
540
- */
541
- function resolvePiNotifySoundCommand(
542
- config: PiNotifyConfigFields,
543
- soundLevel: Exclude<PiNotifySoundLevel, "none">,
544
- ): string {
545
- switch (soundLevel) {
546
- case "low":
547
- return config.PI_NOTIFY_SOUND_LOW_CMD;
548
- case "mid":
549
- return config.PI_NOTIFY_SOUND_MID_CMD;
550
- case "high":
551
- return config.PI_NOTIFY_SOUND_HIGH_CMD;
552
- }
553
- }
554
-
555
- /**
556
- * @brief Executes the configured successful-run sound command on an external shell.
557
- * @details Resolves the runtime installation path, substitutes `%%INSTALLATION_PATH%%`, spawns the configured shell command without waiting, and ignores transport failures so prompt-end handling remains non-blocking. Runtime is dominated by process spawn. Side effects include detached child-process execution.
558
- * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
559
- * @param[in] soundLevel {Exclude<PiNotifySoundLevel, "none">} Requested non-disabled sound level.
850
+ * @brief Replaces the shell-spawn function used for notify and sound commands in deterministic tests.
851
+ * @details Accepts a drop-in `node:child_process.spawn` replacement and restores the native implementation when `undefined` is supplied. Runtime is O(1). Side effect: mutates the module-local shell transport hook.
852
+ * @param[in] spawnImpl {PiNotifySpawn | undefined} Replacement shell-spawn function.
560
853
  * @return {void} No return value.
561
- * @satisfies REQ-132, REQ-133
562
854
  */
563
- export function runPiNotifySoundCommand(
564
- config: PiNotifyConfigFields,
565
- soundLevel: Exclude<PiNotifySoundLevel, "none">,
566
- ): void {
567
- const rawCommand = resolvePiNotifySoundCommand(config, soundLevel);
568
- const command = substitutePiNotifyInstallPath(rawCommand, getInstallationPath());
569
- const shell = process.platform === "win32" ? "cmd.exe" : (process.env.SHELL ?? "sh");
570
- const shellArgs = process.platform === "win32"
571
- ? ["/d", "/s", "/c", command]
572
- : ["-lc", command];
573
- const child = spawn(shell, shellArgs, {
574
- detached: true,
575
- stdio: "ignore",
576
- });
577
- child.once("error", () => undefined);
578
- child.unref();
855
+ export function setPiNotifySpawnForTests(spawnImpl: PiNotifySpawn | undefined): void {
856
+ piNotifySpawn = spawnImpl ?? spawn;
579
857
  }
580
858
 
581
859
  /**
582
- * @brief Dispatches prompt-end beep, sound, and optional Pushover effects for one agent-end payload.
583
- * @details Classifies the terminal outcome, emits the configured terminal notification only for the enabled outcome flag, executes the configured external sound command only for successful completion with a non-disabled sound level, and dispatches the native Pushover request when successful-run Pushover prerequisites are satisfied. Runtime is O(m + c + b) in message count, command length, and Pushover payload size. Side effects include stdout writes, child-process spawning, and outbound HTTPS requests.
860
+ * @brief Dispatches prompt-end notify, sound, and Pushover effects for one agent-end payload.
861
+ * @details Classifies the terminal outcome, executes `PI_NOTIFY_CMD` when command-notify prerequisites are satisfied, executes the configured sound command when sound prerequisites are satisfied, and dispatches the native Pushover request when Pushover prerequisites are satisfied. Runtime is O(m + c + b) in message count, command length, and Pushover payload size. Side effects include child-process spawning and outbound HTTPS requests.
584
862
  * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
585
863
  * @param[in] event {Pick<AgentEndEvent, "messages">} Agent-end payload subset.
586
- * @param[in] pushoverRequest {PiNotifyPushoverRequest | undefined} Optional successful prompt metadata used for Pushover delivery.
864
+ * @param[in] request {PiNotifyEventRequest | undefined} Optional prompt-end request metadata used for command-notify and Pushover substitution.
587
865
  * @return {void} No return value.
588
- * @satisfies REQ-129, REQ-130, REQ-131, REQ-132, REQ-133, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172
866
+ * @satisfies REQ-131, REQ-132, REQ-133, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172, REQ-176, REQ-178, REQ-184, REQ-185, REQ-186, REQ-187, REQ-199
589
867
  */
590
868
  export function runPiNotifyEffects(
591
869
  config: PiNotifyConfigFields,
592
870
  event: Pick<AgentEndEvent, "messages">,
593
- pushoverRequest?: PiNotifyPushoverRequest,
871
+ request?: PiNotifyEventRequest,
594
872
  ): void {
595
873
  const outcome = classifyPiNotifyOutcome(event);
596
- const shouldNotify = (
597
- (outcome === "end" && config["notify-beep-on-end"])
598
- || (outcome === "esc" && config["notify-beep-on-esc"])
599
- || (outcome === "err" && config["notify-beep-on-error"])
600
- );
601
- if (shouldNotify) {
602
- notifyPiTerminal(buildPiNotifyTitle(), buildPiNotifyBody(outcome));
603
- }
604
- if (outcome !== "end") {
605
- return;
874
+ if (shouldRunPiNotifyCommand(config, outcome, request)) {
875
+ runPiNotifyCommand(config, outcome, request as PiNotifyEventRequest);
606
876
  }
607
- if (config["notify-sound"] !== "none") {
877
+ if (shouldRunPiNotifySound(config, outcome)) {
608
878
  runPiNotifySoundCommand(
609
879
  config,
610
880
  config["notify-sound"] as Exclude<PiNotifySoundLevel, "none">,
611
881
  );
612
882
  }
613
- if (!shouldRunPiNotifyPushover(config, pushoverRequest)) {
883
+ if (!shouldRunPiNotifyPushover(config, outcome, request)) {
614
884
  return;
615
885
  }
616
- runPiNotifyPushoverRequest(config, pushoverRequest as PiNotifyPushoverRequest);
886
+ runPiNotifyPushoverRequest(config, outcome, request as PiNotifyEventRequest);
617
887
  }