@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.
- package/CHANGELOG.md +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
package/src/core/ttsr.ts
ADDED
|
@@ -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
|
+
});
|