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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/CHANGELOG.md +1629 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/config-usage.md +113 -0
  5. package/docs/custom-tools.md +541 -0
  6. package/docs/extension-loading.md +1004 -0
  7. package/docs/hooks.md +867 -0
  8. package/docs/rpc.md +1040 -0
  9. package/docs/sdk.md +994 -0
  10. package/docs/session-tree-plan.md +441 -0
  11. package/docs/session.md +240 -0
  12. package/docs/skills.md +290 -0
  13. package/docs/theme.md +670 -0
  14. package/docs/tree.md +197 -0
  15. package/docs/tui.md +341 -0
  16. package/examples/README.md +21 -0
  17. package/examples/custom-tools/README.md +124 -0
  18. package/examples/custom-tools/hello/index.ts +20 -0
  19. package/examples/custom-tools/question/index.ts +84 -0
  20. package/examples/custom-tools/subagent/README.md +172 -0
  21. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  22. package/examples/custom-tools/subagent/agents/scout.md +50 -0
  23. package/examples/custom-tools/subagent/agents/worker.md +24 -0
  24. package/examples/custom-tools/subagent/agents.ts +156 -0
  25. package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
  26. package/examples/custom-tools/subagent/commands/implement.md +10 -0
  27. package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
  28. package/examples/custom-tools/subagent/index.ts +1002 -0
  29. package/examples/custom-tools/todo/index.ts +212 -0
  30. package/examples/hooks/README.md +56 -0
  31. package/examples/hooks/auto-commit-on-exit.ts +49 -0
  32. package/examples/hooks/confirm-destructive.ts +59 -0
  33. package/examples/hooks/custom-compaction.ts +116 -0
  34. package/examples/hooks/dirty-repo-guard.ts +52 -0
  35. package/examples/hooks/file-trigger.ts +41 -0
  36. package/examples/hooks/git-checkpoint.ts +53 -0
  37. package/examples/hooks/handoff.ts +150 -0
  38. package/examples/hooks/permission-gate.ts +34 -0
  39. package/examples/hooks/protected-paths.ts +30 -0
  40. package/examples/hooks/qna.ts +119 -0
  41. package/examples/hooks/snake.ts +343 -0
  42. package/examples/hooks/status-line.ts +40 -0
  43. package/examples/sdk/01-minimal.ts +22 -0
  44. package/examples/sdk/02-custom-model.ts +49 -0
  45. package/examples/sdk/03-custom-prompt.ts +44 -0
  46. package/examples/sdk/04-skills.ts +44 -0
  47. package/examples/sdk/05-tools.ts +90 -0
  48. package/examples/sdk/06-hooks.ts +61 -0
  49. package/examples/sdk/07-context-files.ts +36 -0
  50. package/examples/sdk/08-slash-commands.ts +42 -0
  51. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  52. package/examples/sdk/10-settings.ts +38 -0
  53. package/examples/sdk/11-sessions.ts +48 -0
  54. package/examples/sdk/12-full-control.ts +95 -0
  55. package/examples/sdk/README.md +154 -0
  56. package/package.json +89 -0
  57. package/src/bun-imports.d.ts +16 -0
  58. package/src/capability/context-file.ts +40 -0
  59. package/src/capability/extension.ts +48 -0
  60. package/src/capability/hook.ts +40 -0
  61. package/src/capability/index.ts +616 -0
  62. package/src/capability/instruction.ts +37 -0
  63. package/src/capability/mcp.ts +52 -0
  64. package/src/capability/prompt.ts +35 -0
  65. package/src/capability/rule.ts +56 -0
  66. package/src/capability/settings.ts +35 -0
  67. package/src/capability/skill.ts +49 -0
  68. package/src/capability/slash-command.ts +40 -0
  69. package/src/capability/system-prompt.ts +35 -0
  70. package/src/capability/tool.ts +38 -0
  71. package/src/capability/types.ts +166 -0
  72. package/src/cli/args.ts +259 -0
  73. package/src/cli/file-processor.ts +121 -0
  74. package/src/cli/list-models.ts +104 -0
  75. package/src/cli/plugin-cli.ts +661 -0
  76. package/src/cli/session-picker.ts +41 -0
  77. package/src/cli/update-cli.ts +274 -0
  78. package/src/cli.ts +10 -0
  79. package/src/config.ts +391 -0
  80. package/src/core/agent-session.ts +2178 -0
  81. package/src/core/auth-storage.ts +258 -0
  82. package/src/core/bash-executor.ts +197 -0
  83. package/src/core/compaction/branch-summarization.ts +315 -0
  84. package/src/core/compaction/compaction.ts +664 -0
  85. package/src/core/compaction/index.ts +7 -0
  86. package/src/core/compaction/utils.ts +153 -0
  87. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  88. package/src/core/custom-commands/index.ts +15 -0
  89. package/src/core/custom-commands/loader.ts +226 -0
  90. package/src/core/custom-commands/types.ts +112 -0
  91. package/src/core/custom-tools/index.ts +22 -0
  92. package/src/core/custom-tools/loader.ts +248 -0
  93. package/src/core/custom-tools/types.ts +185 -0
  94. package/src/core/custom-tools/wrapper.ts +29 -0
  95. package/src/core/exec.ts +139 -0
  96. package/src/core/export-html/index.ts +159 -0
  97. package/src/core/export-html/template.css +774 -0
  98. package/src/core/export-html/template.generated.ts +2 -0
  99. package/src/core/export-html/template.html +45 -0
  100. package/src/core/export-html/template.js +1185 -0
  101. package/src/core/export-html/template.macro.ts +24 -0
  102. package/src/core/file-mentions.ts +54 -0
  103. package/src/core/hooks/index.ts +16 -0
  104. package/src/core/hooks/loader.ts +288 -0
  105. package/src/core/hooks/runner.ts +434 -0
  106. package/src/core/hooks/tool-wrapper.ts +98 -0
  107. package/src/core/hooks/types.ts +770 -0
  108. package/src/core/index.ts +53 -0
  109. package/src/core/logger.ts +112 -0
  110. package/src/core/mcp/client.ts +185 -0
  111. package/src/core/mcp/config.ts +248 -0
  112. package/src/core/mcp/index.ts +45 -0
  113. package/src/core/mcp/loader.ts +99 -0
  114. package/src/core/mcp/manager.ts +235 -0
  115. package/src/core/mcp/tool-bridge.ts +156 -0
  116. package/src/core/mcp/transports/http.ts +316 -0
  117. package/src/core/mcp/transports/index.ts +6 -0
  118. package/src/core/mcp/transports/stdio.ts +252 -0
  119. package/src/core/mcp/types.ts +228 -0
  120. package/src/core/messages.ts +211 -0
  121. package/src/core/model-registry.ts +334 -0
  122. package/src/core/model-resolver.ts +494 -0
  123. package/src/core/plugins/doctor.ts +67 -0
  124. package/src/core/plugins/index.ts +38 -0
  125. package/src/core/plugins/installer.ts +189 -0
  126. package/src/core/plugins/loader.ts +339 -0
  127. package/src/core/plugins/manager.ts +672 -0
  128. package/src/core/plugins/parser.ts +105 -0
  129. package/src/core/plugins/paths.ts +37 -0
  130. package/src/core/plugins/types.ts +190 -0
  131. package/src/core/sdk.ts +900 -0
  132. package/src/core/session-manager.ts +1837 -0
  133. package/src/core/settings-manager.ts +860 -0
  134. package/src/core/skills.ts +352 -0
  135. package/src/core/slash-commands.ts +132 -0
  136. package/src/core/system-prompt.ts +442 -0
  137. package/src/core/timings.ts +25 -0
  138. package/src/core/title-generator.ts +110 -0
  139. package/src/core/tools/ask.ts +193 -0
  140. package/src/core/tools/bash-interceptor.ts +120 -0
  141. package/src/core/tools/bash.ts +91 -0
  142. package/src/core/tools/context.ts +32 -0
  143. package/src/core/tools/edit-diff.ts +487 -0
  144. package/src/core/tools/edit.ts +140 -0
  145. package/src/core/tools/exa/company.ts +59 -0
  146. package/src/core/tools/exa/index.ts +63 -0
  147. package/src/core/tools/exa/linkedin.ts +59 -0
  148. package/src/core/tools/exa/mcp-client.ts +368 -0
  149. package/src/core/tools/exa/render.ts +200 -0
  150. package/src/core/tools/exa/researcher.ts +90 -0
  151. package/src/core/tools/exa/search.ts +338 -0
  152. package/src/core/tools/exa/types.ts +167 -0
  153. package/src/core/tools/exa/websets.ts +248 -0
  154. package/src/core/tools/find.ts +244 -0
  155. package/src/core/tools/grep.ts +584 -0
  156. package/src/core/tools/index.ts +283 -0
  157. package/src/core/tools/ls.ts +142 -0
  158. package/src/core/tools/lsp/client.ts +767 -0
  159. package/src/core/tools/lsp/clients/biome-client.ts +207 -0
  160. package/src/core/tools/lsp/clients/index.ts +49 -0
  161. package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
  162. package/src/core/tools/lsp/config.ts +845 -0
  163. package/src/core/tools/lsp/edits.ts +110 -0
  164. package/src/core/tools/lsp/index.ts +1364 -0
  165. package/src/core/tools/lsp/render.ts +560 -0
  166. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  167. package/src/core/tools/lsp/types.ts +495 -0
  168. package/src/core/tools/lsp/utils.ts +526 -0
  169. package/src/core/tools/notebook.ts +182 -0
  170. package/src/core/tools/output.ts +198 -0
  171. package/src/core/tools/path-utils.ts +61 -0
  172. package/src/core/tools/read.ts +507 -0
  173. package/src/core/tools/renderers.ts +820 -0
  174. package/src/core/tools/review.ts +275 -0
  175. package/src/core/tools/rulebook.ts +124 -0
  176. package/src/core/tools/task/agents.ts +158 -0
  177. package/src/core/tools/task/artifacts.ts +114 -0
  178. package/src/core/tools/task/commands.ts +157 -0
  179. package/src/core/tools/task/discovery.ts +217 -0
  180. package/src/core/tools/task/executor.ts +531 -0
  181. package/src/core/tools/task/index.ts +548 -0
  182. package/src/core/tools/task/model-resolver.ts +176 -0
  183. package/src/core/tools/task/parallel.ts +38 -0
  184. package/src/core/tools/task/render.ts +502 -0
  185. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  186. package/src/core/tools/task/types.ts +142 -0
  187. package/src/core/tools/truncate.ts +265 -0
  188. package/src/core/tools/web-fetch.ts +2511 -0
  189. package/src/core/tools/web-search/auth.ts +199 -0
  190. package/src/core/tools/web-search/index.ts +583 -0
  191. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  192. package/src/core/tools/web-search/providers/exa.ts +196 -0
  193. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  194. package/src/core/tools/web-search/render.ts +372 -0
  195. package/src/core/tools/web-search/types.ts +180 -0
  196. package/src/core/tools/write.ts +63 -0
  197. package/src/core/ttsr.ts +211 -0
  198. package/src/core/utils.ts +187 -0
  199. package/src/discovery/agents-md.ts +75 -0
  200. package/src/discovery/builtin.ts +647 -0
  201. package/src/discovery/claude.ts +623 -0
  202. package/src/discovery/cline.ts +104 -0
  203. package/src/discovery/codex.ts +571 -0
  204. package/src/discovery/cursor.ts +266 -0
  205. package/src/discovery/gemini.ts +368 -0
  206. package/src/discovery/github.ts +120 -0
  207. package/src/discovery/helpers.test.ts +127 -0
  208. package/src/discovery/helpers.ts +249 -0
  209. package/src/discovery/index.ts +84 -0
  210. package/src/discovery/mcp-json.ts +127 -0
  211. package/src/discovery/vscode.ts +99 -0
  212. package/src/discovery/windsurf.ts +219 -0
  213. package/src/index.ts +192 -0
  214. package/src/main.ts +507 -0
  215. package/src/migrations.ts +156 -0
  216. package/src/modes/cleanup.ts +23 -0
  217. package/src/modes/index.ts +48 -0
  218. package/src/modes/interactive/components/armin.ts +382 -0
  219. package/src/modes/interactive/components/assistant-message.ts +86 -0
  220. package/src/modes/interactive/components/bash-execution.ts +199 -0
  221. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  222. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  223. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  224. package/src/modes/interactive/components/custom-editor.ts +122 -0
  225. package/src/modes/interactive/components/diff.ts +147 -0
  226. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  227. package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
  228. package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
  229. package/src/modes/interactive/components/extensions/index.ts +9 -0
  230. package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
  231. package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
  232. package/src/modes/interactive/components/extensions/types.ts +191 -0
  233. package/src/modes/interactive/components/hook-editor.ts +117 -0
  234. package/src/modes/interactive/components/hook-input.ts +64 -0
  235. package/src/modes/interactive/components/hook-message.ts +96 -0
  236. package/src/modes/interactive/components/hook-selector.ts +91 -0
  237. package/src/modes/interactive/components/model-selector.ts +560 -0
  238. package/src/modes/interactive/components/oauth-selector.ts +136 -0
  239. package/src/modes/interactive/components/plugin-settings.ts +481 -0
  240. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  241. package/src/modes/interactive/components/session-selector.ts +220 -0
  242. package/src/modes/interactive/components/settings-defs.ts +597 -0
  243. package/src/modes/interactive/components/settings-selector.ts +545 -0
  244. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  245. package/src/modes/interactive/components/status-line/index.ts +4 -0
  246. package/src/modes/interactive/components/status-line/presets.ts +94 -0
  247. package/src/modes/interactive/components/status-line/segments.ts +350 -0
  248. package/src/modes/interactive/components/status-line/separators.ts +55 -0
  249. package/src/modes/interactive/components/status-line/types.ts +81 -0
  250. package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
  251. package/src/modes/interactive/components/status-line.ts +384 -0
  252. package/src/modes/interactive/components/theme-selector.ts +62 -0
  253. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  254. package/src/modes/interactive/components/tool-execution.ts +946 -0
  255. package/src/modes/interactive/components/tree-selector.ts +877 -0
  256. package/src/modes/interactive/components/ttsr-notification.ts +82 -0
  257. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  258. package/src/modes/interactive/components/user-message.ts +18 -0
  259. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  260. package/src/modes/interactive/components/welcome.ts +228 -0
  261. package/src/modes/interactive/interactive-mode.ts +2669 -0
  262. package/src/modes/interactive/theme/dark.json +102 -0
  263. package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
  264. package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
  265. package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
  266. package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
  267. package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
  268. package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
  269. package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
  270. package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
  271. package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
  272. package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
  273. package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
  274. package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
  275. package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
  276. package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
  277. package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
  278. package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
  279. package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
  280. package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
  281. package/src/modes/interactive/theme/defaults/index.ts +67 -0
  282. package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
  283. package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
  284. package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
  285. package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
  286. package/src/modes/interactive/theme/defaults/light-github.json +114 -0
  287. package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
  288. package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
  289. package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
  290. package/src/modes/interactive/theme/defaults/light-one.json +105 -0
  291. package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
  292. package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
  293. package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
  294. package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
  295. package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
  296. package/src/modes/interactive/theme/light.json +99 -0
  297. package/src/modes/interactive/theme/theme-schema.json +424 -0
  298. package/src/modes/interactive/theme/theme.ts +2211 -0
  299. package/src/modes/print-mode.ts +163 -0
  300. package/src/modes/rpc/rpc-client.ts +527 -0
  301. package/src/modes/rpc/rpc-mode.ts +494 -0
  302. package/src/modes/rpc/rpc-types.ts +203 -0
  303. package/src/prompts/architect-plan.md +10 -0
  304. package/src/prompts/branch-summary-preamble.md +3 -0
  305. package/src/prompts/branch-summary.md +28 -0
  306. package/src/prompts/browser.md +71 -0
  307. package/src/prompts/compaction-summary.md +34 -0
  308. package/src/prompts/compaction-turn-prefix.md +16 -0
  309. package/src/prompts/compaction-update-summary.md +41 -0
  310. package/src/prompts/explore.md +82 -0
  311. package/src/prompts/implement-with-critic.md +11 -0
  312. package/src/prompts/implement.md +11 -0
  313. package/src/prompts/init.md +30 -0
  314. package/src/prompts/plan.md +54 -0
  315. package/src/prompts/reviewer.md +81 -0
  316. package/src/prompts/summarization-system.md +3 -0
  317. package/src/prompts/system-prompt.md +27 -0
  318. package/src/prompts/task.md +56 -0
  319. package/src/prompts/title-system.md +8 -0
  320. package/src/prompts/tools/ask.md +24 -0
  321. package/src/prompts/tools/bash.md +23 -0
  322. package/src/prompts/tools/edit.md +9 -0
  323. package/src/prompts/tools/find.md +6 -0
  324. package/src/prompts/tools/grep.md +12 -0
  325. package/src/prompts/tools/lsp.md +14 -0
  326. package/src/prompts/tools/output.md +23 -0
  327. package/src/prompts/tools/read.md +25 -0
  328. package/src/prompts/tools/web-fetch.md +8 -0
  329. package/src/prompts/tools/web-search.md +10 -0
  330. package/src/prompts/tools/write.md +10 -0
  331. package/src/utils/changelog.ts +99 -0
  332. package/src/utils/clipboard.ts +265 -0
  333. package/src/utils/fuzzy.ts +108 -0
  334. package/src/utils/mime.ts +30 -0
  335. package/src/utils/shell-snapshot.ts +218 -0
  336. package/src/utils/shell.ts +364 -0
  337. package/src/utils/tools-manager.ts +265 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Time Traveling Stream Rules (TTSR) Manager
3
+ *
4
+ * Manages rules that get injected mid-stream when their trigger pattern matches
5
+ * the agent's output. When a match occurs, the stream is aborted, the rule is
6
+ * injected as a system reminder, and the request is retried.
7
+ */
8
+
9
+ import type { Rule } from "../capability/rule";
10
+ import { logger } from "./logger";
11
+ import type { TtsrSettings } from "./settings-manager";
12
+
13
+ interface TtsrEntry {
14
+ rule: Rule;
15
+ regex: RegExp;
16
+ }
17
+
18
+ /** Tracks when a rule was last injected (for repeat-after-gap mode) */
19
+ interface InjectionRecord {
20
+ /** Message count when the rule was last injected */
21
+ lastInjectedAt: number;
22
+ }
23
+
24
+ export interface TtsrManager {
25
+ /** Add a TTSR rule to be monitored */
26
+ addRule(rule: Rule): void;
27
+
28
+ /** Check if any uninjected TTSR matches the stream buffer. Returns matching rules. */
29
+ check(streamBuffer: string): Rule[];
30
+
31
+ /** Mark rules as injected (won't trigger again until conditions allow) */
32
+ markInjected(rules: Rule[]): void;
33
+
34
+ /** Get names of all injected rules (for persistence) */
35
+ getInjectedRuleNames(): string[];
36
+
37
+ /** Restore injected state from a list of rule names */
38
+ restoreInjected(ruleNames: string[]): void;
39
+
40
+ /** Reset stream buffer (called on new turn) */
41
+ resetBuffer(): void;
42
+
43
+ /** Get current stream buffer */
44
+ getBuffer(): string;
45
+
46
+ /** Append to stream buffer */
47
+ appendToBuffer(text: string): void;
48
+
49
+ /** Check if any TTSRs are registered */
50
+ hasRules(): boolean;
51
+
52
+ /** Increment message counter (call after each turn) */
53
+ incrementMessageCount(): void;
54
+
55
+ /** Get current message count */
56
+ getMessageCount(): number;
57
+
58
+ /** Get settings */
59
+ getSettings(): Required<TtsrSettings>;
60
+ }
61
+
62
+ const DEFAULT_SETTINGS: Required<TtsrSettings> = {
63
+ enabled: true,
64
+ contextMode: "discard",
65
+ repeatMode: "once",
66
+ repeatGap: 10,
67
+ };
68
+
69
+ export function createTtsrManager(settings?: TtsrSettings): TtsrManager {
70
+ /** Resolved settings with defaults */
71
+ const resolvedSettings: Required<TtsrSettings> = {
72
+ ...DEFAULT_SETTINGS,
73
+ ...settings,
74
+ };
75
+
76
+ /** Map of rule name -> { rule, compiled regex } */
77
+ const rules = new Map<string, TtsrEntry>();
78
+
79
+ /** Map of rule name -> injection record */
80
+ const injectionRecords = new Map<string, InjectionRecord>();
81
+
82
+ /** Current stream buffer for pattern matching */
83
+ let buffer = "";
84
+
85
+ /** Message counter for tracking gap between injections */
86
+ let messageCount = 0;
87
+
88
+ /** Check if a rule can be triggered based on repeat settings */
89
+ function canTrigger(ruleName: string): boolean {
90
+ const record = injectionRecords.get(ruleName);
91
+ if (!record) {
92
+ // Never injected, can trigger
93
+ return true;
94
+ }
95
+
96
+ if (resolvedSettings.repeatMode === "once") {
97
+ // Once mode: never trigger again after first injection
98
+ return false;
99
+ }
100
+
101
+ // After-gap mode: check if enough messages have passed
102
+ const gap = messageCount - record.lastInjectedAt;
103
+ return gap >= resolvedSettings.repeatGap;
104
+ }
105
+
106
+ return {
107
+ addRule(rule: Rule): void {
108
+ // Only add rules that have a TTSR trigger pattern
109
+ if (!rule.ttsrTrigger) {
110
+ return;
111
+ }
112
+
113
+ // Skip if already registered
114
+ if (rules.has(rule.name)) {
115
+ return;
116
+ }
117
+
118
+ // Compile the regex pattern
119
+ try {
120
+ const regex = new RegExp(rule.ttsrTrigger);
121
+ rules.set(rule.name, { rule, regex });
122
+ logger.debug("TTSR rule registered", {
123
+ ruleName: rule.name,
124
+ pattern: rule.ttsrTrigger,
125
+ });
126
+ } catch (err) {
127
+ logger.warn("TTSR rule has invalid regex pattern, skipping", {
128
+ ruleName: rule.name,
129
+ pattern: rule.ttsrTrigger,
130
+ error: err instanceof Error ? err.message : String(err),
131
+ });
132
+ }
133
+ },
134
+
135
+ check(streamBuffer: string): Rule[] {
136
+ const matches: Rule[] = [];
137
+
138
+ for (const [name, entry] of rules) {
139
+ // Skip rules that can't trigger yet
140
+ if (!canTrigger(name)) {
141
+ continue;
142
+ }
143
+
144
+ // Test the buffer against the rule's pattern
145
+ if (entry.regex.test(streamBuffer)) {
146
+ matches.push(entry.rule);
147
+ logger.debug("TTSR pattern matched", {
148
+ ruleName: name,
149
+ pattern: entry.rule.ttsrTrigger,
150
+ });
151
+ }
152
+ }
153
+
154
+ return matches;
155
+ },
156
+
157
+ markInjected(rulesToMark: Rule[]): void {
158
+ for (const rule of rulesToMark) {
159
+ injectionRecords.set(rule.name, { lastInjectedAt: messageCount });
160
+ logger.debug("TTSR rule marked as injected", {
161
+ ruleName: rule.name,
162
+ messageCount,
163
+ repeatMode: resolvedSettings.repeatMode,
164
+ });
165
+ }
166
+ },
167
+
168
+ getInjectedRuleNames(): string[] {
169
+ return Array.from(injectionRecords.keys());
170
+ },
171
+
172
+ restoreInjected(ruleNames: string[]): void {
173
+ // When restoring, we don't know the original message count, so use 0
174
+ // This means in "after-gap" mode, rules can trigger again after the gap
175
+ for (const name of ruleNames) {
176
+ injectionRecords.set(name, { lastInjectedAt: 0 });
177
+ }
178
+ if (ruleNames.length > 0) {
179
+ logger.debug("TTSR injected state restored", { ruleNames });
180
+ }
181
+ },
182
+
183
+ resetBuffer(): void {
184
+ buffer = "";
185
+ },
186
+
187
+ getBuffer(): string {
188
+ return buffer;
189
+ },
190
+
191
+ appendToBuffer(text: string): void {
192
+ buffer += text;
193
+ },
194
+
195
+ hasRules(): boolean {
196
+ return rules.size > 0;
197
+ },
198
+
199
+ incrementMessageCount(): void {
200
+ messageCount++;
201
+ },
202
+
203
+ getMessageCount(): number {
204
+ return messageCount;
205
+ },
206
+
207
+ getSettings(): Required<TtsrSettings> {
208
+ return resolvedSettings;
209
+ },
210
+ };
211
+ }
@@ -0,0 +1,187 @@
1
+ // Utility constant for representing aborted operations
2
+ const kAbortError = new Error("Operation aborted");
3
+
4
+ /**
5
+ * Runs a promise-returning function (`pr`). If the given AbortSignal is aborted before or during
6
+ * execution, the promise is rejected with a standard error.
7
+ *
8
+ * @param signal - Optional AbortSignal to cancel the operation
9
+ * @param pr - Function returning a promise to run
10
+ * @returns Promise resolving as `pr` would, or rejecting on abort
11
+ */
12
+ export function untilAborted<T>(signal: AbortSignal | undefined | null, pr: () => Promise<T>): Promise<T> {
13
+ if (!signal) {
14
+ return pr();
15
+ }
16
+
17
+ if (signal.aborted) {
18
+ return Promise.reject(kAbortError);
19
+ }
20
+
21
+ return new Promise((resolve, reject) => {
22
+ const listener = () => reject(kAbortError);
23
+ signal.addEventListener("abort", listener, { once: true });
24
+
25
+ signal.throwIfAborted();
26
+
27
+ pr()
28
+ .then(resolve, reject)
29
+ .finally(() => {
30
+ signal.removeEventListener("abort", listener);
31
+ });
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Memoizes a function with no arguments, calling it once and caching the result.
37
+ *
38
+ * @param fn - Function to be called once
39
+ * @returns A function that returns the cached result of `fn`
40
+ */
41
+ export function once<T>(fn: () => T): () => T {
42
+ let store = undefined as { value: T } | undefined;
43
+ return () => {
44
+ if (store) {
45
+ return store.value;
46
+ }
47
+ const value = fn();
48
+ store = { value };
49
+ return value;
50
+ };
51
+ }
52
+
53
+ // ScopeSignal is a cancellation/helper utility similar to AbortController but
54
+ // allows composition of an existing AbortSignal and/or a timeout. It exposes a
55
+ // simple API for cancellation observation (finally, catch).
56
+ interface ScopeSignalOptions {
57
+ signal?: AbortSignal;
58
+ timeout?: number;
59
+ }
60
+
61
+ const kTimeoutReason = new Error("Timeout");
62
+ const kDisposedReason = new Error("Disposed");
63
+
64
+ /**
65
+ * Type of signal exit (None = disposed, TimedOut = timed out, Aborted = underlying signal aborted)
66
+ */
67
+ enum ExitReason {
68
+ None = 0,
69
+ TimedOut = 1,
70
+ Aborted = 2,
71
+ }
72
+
73
+ /**
74
+ * ScopeSignal: composable cancellation for async work–observes an external AbortSignal and/or a timeout.
75
+ *
76
+ * Use .finally(fn) to register a one-time callback invoked on *any* exit (abort, timeout, or manual dispose).
77
+ * Use .catch(fn) to register a one-time callback invoked only on abort/timeout.
78
+ *
79
+ * Disposing ScopeSignal disables further callbacks.
80
+ */
81
+ export class ScopeSignal implements Disposable {
82
+ #signal: AbortSignal | undefined;
83
+ #timer: NodeJS.Timeout | undefined;
84
+ #exit = undefined as ExitReason | undefined;
85
+ #onAbort: (() => void) | undefined;
86
+ #callbacks?: (() => void)[];
87
+ #reason: unknown | undefined;
88
+
89
+ /**
90
+ * Provides abort/timeout reason (Error or user-defined).
91
+ */
92
+ get reason(): unknown | undefined {
93
+ return this.#reason;
94
+ }
95
+
96
+ /**
97
+ * True if exited due to external AbortSignal or timeout.
98
+ */
99
+ get aborted(): boolean {
100
+ return this.#exit !== undefined && this.#exit > ExitReason.None;
101
+ }
102
+
103
+ /**
104
+ * True if this ScopeSignal timed out (not external abort).
105
+ */
106
+ timedOut(): boolean {
107
+ return this.#exit === ExitReason.TimedOut;
108
+ }
109
+
110
+ /**
111
+ * Create a new ScopeSignal, optionally observing an AbortSignal and/or auto-aborting after a timeout (ms).
112
+ */
113
+ constructor(options?: ScopeSignalOptions) {
114
+ const { signal, timeout } = options ?? {};
115
+
116
+ if (signal?.aborted) {
117
+ this.#abort(ExitReason.Aborted, signal.reason); // Immediately abort if already-aborted
118
+ return;
119
+ }
120
+ if (timeout && timeout <= 0) {
121
+ this.#abort(ExitReason.TimedOut, kTimeoutReason);
122
+ return;
123
+ }
124
+
125
+ // Observe external signal if provided
126
+ if (signal) {
127
+ const onAbort = () => {
128
+ this.#abort(ExitReason.Aborted, signal.reason);
129
+ };
130
+ this.#signal = signal;
131
+ this.#onAbort = onAbort;
132
+ this.#signal.addEventListener("abort", onAbort, { once: true });
133
+ }
134
+
135
+ // Set up timeout if provided
136
+ if (timeout) {
137
+ this.#timer = setTimeout(() => {
138
+ this.#abort(ExitReason.TimedOut, kTimeoutReason);
139
+ }, timeout);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Register a one-time callback invoked on any exit (abort, timeout, or manual dispose).
145
+ * Runs immediately if already exited.
146
+ */
147
+ finally(onfinally: () => void): void {
148
+ if (this.#exit !== undefined) {
149
+ onfinally();
150
+ return;
151
+ }
152
+ this.#callbacks ??= [];
153
+ this.#callbacks.push(onfinally);
154
+ }
155
+
156
+ /**
157
+ * Register a one-time callback invoked only if exited due to abort/timeout (not normal disposal).
158
+ */
159
+ catch(oncatch: (reason: unknown) => void): void {
160
+ this.finally(() => {
161
+ if (this.aborted) {
162
+ oncatch(this.reason);
163
+ }
164
+ });
165
+ }
166
+
167
+ /** Internal: cause exit; only first call takes effect. */
168
+ #abort(exit: ExitReason, reason?: unknown): void {
169
+ if (this.#exit !== undefined) return;
170
+ this.#reason = reason;
171
+ clearTimeout(this.#timer);
172
+ this.#signal?.removeEventListener("abort", this.#onAbort!);
173
+
174
+ this.#exit = exit;
175
+
176
+ const callbacks = this.#callbacks;
177
+ this.#callbacks = undefined;
178
+ callbacks?.forEach((fn) => void fn());
179
+ }
180
+
181
+ /**
182
+ * Dispose: marks as normally exited (not abort/timeout); disables further callback registration.
183
+ */
184
+ [Symbol.dispose](): void {
185
+ this.#abort(ExitReason.None, kDisposedReason);
186
+ }
187
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * AGENTS.md Provider
3
+ *
4
+ * Discovers standalone AGENTS.md files by walking up from cwd.
5
+ * This handles AGENTS.md files that live in project root (not in config directories
6
+ * like .codex/ or .gemini/, which are handled by their respective providers).
7
+ */
8
+
9
+ import { dirname, join, sep } from "node:path";
10
+ import { type ContextFile, contextFileCapability } from "../capability/context-file";
11
+ import { registerProvider } from "../capability/index";
12
+ import type { LoadContext, LoadResult } from "../capability/types";
13
+ import { calculateDepth, createSourceMeta } from "./helpers";
14
+
15
+ const PROVIDER_ID = "agents-md";
16
+ const DISPLAY_NAME = "AGENTS.md";
17
+ const MAX_DEPTH = 20; // Prevent walking up excessively far from cwd
18
+
19
+ /**
20
+ * Load standalone AGENTS.md files.
21
+ */
22
+ function loadAgentsMd(ctx: LoadContext): LoadResult<ContextFile> {
23
+ const items: ContextFile[] = [];
24
+ const warnings: string[] = [];
25
+
26
+ // Walk up from cwd looking for AGENTS.md files
27
+ let current = ctx.cwd;
28
+ let depth = 0;
29
+
30
+ while (depth < MAX_DEPTH) {
31
+ const candidate = join(current, "AGENTS.md");
32
+
33
+ if (ctx.fs.isFile(candidate)) {
34
+ // Skip if it's inside a config directory (handled by other providers)
35
+ const parent = dirname(candidate);
36
+ const baseName = parent.split(sep).pop() ?? "";
37
+
38
+ // Skip if inside .codex, .gemini, or other config dirs
39
+ if (!baseName.startsWith(".")) {
40
+ const content = ctx.fs.readFile(candidate);
41
+
42
+ if (content === null) {
43
+ warnings.push(`Failed to read: ${candidate}`);
44
+ } else {
45
+ const fileDir = dirname(candidate);
46
+ const calculatedDepth = calculateDepth(ctx.cwd, fileDir, sep);
47
+
48
+ items.push({
49
+ path: candidate,
50
+ content,
51
+ level: "project",
52
+ depth: calculatedDepth,
53
+ _source: createSourceMeta(PROVIDER_ID, candidate, "project"),
54
+ });
55
+ }
56
+ }
57
+ }
58
+
59
+ // Move to parent directory
60
+ const parent = dirname(current);
61
+ if (parent === current) break; // Reached filesystem root
62
+ current = parent;
63
+ depth++;
64
+ }
65
+
66
+ return { items, warnings };
67
+ }
68
+
69
+ registerProvider(contextFileCapability.id, {
70
+ id: PROVIDER_ID,
71
+ displayName: DISPLAY_NAME,
72
+ description: "Standalone AGENTS.md files (Codex/Gemini style)",
73
+ priority: 10,
74
+ load: loadAgentsMd,
75
+ });