@code-yeongyu/senpi 2026.8.3-3 → 2026.8.4

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 (145) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/core/agent-session.d.ts +2 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +64 -26
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +2 -1
  7. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  8. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +126 -26
  9. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  10. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  11. package/dist/core/extensions/builtin/compaction/index.js +12 -7
  12. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  13. package/dist/core/extensions/builtin/compaction/per-turn-cap.d.ts.map +1 -1
  14. package/dist/core/extensions/builtin/compaction/per-turn-cap.js +4 -1
  15. package/dist/core/extensions/builtin/compaction/per-turn-cap.js.map +1 -1
  16. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts +4 -0
  17. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -0
  18. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +155 -0
  19. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -0
  20. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  21. package/dist/core/extensions/builtin/compaction/speculative.js +21 -13
  22. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  23. package/dist/core/extensions/builtin/goal/cache-warm-renderer.d.ts.map +1 -1
  24. package/dist/core/extensions/builtin/goal/cache-warm-renderer.js +9 -14
  25. package/dist/core/extensions/builtin/goal/cache-warm-renderer.js.map +1 -1
  26. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
  27. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
  28. package/dist/core/extensions/builtin/goal/continuation-recovery.js +7 -0
  29. package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
  30. package/dist/core/extensions/builtin/goal/continuation.d.ts +1 -1
  31. package/dist/core/extensions/builtin/goal/continuation.js +1 -1
  32. package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
  33. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  34. package/dist/core/extensions/builtin/goal/index.js +12 -10
  35. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  36. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +7 -1
  37. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
  38. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +14 -2
  39. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
  40. package/dist/core/extensions/builtin/gpt-apply-patch/extension.d.ts.map +1 -1
  41. package/dist/core/extensions/builtin/gpt-apply-patch/extension.js +14 -1
  42. package/dist/core/extensions/builtin/gpt-apply-patch/extension.js.map +1 -1
  43. package/dist/core/extensions/builtin/gpt-apply-patch/types.d.ts +1 -0
  44. package/dist/core/extensions/builtin/gpt-apply-patch/types.d.ts.map +1 -1
  45. package/dist/core/extensions/builtin/gpt-apply-patch/types.js.map +1 -1
  46. package/dist/core/extensions/builtin/loop-guard/renderer.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/loop-guard/renderer.js +4 -12
  48. package/dist/core/extensions/builtin/loop-guard/renderer.js.map +1 -1
  49. package/dist/core/extensions/builtin/rule-activation/index.d.ts +6 -0
  50. package/dist/core/extensions/builtin/rule-activation/index.d.ts.map +1 -0
  51. package/dist/core/extensions/builtin/rule-activation/index.js +10 -0
  52. package/dist/core/extensions/builtin/rule-activation/index.js.map +1 -0
  53. package/dist/core/extensions/builtin/rule-activation/renderer.d.ts +3 -0
  54. package/dist/core/extensions/builtin/rule-activation/renderer.d.ts.map +1 -0
  55. package/dist/core/extensions/builtin/rule-activation/renderer.js +37 -0
  56. package/dist/core/extensions/builtin/rule-activation/renderer.js.map +1 -0
  57. package/dist/core/extensions/builtin/rule-activation/types.d.ts +15 -0
  58. package/dist/core/extensions/builtin/rule-activation/types.d.ts.map +1 -0
  59. package/dist/core/extensions/builtin/rule-activation/types.js +45 -0
  60. package/dist/core/extensions/builtin/rule-activation/types.js.map +1 -0
  61. package/dist/core/extensions/builtin/rules/index.d.ts.map +1 -1
  62. package/dist/core/extensions/builtin/rules/index.js +9 -1
  63. package/dist/core/extensions/builtin/rules/index.js.map +1 -1
  64. package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.d.ts +22 -0
  65. package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.d.ts.map +1 -0
  66. package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.js +93 -0
  67. package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.js.map +1 -0
  68. package/dist/core/extensions/builtin/ttsr/index.d.ts.map +1 -1
  69. package/dist/core/extensions/builtin/ttsr/index.js +60 -21
  70. package/dist/core/extensions/builtin/ttsr/index.js.map +1 -1
  71. package/dist/core/extensions/builtin/ttsr/prompts.d.ts +1 -0
  72. package/dist/core/extensions/builtin/ttsr/prompts.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/ttsr/prompts.js +5 -0
  74. package/dist/core/extensions/builtin/ttsr/prompts.js.map +1 -1
  75. package/dist/core/extensions/builtin/ttsr/repetitive-turns-lane.d.ts +23 -0
  76. package/dist/core/extensions/builtin/ttsr/repetitive-turns-lane.d.ts.map +1 -0
  77. package/dist/core/extensions/builtin/ttsr/repetitive-turns-lane.js +101 -0
  78. package/dist/core/extensions/builtin/ttsr/repetitive-turns-lane.js.map +1 -0
  79. package/dist/core/extensions/builtin/ttsr/stream-remediation.d.ts +15 -0
  80. package/dist/core/extensions/builtin/ttsr/stream-remediation.d.ts.map +1 -0
  81. package/dist/core/extensions/builtin/ttsr/stream-remediation.js +22 -0
  82. package/dist/core/extensions/builtin/ttsr/stream-remediation.js.map +1 -0
  83. package/dist/core/extensions/builtin/ttsr/types.d.ts +1 -1
  84. package/dist/core/extensions/builtin/ttsr/types.d.ts.map +1 -1
  85. package/dist/core/extensions/builtin/ttsr/types.js.map +1 -1
  86. package/dist/core/extensions/notice/adapters.d.ts +15 -0
  87. package/dist/core/extensions/notice/adapters.d.ts.map +1 -0
  88. package/dist/core/extensions/notice/adapters.js +22 -0
  89. package/dist/core/extensions/notice/adapters.js.map +1 -0
  90. package/dist/core/extensions/notice/box.d.ts +8 -0
  91. package/dist/core/extensions/notice/box.d.ts.map +1 -0
  92. package/dist/core/extensions/notice/box.js +17 -0
  93. package/dist/core/extensions/notice/box.js.map +1 -0
  94. package/dist/core/extensions/notice/index.d.ts +4 -0
  95. package/dist/core/extensions/notice/index.d.ts.map +1 -0
  96. package/dist/core/extensions/notice/index.js +3 -0
  97. package/dist/core/extensions/notice/index.js.map +1 -0
  98. package/dist/core/extensions/notice/spec.d.ts +23 -0
  99. package/dist/core/extensions/notice/spec.d.ts.map +1 -0
  100. package/dist/core/extensions/notice/spec.js +2 -0
  101. package/dist/core/extensions/notice/spec.js.map +1 -0
  102. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  103. package/dist/core/retry-fallback/settings.js +16 -1
  104. package/dist/core/retry-fallback/settings.js.map +1 -1
  105. package/dist/core/retry-fallback/validate.d.ts.map +1 -1
  106. package/dist/core/retry-fallback/validate.js +15 -2
  107. package/dist/core/retry-fallback/validate.js.map +1 -1
  108. package/dist/core/settings-manager.d.ts +7 -0
  109. package/dist/core/settings-manager.d.ts.map +1 -1
  110. package/dist/core/settings-manager.js +13 -0
  111. package/dist/core/settings-manager.js.map +1 -1
  112. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  113. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  114. package/dist/modes/interactive/interactive-mode.js +42 -7
  115. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  116. package/dist/modes/interactive/interactive-stderr-guard.d.ts.map +1 -1
  117. package/dist/modes/interactive/interactive-stderr-guard.js +37 -0
  118. package/dist/modes/interactive/interactive-stderr-guard.js.map +1 -1
  119. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +16 -0
  120. package/node_modules/@code-yeongyu/senpi-codemode/README.md +13 -3
  121. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  122. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/eval-status.ts +1 -1
  123. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +6 -6
  124. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-runtime.ts +4 -4
  125. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +2 -2
  126. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-snapshot.ts +2 -2
  127. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-request.ts +20 -2
  128. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +17 -2
  129. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +18 -7
  130. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +19 -5
  131. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  132. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +1 -1
  133. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  134. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  135. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  136. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  137. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  138. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  139. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
  140. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  141. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  142. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  143. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  144. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  145. package/package.json +6 -6
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rule-activation/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,OAAO,EAA8B,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEpF,OAAO,EACN,KAAK,6BAA6B,EAClC,0BAA0B,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC1B,MAAM,YAAY,CAAC;AAEpB,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAErE;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAE3F"}
@@ -0,0 +1,10 @@
1
+ import { renderRuleActivationEntry } from "./renderer.js";
2
+ import { RULE_ACTIVATION_ENTRY_TYPE } from "./types.js";
3
+ export { RULE_ACTIVATION_ENTRY_TYPE, } from "./types.js";
4
+ export function registerRuleActivationRenderer(pi) {
5
+ pi.registerEntryRenderer(RULE_ACTIVATION_ENTRY_TYPE, renderRuleActivationEntry);
6
+ }
7
+ export function appendRuleActivation(pi, details) {
8
+ pi.appendEntry(RULE_ACTIVATION_ENTRY_TYPE, details);
9
+ }
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rule-activation/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAA8B,MAAM,YAAY,CAAC;AAEpF,OAAO,EAEN,0BAA0B,GAG1B,MAAM,YAAY,CAAC;AAEpB,MAAM,UAAU,8BAA8B,CAAC,EAAgB;IAC9D,EAAE,CAAC,qBAAqB,CAAC,0BAA0B,EAAE,yBAAyB,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,EAAgB,EAAE,OAA8B;IACpF,EAAE,CAAC,WAAW,CAAC,0BAA0B,EAAE,OAAO,CAAC,CAAC;AACrD,CAAC","sourcesContent":["import type { ExtensionAPI } from \"../../types.ts\";\nimport { renderRuleActivationEntry } from \"./renderer.ts\";\nimport { RULE_ACTIVATION_ENTRY_TYPE, type RuleActivationDetails } from \"./types.ts\";\n\nexport {\n\ttype ProjectRulesActivationDetails,\n\tRULE_ACTIVATION_ENTRY_TYPE,\n\ttype RuleActivationDetails,\n\ttype TtsrActivationDetails,\n} from \"./types.ts\";\n\nexport function registerRuleActivationRenderer(pi: ExtensionAPI): void {\n\tpi.registerEntryRenderer(RULE_ACTIVATION_ENTRY_TYPE, renderRuleActivationEntry);\n}\n\nexport function appendRuleActivation(pi: ExtensionAPI, details: RuleActivationDetails): void {\n\tpi.appendEntry(RULE_ACTIVATION_ENTRY_TYPE, details);\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { EntryRenderer } from "../../types.ts";
2
+ export declare const renderRuleActivationEntry: EntryRenderer<unknown>;
3
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rule-activation/renderer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,eAAO,MAAM,yBAAyB,EAAE,aAAa,CAAC,OAAO,CAI3D,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { noticeEntryRenderer } from "../../notice/index.js";
2
+ import { parseRuleActivationDetails } from "./types.js";
3
+ export const renderRuleActivationEntry = noticeEntryRenderer((entry) => {
4
+ const details = parseRuleActivationDetails(entry.data);
5
+ if (details === undefined)
6
+ return undefined;
7
+ return { title: titleLine(details), why: summaryLine(details), expandedLine: detailLine(details) };
8
+ });
9
+ function titleLine(details) {
10
+ switch (details.kind) {
11
+ case "project-rules":
12
+ return `● Project rules · ${details.targetPath}`;
13
+ case "ttsr":
14
+ return `⚠ Stream rule · ${details.owner}`;
15
+ }
16
+ }
17
+ function summaryLine(details) {
18
+ switch (details.kind) {
19
+ case "project-rules": {
20
+ const noun = details.rules.length === 1 ? "instruction" : "instructions";
21
+ return `${details.rules.length} ${noun} matched and injected for this tool result.`;
22
+ }
23
+ case "ttsr":
24
+ return details.remediation === "nudge"
25
+ ? "Output interrupted; a corrective nudge was queued."
26
+ : "Corrupted generation discarded; bounded provider retry started.";
27
+ }
28
+ }
29
+ function detailLine(details) {
30
+ switch (details.kind) {
31
+ case "project-rules":
32
+ return details.rules.map((rule) => `rule ${rule}`).join("\n");
33
+ case "ttsr":
34
+ return `remediation ${details.remediation} · observed ${details.rules.join(", ")}`;
35
+ }
36
+ }
37
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rule-activation/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,0BAA0B,EAA8B,MAAM,YAAY,CAAC;AAEpF,MAAM,CAAC,MAAM,yBAAyB,GAA2B,mBAAmB,CAAC,CAAC,KAAK,EAAE,EAAE;IAC9F,MAAM,OAAO,GAAG,0BAA0B,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5C,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;AACpG,CAAC,CAAC,CAAC;AAEH,SAAS,SAAS,CAAC,OAA8B;IAChD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,eAAe;YACnB,OAAO,qBAAqB,OAAO,CAAC,UAAU,EAAE,CAAC;QAClD,KAAK,MAAM;YACV,OAAO,mBAAmB,OAAO,CAAC,KAAK,EAAE,CAAC;IAC5C,CAAC;AACF,CAAC;AAED,SAAS,WAAW,CAAC,OAA8B;IAClD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,eAAe,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC;YACzE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,6CAA6C,CAAC;QACrF,CAAC;QACD,KAAK,MAAM;YACV,OAAO,OAAO,CAAC,WAAW,KAAK,OAAO;gBACrC,CAAC,CAAC,oDAAoD;gBACtD,CAAC,CAAC,iEAAiE,CAAC;IACvE,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,OAA8B;IACjD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,eAAe;YACnB,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/D,KAAK,MAAM;YACV,OAAO,eAAe,OAAO,CAAC,WAAW,eAAe,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACrF,CAAC;AACF,CAAC","sourcesContent":["import { noticeEntryRenderer } from \"../../notice/index.ts\";\nimport type { EntryRenderer } from \"../../types.ts\";\nimport { parseRuleActivationDetails, type RuleActivationDetails } from \"./types.ts\";\n\nexport const renderRuleActivationEntry: EntryRenderer<unknown> = noticeEntryRenderer((entry) => {\n\tconst details = parseRuleActivationDetails(entry.data);\n\tif (details === undefined) return undefined;\n\treturn { title: titleLine(details), why: summaryLine(details), expandedLine: detailLine(details) };\n});\n\nfunction titleLine(details: RuleActivationDetails): string {\n\tswitch (details.kind) {\n\t\tcase \"project-rules\":\n\t\t\treturn `● Project rules · ${details.targetPath}`;\n\t\tcase \"ttsr\":\n\t\t\treturn `⚠ Stream rule · ${details.owner}`;\n\t}\n}\n\nfunction summaryLine(details: RuleActivationDetails): string {\n\tswitch (details.kind) {\n\t\tcase \"project-rules\": {\n\t\t\tconst noun = details.rules.length === 1 ? \"instruction\" : \"instructions\";\n\t\t\treturn `${details.rules.length} ${noun} matched and injected for this tool result.`;\n\t\t}\n\t\tcase \"ttsr\":\n\t\t\treturn details.remediation === \"nudge\"\n\t\t\t\t? \"Output interrupted; a corrective nudge was queued.\"\n\t\t\t\t: \"Corrupted generation discarded; bounded provider retry started.\";\n\t}\n}\n\nfunction detailLine(details: RuleActivationDetails): string {\n\tswitch (details.kind) {\n\t\tcase \"project-rules\":\n\t\t\treturn details.rules.map((rule) => `rule ${rule}`).join(\"\\n\");\n\t\tcase \"ttsr\":\n\t\t\treturn `remediation ${details.remediation} · observed ${details.rules.join(\", \")}`;\n\t}\n}\n"]}
@@ -0,0 +1,15 @@
1
+ export declare const RULE_ACTIVATION_ENTRY_TYPE = "rule-activation";
2
+ export interface ProjectRulesActivationDetails {
3
+ readonly kind: "project-rules";
4
+ readonly targetPath: string;
5
+ readonly rules: readonly string[];
6
+ }
7
+ export interface TtsrActivationDetails {
8
+ readonly kind: "ttsr";
9
+ readonly owner: string;
10
+ readonly rules: readonly string[];
11
+ readonly remediation: "nudge" | "provider-error";
12
+ }
13
+ export type RuleActivationDetails = ProjectRulesActivationDetails | TtsrActivationDetails;
14
+ export declare function parseRuleActivationDetails(value: unknown): RuleActivationDetails | undefined;
15
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rule-activation/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,0BAA0B,oBAAoB,CAAC;AAE5D,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,OAAO,GAAG,gBAAgB,CAAC;CACjD;AAED,MAAM,MAAM,qBAAqB,GAAG,6BAA6B,GAAG,qBAAqB,CAAC;AAE1F,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,qBAAqB,GAAG,SAAS,CAW5F"}
@@ -0,0 +1,45 @@
1
+ export const RULE_ACTIVATION_ENTRY_TYPE = "rule-activation";
2
+ export function parseRuleActivationDetails(value) {
3
+ if (!isObject(value))
4
+ return undefined;
5
+ const kind = Reflect.get(value, "kind");
6
+ switch (kind) {
7
+ case "project-rules":
8
+ return parseProjectRulesActivation(value);
9
+ case "ttsr":
10
+ return parseTtsrActivation(value);
11
+ default:
12
+ return undefined;
13
+ }
14
+ }
15
+ function parseProjectRulesActivation(value) {
16
+ const targetPath = Reflect.get(value, "targetPath");
17
+ const rules = stringList(Reflect.get(value, "rules"));
18
+ if (typeof targetPath !== "string" || targetPath.length === 0 || rules === undefined)
19
+ return undefined;
20
+ return { kind: "project-rules", targetPath, rules };
21
+ }
22
+ function parseTtsrActivation(value) {
23
+ const owner = Reflect.get(value, "owner");
24
+ const rules = stringList(Reflect.get(value, "rules"));
25
+ const remediation = Reflect.get(value, "remediation");
26
+ if (typeof owner !== "string" ||
27
+ owner.length === 0 ||
28
+ rules === undefined ||
29
+ (remediation !== "nudge" && remediation !== "provider-error")) {
30
+ return undefined;
31
+ }
32
+ return { kind: "ttsr", owner, rules, remediation };
33
+ }
34
+ function stringList(value) {
35
+ if (!Array.isArray(value) ||
36
+ value.length === 0 ||
37
+ !value.every((entry) => typeof entry === "string" && entry.length > 0)) {
38
+ return undefined;
39
+ }
40
+ return value;
41
+ }
42
+ function isObject(value) {
43
+ return typeof value === "object" && value !== null && !Array.isArray(value);
44
+ }
45
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rule-activation/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,0BAA0B,GAAG,iBAAiB,CAAC;AAiB5D,MAAM,UAAU,0BAA0B,CAAC,KAAc;IACxD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACxC,QAAQ,IAAI,EAAE,CAAC;QACd,KAAK,eAAe;YACnB,OAAO,2BAA2B,CAAC,KAAK,CAAC,CAAC;QAC3C,KAAK,MAAM;YACV,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACnC;YACC,OAAO,SAAS,CAAC;IACnB,CAAC;AACF,CAAC;AAED,SAAS,2BAA2B,CAAC,KAAa;IACjD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACvG,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IACtD,IACC,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,CAAC,MAAM,KAAK,CAAC;QAClB,KAAK,KAAK,SAAS;QACnB,CAAC,WAAW,KAAK,OAAO,IAAI,WAAW,KAAK,gBAAgB,CAAC,EAC5D,CAAC;QACF,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AACpD,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IACjC,IACC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACrB,KAAK,CAAC,MAAM,KAAK,CAAC;QAClB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EACrE,CAAC;QACF,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["export const RULE_ACTIVATION_ENTRY_TYPE = \"rule-activation\";\n\nexport interface ProjectRulesActivationDetails {\n\treadonly kind: \"project-rules\";\n\treadonly targetPath: string;\n\treadonly rules: readonly string[];\n}\n\nexport interface TtsrActivationDetails {\n\treadonly kind: \"ttsr\";\n\treadonly owner: string;\n\treadonly rules: readonly string[];\n\treadonly remediation: \"nudge\" | \"provider-error\";\n}\n\nexport type RuleActivationDetails = ProjectRulesActivationDetails | TtsrActivationDetails;\n\nexport function parseRuleActivationDetails(value: unknown): RuleActivationDetails | undefined {\n\tif (!isObject(value)) return undefined;\n\tconst kind = Reflect.get(value, \"kind\");\n\tswitch (kind) {\n\t\tcase \"project-rules\":\n\t\t\treturn parseProjectRulesActivation(value);\n\t\tcase \"ttsr\":\n\t\t\treturn parseTtsrActivation(value);\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction parseProjectRulesActivation(value: object): ProjectRulesActivationDetails | undefined {\n\tconst targetPath = Reflect.get(value, \"targetPath\");\n\tconst rules = stringList(Reflect.get(value, \"rules\"));\n\tif (typeof targetPath !== \"string\" || targetPath.length === 0 || rules === undefined) return undefined;\n\treturn { kind: \"project-rules\", targetPath, rules };\n}\n\nfunction parseTtsrActivation(value: object): TtsrActivationDetails | undefined {\n\tconst owner = Reflect.get(value, \"owner\");\n\tconst rules = stringList(Reflect.get(value, \"rules\"));\n\tconst remediation = Reflect.get(value, \"remediation\");\n\tif (\n\t\ttypeof owner !== \"string\" ||\n\t\towner.length === 0 ||\n\t\trules === undefined ||\n\t\t(remediation !== \"nudge\" && remediation !== \"provider-error\")\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { kind: \"ttsr\", owner, rules, remediation };\n}\n\nfunction stringList(value: unknown): readonly string[] | undefined {\n\tif (\n\t\t!Array.isArray(value) ||\n\t\tvalue.length === 0 ||\n\t\t!value.every((entry) => typeof entry === \"string\" && entry.length > 0)\n\t) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction isObject(value: unknown): value is object {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rules/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAcnD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmI/D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rules/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAenD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA0I/D"}
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, realpathSync } from "node:fs";
2
2
  import { isAbsolute, relative } from "node:path";
3
+ import { appendRuleActivation, registerRuleActivationRenderer } from "../rule-activation/index.js";
3
4
  import { registerSlashCommands } from "./commands.js";
4
5
  import { configFromEnvironment } from "./config.js";
5
6
  import { createEngine } from "./rules/engine.js";
@@ -34,6 +35,7 @@ export default function piRulesExtension(pi) {
34
35
  extractToolPaths,
35
36
  });
36
37
  registerSlashCommands(pi, engine);
38
+ registerRuleActivationRenderer(pi);
37
39
  function syncConfigFromFlags() {
38
40
  const disabled = pi.getFlag("pi-rules-disabled");
39
41
  const mode = pi.getFlag("pi-rules-mode");
@@ -109,10 +111,16 @@ export default function piRulesExtension(pi) {
109
111
  return undefined;
110
112
  }
111
113
  const firstPendingTarget = pendingFingerprints[0]?.targetPath ?? firstTargetPath;
112
- const block = engine.formatDynamic(rules, displayPath(ctx.cwd, firstPendingTarget));
114
+ const targetPath = displayPath(ctx.cwd, firstPendingTarget);
115
+ const block = engine.formatDynamic(rules, targetPath);
113
116
  for (const rule of rules) {
114
117
  engine.markDynamicInjected(firstTargetPath, rule);
115
118
  }
119
+ appendRuleActivation(pi, {
120
+ kind: "project-rules",
121
+ targetPath,
122
+ rules: rules.map((rule) => rule.relativePath),
123
+ });
116
124
  return { content: [...event.content, { type: "text", text: block }] };
117
125
  });
118
126
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rules/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, realpathSync } from \"node:fs\";\nimport { isAbsolute, relative } from \"node:path\";\n\nimport type { ExtensionAPI } from \"../../types.ts\";\n\nimport { registerSlashCommands } from \"./commands.ts\";\nimport { configFromEnvironment } from \"./config.ts\";\nimport { createEngine } from \"./rules/engine.ts\";\nimport { findRuleCandidates } from \"./rules/finder.ts\";\nimport { findProjectRoot } from \"./rules/project-root.ts\";\nimport { extractToolPaths } from \"./rules/tool-paths.ts\";\nimport type { PiRulesConfig } from \"./rules/types.ts\";\n\ntype PiRulesMode = PiRulesConfig[\"mode\"];\n\nconst MODE_VALUES = new Set<string>([\"static\", \"dynamic\", \"both\", \"off\"]);\n\nexport default function piRulesExtension(pi: ExtensionAPI): void {\n\tpi.registerFlag(\"pi-rules-disabled\", {\n\t\ttype: \"boolean\",\n\t\tdefault: false,\n\t\tdescription: \"Disable pi-rules hooks.\",\n\t});\n\tpi.registerFlag(\"pi-rules-mode\", {\n\t\ttype: \"string\",\n\t\tdefault: \"both\",\n\t\tdescription: \"Rule injection mode: static, dynamic, both, or off.\",\n\t});\n\tconst config = configFromEnvironment();\n\tconst envDisabled = config.disabled;\n\tconst engine = createEngine(config, {\n\t\tfindCandidates: findRuleCandidates,\n\t\treadFile: (path) => {\n\t\t\ttry {\n\t\t\t\treturn readFileSync(path, \"utf-8\");\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tfindProjectRoot,\n\t\textractToolPaths,\n\t});\n\tregisterSlashCommands(pi, engine);\n\n\tfunction syncConfigFromFlags(): void {\n\t\tconst disabled = pi.getFlag(\"pi-rules-disabled\");\n\t\tconst mode = pi.getFlag(\"pi-rules-mode\");\n\n\t\tif (typeof disabled === \"boolean\") {\n\t\t\tengine.config.disabled = disabled || envDisabled;\n\t\t}\n\t\tif (typeof mode === \"string\" && isPiRulesMode(mode)) {\n\t\t\tengine.config.mode = mode;\n\t\t}\n\t}\n\n\tpi.on(\"session_start\", async (event, ctx) => {\n\t\tsyncConfigFromFlags();\n\t\tif (engine.config.disabled) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tengine.resetSession(ctx.cwd);\n\t\tpi.appendEntry(\"pi-rules.scan\", { cwd: ctx.cwd, reason: event.reason });\n\t\treturn undefined;\n\t});\n\n\tpi.on(\"session_compact\", async (event, ctx) => {\n\t\t// Rejected compactions do not mutate session state; do not reset rules.\n\t\tif (!event.accepted) return undefined;\n\t\tengine.resetSession(ctx.cwd);\n\t\tpi.appendEntry(\"pi-rules.scan\", { cwd: ctx.cwd, reason: \"compact\" });\n\t\treturn undefined;\n\t});\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\tsyncConfigFromFlags();\n\t\tif (engine.config.disabled || engine.config.mode === \"off\" || engine.config.mode === \"dynamic\") {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst loaded = engine.loadStaticRules(ctx.cwd);\n\t\tconst nativeContextPaths = new Set(\n\t\t\tevent.systemPromptOptions.contextFiles?.flatMap((contextFile) => pathKeys(contextFile.path)) ?? [],\n\t\t);\n\t\tfor (const rule of loaded.rules) {\n\t\t\tif (nativeContextPaths.has(rule.path) || nativeContextPaths.has(rule.realPath)) {\n\t\t\t\tengine.markStaticInjected(rule);\n\t\t\t}\n\t\t}\n\t\t// Deliberately NOT gated on isStaticInjected: the host re-emits this from the BASE prompt\n\t\t// every user prompt, so a prior turn's mark would drop the block from turn 2 onward. The\n\t\t// marks below serve only the dynamic tool_result path.\n\t\tconst rules = loaded.rules.filter(\n\t\t\t(rule) => !nativeContextPaths.has(rule.path) && !nativeContextPaths.has(rule.realPath),\n\t\t);\n\n\t\tif (rules.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst block = engine.formatStatic(rules);\n\t\tfor (const rule of rules) {\n\t\t\tengine.markStaticInjected(rule);\n\t\t}\n\n\t\treturn { systemPrompt: event.systemPrompt + block };\n\t});\n\n\tpi.on(\"tool_result\", async (event, ctx) => {\n\t\tsyncConfigFromFlags();\n\t\tif (engine.config.disabled || engine.config.mode === \"off\" || engine.config.mode === \"static\" || event.isError) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst targetPaths = extractToolPaths(event, ctx.cwd);\n\t\tconst firstTargetPath = targetPaths[0];\n\t\tif (firstTargetPath === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst fingerprints = engine.fingerprintDynamicTargets(ctx.cwd, targetPaths);\n\t\tconst pendingFingerprints = fingerprints.filter((target) => !engine.isDynamicTargetFingerprintCurrent(target));\n\t\tif (pendingFingerprints.length === 0) {\n\t\t\tengine.commitDynamicTargetFingerprints(fingerprints);\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst loaded = engine.loadDynamicRules(\n\t\t\tctx.cwd,\n\t\t\tpendingFingerprints.map((target) => target.targetPath),\n\t\t);\n\t\tengine.commitDynamicTargetFingerprints(fingerprints);\n\t\tconst rules = loaded.rules.filter(\n\t\t\t(rule) => !engine.isStaticInjected(rule) && !engine.isDynamicInjected(firstTargetPath, rule),\n\t\t);\n\t\tif (rules.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst firstPendingTarget = pendingFingerprints[0]?.targetPath ?? firstTargetPath;\n\t\tconst block = engine.formatDynamic(rules, displayPath(ctx.cwd, firstPendingTarget));\n\t\tfor (const rule of rules) {\n\t\t\tengine.markDynamicInjected(firstTargetPath, rule);\n\t\t}\n\n\t\treturn { content: [...event.content, { type: \"text\", text: block }] };\n\t});\n}\n\nfunction isPiRulesMode(value: string): value is PiRulesMode {\n\treturn MODE_VALUES.has(value);\n}\n\nfunction pathKeys(filePath: string): string[] {\n\ttry {\n\t\treturn [filePath, realpathSync.native(filePath)];\n\t} catch {\n\t\treturn [filePath];\n\t}\n}\n\nfunction displayPath(cwd: string, filePath: string): string {\n\treturn isAbsolute(filePath) ? relative(cwd, filePath) : filePath;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/rules/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, realpathSync } from \"node:fs\";\nimport { isAbsolute, relative } from \"node:path\";\n\nimport type { ExtensionAPI } from \"../../types.ts\";\n\nimport { appendRuleActivation, registerRuleActivationRenderer } from \"../rule-activation/index.ts\";\nimport { registerSlashCommands } from \"./commands.ts\";\nimport { configFromEnvironment } from \"./config.ts\";\nimport { createEngine } from \"./rules/engine.ts\";\nimport { findRuleCandidates } from \"./rules/finder.ts\";\nimport { findProjectRoot } from \"./rules/project-root.ts\";\nimport { extractToolPaths } from \"./rules/tool-paths.ts\";\nimport type { PiRulesConfig } from \"./rules/types.ts\";\n\ntype PiRulesMode = PiRulesConfig[\"mode\"];\n\nconst MODE_VALUES = new Set<string>([\"static\", \"dynamic\", \"both\", \"off\"]);\n\nexport default function piRulesExtension(pi: ExtensionAPI): void {\n\tpi.registerFlag(\"pi-rules-disabled\", {\n\t\ttype: \"boolean\",\n\t\tdefault: false,\n\t\tdescription: \"Disable pi-rules hooks.\",\n\t});\n\tpi.registerFlag(\"pi-rules-mode\", {\n\t\ttype: \"string\",\n\t\tdefault: \"both\",\n\t\tdescription: \"Rule injection mode: static, dynamic, both, or off.\",\n\t});\n\tconst config = configFromEnvironment();\n\tconst envDisabled = config.disabled;\n\tconst engine = createEngine(config, {\n\t\tfindCandidates: findRuleCandidates,\n\t\treadFile: (path) => {\n\t\t\ttry {\n\t\t\t\treturn readFileSync(path, \"utf-8\");\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tfindProjectRoot,\n\t\textractToolPaths,\n\t});\n\tregisterSlashCommands(pi, engine);\n\tregisterRuleActivationRenderer(pi);\n\n\tfunction syncConfigFromFlags(): void {\n\t\tconst disabled = pi.getFlag(\"pi-rules-disabled\");\n\t\tconst mode = pi.getFlag(\"pi-rules-mode\");\n\n\t\tif (typeof disabled === \"boolean\") {\n\t\t\tengine.config.disabled = disabled || envDisabled;\n\t\t}\n\t\tif (typeof mode === \"string\" && isPiRulesMode(mode)) {\n\t\t\tengine.config.mode = mode;\n\t\t}\n\t}\n\n\tpi.on(\"session_start\", async (event, ctx) => {\n\t\tsyncConfigFromFlags();\n\t\tif (engine.config.disabled) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tengine.resetSession(ctx.cwd);\n\t\tpi.appendEntry(\"pi-rules.scan\", { cwd: ctx.cwd, reason: event.reason });\n\t\treturn undefined;\n\t});\n\n\tpi.on(\"session_compact\", async (event, ctx) => {\n\t\t// Rejected compactions do not mutate session state; do not reset rules.\n\t\tif (!event.accepted) return undefined;\n\t\tengine.resetSession(ctx.cwd);\n\t\tpi.appendEntry(\"pi-rules.scan\", { cwd: ctx.cwd, reason: \"compact\" });\n\t\treturn undefined;\n\t});\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\tsyncConfigFromFlags();\n\t\tif (engine.config.disabled || engine.config.mode === \"off\" || engine.config.mode === \"dynamic\") {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst loaded = engine.loadStaticRules(ctx.cwd);\n\t\tconst nativeContextPaths = new Set(\n\t\t\tevent.systemPromptOptions.contextFiles?.flatMap((contextFile) => pathKeys(contextFile.path)) ?? [],\n\t\t);\n\t\tfor (const rule of loaded.rules) {\n\t\t\tif (nativeContextPaths.has(rule.path) || nativeContextPaths.has(rule.realPath)) {\n\t\t\t\tengine.markStaticInjected(rule);\n\t\t\t}\n\t\t}\n\t\t// Deliberately NOT gated on isStaticInjected: the host re-emits this from the BASE prompt\n\t\t// every user prompt, so a prior turn's mark would drop the block from turn 2 onward. The\n\t\t// marks below serve only the dynamic tool_result path.\n\t\tconst rules = loaded.rules.filter(\n\t\t\t(rule) => !nativeContextPaths.has(rule.path) && !nativeContextPaths.has(rule.realPath),\n\t\t);\n\n\t\tif (rules.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst block = engine.formatStatic(rules);\n\t\tfor (const rule of rules) {\n\t\t\tengine.markStaticInjected(rule);\n\t\t}\n\n\t\treturn { systemPrompt: event.systemPrompt + block };\n\t});\n\n\tpi.on(\"tool_result\", async (event, ctx) => {\n\t\tsyncConfigFromFlags();\n\t\tif (engine.config.disabled || engine.config.mode === \"off\" || engine.config.mode === \"static\" || event.isError) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst targetPaths = extractToolPaths(event, ctx.cwd);\n\t\tconst firstTargetPath = targetPaths[0];\n\t\tif (firstTargetPath === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst fingerprints = engine.fingerprintDynamicTargets(ctx.cwd, targetPaths);\n\t\tconst pendingFingerprints = fingerprints.filter((target) => !engine.isDynamicTargetFingerprintCurrent(target));\n\t\tif (pendingFingerprints.length === 0) {\n\t\t\tengine.commitDynamicTargetFingerprints(fingerprints);\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst loaded = engine.loadDynamicRules(\n\t\t\tctx.cwd,\n\t\t\tpendingFingerprints.map((target) => target.targetPath),\n\t\t);\n\t\tengine.commitDynamicTargetFingerprints(fingerprints);\n\t\tconst rules = loaded.rules.filter(\n\t\t\t(rule) => !engine.isStaticInjected(rule) && !engine.isDynamicInjected(firstTargetPath, rule),\n\t\t);\n\t\tif (rules.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst firstPendingTarget = pendingFingerprints[0]?.targetPath ?? firstTargetPath;\n\t\tconst targetPath = displayPath(ctx.cwd, firstPendingTarget);\n\t\tconst block = engine.formatDynamic(rules, targetPath);\n\t\tfor (const rule of rules) {\n\t\t\tengine.markDynamicInjected(firstTargetPath, rule);\n\t\t}\n\t\tappendRuleActivation(pi, {\n\t\t\tkind: \"project-rules\",\n\t\t\ttargetPath,\n\t\t\trules: rules.map((rule) => rule.relativePath),\n\t\t});\n\n\t\treturn { content: [...event.content, { type: \"text\", text: block }] };\n\t});\n}\n\nfunction isPiRulesMode(value: string): value is PiRulesMode {\n\treturn MODE_VALUES.has(value);\n}\n\nfunction pathKeys(filePath: string): string[] {\n\ttry {\n\t\treturn [filePath, realpathSync.native(filePath)];\n\t} catch {\n\t\treturn [filePath];\n\t}\n}\n\nfunction displayPath(cwd: string, filePath: string): string {\n\treturn isAbsolute(filePath) ? relative(cwd, filePath) : filePath;\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import type { DetectorMatch } from "../types.ts";
2
+ export declare const REPETITIVE_TURNS_RULE_NAME = "repetitive-turns";
3
+ export declare const REPETITIVE_TURNS_SIMILARITY_THRESHOLD = 0.55;
4
+ export declare const REPETITIVE_TURNS_STREAK_LENGTH = 3;
5
+ export declare const REPETITIVE_TURNS_MIN_NORMALIZED_CHARS = 40;
6
+ export declare const REPETITIVE_TURNS_HISTORY_CAPACITY = 6;
7
+ export declare function normalizeTurnText(text: string): string;
8
+ export declare function trigramJaccard(a: string, b: string): number;
9
+ export declare function isNearDuplicateOfPreviousTurn(normalizedCandidate: string, normalizedPrevious: string): boolean;
10
+ interface TurnEntry {
11
+ readonly normalized: string;
12
+ readonly grams: Set<string>;
13
+ }
14
+ export interface RepetitiveTurnsState {
15
+ readonly history: TurnEntry[];
16
+ streak: number;
17
+ latched: boolean;
18
+ }
19
+ export declare function createRepetitiveTurnsState(): RepetitiveTurnsState;
20
+ export declare function recordTurnText(state: RepetitiveTurnsState, text: string): DetectorMatch | null;
21
+ export {};
22
+ //# sourceMappingURL=repetitive-turns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repetitive-turns.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/ttsr/detectors/repetitive-turns.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,eAAO,MAAM,0BAA0B,qBAAqB,CAAC;AAE7D,eAAO,MAAM,qCAAqC,OAAO,CAAC;AAC1D,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAChD,eAAO,MAAM,qCAAqC,KAAK,CAAC;AACxD,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAOnD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAStD;AAWD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAS3D;AAED,wBAAgB,6BAA6B,CAAC,mBAAmB,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAE9G;AAED,UAAU,SAAS;IAClB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,0BAA0B,IAAI,oBAAoB,CAEjE;AAWD,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAkC9F"}
@@ -0,0 +1,93 @@
1
+ export const REPETITIVE_TURNS_RULE_NAME = "repetitive-turns";
2
+ export const REPETITIVE_TURNS_SIMILARITY_THRESHOLD = 0.55;
3
+ export const REPETITIVE_TURNS_STREAK_LENGTH = 3;
4
+ export const REPETITIVE_TURNS_MIN_NORMALIZED_CHARS = 40;
5
+ export const REPETITIVE_TURNS_HISTORY_CAPACITY = 6;
6
+ const WORD_PATTERN = /[\p{L}\p{N}#]+/gu;
7
+ const DIGIT_RUN_PATTERN = /\d[\d.,:/-]*/g;
8
+ const HEX_LIKE_PATTERN = /\b[0-9a-f]{7,}\b/gi;
9
+ const SAMPLE_LENGTH = 80;
10
+ export function normalizeTurnText(text) {
11
+ return text
12
+ .toLowerCase()
13
+ .replace(HEX_LIKE_PATTERN, "#")
14
+ .replace(DIGIT_RUN_PATTERN, "#")
15
+ .split(/\s+/)
16
+ .filter((token) => token.length > 0)
17
+ .join(" ")
18
+ .trim();
19
+ }
20
+ function wordTrigrams(normalized) {
21
+ const words = normalized.match(WORD_PATTERN) ?? [];
22
+ const grams = new Set();
23
+ for (let i = 0; i + 3 <= words.length; i++) {
24
+ grams.add(`${words[i]} ${words[i + 1]} ${words[i + 2]}`);
25
+ }
26
+ return grams;
27
+ }
28
+ export function trigramJaccard(a, b) {
29
+ const gramsA = wordTrigrams(a);
30
+ const gramsB = wordTrigrams(b);
31
+ if (gramsA.size === 0 || gramsB.size === 0)
32
+ return 0;
33
+ let intersection = 0;
34
+ for (const gram of gramsA) {
35
+ if (gramsB.has(gram))
36
+ intersection += 1;
37
+ }
38
+ return intersection / (gramsA.size + gramsB.size - intersection);
39
+ }
40
+ export function isNearDuplicateOfPreviousTurn(normalizedCandidate, normalizedPrevious) {
41
+ return trigramJaccard(normalizedCandidate, normalizedPrevious) >= REPETITIVE_TURNS_SIMILARITY_THRESHOLD;
42
+ }
43
+ export function createRepetitiveTurnsState() {
44
+ return { history: [], streak: 0, latched: false };
45
+ }
46
+ function jaccardSets(a, b) {
47
+ if (a.size === 0 || b.size === 0)
48
+ return 0;
49
+ let intersection = 0;
50
+ for (const gram of a) {
51
+ if (b.has(gram))
52
+ intersection += 1;
53
+ }
54
+ return intersection / (a.size + b.size - intersection);
55
+ }
56
+ export function recordTurnText(state, text) {
57
+ const normalized = normalizeTurnText(text);
58
+ if (normalized.length < REPETITIVE_TURNS_MIN_NORMALIZED_CHARS) {
59
+ return null;
60
+ }
61
+ const grams = wordTrigrams(normalized);
62
+ const previous = state.history[state.history.length - 1];
63
+ const similarity = previous === undefined ? 0 : jaccardSets(grams, previous.grams);
64
+ state.history.push({ normalized, grams });
65
+ if (state.history.length > REPETITIVE_TURNS_HISTORY_CAPACITY) {
66
+ state.history.shift();
67
+ }
68
+ if (similarity >= REPETITIVE_TURNS_SIMILARITY_THRESHOLD) {
69
+ state.streak += 1;
70
+ }
71
+ else {
72
+ state.streak = 0;
73
+ if (state.latched)
74
+ state.latched = false;
75
+ }
76
+ if (state.latched || state.streak < REPETITIVE_TURNS_STREAK_LENGTH - 1) {
77
+ return null;
78
+ }
79
+ state.latched = true;
80
+ return {
81
+ rule: REPETITIVE_TURNS_RULE_NAME,
82
+ reason: `assistant repeated a near-identical message across ${state.streak + 1} consecutive turns (jaccard ${similarity.toFixed(2)})`,
83
+ anomalyStartOffset: 0,
84
+ garbageStartOffset: 0,
85
+ detail: {
86
+ mechanism: "cross-turn",
87
+ streak: state.streak + 1,
88
+ similarity: Number(similarity.toFixed(3)),
89
+ sample: normalized.slice(0, SAMPLE_LENGTH),
90
+ },
91
+ };
92
+ }
93
+ //# sourceMappingURL=repetitive-turns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repetitive-turns.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/ttsr/detectors/repetitive-turns.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DetectorMatch } from \"../types.ts\";\n\nexport const REPETITIVE_TURNS_RULE_NAME = \"repetitive-turns\";\n\nexport const REPETITIVE_TURNS_SIMILARITY_THRESHOLD = 0.55;\nexport const REPETITIVE_TURNS_STREAK_LENGTH = 3;\nexport const REPETITIVE_TURNS_MIN_NORMALIZED_CHARS = 40;\nexport const REPETITIVE_TURNS_HISTORY_CAPACITY = 6;\n\nconst WORD_PATTERN = /[\\p{L}\\p{N}#]+/gu;\nconst DIGIT_RUN_PATTERN = /\\d[\\d.,:/-]*/g;\nconst HEX_LIKE_PATTERN = /\\b[0-9a-f]{7,}\\b/gi;\nconst SAMPLE_LENGTH = 80;\n\nexport function normalizeTurnText(text: string): string {\n\treturn text\n\t\t.toLowerCase()\n\t\t.replace(HEX_LIKE_PATTERN, \"#\")\n\t\t.replace(DIGIT_RUN_PATTERN, \"#\")\n\t\t.split(/\\s+/)\n\t\t.filter((token) => token.length > 0)\n\t\t.join(\" \")\n\t\t.trim();\n}\n\nfunction wordTrigrams(normalized: string): Set<string> {\n\tconst words = normalized.match(WORD_PATTERN) ?? [];\n\tconst grams = new Set<string>();\n\tfor (let i = 0; i + 3 <= words.length; i++) {\n\t\tgrams.add(`${words[i]} ${words[i + 1]} ${words[i + 2]}`);\n\t}\n\treturn grams;\n}\n\nexport function trigramJaccard(a: string, b: string): number {\n\tconst gramsA = wordTrigrams(a);\n\tconst gramsB = wordTrigrams(b);\n\tif (gramsA.size === 0 || gramsB.size === 0) return 0;\n\tlet intersection = 0;\n\tfor (const gram of gramsA) {\n\t\tif (gramsB.has(gram)) intersection += 1;\n\t}\n\treturn intersection / (gramsA.size + gramsB.size - intersection);\n}\n\nexport function isNearDuplicateOfPreviousTurn(normalizedCandidate: string, normalizedPrevious: string): boolean {\n\treturn trigramJaccard(normalizedCandidate, normalizedPrevious) >= REPETITIVE_TURNS_SIMILARITY_THRESHOLD;\n}\n\ninterface TurnEntry {\n\treadonly normalized: string;\n\treadonly grams: Set<string>;\n}\n\nexport interface RepetitiveTurnsState {\n\treadonly history: TurnEntry[];\n\tstreak: number;\n\tlatched: boolean;\n}\n\nexport function createRepetitiveTurnsState(): RepetitiveTurnsState {\n\treturn { history: [], streak: 0, latched: false };\n}\n\nfunction jaccardSets(a: Set<string>, b: Set<string>): number {\n\tif (a.size === 0 || b.size === 0) return 0;\n\tlet intersection = 0;\n\tfor (const gram of a) {\n\t\tif (b.has(gram)) intersection += 1;\n\t}\n\treturn intersection / (a.size + b.size - intersection);\n}\n\nexport function recordTurnText(state: RepetitiveTurnsState, text: string): DetectorMatch | null {\n\tconst normalized = normalizeTurnText(text);\n\tif (normalized.length < REPETITIVE_TURNS_MIN_NORMALIZED_CHARS) {\n\t\treturn null;\n\t}\n\tconst grams = wordTrigrams(normalized);\n\tconst previous = state.history[state.history.length - 1];\n\tconst similarity = previous === undefined ? 0 : jaccardSets(grams, previous.grams);\n\tstate.history.push({ normalized, grams });\n\tif (state.history.length > REPETITIVE_TURNS_HISTORY_CAPACITY) {\n\t\tstate.history.shift();\n\t}\n\tif (similarity >= REPETITIVE_TURNS_SIMILARITY_THRESHOLD) {\n\t\tstate.streak += 1;\n\t} else {\n\t\tstate.streak = 0;\n\t\tif (state.latched) state.latched = false;\n\t}\n\tif (state.latched || state.streak < REPETITIVE_TURNS_STREAK_LENGTH - 1) {\n\t\treturn null;\n\t}\n\tstate.latched = true;\n\treturn {\n\t\trule: REPETITIVE_TURNS_RULE_NAME,\n\t\treason: `assistant repeated a near-identical message across ${state.streak + 1} consecutive turns (jaccard ${similarity.toFixed(2)})`,\n\t\tanomalyStartOffset: 0,\n\t\tgarbageStartOffset: 0,\n\t\tdetail: {\n\t\t\tmechanism: \"cross-turn\",\n\t\t\tstreak: state.streak + 1,\n\t\t\tsimilarity: Number(similarity.toFixed(3)),\n\t\t\tsample: normalized.slice(0, SAMPLE_LENGTH),\n\t\t},\n\t};\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/ttsr/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAwC,MAAM,gBAAgB,CAAC;AAoDzF,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA6L5D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/ttsr/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAwC,MAAM,gBAAgB,CAAC;AAkDzF,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA2N5D"}
@@ -1,12 +1,16 @@
1
1
  import { getKeybindings } from "@earendil-works/pi-tui";
2
+ import { appendRuleActivation, registerRuleActivationRenderer } from "../rule-activation/index.js";
2
3
  import { BUILTIN_TTSR_RULES } from "./builtin-rules.js";
3
4
  import { registerTtsrCommands } from "./commands.js";
4
5
  import { claimAbort, createGenerationState, markUserCancelled } from "./coordinator.js";
6
+ import { REPETITIVE_TURNS_RULE_NAME } from "./detectors/repetitive-turns.js";
5
7
  import { discoverTtsrRulesSync } from "./discovery.js";
6
8
  import { TtsrManager } from "./manager.js";
7
- import { COLLAPSE_RULE_CONTENT } from "./prompts.js";
8
- import { buildErrorShellReplacement, buildNudgeMessage, buildTruncateReplacement, } from "./remediation.js";
9
+ import { REPETITIVE_TURNS_RULE_CONTENT } from "./prompts.js";
10
+ import { buildNudgeMessage } from "./remediation.js";
11
+ import { collectAssistantText, RepetitiveTurnsLane, readPersistedAssistantTexts } from "./repetitive-turns-lane.js";
9
12
  import { compileRuleCondition } from "./rule-condition.js";
13
+ import { buildStreamRemediation } from "./stream-remediation.js";
10
14
  import { DEFAULT_TTSR_SETTINGS, TTSR_INJECTION_CUSTOM_TYPE, } from "./types.js";
11
15
  import { StreamWatcher } from "./watch.js";
12
16
  const INTERRUPT_KEYBINDING = "app.interrupt";
@@ -27,6 +31,7 @@ function parseDisabledRules(raw) {
27
31
  : [];
28
32
  }
29
33
  export default function ttsrExtension(pi) {
34
+ registerRuleActivationRenderer(pi);
30
35
  pi.registerFlag("ttsr-disabled", {
31
36
  type: "boolean",
32
37
  default: false,
@@ -45,12 +50,14 @@ export default function ttsrExtension(pi) {
45
50
  let pendingRuleNudge = null;
46
51
  let pendingNudge = null;
47
52
  let disabled = false;
53
+ const repetitiveTurns = new RepetitiveTurnsLane();
48
54
  function cancelRemediation() {
49
55
  if (pendingRemediation !== null || pendingNudge !== null || pendingRuleNudge !== null) {
50
56
  markUserCancelled(genState);
51
57
  pendingRemediation = null;
52
58
  pendingRuleNudge = null;
53
59
  pendingNudge = null;
60
+ repetitiveTurns.disarm();
54
61
  }
55
62
  }
56
63
  function recordInjection(owner, observed, retryMode) {
@@ -60,6 +67,12 @@ export default function ttsrExtension(pi) {
60
67
  remediation: retryMode,
61
68
  at: Date.now(),
62
69
  });
70
+ appendRuleActivation(pi, {
71
+ kind: "ttsr",
72
+ owner,
73
+ rules: observed,
74
+ remediation: retryMode,
75
+ });
63
76
  }
64
77
  function notify(ctx, owner) {
65
78
  if (ctx.mode !== "tui")
@@ -76,6 +89,7 @@ export default function ttsrExtension(pi) {
76
89
  return;
77
90
  disabled = pi.getFlag("ttsr-disabled") === true;
78
91
  const disabledRules = parseDisabledRules(pi.getFlag("ttsr-rules-disabled"));
92
+ repetitiveTurns.configure(new Set(disabledRules));
79
93
  const settings = { ...DEFAULT_TTSR_SETTINGS, enabled: !disabled, disabledRules };
80
94
  manager = new TtsrManager(settings, (pattern) => compileRuleCondition(pattern).regex);
81
95
  const injectedNames = ctx.sessionManager
@@ -97,6 +111,7 @@ export default function ttsrExtension(pi) {
97
111
  manager.addRule(rule);
98
112
  }
99
113
  watcher = new StreamWatcher(manager, disabledRules);
114
+ repetitiveTurns.restoreFromHistory(readPersistedAssistantTexts(ctx));
100
115
  if (ctx.mode === "tui") {
101
116
  try {
102
117
  ctx.ui.onTerminalInput((data) => {
@@ -132,6 +147,7 @@ export default function ttsrExtension(pi) {
132
147
  genState = createGenerationState();
133
148
  pendingRemediation = null;
134
149
  pendingRuleNudge = null;
150
+ repetitiveTurns.resetTurn();
135
151
  watcher?.reset();
136
152
  });
137
153
  pi.on("turn_end", () => {
@@ -153,6 +169,17 @@ export default function ttsrExtension(pi) {
153
169
  ctx.abort();
154
170
  return;
155
171
  }
172
+ if (source === "text") {
173
+ const canArm = pendingRuleNudge === null && !genState.abortClaimed;
174
+ if (repetitiveTurns.observeTextDelta(deltaEvent.delta, canArm) && !genState.abortClaimed) {
175
+ genState.abortClaimed = true;
176
+ genState.abortOwner = "collapse-repetition";
177
+ genState.selfAbortAt = Date.now();
178
+ notify(ctx, REPETITIVE_TURNS_RULE_NAME);
179
+ ctx.abort();
180
+ return;
181
+ }
182
+ }
156
183
  const interrupting = outcome.ruleMatches.filter((rule) => rule.interruptMode === "always");
157
184
  const rule = interrupting[0];
158
185
  if (rule !== undefined && pendingRuleNudge === null && !genState.abortClaimed) {
@@ -169,6 +196,14 @@ export default function ttsrExtension(pi) {
169
196
  return undefined;
170
197
  if (event.message.role !== "assistant")
171
198
  return undefined;
199
+ const turnText = collectAssistantText(event.message);
200
+ if (repetitiveTurns.armed) {
201
+ repetitiveTurns.commitArmedTurn(turnText);
202
+ manager?.markInjectedByNames([REPETITIVE_TURNS_RULE_NAME]);
203
+ recordInjection(REPETITIVE_TURNS_RULE_NAME, [REPETITIVE_TURNS_RULE_NAME], "nudge");
204
+ pendingNudge = buildNudgeMessage(REPETITIVE_TURNS_RULE_NAME, REPETITIVE_TURNS_RULE_CONTENT);
205
+ return undefined;
206
+ }
172
207
  if (pendingRuleNudge !== null) {
173
208
  const pending = pendingRuleNudge;
174
209
  pendingRuleNudge = null;
@@ -177,27 +212,31 @@ export default function ttsrExtension(pi) {
177
212
  pendingNudge = buildNudgeMessage(pending.rule.name, pending.rule.content);
178
213
  return undefined;
179
214
  }
180
- if (pendingRemediation === null)
181
- return undefined;
182
- const pending = pendingRemediation;
183
- pendingRemediation = null;
184
- try {
185
- if (pending.resolution.remediation.corruptionScope === "generation") {
186
- recordInjection(pending.resolution.owner, pending.resolution.observedRules, "provider-error");
187
- return { message: { ...event.message, ...buildErrorShellReplacement() } };
215
+ if (pendingRemediation !== null) {
216
+ if (turnText !== null)
217
+ repetitiveTurns.recordCompletedTurn(turnText);
218
+ const pending = pendingRemediation;
219
+ pendingRemediation = null;
220
+ try {
221
+ const outcome = buildStreamRemediation(pending, event.message);
222
+ recordInjection(outcome.owner, outcome.observedRules, outcome.retryMode);
223
+ if (outcome.nudge !== null) {
224
+ pendingNudge = outcome.nudge;
225
+ }
226
+ const merged = { ...event.message, ...outcome.replacement };
227
+ return { message: merged };
228
+ }
229
+ catch (error) {
230
+ pi.appendEntry("ttsr-remediation-error", {
231
+ message: error instanceof Error ? error.message : String(error),
232
+ at: Date.now(),
233
+ });
234
+ return undefined;
188
235
  }
189
- const replaced = buildTruncateReplacement(event.message, pending.resolution.match.garbageStartOffset, pending.streamKind);
190
- recordInjection(pending.resolution.owner, pending.resolution.observedRules, "nudge");
191
- pendingNudge = buildNudgeMessage(pending.resolution.owner, COLLAPSE_RULE_CONTENT);
192
- return { message: replaced };
193
- }
194
- catch (error) {
195
- pi.appendEntry("ttsr-remediation-error", {
196
- message: error instanceof Error ? error.message : String(error),
197
- at: Date.now(),
198
- });
199
- return undefined;
200
236
  }
237
+ if (turnText !== null)
238
+ repetitiveTurns.recordCompletedTurn(turnText);
239
+ return undefined;
201
240
  });
202
241
  pi.on("agent_settled", () => {
203
242
  if (pendingNudge === null || genState.userCancelled) {