@renseiai/agentfactory 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/dist/src/config/index.d.ts +3 -0
  4. package/dist/src/config/index.d.ts.map +1 -0
  5. package/dist/src/config/index.js +1 -0
  6. package/dist/src/config/repository-config.d.ts +44 -0
  7. package/dist/src/config/repository-config.d.ts.map +1 -0
  8. package/dist/src/config/repository-config.js +88 -0
  9. package/dist/src/config/repository-config.test.d.ts +2 -0
  10. package/dist/src/config/repository-config.test.d.ts.map +1 -0
  11. package/dist/src/config/repository-config.test.js +249 -0
  12. package/dist/src/deployment/deployment-checker.d.ts +110 -0
  13. package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
  14. package/dist/src/deployment/deployment-checker.js +242 -0
  15. package/dist/src/deployment/index.d.ts +3 -0
  16. package/dist/src/deployment/index.d.ts.map +1 -0
  17. package/dist/src/deployment/index.js +2 -0
  18. package/dist/src/frontend/index.d.ts +2 -0
  19. package/dist/src/frontend/index.d.ts.map +1 -0
  20. package/dist/src/frontend/index.js +1 -0
  21. package/dist/src/frontend/types.d.ts +106 -0
  22. package/dist/src/frontend/types.d.ts.map +1 -0
  23. package/dist/src/frontend/types.js +11 -0
  24. package/dist/src/governor/decision-engine.d.ts +52 -0
  25. package/dist/src/governor/decision-engine.d.ts.map +1 -0
  26. package/dist/src/governor/decision-engine.js +220 -0
  27. package/dist/src/governor/decision-engine.test.d.ts +2 -0
  28. package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
  29. package/dist/src/governor/decision-engine.test.js +629 -0
  30. package/dist/src/governor/event-bus.d.ts +43 -0
  31. package/dist/src/governor/event-bus.d.ts.map +1 -0
  32. package/dist/src/governor/event-bus.js +8 -0
  33. package/dist/src/governor/event-deduplicator.d.ts +43 -0
  34. package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
  35. package/dist/src/governor/event-deduplicator.js +53 -0
  36. package/dist/src/governor/event-driven-governor.d.ts +131 -0
  37. package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
  38. package/dist/src/governor/event-driven-governor.js +379 -0
  39. package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
  40. package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
  41. package/dist/src/governor/event-driven-governor.test.js +673 -0
  42. package/dist/src/governor/event-types.d.ts +78 -0
  43. package/dist/src/governor/event-types.d.ts.map +1 -0
  44. package/dist/src/governor/event-types.js +32 -0
  45. package/dist/src/governor/governor-types.d.ts +82 -0
  46. package/dist/src/governor/governor-types.d.ts.map +1 -0
  47. package/dist/src/governor/governor-types.js +21 -0
  48. package/dist/src/governor/governor.d.ts +100 -0
  49. package/dist/src/governor/governor.d.ts.map +1 -0
  50. package/dist/src/governor/governor.js +262 -0
  51. package/dist/src/governor/governor.test.d.ts +2 -0
  52. package/dist/src/governor/governor.test.d.ts.map +1 -0
  53. package/dist/src/governor/governor.test.js +514 -0
  54. package/dist/src/governor/human-touchpoints.d.ts +131 -0
  55. package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
  56. package/dist/src/governor/human-touchpoints.js +251 -0
  57. package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
  58. package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
  59. package/dist/src/governor/human-touchpoints.test.js +366 -0
  60. package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
  61. package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
  62. package/dist/src/governor/in-memory-event-bus.js +79 -0
  63. package/dist/src/governor/index.d.ts +14 -0
  64. package/dist/src/governor/index.d.ts.map +1 -0
  65. package/dist/src/governor/index.js +13 -0
  66. package/dist/src/governor/override-parser.d.ts +60 -0
  67. package/dist/src/governor/override-parser.d.ts.map +1 -0
  68. package/dist/src/governor/override-parser.js +98 -0
  69. package/dist/src/governor/override-parser.test.d.ts +2 -0
  70. package/dist/src/governor/override-parser.test.d.ts.map +1 -0
  71. package/dist/src/governor/override-parser.test.js +312 -0
  72. package/dist/src/governor/platform-adapter.d.ts +69 -0
  73. package/dist/src/governor/platform-adapter.d.ts.map +1 -0
  74. package/dist/src/governor/platform-adapter.js +11 -0
  75. package/dist/src/governor/processing-state.d.ts +66 -0
  76. package/dist/src/governor/processing-state.d.ts.map +1 -0
  77. package/dist/src/governor/processing-state.js +43 -0
  78. package/dist/src/governor/processing-state.test.d.ts +2 -0
  79. package/dist/src/governor/processing-state.test.d.ts.map +1 -0
  80. package/dist/src/governor/processing-state.test.js +96 -0
  81. package/dist/src/governor/top-of-funnel.d.ts +118 -0
  82. package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
  83. package/dist/src/governor/top-of-funnel.js +168 -0
  84. package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
  85. package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
  86. package/dist/src/governor/top-of-funnel.test.js +331 -0
  87. package/dist/src/index.d.ts +11 -0
  88. package/dist/src/index.d.ts.map +1 -0
  89. package/dist/src/index.js +10 -0
  90. package/dist/src/linear-cli.d.ts +38 -0
  91. package/dist/src/linear-cli.d.ts.map +1 -0
  92. package/dist/src/linear-cli.js +674 -0
  93. package/dist/src/logger.d.ts +117 -0
  94. package/dist/src/logger.d.ts.map +1 -0
  95. package/dist/src/logger.js +430 -0
  96. package/dist/src/manifest/generate.d.ts +20 -0
  97. package/dist/src/manifest/generate.d.ts.map +1 -0
  98. package/dist/src/manifest/generate.js +65 -0
  99. package/dist/src/manifest/index.d.ts +4 -0
  100. package/dist/src/manifest/index.d.ts.map +1 -0
  101. package/dist/src/manifest/index.js +2 -0
  102. package/dist/src/manifest/route-manifest.d.ts +34 -0
  103. package/dist/src/manifest/route-manifest.d.ts.map +1 -0
  104. package/dist/src/manifest/route-manifest.js +148 -0
  105. package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
  106. package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
  107. package/dist/src/orchestrator/activity-emitter.js +306 -0
  108. package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
  109. package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
  110. package/dist/src/orchestrator/api-activity-emitter.js +417 -0
  111. package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
  112. package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
  113. package/dist/src/orchestrator/heartbeat-writer.js +137 -0
  114. package/dist/src/orchestrator/index.d.ts +20 -0
  115. package/dist/src/orchestrator/index.d.ts.map +1 -0
  116. package/dist/src/orchestrator/index.js +22 -0
  117. package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
  118. package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
  119. package/dist/src/orchestrator/log-analyzer.js +572 -0
  120. package/dist/src/orchestrator/log-config.d.ts +39 -0
  121. package/dist/src/orchestrator/log-config.d.ts.map +1 -0
  122. package/dist/src/orchestrator/log-config.js +45 -0
  123. package/dist/src/orchestrator/orchestrator.d.ts +316 -0
  124. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
  125. package/dist/src/orchestrator/orchestrator.js +3290 -0
  126. package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
  127. package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
  128. package/dist/src/orchestrator/parse-work-result.js +135 -0
  129. package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
  130. package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
  131. package/dist/src/orchestrator/parse-work-result.test.js +234 -0
  132. package/dist/src/orchestrator/progress-logger.d.ts +72 -0
  133. package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
  134. package/dist/src/orchestrator/progress-logger.js +135 -0
  135. package/dist/src/orchestrator/session-logger.d.ts +159 -0
  136. package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
  137. package/dist/src/orchestrator/session-logger.js +275 -0
  138. package/dist/src/orchestrator/state-recovery.d.ts +96 -0
  139. package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
  140. package/dist/src/orchestrator/state-recovery.js +302 -0
  141. package/dist/src/orchestrator/state-types.d.ts +165 -0
  142. package/dist/src/orchestrator/state-types.d.ts.map +1 -0
  143. package/dist/src/orchestrator/state-types.js +7 -0
  144. package/dist/src/orchestrator/stream-parser.d.ts +151 -0
  145. package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
  146. package/dist/src/orchestrator/stream-parser.js +137 -0
  147. package/dist/src/orchestrator/types.d.ts +232 -0
  148. package/dist/src/orchestrator/types.d.ts.map +1 -0
  149. package/dist/src/orchestrator/types.js +4 -0
  150. package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
  151. package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
  152. package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
  153. package/dist/src/providers/a2a-auth.d.ts +81 -0
  154. package/dist/src/providers/a2a-auth.d.ts.map +1 -0
  155. package/dist/src/providers/a2a-auth.js +188 -0
  156. package/dist/src/providers/a2a-auth.test.d.ts +2 -0
  157. package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
  158. package/dist/src/providers/a2a-auth.test.js +232 -0
  159. package/dist/src/providers/a2a-provider.d.ts +254 -0
  160. package/dist/src/providers/a2a-provider.d.ts.map +1 -0
  161. package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
  162. package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
  163. package/dist/src/providers/a2a-provider.integration.test.js +665 -0
  164. package/dist/src/providers/a2a-provider.js +811 -0
  165. package/dist/src/providers/a2a-provider.test.d.ts +2 -0
  166. package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
  167. package/dist/src/providers/a2a-provider.test.js +681 -0
  168. package/dist/src/providers/amp-provider.d.ts +20 -0
  169. package/dist/src/providers/amp-provider.d.ts.map +1 -0
  170. package/dist/src/providers/amp-provider.js +24 -0
  171. package/dist/src/providers/claude-provider.d.ts +18 -0
  172. package/dist/src/providers/claude-provider.d.ts.map +1 -0
  173. package/dist/src/providers/claude-provider.js +437 -0
  174. package/dist/src/providers/codex-provider.d.ts +133 -0
  175. package/dist/src/providers/codex-provider.d.ts.map +1 -0
  176. package/dist/src/providers/codex-provider.js +381 -0
  177. package/dist/src/providers/codex-provider.test.d.ts +2 -0
  178. package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
  179. package/dist/src/providers/codex-provider.test.js +387 -0
  180. package/dist/src/providers/index.d.ts +44 -0
  181. package/dist/src/providers/index.d.ts.map +1 -0
  182. package/dist/src/providers/index.js +85 -0
  183. package/dist/src/providers/spring-ai-provider.d.ts +90 -0
  184. package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
  185. package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
  186. package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
  187. package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
  188. package/dist/src/providers/spring-ai-provider.js +317 -0
  189. package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
  190. package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
  191. package/dist/src/providers/spring-ai-provider.test.js +200 -0
  192. package/dist/src/providers/types.d.ts +165 -0
  193. package/dist/src/providers/types.d.ts.map +1 -0
  194. package/dist/src/providers/types.js +13 -0
  195. package/dist/src/templates/adapters.d.ts +51 -0
  196. package/dist/src/templates/adapters.d.ts.map +1 -0
  197. package/dist/src/templates/adapters.js +104 -0
  198. package/dist/src/templates/adapters.test.d.ts +2 -0
  199. package/dist/src/templates/adapters.test.d.ts.map +1 -0
  200. package/dist/src/templates/adapters.test.js +165 -0
  201. package/dist/src/templates/agent-definition.d.ts +85 -0
  202. package/dist/src/templates/agent-definition.d.ts.map +1 -0
  203. package/dist/src/templates/agent-definition.js +97 -0
  204. package/dist/src/templates/agent-definition.test.d.ts +2 -0
  205. package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
  206. package/dist/src/templates/agent-definition.test.js +209 -0
  207. package/dist/src/templates/index.d.ts +14 -0
  208. package/dist/src/templates/index.d.ts.map +1 -0
  209. package/dist/src/templates/index.js +11 -0
  210. package/dist/src/templates/loader.d.ts +41 -0
  211. package/dist/src/templates/loader.d.ts.map +1 -0
  212. package/dist/src/templates/loader.js +114 -0
  213. package/dist/src/templates/registry.d.ts +80 -0
  214. package/dist/src/templates/registry.d.ts.map +1 -0
  215. package/dist/src/templates/registry.js +177 -0
  216. package/dist/src/templates/registry.test.d.ts +2 -0
  217. package/dist/src/templates/registry.test.d.ts.map +1 -0
  218. package/dist/src/templates/registry.test.js +198 -0
  219. package/dist/src/templates/renderer.d.ts +29 -0
  220. package/dist/src/templates/renderer.d.ts.map +1 -0
  221. package/dist/src/templates/renderer.js +35 -0
  222. package/dist/src/templates/strategy-templates.test.d.ts +2 -0
  223. package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
  224. package/dist/src/templates/strategy-templates.test.js +619 -0
  225. package/dist/src/templates/types.d.ts +233 -0
  226. package/dist/src/templates/types.d.ts.map +1 -0
  227. package/dist/src/templates/types.js +127 -0
  228. package/dist/src/templates/types.test.d.ts +2 -0
  229. package/dist/src/templates/types.test.d.ts.map +1 -0
  230. package/dist/src/templates/types.test.js +232 -0
  231. package/dist/src/tools/index.d.ts +6 -0
  232. package/dist/src/tools/index.d.ts.map +1 -0
  233. package/dist/src/tools/index.js +3 -0
  234. package/dist/src/tools/linear-runner.d.ts +34 -0
  235. package/dist/src/tools/linear-runner.d.ts.map +1 -0
  236. package/dist/src/tools/linear-runner.js +700 -0
  237. package/dist/src/tools/plugins/linear.d.ts +9 -0
  238. package/dist/src/tools/plugins/linear.d.ts.map +1 -0
  239. package/dist/src/tools/plugins/linear.js +138 -0
  240. package/dist/src/tools/registry.d.ts +9 -0
  241. package/dist/src/tools/registry.d.ts.map +1 -0
  242. package/dist/src/tools/registry.js +18 -0
  243. package/dist/src/tools/types.d.ts +18 -0
  244. package/dist/src/tools/types.d.ts.map +1 -0
  245. package/dist/src/tools/types.js +1 -0
  246. package/package.json +78 -0
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Event Deduplicator
3
+ *
4
+ * Prevents duplicate processing of events within a configurable time window.
5
+ * Dedup key is typically `${issueId}:${status}` — same issue at the same
6
+ * status within the window is a duplicate.
7
+ *
8
+ * Two implementations:
9
+ * - InMemoryEventDeduplicator: for testing and single-process CLI
10
+ * - RedisEventDeduplicator: for production (in packages/server)
11
+ */
12
+ /**
13
+ * Deduplicator contract. `isDuplicate` checks and marks atomically.
14
+ */
15
+ export interface EventDeduplicator {
16
+ /**
17
+ * Check if a key has been seen within the dedup window.
18
+ * If not seen, marks it and returns `false` (not a duplicate).
19
+ * If seen, returns `true` (is a duplicate, skip processing).
20
+ */
21
+ isDuplicate(key: string): Promise<boolean>;
22
+ /**
23
+ * Clear all dedup state. Primarily for testing.
24
+ */
25
+ clear(): Promise<void>;
26
+ }
27
+ export interface EventDeduplicatorConfig {
28
+ /** Time window in milliseconds. Events with the same key within this window are considered duplicates. */
29
+ windowMs: number;
30
+ }
31
+ export declare const DEFAULT_DEDUP_CONFIG: EventDeduplicatorConfig;
32
+ export declare class InMemoryEventDeduplicator implements EventDeduplicator {
33
+ private seen;
34
+ private readonly windowMs;
35
+ constructor(config?: Partial<EventDeduplicatorConfig>);
36
+ isDuplicate(key: string): Promise<boolean>;
37
+ clear(): Promise<void>;
38
+ /** Remove expired entries */
39
+ private cleanup;
40
+ /** Get the number of active dedup entries */
41
+ get size(): number;
42
+ }
43
+ //# sourceMappingURL=event-deduplicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-deduplicator.d.ts","sourceRoot":"","sources":["../../../src/governor/event-deduplicator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAE1C;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAMD,MAAM,WAAW,uBAAuB;IACtC,0GAA0G;IAC1G,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,oBAAoB,EAAE,uBAElC,CAAA;AAMD,qBAAa,yBAA0B,YAAW,iBAAiB;IACjE,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,MAAM,GAAE,OAAO,CAAC,uBAAuB,CAAM;IAInD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,6BAA6B;IAC7B,OAAO,CAAC,OAAO;IAUf,6CAA6C;IAC7C,IAAI,IAAI,IAAI,MAAM,CAGjB;CACF"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Event Deduplicator
3
+ *
4
+ * Prevents duplicate processing of events within a configurable time window.
5
+ * Dedup key is typically `${issueId}:${status}` — same issue at the same
6
+ * status within the window is a duplicate.
7
+ *
8
+ * Two implementations:
9
+ * - InMemoryEventDeduplicator: for testing and single-process CLI
10
+ * - RedisEventDeduplicator: for production (in packages/server)
11
+ */
12
+ export const DEFAULT_DEDUP_CONFIG = {
13
+ windowMs: 10_000, // 10 seconds
14
+ };
15
+ // ---------------------------------------------------------------------------
16
+ // In-Memory Implementation
17
+ // ---------------------------------------------------------------------------
18
+ export class InMemoryEventDeduplicator {
19
+ seen = new Map(); // key → expiresAt
20
+ windowMs;
21
+ constructor(config = {}) {
22
+ this.windowMs = config.windowMs ?? DEFAULT_DEDUP_CONFIG.windowMs;
23
+ }
24
+ async isDuplicate(key) {
25
+ const now = Date.now();
26
+ // Clean expired entries on each check (cheap for in-memory)
27
+ this.cleanup(now);
28
+ const expiresAt = this.seen.get(key);
29
+ if (expiresAt !== undefined && now < expiresAt) {
30
+ return true; // duplicate
31
+ }
32
+ // Mark as seen
33
+ this.seen.set(key, now + this.windowMs);
34
+ return false;
35
+ }
36
+ async clear() {
37
+ this.seen.clear();
38
+ }
39
+ /** Remove expired entries */
40
+ cleanup(now) {
41
+ for (const [key, expiresAt] of this.seen) {
42
+ if (now >= expiresAt) {
43
+ this.seen.delete(key);
44
+ }
45
+ }
46
+ }
47
+ // ---- Test helpers ----
48
+ /** Get the number of active dedup entries */
49
+ get size() {
50
+ this.cleanup(Date.now());
51
+ return this.seen.size;
52
+ }
53
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Event-Driven Governor
3
+ *
4
+ * Wraps the existing decision engine with an event-driven processing loop
5
+ * and a periodic poll safety net. This is a hybrid model: real-time events
6
+ * (from webhooks) trigger immediate evaluation, while a configurable poll
7
+ * sweep ensures nothing is missed even if an event is lost.
8
+ *
9
+ * The EventDrivenGovernor does NOT replace WorkflowGovernor. Instead, it
10
+ * provides an alternative execution model that can react to events in
11
+ * real time while still benefiting from periodic full scans.
12
+ */
13
+ import type { GovernorEventBus } from './event-bus.js';
14
+ import type { EventDeduplicator } from './event-deduplicator.js';
15
+ import type { GovernorConfig } from './governor-types.js';
16
+ import type { GovernorDependencies } from './governor.js';
17
+ /**
18
+ * Configuration for the EventDrivenGovernor.
19
+ * Extends the base GovernorConfig with event-bus-specific options.
20
+ */
21
+ export interface EventDrivenGovernorConfig extends GovernorConfig {
22
+ /** The event bus used to receive and publish events */
23
+ eventBus: GovernorEventBus;
24
+ /** Optional deduplicator to prevent processing the same event twice */
25
+ deduplicator?: EventDeduplicator;
26
+ /** Poll interval for the safety-net sweep in milliseconds (default: 300000 = 5 min) */
27
+ pollIntervalMs?: number;
28
+ /** Enable periodic poll sweep (default: true) */
29
+ enablePolling?: boolean;
30
+ }
31
+ /**
32
+ * Hybrid event-driven + poll-based governor.
33
+ *
34
+ * Listens for events on the GovernorEventBus and processes them through the
35
+ * decision engine. Optionally runs a periodic poll sweep that publishes
36
+ * PollSnapshotEvents for every issue, ensuring eventual consistency even
37
+ * if a webhook event is lost.
38
+ *
39
+ * Usage:
40
+ * ```ts
41
+ * const governor = new EventDrivenGovernor(config, deps)
42
+ * await governor.start()
43
+ * // ... governor processes events until stopped
44
+ * await governor.stop()
45
+ * ```
46
+ */
47
+ export declare class EventDrivenGovernor {
48
+ private readonly config;
49
+ private readonly deps;
50
+ private readonly eventBus;
51
+ private readonly deduplicator;
52
+ private running;
53
+ private pollTimer;
54
+ private eventLoopPromise;
55
+ constructor(config: EventDrivenGovernorConfig, deps: GovernorDependencies);
56
+ /**
57
+ * Start the event-driven governor.
58
+ *
59
+ * Begins consuming events from the event bus and optionally starts a
60
+ * periodic poll sweep timer. The event loop runs until `stop()` is called.
61
+ */
62
+ start(): Promise<void>;
63
+ /**
64
+ * Stop the event-driven governor.
65
+ *
66
+ * Clears the poll timer, closes the event bus (which ends the event loop),
67
+ * and waits for the event loop to finish processing.
68
+ */
69
+ stop(): Promise<void>;
70
+ /**
71
+ * Check if the governor is currently running.
72
+ */
73
+ isRunning(): boolean;
74
+ /**
75
+ * Main event processing loop. Consumes events from the event bus,
76
+ * deduplicates them, and routes to the appropriate handler.
77
+ *
78
+ * This runs until the event bus is closed (via `stop()`).
79
+ */
80
+ private runEventLoop;
81
+ /**
82
+ * Process a single event from the event bus.
83
+ *
84
+ * Steps:
85
+ * 1. Check for duplicates using the deduplicator (if configured)
86
+ * 2. Route the event to the appropriate handler based on type
87
+ * 3. Acknowledge the event on the bus
88
+ *
89
+ * @param eventId - Transport-assigned event ID for acknowledgement
90
+ * @param event - The governor event to process
91
+ */
92
+ private processEvent;
93
+ /**
94
+ * Handle a comment-added event by parsing it for override directives.
95
+ *
96
+ * If a directive is found:
97
+ * - `hold`: Persist the hold state via `setOverrideState`
98
+ * - `resume`: Clear override state and re-evaluate the issue immediately
99
+ * - `priority`: Persist the priority override via `setOverrideState`
100
+ * - Other directives: Persist via `setOverrideState`
101
+ *
102
+ * If no directive is found, the issue is still evaluated (the comment
103
+ * may contain context that affects the decision).
104
+ *
105
+ * @param event - The comment-added event to handle
106
+ */
107
+ private handleComment;
108
+ /**
109
+ * Evaluate a single issue and dispatch work if the decision engine
110
+ * determines an action is needed.
111
+ *
112
+ * Gathers all context in parallel (same approach as WorkflowGovernor),
113
+ * runs the decision engine, and dispatches if the action is not 'none'.
114
+ *
115
+ * @param issue - The issue to evaluate
116
+ */
117
+ private evaluateAndDispatch;
118
+ /**
119
+ * Run a full poll sweep across all configured projects.
120
+ *
121
+ * For each project, lists all issues and publishes a PollSnapshotEvent
122
+ * for each one. These events flow through the normal event loop and
123
+ * are subject to deduplication, ensuring that issues already processed
124
+ * via webhook events are not re-evaluated unnecessarily.
125
+ *
126
+ * This method is called periodically by the poll timer and can also
127
+ * be invoked manually for testing.
128
+ */
129
+ pollSweep(): Promise<void>;
130
+ }
131
+ //# sourceMappingURL=event-driven-governor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-driven-governor.d.ts","sourceRoot":"","sources":["../../../src/governor/event-driven-governor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAOhE,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,qBAAqB,CAAA;AAGxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAyBzD;;;GAGG;AACH,MAAM,WAAW,yBAA0B,SAAQ,cAAc;IAC/D,uDAAuD;IACvD,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,uEAAuE;IACvE,YAAY,CAAC,EAAE,iBAAiB,CAAA;IAChC,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iDAAiD;IACjD,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAMD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAsB;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0B;IACvD,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,gBAAgB,CAA6B;gBAEzC,MAAM,EAAE,yBAAyB,EAAE,IAAI,EAAE,oBAAoB;IAWzE;;;;;OAKG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B5B;;;;;OAKG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B3B;;OAEG;IACH,SAAS,IAAI,OAAO;IAQpB;;;;;OAKG;YACW,YAAY;IAgC1B;;;;;;;;;;OAUG;YACW,YAAY;IA4C1B;;;;;;;;;;;;;OAaG;YACW,aAAa;IAqD3B;;;;;;;;OAQG;YACW,mBAAmB;IA4DjC;;;;;;;;;;OAUG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CA2CjC"}
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Event-Driven Governor
3
+ *
4
+ * Wraps the existing decision engine with an event-driven processing loop
5
+ * and a periodic poll safety net. This is a hybrid model: real-time events
6
+ * (from webhooks) trigger immediate evaluation, while a configurable poll
7
+ * sweep ensures nothing is missed even if an event is lost.
8
+ *
9
+ * The EventDrivenGovernor does NOT replace WorkflowGovernor. Instead, it
10
+ * provides an alternative execution model that can react to events in
11
+ * real time while still benefiting from periodic full scans.
12
+ */
13
+ import { eventDedupKey, eventTimestamp } from './event-types.js';
14
+ import { DEFAULT_GOVERNOR_CONFIG } from './governor-types.js';
15
+ import { decideAction } from './decision-engine.js';
16
+ import { parseOverrideDirective } from './override-parser.js';
17
+ import { setOverrideState, clearOverrideState } from './human-touchpoints.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Logging
20
+ // ---------------------------------------------------------------------------
21
+ const log = {
22
+ info: (msg, data) => console.log(`[event-governor] ${msg}`, data ? JSON.stringify(data) : ''),
23
+ warn: (msg, data) => console.warn(`[event-governor] ${msg}`, data ? JSON.stringify(data) : ''),
24
+ error: (msg, data) => console.error(`[event-governor] ${msg}`, data ? JSON.stringify(data) : ''),
25
+ debug: (_msg, _data) => { },
26
+ };
27
+ // ---------------------------------------------------------------------------
28
+ // Configuration
29
+ // ---------------------------------------------------------------------------
30
+ /** Default poll interval for the safety-net sweep (5 minutes) */
31
+ const DEFAULT_POLL_INTERVAL_MS = 300_000;
32
+ // ---------------------------------------------------------------------------
33
+ // EventDrivenGovernor
34
+ // ---------------------------------------------------------------------------
35
+ /**
36
+ * Hybrid event-driven + poll-based governor.
37
+ *
38
+ * Listens for events on the GovernorEventBus and processes them through the
39
+ * decision engine. Optionally runs a periodic poll sweep that publishes
40
+ * PollSnapshotEvents for every issue, ensuring eventual consistency even
41
+ * if a webhook event is lost.
42
+ *
43
+ * Usage:
44
+ * ```ts
45
+ * const governor = new EventDrivenGovernor(config, deps)
46
+ * await governor.start()
47
+ * // ... governor processes events until stopped
48
+ * await governor.stop()
49
+ * ```
50
+ */
51
+ export class EventDrivenGovernor {
52
+ config;
53
+ deps;
54
+ eventBus;
55
+ deduplicator;
56
+ running = false;
57
+ pollTimer = null;
58
+ eventLoopPromise = null;
59
+ constructor(config, deps) {
60
+ this.config = { ...DEFAULT_GOVERNOR_CONFIG, ...config };
61
+ this.deps = deps;
62
+ this.eventBus = config.eventBus;
63
+ this.deduplicator = config.deduplicator ?? null;
64
+ }
65
+ // -------------------------------------------------------------------------
66
+ // Lifecycle
67
+ // -------------------------------------------------------------------------
68
+ /**
69
+ * Start the event-driven governor.
70
+ *
71
+ * Begins consuming events from the event bus and optionally starts a
72
+ * periodic poll sweep timer. The event loop runs until `stop()` is called.
73
+ */
74
+ async start() {
75
+ if (this.running) {
76
+ log.warn('Event-driven governor is already running');
77
+ return;
78
+ }
79
+ this.running = true;
80
+ log.info('Event-driven governor started', {
81
+ projects: this.config.projects,
82
+ enablePolling: this.config.enablePolling !== false,
83
+ pollIntervalMs: this.config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
84
+ hasDeduplicator: this.deduplicator !== null,
85
+ });
86
+ // Start the event processing loop (runs in background)
87
+ this.eventLoopPromise = this.runEventLoop();
88
+ // Start the poll safety net if enabled (default: true)
89
+ if (this.config.enablePolling !== false) {
90
+ const intervalMs = this.config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
91
+ this.pollTimer = setInterval(() => {
92
+ void this.pollSweep();
93
+ }, intervalMs);
94
+ }
95
+ }
96
+ /**
97
+ * Stop the event-driven governor.
98
+ *
99
+ * Clears the poll timer, closes the event bus (which ends the event loop),
100
+ * and waits for the event loop to finish processing.
101
+ */
102
+ async stop() {
103
+ if (!this.running) {
104
+ log.warn('Event-driven governor is not running');
105
+ return;
106
+ }
107
+ this.running = false;
108
+ // Clear the poll timer
109
+ if (this.pollTimer !== null) {
110
+ clearInterval(this.pollTimer);
111
+ this.pollTimer = null;
112
+ }
113
+ // Close the event bus, which will end the subscribe() async iterable
114
+ await this.eventBus.close();
115
+ // Wait for the event loop to finish
116
+ if (this.eventLoopPromise) {
117
+ await this.eventLoopPromise;
118
+ this.eventLoopPromise = null;
119
+ }
120
+ log.info('Event-driven governor stopped');
121
+ }
122
+ /**
123
+ * Check if the governor is currently running.
124
+ */
125
+ isRunning() {
126
+ return this.running;
127
+ }
128
+ // -------------------------------------------------------------------------
129
+ // Event Loop
130
+ // -------------------------------------------------------------------------
131
+ /**
132
+ * Main event processing loop. Consumes events from the event bus,
133
+ * deduplicates them, and routes to the appropriate handler.
134
+ *
135
+ * This runs until the event bus is closed (via `stop()`).
136
+ */
137
+ async runEventLoop() {
138
+ try {
139
+ for await (const { id, event } of this.eventBus.subscribe()) {
140
+ if (!this.running) {
141
+ break;
142
+ }
143
+ try {
144
+ await this.processEvent(id, event);
145
+ }
146
+ catch (err) {
147
+ const errorMsg = err instanceof Error ? err.message : String(err);
148
+ log.error('Error processing event', {
149
+ eventId: id,
150
+ eventType: event.type,
151
+ issueId: event.issueId,
152
+ error: errorMsg,
153
+ });
154
+ }
155
+ }
156
+ }
157
+ catch (err) {
158
+ // The subscribe() iterable may throw if the bus encounters a fatal error
159
+ if (this.running) {
160
+ const errorMsg = err instanceof Error ? err.message : String(err);
161
+ log.error('Event loop terminated unexpectedly', { error: errorMsg });
162
+ }
163
+ }
164
+ }
165
+ // -------------------------------------------------------------------------
166
+ // Event Processing
167
+ // -------------------------------------------------------------------------
168
+ /**
169
+ * Process a single event from the event bus.
170
+ *
171
+ * Steps:
172
+ * 1. Check for duplicates using the deduplicator (if configured)
173
+ * 2. Route the event to the appropriate handler based on type
174
+ * 3. Acknowledge the event on the bus
175
+ *
176
+ * @param eventId - Transport-assigned event ID for acknowledgement
177
+ * @param event - The governor event to process
178
+ */
179
+ async processEvent(eventId, event) {
180
+ // Step 1: Deduplication
181
+ if (this.deduplicator) {
182
+ const dedupKey = eventDedupKey(event);
183
+ const isDuplicate = await this.deduplicator.isDuplicate(dedupKey);
184
+ if (isDuplicate) {
185
+ log.debug('Skipping duplicate event', {
186
+ eventId,
187
+ eventType: event.type,
188
+ issueId: event.issueId,
189
+ dedupKey,
190
+ });
191
+ await this.eventBus.ack(eventId);
192
+ return;
193
+ }
194
+ }
195
+ // Step 2: Route by event type
196
+ log.info('Processing event', {
197
+ eventId,
198
+ eventType: event.type,
199
+ issueId: event.issueId,
200
+ });
201
+ switch (event.type) {
202
+ case 'comment-added':
203
+ await this.handleComment(event);
204
+ break;
205
+ case 'issue-status-changed':
206
+ case 'session-completed':
207
+ case 'poll-snapshot':
208
+ await this.evaluateAndDispatch(event.issue);
209
+ break;
210
+ }
211
+ // Step 3: Acknowledge
212
+ await this.eventBus.ack(eventId);
213
+ }
214
+ // -------------------------------------------------------------------------
215
+ // Comment Handling
216
+ // -------------------------------------------------------------------------
217
+ /**
218
+ * Handle a comment-added event by parsing it for override directives.
219
+ *
220
+ * If a directive is found:
221
+ * - `hold`: Persist the hold state via `setOverrideState`
222
+ * - `resume`: Clear override state and re-evaluate the issue immediately
223
+ * - `priority`: Persist the priority override via `setOverrideState`
224
+ * - Other directives: Persist via `setOverrideState`
225
+ *
226
+ * If no directive is found, the issue is still evaluated (the comment
227
+ * may contain context that affects the decision).
228
+ *
229
+ * @param event - The comment-added event to handle
230
+ */
231
+ async handleComment(event) {
232
+ const commentInfo = {
233
+ id: event.commentId,
234
+ body: event.commentBody,
235
+ userId: event.userId ?? '',
236
+ isBot: false, // Events from the bus are pre-filtered; bot comments are not published
237
+ createdAt: new Date(event.timestamp).getTime(),
238
+ };
239
+ const directive = parseOverrideDirective(commentInfo);
240
+ if (directive) {
241
+ log.info('Override directive found in comment', {
242
+ issueId: event.issueId,
243
+ directiveType: directive.type,
244
+ commentId: event.commentId,
245
+ });
246
+ switch (directive.type) {
247
+ case 'hold':
248
+ await setOverrideState(event.issueId, directive);
249
+ break;
250
+ case 'resume':
251
+ await clearOverrideState(event.issueId);
252
+ // Re-evaluate immediately after resuming
253
+ await this.evaluateAndDispatch(event.issue);
254
+ break;
255
+ case 'priority':
256
+ await setOverrideState(event.issueId, directive);
257
+ break;
258
+ default:
259
+ // skip-qa, decompose, reassign, etc.
260
+ await setOverrideState(event.issueId, directive);
261
+ break;
262
+ }
263
+ }
264
+ else {
265
+ // No directive found, but the comment may contain useful context.
266
+ // Evaluate the issue so the governor can react to any state changes.
267
+ log.debug('No directive in comment, evaluating issue', {
268
+ issueId: event.issueId,
269
+ commentId: event.commentId,
270
+ });
271
+ await this.evaluateAndDispatch(event.issue);
272
+ }
273
+ }
274
+ // -------------------------------------------------------------------------
275
+ // Issue Evaluation
276
+ // -------------------------------------------------------------------------
277
+ /**
278
+ * Evaluate a single issue and dispatch work if the decision engine
279
+ * determines an action is needed.
280
+ *
281
+ * Gathers all context in parallel (same approach as WorkflowGovernor),
282
+ * runs the decision engine, and dispatches if the action is not 'none'.
283
+ *
284
+ * @param issue - The issue to evaluate
285
+ */
286
+ async evaluateAndDispatch(issue) {
287
+ // Gather all context in parallel for efficiency
288
+ const [hasActiveSession, isWithinCooldown, isParentIssue, isHeld, workflowStrategy, researchCompleted, backlogCreationCompleted, completedSessionCount,] = await Promise.all([
289
+ this.deps.hasActiveSession(issue.id),
290
+ this.deps.isWithinCooldown(issue.id),
291
+ this.deps.isParentIssue(issue.id),
292
+ this.deps.isHeld(issue.id),
293
+ this.deps.getWorkflowStrategy(issue.id),
294
+ this.deps.isResearchCompleted(issue.id),
295
+ this.deps.isBacklogCreationCompleted(issue.id),
296
+ this.deps.getCompletedSessionCount(issue.id),
297
+ ]);
298
+ const ctx = {
299
+ issue,
300
+ config: this.config,
301
+ hasActiveSession,
302
+ isHeld,
303
+ isWithinCooldown,
304
+ isParentIssue,
305
+ workflowStrategy,
306
+ researchCompleted,
307
+ backlogCreationCompleted,
308
+ completedSessionCount,
309
+ };
310
+ const decision = decideAction(ctx);
311
+ if (decision.action === 'none') {
312
+ log.debug('No action for issue', {
313
+ issueId: issue.id,
314
+ identifier: issue.identifier,
315
+ reason: decision.reason,
316
+ });
317
+ return;
318
+ }
319
+ log.info('Dispatching action', {
320
+ issueId: issue.id,
321
+ identifier: issue.identifier,
322
+ action: decision.action,
323
+ reason: decision.reason,
324
+ });
325
+ await this.deps.dispatchWork(issue, decision.action);
326
+ }
327
+ // -------------------------------------------------------------------------
328
+ // Poll Sweep
329
+ // -------------------------------------------------------------------------
330
+ /**
331
+ * Run a full poll sweep across all configured projects.
332
+ *
333
+ * For each project, lists all issues and publishes a PollSnapshotEvent
334
+ * for each one. These events flow through the normal event loop and
335
+ * are subject to deduplication, ensuring that issues already processed
336
+ * via webhook events are not re-evaluated unnecessarily.
337
+ *
338
+ * This method is called periodically by the poll timer and can also
339
+ * be invoked manually for testing.
340
+ */
341
+ async pollSweep() {
342
+ log.info('Starting poll sweep', {
343
+ projects: this.config.projects,
344
+ });
345
+ let totalIssues = 0;
346
+ for (const project of this.config.projects) {
347
+ try {
348
+ const issues = await this.deps.listIssues(project);
349
+ totalIssues += issues.length;
350
+ for (const issue of issues) {
351
+ const event = {
352
+ type: 'poll-snapshot',
353
+ issueId: issue.id,
354
+ issue,
355
+ project,
356
+ timestamp: eventTimestamp(),
357
+ source: 'poll',
358
+ };
359
+ await this.eventBus.publish(event);
360
+ }
361
+ log.debug('Poll sweep published events for project', {
362
+ project,
363
+ issueCount: issues.length,
364
+ });
365
+ }
366
+ catch (err) {
367
+ const errorMsg = err instanceof Error ? err.message : String(err);
368
+ log.error('Poll sweep failed for project', {
369
+ project,
370
+ error: errorMsg,
371
+ });
372
+ }
373
+ }
374
+ log.info('Poll sweep complete', {
375
+ projects: this.config.projects,
376
+ totalIssues,
377
+ });
378
+ }
379
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=event-driven-governor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-driven-governor.test.d.ts","sourceRoot":"","sources":["../../../src/governor/event-driven-governor.test.ts"],"names":[],"mappings":""}