@sayknow-cli/coding-agent 0.3.7 → 0.3.8

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 (248) hide show
  1. package/dist/types/append-only-mode.d.ts +31 -0
  2. package/dist/types/capability/rule.d.ts +8 -0
  3. package/dist/types/cli/completion-cli.d.ts +41 -0
  4. package/dist/types/cli/config-cli.d.ts +1 -0
  5. package/dist/types/cli/fixture-report.d.ts +20 -0
  6. package/dist/types/cli/fixture-report.test.d.ts +1 -0
  7. package/dist/types/cli/nofile-limit.d.ts +11 -0
  8. package/dist/types/cli/notify-cli.d.ts +7 -0
  9. package/dist/types/cli/update-cli.d.ts +22 -0
  10. package/dist/types/cli.d.ts +124 -1
  11. package/dist/types/commands/completion.d.ts +29 -0
  12. package/dist/types/commands/config.d.ts +3 -0
  13. package/dist/types/commands/deep-interview.d.ts +3 -0
  14. package/dist/types/config/file-lock.d.ts +7 -6
  15. package/dist/types/config/keybindings.d.ts +2 -2
  16. package/dist/types/config/model-profile-activation.d.ts +6 -0
  17. package/dist/types/config/model-profiles.d.ts +1 -0
  18. package/dist/types/config/settings-schema.d.ts +105 -1
  19. package/dist/types/config/settings.d.ts +8 -0
  20. package/dist/types/discovery/index.d.ts +1 -0
  21. package/dist/types/export/html/template.generated.d.ts +1 -1
  22. package/dist/types/export/ttsr.d.ts +14 -3
  23. package/dist/types/extensibility/extensions/runner.d.ts +2 -1
  24. package/dist/types/extensibility/extensions/types.d.ts +8 -0
  25. package/dist/types/goals/runtime.d.ts +1 -0
  26. package/dist/types/hooks/native-skill-hook.d.ts +12 -0
  27. package/dist/types/main.d.ts +4 -1
  28. package/dist/types/modes/components/assistant-message.d.ts +5 -1
  29. package/dist/types/modes/components/model-selector.d.ts +4 -0
  30. package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
  31. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  32. package/dist/types/modes/components/status-line.d.ts +9 -0
  33. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  34. package/dist/types/modes/controllers/event-controller.d.ts +5 -0
  35. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  36. package/dist/types/modes/interactive-mode.d.ts +2 -0
  37. package/dist/types/modes/print-mode.d.ts +13 -1
  38. package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
  39. package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
  40. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
  41. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
  42. package/dist/types/modes/types.d.ts +1 -0
  43. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  44. package/dist/types/notifications/config.d.ts +14 -1
  45. package/dist/types/notifications/daemon-paths.d.ts +1 -0
  46. package/dist/types/notifications/index.d.ts +6 -3
  47. package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
  48. package/dist/types/notifications/telegram-daemon.d.ts +23 -3
  49. package/dist/types/notifications/telegram-reference.d.ts +0 -5
  50. package/dist/types/notifications/threaded-render.d.ts +6 -0
  51. package/dist/types/runtime-api-key.d.ts +7 -0
  52. package/dist/types/sdk.d.ts +3 -0
  53. package/dist/types/session/agent-session.d.ts +61 -6
  54. package/dist/types/session/agent-storage.d.ts +1 -0
  55. package/dist/types/session/file-mention-pruning.d.ts +8 -0
  56. package/dist/types/session/messages.d.ts +4 -0
  57. package/dist/types/session/session-manager.d.ts +17 -1
  58. package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
  59. package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
  60. package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
  61. package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
  62. package/dist/types/skc-runtime/state-writer.d.ts +3 -0
  63. package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
  64. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
  65. package/dist/types/skill-state/active-state.d.ts +18 -1
  66. package/dist/types/system-prompt.d.ts +18 -2
  67. package/dist/types/task/executor.d.ts +3 -0
  68. package/dist/types/task/fork-context-budget.d.ts +7 -0
  69. package/dist/types/task/token-log.d.ts +30 -0
  70. package/dist/types/task/token-log.test.d.ts +1 -0
  71. package/dist/types/tools/bisect.d.ts +81 -0
  72. package/dist/types/tools/computer.d.ts +2 -2
  73. package/dist/types/tools/index.d.ts +1 -0
  74. package/dist/types/tools/search.d.ts +2 -2
  75. package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
  76. package/dist/types/utils/file-mentions.d.ts +14 -0
  77. package/dist/types/utils/git.d.ts +32 -0
  78. package/dist/types/web/scrapers/youtube.d.ts +8 -0
  79. package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
  80. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
  81. package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
  82. package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
  83. package/package.json +8 -8
  84. package/src/append-only-mode.ts +58 -0
  85. package/src/autoresearch/prompt.md +1 -1
  86. package/src/autoresearch/tools/log-experiment.ts +1 -1
  87. package/src/capability/rule.ts +9 -0
  88. package/src/cli/completion-cli.ts +234 -0
  89. package/src/cli/config-cli.ts +61 -9
  90. package/src/cli/fast-help.ts +1 -0
  91. package/src/cli/fixture-report.test.ts +230 -0
  92. package/src/cli/fixture-report.ts +171 -0
  93. package/src/cli/nofile-limit.ts +48 -0
  94. package/src/cli/notify-cli.ts +92 -8
  95. package/src/cli/telegram-cli.ts +1 -1
  96. package/src/cli/update-cli.ts +85 -9
  97. package/src/cli.ts +29 -1
  98. package/src/commands/completion.ts +84 -0
  99. package/src/commands/config.ts +2 -0
  100. package/src/commands/contribution-prep.ts +1 -1
  101. package/src/commands/deep-interview.ts +2 -1
  102. package/src/config/file-lock.ts +73 -34
  103. package/src/config/keybindings.ts +2 -2
  104. package/src/config/model-profile-activation.ts +65 -0
  105. package/src/config/model-profiles.ts +31 -2
  106. package/src/config/settings-schema.ts +68 -1
  107. package/src/config/settings.ts +22 -0
  108. package/src/defaults/skc/skills/team/SKILL.md +17 -14
  109. package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
  110. package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
  111. package/src/defaults/skc-defaults.ts +9 -0
  112. package/src/discovery/helpers.ts +10 -0
  113. package/src/discovery/index.ts +1 -0
  114. package/src/export/html/template.generated.ts +1 -1
  115. package/src/export/html/template.js +60 -2
  116. package/src/export/ttsr.ts +96 -18
  117. package/src/extensibility/extensions/runner.ts +183 -219
  118. package/src/extensibility/extensions/types.ts +9 -0
  119. package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
  120. package/src/goals/runtime.ts +10 -2
  121. package/src/hindsight/backend.ts +1 -3
  122. package/src/hooks/native-skill-hook.ts +124 -4
  123. package/src/internal-urls/docs-index.generated.ts +14 -5
  124. package/src/main.ts +95 -43
  125. package/src/memories/index.ts +12 -1
  126. package/src/modes/components/assistant-message.ts +241 -82
  127. package/src/modes/components/custom-editor.ts +1 -0
  128. package/src/modes/components/model-selector.ts +54 -11
  129. package/src/modes/components/queued-message-selector.ts +92 -0
  130. package/src/modes/components/settings-selector.ts +4 -0
  131. package/src/modes/components/status-line.ts +167 -54
  132. package/src/modes/components/tool-execution.ts +44 -19
  133. package/src/modes/components/welcome.ts +181 -44
  134. package/src/modes/controllers/command-controller.ts +40 -4
  135. package/src/modes/controllers/event-controller.ts +66 -47
  136. package/src/modes/controllers/extension-ui-controller.ts +4 -10
  137. package/src/modes/controllers/input-controller.ts +180 -18
  138. package/src/modes/controllers/selector-controller.ts +127 -24
  139. package/src/modes/interactive-mode.ts +51 -16
  140. package/src/modes/print-mode.ts +43 -4
  141. package/src/modes/prompt-action-autocomplete.ts +21 -20
  142. package/src/modes/rpc/rpc-mode.ts +29 -3
  143. package/src/modes/rpc/rpc-types.ts +9 -0
  144. package/src/modes/shared/agent-wire/command-validation.ts +4 -0
  145. package/src/modes/shared/agent-wire/event-contract.ts +22 -0
  146. package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
  147. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  148. package/src/modes/types.ts +1 -0
  149. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  150. package/src/modes/utils/ui-helpers.ts +20 -7
  151. package/src/notifications/config.ts +25 -5
  152. package/src/notifications/daemon-paths.ts +2 -0
  153. package/src/notifications/index.ts +117 -14
  154. package/src/notifications/lifecycle-commands.ts +37 -7
  155. package/src/notifications/lifecycle-orchestrator.ts +19 -6
  156. package/src/notifications/telegram-daemon-control.ts +5 -3
  157. package/src/notifications/telegram-daemon.ts +302 -51
  158. package/src/notifications/telegram-reference.ts +0 -16
  159. package/src/notifications/threaded-render.ts +17 -1
  160. package/src/prompts/agents/architect.md +19 -10
  161. package/src/prompts/agents/critic.md +16 -18
  162. package/src/prompts/agents/executor.md +14 -11
  163. package/src/prompts/agents/planner.md +18 -13
  164. package/src/prompts/ci-green-request.md +3 -8
  165. package/src/prompts/goals/goal-continuation.md +0 -5
  166. package/src/prompts/goals/goal-mode-active.md +0 -5
  167. package/src/prompts/system/agent-creation-architect.md +2 -2
  168. package/src/prompts/system/plan-mode-active.md +1 -1
  169. package/src/prompts/system/plan-mode-subagent.md +3 -3
  170. package/src/prompts/system/project-prompt.md +0 -13
  171. package/src/prompts/system/subagent-system-prompt.md +3 -2
  172. package/src/prompts/system/system-prompt.md +29 -17
  173. package/src/prompts/system/volatile-project-context.md +10 -0
  174. package/src/prompts/system/web-search.md +4 -7
  175. package/src/prompts/tools/apply-patch.md +6 -0
  176. package/src/prompts/tools/ast-edit.md +1 -0
  177. package/src/prompts/tools/bash.md +1 -1
  178. package/src/prompts/tools/bisect.md +22 -0
  179. package/src/prompts/tools/browser.md +1 -1
  180. package/src/prompts/tools/computer.md +2 -2
  181. package/src/prompts/tools/cron.md +0 -2
  182. package/src/prompts/tools/eval.md +2 -2
  183. package/src/prompts/tools/find.md +1 -1
  184. package/src/prompts/tools/github.md +6 -6
  185. package/src/prompts/tools/goal.md +1 -3
  186. package/src/prompts/tools/image-gen.md +12 -5
  187. package/src/prompts/tools/irc.md +5 -4
  188. package/src/prompts/tools/job.md +2 -2
  189. package/src/prompts/tools/lsp.md +1 -1
  190. package/src/prompts/tools/monitor.md +2 -2
  191. package/src/prompts/tools/patch.md +2 -2
  192. package/src/prompts/tools/replace.md +2 -21
  193. package/src/prompts/tools/rewind.md +1 -1
  194. package/src/prompts/tools/search-tool-bm25.md +1 -11
  195. package/src/prompts/tools/search.md +1 -1
  196. package/src/prompts/tools/skill.md +3 -2
  197. package/src/prompts/tools/ssh.md +2 -1
  198. package/src/prompts/tools/subagent.md +2 -2
  199. package/src/prompts/tools/task.md +4 -5
  200. package/src/prompts/tools/todo-write.md +1 -1
  201. package/src/prompts/tools/vim.md +1 -0
  202. package/src/prompts/tools/web-search.md +2 -0
  203. package/src/prompts/tools/write.md +8 -2
  204. package/src/runtime-api-key.ts +16 -0
  205. package/src/sdk.ts +52 -28
  206. package/src/session/agent-session.ts +1011 -141
  207. package/src/session/agent-storage.ts +4 -0
  208. package/src/session/file-mention-pruning.ts +64 -0
  209. package/src/session/messages.ts +4 -0
  210. package/src/session/session-manager.ts +103 -4
  211. package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
  212. package/src/skc-runtime/deep-interview-recorder.ts +62 -8
  213. package/src/skc-runtime/deep-interview-state.ts +44 -2
  214. package/src/skc-runtime/session-state-sidecar.ts +263 -20
  215. package/src/skc-runtime/state-runtime.ts +12 -2
  216. package/src/skc-runtime/state-writer.ts +15 -0
  217. package/src/skc-runtime/ultragoal-guard.ts +55 -66
  218. package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
  219. package/src/skc-runtime/ultragoal-runtime.ts +1663 -159
  220. package/src/skc-runtime/workflow-manifest.generated.json +113 -2
  221. package/src/skc-runtime/workflow-manifest.ts +74 -1
  222. package/src/skill-state/active-state.ts +141 -5
  223. package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
  224. package/src/slash-commands/builtin-registry.ts +218 -71
  225. package/src/system-prompt.ts +57 -16
  226. package/src/task/executor.ts +132 -24
  227. package/src/task/fork-context-advisory.ts +2 -7
  228. package/src/task/fork-context-budget.ts +9 -0
  229. package/src/task/index.ts +13 -6
  230. package/src/task/output-manager.ts +20 -8
  231. package/src/task/receipt.ts +26 -7
  232. package/src/task/token-log.test.ts +188 -0
  233. package/src/task/token-log.ts +179 -0
  234. package/src/tools/bisect.ts +367 -0
  235. package/src/tools/computer.ts +246 -43
  236. package/src/tools/index.ts +3 -0
  237. package/src/tools/output-meta.ts +116 -4
  238. package/src/tools/search.ts +8 -2
  239. package/src/tools/skill.ts +15 -0
  240. package/src/tools/telegram-send.ts +6 -0
  241. package/src/tools/ultragoal-ask-guard.ts +39 -5
  242. package/src/utils/file-mentions.ts +39 -7
  243. package/src/utils/git.ts +73 -0
  244. package/src/web/scrapers/youtube.ts +24 -12
  245. package/src/workflow/workflow-intent-diff.ts +29 -6
  246. package/src/workflow/workflow-intent-report-consumer.ts +221 -0
  247. package/src/workflow/workflow-intent-report-schema.ts +129 -0
  248. package/src/workflow/workflow-intent-report.ts +135 -0
@@ -2008,12 +2008,50 @@
2008
2008
  // INITIALIZATION
2009
2009
  // ============================================================
2010
2010
 
2011
- // Escape HTML tags in text (but not code blocks)
2011
+ // Escape raw HTML tags in markdown text while keeping markdown/code rendering.
2012
2012
  function escapeHtmlTags(text) {
2013
2013
  return text.replace(/<(?=[a-zA-Z\/])/g, '&lt;');
2014
2014
  }
2015
2015
 
2016
- // Configure marked with syntax highlighting and HTML escaping for text
2016
+ function sanitizeMarkdownUrl(href) {
2017
+ const value = String(href || '').trim();
2018
+ if (!value) return '';
2019
+
2020
+ // Collapse whitespace/control characters before scheme checks so
2021
+ // variants like "java\nscript:" cannot bypass the allowlist.
2022
+ const compact = value.replace(/[\u0000-\u001F\u007F\s]+/g, '');
2023
+ // Browser URL parsing treats backslashes in leading path separators as
2024
+ // network-path separators; Marked may pass them through or encode them.
2025
+ if (compact.includes('\\') || /%5c/i.test(compact)) return null;
2026
+
2027
+ const colonIndex = compact.indexOf(':');
2028
+ const firstPathIndex = compact.search(/[/?#]/);
2029
+ if (colonIndex !== -1 && (firstPathIndex === -1 || colonIndex < firstPathIndex)) {
2030
+ const scheme = compact.slice(0, colonIndex).toLowerCase();
2031
+ if (scheme === 'http' || scheme === 'https' || scheme === 'mailto' || scheme === 'tel') {
2032
+ return value;
2033
+ }
2034
+ return null;
2035
+ }
2036
+
2037
+ if (compact.startsWith('//')) return null;
2038
+ return value;
2039
+ }
2040
+
2041
+ function renderAttribute(name, value) {
2042
+ return value ? ` ${name}="${escapeHtml(value)}"` : '';
2043
+ }
2044
+
2045
+ function renderUnsafeLinkText(tokens, href) {
2046
+ const text = this.parser.parseInline(tokens);
2047
+ return href ? `${text} (${escapeHtml(href)})` : text;
2048
+ }
2049
+
2050
+ function renderUnsafeImageText(text, href) {
2051
+ return href ? `![${escapeHtml(text)}](${escapeHtml(href)})` : `![${escapeHtml(text)}]`;
2052
+ }
2053
+
2054
+ // Configure marked with syntax highlighting and raw HTML escaping
2017
2055
  marked.use({
2018
2056
  breaks: true,
2019
2057
  gfm: true,
@@ -2043,9 +2081,29 @@
2043
2081
  text(token) {
2044
2082
  return escapeHtmlTags(escapeHtml(token.text));
2045
2083
  },
2084
+ // Raw HTML: render as text, never as executable DOM
2085
+ html(token) {
2086
+ return escapeHtml(token.raw || token.text || '');
2087
+ },
2046
2088
  // Inline code: escape HTML
2047
2089
  codespan(token) {
2048
2090
  return `<code>${escapeHtml(token.text)}</code>`;
2091
+ },
2092
+ link(token) {
2093
+ const href = token.href || '';
2094
+ const sanitizedHref = sanitizeMarkdownUrl(href);
2095
+ if (sanitizedHref === null) {
2096
+ return renderUnsafeLinkText.call(this, token.tokens || [], href);
2097
+ }
2098
+ return `<a href="${escapeHtml(sanitizedHref)}"${renderAttribute('title', token.title)}>${this.parser.parseInline(token.tokens || [])}</a>`;
2099
+ },
2100
+ image(token) {
2101
+ const href = token.href || '';
2102
+ const sanitizedHref = sanitizeMarkdownUrl(href);
2103
+ if (sanitizedHref === null) {
2104
+ return renderUnsafeImageText(token.text || '', href);
2105
+ }
2106
+ return `<img src="${escapeHtml(sanitizedHref)}" alt="${escapeHtml(token.text || '')}"${renderAttribute('title', token.title)}>`;
2049
2107
  }
2050
2108
  }
2051
2109
  });
@@ -38,14 +38,25 @@ interface TtsrScope {
38
38
  interface TtsrEntry {
39
39
  rule: Rule;
40
40
  conditions: RegExp[];
41
+ conditionWindows: Array<number | undefined>;
42
+
41
43
  scope: TtsrScope;
42
44
  globalPathGlobs?: Bun.Glob[];
43
45
  }
44
46
 
45
47
  /** Tracks when a rule was last injected (for repeat gating). */
48
+ export interface TtsrInjectionRecord {
49
+ name: string;
50
+ lastInjectedAt: number;
51
+ repeatMode?: "once" | "after-gap";
52
+ repeatGap?: number;
53
+ }
54
+
46
55
  interface InjectionRecord {
47
56
  /** Message count (turn index) when the rule was last injected. */
48
57
  lastInjectedAt: number;
58
+ repeatMode?: "once" | "after-gap";
59
+ repeatGap?: number;
49
60
  }
50
61
 
51
62
  const DEFAULT_SETTINGS: Required<TtsrSettings> = {
@@ -56,6 +67,9 @@ const DEFAULT_SETTINGS: Required<TtsrSettings> = {
56
67
  repeatGap: 10,
57
68
  };
58
69
 
70
+ const SAFE_REGEX_WINDOW_CAP = 4096;
71
+ const SAFE_REGEX_WINDOW_MULTIPLIER = 4;
72
+
59
73
  const DEFAULT_SCOPE: TtsrScope = {
60
74
  allowText: true,
61
75
  allowThinking: false,
@@ -70,7 +84,7 @@ export class TtsrManager {
70
84
  readonly #buffers = new Map<string, string>();
71
85
  #messageCount = 0;
72
86
 
73
- constructor(settings?: TtsrSettings) {
87
+ constructor(settings?: Partial<TtsrSettings>) {
74
88
  this.#settings = { ...DEFAULT_SETTINGS, ...settings };
75
89
  }
76
90
 
@@ -81,12 +95,15 @@ export class TtsrManager {
81
95
  return true;
82
96
  }
83
97
 
84
- if (this.#settings.repeatMode === "once") {
98
+ const entry = this.#rules.get(ruleName);
99
+ const repeatMode = entry?.rule.repeatMode ?? record.repeatMode ?? this.#settings.repeatMode;
100
+ if (repeatMode === "once") {
85
101
  return false;
86
102
  }
87
103
 
104
+ const repeatGap = entry?.rule.repeatGap ?? record.repeatGap ?? this.#settings.repeatGap;
88
105
  const gap = this.#messageCount - record.lastInjectedAt;
89
- return gap >= this.#settings.repeatGap;
106
+ return gap >= repeatGap;
90
107
  }
91
108
 
92
109
  #compileConditions(rule: Rule): RegExp[] {
@@ -106,6 +123,20 @@ export class TtsrManager {
106
123
  return compiled;
107
124
  }
108
125
 
126
+ #conditionWindow(condition: RegExp): number | undefined {
127
+ const source = condition.source;
128
+ if (source.length === 0 || source.length > SAFE_REGEX_WINDOW_CAP) {
129
+ return undefined;
130
+ }
131
+ // Quantifiers (*, +, {n,}) and anchors/lookaround/backrefs can make a match's
132
+ // witness exceed a bounded window, so fall back to full-buffer scanning (always
133
+ // correct) for those patterns rather than risk silently missing a match.
134
+ if (/[$^*+{]|\\[bBAZz]|\\[1-9]|\(\?/.test(source)) {
135
+ return undefined;
136
+ }
137
+ return Math.min(SAFE_REGEX_WINDOW_CAP, Math.max(1, source.length * SAFE_REGEX_WINDOW_MULTIPLIER));
138
+ }
139
+
109
140
  #compileGlobalPathGlobs(globs: Rule["globs"]): Bun.Glob[] | undefined {
110
141
  if (!globs || globs.length === 0) {
111
142
  return undefined;
@@ -281,17 +312,28 @@ export class TtsrManager {
281
312
  }
282
313
 
283
314
  #matchesCondition(entry: TtsrEntry, streamBuffer: string): boolean {
284
- for (const condition of entry.conditions) {
315
+ for (let i = 0; i < entry.conditions.length; i++) {
316
+ const condition = entry.conditions[i];
317
+ const window = entry.conditionWindows[i];
318
+ const target = window === undefined ? streamBuffer : streamBuffer.slice(-window);
285
319
  condition.lastIndex = 0;
286
- if (condition.test(streamBuffer)) {
320
+ if (condition.test(target)) {
287
321
  return true;
288
322
  }
289
323
  }
290
324
  return false;
291
325
  }
292
326
 
327
+ /** Check if TTSR is disabled for this manager. */
328
+ #isDisabled(): boolean {
329
+ return this.#settings.enabled === false;
330
+ }
331
+
293
332
  /** Add a TTSR rule to be monitored. */
294
333
  addRule(rule: Rule): boolean {
334
+ if (this.#isDisabled()) {
335
+ return false;
336
+ }
295
337
  if (this.#rules.has(rule.name)) {
296
338
  return false;
297
339
  }
@@ -310,9 +352,11 @@ export class TtsrManager {
310
352
  return false;
311
353
  }
312
354
  const globalPathGlobs = this.#compileGlobalPathGlobs(rule.globs);
355
+ const conditionWindows = conditions.map(condition => this.#conditionWindow(condition));
313
356
  this.#rules.set(rule.name, {
314
357
  rule,
315
358
  conditions,
359
+ conditionWindows,
316
360
  scope,
317
361
  globalPathGlobs,
318
362
  });
@@ -334,6 +378,9 @@ export class TtsrManager {
334
378
  * assistant prose, thinking text, and unrelated tool argument streams.
335
379
  */
336
380
  checkDelta(delta: string, context: TtsrMatchContext): Rule[] {
381
+ if (this.#isDisabled()) {
382
+ return [];
383
+ }
337
384
  const bufferKey = this.#bufferKey(context);
338
385
  const nextBuffer = `${this.#buffers.get(bufferKey) ?? ""}${delta}`;
339
386
  this.#buffers.set(bufferKey, nextBuffer);
@@ -378,16 +425,18 @@ export class TtsrManager {
378
425
  if (ruleName.length === 0) {
379
426
  continue;
380
427
  }
381
- const record = this.#injectionRecords.get(ruleName);
382
- if (!record) {
383
- this.#injectionRecords.set(ruleName, { lastInjectedAt: this.#messageCount });
384
- } else {
385
- record.lastInjectedAt = this.#messageCount;
386
- }
428
+ const entry = this.#rules.get(ruleName);
429
+ const nextRecord: InjectionRecord = {
430
+ lastInjectedAt: this.#messageCount,
431
+ repeatMode: entry?.rule.repeatMode ?? this.#settings.repeatMode,
432
+ repeatGap: entry?.rule.repeatGap ?? this.#settings.repeatGap,
433
+ };
434
+ this.#injectionRecords.set(ruleName, nextRecord);
435
+
387
436
  logger.debug("TTSR rule marked as injected", {
388
437
  ruleName,
389
438
  messageCount: this.#messageCount,
390
- repeatMode: this.#settings.repeatMode,
439
+ repeatMode: nextRecord.repeatMode,
391
440
  });
392
441
  }
393
442
  }
@@ -397,13 +446,31 @@ export class TtsrManager {
397
446
  return Array.from(this.#injectionRecords.keys());
398
447
  }
399
448
 
400
- /** Restore injected state from a list of rule names. */
401
- restoreInjected(ruleNames: string[]): void {
402
- for (const name of ruleNames) {
403
- this.#injectionRecords.set(name, { lastInjectedAt: 0 });
449
+ /** Get full injected rule records (for persistence). */
450
+ getInjectedRecords(): TtsrInjectionRecord[] {
451
+ return Array.from(this.#injectionRecords, ([name, record]) => ({ name, ...record }));
452
+ }
453
+
454
+ /** Restore injected state from legacy rule names or persisted records. */
455
+ restoreInjected(records: string[] | TtsrInjectionRecord[]): void {
456
+ if (this.#isDisabled()) {
457
+ return;
404
458
  }
405
- if (ruleNames.length > 0) {
406
- logger.debug("TTSR injected state restored", { ruleNames });
459
+ for (const record of records) {
460
+ if (typeof record === "string") {
461
+ this.#injectionRecords.set(record, { lastInjectedAt: 0 });
462
+ continue;
463
+ }
464
+ const name = record.name.trim();
465
+ if (name.length === 0) continue;
466
+ this.#injectionRecords.set(name, {
467
+ lastInjectedAt: Number.isFinite(record.lastInjectedAt) ? record.lastInjectedAt : 0,
468
+ repeatMode: record.repeatMode,
469
+ repeatGap: record.repeatGap,
470
+ });
471
+ }
472
+ if (records.length > 0) {
473
+ logger.debug("TTSR injected state restored", { records });
407
474
  }
408
475
  }
409
476
 
@@ -414,6 +481,9 @@ export class TtsrManager {
414
481
 
415
482
  /** Check if any TTSR rules are registered. */
416
483
  hasRules(): boolean {
484
+ if (this.#isDisabled()) {
485
+ return false;
486
+ }
417
487
  return this.#rules.size > 0;
418
488
  }
419
489
 
@@ -427,6 +497,14 @@ export class TtsrManager {
427
497
  return this.#messageCount;
428
498
  }
429
499
 
500
+ /** Restore message counter after session resume. */
501
+ restoreMessageCount(messageCount: number): void {
502
+ if (this.#isDisabled()) {
503
+ return;
504
+ }
505
+ this.#messageCount = Math.max(0, Math.floor(messageCount));
506
+ }
507
+
430
508
  /** Get settings. */
431
509
  getSettings(): Required<TtsrSettings> {
432
510
  return this.#settings;