@renseiai/agentfactory 0.8.7 → 0.8.9
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/dist/src/config/index.d.ts +1 -1
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +1 -1
- package/dist/src/config/repository-config.d.ts +37 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +47 -0
- package/dist/src/config/repository-config.test.js +140 -1
- package/dist/src/governor/decision-engine.d.ts +3 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -1
- package/dist/src/governor/decision-engine.js +11 -0
- package/dist/src/governor/decision-engine.test.js +33 -0
- package/dist/src/governor/event-types.d.ts +18 -1
- package/dist/src/governor/event-types.d.ts.map +1 -1
- package/dist/src/governor/event-types.js +4 -0
- package/dist/src/governor/governor-types.d.ts +1 -1
- package/dist/src/governor/governor-types.d.ts.map +1 -1
- package/dist/src/governor/governor.d.ts +17 -1
- package/dist/src/governor/governor.d.ts.map +1 -1
- package/dist/src/governor/governor.js +112 -1
- package/dist/src/governor/governor.test.js +155 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/merge-queue/adapters/github-native.d.ts +22 -0
- package/dist/src/merge-queue/adapters/github-native.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/github-native.js +243 -0
- package/dist/src/merge-queue/adapters/github-native.test.d.ts +2 -0
- package/dist/src/merge-queue/adapters/github-native.test.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/github-native.test.js +384 -0
- package/dist/src/merge-queue/index.d.ts +18 -0
- package/dist/src/merge-queue/index.d.ts.map +1 -0
- package/dist/src/merge-queue/index.js +28 -0
- package/dist/src/merge-queue/merge-queue.integration.test.d.ts +2 -0
- package/dist/src/merge-queue/merge-queue.integration.test.d.ts.map +1 -0
- package/dist/src/merge-queue/merge-queue.integration.test.js +128 -0
- package/dist/src/merge-queue/types.d.ts +48 -0
- package/dist/src/merge-queue/types.d.ts.map +1 -0
- package/dist/src/merge-queue/types.js +8 -0
- package/dist/src/orchestrator/artifact-tracker.d.ts +93 -0
- package/dist/src/orchestrator/artifact-tracker.d.ts.map +1 -0
- package/dist/src/orchestrator/artifact-tracker.js +235 -0
- package/dist/src/orchestrator/artifact-tracker.test.d.ts +2 -0
- package/dist/src/orchestrator/artifact-tracker.test.d.ts.map +1 -0
- package/dist/src/orchestrator/artifact-tracker.test.js +189 -0
- package/dist/src/orchestrator/context-manager.d.ts +72 -0
- package/dist/src/orchestrator/context-manager.d.ts.map +1 -0
- package/dist/src/orchestrator/context-manager.js +120 -0
- package/dist/src/orchestrator/context-manager.test.d.ts +2 -0
- package/dist/src/orchestrator/context-manager.test.d.ts.map +1 -0
- package/dist/src/orchestrator/context-manager.test.js +137 -0
- package/dist/src/orchestrator/index.d.ts +8 -2
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +8 -1
- package/dist/src/orchestrator/issue-tracker-client.d.ts +4 -0
- package/dist/src/orchestrator/issue-tracker-client.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.d.ts +12 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +282 -2
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
- package/dist/src/orchestrator/parse-work-result.js +6 -0
- package/dist/src/orchestrator/parse-work-result.test.js +19 -0
- package/dist/src/orchestrator/state-recovery.d.ts +21 -2
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -1
- package/dist/src/orchestrator/state-recovery.js +54 -2
- package/dist/src/orchestrator/state-recovery.test.js +106 -2
- package/dist/src/orchestrator/state-types.d.ts +62 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -1
- package/dist/src/orchestrator/state-types.js +5 -1
- package/dist/src/orchestrator/summary-builder.d.ts +47 -0
- package/dist/src/orchestrator/summary-builder.d.ts.map +1 -0
- package/dist/src/orchestrator/summary-builder.js +240 -0
- package/dist/src/orchestrator/summary-builder.test.d.ts +2 -0
- package/dist/src/orchestrator/summary-builder.test.d.ts.map +1 -0
- package/dist/src/orchestrator/summary-builder.test.js +236 -0
- package/dist/src/orchestrator/types.d.ts +2 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/orchestrator/work-types.d.ts +1 -1
- package/dist/src/orchestrator/work-types.d.ts.map +1 -1
- package/dist/src/providers/index.d.ts +64 -1
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/index.js +132 -1
- package/dist/src/providers/index.test.js +340 -2
- package/dist/src/routing/index.d.ts +7 -0
- package/dist/src/routing/index.d.ts.map +1 -0
- package/dist/src/routing/index.js +6 -0
- package/dist/src/routing/observation-recorder.d.ts +19 -0
- package/dist/src/routing/observation-recorder.d.ts.map +1 -0
- package/dist/src/routing/observation-recorder.js +73 -0
- package/dist/src/routing/observation-recorder.test.d.ts +2 -0
- package/dist/src/routing/observation-recorder.test.d.ts.map +1 -0
- package/dist/src/routing/observation-recorder.test.js +322 -0
- package/dist/src/routing/observation-store.d.ts +40 -0
- package/dist/src/routing/observation-store.d.ts.map +1 -0
- package/dist/src/routing/observation-store.js +1 -0
- package/dist/src/routing/observation-store.test.d.ts +2 -0
- package/dist/src/routing/observation-store.test.d.ts.map +1 -0
- package/dist/src/routing/observation-store.test.js +138 -0
- package/dist/src/routing/posterior-store.d.ts +12 -0
- package/dist/src/routing/posterior-store.d.ts.map +1 -0
- package/dist/src/routing/posterior-store.js +13 -0
- package/dist/src/routing/posterior-store.test.d.ts +2 -0
- package/dist/src/routing/posterior-store.test.d.ts.map +1 -0
- package/dist/src/routing/posterior-store.test.js +37 -0
- package/dist/src/routing/reward.d.ts +16 -0
- package/dist/src/routing/reward.d.ts.map +1 -0
- package/dist/src/routing/reward.js +29 -0
- package/dist/src/routing/reward.test.d.ts +2 -0
- package/dist/src/routing/reward.test.d.ts.map +1 -0
- package/dist/src/routing/reward.test.js +210 -0
- package/dist/src/routing/routing-engine.d.ts +20 -0
- package/dist/src/routing/routing-engine.d.ts.map +1 -0
- package/dist/src/routing/routing-engine.js +113 -0
- package/dist/src/routing/routing-engine.test.d.ts +2 -0
- package/dist/src/routing/routing-engine.test.d.ts.map +1 -0
- package/dist/src/routing/routing-engine.test.js +310 -0
- package/dist/src/routing/types.d.ts +157 -0
- package/dist/src/routing/types.d.ts.map +1 -0
- package/dist/src/routing/types.js +68 -0
- package/dist/src/routing/types.test.d.ts +2 -0
- package/dist/src/routing/types.test.d.ts.map +1 -0
- package/dist/src/routing/types.test.js +184 -0
- package/dist/src/templates/registry.test.js +2 -2
- package/dist/src/templates/types.d.ts +5 -0
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +3 -0
- package/dist/src/workflow/agent-cancellation.d.ts +37 -0
- package/dist/src/workflow/agent-cancellation.d.ts.map +1 -0
- package/dist/src/workflow/agent-cancellation.js +41 -0
- package/dist/src/workflow/agent-cancellation.test.d.ts +2 -0
- package/dist/src/workflow/agent-cancellation.test.d.ts.map +1 -0
- package/dist/src/workflow/agent-cancellation.test.js +86 -0
- package/dist/src/workflow/branching-router.d.ts +38 -0
- package/dist/src/workflow/branching-router.d.ts.map +1 -0
- package/dist/src/workflow/branching-router.js +52 -0
- package/dist/src/workflow/branching-router.test.d.ts +2 -0
- package/dist/src/workflow/branching-router.test.d.ts.map +1 -0
- package/dist/src/workflow/branching-router.test.js +209 -0
- package/dist/src/workflow/concurrency-semaphore.d.ts +21 -0
- package/dist/src/workflow/concurrency-semaphore.d.ts.map +1 -0
- package/dist/src/workflow/concurrency-semaphore.js +46 -0
- package/dist/src/workflow/concurrency-semaphore.test.d.ts +2 -0
- package/dist/src/workflow/concurrency-semaphore.test.d.ts.map +1 -0
- package/dist/src/workflow/concurrency-semaphore.test.js +183 -0
- package/dist/src/workflow/duration.d.ts +28 -0
- package/dist/src/workflow/duration.d.ts.map +1 -0
- package/dist/src/workflow/duration.js +57 -0
- package/dist/src/workflow/duration.test.d.ts +2 -0
- package/dist/src/workflow/duration.test.d.ts.map +1 -0
- package/dist/src/workflow/duration.test.js +74 -0
- package/dist/src/workflow/expression/ast.d.ts +53 -0
- package/dist/src/workflow/expression/ast.d.ts.map +1 -0
- package/dist/src/workflow/expression/ast.js +8 -0
- package/dist/src/workflow/expression/context.d.ts +40 -0
- package/dist/src/workflow/expression/context.d.ts.map +1 -0
- package/dist/src/workflow/expression/context.js +37 -0
- package/dist/src/workflow/expression/evaluator.d.ts +28 -0
- package/dist/src/workflow/expression/evaluator.d.ts.map +1 -0
- package/dist/src/workflow/expression/evaluator.js +165 -0
- package/dist/src/workflow/expression/evaluator.test.d.ts +2 -0
- package/dist/src/workflow/expression/evaluator.test.d.ts.map +1 -0
- package/dist/src/workflow/expression/evaluator.test.js +792 -0
- package/dist/src/workflow/expression/expression.test.d.ts +2 -0
- package/dist/src/workflow/expression/expression.test.d.ts.map +1 -0
- package/dist/src/workflow/expression/expression.test.js +516 -0
- package/dist/src/workflow/expression/helpers.d.ts +21 -0
- package/dist/src/workflow/expression/helpers.d.ts.map +1 -0
- package/dist/src/workflow/expression/helpers.js +56 -0
- package/dist/src/workflow/expression/index.d.ts +55 -0
- package/dist/src/workflow/expression/index.d.ts.map +1 -0
- package/dist/src/workflow/expression/index.js +71 -0
- package/dist/src/workflow/expression/lexer.d.ts +37 -0
- package/dist/src/workflow/expression/lexer.d.ts.map +1 -0
- package/dist/src/workflow/expression/lexer.js +166 -0
- package/dist/src/workflow/expression/parser.d.ts +23 -0
- package/dist/src/workflow/expression/parser.d.ts.map +1 -0
- package/dist/src/workflow/expression/parser.js +181 -0
- package/dist/src/workflow/gate-state.d.ts +115 -0
- package/dist/src/workflow/gate-state.d.ts.map +1 -0
- package/dist/src/workflow/gate-state.js +185 -0
- package/dist/src/workflow/gate-state.test.d.ts +2 -0
- package/dist/src/workflow/gate-state.test.d.ts.map +1 -0
- package/dist/src/workflow/gate-state.test.js +251 -0
- package/dist/src/workflow/gates/gate-evaluator.d.ts +119 -0
- package/dist/src/workflow/gates/gate-evaluator.d.ts.map +1 -0
- package/dist/src/workflow/gates/gate-evaluator.js +243 -0
- package/dist/src/workflow/gates/gate-evaluator.test.d.ts +2 -0
- package/dist/src/workflow/gates/gate-evaluator.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/gate-evaluator.test.js +240 -0
- package/dist/src/workflow/gates/signal-gate.d.ts +114 -0
- package/dist/src/workflow/gates/signal-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/signal-gate.js +216 -0
- package/dist/src/workflow/gates/signal-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/signal-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/signal-gate.test.js +199 -0
- package/dist/src/workflow/gates/timeout-engine.d.ts +96 -0
- package/dist/src/workflow/gates/timeout-engine.d.ts.map +1 -0
- package/dist/src/workflow/gates/timeout-engine.js +162 -0
- package/dist/src/workflow/gates/timeout-engine.test.d.ts +2 -0
- package/dist/src/workflow/gates/timeout-engine.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/timeout-engine.test.js +186 -0
- package/dist/src/workflow/gates/timer-gate.d.ts +125 -0
- package/dist/src/workflow/gates/timer-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/timer-gate.js +381 -0
- package/dist/src/workflow/gates/timer-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/timer-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/timer-gate.test.js +211 -0
- package/dist/src/workflow/gates/webhook-gate.d.ts +132 -0
- package/dist/src/workflow/gates/webhook-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/webhook-gate.js +216 -0
- package/dist/src/workflow/gates/webhook-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/webhook-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/webhook-gate.test.js +182 -0
- package/dist/src/workflow/index.d.ts +31 -3
- package/dist/src/workflow/index.d.ts.map +1 -1
- package/dist/src/workflow/index.js +20 -1
- package/dist/src/workflow/parallelism-executor.d.ts +25 -0
- package/dist/src/workflow/parallelism-executor.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-executor.js +53 -0
- package/dist/src/workflow/parallelism-executor.test.d.ts +2 -0
- package/dist/src/workflow/parallelism-executor.test.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-executor.test.js +191 -0
- package/dist/src/workflow/parallelism-types.d.ts +80 -0
- package/dist/src/workflow/parallelism-types.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-types.js +8 -0
- package/dist/src/workflow/phase-context-injector.d.ts +29 -0
- package/dist/src/workflow/phase-context-injector.d.ts.map +1 -0
- package/dist/src/workflow/phase-context-injector.js +43 -0
- package/dist/src/workflow/phase-context-injector.test.d.ts +2 -0
- package/dist/src/workflow/phase-context-injector.test.d.ts.map +1 -0
- package/dist/src/workflow/phase-context-injector.test.js +123 -0
- package/dist/src/workflow/phase-output-collector.d.ts +39 -0
- package/dist/src/workflow/phase-output-collector.d.ts.map +1 -0
- package/dist/src/workflow/phase-output-collector.js +141 -0
- package/dist/src/workflow/phase-output-collector.test.d.ts +2 -0
- package/dist/src/workflow/phase-output-collector.test.d.ts.map +1 -0
- package/dist/src/workflow/phase-output-collector.test.js +179 -0
- package/dist/src/workflow/retry-resolver.d.ts +51 -0
- package/dist/src/workflow/retry-resolver.d.ts.map +1 -0
- package/dist/src/workflow/retry-resolver.js +70 -0
- package/dist/src/workflow/retry-resolver.test.d.ts +2 -0
- package/dist/src/workflow/retry-resolver.test.d.ts.map +1 -0
- package/dist/src/workflow/retry-resolver.test.js +149 -0
- package/dist/src/workflow/strategies/fan-in-strategy.d.ts +21 -0
- package/dist/src/workflow/strategies/fan-in-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-in-strategy.js +92 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.js +182 -0
- package/dist/src/workflow/strategies/fan-out-strategy.d.ts +16 -0
- package/dist/src/workflow/strategies/fan-out-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-out-strategy.js +47 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.js +97 -0
- package/dist/src/workflow/strategies/index.d.ts +4 -0
- package/dist/src/workflow/strategies/index.d.ts.map +1 -0
- package/dist/src/workflow/strategies/index.js +3 -0
- package/dist/src/workflow/strategies/race-strategy.d.ts +19 -0
- package/dist/src/workflow/strategies/race-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/race-strategy.js +92 -0
- package/dist/src/workflow/strategies/race-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/race-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/race-strategy.test.js +318 -0
- package/dist/src/workflow/transition-engine.d.ts +3 -1
- package/dist/src/workflow/transition-engine.d.ts.map +1 -1
- package/dist/src/workflow/transition-engine.js +26 -7
- package/dist/src/workflow/transition-engine.test.js +215 -11
- package/dist/src/workflow/workflow-registry.d.ts +46 -1
- package/dist/src/workflow/workflow-registry.d.ts.map +1 -1
- package/dist/src/workflow/workflow-registry.js +74 -0
- package/dist/src/workflow/workflow-registry.test.js +54 -0
- package/dist/src/workflow/workflow-types.d.ts +330 -12
- package/dist/src/workflow/workflow-types.d.ts.map +1 -1
- package/dist/src/workflow/workflow-types.js +100 -5
- package/dist/src/workflow/workflow-types.test.js +293 -2
- package/package.json +2 -2
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase Output Collector
|
|
3
|
+
*
|
|
4
|
+
* Extracts structured outputs from agent result text using marker comments.
|
|
5
|
+
* Supports two marker formats:
|
|
6
|
+
* - <!-- PHASE_OUTPUT:key=value --> (string, url, boolean types)
|
|
7
|
+
* - <!-- PHASE_OUTPUT_JSON:key={...} --> (json type)
|
|
8
|
+
*
|
|
9
|
+
* Validates collected outputs against PhaseOutputDeclaration when provided.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Regex to match string-type output markers.
|
|
13
|
+
* Captures: key and value from <!-- PHASE_OUTPUT:key=value -->
|
|
14
|
+
*/
|
|
15
|
+
const STRING_MARKER_RE = /<!--\s*PHASE_OUTPUT:(\w+)=(.*?)\s*-->/g;
|
|
16
|
+
/**
|
|
17
|
+
* Regex to match JSON-type output markers.
|
|
18
|
+
* Captures: key and JSON value from <!-- PHASE_OUTPUT_JSON:key={...} -->
|
|
19
|
+
*/
|
|
20
|
+
const JSON_MARKER_RE = /<!--\s*PHASE_OUTPUT_JSON:(\w+)=([\s\S]*?)\s*-->/g;
|
|
21
|
+
/**
|
|
22
|
+
* Utility class to extract structured outputs from agent result text.
|
|
23
|
+
*/
|
|
24
|
+
export class PhaseOutputCollector {
|
|
25
|
+
/**
|
|
26
|
+
* Collect structured outputs from agent output text.
|
|
27
|
+
*
|
|
28
|
+
* @param agentOutput - The raw text output from an agent
|
|
29
|
+
* @param declarations - Optional output declarations for validation
|
|
30
|
+
* @returns Record of collected output key-value pairs
|
|
31
|
+
* @throws Error if a required output is missing or type validation fails
|
|
32
|
+
*/
|
|
33
|
+
collect(agentOutput, declarations) {
|
|
34
|
+
const outputs = {};
|
|
35
|
+
// Extract string-type markers
|
|
36
|
+
for (const match of agentOutput.matchAll(STRING_MARKER_RE)) {
|
|
37
|
+
const key = match[1];
|
|
38
|
+
const rawValue = match[2];
|
|
39
|
+
outputs[key] = rawValue;
|
|
40
|
+
}
|
|
41
|
+
// Extract JSON-type markers (these override string markers for same key)
|
|
42
|
+
for (const match of agentOutput.matchAll(JSON_MARKER_RE)) {
|
|
43
|
+
const key = match[1];
|
|
44
|
+
const rawJson = match[2];
|
|
45
|
+
try {
|
|
46
|
+
outputs[key] = JSON.parse(rawJson);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// If JSON parsing fails, store as raw string
|
|
50
|
+
outputs[key] = rawJson;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Validate against declarations if provided
|
|
54
|
+
if (declarations) {
|
|
55
|
+
this.validate(outputs, declarations);
|
|
56
|
+
}
|
|
57
|
+
// Coerce types based on declarations
|
|
58
|
+
if (declarations) {
|
|
59
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
60
|
+
const decl = declarations[key];
|
|
61
|
+
if (decl) {
|
|
62
|
+
outputs[key] = this.coerceType(value, decl.type);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return outputs;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate collected outputs against declarations.
|
|
70
|
+
* Checks required fields and type compatibility.
|
|
71
|
+
*/
|
|
72
|
+
validate(outputs, declarations) {
|
|
73
|
+
for (const [key, decl] of Object.entries(declarations)) {
|
|
74
|
+
// Check required outputs
|
|
75
|
+
if (decl.required && !(key in outputs)) {
|
|
76
|
+
throw new Error(`Required phase output "${key}" is missing`);
|
|
77
|
+
}
|
|
78
|
+
// Validate type if value is present
|
|
79
|
+
if (key in outputs) {
|
|
80
|
+
const value = outputs[key];
|
|
81
|
+
this.validateType(key, value, decl.type);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validate that a collected value is compatible with the declared type.
|
|
87
|
+
*/
|
|
88
|
+
validateType(key, value, type) {
|
|
89
|
+
switch (type) {
|
|
90
|
+
case 'string':
|
|
91
|
+
case 'url':
|
|
92
|
+
if (typeof value !== 'string') {
|
|
93
|
+
throw new Error(`Phase output "${key}" expected type "${type}" but got ${typeof value}`);
|
|
94
|
+
}
|
|
95
|
+
if (type === 'url' && typeof value === 'string') {
|
|
96
|
+
// Basic URL validation: must have a protocol-like prefix
|
|
97
|
+
if (!/^https?:\/\/.+/.test(value)) {
|
|
98
|
+
throw new Error(`Phase output "${key}" expected a valid URL but got "${value}"`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
case 'boolean':
|
|
103
|
+
// Booleans may arrive as strings from markers; we accept string booleans
|
|
104
|
+
if (typeof value === 'string') {
|
|
105
|
+
if (value !== 'true' && value !== 'false') {
|
|
106
|
+
throw new Error(`Phase output "${key}" expected boolean but got "${value}"`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (typeof value !== 'boolean') {
|
|
110
|
+
throw new Error(`Phase output "${key}" expected type "boolean" but got ${typeof value}`);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case 'json':
|
|
114
|
+
// JSON type accepts any non-string parsed value, or a string that was kept as-is
|
|
115
|
+
// No additional validation needed — the value was either parsed or kept raw
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Coerce a raw collected value to the declared type.
|
|
121
|
+
*/
|
|
122
|
+
coerceType(value, type) {
|
|
123
|
+
switch (type) {
|
|
124
|
+
case 'boolean':
|
|
125
|
+
if (typeof value === 'string') {
|
|
126
|
+
return value === 'true';
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
case 'string':
|
|
130
|
+
case 'url':
|
|
131
|
+
if (typeof value !== 'string') {
|
|
132
|
+
return String(value);
|
|
133
|
+
}
|
|
134
|
+
return value;
|
|
135
|
+
case 'json':
|
|
136
|
+
return value;
|
|
137
|
+
default:
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase-output-collector.test.d.ts","sourceRoot":"","sources":["../../../src/workflow/phase-output-collector.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { PhaseOutputCollector } from './phase-output-collector.js';
|
|
3
|
+
describe('PhaseOutputCollector', () => {
|
|
4
|
+
const collector = new PhaseOutputCollector();
|
|
5
|
+
describe('string marker parsing', () => {
|
|
6
|
+
it('extracts a single string output', () => {
|
|
7
|
+
const output = 'Some text <!-- PHASE_OUTPUT:prUrl=https://github.com/org/repo/pull/42 --> more text';
|
|
8
|
+
const result = collector.collect(output);
|
|
9
|
+
expect(result).toEqual({
|
|
10
|
+
prUrl: 'https://github.com/org/repo/pull/42',
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
it('extracts multiple string outputs', () => {
|
|
14
|
+
const output = [
|
|
15
|
+
'<!-- PHASE_OUTPUT:prUrl=https://github.com/org/repo/pull/42 -->',
|
|
16
|
+
'<!-- PHASE_OUTPUT:branch=feature/my-branch -->',
|
|
17
|
+
'<!-- PHASE_OUTPUT:status=success -->',
|
|
18
|
+
].join('\n');
|
|
19
|
+
const result = collector.collect(output);
|
|
20
|
+
expect(result).toEqual({
|
|
21
|
+
prUrl: 'https://github.com/org/repo/pull/42',
|
|
22
|
+
branch: 'feature/my-branch',
|
|
23
|
+
status: 'success',
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
it('handles empty value', () => {
|
|
27
|
+
const output = '<!-- PHASE_OUTPUT:empty= -->';
|
|
28
|
+
const result = collector.collect(output);
|
|
29
|
+
expect(result).toEqual({ empty: '' });
|
|
30
|
+
});
|
|
31
|
+
it('handles whitespace in marker', () => {
|
|
32
|
+
const output = '<!-- PHASE_OUTPUT:key=value -->';
|
|
33
|
+
const result = collector.collect(output);
|
|
34
|
+
expect(result).toEqual({ key: 'value' });
|
|
35
|
+
});
|
|
36
|
+
it('uses last value when same key appears multiple times', () => {
|
|
37
|
+
const output = [
|
|
38
|
+
'<!-- PHASE_OUTPUT:key=first -->',
|
|
39
|
+
'<!-- PHASE_OUTPUT:key=second -->',
|
|
40
|
+
].join('\n');
|
|
41
|
+
const result = collector.collect(output);
|
|
42
|
+
expect(result.key).toBe('second');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('JSON marker parsing', () => {
|
|
46
|
+
it('extracts a JSON object output', () => {
|
|
47
|
+
const output = '<!-- PHASE_OUTPUT_JSON:config={"port":3000,"host":"localhost"} -->';
|
|
48
|
+
const result = collector.collect(output);
|
|
49
|
+
expect(result).toEqual({
|
|
50
|
+
config: { port: 3000, host: 'localhost' },
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
it('extracts a JSON array output', () => {
|
|
54
|
+
const output = '<!-- PHASE_OUTPUT_JSON:files=["a.ts","b.ts","c.ts"] -->';
|
|
55
|
+
const result = collector.collect(output);
|
|
56
|
+
expect(result).toEqual({
|
|
57
|
+
files: ['a.ts', 'b.ts', 'c.ts'],
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it('falls back to raw string on invalid JSON', () => {
|
|
61
|
+
const output = '<!-- PHASE_OUTPUT_JSON:bad={not valid json} -->';
|
|
62
|
+
const result = collector.collect(output);
|
|
63
|
+
expect(result.bad).toBe('{not valid json}');
|
|
64
|
+
});
|
|
65
|
+
it('JSON marker overrides string marker for same key', () => {
|
|
66
|
+
const output = [
|
|
67
|
+
'<!-- PHASE_OUTPUT:data=string-value -->',
|
|
68
|
+
'<!-- PHASE_OUTPUT_JSON:data={"complex":true} -->',
|
|
69
|
+
].join('\n');
|
|
70
|
+
const result = collector.collect(output);
|
|
71
|
+
expect(result.data).toEqual({ complex: true });
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('mixed markers', () => {
|
|
75
|
+
it('extracts both string and JSON markers from same output', () => {
|
|
76
|
+
const output = [
|
|
77
|
+
'Agent completed successfully.',
|
|
78
|
+
'<!-- PHASE_OUTPUT:prUrl=https://github.com/org/repo/pull/42 -->',
|
|
79
|
+
'<!-- PHASE_OUTPUT:branch=feat/new-feature -->',
|
|
80
|
+
'<!-- PHASE_OUTPUT_JSON:testResults={"passed":10,"failed":0} -->',
|
|
81
|
+
'Done.',
|
|
82
|
+
].join('\n');
|
|
83
|
+
const result = collector.collect(output);
|
|
84
|
+
expect(result).toEqual({
|
|
85
|
+
prUrl: 'https://github.com/org/repo/pull/42',
|
|
86
|
+
branch: 'feat/new-feature',
|
|
87
|
+
testResults: { passed: 10, failed: 0 },
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
it('returns empty record when no markers present', () => {
|
|
91
|
+
const output = 'Just some plain text with no markers at all.';
|
|
92
|
+
const result = collector.collect(output);
|
|
93
|
+
expect(result).toEqual({});
|
|
94
|
+
});
|
|
95
|
+
it('handles empty input', () => {
|
|
96
|
+
const result = collector.collect('');
|
|
97
|
+
expect(result).toEqual({});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('validation with declarations', () => {
|
|
101
|
+
it('passes validation for correct types', () => {
|
|
102
|
+
const declarations = {
|
|
103
|
+
prUrl: { type: 'url', required: true },
|
|
104
|
+
message: { type: 'string' },
|
|
105
|
+
};
|
|
106
|
+
const output = [
|
|
107
|
+
'<!-- PHASE_OUTPUT:prUrl=https://github.com/org/repo/pull/42 -->',
|
|
108
|
+
'<!-- PHASE_OUTPUT:message=All tests passed -->',
|
|
109
|
+
].join('\n');
|
|
110
|
+
const result = collector.collect(output, declarations);
|
|
111
|
+
expect(result.prUrl).toBe('https://github.com/org/repo/pull/42');
|
|
112
|
+
expect(result.message).toBe('All tests passed');
|
|
113
|
+
});
|
|
114
|
+
it('throws on missing required output', () => {
|
|
115
|
+
const declarations = {
|
|
116
|
+
prUrl: { type: 'url', required: true },
|
|
117
|
+
};
|
|
118
|
+
expect(() => collector.collect('no markers here', declarations)).toThrow('Required phase output "prUrl" is missing');
|
|
119
|
+
});
|
|
120
|
+
it('does not throw for missing optional output', () => {
|
|
121
|
+
const declarations = {
|
|
122
|
+
prUrl: { type: 'url', required: false },
|
|
123
|
+
message: { type: 'string' },
|
|
124
|
+
};
|
|
125
|
+
const result = collector.collect('no markers', declarations);
|
|
126
|
+
expect(result).toEqual({});
|
|
127
|
+
});
|
|
128
|
+
it('throws on invalid URL format', () => {
|
|
129
|
+
const declarations = {
|
|
130
|
+
prUrl: { type: 'url', required: true },
|
|
131
|
+
};
|
|
132
|
+
const output = '<!-- PHASE_OUTPUT:prUrl=not-a-url -->';
|
|
133
|
+
expect(() => collector.collect(output, declarations)).toThrow('expected a valid URL');
|
|
134
|
+
});
|
|
135
|
+
it('throws on invalid boolean value', () => {
|
|
136
|
+
const declarations = {
|
|
137
|
+
passed: { type: 'boolean', required: true },
|
|
138
|
+
};
|
|
139
|
+
const output = '<!-- PHASE_OUTPUT:passed=maybe -->';
|
|
140
|
+
expect(() => collector.collect(output, declarations)).toThrow('expected boolean');
|
|
141
|
+
});
|
|
142
|
+
it('accepts string boolean "true"', () => {
|
|
143
|
+
const declarations = {
|
|
144
|
+
passed: { type: 'boolean' },
|
|
145
|
+
};
|
|
146
|
+
const output = '<!-- PHASE_OUTPUT:passed=true -->';
|
|
147
|
+
const result = collector.collect(output, declarations);
|
|
148
|
+
expect(result.passed).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
it('coerces string boolean "false"', () => {
|
|
151
|
+
const declarations = {
|
|
152
|
+
passed: { type: 'boolean' },
|
|
153
|
+
};
|
|
154
|
+
const output = '<!-- PHASE_OUTPUT:passed=false -->';
|
|
155
|
+
const result = collector.collect(output, declarations);
|
|
156
|
+
expect(result.passed).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
it('validates json type accepts objects', () => {
|
|
159
|
+
const declarations = {
|
|
160
|
+
data: { type: 'json', required: true },
|
|
161
|
+
};
|
|
162
|
+
const output = '<!-- PHASE_OUTPUT_JSON:data={"key":"value"} -->';
|
|
163
|
+
const result = collector.collect(output, declarations);
|
|
164
|
+
expect(result.data).toEqual({ key: 'value' });
|
|
165
|
+
});
|
|
166
|
+
it('allows undeclared outputs to pass through', () => {
|
|
167
|
+
const declarations = {
|
|
168
|
+
declared: { type: 'string' },
|
|
169
|
+
};
|
|
170
|
+
const output = [
|
|
171
|
+
'<!-- PHASE_OUTPUT:declared=yes -->',
|
|
172
|
+
'<!-- PHASE_OUTPUT:undeclared=also-yes -->',
|
|
173
|
+
].join('\n');
|
|
174
|
+
const result = collector.collect(output, declarations);
|
|
175
|
+
expect(result.declared).toBe('yes');
|
|
176
|
+
expect(result.undeclared).toBe('also-yes');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry & Timeout Resolution Logic
|
|
3
|
+
*
|
|
4
|
+
* Resolves per-template retry and timeout configurations with a layered
|
|
5
|
+
* override precedence:
|
|
6
|
+
*
|
|
7
|
+
* template-level → phase-level → global escalation config → defaults
|
|
8
|
+
*
|
|
9
|
+
* All functions are pure — no side effects.
|
|
10
|
+
*/
|
|
11
|
+
import type { TemplateRetryConfig, TemplateTimeoutConfig, EscalationConfig, EscalationLadderRung } from './workflow-types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Resolved retry configuration with all values filled in.
|
|
14
|
+
*/
|
|
15
|
+
export interface ResolvedRetryConfig {
|
|
16
|
+
maxAttempts: number;
|
|
17
|
+
ladder: EscalationLadderRung[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolved timeout configuration with duration in milliseconds.
|
|
21
|
+
*/
|
|
22
|
+
export interface ResolvedTimeoutConfig {
|
|
23
|
+
durationMs: number;
|
|
24
|
+
action: 'escalate' | 'skip' | 'fail';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve retry configuration with override precedence:
|
|
28
|
+
* template-level retry → phase-level retry → global escalation config → defaults
|
|
29
|
+
*
|
|
30
|
+
* For each field (maxAttempts, ladder), the first defined value wins:
|
|
31
|
+
* 1. templateRetry (from branching block)
|
|
32
|
+
* 2. phaseRetry (from phase definition)
|
|
33
|
+
* 3. globalEscalation (from workflow definition)
|
|
34
|
+
* 4. Built-in defaults
|
|
35
|
+
*
|
|
36
|
+
* @param templateRetry - Per-template retry config (from branching block)
|
|
37
|
+
* @param phaseRetry - Per-phase retry config (from phase definition)
|
|
38
|
+
* @param globalEscalation - Global escalation config (from workflow definition)
|
|
39
|
+
* @returns Fully resolved retry config with fallback defaults
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveRetryConfig(templateRetry?: TemplateRetryConfig, phaseRetry?: TemplateRetryConfig, globalEscalation?: EscalationConfig): ResolvedRetryConfig;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve timeout configuration with override precedence:
|
|
44
|
+
* template-level timeout → phase-level timeout → null (no timeout)
|
|
45
|
+
*
|
|
46
|
+
* @param templateTimeout - Per-template timeout config (from branching block)
|
|
47
|
+
* @param phaseTimeout - Per-phase timeout config (from phase definition)
|
|
48
|
+
* @returns Resolved timeout config, or null if no timeout configured
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveTimeoutConfig(templateTimeout?: TemplateTimeoutConfig, phaseTimeout?: TemplateTimeoutConfig): ResolvedTimeoutConfig | null;
|
|
51
|
+
//# sourceMappingURL=retry-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-resolver.d.ts","sourceRoot":"","sources":["../../../src/workflow/retry-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,qBAAqB,CAAA;AAO5B;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,oBAAoB,EAAE,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;CACrC;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,CAAC,EAAE,mBAAmB,EACnC,UAAU,CAAC,EAAE,mBAAmB,EAChC,gBAAgB,CAAC,EAAE,gBAAgB,GAClC,mBAAmB,CAcrB;AAMD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,CAAC,EAAE,qBAAqB,EACvC,YAAY,CAAC,EAAE,qBAAqB,GACnC,qBAAqB,GAAG,IAAI,CAW9B"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry & Timeout Resolution Logic
|
|
3
|
+
*
|
|
4
|
+
* Resolves per-template retry and timeout configurations with a layered
|
|
5
|
+
* override precedence:
|
|
6
|
+
*
|
|
7
|
+
* template-level → phase-level → global escalation config → defaults
|
|
8
|
+
*
|
|
9
|
+
* All functions are pure — no side effects.
|
|
10
|
+
*/
|
|
11
|
+
import { parseDuration } from './duration.js';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Defaults
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
16
|
+
const DEFAULT_LADDER = [
|
|
17
|
+
{ cycle: 1, strategy: 'normal' },
|
|
18
|
+
{ cycle: 2, strategy: 'context-enriched' },
|
|
19
|
+
{ cycle: 3, strategy: 'decompose' },
|
|
20
|
+
];
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Retry Resolution
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Resolve retry configuration with override precedence:
|
|
26
|
+
* template-level retry → phase-level retry → global escalation config → defaults
|
|
27
|
+
*
|
|
28
|
+
* For each field (maxAttempts, ladder), the first defined value wins:
|
|
29
|
+
* 1. templateRetry (from branching block)
|
|
30
|
+
* 2. phaseRetry (from phase definition)
|
|
31
|
+
* 3. globalEscalation (from workflow definition)
|
|
32
|
+
* 4. Built-in defaults
|
|
33
|
+
*
|
|
34
|
+
* @param templateRetry - Per-template retry config (from branching block)
|
|
35
|
+
* @param phaseRetry - Per-phase retry config (from phase definition)
|
|
36
|
+
* @param globalEscalation - Global escalation config (from workflow definition)
|
|
37
|
+
* @returns Fully resolved retry config with fallback defaults
|
|
38
|
+
*/
|
|
39
|
+
export function resolveRetryConfig(templateRetry, phaseRetry, globalEscalation) {
|
|
40
|
+
const maxAttempts = templateRetry?.maxAttempts
|
|
41
|
+
?? phaseRetry?.maxAttempts
|
|
42
|
+
?? globalEscalation?.circuitBreaker.maxSessionsPerPhase
|
|
43
|
+
?? DEFAULT_MAX_ATTEMPTS;
|
|
44
|
+
const ladder = templateRetry?.ladder
|
|
45
|
+
?? phaseRetry?.ladder
|
|
46
|
+
?? globalEscalation?.ladder
|
|
47
|
+
?? DEFAULT_LADDER;
|
|
48
|
+
return { maxAttempts, ladder };
|
|
49
|
+
}
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Timeout Resolution
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
/**
|
|
54
|
+
* Resolve timeout configuration with override precedence:
|
|
55
|
+
* template-level timeout → phase-level timeout → null (no timeout)
|
|
56
|
+
*
|
|
57
|
+
* @param templateTimeout - Per-template timeout config (from branching block)
|
|
58
|
+
* @param phaseTimeout - Per-phase timeout config (from phase definition)
|
|
59
|
+
* @returns Resolved timeout config, or null if no timeout configured
|
|
60
|
+
*/
|
|
61
|
+
export function resolveTimeoutConfig(templateTimeout, phaseTimeout) {
|
|
62
|
+
const timeout = templateTimeout ?? phaseTimeout;
|
|
63
|
+
if (!timeout) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
durationMs: parseDuration(timeout.duration),
|
|
68
|
+
action: timeout.action,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-resolver.test.d.ts","sourceRoot":"","sources":["../../../src/workflow/retry-resolver.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resolveRetryConfig, resolveTimeoutConfig } from './retry-resolver.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Helpers
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
const globalEscalation = {
|
|
7
|
+
ladder: [
|
|
8
|
+
{ cycle: 1, strategy: 'normal' },
|
|
9
|
+
{ cycle: 2, strategy: 'context-enriched' },
|
|
10
|
+
{ cycle: 3, strategy: 'decompose' },
|
|
11
|
+
{ cycle: 4, strategy: 'escalate-human' },
|
|
12
|
+
],
|
|
13
|
+
circuitBreaker: {
|
|
14
|
+
maxSessionsPerIssue: 8,
|
|
15
|
+
maxSessionsPerPhase: 5,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// resolveRetryConfig
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
describe('resolveRetryConfig', () => {
|
|
22
|
+
it('template retry overrides phase retry', () => {
|
|
23
|
+
const templateRetry = {
|
|
24
|
+
maxAttempts: 2,
|
|
25
|
+
ladder: [{ cycle: 1, strategy: 'normal' }],
|
|
26
|
+
};
|
|
27
|
+
const phaseRetry = {
|
|
28
|
+
maxAttempts: 4,
|
|
29
|
+
ladder: [
|
|
30
|
+
{ cycle: 1, strategy: 'normal' },
|
|
31
|
+
{ cycle: 2, strategy: 'decompose' },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
const result = resolveRetryConfig(templateRetry, phaseRetry, globalEscalation);
|
|
35
|
+
expect(result.maxAttempts).toBe(2);
|
|
36
|
+
expect(result.ladder).toEqual([{ cycle: 1, strategy: 'normal' }]);
|
|
37
|
+
});
|
|
38
|
+
it('phase retry overrides global escalation', () => {
|
|
39
|
+
const phaseRetry = {
|
|
40
|
+
maxAttempts: 4,
|
|
41
|
+
ladder: [
|
|
42
|
+
{ cycle: 1, strategy: 'normal' },
|
|
43
|
+
{ cycle: 3, strategy: 'escalate-human' },
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
const result = resolveRetryConfig(undefined, phaseRetry, globalEscalation);
|
|
47
|
+
expect(result.maxAttempts).toBe(4);
|
|
48
|
+
expect(result.ladder).toEqual([
|
|
49
|
+
{ cycle: 1, strategy: 'normal' },
|
|
50
|
+
{ cycle: 3, strategy: 'escalate-human' },
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
it('global escalation used as fallback when no overrides', () => {
|
|
54
|
+
const result = resolveRetryConfig(undefined, undefined, globalEscalation);
|
|
55
|
+
// maxAttempts falls back to circuitBreaker.maxSessionsPerPhase
|
|
56
|
+
expect(result.maxAttempts).toBe(5);
|
|
57
|
+
expect(result.ladder).toEqual(globalEscalation.ladder);
|
|
58
|
+
});
|
|
59
|
+
it('uses defaults when nothing is configured', () => {
|
|
60
|
+
const result = resolveRetryConfig(undefined, undefined, undefined);
|
|
61
|
+
expect(result.maxAttempts).toBe(3);
|
|
62
|
+
expect(result.ladder).toEqual([
|
|
63
|
+
{ cycle: 1, strategy: 'normal' },
|
|
64
|
+
{ cycle: 2, strategy: 'context-enriched' },
|
|
65
|
+
{ cycle: 3, strategy: 'decompose' },
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
it('partial override: only maxAttempts overridden at template level', () => {
|
|
69
|
+
const templateRetry = { maxAttempts: 1 };
|
|
70
|
+
const phaseRetry = {
|
|
71
|
+
maxAttempts: 4,
|
|
72
|
+
ladder: [{ cycle: 1, strategy: 'decompose' }],
|
|
73
|
+
};
|
|
74
|
+
const result = resolveRetryConfig(templateRetry, phaseRetry, globalEscalation);
|
|
75
|
+
// maxAttempts from template, ladder from phase (template has none)
|
|
76
|
+
expect(result.maxAttempts).toBe(1);
|
|
77
|
+
expect(result.ladder).toEqual([{ cycle: 1, strategy: 'decompose' }]);
|
|
78
|
+
});
|
|
79
|
+
it('partial override: only ladder overridden at template level', () => {
|
|
80
|
+
const templateRetry = {
|
|
81
|
+
ladder: [{ cycle: 1, strategy: 'escalate-human' }],
|
|
82
|
+
};
|
|
83
|
+
const phaseRetry = { maxAttempts: 7 };
|
|
84
|
+
const result = resolveRetryConfig(templateRetry, phaseRetry, globalEscalation);
|
|
85
|
+
// maxAttempts falls through to phase, ladder from template
|
|
86
|
+
expect(result.maxAttempts).toBe(7);
|
|
87
|
+
expect(result.ladder).toEqual([{ cycle: 1, strategy: 'escalate-human' }]);
|
|
88
|
+
});
|
|
89
|
+
it('global escalation without maxSessionsPerPhase falls back to default', () => {
|
|
90
|
+
const escalationNoPhaseMax = {
|
|
91
|
+
ladder: [{ cycle: 1, strategy: 'normal' }],
|
|
92
|
+
circuitBreaker: {
|
|
93
|
+
maxSessionsPerIssue: 8,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const result = resolveRetryConfig(undefined, undefined, escalationNoPhaseMax);
|
|
97
|
+
expect(result.maxAttempts).toBe(3); // default
|
|
98
|
+
expect(result.ladder).toEqual([{ cycle: 1, strategy: 'normal' }]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// resolveTimeoutConfig
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
describe('resolveTimeoutConfig', () => {
|
|
105
|
+
it('template timeout overrides phase timeout', () => {
|
|
106
|
+
const templateTimeout = { duration: '30m', action: 'fail' };
|
|
107
|
+
const phaseTimeout = { duration: '2h', action: 'escalate' };
|
|
108
|
+
const result = resolveTimeoutConfig(templateTimeout, phaseTimeout);
|
|
109
|
+
expect(result).not.toBeNull();
|
|
110
|
+
expect(result.durationMs).toBe(1_800_000); // 30m
|
|
111
|
+
expect(result.action).toBe('fail');
|
|
112
|
+
});
|
|
113
|
+
it('phase timeout used when no template timeout', () => {
|
|
114
|
+
const phaseTimeout = { duration: '2h', action: 'escalate' };
|
|
115
|
+
const result = resolveTimeoutConfig(undefined, phaseTimeout);
|
|
116
|
+
expect(result).not.toBeNull();
|
|
117
|
+
expect(result.durationMs).toBe(7_200_000); // 2h
|
|
118
|
+
expect(result.action).toBe('escalate');
|
|
119
|
+
});
|
|
120
|
+
it('returns null when no timeout configured', () => {
|
|
121
|
+
const result = resolveTimeoutConfig(undefined, undefined);
|
|
122
|
+
expect(result).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
it('resolves "1d" duration correctly', () => {
|
|
125
|
+
const timeout = { duration: '1d', action: 'skip' };
|
|
126
|
+
const result = resolveTimeoutConfig(timeout);
|
|
127
|
+
expect(result).not.toBeNull();
|
|
128
|
+
expect(result.durationMs).toBe(86_400_000); // 1d
|
|
129
|
+
expect(result.action).toBe('skip');
|
|
130
|
+
});
|
|
131
|
+
it('resolves "90m" duration correctly', () => {
|
|
132
|
+
const timeout = { duration: '90m', action: 'escalate' };
|
|
133
|
+
const result = resolveTimeoutConfig(timeout);
|
|
134
|
+
expect(result).not.toBeNull();
|
|
135
|
+
expect(result.durationMs).toBe(5_400_000); // 90m
|
|
136
|
+
});
|
|
137
|
+
it('handles skip action', () => {
|
|
138
|
+
const timeout = { duration: '1h', action: 'skip' };
|
|
139
|
+
const result = resolveTimeoutConfig(timeout);
|
|
140
|
+
expect(result).not.toBeNull();
|
|
141
|
+
expect(result.action).toBe('skip');
|
|
142
|
+
});
|
|
143
|
+
it('handles fail action', () => {
|
|
144
|
+
const timeout = { duration: '1h', action: 'fail' };
|
|
145
|
+
const result = resolveTimeoutConfig(timeout);
|
|
146
|
+
expect(result).not.toBeNull();
|
|
147
|
+
expect(result.action).toBe('fail');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ParallelTask, ParallelismResult, ParallelismStrategy, ParallelismStrategyOptions } from '../parallelism-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fan-in parallelism strategy.
|
|
4
|
+
*
|
|
5
|
+
* Dispatches N agents in parallel and waits for results.
|
|
6
|
+
*
|
|
7
|
+
* When `waitForAll` is true (default): waits for ALL tasks to settle
|
|
8
|
+
* using Promise.allSettled() semantics.
|
|
9
|
+
*
|
|
10
|
+
* When `waitForAll` is false: resolves on the first successful result,
|
|
11
|
+
* but still waits for all remaining tasks to complete for full result
|
|
12
|
+
* collection.
|
|
13
|
+
*
|
|
14
|
+
* NOTE: Concurrency limiting (maxConcurrent) is handled by the
|
|
15
|
+
* ConcurrencySemaphore in the ParallelismExecutor — this strategy
|
|
16
|
+
* simply calls `options.dispatch()` for each task.
|
|
17
|
+
*/
|
|
18
|
+
export declare class FanInStrategy implements ParallelismStrategy {
|
|
19
|
+
execute(tasks: ParallelTask[], options: ParallelismStrategyOptions): Promise<ParallelismResult>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=fan-in-strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fan-in-strategy.d.ts","sourceRoot":"","sources":["../../../../src/workflow/strategies/fan-in-strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAGZ,iBAAiB,EACjB,mBAAmB,EACnB,0BAA0B,EAC3B,MAAM,yBAAyB,CAAA;AAEhC;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,aAAc,YAAW,mBAAmB;IACjD,OAAO,CACX,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,iBAAiB,CAAC;CAuF9B"}
|