@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.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/src/config/index.d.ts +3 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +1 -0
- package/dist/src/config/repository-config.d.ts +44 -0
- package/dist/src/config/repository-config.d.ts.map +1 -0
- package/dist/src/config/repository-config.js +88 -0
- package/dist/src/config/repository-config.test.d.ts +2 -0
- package/dist/src/config/repository-config.test.d.ts.map +1 -0
- package/dist/src/config/repository-config.test.js +249 -0
- package/dist/src/deployment/deployment-checker.d.ts +110 -0
- package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
- package/dist/src/deployment/deployment-checker.js +242 -0
- package/dist/src/deployment/index.d.ts +3 -0
- package/dist/src/deployment/index.d.ts.map +1 -0
- package/dist/src/deployment/index.js +2 -0
- package/dist/src/frontend/index.d.ts +2 -0
- package/dist/src/frontend/index.d.ts.map +1 -0
- package/dist/src/frontend/index.js +1 -0
- package/dist/src/frontend/types.d.ts +106 -0
- package/dist/src/frontend/types.d.ts.map +1 -0
- package/dist/src/frontend/types.js +11 -0
- package/dist/src/governor/decision-engine.d.ts +52 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.js +220 -0
- package/dist/src/governor/decision-engine.test.d.ts +2 -0
- package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.test.js +629 -0
- package/dist/src/governor/event-bus.d.ts +43 -0
- package/dist/src/governor/event-bus.d.ts.map +1 -0
- package/dist/src/governor/event-bus.js +8 -0
- package/dist/src/governor/event-deduplicator.d.ts +43 -0
- package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
- package/dist/src/governor/event-deduplicator.js +53 -0
- package/dist/src/governor/event-driven-governor.d.ts +131 -0
- package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.js +379 -0
- package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
- package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.test.js +673 -0
- package/dist/src/governor/event-types.d.ts +78 -0
- package/dist/src/governor/event-types.d.ts.map +1 -0
- package/dist/src/governor/event-types.js +32 -0
- package/dist/src/governor/governor-types.d.ts +82 -0
- package/dist/src/governor/governor-types.d.ts.map +1 -0
- package/dist/src/governor/governor-types.js +21 -0
- package/dist/src/governor/governor.d.ts +100 -0
- package/dist/src/governor/governor.d.ts.map +1 -0
- package/dist/src/governor/governor.js +262 -0
- package/dist/src/governor/governor.test.d.ts +2 -0
- package/dist/src/governor/governor.test.d.ts.map +1 -0
- package/dist/src/governor/governor.test.js +514 -0
- package/dist/src/governor/human-touchpoints.d.ts +131 -0
- package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.js +251 -0
- package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
- package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.test.js +366 -0
- package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
- package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
- package/dist/src/governor/in-memory-event-bus.js +79 -0
- package/dist/src/governor/index.d.ts +14 -0
- package/dist/src/governor/index.d.ts.map +1 -0
- package/dist/src/governor/index.js +13 -0
- package/dist/src/governor/override-parser.d.ts +60 -0
- package/dist/src/governor/override-parser.d.ts.map +1 -0
- package/dist/src/governor/override-parser.js +98 -0
- package/dist/src/governor/override-parser.test.d.ts +2 -0
- package/dist/src/governor/override-parser.test.d.ts.map +1 -0
- package/dist/src/governor/override-parser.test.js +312 -0
- package/dist/src/governor/platform-adapter.d.ts +69 -0
- package/dist/src/governor/platform-adapter.d.ts.map +1 -0
- package/dist/src/governor/platform-adapter.js +11 -0
- package/dist/src/governor/processing-state.d.ts +66 -0
- package/dist/src/governor/processing-state.d.ts.map +1 -0
- package/dist/src/governor/processing-state.js +43 -0
- package/dist/src/governor/processing-state.test.d.ts +2 -0
- package/dist/src/governor/processing-state.test.d.ts.map +1 -0
- package/dist/src/governor/processing-state.test.js +96 -0
- package/dist/src/governor/top-of-funnel.d.ts +118 -0
- package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.js +168 -0
- package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
- package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.test.js +331 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/linear-cli.d.ts +38 -0
- package/dist/src/linear-cli.d.ts.map +1 -0
- package/dist/src/linear-cli.js +674 -0
- package/dist/src/logger.d.ts +117 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +430 -0
- package/dist/src/manifest/generate.d.ts +20 -0
- package/dist/src/manifest/generate.d.ts.map +1 -0
- package/dist/src/manifest/generate.js +65 -0
- package/dist/src/manifest/index.d.ts +4 -0
- package/dist/src/manifest/index.d.ts.map +1 -0
- package/dist/src/manifest/index.js +2 -0
- package/dist/src/manifest/route-manifest.d.ts +34 -0
- package/dist/src/manifest/route-manifest.d.ts.map +1 -0
- package/dist/src/manifest/route-manifest.js +148 -0
- package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
- package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/activity-emitter.js +306 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/api-activity-emitter.js +417 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
- package/dist/src/orchestrator/heartbeat-writer.js +137 -0
- package/dist/src/orchestrator/index.d.ts +20 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -0
- package/dist/src/orchestrator/index.js +22 -0
- package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
- package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
- package/dist/src/orchestrator/log-analyzer.js +572 -0
- package/dist/src/orchestrator/log-config.d.ts +39 -0
- package/dist/src/orchestrator/log-config.d.ts.map +1 -0
- package/dist/src/orchestrator/log-config.js +45 -0
- package/dist/src/orchestrator/orchestrator.d.ts +316 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
- package/dist/src/orchestrator/orchestrator.js +3290 -0
- package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.js +135 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.test.js +234 -0
- package/dist/src/orchestrator/progress-logger.d.ts +72 -0
- package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/progress-logger.js +135 -0
- package/dist/src/orchestrator/session-logger.d.ts +159 -0
- package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/session-logger.js +275 -0
- package/dist/src/orchestrator/state-recovery.d.ts +96 -0
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
- package/dist/src/orchestrator/state-recovery.js +302 -0
- package/dist/src/orchestrator/state-types.d.ts +165 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -0
- package/dist/src/orchestrator/state-types.js +7 -0
- package/dist/src/orchestrator/stream-parser.d.ts +151 -0
- package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
- package/dist/src/orchestrator/stream-parser.js +137 -0
- package/dist/src/orchestrator/types.d.ts +232 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -0
- package/dist/src/orchestrator/types.js +4 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
- package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
- package/dist/src/providers/a2a-auth.d.ts +81 -0
- package/dist/src/providers/a2a-auth.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.js +188 -0
- package/dist/src/providers/a2a-auth.test.d.ts +2 -0
- package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.test.js +232 -0
- package/dist/src/providers/a2a-provider.d.ts +254 -0
- package/dist/src/providers/a2a-provider.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.js +665 -0
- package/dist/src/providers/a2a-provider.js +811 -0
- package/dist/src/providers/a2a-provider.test.d.ts +2 -0
- package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.test.js +681 -0
- package/dist/src/providers/amp-provider.d.ts +20 -0
- package/dist/src/providers/amp-provider.d.ts.map +1 -0
- package/dist/src/providers/amp-provider.js +24 -0
- package/dist/src/providers/claude-provider.d.ts +18 -0
- package/dist/src/providers/claude-provider.d.ts.map +1 -0
- package/dist/src/providers/claude-provider.js +437 -0
- package/dist/src/providers/codex-provider.d.ts +133 -0
- package/dist/src/providers/codex-provider.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.js +381 -0
- package/dist/src/providers/codex-provider.test.d.ts +2 -0
- package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.test.js +387 -0
- package/dist/src/providers/index.d.ts +44 -0
- package/dist/src/providers/index.d.ts.map +1 -0
- package/dist/src/providers/index.js +85 -0
- package/dist/src/providers/spring-ai-provider.d.ts +90 -0
- package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
- package/dist/src/providers/spring-ai-provider.js +317 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.test.js +200 -0
- package/dist/src/providers/types.d.ts +165 -0
- package/dist/src/providers/types.d.ts.map +1 -0
- package/dist/src/providers/types.js +13 -0
- package/dist/src/templates/adapters.d.ts +51 -0
- package/dist/src/templates/adapters.d.ts.map +1 -0
- package/dist/src/templates/adapters.js +104 -0
- package/dist/src/templates/adapters.test.d.ts +2 -0
- package/dist/src/templates/adapters.test.d.ts.map +1 -0
- package/dist/src/templates/adapters.test.js +165 -0
- package/dist/src/templates/agent-definition.d.ts +85 -0
- package/dist/src/templates/agent-definition.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.js +97 -0
- package/dist/src/templates/agent-definition.test.d.ts +2 -0
- package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.test.js +209 -0
- package/dist/src/templates/index.d.ts +14 -0
- package/dist/src/templates/index.d.ts.map +1 -0
- package/dist/src/templates/index.js +11 -0
- package/dist/src/templates/loader.d.ts +41 -0
- package/dist/src/templates/loader.d.ts.map +1 -0
- package/dist/src/templates/loader.js +114 -0
- package/dist/src/templates/registry.d.ts +80 -0
- package/dist/src/templates/registry.d.ts.map +1 -0
- package/dist/src/templates/registry.js +177 -0
- package/dist/src/templates/registry.test.d.ts +2 -0
- package/dist/src/templates/registry.test.d.ts.map +1 -0
- package/dist/src/templates/registry.test.js +198 -0
- package/dist/src/templates/renderer.d.ts +29 -0
- package/dist/src/templates/renderer.d.ts.map +1 -0
- package/dist/src/templates/renderer.js +35 -0
- package/dist/src/templates/strategy-templates.test.d.ts +2 -0
- package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
- package/dist/src/templates/strategy-templates.test.js +619 -0
- package/dist/src/templates/types.d.ts +233 -0
- package/dist/src/templates/types.d.ts.map +1 -0
- package/dist/src/templates/types.js +127 -0
- package/dist/src/templates/types.test.d.ts +2 -0
- package/dist/src/templates/types.test.d.ts.map +1 -0
- package/dist/src/templates/types.test.js +232 -0
- package/dist/src/tools/index.d.ts +6 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +3 -0
- package/dist/src/tools/linear-runner.d.ts +34 -0
- package/dist/src/tools/linear-runner.d.ts.map +1 -0
- package/dist/src/tools/linear-runner.js +700 -0
- package/dist/src/tools/plugins/linear.d.ts +9 -0
- package/dist/src/tools/plugins/linear.d.ts.map +1 -0
- package/dist/src/tools/plugins/linear.js +138 -0
- package/dist/src/tools/registry.d.ts +9 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +18 -0
- package/dist/src/tools/types.d.ts +18 -0
- package/dist/src/tools/types.d.ts.map +1 -0
- package/dist/src/tools/types.js +1 -0
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"event-driven-governor.test.d.ts","sourceRoot":"","sources":["../../../src/governor/event-driven-governor.test.ts"],"names":[],"mappings":""}
|