agentweaver 0.1.16 → 0.1.17
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/README.md +50 -10
- package/dist/artifacts.js +73 -3
- package/dist/doctor/checks/executors.js +2 -2
- package/dist/flow-state.js +138 -1
- package/dist/index.js +175 -61
- package/dist/interactive/controller.js +56 -23
- package/dist/interactive/ink/index.js +22 -1
- package/dist/interactive/tree.js +2 -2
- package/dist/pipeline/auto-flow.js +9 -6
- package/dist/pipeline/context.js +6 -5
- package/dist/pipeline/declarative-flows.js +39 -20
- package/dist/pipeline/flow-catalog.js +36 -14
- package/dist/pipeline/flow-specs/auto-common.json +1 -0
- package/dist/pipeline/flow-specs/auto-golang.json +27 -1
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +13 -1
- package/dist/pipeline/flow-specs/plan.json +4 -2
- package/dist/pipeline/launch-profile-config.js +30 -18
- package/dist/pipeline/node-contract.js +1 -0
- package/dist/pipeline/node-registry.js +74 -5
- package/dist/pipeline/nodes/flow-run-node.js +188 -173
- package/dist/pipeline/nodes/llm-prompt-node.js +15 -33
- package/dist/pipeline/plugin-loader.js +389 -0
- package/dist/pipeline/plugin-types.js +1 -0
- package/dist/pipeline/registry.js +71 -4
- package/dist/pipeline/spec-compiler.js +1 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-validator.js +6 -0
- package/dist/pipeline/value-resolver.js +2 -1
- package/dist/plugin-sdk.js +1 -0
- package/dist/runtime/artifact-registry.js +3 -0
- package/dist/runtime/execution-routing.js +25 -19
- package/dist/runtime/interactive-execution-routing.js +66 -57
- package/docs/example/.flows/examples/claude-example.json +50 -0
- package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/examples/.flows/claude-example.json +50 -0
- package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/plugin-sdk.md +731 -0
- package/package.json +6 -2
|
@@ -23,6 +23,8 @@ const HELP_TEXT = renderMarkdownToTerminal([
|
|
|
23
23
|
"q / Ctrl+C exit",
|
|
24
24
|
].join("\n"));
|
|
25
25
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
26
|
+
const SPINNER_INTERVAL_MS = 200;
|
|
27
|
+
const LOG_FLUSH_INTERVAL_MS = 120;
|
|
26
28
|
function clamp(value, min, max) {
|
|
27
29
|
return Math.min(max, Math.max(min, value));
|
|
28
30
|
}
|
|
@@ -428,7 +430,8 @@ export class InteractiveSessionController {
|
|
|
428
430
|
return "Flow structure is not available.";
|
|
429
431
|
}
|
|
430
432
|
if (selectedItem.kind === "folder") {
|
|
431
|
-
const
|
|
433
|
+
const rootName = selectedItem.pathSegments[0];
|
|
434
|
+
const kindLabel = rootName === "custom" ? "project-local" : rootName === "global" ? "global" : "built-in";
|
|
432
435
|
return [
|
|
433
436
|
`Flow folder '${selectedItem.pathSegments.join("/")}'.`,
|
|
434
437
|
"",
|
|
@@ -440,8 +443,8 @@ export class InteractiveSessionController {
|
|
|
440
443
|
const description = flow.description?.trim() || "No description available for this flow.";
|
|
441
444
|
const details = [
|
|
442
445
|
`Path: ${flow.treePath.join("/")}`,
|
|
443
|
-
`Source: ${flow.source === "project-local" ? "project-local" : "built-in"}`,
|
|
444
|
-
flow.source
|
|
446
|
+
`Source: ${flow.source === "project-local" ? "project-local" : flow.source === "global" ? "global" : "built-in"}`,
|
|
447
|
+
flow.source !== "built-in" && flow.sourcePath ? `File: ${flow.sourcePath}` : "",
|
|
445
448
|
]
|
|
446
449
|
.filter((line) => line.length > 0)
|
|
447
450
|
.join("\n");
|
|
@@ -488,11 +491,13 @@ export class InteractiveSessionController {
|
|
|
488
491
|
? "Stop"
|
|
489
492
|
: action === "resume"
|
|
490
493
|
? "Resume"
|
|
491
|
-
: action === "
|
|
492
|
-
? "
|
|
493
|
-
: action === "
|
|
494
|
-
? "
|
|
495
|
-
: "
|
|
494
|
+
: action === "continue"
|
|
495
|
+
? "Continue"
|
|
496
|
+
: action === "restart"
|
|
497
|
+
? "Restart"
|
|
498
|
+
: action === "ok"
|
|
499
|
+
? "OK"
|
|
500
|
+
: "Cancel";
|
|
496
501
|
return session.selectedAction === action ? `[ ${label} ]` : ` ${label} `;
|
|
497
502
|
})
|
|
498
503
|
.join(" ");
|
|
@@ -759,10 +764,20 @@ export class InteractiveSessionController {
|
|
|
759
764
|
this.confirmSession = {
|
|
760
765
|
kind: "run",
|
|
761
766
|
flowId: selectedItem.flow.id,
|
|
762
|
-
|
|
763
|
-
|
|
767
|
+
availability: {
|
|
768
|
+
hasExistingState: confirmation.hasExistingState,
|
|
769
|
+
resume: confirmation.resume.available,
|
|
770
|
+
continue: confirmation.continue.available,
|
|
771
|
+
restart: confirmation.restart.available,
|
|
772
|
+
},
|
|
764
773
|
details: confirmation.details ?? null,
|
|
765
|
-
selectedAction: confirmation.
|
|
774
|
+
selectedAction: confirmation.resume.available
|
|
775
|
+
? "resume"
|
|
776
|
+
: confirmation.continue.available
|
|
777
|
+
? "continue"
|
|
778
|
+
: confirmation.restart.available
|
|
779
|
+
? "restart"
|
|
780
|
+
: "ok",
|
|
766
781
|
};
|
|
767
782
|
this.emitChange();
|
|
768
783
|
}
|
|
@@ -774,8 +789,12 @@ export class InteractiveSessionController {
|
|
|
774
789
|
this.confirmSession = {
|
|
775
790
|
kind: "interrupt",
|
|
776
791
|
flowId,
|
|
777
|
-
|
|
778
|
-
|
|
792
|
+
availability: {
|
|
793
|
+
hasExistingState: true,
|
|
794
|
+
resume: true,
|
|
795
|
+
continue: false,
|
|
796
|
+
restart: false,
|
|
797
|
+
},
|
|
779
798
|
details: "The current flow will be stopped. State will be saved and can be continued via Resume.",
|
|
780
799
|
selectedAction: "stop",
|
|
781
800
|
};
|
|
@@ -791,8 +810,12 @@ export class InteractiveSessionController {
|
|
|
791
810
|
this.confirmSession = {
|
|
792
811
|
kind: "exit",
|
|
793
812
|
flowId: null,
|
|
794
|
-
|
|
795
|
-
|
|
813
|
+
availability: {
|
|
814
|
+
hasExistingState: false,
|
|
815
|
+
resume: false,
|
|
816
|
+
continue: false,
|
|
817
|
+
restart: false,
|
|
818
|
+
},
|
|
796
819
|
details,
|
|
797
820
|
selectedAction: "ok",
|
|
798
821
|
};
|
|
@@ -808,11 +831,17 @@ export class InteractiveSessionController {
|
|
|
808
831
|
if (this.confirmSession.kind === "exit") {
|
|
809
832
|
return ["ok", "cancel"];
|
|
810
833
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
834
|
+
const actions = [];
|
|
835
|
+
if (this.confirmSession.availability.resume) {
|
|
836
|
+
actions.push("resume");
|
|
837
|
+
}
|
|
838
|
+
if (this.confirmSession.availability.continue) {
|
|
839
|
+
actions.push("continue");
|
|
840
|
+
}
|
|
841
|
+
if (this.confirmSession.availability.restart) {
|
|
842
|
+
actions.push("restart");
|
|
843
|
+
}
|
|
844
|
+
return actions.length > 0 ? [...actions, "cancel"] : ["ok", "cancel"];
|
|
816
845
|
}
|
|
817
846
|
moveConfirmSelection(delta) {
|
|
818
847
|
if (!this.confirmSession) {
|
|
@@ -850,7 +879,11 @@ export class InteractiveSessionController {
|
|
|
850
879
|
return;
|
|
851
880
|
}
|
|
852
881
|
const flowId = session.flowId ?? this.state.selectedFlowId;
|
|
853
|
-
const launchMode = session.selectedAction === "resume"
|
|
882
|
+
const launchMode = session.selectedAction === "resume"
|
|
883
|
+
? "resume"
|
|
884
|
+
: session.selectedAction === "continue"
|
|
885
|
+
? "continue"
|
|
886
|
+
: "restart";
|
|
854
887
|
this.confirmSession = null;
|
|
855
888
|
this.setBusy(true, flowId);
|
|
856
889
|
this.clearFlowFailure(flowId);
|
|
@@ -895,7 +928,7 @@ export class InteractiveSessionController {
|
|
|
895
928
|
this.spinnerTimer = setInterval(() => {
|
|
896
929
|
this.state.spinnerFrame = (this.state.spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
897
930
|
this.emitChange();
|
|
898
|
-
},
|
|
931
|
+
}, SPINNER_INTERVAL_MS);
|
|
899
932
|
return;
|
|
900
933
|
}
|
|
901
934
|
if (!running && this.spinnerTimer) {
|
|
@@ -1278,7 +1311,7 @@ export class InteractiveSessionController {
|
|
|
1278
1311
|
this.logFlushTimer = setTimeout(() => {
|
|
1279
1312
|
this.logFlushTimer = null;
|
|
1280
1313
|
this.flushPendingLogLines();
|
|
1281
|
-
},
|
|
1314
|
+
}, LOG_FLUSH_INTERVAL_MS);
|
|
1282
1315
|
}
|
|
1283
1316
|
flushPendingLogLines() {
|
|
1284
1317
|
if (this.pendingLogLines.length === 0) {
|
|
@@ -370,14 +370,35 @@ function createInkApp(react, ink, controller) {
|
|
|
370
370
|
const { Fragment, createElement, useEffect, useState } = react;
|
|
371
371
|
const { Box, Text, useInput, useStdout } = ink;
|
|
372
372
|
const Panel = createPanelComponent(react, ink);
|
|
373
|
+
const LOG_REPAINT_DEBOUNCE_MS = 100;
|
|
373
374
|
const App = () => {
|
|
374
375
|
const [, setVersion] = useState(0);
|
|
375
376
|
useEffect(() => {
|
|
376
|
-
|
|
377
|
+
let logRepaintTimer = null;
|
|
378
|
+
const flushRepaint = () => {
|
|
379
|
+
if (logRepaintTimer) {
|
|
380
|
+
clearTimeout(logRepaintTimer);
|
|
381
|
+
logRepaintTimer = null;
|
|
382
|
+
}
|
|
377
383
|
setVersion((previous) => previous + 1);
|
|
384
|
+
};
|
|
385
|
+
const unsubscribe = controller.subscribe((event) => {
|
|
386
|
+
if (event.type === "log") {
|
|
387
|
+
if (logRepaintTimer) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
logRepaintTimer = setTimeout(() => {
|
|
391
|
+
flushRepaint();
|
|
392
|
+
}, LOG_REPAINT_DEBOUNCE_MS);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
flushRepaint();
|
|
378
396
|
});
|
|
379
397
|
controller.mount();
|
|
380
398
|
return () => {
|
|
399
|
+
if (logRepaintTimer) {
|
|
400
|
+
clearTimeout(logRepaintTimer);
|
|
401
|
+
}
|
|
381
402
|
unsubscribe();
|
|
382
403
|
controller.destroy();
|
|
383
404
|
};
|
package/dist/interactive/tree.js
CHANGED
|
@@ -75,7 +75,7 @@ export function buildFlowTree(flows) {
|
|
|
75
75
|
children: sortNodes(node.children),
|
|
76
76
|
}
|
|
77
77
|
: node);
|
|
78
|
-
const orderedRootNames = ["custom", "default"];
|
|
78
|
+
const orderedRootNames = ["global", "custom", "default"];
|
|
79
79
|
const sortedRoots = [...roots.values()].sort((left, right) => {
|
|
80
80
|
const leftIndex = orderedRootNames.indexOf(left.name);
|
|
81
81
|
const rightIndex = orderedRootNames.indexOf(right.name);
|
|
@@ -143,7 +143,7 @@ export function collectInitiallyExpandedFolderKeys(flowTree) {
|
|
|
143
143
|
if (node.kind !== "folder") {
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
146
|
-
const expandedByDefault = node.pathSegments.length === 1 && node.name === "default";
|
|
146
|
+
const expandedByDefault = node.pathSegments.length === 1 && (node.name === "default" || node.name === "global");
|
|
147
147
|
if (expandedByDefault) {
|
|
148
148
|
keys.push(node.key);
|
|
149
149
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { loadDeclarativeFlow } from "./declarative-flows.js";
|
|
2
|
-
|
|
3
|
-
export function loadAutoGolangFlow() {
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
const cachedAutoGolangFlows = new Map();
|
|
3
|
+
export async function loadAutoGolangFlow(options = {}) {
|
|
4
|
+
const cacheKey = options.registryContext?.cacheKey ?? `cwd:${options.cwd ?? process.cwd()}`;
|
|
5
|
+
const cached = cachedAutoGolangFlows.get(cacheKey);
|
|
6
|
+
if (cached) {
|
|
7
|
+
return cached;
|
|
6
8
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
const flow = await loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" }, options);
|
|
10
|
+
cachedAutoGolangFlows.set(cacheKey, flow);
|
|
11
|
+
return flow;
|
|
9
12
|
}
|
package/dist/pipeline/context.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import { getOutputAdapter } from "../tui.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { createPipelineRegistryContext } from "./plugin-loader.js";
|
|
4
|
+
export async function createPipelineContext(input) {
|
|
5
|
+
const registryContext = input.registryContext ?? await createPipelineRegistryContext(process.cwd());
|
|
6
6
|
return {
|
|
7
7
|
issueKey: input.issueKey,
|
|
8
8
|
jiraRef: input.jiraRef,
|
|
@@ -13,8 +13,9 @@ export function createPipelineContext(input) {
|
|
|
13
13
|
verbose: input.verbose,
|
|
14
14
|
mdLang: input.mdLang ?? null,
|
|
15
15
|
runtime: input.runtime,
|
|
16
|
-
executors:
|
|
17
|
-
nodes:
|
|
16
|
+
executors: registryContext.executors,
|
|
17
|
+
nodes: registryContext.nodes,
|
|
18
|
+
registryContext,
|
|
18
19
|
...(input.setSummary ? { setSummary: input.setSummary } : {}),
|
|
19
20
|
...(input.requestUserInput ? { requestUserInput: input.requestUserInput } : {}),
|
|
20
21
|
...(input.executionRouting ? { executionRouting: input.executionRouting } : {}),
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import { createExecutorRegistry } from "./registry.js";
|
|
2
|
+
import { createPipelineRegistryContext } from "./plugin-loader.js";
|
|
4
3
|
import { compileFlowSpec } from "./spec-compiler.js";
|
|
5
|
-
import { listBuiltInFlowSpecFiles, listProjectFlowSpecFiles, loadFlowSpecSync, projectFlowSpecsDir, resolveBuiltInFlowSpecPath } from "./spec-loader.js";
|
|
4
|
+
import { globalFlowSpecsDir, listBuiltInFlowSpecFiles, listGlobalFlowSpecFiles, listProjectFlowSpecFiles, loadFlowSpecSync, projectFlowSpecsDir, resolveBuiltInFlowSpecPath, } from "./spec-loader.js";
|
|
6
5
|
import { validateExpandedPhases, validateFlowSpec } from "./spec-validator.js";
|
|
7
6
|
const cache = new Map();
|
|
8
7
|
function toFlowSpecSource(ref) {
|
|
9
|
-
return ref.source === "built-in"
|
|
8
|
+
return ref.source === "built-in"
|
|
9
|
+
? { source: "built-in", fileName: ref.fileName }
|
|
10
|
+
: { source: ref.source, filePath: ref.filePath };
|
|
10
11
|
}
|
|
11
|
-
function cacheKey(ref) {
|
|
12
|
-
|
|
12
|
+
function cacheKey(ref, registryContext) {
|
|
13
|
+
const flowKey = ref.source === "built-in"
|
|
14
|
+
? `built-in:${ref.fileName}`
|
|
15
|
+
: `${ref.source}:${path.resolve(ref.filePath)}`;
|
|
16
|
+
return `${registryContext.cacheKey}:${flowKey}`;
|
|
13
17
|
}
|
|
14
|
-
export function loadDeclarativeFlow(flow) {
|
|
18
|
+
export async function loadDeclarativeFlow(flow, options = {}) {
|
|
15
19
|
const ref = typeof flow === "string" ? { source: "built-in", fileName: flow } : flow;
|
|
16
|
-
const
|
|
20
|
+
const cwd = path.resolve(options.cwd ?? options.registryContext?.cwd ?? process.cwd());
|
|
21
|
+
const registryContext = options.registryContext ?? await createPipelineRegistryContext(cwd);
|
|
22
|
+
const cached = cache.get(cacheKey(ref, registryContext));
|
|
17
23
|
if (cached) {
|
|
18
24
|
return cached;
|
|
19
25
|
}
|
|
20
26
|
const spec = loadFlowSpecSync(toFlowSpecSource(ref));
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
validateFlowSpec(spec, nodeRegistry, executorRegistry, {
|
|
24
|
-
resolveFlowByName: (fileName) => resolveNamedDeclarativeFlowRef(fileName, process.cwd()),
|
|
27
|
+
validateFlowSpec(spec, registryContext.nodes, registryContext.executors, {
|
|
28
|
+
resolveFlowByName: (fileName) => resolveNamedDeclarativeFlowRef(fileName, cwd),
|
|
25
29
|
});
|
|
26
30
|
const phases = compileFlowSpec(spec);
|
|
27
31
|
validateExpandedPhases(phases);
|
|
@@ -36,33 +40,48 @@ export function loadDeclarativeFlow(flow) {
|
|
|
36
40
|
fileName: ref.source === "built-in" ? ref.fileName : path.basename(ref.filePath),
|
|
37
41
|
absolutePath: ref.source === "built-in" ? resolveBuiltInFlowSpecPath(ref.fileName) : path.resolve(ref.filePath),
|
|
38
42
|
};
|
|
39
|
-
cache.set(cacheKey(ref), loaded);
|
|
43
|
+
cache.set(cacheKey(ref, registryContext), loaded);
|
|
40
44
|
return loaded;
|
|
41
45
|
}
|
|
42
46
|
export function resolveNamedDeclarativeFlowRef(fileName, cwd) {
|
|
43
47
|
const projectMatches = listProjectFlowSpecFiles(cwd).filter((candidate) => path.basename(candidate) === fileName);
|
|
48
|
+
const globalMatches = listGlobalFlowSpecFiles().filter((candidate) => path.basename(candidate) === fileName);
|
|
44
49
|
const builtInMatches = listBuiltInFlowSpecFiles().filter((candidate) => path.basename(candidate) === fileName);
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
const matches = [
|
|
51
|
+
...builtInMatches.map((candidate) => ({ source: "built-in", candidate })),
|
|
52
|
+
...globalMatches.map((candidate) => ({ source: "global", candidate })),
|
|
53
|
+
...projectMatches.map((candidate) => ({ source: "project-local", candidate })),
|
|
54
|
+
];
|
|
55
|
+
if (matches.length > 1) {
|
|
56
|
+
throw new Error(`Ambiguous nested flow '${fileName}': matches exist in built-in flows, ${globalFlowSpecsDir()}, or ${projectFlowSpecsDir(cwd)}. Use unique nested flow file names.`);
|
|
47
57
|
}
|
|
48
58
|
if (projectMatches.length > 1) {
|
|
49
59
|
throw new Error(`Ambiguous project-local flow '${fileName}' in ${projectFlowSpecsDir(cwd)}.`);
|
|
50
60
|
}
|
|
61
|
+
if (globalMatches.length > 1) {
|
|
62
|
+
throw new Error(`Ambiguous global flow '${fileName}' in ${globalFlowSpecsDir()}.`);
|
|
63
|
+
}
|
|
51
64
|
if (builtInMatches.length > 1) {
|
|
52
65
|
throw new Error(`Ambiguous built-in flow '${fileName}'. Use unique nested flow file names.`);
|
|
53
66
|
}
|
|
54
67
|
if (projectMatches[0]) {
|
|
55
68
|
return { source: "project-local", filePath: projectMatches[0] };
|
|
56
69
|
}
|
|
70
|
+
if (globalMatches[0]) {
|
|
71
|
+
return { source: "global", filePath: globalMatches[0] };
|
|
72
|
+
}
|
|
57
73
|
if (builtInMatches[0]) {
|
|
58
74
|
return { source: "built-in", fileName: builtInMatches[0] };
|
|
59
75
|
}
|
|
60
76
|
throw new Error(`Nested flow '${fileName}' was not found.`);
|
|
61
77
|
}
|
|
62
|
-
export function loadNamedDeclarativeFlow(fileName, cwd) {
|
|
63
|
-
return loadDeclarativeFlow(resolveNamedDeclarativeFlowRef(fileName, cwd)
|
|
78
|
+
export async function loadNamedDeclarativeFlow(fileName, cwd, options = {}) {
|
|
79
|
+
return loadDeclarativeFlow(resolveNamedDeclarativeFlowRef(fileName, cwd), {
|
|
80
|
+
cwd,
|
|
81
|
+
...(options.registryContext ? { registryContext: options.registryContext } : {}),
|
|
82
|
+
});
|
|
64
83
|
}
|
|
65
|
-
export function collectFlowRoutingGroups(flow, cwd, visited = new Set()) {
|
|
84
|
+
export async function collectFlowRoutingGroups(flow, cwd, visited = new Set(), options = {}) {
|
|
66
85
|
if (visited.has(flow.absolutePath)) {
|
|
67
86
|
return [];
|
|
68
87
|
}
|
|
@@ -80,8 +99,8 @@ export function collectFlowRoutingGroups(flow, cwd, visited = new Set()) {
|
|
|
80
99
|
if (!nestedFlowName || !("const" in nestedFlowName) || typeof nestedFlowName.const !== "string") {
|
|
81
100
|
continue;
|
|
82
101
|
}
|
|
83
|
-
const nestedFlow = loadNamedDeclarativeFlow(nestedFlowName.const, cwd);
|
|
84
|
-
for (const nestedGroup of collectFlowRoutingGroups(nestedFlow, cwd, visited)) {
|
|
102
|
+
const nestedFlow = await loadNamedDeclarativeFlow(nestedFlowName.const, cwd, options);
|
|
103
|
+
for (const nestedGroup of await collectFlowRoutingGroups(nestedFlow, cwd, visited, options)) {
|
|
85
104
|
groups.add(nestedGroup);
|
|
86
105
|
}
|
|
87
106
|
}
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { TaskRunnerError } from "../errors.js";
|
|
3
3
|
import { loadAutoGolangFlow } from "./auto-flow.js";
|
|
4
4
|
import { collectFlowRoutingGroups, loadDeclarativeFlow } from "./declarative-flows.js";
|
|
5
|
-
import { listBuiltInFlowSpecFiles, listProjectFlowSpecFiles, projectFlowSpecsDir } from "./spec-loader.js";
|
|
5
|
+
import { globalFlowSpecsDir, listBuiltInFlowSpecFiles, listGlobalFlowSpecFiles, listProjectFlowSpecFiles, projectFlowSpecsDir } from "./spec-loader.js";
|
|
6
6
|
export const BUILT_IN_COMMAND_FLOW_IDS = [
|
|
7
7
|
"auto-golang",
|
|
8
8
|
"auto-common",
|
|
@@ -58,11 +58,13 @@ function builtInCommandIdForFile(fileName) {
|
|
|
58
58
|
}
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
|
-
function loadBuiltInCatalogEntry(fileName) {
|
|
61
|
+
async function loadBuiltInCatalogEntry(fileName, options) {
|
|
62
62
|
const commandId = builtInCommandIdForFile(fileName);
|
|
63
63
|
const relativePath = fileName.replace(/\.json$/i, "").split(/[\\/]+/).filter((segment) => segment.length > 0);
|
|
64
64
|
const id = commandId ?? relativePath.join("/");
|
|
65
|
-
const flow = id === "auto-golang"
|
|
65
|
+
const flow = id === "auto-golang"
|
|
66
|
+
? await loadAutoGolangFlow(options)
|
|
67
|
+
: await loadDeclarativeFlow({ source: "built-in", fileName }, options);
|
|
66
68
|
return {
|
|
67
69
|
id,
|
|
68
70
|
source: "built-in",
|
|
@@ -72,8 +74,8 @@ function loadBuiltInCatalogEntry(fileName) {
|
|
|
72
74
|
flow,
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
|
-
function loadProjectCatalogEntry(cwd, filePath) {
|
|
76
|
-
const flow = loadDeclarativeFlow({ source: "project-local", filePath });
|
|
77
|
+
async function loadProjectCatalogEntry(cwd, filePath, options) {
|
|
78
|
+
const flow = await loadDeclarativeFlow({ source: "project-local", filePath }, { ...options, cwd });
|
|
77
79
|
const relativeFilePath = path.relative(projectFlowSpecsDir(cwd), path.resolve(filePath));
|
|
78
80
|
const relativePathWithoutExt = relativeFilePath.replace(/\.json$/i, "");
|
|
79
81
|
const relativeSegments = relativePathWithoutExt.split(path.sep).filter((segment) => segment.length > 0);
|
|
@@ -86,10 +88,30 @@ function loadProjectCatalogEntry(cwd, filePath) {
|
|
|
86
88
|
flow,
|
|
87
89
|
};
|
|
88
90
|
}
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
+
async function loadGlobalCatalogEntry(filePath, options) {
|
|
92
|
+
const flow = await loadDeclarativeFlow({ source: "global", filePath }, options);
|
|
93
|
+
const relativeFilePath = path.relative(globalFlowSpecsDir(), path.resolve(filePath));
|
|
94
|
+
const relativePathWithoutExt = relativeFilePath.replace(/\.json$/i, "");
|
|
95
|
+
const relativeSegments = relativePathWithoutExt.split(path.sep).filter((segment) => segment.length > 0);
|
|
96
|
+
return {
|
|
97
|
+
id: relativeSegments.join("/"),
|
|
98
|
+
source: "global",
|
|
99
|
+
fileName: path.basename(filePath),
|
|
100
|
+
absolutePath: path.resolve(filePath),
|
|
101
|
+
treePath: ["global", ...relativeSegments],
|
|
102
|
+
flow,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export async function loadInteractiveFlowCatalog(cwd, options = {}) {
|
|
106
|
+
const entries = [];
|
|
107
|
+
for (const fileName of listBuiltInFlowSpecFiles()) {
|
|
108
|
+
entries.push(await loadBuiltInCatalogEntry(fileName, { ...options, cwd }));
|
|
109
|
+
}
|
|
110
|
+
for (const filePath of listGlobalFlowSpecFiles()) {
|
|
111
|
+
entries.push(await loadGlobalCatalogEntry(filePath, { ...options, cwd }));
|
|
112
|
+
}
|
|
91
113
|
for (const filePath of listProjectFlowSpecFiles(cwd)) {
|
|
92
|
-
entries.push(loadProjectCatalogEntry(cwd, filePath));
|
|
114
|
+
entries.push(await loadProjectCatalogEntry(cwd, filePath, { ...options, cwd }));
|
|
93
115
|
}
|
|
94
116
|
const visibleEntries = entries.filter((entry) => entry.flow.catalogVisibility !== "hidden");
|
|
95
117
|
const byId = new Map();
|
|
@@ -111,13 +133,13 @@ export function isBuiltInCommandFlowId(flowId) {
|
|
|
111
133
|
export function toDeclarativeFlowRef(entry) {
|
|
112
134
|
return entry.source === "built-in"
|
|
113
135
|
? { source: "built-in", fileName: entry.fileName }
|
|
114
|
-
: { source:
|
|
136
|
+
: { source: entry.source, filePath: entry.absolutePath };
|
|
115
137
|
}
|
|
116
138
|
export function flowRoutingKey(entry) {
|
|
117
|
-
return entry.source === "
|
|
118
|
-
? `
|
|
119
|
-
:
|
|
139
|
+
return entry.source === "built-in"
|
|
140
|
+
? `built-in:${entry.id}`
|
|
141
|
+
: `${entry.source}:${entry.absolutePath}`;
|
|
120
142
|
}
|
|
121
|
-
export function flowRoutingGroups(entry, cwd) {
|
|
122
|
-
return collectFlowRoutingGroups(entry.flow, cwd);
|
|
143
|
+
export async function flowRoutingGroups(entry, cwd, options = {}) {
|
|
144
|
+
return collectFlowRoutingGroups(entry.flow, cwd, new Set(), options);
|
|
123
145
|
}
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"fileName": { "const": "design-review-loop.json" },
|
|
69
69
|
"labelText": { "const": "Running design-review loop" },
|
|
70
70
|
"taskKey": { "ref": "params.taskKey" },
|
|
71
|
+
"baseIteration": { "ref": "params.designReviewBaseIteration" },
|
|
71
72
|
"workspaceDir": { "ref": "params.workspaceDir" },
|
|
72
73
|
"extraPrompt": { "ref": "params.extraPrompt" },
|
|
73
74
|
"llmExecutor": { "ref": "params.llmExecutor" },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"kind": "auto-flow",
|
|
3
3
|
"version": 1,
|
|
4
|
-
"description": "End-to-end resumable pipeline for Go projects. Runs the full sequence: Jira fetch → task source normalization → plan → implement → linter loop → test loop → review loop → final linter loop → final test loop. Supports --from to restart from a specific phase and auto-status/auto-reset for state management.",
|
|
4
|
+
"description": "End-to-end resumable pipeline for Go projects. Runs the full sequence: Jira fetch → task source normalization → plan → design-review loop → implement → linter loop → test loop → review loop → final linter loop → final test loop. Supports --from to restart from a specific phase and auto-status/auto-reset for state management.",
|
|
5
5
|
"constants": {
|
|
6
6
|
"autoReviewFixExtraPrompt": "Fix only blockers, criticals, and important findings"
|
|
7
7
|
},
|
|
@@ -61,6 +61,32 @@
|
|
|
61
61
|
}
|
|
62
62
|
]
|
|
63
63
|
},
|
|
64
|
+
{
|
|
65
|
+
"id": "design_review_loop",
|
|
66
|
+
"steps": [
|
|
67
|
+
{
|
|
68
|
+
"id": "run_design_review_loop",
|
|
69
|
+
"node": "flow-run",
|
|
70
|
+
"params": {
|
|
71
|
+
"fileName": { "const": "design-review-loop.json" },
|
|
72
|
+
"labelText": { "const": "Running design-review loop" },
|
|
73
|
+
"taskKey": { "ref": "params.taskKey" },
|
|
74
|
+
"baseIteration": { "ref": "params.designReviewBaseIteration" },
|
|
75
|
+
"workspaceDir": { "ref": "params.workspaceDir" },
|
|
76
|
+
"extraPrompt": { "ref": "params.extraPrompt" },
|
|
77
|
+
"llmExecutor": { "ref": "params.llmExecutor" },
|
|
78
|
+
"llmModel": { "ref": "params.llmModel" }
|
|
79
|
+
},
|
|
80
|
+
"stopFlowIf": {
|
|
81
|
+
"equals": [
|
|
82
|
+
{ "ref": "steps.design_review_loop.run_design_review_loop.value.executionState.terminationOutcome" },
|
|
83
|
+
{ "const": "stopped" }
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"stopFlowOutcome": "stopped"
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
},
|
|
64
90
|
{
|
|
65
91
|
"id": "implement",
|
|
66
92
|
"steps": [
|
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
"template": "Running design review (iteration ${iteration})"
|
|
24
24
|
},
|
|
25
25
|
"taskKey": { "ref": "params.taskKey" },
|
|
26
|
+
"iteration": {
|
|
27
|
+
"add": [
|
|
28
|
+
{ "ref": "params.baseIteration" },
|
|
29
|
+
{ "add": [{ "ref": "repeat.iteration" }, { "const": -1 }] }
|
|
30
|
+
]
|
|
31
|
+
},
|
|
26
32
|
"workspaceDir": { "ref": "params.workspaceDir" },
|
|
27
33
|
"extraPrompt": { "ref": "params.extraPrompt" },
|
|
28
34
|
"llmExecutor": { "ref": "params.llmExecutor" },
|
|
@@ -33,7 +39,13 @@
|
|
|
33
39
|
"id": "check_design_review_verdict",
|
|
34
40
|
"node": "design-review-verdict",
|
|
35
41
|
"params": {
|
|
36
|
-
"taskKey": { "ref": "params.taskKey" }
|
|
42
|
+
"taskKey": { "ref": "params.taskKey" },
|
|
43
|
+
"iteration": {
|
|
44
|
+
"add": [
|
|
45
|
+
{ "ref": "params.baseIteration" },
|
|
46
|
+
{ "add": [{ "ref": "repeat.iteration" }, { "const": -1 }] }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
37
49
|
}
|
|
38
50
|
},
|
|
39
51
|
{
|
|
@@ -258,7 +258,8 @@
|
|
|
258
258
|
"requiredArtifacts": {
|
|
259
259
|
"artifactList": {
|
|
260
260
|
"kind": "plan-artifacts",
|
|
261
|
-
"taskKey": { "ref": "params.taskKey" }
|
|
261
|
+
"taskKey": { "ref": "params.taskKey" },
|
|
262
|
+
"iteration": { "ref": "params.planIteration" }
|
|
262
263
|
}
|
|
263
264
|
}
|
|
264
265
|
},
|
|
@@ -269,7 +270,8 @@
|
|
|
269
270
|
"paths": {
|
|
270
271
|
"artifactList": {
|
|
271
272
|
"kind": "plan-artifacts",
|
|
272
|
-
"taskKey": { "ref": "params.taskKey" }
|
|
273
|
+
"taskKey": { "ref": "params.taskKey" },
|
|
274
|
+
"iteration": { "ref": "params.planIteration" }
|
|
273
275
|
}
|
|
274
276
|
},
|
|
275
277
|
"message": "Plan mode did not produce the required artifacts."
|
|
@@ -1,32 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
codex: ["gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex"],
|
|
4
|
-
opencode: ["opencode/minimax-m2.5-free", "minimax-coding-plan/MiniMax-M2.7", "zhipuai-coding-plan/glm-5.1", "zhipuai-coding-plan/glm-4.7"],
|
|
5
|
-
};
|
|
1
|
+
import { createExecutorRegistry } from "./registry.js";
|
|
2
|
+
const BUILT_IN_EXECUTOR_REGISTRY = createExecutorRegistry();
|
|
6
3
|
export const DEFAULT_EXECUTOR = "opencode";
|
|
7
|
-
export const DEFAULT_MODEL_BY_EXECUTOR = {
|
|
8
|
-
codex: "gpt-5.4",
|
|
9
|
-
opencode: "minimax-coding-plan/MiniMax-M2.7",
|
|
10
|
-
};
|
|
11
4
|
export const DEFAULT_LAUNCH_PROFILE = {
|
|
12
5
|
executor: DEFAULT_EXECUTOR,
|
|
13
|
-
model:
|
|
6
|
+
model: "minimax-coding-plan/MiniMax-M2.7",
|
|
14
7
|
};
|
|
15
|
-
|
|
16
|
-
return
|
|
8
|
+
function registryOrBuiltIn(executors) {
|
|
9
|
+
return executors ?? BUILT_IN_EXECUTOR_REGISTRY;
|
|
10
|
+
}
|
|
11
|
+
export function llmExecutorIds(executors) {
|
|
12
|
+
return registryOrBuiltIn(executors).llmExecutors().map((entry) => entry.id);
|
|
13
|
+
}
|
|
14
|
+
export function defaultModelForExecutor(executor, executors) {
|
|
15
|
+
const routing = registryOrBuiltIn(executors).getRouting(executor);
|
|
16
|
+
if (!routing || routing.kind !== "llm") {
|
|
17
|
+
throw new Error(`Unsupported llm executor '${executor}'.`);
|
|
18
|
+
}
|
|
19
|
+
return routing.defaultModel;
|
|
20
|
+
}
|
|
21
|
+
export function isLlmExecutorId(value, executors) {
|
|
22
|
+
const routing = registryOrBuiltIn(executors).getRouting(value);
|
|
23
|
+
return routing?.kind === "llm";
|
|
17
24
|
}
|
|
18
|
-
export function
|
|
19
|
-
|
|
25
|
+
export function isAllowedModelForExecutor(executor, model, executors) {
|
|
26
|
+
const routing = registryOrBuiltIn(executors).getRouting(executor);
|
|
27
|
+
return routing?.kind === "llm" ? routing.models.includes(model) : false;
|
|
20
28
|
}
|
|
21
|
-
export function
|
|
22
|
-
|
|
29
|
+
export function allowedModelsForExecutor(executor, executors) {
|
|
30
|
+
const routing = registryOrBuiltIn(executors).getRouting(executor);
|
|
31
|
+
if (!routing || routing.kind !== "llm") {
|
|
32
|
+
throw new Error(`Unsupported llm executor '${executor}'.`);
|
|
33
|
+
}
|
|
34
|
+
return [...routing.models];
|
|
23
35
|
}
|
|
24
|
-
export function resolveLaunchProfile(selection, fallback = DEFAULT_LAUNCH_PROFILE) {
|
|
36
|
+
export function resolveLaunchProfile(selection, fallback = DEFAULT_LAUNCH_PROFILE, executors) {
|
|
25
37
|
const executor = selection.executor === "default" ? fallback.executor : selection.executor;
|
|
26
38
|
const model = selection.model === "default"
|
|
27
39
|
? selection.executor === "default"
|
|
28
40
|
? fallback.model
|
|
29
|
-
: defaultModelForExecutor(executor)
|
|
41
|
+
: defaultModelForExecutor(executor, executors)
|
|
30
42
|
: selection.model;
|
|
31
43
|
return {
|
|
32
44
|
executor,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|