agentweaver 0.1.9 → 0.1.11
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 +226 -200
- package/dist/artifacts.js +101 -56
- package/dist/errors.js +7 -0
- package/dist/executors/{codex-local-executor.js → codex-executor.js} +4 -4
- package/dist/executors/configs/{codex-local-config.js → codex-config.js} +1 -1
- package/dist/executors/configs/jira-fetch-config.js +2 -0
- package/dist/executors/configs/telegram-notifier-config.js +3 -0
- package/dist/executors/fetch-gitlab-diff-executor.js +1 -1
- package/dist/executors/fetch-gitlab-review-executor.js +1 -1
- package/dist/executors/git-commit-executor.js +25 -0
- package/dist/executors/telegram-notifier-executor.js +54 -0
- package/dist/flow-state.js +46 -1
- package/dist/gitlab.js +13 -8
- package/dist/index.js +507 -520
- package/dist/interactive-ui.js +495 -87
- package/dist/jira.js +52 -5
- package/dist/pipeline/auto-flow.js +6 -6
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/declarative-flows.js +7 -4
- package/dist/pipeline/flow-catalog.js +60 -23
- package/dist/pipeline/flow-model-settings.js +77 -0
- package/dist/pipeline/flow-specs/auto-common.json +446 -0
- package/dist/pipeline/flow-specs/auto-golang.json +563 -0
- package/dist/pipeline/flow-specs/{bug-analyze.json → bugz/bug-analyze.json} +43 -25
- package/dist/pipeline/flow-specs/{bug-fix.json → bugz/bug-fix.json} +5 -4
- package/dist/pipeline/flow-specs/git-commit.json +196 -0
- package/dist/pipeline/flow-specs/{gitlab-diff-review.json → gitlab/gitlab-diff-review.json} +20 -50
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +165 -0
- package/dist/pipeline/flow-specs/{mr-description.json → gitlab/mr-description.json} +17 -10
- package/dist/pipeline/flow-specs/{run-go-linter-loop.json → go/run-go-linter-loop.json} +40 -14
- package/dist/pipeline/flow-specs/{run-go-tests-loop.json → go/run-go-tests-loop.json} +40 -14
- package/dist/pipeline/flow-specs/implement.json +5 -4
- package/dist/pipeline/flow-specs/plan.json +40 -148
- package/dist/pipeline/flow-specs/{review-fix.json → review/review-fix.json} +73 -13
- package/dist/pipeline/flow-specs/review/review-loop.json +280 -0
- package/dist/pipeline/flow-specs/review/review-project.json +87 -0
- package/dist/pipeline/flow-specs/review/review.json +126 -0
- package/dist/pipeline/flow-specs/task-describe.json +191 -11
- package/dist/pipeline/launch-profile-config.js +38 -0
- package/dist/pipeline/node-registry.js +75 -45
- package/dist/pipeline/nodes/build-failure-summary-node.js +16 -29
- package/dist/pipeline/nodes/build-review-fix-prompt-node.js +36 -0
- package/dist/pipeline/nodes/codex-prompt-node.js +41 -0
- package/dist/pipeline/nodes/commit-message-form-node.js +79 -0
- package/dist/pipeline/nodes/git-commit-form-node.js +138 -0
- package/dist/pipeline/nodes/git-commit-node.js +28 -0
- package/dist/pipeline/nodes/git-status-node.js +221 -0
- package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +10 -6
- package/dist/pipeline/nodes/jira-context-node.js +10 -0
- package/dist/pipeline/nodes/llm-prompt-node.js +62 -0
- package/dist/pipeline/nodes/plan-codex-node.js +1 -1
- package/dist/pipeline/nodes/read-file-node.js +11 -0
- package/dist/pipeline/nodes/review-findings-form-node.js +18 -14
- package/dist/pipeline/nodes/select-files-form-node.js +72 -0
- package/dist/pipeline/nodes/telegram-notifier-node.js +28 -0
- package/dist/pipeline/nodes/user-input-node.js +29 -8
- package/dist/pipeline/nodes/write-selection-file-node.js +46 -0
- package/dist/pipeline/prompt-registry.js +2 -4
- package/dist/pipeline/prompt-runtime.js +13 -3
- package/dist/pipeline/registry.js +6 -8
- package/dist/pipeline/spec-compiler.js +5 -0
- package/dist/pipeline/spec-loader.js +18 -7
- package/dist/pipeline/spec-types.js +7 -3
- package/dist/pipeline/spec-validator.js +4 -0
- package/dist/pipeline/types.js +1 -0
- package/dist/pipeline/value-resolver.js +40 -38
- package/dist/prompts.js +104 -110
- package/dist/runtime/agentweaver-home.js +8 -0
- package/dist/runtime/command-resolution.js +0 -38
- package/dist/runtime/env-loader.js +43 -0
- package/dist/runtime/process-runner.js +45 -1
- package/dist/structured-artifact-schema-registry.js +53 -0
- package/dist/structured-artifact-schemas.json +0 -20
- package/dist/structured-artifacts.js +3 -43
- package/dist/user-input.js +30 -2
- package/package.json +2 -6
- package/Dockerfile.codex +0 -56
- package/dist/executors/claude-executor.js +0 -46
- package/dist/executors/codex-docker-executor.js +0 -27
- package/dist/executors/configs/claude-config.js +0 -12
- package/dist/executors/configs/codex-docker-config.js +0 -10
- package/dist/executors/configs/verify-build-config.js +0 -7
- package/dist/executors/verify-build-executor.js +0 -123
- package/dist/pipeline/flow-specs/auto.json +0 -979
- package/dist/pipeline/flow-specs/gitlab-review.json +0 -317
- package/dist/pipeline/flow-specs/plan-opencode.json +0 -603
- package/dist/pipeline/flow-specs/preflight.json +0 -206
- package/dist/pipeline/flow-specs/review-project.json +0 -243
- package/dist/pipeline/flow-specs/review.json +0 -312
- package/dist/pipeline/flow-specs/run-linter-loop.json +0 -155
- package/dist/pipeline/flow-specs/run-tests-loop.json +0 -155
- package/dist/pipeline/flows/preflight-flow.js +0 -19
- package/dist/pipeline/nodes/claude-prompt-node.js +0 -54
- package/dist/pipeline/nodes/codex-docker-prompt-node.js +0 -32
- package/dist/pipeline/nodes/codex-local-prompt-node.js +0 -32
- package/dist/pipeline/nodes/review-claude-node.js +0 -38
- package/dist/pipeline/nodes/review-reply-codex-node.js +0 -40
- package/dist/pipeline/nodes/verify-build-node.js +0 -15
- package/dist/runtime/docker-runtime.js +0 -51
- package/docker-compose.yml +0 -445
- package/verify_build.sh +0 -105
package/dist/index.js
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readdirSync, readFileSync
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { REVIEW_FILE_RE,
|
|
7
|
-
import { TaskRunnerError } from "./errors.js";
|
|
8
|
-
import { createFlowRunState, hasResumableFlowState, loadFlowRunState, prepareFlowStateForResume, resetFlowRunState, saveFlowRunState, stripExecutionStatePayload, } from "./flow-state.js";
|
|
6
|
+
import { REVIEW_FILE_RE, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, latestArtifactIteration, nextArtifactIteration, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, flowStateFile, taskSummaryFile, } from "./artifacts.js";
|
|
7
|
+
import { FlowInterruptedError, TaskRunnerError } from "./errors.js";
|
|
8
|
+
import { createFlowRunState, hasResumableFlowState, loadFlowRunState, prepareFlowStateForResume, resetFlowRunState, rewindFlowRunStateToPhase, saveFlowRunState, stripExecutionStatePayload, } from "./flow-state.js";
|
|
9
9
|
import { requireJiraTaskFile } from "./jira.js";
|
|
10
10
|
import { validateStructuredArtifacts } from "./structured-artifacts.js";
|
|
11
11
|
import { summarizeBuildFailure as summarizeBuildFailureViaPipeline } from "./pipeline/build-failure-summary.js";
|
|
12
|
+
import { runNodeChecks } from "./pipeline/checks.js";
|
|
12
13
|
import { createPipelineContext } from "./pipeline/context.js";
|
|
13
|
-
import { loadAutoFlow } from "./pipeline/auto-flow.js";
|
|
14
14
|
import { loadDeclarativeFlow } from "./pipeline/declarative-flows.js";
|
|
15
|
-
import {
|
|
15
|
+
import { runExpandedPhase } from "./pipeline/declarative-flow-runner.js";
|
|
16
16
|
import { findCatalogEntry, isBuiltInCommandFlowId, loadInteractiveFlowCatalog, toDeclarativeFlowRef } from "./pipeline/flow-catalog.js";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
17
|
+
import { ALLOWED_MODELS_BY_EXECUTOR, defaultModelForExecutor, DEFAULT_LAUNCH_PROFILE, LLM_EXECUTOR_IDS, resolveLaunchProfile, } from "./pipeline/launch-profile-config.js";
|
|
18
|
+
import { evaluateCondition, resolveValue } from "./pipeline/value-resolver.js";
|
|
19
|
+
import { resolveCmd } from "./runtime/command-resolution.js";
|
|
20
|
+
import { loadTieredEnv } from "./runtime/env-loader.js";
|
|
21
|
+
import { agentweaverHome } from "./runtime/agentweaver-home.js";
|
|
19
22
|
import { runCommand } from "./runtime/process-runner.js";
|
|
20
23
|
import { InteractiveUi } from "./interactive-ui.js";
|
|
21
24
|
import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
|
|
22
25
|
import { requestUserInputInTerminal } from "./user-input.js";
|
|
23
26
|
import { attachJiraContext, detectGitBranchName, requestJiraContext, resolveProjectScope, } from "./scope.js";
|
|
24
27
|
const COMMANDS = [
|
|
28
|
+
"auto-golang",
|
|
29
|
+
"auto-common",
|
|
30
|
+
"auto-status",
|
|
31
|
+
"auto-reset",
|
|
25
32
|
"bug-analyze",
|
|
26
33
|
"bug-fix",
|
|
34
|
+
"git-commit",
|
|
27
35
|
"gitlab-diff-review",
|
|
28
36
|
"gitlab-review",
|
|
29
37
|
"mr-description",
|
|
@@ -32,21 +40,19 @@ const COMMANDS = [
|
|
|
32
40
|
"implement",
|
|
33
41
|
"review",
|
|
34
42
|
"review-fix",
|
|
43
|
+
"review-loop",
|
|
35
44
|
"run-go-tests-loop",
|
|
36
45
|
"run-go-linter-loop",
|
|
37
|
-
"auto",
|
|
38
|
-
"auto-status",
|
|
39
|
-
"auto-reset",
|
|
40
46
|
];
|
|
41
|
-
const AUTO_STATE_SCHEMA_VERSION = 3;
|
|
42
47
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
43
48
|
const PACKAGE_ROOT = path.resolve(MODULE_DIR, "..");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
49
|
+
function createRuntimeServices(signal) {
|
|
50
|
+
return {
|
|
51
|
+
resolveCmd,
|
|
52
|
+
runCommand: (argv, options = {}) => runCommand(argv, { ...options, ...(signal ? { signal } : {}) }),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const runtimeServices = createRuntimeServices();
|
|
50
56
|
function buildFailureOutputPreview(output) {
|
|
51
57
|
const normalized = stripAnsi(output).replace(/\r\n/g, "\n").trim();
|
|
52
58
|
if (!normalized) {
|
|
@@ -78,59 +84,64 @@ function formatProcessFailure(error) {
|
|
|
78
84
|
}
|
|
79
85
|
return `${baseMessage}\nПричина:\n${preview}`;
|
|
80
86
|
}
|
|
87
|
+
function escapeRegExp(value) {
|
|
88
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
89
|
+
}
|
|
81
90
|
function usage() {
|
|
82
91
|
return `Usage:
|
|
83
92
|
agentweaver
|
|
84
93
|
agentweaver <jira-browse-url|jira-issue-key>
|
|
85
94
|
agentweaver --force <jira-browse-url|jira-issue-key>
|
|
95
|
+
agentweaver git-commit [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
86
96
|
agentweaver gitlab-diff-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
|
|
87
97
|
agentweaver gitlab-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
|
|
88
98
|
agentweaver bug-analyze [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
89
99
|
agentweaver bug-fix [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
90
100
|
agentweaver mr-description [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
91
|
-
agentweaver plan [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
|
|
92
|
-
agentweaver task-describe [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
101
|
+
agentweaver plan [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
|
|
102
|
+
agentweaver task-describe [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
|
|
93
103
|
agentweaver implement [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
94
104
|
agentweaver review [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
95
105
|
agentweaver review-fix [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
106
|
+
agentweaver review-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
96
107
|
agentweaver run-go-tests-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
97
108
|
agentweaver run-go-linter-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
98
|
-
agentweaver auto [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
|
|
99
|
-
agentweaver auto [--dry] [--verbose] [--prompt <text>] --from <phase> [<jira-browse-url|jira-issue-key>]
|
|
100
|
-
agentweaver auto --help-phases
|
|
109
|
+
agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
|
|
110
|
+
agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] --from <phase> [<jira-browse-url|jira-issue-key>]
|
|
111
|
+
agentweaver auto-golang --help-phases
|
|
101
112
|
agentweaver auto-status [<jira-browse-url|jira-issue-key>]
|
|
102
113
|
agentweaver auto-reset [<jira-browse-url|jira-issue-key>]
|
|
103
114
|
|
|
104
115
|
Interactive Mode:
|
|
105
116
|
When started without a command, the script opens an interactive UI.
|
|
106
117
|
If a Jira task is provided, interactive mode starts in the current project scope with Jira context attached.
|
|
107
|
-
Use Up/Down to
|
|
118
|
+
Use Up/Down to move in the flow tree, Left/Right to collapse or expand folders, Enter to toggle a folder or run a flow, h for help, q to exit.
|
|
108
119
|
|
|
109
120
|
Flags:
|
|
110
121
|
--version Show package version
|
|
111
122
|
--force In interactive mode, regenerate task summary in Jira-backed flows
|
|
112
|
-
--dry Fetch Jira task, but print
|
|
123
|
+
--dry Fetch Jira task, but print codex/opencode commands instead of executing them
|
|
113
124
|
--verbose Show live stdout/stderr of launched commands
|
|
114
125
|
--scope Explicit workflow scope name for non-Jira runs
|
|
115
126
|
--prompt Extra prompt text appended to the base prompt
|
|
127
|
+
--md-lang Language for markdown output files: en (English) or ru (Russian, default)
|
|
116
128
|
|
|
117
129
|
Required environment variables:
|
|
118
|
-
JIRA_API_KEY Jira API
|
|
130
|
+
JIRA_API_KEY Jira API token used for Jira-backed flows (Bearer by default, or Basic with Jira Cloud)
|
|
119
131
|
|
|
120
132
|
Optional environment variables:
|
|
133
|
+
JIRA_USERNAME Required for Jira Cloud Basic auth (usually Atlassian account email)
|
|
134
|
+
JIRA_AUTH_MODE Override Jira auth mode: auto | basic | bearer
|
|
121
135
|
JIRA_BASE_URL
|
|
122
136
|
GITLAB_TOKEN
|
|
123
137
|
AGENTWEAVER_HOME
|
|
124
|
-
DOCKER_COMPOSE_BIN
|
|
125
138
|
CODEX_BIN
|
|
126
139
|
CODEX_MODEL
|
|
127
140
|
OPENCODE_BIN
|
|
128
141
|
OPENCODE_MODEL
|
|
129
|
-
CLAUDE_BIN
|
|
130
|
-
CLAUDE_MODEL
|
|
131
142
|
|
|
132
143
|
Notes:
|
|
133
|
-
-
|
|
144
|
+
- Jira-backed task flows will ask for Jira task via user-input when it is not passed as an argument. task-describe can also work from a manual task description without Jira.
|
|
134
145
|
- All flow state and artifacts are stored in the current project scope by default.
|
|
135
146
|
- gitlab-review and gitlab-diff-review ask for GitLab merge request URL via user-input.`;
|
|
136
147
|
}
|
|
@@ -142,212 +153,235 @@ function packageVersion() {
|
|
|
142
153
|
}
|
|
143
154
|
return raw.version;
|
|
144
155
|
}
|
|
145
|
-
function nowIso8601() {
|
|
146
|
-
return new Date().toISOString();
|
|
147
|
-
}
|
|
148
156
|
function normalizeAutoPhaseId(phaseId) {
|
|
149
157
|
return phaseId.trim().toLowerCase().replaceAll("-", "_");
|
|
150
158
|
}
|
|
151
|
-
function buildAutoSteps() {
|
|
152
|
-
return loadAutoFlow().phases.map((phase) => ({
|
|
153
|
-
id: phase.id,
|
|
154
|
-
status: "pending",
|
|
155
|
-
}));
|
|
156
|
-
}
|
|
157
159
|
function autoPhaseIds() {
|
|
158
|
-
return
|
|
160
|
+
return loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" }).phases.map((phase) => phase.id);
|
|
159
161
|
}
|
|
160
162
|
function validateAutoPhaseId(phaseId) {
|
|
161
163
|
const normalized = normalizeAutoPhaseId(phaseId);
|
|
162
164
|
if (!autoPhaseIds().includes(normalized)) {
|
|
163
|
-
throw new TaskRunnerError(`Unknown auto phase: ${phaseId}\nUse 'agentweaver auto --help-phases' or '/help auto' to list valid phases.`);
|
|
165
|
+
throw new TaskRunnerError(`Unknown auto-golang phase: ${phaseId}\nUse 'agentweaver auto-golang --help-phases' or '/help auto-golang' to list valid phases.`);
|
|
164
166
|
}
|
|
165
167
|
return normalized;
|
|
166
168
|
}
|
|
167
|
-
function
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
maxReviewIterations,
|
|
177
|
-
updatedAt: nowIso8601(),
|
|
178
|
-
steps: buildAutoSteps(),
|
|
179
|
-
executionState: {
|
|
180
|
-
flowKind: autoFlow.kind,
|
|
181
|
-
flowVersion: autoFlow.version,
|
|
182
|
-
terminated: false,
|
|
183
|
-
phases: [],
|
|
184
|
-
},
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
function loadAutoPipelineState(config) {
|
|
188
|
-
const filePath = autoStateFile(config.taskKey);
|
|
189
|
-
if (!existsSync(filePath)) {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
let raw;
|
|
193
|
-
try {
|
|
194
|
-
raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
169
|
+
function buildFlowResumeDetails(state) {
|
|
170
|
+
const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
|
|
171
|
+
const lines = [
|
|
172
|
+
"Interrupted run found.",
|
|
173
|
+
`Current step: ${currentStep}`,
|
|
174
|
+
`Updated: ${state.updatedAt}`,
|
|
175
|
+
];
|
|
176
|
+
if (state.launchProfile) {
|
|
177
|
+
lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
|
|
195
178
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
if (!raw || typeof raw !== "object") {
|
|
200
|
-
throw new TaskRunnerError(`Invalid auto state file format: ${filePath}`);
|
|
201
|
-
}
|
|
202
|
-
const state = raw;
|
|
203
|
-
if (state.schemaVersion !== AUTO_STATE_SCHEMA_VERSION) {
|
|
204
|
-
throw new TaskRunnerError(`Unsupported auto state schema in ${filePath}: ${state.schemaVersion}`);
|
|
205
|
-
}
|
|
206
|
-
if (!state.executionState) {
|
|
207
|
-
const autoFlow = loadAutoFlow();
|
|
208
|
-
state.executionState = {
|
|
209
|
-
flowKind: autoFlow.kind,
|
|
210
|
-
flowVersion: autoFlow.version,
|
|
211
|
-
terminated: false,
|
|
212
|
-
phases: [],
|
|
213
|
-
};
|
|
179
|
+
if (state.lastError) {
|
|
180
|
+
lines.push(`Last error: ${state.lastError.message ?? "-"} (exit ${state.lastError.returnCode ?? "-"})`);
|
|
214
181
|
}
|
|
215
|
-
|
|
216
|
-
return state;
|
|
182
|
+
return lines.join("\n");
|
|
217
183
|
}
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
184
|
+
function launchProfileSelectionForm() {
|
|
185
|
+
const defaultExecutor = DEFAULT_LAUNCH_PROFILE.executor;
|
|
186
|
+
return {
|
|
187
|
+
formId: "flow-launch-profile",
|
|
188
|
+
title: "Настройки запуска LLM",
|
|
189
|
+
description: `Выберите executor для запуска flow. Текущий default: ${defaultExecutor}.`,
|
|
190
|
+
submitLabel: "Continue",
|
|
191
|
+
fields: [
|
|
192
|
+
{
|
|
193
|
+
id: "executor",
|
|
194
|
+
type: "single-select",
|
|
195
|
+
label: "Executor",
|
|
196
|
+
required: true,
|
|
197
|
+
default: "default",
|
|
198
|
+
options: [
|
|
199
|
+
{ value: "default", label: `default (${defaultExecutor})` },
|
|
200
|
+
...LLM_EXECUTOR_IDS.map((id) => ({
|
|
201
|
+
value: id,
|
|
202
|
+
label: id === defaultExecutor ? `${id} [default]` : id,
|
|
203
|
+
})),
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
225
208
|
}
|
|
226
|
-
function
|
|
227
|
-
|
|
228
|
-
|
|
209
|
+
function launchModelSelectionForm(executor) {
|
|
210
|
+
const resolvedExecutor = executor === "default" ? DEFAULT_LAUNCH_PROFILE.executor : executor;
|
|
211
|
+
const defaultModel = defaultModelForExecutor(resolvedExecutor);
|
|
212
|
+
const options = executor === "default"
|
|
213
|
+
? [{ value: "default", label: `default (${DEFAULT_LAUNCH_PROFILE.model})` }]
|
|
214
|
+
: [
|
|
215
|
+
{ value: "default", label: `default (${defaultModel})` },
|
|
216
|
+
...ALLOWED_MODELS_BY_EXECUTOR[executor].map((model) => ({
|
|
217
|
+
value: model,
|
|
218
|
+
label: model === defaultModel ? `${model} [default]` : model,
|
|
219
|
+
})),
|
|
220
|
+
];
|
|
221
|
+
return {
|
|
222
|
+
formId: "flow-launch-model",
|
|
223
|
+
title: "Настройки запуска LLM",
|
|
224
|
+
description: `Выберите модель для запуска flow. Текущий default для ${resolvedExecutor}: ${defaultModel}.`,
|
|
225
|
+
submitLabel: "Start",
|
|
226
|
+
fields: [
|
|
227
|
+
{
|
|
228
|
+
id: "model",
|
|
229
|
+
type: "single-select",
|
|
230
|
+
label: "Model",
|
|
231
|
+
required: true,
|
|
232
|
+
default: "default",
|
|
233
|
+
options,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
229
237
|
}
|
|
230
|
-
function
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
238
|
+
async function requestInteractiveLaunchProfile(requestUserInput) {
|
|
239
|
+
const executorFormResult = await requestUserInput(launchProfileSelectionForm());
|
|
240
|
+
const rawExecutor = String(executorFormResult.values.executor ?? "default");
|
|
241
|
+
const executor = rawExecutor === "default" ? "default" : LLM_EXECUTOR_IDS.find((id) => id === rawExecutor);
|
|
242
|
+
if (!executor) {
|
|
243
|
+
throw new TaskRunnerError(`Unsupported launch executor '${rawExecutor}'.`);
|
|
244
|
+
}
|
|
245
|
+
const modelFormResult = await requestUserInput(launchModelSelectionForm(executor));
|
|
246
|
+
const rawModel = String(modelFormResult.values.model ?? "default").trim();
|
|
247
|
+
return resolveLaunchProfile({
|
|
248
|
+
executor,
|
|
249
|
+
model: rawModel.length > 0 ? rawModel : "default",
|
|
250
|
+
}, DEFAULT_LAUNCH_PROFILE);
|
|
237
251
|
}
|
|
238
|
-
function
|
|
239
|
-
return
|
|
252
|
+
function buildResolverContext(pipelineContext, flowParams, flowConstants, repeatVars, executionState) {
|
|
253
|
+
return {
|
|
254
|
+
flowParams,
|
|
255
|
+
flowConstants,
|
|
256
|
+
pipelineContext,
|
|
257
|
+
repeatVars,
|
|
258
|
+
executionState,
|
|
259
|
+
};
|
|
240
260
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
261
|
+
function resolveResumeChecks(step, context) {
|
|
262
|
+
return (step.expect ?? [])
|
|
263
|
+
.filter((expectation) => evaluateCondition(expectation.when, context))
|
|
264
|
+
.flatMap((expectation) => {
|
|
265
|
+
if (expectation.kind === "step-output") {
|
|
266
|
+
const value = resolveValue(expectation.value, context);
|
|
267
|
+
if (expectation.equals !== undefined) {
|
|
268
|
+
const expected = resolveValue(expectation.equals, context);
|
|
269
|
+
if (value !== expected) {
|
|
270
|
+
throw new TaskRunnerError(expectation.message);
|
|
271
|
+
}
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
if (!value) {
|
|
275
|
+
throw new TaskRunnerError(expectation.message);
|
|
276
|
+
}
|
|
277
|
+
return [];
|
|
246
278
|
}
|
|
247
|
-
|
|
248
|
-
|
|
279
|
+
if (expectation.kind === "require-artifacts") {
|
|
280
|
+
const value = resolveValue(expectation.paths, context);
|
|
281
|
+
if (!Array.isArray(value) || value.some((candidate) => typeof candidate !== "string")) {
|
|
282
|
+
throw new TaskRunnerError("Expectation 'require-artifacts' must resolve to string[]");
|
|
283
|
+
}
|
|
284
|
+
return [{ kind: "require-artifacts", paths: value, message: expectation.message }];
|
|
285
|
+
}
|
|
286
|
+
if (expectation.kind === "require-file") {
|
|
287
|
+
const value = resolveValue(expectation.path, context);
|
|
288
|
+
if (typeof value !== "string") {
|
|
289
|
+
throw new TaskRunnerError("Expectation 'require-file' must resolve to string");
|
|
290
|
+
}
|
|
291
|
+
return [{ kind: "require-file", path: value, message: expectation.message }];
|
|
292
|
+
}
|
|
293
|
+
const items = expectation.items.map((item) => {
|
|
294
|
+
const value = resolveValue(item.path, context);
|
|
295
|
+
if (typeof value !== "string") {
|
|
296
|
+
throw new TaskRunnerError("Expectation 'require-structured-artifacts' item path must resolve to string");
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
path: value,
|
|
300
|
+
schemaId: item.schemaId,
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
return [{ kind: "require-structured-artifacts", items, message: expectation.message }];
|
|
304
|
+
});
|
|
249
305
|
}
|
|
250
|
-
function
|
|
251
|
-
if (
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
if (state.executionState.terminated) {
|
|
255
|
-
return "completed";
|
|
256
|
-
}
|
|
257
|
-
if (state.steps.some((candidate) => candidate.status === "running")) {
|
|
258
|
-
return "running";
|
|
259
|
-
}
|
|
260
|
-
if (state.steps.some((candidate) => candidate.status === "pending")) {
|
|
261
|
-
return "pending";
|
|
262
|
-
}
|
|
263
|
-
if (state.steps.some((candidate) => candidate.status === "skipped")) {
|
|
264
|
-
return "completed";
|
|
306
|
+
function validateDeclarativePhaseResumeState(phase, phaseState, pipelineContext, flowParams, flowConstants, executionState) {
|
|
307
|
+
if (phaseState.status === "done") {
|
|
308
|
+
return;
|
|
265
309
|
}
|
|
266
|
-
|
|
267
|
-
|
|
310
|
+
for (const [stepIndex, step] of phase.steps.entries()) {
|
|
311
|
+
const stepState = phaseState.steps[stepIndex];
|
|
312
|
+
if (!stepState || stepState.status !== "done") {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const context = buildResolverContext(pipelineContext, flowParams, flowConstants, step.repeatVars, executionState);
|
|
316
|
+
const checks = resolveResumeChecks(step, context);
|
|
317
|
+
try {
|
|
318
|
+
runNodeChecks(checks);
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
throw new TaskRunnerError(`Resume is impossible for '${phase.id}:${step.id}' because required artifacts are missing or invalid. Use restart.\n${error.message}`);
|
|
322
|
+
}
|
|
268
323
|
}
|
|
269
|
-
return state.status;
|
|
270
324
|
}
|
|
271
|
-
function
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
];
|
|
279
|
-
if (state.lastError) {
|
|
280
|
-
lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
|
|
281
|
-
}
|
|
282
|
-
lines.push("");
|
|
283
|
-
for (const step of state.steps) {
|
|
284
|
-
lines.push(`[${step.status}] ${step.id}${step.note ? ` (${step.note})` : ""}`);
|
|
285
|
-
const phaseState = state.executionState.phases.find((candidate) => candidate.id === step.id);
|
|
286
|
-
for (const childStep of phaseState?.steps ?? []) {
|
|
287
|
-
lines.push(` - [${childStep.status}] ${childStep.id}`);
|
|
325
|
+
function validateDeclarativeFlowResumeState(flowEntry, config, state, launchProfile, runtime = runtimeServices) {
|
|
326
|
+
if (state.launchProfile) {
|
|
327
|
+
if (!launchProfile) {
|
|
328
|
+
throw new TaskRunnerError("Resume is impossible because launch profile is missing. Use restart.");
|
|
329
|
+
}
|
|
330
|
+
if (state.launchProfile.fingerprint !== launchProfile.fingerprint) {
|
|
331
|
+
throw new TaskRunnerError(`Resume is impossible because launch profile changed (${state.launchProfile.executor}/${state.launchProfile.model} -> ${launchProfile.executor}/${launchProfile.model}). Use restart.`);
|
|
288
332
|
}
|
|
289
333
|
}
|
|
290
|
-
if (
|
|
291
|
-
|
|
334
|
+
if (flowRequiresTaskScope(flowEntry) && !config.jiraRef) {
|
|
335
|
+
throw new TaskRunnerError("Resume is impossible because Jira context is missing for this flow state. Use restart.");
|
|
292
336
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
337
|
+
const pipelineContext = createPipelineContext({
|
|
338
|
+
issueKey: config.taskKey,
|
|
339
|
+
jiraRef: config.jiraRef,
|
|
340
|
+
dryRun: config.dryRun,
|
|
341
|
+
verbose: config.verbose,
|
|
342
|
+
...(config.mdLang !== undefined ? { mdLang: config.mdLang } : {}),
|
|
343
|
+
runtime,
|
|
344
|
+
requestUserInput: requestUserInputInTerminal,
|
|
345
|
+
});
|
|
346
|
+
const flowParams = defaultDeclarativeFlowParams(config, false, launchProfile ? { launchProfile } : {});
|
|
347
|
+
for (const phase of flowEntry.flow.phases) {
|
|
348
|
+
const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
|
|
298
349
|
if (!phaseState) {
|
|
299
350
|
continue;
|
|
300
351
|
}
|
|
301
|
-
|
|
302
|
-
step.startedAt = phaseState.startedAt ?? null;
|
|
303
|
-
step.finishedAt = phaseState.finishedAt ?? null;
|
|
304
|
-
step.note = null;
|
|
305
|
-
if (phaseState.status === "skipped") {
|
|
306
|
-
step.note = "condition not met";
|
|
307
|
-
step.returnCode ??= 0;
|
|
308
|
-
}
|
|
309
|
-
else if (phaseState.status === "done") {
|
|
310
|
-
step.returnCode ??= 0;
|
|
311
|
-
if (state.executionState.terminated && state.executionState.terminationReason?.startsWith(`Stopped by ${step.id}:`)) {
|
|
312
|
-
step.note = "stop condition met";
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
state.currentStep = findCurrentExecutionStep(state);
|
|
317
|
-
state.status = deriveAutoPipelineStatus(state);
|
|
318
|
-
}
|
|
319
|
-
function buildAutoResumeDetails(state) {
|
|
320
|
-
const currentStep = findCurrentExecutionStep(state) ?? state.currentStep ?? "-";
|
|
321
|
-
const lines = [
|
|
322
|
-
"Interrupted auto run found.",
|
|
323
|
-
`Current step: ${currentStep}`,
|
|
324
|
-
`Updated: ${state.updatedAt}`,
|
|
325
|
-
];
|
|
326
|
-
if (state.lastError) {
|
|
327
|
-
lines.push(`Last error: ${state.lastError.message ?? "-"} (exit ${state.lastError.returnCode ?? "-"})`);
|
|
352
|
+
validateDeclarativePhaseResumeState(phase, phaseState, pipelineContext, flowParams, flowEntry.flow.constants, state.executionState);
|
|
328
353
|
}
|
|
329
|
-
return lines.join("\n");
|
|
330
354
|
}
|
|
331
|
-
function
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
"Interrupted run found.",
|
|
335
|
-
`Current step: ${currentStep}`,
|
|
336
|
-
`Updated: ${state.updatedAt}`,
|
|
337
|
-
];
|
|
338
|
-
if (state.lastError) {
|
|
339
|
-
lines.push(`Last error: ${state.lastError.message ?? "-"} (exit ${state.lastError.returnCode ?? "-"})`);
|
|
355
|
+
function scopeWithRestoredJiraContext(scope, state) {
|
|
356
|
+
if (scope.jiraRef || !state?.jiraRef?.trim()) {
|
|
357
|
+
return scope;
|
|
340
358
|
}
|
|
341
|
-
return
|
|
359
|
+
return attachJiraContext(scope, state.jiraRef);
|
|
342
360
|
}
|
|
343
361
|
function lookupInteractiveFlowResume(flowEntry, currentScope) {
|
|
344
362
|
const directState = loadFlowRunState(currentScope.scopeKey, flowEntry.id);
|
|
345
363
|
if (directState && hasResumableFlowState(directState)) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
364
|
+
try {
|
|
365
|
+
const effectiveScope = scopeWithRestoredJiraContext(currentScope, directState);
|
|
366
|
+
const baseConfig = buildBaseConfig(flowEntry.id, {
|
|
367
|
+
...(effectiveScope.jiraRef ? { jiraRef: effectiveScope.jiraRef } : {}),
|
|
368
|
+
scopeName: effectiveScope.scopeKey,
|
|
369
|
+
});
|
|
370
|
+
const config = buildRuntimeConfig(baseConfig, effectiveScope);
|
|
371
|
+
validateDeclarativeFlowResumeState(flowEntry, config, directState, directState.launchProfile);
|
|
372
|
+
return {
|
|
373
|
+
resumeAvailable: true,
|
|
374
|
+
hasExistingState: true,
|
|
375
|
+
details: buildFlowResumeDetails(directState),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
return {
|
|
380
|
+
resumeAvailable: false,
|
|
381
|
+
hasExistingState: true,
|
|
382
|
+
details: `Interrupted run found, but resume is unavailable.\n${error.message}`,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
351
385
|
}
|
|
352
386
|
return {
|
|
353
387
|
resumeAvailable: false,
|
|
@@ -355,37 +389,17 @@ function lookupInteractiveFlowResume(flowEntry, currentScope) {
|
|
|
355
389
|
};
|
|
356
390
|
}
|
|
357
391
|
function printAutoPhasesHelp() {
|
|
358
|
-
const phaseLines = ["Available auto phases:", "", ...autoPhaseIds()];
|
|
359
|
-
phaseLines.push("", "You can resume auto from a phase with:", "agentweaver auto --from <phase> <jira>", "or in interactive mode:", "/auto --from <phase>");
|
|
360
|
-
printPanel("Auto Phases", phaseLines.join("\n"), "magenta");
|
|
392
|
+
const phaseLines = ["Available auto-golang phases:", "", ...autoPhaseIds()];
|
|
393
|
+
phaseLines.push("", "You can resume auto-golang from a phase with:", "agentweaver auto-golang --from <phase> <jira>", "or in interactive mode:", "/auto-golang --from <phase>");
|
|
394
|
+
printPanel("Auto-Golang Phases", phaseLines.join("\n"), "magenta");
|
|
361
395
|
}
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (!line || line.startsWith("#")) {
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
if (line.startsWith("export ")) {
|
|
373
|
-
line = line.slice(7).trim();
|
|
374
|
-
}
|
|
375
|
-
const separatorIndex = line.indexOf("=");
|
|
376
|
-
if (separatorIndex < 0) {
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
const key = line.slice(0, separatorIndex).trim();
|
|
380
|
-
if (!key || process.env[key] !== undefined) {
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
let value = line.slice(separatorIndex + 1).trim();
|
|
384
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
385
|
-
value = value.slice(1, -1);
|
|
386
|
-
}
|
|
387
|
-
process.env[key] = value;
|
|
388
|
-
}
|
|
396
|
+
function autoCommonPhaseIds() {
|
|
397
|
+
return loadDeclarativeFlow({ source: "built-in", fileName: "auto-common.json" }).phases.map((phase) => phase.id);
|
|
398
|
+
}
|
|
399
|
+
function printAutoCommonPhasesHelp() {
|
|
400
|
+
const phaseLines = ["Available auto-common phases:", "", ...autoCommonPhaseIds()];
|
|
401
|
+
phaseLines.push("", "You can run auto-common with:", "agentweaver auto-common <jira>");
|
|
402
|
+
printPanel("Auto-Common Phases", phaseLines.join("\n"), "magenta");
|
|
389
403
|
}
|
|
390
404
|
function nextReviewIterationForTask(taskKey) {
|
|
391
405
|
let maxIndex = 0;
|
|
@@ -397,33 +411,14 @@ function nextReviewIterationForTask(taskKey) {
|
|
|
397
411
|
if (!entry.isFile()) {
|
|
398
412
|
continue;
|
|
399
413
|
}
|
|
400
|
-
const match = REVIEW_FILE_RE.exec(entry.name)
|
|
414
|
+
const match = REVIEW_FILE_RE.exec(entry.name);
|
|
401
415
|
if (match && match[1] === taskKey) {
|
|
402
416
|
maxIndex = Math.max(maxIndex, Number.parseInt(match[2] ?? "0", 10));
|
|
403
417
|
}
|
|
404
418
|
}
|
|
405
419
|
return maxIndex + 1;
|
|
406
420
|
}
|
|
407
|
-
function latestReviewReplyIteration(taskKey) {
|
|
408
|
-
let maxIndex = null;
|
|
409
|
-
const workspaceDir = scopeWorkspaceDir(taskKey);
|
|
410
|
-
if (!existsSync(workspaceDir)) {
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
for (const entry of readdirSync(workspaceDir, { withFileTypes: true })) {
|
|
414
|
-
if (!entry.isFile()) {
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
const match = REVIEW_REPLY_FILE_RE.exec(entry.name);
|
|
418
|
-
if (match && match[1] === taskKey) {
|
|
419
|
-
const current = Number.parseInt(match[2] ?? "0", 10);
|
|
420
|
-
maxIndex = maxIndex === null ? current : Math.max(maxIndex, current);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return maxIndex;
|
|
424
|
-
}
|
|
425
421
|
function buildBaseConfig(command, options = {}) {
|
|
426
|
-
const homeDir = agentweaverHome(PACKAGE_ROOT);
|
|
427
422
|
return {
|
|
428
423
|
command,
|
|
429
424
|
jiraRef: options.jiraRef ?? null,
|
|
@@ -431,12 +426,9 @@ function buildBaseConfig(command, options = {}) {
|
|
|
431
426
|
reviewFixPoints: options.reviewFixPoints ?? null,
|
|
432
427
|
extraPrompt: options.extraPrompt ?? null,
|
|
433
428
|
autoFromPhase: options.autoFromPhase ? validateAutoPhaseId(options.autoFromPhase) : null,
|
|
429
|
+
mdLang: options.mdLang ?? null,
|
|
434
430
|
dryRun: options.dryRun ?? false,
|
|
435
431
|
verbose: options.verbose ?? false,
|
|
436
|
-
dockerComposeFile: defaultDockerComposeFile(PACKAGE_ROOT),
|
|
437
|
-
runGoTestsScript: path.join(homeDir, "run_go_tests.py"),
|
|
438
|
-
runGoLinterScript: path.join(homeDir, "run_go_linter.py"),
|
|
439
|
-
runGoCoverageScript: path.join(homeDir, "run_go_coverage.sh"),
|
|
440
432
|
};
|
|
441
433
|
}
|
|
442
434
|
function commandRequiresTask(command) {
|
|
@@ -444,17 +436,20 @@ function commandRequiresTask(command) {
|
|
|
444
436
|
command === "bug-analyze" ||
|
|
445
437
|
command === "bug-fix" ||
|
|
446
438
|
command === "mr-description" ||
|
|
447
|
-
command === "
|
|
448
|
-
command === "auto" ||
|
|
439
|
+
command === "auto-golang" ||
|
|
440
|
+
command === "auto-common" ||
|
|
449
441
|
command === "auto-status" ||
|
|
450
442
|
command === "auto-reset");
|
|
451
443
|
}
|
|
452
444
|
function commandSupportsProjectScope(command) {
|
|
453
|
-
return (command === "
|
|
445
|
+
return (command === "git-commit" ||
|
|
446
|
+
command === "gitlab-diff-review" ||
|
|
454
447
|
command === "gitlab-review" ||
|
|
448
|
+
command === "task-describe" ||
|
|
455
449
|
command === "implement" ||
|
|
456
450
|
command === "review" ||
|
|
457
451
|
command === "review-fix" ||
|
|
452
|
+
command === "review-loop" ||
|
|
458
453
|
command === "run-go-tests-loop" ||
|
|
459
454
|
command === "run-go-linter-loop");
|
|
460
455
|
}
|
|
@@ -503,41 +498,40 @@ function checkPrerequisites(config) {
|
|
|
503
498
|
config.command === "run-go-linter-loop") {
|
|
504
499
|
resolveCmd("codex", "CODEX_BIN");
|
|
505
500
|
}
|
|
506
|
-
if (config.command === "review" || config.command === "gitlab-diff-review") {
|
|
507
|
-
resolveCmd("claude", "CLAUDE_BIN");
|
|
508
|
-
}
|
|
509
501
|
}
|
|
510
502
|
function checkAutoPrerequisites(config) {
|
|
511
503
|
resolveCmd("codex", "CODEX_BIN");
|
|
512
|
-
resolveCmd("claude", "CLAUDE_BIN");
|
|
513
504
|
}
|
|
514
505
|
function autoFlowParams(config, forceRefreshSummary = false) {
|
|
515
506
|
return {
|
|
516
507
|
jiraApiUrl: config.jiraApiUrl,
|
|
517
508
|
taskKey: config.taskKey,
|
|
518
|
-
dockerComposeFile: config.dockerComposeFile,
|
|
519
|
-
runGoTestsScript: config.runGoTestsScript,
|
|
520
|
-
runGoLinterScript: config.runGoLinterScript,
|
|
521
|
-
runGoCoverageScript: config.runGoCoverageScript,
|
|
522
509
|
extraPrompt: config.extraPrompt,
|
|
523
510
|
reviewFixPoints: config.reviewFixPoints,
|
|
524
511
|
forceRefresh: forceRefreshSummary,
|
|
512
|
+
mdLang: config.mdLang,
|
|
513
|
+
runGoTestsScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_tests.py"),
|
|
514
|
+
runGoLinterScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_linter.py"),
|
|
515
|
+
runGoTestsIteration: nextArtifactIteration(config.taskKey, "run-go-tests-result", "json"),
|
|
516
|
+
runGoLinterIteration: nextArtifactIteration(config.taskKey, "run-go-linter-result", "json"),
|
|
525
517
|
};
|
|
526
518
|
}
|
|
527
519
|
const FLOW_DESCRIPTIONS = {
|
|
528
|
-
auto: "
|
|
529
|
-
"bug-analyze": "
|
|
530
|
-
"
|
|
531
|
-
"gitlab-review": "
|
|
532
|
-
"
|
|
533
|
-
"
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
"
|
|
540
|
-
"
|
|
520
|
+
"auto-golang": "Full task pipeline: planning, implementation, checks, review, review replies, and repeated iterations until ready to merge.",
|
|
521
|
+
"bug-analyze": "Analyzes bug from Jira and creates structured artifacts: root cause hypothesis, fix design, and implementation plan.",
|
|
522
|
+
"git-commit": "Collects git status/diff, generates commit message via LLM, allows file selection and commit confirmation.",
|
|
523
|
+
"gitlab-diff-review": "Requests GitLab MR URL via user-input, downloads merge request diff via API, and runs code review with markdown and structured JSON artifacts.",
|
|
524
|
+
"gitlab-review": "Requests GitLab MR URL via user-input, downloads code review comments via API, and saves markdown plus structured JSON artifact.",
|
|
525
|
+
"bug-fix": "Takes bug-analyze results as source of truth and implements the bug fix in code.",
|
|
526
|
+
"mr-description": "Prepares a brief intent description for a merge request based on the task and current changes.",
|
|
527
|
+
plan: "Loads task from Jira and creates design, implementation plan, and QA plan in structured JSON and markdown.",
|
|
528
|
+
"task-describe": "Builds a brief task description either from Jira or from quick user-input without Jira.",
|
|
529
|
+
implement: "Implements the task from approved design/plan artifacts and runs post-verify builds if needed.",
|
|
530
|
+
review: "Runs code review of current changes, validates structured findings, then prepares a reply to comments.",
|
|
531
|
+
"review-fix": "Fixes issues after review-reply, updates code, and runs mandatory checks after modifications.",
|
|
532
|
+
"review-loop": "Iteratively runs review and review-fix cycles up to 5 times until ready-to-merge is achieved.",
|
|
533
|
+
"run-go-tests-loop": "Cycles through `./run_go_tests.py` locally, analyzes the last error, and fixes code until successful or attempts exhausted.",
|
|
534
|
+
"run-go-linter-loop": "Cycles through `./run_go_linter.py` locally, fixes linter or generation issues, and retries until success.",
|
|
541
535
|
};
|
|
542
536
|
function flowDescription(id) {
|
|
543
537
|
return FLOW_DESCRIPTIONS[id] ?? "Описание для этого flow пока не задано.";
|
|
@@ -549,6 +543,7 @@ function interactiveFlowDefinition(entry) {
|
|
|
549
543
|
label: entry.id,
|
|
550
544
|
description: flowDescription(entry.id),
|
|
551
545
|
source: entry.source,
|
|
546
|
+
treePath: [...entry.treePath],
|
|
552
547
|
...(entry.source === "project-local" ? { sourcePath: entry.absolutePath } : {}),
|
|
553
548
|
phases: flow.phases.map((phase) => ({
|
|
554
549
|
id: phase.id,
|
|
@@ -598,13 +593,14 @@ function findCurrentFlowExecutionStep(state) {
|
|
|
598
593
|
}
|
|
599
594
|
return null;
|
|
600
595
|
}
|
|
601
|
-
async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart") {
|
|
596
|
+
async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
|
|
602
597
|
const context = createPipelineContext({
|
|
603
598
|
issueKey: config.taskKey,
|
|
604
599
|
jiraRef: config.jiraRef,
|
|
605
600
|
dryRun: config.dryRun,
|
|
606
601
|
verbose: config.verbose,
|
|
607
|
-
|
|
602
|
+
...(config.mdLang !== undefined ? { mdLang: config.mdLang } : {}),
|
|
603
|
+
runtime,
|
|
608
604
|
...(setSummary ? { setSummary } : {}),
|
|
609
605
|
requestUserInput,
|
|
610
606
|
});
|
|
@@ -617,13 +613,25 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requ
|
|
|
617
613
|
};
|
|
618
614
|
let persistedState = launchMode === "resume" ? loadFlowRunState(config.scope.scopeKey, flowId) : null;
|
|
619
615
|
if (persistedState && launchMode === "resume") {
|
|
616
|
+
validateDeclarativeFlowResumeState({
|
|
617
|
+
id: flowId,
|
|
618
|
+
source: flow.source,
|
|
619
|
+
fileName: flow.fileName,
|
|
620
|
+
absolutePath: flow.absolutePath,
|
|
621
|
+
treePath: [],
|
|
622
|
+
flow,
|
|
623
|
+
}, config, persistedState, overrides.launchProfile, runtime);
|
|
620
624
|
persistedState = prepareFlowStateForResume(persistedState);
|
|
621
625
|
}
|
|
622
626
|
else if (launchMode === "restart") {
|
|
623
627
|
resetFlowRunState(config.scope.scopeKey, flowId);
|
|
624
628
|
}
|
|
625
629
|
const executionState = persistedState?.executionState ?? initialExecutionState;
|
|
626
|
-
const state = persistedState
|
|
630
|
+
const state = persistedState
|
|
631
|
+
?? createFlowRunState(config.scope.scopeKey, flowId, executionState, config.jiraRef, overrides.launchProfile);
|
|
632
|
+
if (overrides.launchProfile) {
|
|
633
|
+
state.launchProfile = overrides.launchProfile;
|
|
634
|
+
}
|
|
627
635
|
state.status = "running";
|
|
628
636
|
state.lastError = null;
|
|
629
637
|
state.currentStep = findCurrentFlowExecutionStep(state);
|
|
@@ -669,10 +677,17 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requ
|
|
|
669
677
|
throw error;
|
|
670
678
|
}
|
|
671
679
|
}
|
|
672
|
-
async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart") {
|
|
673
|
-
|
|
680
|
+
async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
|
|
681
|
+
const mergedFlowParams = {
|
|
682
|
+
...defaultDeclarativeFlowParams(config, false, overrides),
|
|
683
|
+
...flowParams,
|
|
684
|
+
};
|
|
685
|
+
await runDeclarativeFlowByRef(config.command, { source: "built-in", fileName }, config, mergedFlowParams, overrides, requestUserInput, setSummary, launchMode, runtime);
|
|
674
686
|
}
|
|
675
|
-
function defaultDeclarativeFlowParams(config, forceRefreshSummary = false) {
|
|
687
|
+
function defaultDeclarativeFlowParams(config, forceRefreshSummary = false, overrides = {}) {
|
|
688
|
+
const iteration = nextReviewIterationForTask(config.taskKey);
|
|
689
|
+
const latestIteration = latestArtifactIteration(config.taskKey, "review");
|
|
690
|
+
const launchProfile = overrides.launchProfile ?? resolveLaunchProfile({ executor: "default", model: "default" }, DEFAULT_LAUNCH_PROFILE);
|
|
676
691
|
return {
|
|
677
692
|
taskKey: config.taskKey,
|
|
678
693
|
jiraRef: config.jiraRef,
|
|
@@ -680,12 +695,20 @@ function defaultDeclarativeFlowParams(config, forceRefreshSummary = false) {
|
|
|
680
695
|
jiraApiUrl: config.jiraApiUrl,
|
|
681
696
|
jiraTaskFile: config.jiraTaskFile,
|
|
682
697
|
scopeKey: config.scope.scopeKey,
|
|
683
|
-
|
|
684
|
-
runGoTestsScript: config.runGoTestsScript,
|
|
685
|
-
runGoLinterScript: config.runGoLinterScript,
|
|
686
|
-
runGoCoverageScript: config.runGoCoverageScript,
|
|
698
|
+
workspaceDir: scopeWorkspaceDir(config.taskKey),
|
|
687
699
|
extraPrompt: config.extraPrompt,
|
|
688
700
|
reviewFixPoints: config.reviewFixPoints,
|
|
701
|
+
mdLang: config.mdLang,
|
|
702
|
+
llmExecutor: launchProfile.executor,
|
|
703
|
+
llmModel: launchProfile.model,
|
|
704
|
+
launchProfile,
|
|
705
|
+
iteration,
|
|
706
|
+
latestIteration,
|
|
707
|
+
taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
|
|
708
|
+
designIteration: nextArtifactIteration(config.taskKey, "design"),
|
|
709
|
+
planIteration: nextArtifactIteration(config.taskKey, "plan"),
|
|
710
|
+
qaIteration: nextArtifactIteration(config.taskKey, "qa"),
|
|
711
|
+
...(latestIteration !== null ? { reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration) } : {}),
|
|
689
712
|
forceRefresh: forceRefreshSummary,
|
|
690
713
|
};
|
|
691
714
|
}
|
|
@@ -710,88 +733,13 @@ function flowRequiresTaskScope(entry) {
|
|
|
710
733
|
}
|
|
711
734
|
return valueReferencesTaskScopeParams(entry.flow.phases);
|
|
712
735
|
}
|
|
713
|
-
async function runAutoPhaseViaSpec(config, phaseId, executionState, state, setSummary, forceRefreshSummary = false) {
|
|
714
|
-
const context = createPipelineContext({
|
|
715
|
-
issueKey: config.taskKey,
|
|
716
|
-
jiraRef: config.jiraRef,
|
|
717
|
-
dryRun: config.dryRun,
|
|
718
|
-
verbose: config.verbose,
|
|
719
|
-
runtime: runtimeServices,
|
|
720
|
-
...(setSummary ? { setSummary } : {}),
|
|
721
|
-
requestUserInput: requestUserInputInTerminal,
|
|
722
|
-
});
|
|
723
|
-
const autoFlow = loadAutoFlow();
|
|
724
|
-
const phase = findPhaseById(autoFlow.phases, phaseId);
|
|
725
|
-
publishFlowState("auto", executionState);
|
|
726
|
-
try {
|
|
727
|
-
const result = await runExpandedPhase(phase, context, autoFlowParams(config, forceRefreshSummary), autoFlow.constants, {
|
|
728
|
-
executionState,
|
|
729
|
-
flowKind: autoFlow.kind,
|
|
730
|
-
flowVersion: autoFlow.version,
|
|
731
|
-
onStateChange: async (state) => {
|
|
732
|
-
publishFlowState("auto", state);
|
|
733
|
-
},
|
|
734
|
-
onStepStart: async (_phase, step) => {
|
|
735
|
-
if (!state) {
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
state.currentStep = `${phaseId}:${step.id}`;
|
|
739
|
-
saveAutoPipelineState(state);
|
|
740
|
-
},
|
|
741
|
-
});
|
|
742
|
-
if (state) {
|
|
743
|
-
state.executionState = result.executionState;
|
|
744
|
-
syncAndSaveAutoPipelineState(state);
|
|
745
|
-
}
|
|
746
|
-
return result.status === "skipped" ? "skipped" : "done";
|
|
747
|
-
}
|
|
748
|
-
catch (error) {
|
|
749
|
-
if (!config.dryRun) {
|
|
750
|
-
const output = String(error.output ?? "");
|
|
751
|
-
if (output.trim()) {
|
|
752
|
-
printError("Build verification failed");
|
|
753
|
-
printSummary("Build Failure Summary", await summarizeBuildFailure(output));
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
throw error;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
function rewindAutoPipelineState(state, phaseId) {
|
|
760
|
-
const targetPhaseId = validateAutoPhaseId(phaseId);
|
|
761
|
-
let phaseSeen = false;
|
|
762
|
-
for (const step of state.steps) {
|
|
763
|
-
if (step.id === targetPhaseId) {
|
|
764
|
-
phaseSeen = true;
|
|
765
|
-
}
|
|
766
|
-
if (phaseSeen) {
|
|
767
|
-
step.status = "pending";
|
|
768
|
-
step.startedAt = null;
|
|
769
|
-
step.finishedAt = null;
|
|
770
|
-
step.returnCode = null;
|
|
771
|
-
step.note = null;
|
|
772
|
-
}
|
|
773
|
-
else {
|
|
774
|
-
step.status = "done";
|
|
775
|
-
step.returnCode = 0;
|
|
776
|
-
step.finishedAt ??= nowIso8601();
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
state.status = "pending";
|
|
780
|
-
state.currentStep = null;
|
|
781
|
-
state.lastError = null;
|
|
782
|
-
const targetIndex = state.executionState.phases.findIndex((phase) => phase.id === targetPhaseId);
|
|
783
|
-
if (targetIndex >= 0) {
|
|
784
|
-
state.executionState.phases = state.executionState.phases.slice(0, targetIndex);
|
|
785
|
-
}
|
|
786
|
-
state.executionState.terminated = false;
|
|
787
|
-
delete state.executionState.terminationReason;
|
|
788
|
-
}
|
|
789
736
|
async function summarizeBuildFailure(output) {
|
|
790
737
|
return summarizeBuildFailureViaPipeline(createPipelineContext({
|
|
791
738
|
issueKey: "build-failure-summary",
|
|
792
739
|
jiraRef: "build-failure-summary",
|
|
793
740
|
dryRun: false,
|
|
794
741
|
verbose: false,
|
|
742
|
+
mdLang: null,
|
|
795
743
|
runtime: runtimeServices,
|
|
796
744
|
requestUserInput: requestUserInputInTerminal,
|
|
797
745
|
}), output);
|
|
@@ -801,27 +749,78 @@ function requireJiraConfig(config) {
|
|
|
801
749
|
throw new TaskRunnerError(`Command '${config.command}' requires Jira context in the current project scope.`);
|
|
802
750
|
}
|
|
803
751
|
}
|
|
804
|
-
async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart") {
|
|
752
|
+
async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", launchProfile, runtime = runtimeServices) {
|
|
805
753
|
const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput)));
|
|
806
|
-
if (config.command === "auto") {
|
|
807
|
-
|
|
808
|
-
|
|
754
|
+
if (config.command === "auto-golang") {
|
|
755
|
+
requireJiraConfig(config);
|
|
756
|
+
checkAutoPrerequisites(config);
|
|
757
|
+
process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
|
|
758
|
+
process.env.JIRA_API_URL = config.jiraApiUrl;
|
|
759
|
+
process.env.JIRA_TASK_FILE = config.jiraTaskFile;
|
|
760
|
+
let effectiveLaunchMode = launchMode;
|
|
761
|
+
let effectiveLaunchProfile = launchProfile;
|
|
762
|
+
if (config.autoFromPhase) {
|
|
763
|
+
const flow = loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" });
|
|
764
|
+
const persistedState = loadFlowRunState(config.scope.scopeKey, "auto-golang");
|
|
765
|
+
if (!persistedState) {
|
|
766
|
+
throw new TaskRunnerError(`Cannot restart auto-golang from phase '${config.autoFromPhase}' because persisted flow state was not found.`);
|
|
767
|
+
}
|
|
768
|
+
rewindFlowRunStateToPhase(persistedState, flow.phases, config.autoFromPhase);
|
|
769
|
+
saveFlowRunState(persistedState);
|
|
770
|
+
effectiveLaunchMode = "resume";
|
|
771
|
+
effectiveLaunchProfile ??= persistedState.launchProfile;
|
|
772
|
+
printPanel("Auto-Golang Resume", `Auto-golang pipeline will continue from phase: ${config.autoFromPhase}`, "yellow");
|
|
809
773
|
}
|
|
810
|
-
await
|
|
774
|
+
await runDeclarativeFlowBySpecFile("auto-golang.json", config, autoFlowParams(config, forceRefreshSummary), effectiveLaunchProfile ? { launchProfile: effectiveLaunchProfile } : {}, requestUserInput, setSummary, effectiveLaunchMode, runtime);
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
if (config.command === "auto-common") {
|
|
778
|
+
requireJiraConfig(config);
|
|
779
|
+
checkAutoPrerequisites(config);
|
|
780
|
+
process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
|
|
781
|
+
process.env.JIRA_API_URL = config.jiraApiUrl;
|
|
782
|
+
process.env.JIRA_TASK_FILE = config.jiraTaskFile;
|
|
783
|
+
await runDeclarativeFlowBySpecFile("auto-common.json", config, autoFlowParams(config, forceRefreshSummary), launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
|
|
811
784
|
return false;
|
|
812
785
|
}
|
|
813
786
|
if (config.command === "auto-status") {
|
|
814
|
-
const state =
|
|
787
|
+
const state = loadFlowRunState(config.scope.scopeKey, "auto-golang");
|
|
815
788
|
if (!state) {
|
|
816
|
-
printPanel("Auto Status", `No
|
|
789
|
+
printPanel("Auto-Golang Status", `No flow state file found for ${config.taskKey}.`, "yellow");
|
|
817
790
|
return false;
|
|
818
791
|
}
|
|
819
|
-
|
|
792
|
+
const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
|
|
793
|
+
const phaseOrder = loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" }).phases;
|
|
794
|
+
const lines = [
|
|
795
|
+
`Issue: ${config.taskKey}`,
|
|
796
|
+
`Status: ${state.status}`,
|
|
797
|
+
`Current step: ${currentStep}`,
|
|
798
|
+
`Updated: ${state.updatedAt}`,
|
|
799
|
+
];
|
|
800
|
+
if (state.launchProfile) {
|
|
801
|
+
lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
|
|
802
|
+
}
|
|
803
|
+
if (state.lastError) {
|
|
804
|
+
lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
|
|
805
|
+
}
|
|
806
|
+
lines.push("");
|
|
807
|
+
for (const phase of phaseOrder) {
|
|
808
|
+
const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
|
|
809
|
+
lines.push(`[${phaseState?.status ?? "pending"}] ${phase.id}`);
|
|
810
|
+
for (const step of phase.steps) {
|
|
811
|
+
const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
|
|
812
|
+
lines.push(` - [${stepState?.status ?? "pending"}] ${step.id}`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (state.executionState.terminated) {
|
|
816
|
+
lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
|
|
817
|
+
}
|
|
818
|
+
printPanel("Auto-Golang Status", lines.join("\n"), "cyan");
|
|
820
819
|
return false;
|
|
821
820
|
}
|
|
822
821
|
if (config.command === "auto-reset") {
|
|
823
|
-
const removed =
|
|
824
|
-
printPanel("Auto Reset", removed ? `State file ${
|
|
822
|
+
const removed = resetFlowRunState(config.scope.scopeKey, "auto-golang");
|
|
823
|
+
printPanel("Auto-Golang Reset", removed ? `State file ${flowStateFile(config.scope.scopeKey, "auto-golang")} removed.` : "No flow state file found.", "yellow");
|
|
825
824
|
return false;
|
|
826
825
|
}
|
|
827
826
|
checkPrerequisites(config);
|
|
@@ -845,9 +844,13 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
845
844
|
await runDeclarativeFlowBySpecFile("plan.json", config, {
|
|
846
845
|
jiraApiUrl: config.jiraApiUrl,
|
|
847
846
|
taskKey: config.taskKey,
|
|
847
|
+
taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
|
|
848
|
+
designIteration: nextArtifactIteration(config.taskKey, "design"),
|
|
849
|
+
planIteration: nextArtifactIteration(config.taskKey, "plan"),
|
|
850
|
+
qaIteration: nextArtifactIteration(config.taskKey, "qa"),
|
|
848
851
|
extraPrompt: config.extraPrompt,
|
|
849
852
|
forceRefresh: forceRefreshSummary,
|
|
850
|
-
}, requestUserInput, setSummary, launchMode);
|
|
853
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
|
|
851
854
|
return false;
|
|
852
855
|
}
|
|
853
856
|
if (config.command === "bug-analyze") {
|
|
@@ -857,21 +860,27 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
857
860
|
process.stdout.write(`Resolved Jira API URL: ${config.jiraApiUrl}\n`);
|
|
858
861
|
process.stdout.write(`Saving Jira issue JSON to: ${config.jiraTaskFile}\n`);
|
|
859
862
|
}
|
|
860
|
-
await runDeclarativeFlowBySpecFile("bug-analyze.json", config, {
|
|
863
|
+
await runDeclarativeFlowBySpecFile("bugz/bug-analyze.json", config, {
|
|
861
864
|
jiraApiUrl: config.jiraApiUrl,
|
|
862
865
|
taskKey: config.taskKey,
|
|
866
|
+
taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
|
|
867
|
+
bugAnalyzeIteration: nextArtifactIteration(config.taskKey, "bug-analyze"),
|
|
868
|
+
bugFixDesignIteration: nextArtifactIteration(config.taskKey, "bug-fix-design"),
|
|
869
|
+
bugFixPlanIteration: nextArtifactIteration(config.taskKey, "bug-fix-plan"),
|
|
863
870
|
extraPrompt: config.extraPrompt,
|
|
864
871
|
forceRefresh: forceRefreshSummary,
|
|
865
|
-
}, requestUserInput, setSummary, launchMode);
|
|
872
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
|
|
866
873
|
return false;
|
|
867
874
|
}
|
|
868
875
|
if (config.command === "gitlab-review") {
|
|
869
876
|
const iteration = nextReviewIterationForTask(config.taskKey);
|
|
870
|
-
|
|
877
|
+
const gitlabReviewIteration = nextArtifactIteration(config.taskKey, "gitlab-review");
|
|
878
|
+
await runDeclarativeFlowBySpecFile("gitlab/gitlab-review.json", config, {
|
|
871
879
|
taskKey: config.taskKey,
|
|
872
880
|
iteration,
|
|
881
|
+
gitlabReviewIteration,
|
|
873
882
|
extraPrompt: config.extraPrompt,
|
|
874
|
-
}, requestUserInput, undefined, launchMode);
|
|
883
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
875
884
|
if (!config.dryRun) {
|
|
876
885
|
printSummary("GitLab Review", `Artifacts:\n${gitlabReviewFile(config.taskKey)}\n${gitlabReviewJsonFile(config.taskKey)}`);
|
|
877
886
|
}
|
|
@@ -879,11 +888,13 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
879
888
|
}
|
|
880
889
|
if (config.command === "gitlab-diff-review") {
|
|
881
890
|
const iteration = nextReviewIterationForTask(config.taskKey);
|
|
882
|
-
|
|
891
|
+
const gitlabDiffIteration = nextArtifactIteration(config.taskKey, "gitlab-diff");
|
|
892
|
+
await runDeclarativeFlowBySpecFile("gitlab/gitlab-diff-review.json", config, {
|
|
883
893
|
taskKey: config.taskKey,
|
|
884
894
|
iteration,
|
|
895
|
+
gitlabDiffIteration,
|
|
885
896
|
extraPrompt: config.extraPrompt,
|
|
886
|
-
}, requestUserInput, undefined, launchMode);
|
|
897
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
887
898
|
if (!config.dryRun) {
|
|
888
899
|
printSummary("GitLab Diff Review", `Artifacts:\n${gitlabDiffFile(config.taskKey)}\n${gitlabDiffJsonFile(config.taskKey)}\n${reviewFile(config.taskKey, iteration)}\n${reviewJsonFile(config.taskKey, iteration)}`);
|
|
889
900
|
}
|
|
@@ -898,28 +909,30 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
898
909
|
{ path: bugFixDesignJsonFile(config.taskKey), schemaId: "bug-fix-design/v1" },
|
|
899
910
|
{ path: bugFixPlanJsonFile(config.taskKey), schemaId: "bug-fix-plan/v1" },
|
|
900
911
|
], "Bug-fix mode requires valid structured artifacts from the bug analysis phase.");
|
|
901
|
-
await runDeclarativeFlowBySpecFile("bug-fix.json", config, {
|
|
912
|
+
await runDeclarativeFlowBySpecFile("bugz/bug-fix.json", config, {
|
|
902
913
|
taskKey: config.taskKey,
|
|
903
914
|
extraPrompt: config.extraPrompt,
|
|
904
|
-
}, requestUserInput, undefined, launchMode);
|
|
915
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
905
916
|
return false;
|
|
906
917
|
}
|
|
907
918
|
if (config.command === "mr-description") {
|
|
908
919
|
requireJiraConfig(config);
|
|
909
920
|
requireJiraTaskFile(config.jiraTaskFile);
|
|
910
|
-
await runDeclarativeFlowBySpecFile("mr-description.json", config, {
|
|
921
|
+
await runDeclarativeFlowBySpecFile("gitlab/mr-description.json", config, {
|
|
911
922
|
taskKey: config.taskKey,
|
|
923
|
+
iteration: nextArtifactIteration(config.taskKey, "mr-description"),
|
|
912
924
|
extraPrompt: config.extraPrompt,
|
|
913
|
-
}, requestUserInput, undefined, launchMode);
|
|
925
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
914
926
|
return false;
|
|
915
927
|
}
|
|
916
928
|
if (config.command === "task-describe") {
|
|
917
|
-
|
|
929
|
+
const iteration = nextArtifactIteration(config.taskKey, "jira-description");
|
|
918
930
|
await runDeclarativeFlowBySpecFile("task-describe.json", config, {
|
|
919
931
|
jiraApiUrl: config.jiraApiUrl,
|
|
920
932
|
taskKey: config.taskKey,
|
|
933
|
+
iteration,
|
|
921
934
|
extraPrompt: config.extraPrompt,
|
|
922
|
-
}, requestUserInput, undefined, launchMode);
|
|
935
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
923
936
|
return false;
|
|
924
937
|
}
|
|
925
938
|
if (config.command === "implement") {
|
|
@@ -932,7 +945,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
932
945
|
await runDeclarativeFlowBySpecFile("implement.json", config, {
|
|
933
946
|
taskKey: config.taskKey,
|
|
934
947
|
extraPrompt: config.extraPrompt,
|
|
935
|
-
}, requestUserInput, undefined, launchMode);
|
|
948
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode);
|
|
936
949
|
return false;
|
|
937
950
|
}
|
|
938
951
|
if (config.command === "review") {
|
|
@@ -943,142 +956,73 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
943
956
|
{ path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
|
|
944
957
|
{ path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
|
|
945
958
|
], "Review mode requires valid structured plan artifacts from the planning phase.");
|
|
946
|
-
await runDeclarativeFlowBySpecFile("review.json", config, {
|
|
959
|
+
await runDeclarativeFlowBySpecFile("review/review.json", config, {
|
|
947
960
|
taskKey: config.taskKey,
|
|
948
961
|
iteration,
|
|
949
962
|
extraPrompt: config.extraPrompt,
|
|
950
|
-
}, requestUserInput, undefined, launchMode);
|
|
963
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
951
964
|
}
|
|
952
965
|
else {
|
|
953
|
-
await runDeclarativeFlowBySpecFile("review-project.json", config, {
|
|
966
|
+
await runDeclarativeFlowBySpecFile("review/review-project.json", config, {
|
|
954
967
|
taskKey: config.taskKey,
|
|
955
968
|
iteration,
|
|
956
969
|
extraPrompt: config.extraPrompt,
|
|
957
|
-
}, requestUserInput, undefined, launchMode);
|
|
970
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
958
971
|
}
|
|
959
972
|
return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
|
|
960
973
|
}
|
|
961
974
|
if (config.command === "review-fix") {
|
|
962
|
-
const latestIteration =
|
|
975
|
+
const latestIteration = latestArtifactIteration(config.taskKey, "review");
|
|
963
976
|
if (latestIteration === null) {
|
|
964
|
-
throw new TaskRunnerError("Review-fix mode requires at least one review
|
|
977
|
+
throw new TaskRunnerError("Review-fix mode requires at least one review artifact.");
|
|
965
978
|
}
|
|
966
979
|
validateStructuredArtifacts([
|
|
967
980
|
{ path: reviewJsonFile(config.taskKey, latestIteration), schemaId: "review-findings/v1" },
|
|
968
|
-
{ path: reviewReplyJsonFile(config.taskKey, latestIteration), schemaId: "review-reply/v1" },
|
|
969
981
|
], "Review-fix mode requires valid structured review artifacts.");
|
|
970
|
-
await runDeclarativeFlowBySpecFile("review-fix.json", config, {
|
|
982
|
+
await runDeclarativeFlowBySpecFile("review/review-fix.json", config, {
|
|
971
983
|
taskKey: config.taskKey,
|
|
972
984
|
latestIteration,
|
|
973
985
|
reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration),
|
|
974
986
|
extraPrompt: config.extraPrompt,
|
|
975
987
|
reviewFixPoints: config.reviewFixPoints,
|
|
976
|
-
}, requestUserInput, undefined, launchMode);
|
|
988
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
977
989
|
return false;
|
|
978
990
|
}
|
|
991
|
+
if (config.command === "review-loop") {
|
|
992
|
+
const iteration = nextReviewIterationForTask(config.taskKey);
|
|
993
|
+
if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
|
|
994
|
+
requireJiraConfig(config);
|
|
995
|
+
validateStructuredArtifacts([
|
|
996
|
+
{ path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
|
|
997
|
+
{ path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
|
|
998
|
+
], "Review-loop mode requires valid structured plan artifacts from the planning phase.");
|
|
999
|
+
}
|
|
1000
|
+
await runDeclarativeFlowBySpecFile("review/review-loop.json", config, {
|
|
1001
|
+
taskKey: config.taskKey,
|
|
1002
|
+
iteration,
|
|
1003
|
+
extraPrompt: config.extraPrompt,
|
|
1004
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
1005
|
+
return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
|
|
1006
|
+
}
|
|
979
1007
|
if (config.command === "run-go-tests-loop" || config.command === "run-go-linter-loop") {
|
|
980
|
-
await runDeclarativeFlowBySpecFile(config.command === "run-go-tests-loop" ? "run-go-tests-loop.json" : "run-go-linter-loop.json", config, {
|
|
1008
|
+
await runDeclarativeFlowBySpecFile(config.command === "run-go-tests-loop" ? "go/run-go-tests-loop.json" : "go/run-go-linter-loop.json", config, {
|
|
981
1009
|
taskKey: config.taskKey,
|
|
982
|
-
runGoTestsScript:
|
|
983
|
-
runGoLinterScript:
|
|
1010
|
+
runGoTestsScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_tests.py"),
|
|
1011
|
+
runGoLinterScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_linter.py"),
|
|
1012
|
+
runGoTestsIteration: nextArtifactIteration(config.taskKey, "run-go-tests-result", "json"),
|
|
1013
|
+
runGoLinterIteration: nextArtifactIteration(config.taskKey, "run-go-linter-result", "json"),
|
|
984
1014
|
extraPrompt: config.extraPrompt,
|
|
985
|
-
}, requestUserInput, undefined, launchMode);
|
|
1015
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
986
1016
|
return false;
|
|
987
1017
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const executionState = {
|
|
995
|
-
flowKind: autoFlow.kind,
|
|
996
|
-
flowVersion: autoFlow.version,
|
|
997
|
-
terminated: false,
|
|
998
|
-
phases: [],
|
|
999
|
-
};
|
|
1000
|
-
publishFlowState("auto", executionState);
|
|
1001
|
-
for (const phase of autoFlow.phases) {
|
|
1002
|
-
printInfo(`Dry-run auto phase: ${phase.id}`);
|
|
1003
|
-
await runAutoPhaseViaSpec(config, phase.id, executionState, undefined, setSummary, forceRefreshSummary);
|
|
1004
|
-
if (executionState.terminated) {
|
|
1005
|
-
break;
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
async function runAutoPipeline(config, setSummary, forceRefreshSummary = false) {
|
|
1010
|
-
requireJiraConfig(config);
|
|
1011
|
-
if (config.dryRun) {
|
|
1012
|
-
await runAutoPipelineDryRun(config, setSummary, forceRefreshSummary);
|
|
1013
|
-
return;
|
|
1014
|
-
}
|
|
1015
|
-
checkAutoPrerequisites(config);
|
|
1016
|
-
process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
|
|
1017
|
-
process.env.JIRA_API_URL = config.jiraApiUrl;
|
|
1018
|
-
process.env.JIRA_TASK_FILE = config.jiraTaskFile;
|
|
1019
|
-
let state = loadAutoPipelineState(config) ?? createAutoPipelineState(config);
|
|
1020
|
-
if (config.autoFromPhase) {
|
|
1021
|
-
rewindAutoPipelineState(state, config.autoFromPhase);
|
|
1022
|
-
printPanel("Auto Resume", `Auto pipeline will continue from phase: ${config.autoFromPhase}`, "yellow");
|
|
1023
|
-
saveAutoPipelineState(state);
|
|
1024
|
-
}
|
|
1025
|
-
else if (!existsSync(autoStateFile(config.taskKey))) {
|
|
1026
|
-
saveAutoPipelineState(state);
|
|
1027
|
-
}
|
|
1028
|
-
printInfo("Running auto pipeline with persisted state");
|
|
1029
|
-
while (true) {
|
|
1030
|
-
const step = nextAutoStep(state);
|
|
1031
|
-
if (!step) {
|
|
1032
|
-
syncAndSaveAutoPipelineState(state);
|
|
1033
|
-
if (state.status === "completed") {
|
|
1034
|
-
printPanel("Auto", "Auto pipeline finished", "green");
|
|
1035
|
-
}
|
|
1036
|
-
else {
|
|
1037
|
-
printInfo(`Auto pipeline finished with status: ${state.status}`);
|
|
1038
|
-
}
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
state.status = "running";
|
|
1042
|
-
state.currentStep = step.id;
|
|
1043
|
-
step.status = "running";
|
|
1044
|
-
step.startedAt = nowIso8601();
|
|
1045
|
-
step.finishedAt = null;
|
|
1046
|
-
step.returnCode = null;
|
|
1047
|
-
step.note = null;
|
|
1048
|
-
state.lastError = null;
|
|
1049
|
-
saveAutoPipelineState(state);
|
|
1050
|
-
try {
|
|
1051
|
-
printInfo(`Running auto step: ${step.id}`);
|
|
1052
|
-
const status = await runAutoPhaseViaSpec(config, step.id, state.executionState, state, setSummary, forceRefreshSummary);
|
|
1053
|
-
step.status = status;
|
|
1054
|
-
step.finishedAt = nowIso8601();
|
|
1055
|
-
step.returnCode = 0;
|
|
1056
|
-
if (status === "skipped") {
|
|
1057
|
-
step.note = "condition not met";
|
|
1058
|
-
}
|
|
1059
|
-
syncAndSaveAutoPipelineState(state);
|
|
1060
|
-
}
|
|
1061
|
-
catch (error) {
|
|
1062
|
-
const returnCode = Number(error.returnCode ?? 1);
|
|
1063
|
-
step.status = "failed";
|
|
1064
|
-
step.finishedAt = nowIso8601();
|
|
1065
|
-
step.returnCode = returnCode;
|
|
1066
|
-
state.status = "blocked";
|
|
1067
|
-
state.currentStep = step.id;
|
|
1068
|
-
state.lastError = {
|
|
1069
|
-
step: step.id,
|
|
1070
|
-
returnCode,
|
|
1071
|
-
message: "command failed",
|
|
1072
|
-
};
|
|
1073
|
-
saveAutoPipelineState(state);
|
|
1074
|
-
throw error;
|
|
1075
|
-
}
|
|
1076
|
-
if (state.executionState.terminated) {
|
|
1077
|
-
syncAndSaveAutoPipelineState(state);
|
|
1078
|
-
printPanel("Auto", "Auto pipeline finished", "green");
|
|
1079
|
-
return;
|
|
1080
|
-
}
|
|
1018
|
+
if (config.command === "git-commit") {
|
|
1019
|
+
await runDeclarativeFlowBySpecFile("git-commit.json", config, {
|
|
1020
|
+
taskKey: config.taskKey,
|
|
1021
|
+
extraPrompt: config.extraPrompt,
|
|
1022
|
+
}, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
|
|
1023
|
+
return false;
|
|
1081
1024
|
}
|
|
1025
|
+
throw new TaskRunnerError(`Unsupported command: ${config.command}`);
|
|
1082
1026
|
}
|
|
1083
1027
|
function parseCliArgs(argv) {
|
|
1084
1028
|
if (argv.includes("--version") || argv.includes("-v")) {
|
|
@@ -1105,6 +1049,7 @@ function parseCliArgs(argv) {
|
|
|
1105
1049
|
let scopeName;
|
|
1106
1050
|
let helpPhases = false;
|
|
1107
1051
|
let jiraRef;
|
|
1052
|
+
let mdLang;
|
|
1108
1053
|
for (let index = 1; index < argv.length; index += 1) {
|
|
1109
1054
|
const token = argv[index] ?? "";
|
|
1110
1055
|
if (token === "--dry") {
|
|
@@ -1134,12 +1079,39 @@ function parseCliArgs(argv) {
|
|
|
1134
1079
|
index += 1;
|
|
1135
1080
|
continue;
|
|
1136
1081
|
}
|
|
1082
|
+
if (token === "--md-lang") {
|
|
1083
|
+
const langValue = argv[index + 1];
|
|
1084
|
+
if (langValue === "en" || langValue === "ru") {
|
|
1085
|
+
mdLang = langValue;
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
process.stderr.write("Error: --md-lang accepts only 'en' or 'ru' as values.\n");
|
|
1089
|
+
process.exit(1);
|
|
1090
|
+
}
|
|
1091
|
+
index += 1;
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (token.startsWith("--md-lang=")) {
|
|
1095
|
+
const langValue = token.slice("--md-lang=".length);
|
|
1096
|
+
if (langValue === "en" || langValue === "ru") {
|
|
1097
|
+
mdLang = langValue;
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
process.stderr.write("Error: --md-lang accepts only 'en' or 'ru' as values.\n");
|
|
1101
|
+
process.exit(1);
|
|
1102
|
+
}
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1137
1105
|
jiraRef = token;
|
|
1138
1106
|
}
|
|
1139
|
-
if (command === "auto" && helpPhases) {
|
|
1107
|
+
if (command === "auto-golang" && helpPhases) {
|
|
1140
1108
|
printAutoPhasesHelp();
|
|
1141
1109
|
process.exit(0);
|
|
1142
1110
|
}
|
|
1111
|
+
if (command === "auto-common" && helpPhases) {
|
|
1112
|
+
printAutoCommonPhasesHelp();
|
|
1113
|
+
process.exit(0);
|
|
1114
|
+
}
|
|
1143
1115
|
return {
|
|
1144
1116
|
command: command,
|
|
1145
1117
|
dry,
|
|
@@ -1149,6 +1121,7 @@ function parseCliArgs(argv) {
|
|
|
1149
1121
|
...(scopeName !== undefined ? { scopeName } : {}),
|
|
1150
1122
|
...(prompt !== undefined ? { prompt } : {}),
|
|
1151
1123
|
...(autoFromPhase !== undefined ? { autoFromPhase } : {}),
|
|
1124
|
+
...(mdLang !== undefined ? { mdLang } : {}),
|
|
1152
1125
|
};
|
|
1153
1126
|
}
|
|
1154
1127
|
function buildConfigFromArgs(args) {
|
|
@@ -1157,6 +1130,7 @@ function buildConfigFromArgs(args) {
|
|
|
1157
1130
|
...(args.scopeName !== undefined ? { scopeName: args.scopeName } : {}),
|
|
1158
1131
|
...(args.prompt !== undefined ? { extraPrompt: args.prompt } : {}),
|
|
1159
1132
|
...(args.autoFromPhase !== undefined ? { autoFromPhase: args.autoFromPhase } : {}),
|
|
1133
|
+
...(args.mdLang !== undefined ? { mdLang: args.mdLang } : {}),
|
|
1160
1134
|
dryRun: args.dry,
|
|
1161
1135
|
verbose: args.verbose,
|
|
1162
1136
|
});
|
|
@@ -1165,6 +1139,8 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1165
1139
|
let currentScope = resolveProjectScope(scopeName, jiraRef);
|
|
1166
1140
|
const gitBranchName = detectGitBranchName();
|
|
1167
1141
|
const flowCatalog = loadInteractiveFlowCatalog(process.cwd());
|
|
1142
|
+
let activeAbortController = null;
|
|
1143
|
+
let activeFlowId = null;
|
|
1168
1144
|
let exiting = false;
|
|
1169
1145
|
const ui = new InteractiveUi({
|
|
1170
1146
|
scopeKey: currentScope.scopeKey,
|
|
@@ -1172,43 +1148,35 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1172
1148
|
summaryText: "",
|
|
1173
1149
|
cwd: process.cwd(),
|
|
1174
1150
|
gitBranchName,
|
|
1151
|
+
version: packageVersion(),
|
|
1175
1152
|
flows: interactiveFlowDefinitions(flowCatalog),
|
|
1176
1153
|
getRunConfirmation: async (flowId) => {
|
|
1177
1154
|
const flowEntry = findCatalogEntry(flowId, flowCatalog);
|
|
1178
1155
|
if (!flowEntry) {
|
|
1179
1156
|
throw new TaskRunnerError(`Unknown flow: ${flowId}`);
|
|
1180
1157
|
}
|
|
1181
|
-
if (flowId === "auto") {
|
|
1182
|
-
if (!currentScope.jiraRef) {
|
|
1183
|
-
return { resumeAvailable: false, hasExistingState: false };
|
|
1184
|
-
}
|
|
1185
|
-
const baseConfig = buildBaseConfig("auto", {
|
|
1186
|
-
jiraRef: currentScope.jiraRef,
|
|
1187
|
-
scopeName: currentScope.scopeKey,
|
|
1188
|
-
});
|
|
1189
|
-
const state = loadAutoPipelineState(buildRuntimeConfig(baseConfig, currentScope));
|
|
1190
|
-
if (!state) {
|
|
1191
|
-
return { resumeAvailable: false, hasExistingState: false };
|
|
1192
|
-
}
|
|
1193
|
-
const status = deriveAutoPipelineStatus(state);
|
|
1194
|
-
if (status === "completed") {
|
|
1195
|
-
return { resumeAvailable: false, hasExistingState: true };
|
|
1196
|
-
}
|
|
1197
|
-
return {
|
|
1198
|
-
resumeAvailable: true,
|
|
1199
|
-
hasExistingState: true,
|
|
1200
|
-
details: buildAutoResumeDetails(state),
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
1158
|
const resumeLookup = lookupInteractiveFlowResume(flowEntry, currentScope);
|
|
1204
1159
|
return resumeLookup;
|
|
1205
1160
|
},
|
|
1206
1161
|
onRun: async (flowId, launchMode) => {
|
|
1162
|
+
const abortController = new AbortController();
|
|
1163
|
+
activeAbortController = abortController;
|
|
1164
|
+
activeFlowId = flowId;
|
|
1207
1165
|
try {
|
|
1208
1166
|
const flowEntry = findCatalogEntry(flowId, flowCatalog);
|
|
1209
1167
|
if (!flowEntry) {
|
|
1210
1168
|
throw new TaskRunnerError(`Unknown flow: ${flowId}`);
|
|
1211
1169
|
}
|
|
1170
|
+
const resumeState = launchMode === "resume" ? loadFlowRunState(currentScope.scopeKey, flowId) : null;
|
|
1171
|
+
if (resumeState) {
|
|
1172
|
+
currentScope = scopeWithRestoredJiraContext(currentScope, resumeState);
|
|
1173
|
+
}
|
|
1174
|
+
const launchProfile = launchMode === "resume"
|
|
1175
|
+
? resumeState?.launchProfile
|
|
1176
|
+
: await requestInteractiveLaunchProfile((form) => ui.requestUserInput(form));
|
|
1177
|
+
if (!launchProfile) {
|
|
1178
|
+
throw new TaskRunnerError("Resume is impossible because launch profile was not saved. Use restart.");
|
|
1179
|
+
}
|
|
1212
1180
|
const previousScopeKey = currentScope.scopeKey;
|
|
1213
1181
|
const baseConfig = buildBaseConfig(flowId, {
|
|
1214
1182
|
...(currentScope.jiraRef ? { jiraRef: currentScope.jiraRef } : {}),
|
|
@@ -1226,14 +1194,20 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1226
1194
|
if (previousScopeKey !== currentScope.scopeKey || currentScope.jiraIssueKey) {
|
|
1227
1195
|
syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
|
|
1228
1196
|
}
|
|
1197
|
+
printPanel("Effective Launch Config", `executor: ${launchProfile.executor}\nmodel: ${launchProfile.model}\nmode: ${launchMode}`, "cyan");
|
|
1229
1198
|
if (flowEntry.source === "built-in" && isBuiltInCommandFlowId(flowId)) {
|
|
1230
|
-
await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode);
|
|
1199
|
+
await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode, launchProfile, createRuntimeServices(abortController.signal));
|
|
1231
1200
|
return;
|
|
1232
1201
|
}
|
|
1233
1202
|
const runtimeConfig = buildRuntimeConfig(baseConfig, currentScope);
|
|
1234
|
-
await runDeclarativeFlowByRef(flowId, toDeclarativeFlowRef(flowEntry), runtimeConfig, defaultDeclarativeFlowParams(runtimeConfig, forceRefresh), (form) => ui.requestUserInput(form), (markdown) => ui.setSummary(markdown), launchMode);
|
|
1203
|
+
await runDeclarativeFlowByRef(flowId, toDeclarativeFlowRef(flowEntry), runtimeConfig, defaultDeclarativeFlowParams(runtimeConfig, forceRefresh, { launchProfile }), { launchProfile }, (form) => ui.requestUserInput(form), (markdown) => ui.setSummary(markdown), launchMode, createRuntimeServices(abortController.signal));
|
|
1235
1204
|
}
|
|
1236
1205
|
catch (error) {
|
|
1206
|
+
if (error instanceof FlowInterruptedError) {
|
|
1207
|
+
ui.appendLog(`[interrupt] ${error.message}`);
|
|
1208
|
+
printInfo(error.message);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1237
1211
|
if (error instanceof TaskRunnerError) {
|
|
1238
1212
|
ui.setFlowFailed(flowId);
|
|
1239
1213
|
printError(error.message);
|
|
@@ -1247,6 +1221,19 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1247
1221
|
}
|
|
1248
1222
|
throw error;
|
|
1249
1223
|
}
|
|
1224
|
+
finally {
|
|
1225
|
+
if (activeAbortController === abortController) {
|
|
1226
|
+
activeAbortController = null;
|
|
1227
|
+
activeFlowId = null;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1231
|
+
onInterrupt: async (flowId) => {
|
|
1232
|
+
if (!activeAbortController || activeFlowId !== flowId) {
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
ui.interruptActiveForm();
|
|
1236
|
+
activeAbortController.abort();
|
|
1250
1237
|
},
|
|
1251
1238
|
onExit: () => {
|
|
1252
1239
|
exiting = true;
|
|
@@ -1277,7 +1264,7 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1277
1264
|
});
|
|
1278
1265
|
}
|
|
1279
1266
|
export async function main(argv = process.argv.slice(2)) {
|
|
1280
|
-
|
|
1267
|
+
loadTieredEnv(process.cwd());
|
|
1281
1268
|
let forceRefresh = false;
|
|
1282
1269
|
const args = [...argv];
|
|
1283
1270
|
if (args[0] === "--force") {
|