agentweaver 0.1.18 → 0.1.20
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 +54 -6
- package/dist/artifacts.js +9 -0
- package/dist/executors/git-commit-executor.js +24 -6
- package/dist/flow-state.js +3 -8
- package/dist/git/git-diff-parser.js +223 -0
- package/dist/git/git-service.js +562 -0
- package/dist/git/git-stage-selection.js +24 -0
- package/dist/git/git-status-parser.js +171 -0
- package/dist/git/git-types.js +1 -0
- package/dist/index.js +454 -108
- package/dist/interactive/auto-flow.js +644 -0
- package/dist/interactive/controller.js +489 -7
- package/dist/interactive/progress.js +194 -1
- package/dist/interactive/state.js +34 -0
- package/dist/interactive/web/index.js +237 -5
- package/dist/interactive/web/protocol.js +222 -1
- package/dist/interactive/web/server.js +497 -3
- package/dist/interactive/web/static/app.js +2462 -37
- package/dist/interactive/web/static/index.html +113 -11
- package/dist/interactive/web/static/styles.css +1 -1
- package/dist/interactive/web/static/styles.input.css +1383 -149
- package/dist/pipeline/auto-flow-blocks.js +307 -0
- package/dist/pipeline/auto-flow-config.js +273 -0
- package/dist/pipeline/auto-flow-identity.js +49 -0
- package/dist/pipeline/auto-flow-presets.js +52 -0
- package/dist/pipeline/auto-flow-resolver.js +830 -0
- package/dist/pipeline/auto-flow-types.js +17 -0
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/declarative-flows.js +27 -1
- package/dist/pipeline/flow-specs/auto-common-guided.json +11 -0
- package/dist/pipeline/flow-specs/auto-golang.json +12 -1
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +54 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +19 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +33 -1
- package/dist/pipeline/flow-specs/review/review-project.json +19 -1
- package/dist/pipeline/flow-specs/task-source/manual-jira-input.json +70 -0
- package/dist/pipeline/node-registry.js +9 -0
- package/dist/pipeline/nodes/codex-prompt-node.js +8 -1
- package/dist/pipeline/nodes/flow-run-node.js +5 -3
- package/dist/pipeline/nodes/git-status-node.js +2 -168
- package/dist/pipeline/nodes/manual-jira-task-input-node.js +146 -0
- package/dist/pipeline/nodes/opencode-prompt-node.js +8 -1
- package/dist/pipeline/nodes/plan-codex-node.js +8 -1
- package/dist/pipeline/spec-loader.js +14 -4
- package/dist/runtime/artifact-catalog.js +403 -0
- package/dist/runtime/settings.js +114 -0
- package/dist/scope.js +14 -4
- package/package.json +1 -1
- package/dist/pipeline/flow-specs/auto-common.json +0 -179
- package/dist/pipeline/flow-specs/auto-simple.json +0 -141
package/dist/index.js
CHANGED
|
@@ -12,8 +12,11 @@ import { AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV, parseReviewSeverityCsv, res
|
|
|
12
12
|
import { summarizeBuildFailure as summarizeBuildFailureViaPipeline } from "./pipeline/build-failure-summary.js";
|
|
13
13
|
import { runNodeChecks } from "./pipeline/checks.js";
|
|
14
14
|
import { createPipelineContext } from "./pipeline/context.js";
|
|
15
|
-
import { collectFlowRoutingGroups, loadDeclarativeFlow } from "./pipeline/declarative-flows.js";
|
|
15
|
+
import { collectFlowRoutingGroups, loadDeclarativeFlow, } from "./pipeline/declarative-flows.js";
|
|
16
16
|
import { runExpandedPhase } from "./pipeline/declarative-flow-runner.js";
|
|
17
|
+
import { listAutoFlowConfigs, loadAutoFlowConfigByName } from "./pipeline/auto-flow-config.js";
|
|
18
|
+
import { formatAutoFlowDryRunPreview, persistResolvedAutoFlowArtifacts, resolveAutoFlow, } from "./pipeline/auto-flow-resolver.js";
|
|
19
|
+
import { autoFlowIdentityForSelection, defaultAutoFlowSelection, isRestartArchivingFlowId, } from "./pipeline/auto-flow-identity.js";
|
|
17
20
|
import { builtInCommandFlowFile, findCatalogEntry, flowRoutingGroups, isBuiltInCommandFlowId, loadInteractiveFlowCatalog, toDeclarativeFlowRef, } from "./pipeline/flow-catalog.js";
|
|
18
21
|
import { createPipelineRegistryContext } from "./pipeline/plugin-loader.js";
|
|
19
22
|
import { DEFAULT_LAUNCH_PROFILE, } from "./pipeline/launch-profile-config.js";
|
|
@@ -33,11 +36,13 @@ import { describeExecutionRouting, executorsForRoutingGroups, resolveExecutionRo
|
|
|
33
36
|
import { requestInteractiveExecutionRouting } from "./runtime/interactive-execution-routing.js";
|
|
34
37
|
import { createInteractiveSession } from "./interactive/create-interactive-session.js";
|
|
35
38
|
import { createWebInteractiveSession } from "./interactive/web/index.js";
|
|
36
|
-
import {
|
|
39
|
+
import { autoFlowSelectionForFlowId, createConfigAutoFlowDefinition, createPresetAutoFlowDefinition, } from "./interactive/auto-flow.js";
|
|
40
|
+
import { bye, getOutputAdapter, printError, printInfo, printPanel, renderPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
|
|
37
41
|
import { requestUserInputInTerminal } from "./user-input.js";
|
|
38
42
|
import { runDoctorCommand } from "./doctor/index.js";
|
|
39
|
-
import { requestJiraContext, resolveProjectScope, } from "./scope.js";
|
|
43
|
+
import { requestJiraContext, requestOptionalJiraContext, resolveProjectScope, } from "./scope.js";
|
|
40
44
|
const COMMANDS = [
|
|
45
|
+
"auto",
|
|
41
46
|
"auto-golang",
|
|
42
47
|
"auto-common-guided",
|
|
43
48
|
"auto-common",
|
|
@@ -76,6 +81,17 @@ function writeStdoutSync(text) {
|
|
|
76
81
|
function writeStderrSync(text) {
|
|
77
82
|
writeSync(process.stderr.fd, text);
|
|
78
83
|
}
|
|
84
|
+
function printPanelSync(title, text) {
|
|
85
|
+
const adapter = getOutputAdapter();
|
|
86
|
+
if (adapter.renderAuxiliaryOutput === false) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (adapter.renderPanelsAsPlainText) {
|
|
90
|
+
writeStdoutSync(`${title}\n${text}\n\n`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
writeStdoutSync(`${renderPanel(title, text)}\n`);
|
|
94
|
+
}
|
|
79
95
|
function createRuntimeServices(signal) {
|
|
80
96
|
return {
|
|
81
97
|
resolveCmd,
|
|
@@ -174,16 +190,18 @@ function usage() {
|
|
|
174
190
|
agentweaver review-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [--blocking-severities <list>] [<jira-browse-url|jira-issue-key>]
|
|
175
191
|
agentweaver run-go-tests-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
176
192
|
agentweaver run-go-linter-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
193
|
+
agentweaver auto [--preset <simple|standard>|--config <name>] [--dry-run-flow] [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
|
|
194
|
+
agentweaver auto --help-phases
|
|
177
195
|
agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
|
|
178
196
|
agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] --from <phase> [<jira-browse-url|jira-issue-key>]
|
|
179
197
|
agentweaver auto-golang --help-phases
|
|
180
|
-
agentweaver auto-common-guided [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [--accept-playbook-draft] <jira-browse-url|jira-issue-key>
|
|
181
|
-
agentweaver auto-common [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
|
|
198
|
+
agentweaver auto-common-guided [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [--accept-playbook-draft] [<jira-browse-url|jira-issue-key>]
|
|
199
|
+
agentweaver auto-common [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
|
|
182
200
|
agentweaver auto-common --help-phases
|
|
183
|
-
agentweaver auto-simple [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
|
|
201
|
+
agentweaver auto-simple [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
|
|
184
202
|
agentweaver auto-simple --help-phases
|
|
185
|
-
agentweaver auto-status [<jira-browse-url|jira-issue-key>]
|
|
186
|
-
agentweaver auto-reset [<jira-browse-url|jira-issue-key>]
|
|
203
|
+
agentweaver auto-status [--preset <simple|standard>|--config <name>] [<jira-browse-url|jira-issue-key>]
|
|
204
|
+
agentweaver auto-reset [--preset <simple|standard>|--config <name>] [<jira-browse-url|jira-issue-key>]
|
|
187
205
|
|
|
188
206
|
Interactive Mode:
|
|
189
207
|
When started without a command, the script opens an interactive UI.
|
|
@@ -196,13 +214,16 @@ Flags:
|
|
|
196
214
|
--no-open Web command only: print the Web UI URL without opening a browser
|
|
197
215
|
--host Web command only: bind Web UI to this host (default: 127.0.0.1)
|
|
198
216
|
--listen-all Web command only: bind Web UI to 0.0.0.0
|
|
199
|
-
--dry Fetch
|
|
217
|
+
--dry Fetch or collect task context, but print codex/opencode commands instead of executing them
|
|
218
|
+
--preset Auto, auto-status, and auto-reset: resolve the simple or standard preset (default: standard)
|
|
219
|
+
--config Auto, auto-status, and auto-reset: load .agentweaver/flow-configs/<name>.yaml or ~/.agentweaver/flow-configs/<name>.yaml
|
|
220
|
+
--dry-run-flow Auto command only: validate and preview flow resolution without running workflow steps or writing resolver artifacts
|
|
200
221
|
--verbose Show live stdout/stderr of launched commands
|
|
201
222
|
--scope Explicit workflow scope name for non-Jira runs except instant-task
|
|
202
223
|
--prompt Extra prompt text appended to the base prompt
|
|
203
224
|
--resume Resume an interrupted run when valid
|
|
204
225
|
--continue Continue a terminated iterative run when valid
|
|
205
|
-
--restart
|
|
226
|
+
--restart Start a fresh run; end-to-end attempt flows archive the active attempt first
|
|
206
227
|
--blocking-severities Comma-separated severities that block merge and drive review-fix auto-selection
|
|
207
228
|
--md-lang Language for workflow markdown artifacts only: en (English) or ru (Russian, default)
|
|
208
229
|
--accept-playbook-draft Non-interactively accept generated playbook content for playbook-init or auto-common-guided missing-manifest runs
|
|
@@ -226,7 +247,10 @@ Optional environment variables:
|
|
|
226
247
|
${WEB_AUTH_PASSWORD_ENV} Web UI Basic auth password; required for external Web UI binding
|
|
227
248
|
|
|
228
249
|
Notes:
|
|
229
|
-
-
|
|
250
|
+
- agentweaver auto defaults to --preset standard, equivalent to auto-common. Use --dry-run-flow to inspect the resolved preset or saved config before execution.
|
|
251
|
+
- Saved auto flow configs are YAML files named .agentweaver/flow-configs/<name>.yaml or ~/.agentweaver/flow-configs/<name>.yaml; project configs take precedence over user configs.
|
|
252
|
+
- Successful configurable auto runs write flow-config.yaml, resolved-flow.json, and resolved-flow-summary.json under the current scope .artifacts directory. --dry-run-flow writes none of them.
|
|
253
|
+
- auto-golang, auto-common-guided, auto-common, auto-simple, and configurable auto ask for Jira input when Jira is not passed as an argument; leave it empty to paste the task description manually in the next step. task-describe can also work from a manual task description without Jira.
|
|
230
254
|
- agentweaver web binds to 127.0.0.1 by default on an operating-system-assigned port and does not require auth unless Web UI credentials are configured.
|
|
231
255
|
- External Web UI binding through --listen-all, --host 0.0.0.0, --host ::, non-loopback IPs, or hostnames other than localhost requires ${WEB_AUTH_USERNAME_ENV} and ${WEB_AUTH_PASSWORD_ENV}.
|
|
232
256
|
- Web UI Basic auth over plain HTTP is suitable only on trusted networks; use TLS termination or a reverse proxy on untrusted networks.
|
|
@@ -406,8 +430,15 @@ function scopeWithRestoredJiraContext(scope, state) {
|
|
|
406
430
|
return resolveProjectScope(null, state.jiraRef);
|
|
407
431
|
}
|
|
408
432
|
function buildInteractiveBaseConfig(flowId, scope) {
|
|
409
|
-
|
|
433
|
+
const autoFlowSelection = autoFlowSelectionForFlowId(flowId);
|
|
434
|
+
const command = autoFlowSelection
|
|
435
|
+
? autoFlowSelection.kind === "preset" && autoFlowSelection.preset === "simple"
|
|
436
|
+
? "auto-simple"
|
|
437
|
+
: "auto-common"
|
|
438
|
+
: flowId;
|
|
439
|
+
return buildBaseConfig(command, {
|
|
410
440
|
...(flowId !== "instant-task" && scope.jiraRef ? { jiraRef: scope.jiraRef } : {}),
|
|
441
|
+
...(autoFlowSelection ? { autoFlowSelection } : {}),
|
|
411
442
|
});
|
|
412
443
|
}
|
|
413
444
|
async function lookupInteractiveFlowResume(flowEntry, currentScope) {
|
|
@@ -445,9 +476,31 @@ async function lookupInteractiveFlowResume(flowEntry, currentScope) {
|
|
|
445
476
|
...availability,
|
|
446
477
|
};
|
|
447
478
|
}
|
|
479
|
+
async function lookupInteractiveAutoFlowResume(selection, currentScope) {
|
|
480
|
+
const resolved = await resolveAutoFlow(selection, {
|
|
481
|
+
cwd: process.cwd(),
|
|
482
|
+
scopeKey: currentScope.scopeKey,
|
|
483
|
+
});
|
|
484
|
+
const identity = autoFlowIdentityForSelection(selection, resolved);
|
|
485
|
+
const state = loadFlowRunState(currentScope.scopeKey, identity.flowId);
|
|
486
|
+
const availability = classifyFlowLaunchAvailability(state);
|
|
487
|
+
if (state && availability.resume.available) {
|
|
488
|
+
return {
|
|
489
|
+
...availability,
|
|
490
|
+
details: buildFlowResumeDetails(state),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
if (state && availability.continue.available) {
|
|
494
|
+
return {
|
|
495
|
+
...availability,
|
|
496
|
+
details: buildFlowContinueDetails(state),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
return availability;
|
|
500
|
+
}
|
|
448
501
|
async function printAutoPhasesHelp() {
|
|
449
502
|
const phaseLines = ["Available auto-golang phases:", "", ...(await autoPhaseIds())];
|
|
450
|
-
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>");
|
|
503
|
+
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>");
|
|
451
504
|
printPanel("Auto-Golang Phases", phaseLines.join("\n"), "magenta");
|
|
452
505
|
}
|
|
453
506
|
async function autoCommonPhaseIds(fileName = "auto-common.json") {
|
|
@@ -455,7 +508,7 @@ async function autoCommonPhaseIds(fileName = "auto-common.json") {
|
|
|
455
508
|
}
|
|
456
509
|
async function printAutoCommonPhasesHelp(command = "auto-common", fileName = "auto-common.json") {
|
|
457
510
|
const phaseLines = [`Available ${command} phases:`, "", ...(await autoCommonPhaseIds(fileName))];
|
|
458
|
-
phaseLines.push("", `You can run ${command} with:`, `agentweaver ${command} <jira
|
|
511
|
+
phaseLines.push("", `You can run ${command} with:`, `agentweaver ${command} [<jira>]`);
|
|
459
512
|
printPanel(command === "auto-common-guided" ? "Auto-Common Guided Phases" : "Auto-Common Phases", phaseLines.join("\n"), "magenta");
|
|
460
513
|
}
|
|
461
514
|
async function autoSimplePhaseIds() {
|
|
@@ -463,7 +516,7 @@ async function autoSimplePhaseIds() {
|
|
|
463
516
|
}
|
|
464
517
|
async function printAutoSimplePhasesHelp() {
|
|
465
518
|
const phaseLines = ["Available auto-simple phases:", "", ...(await autoSimplePhaseIds())];
|
|
466
|
-
phaseLines.push("", "You can run auto-simple with:", "agentweaver auto-simple <jira>");
|
|
519
|
+
phaseLines.push("", "You can run auto-simple with:", "agentweaver auto-simple [<jira>]");
|
|
467
520
|
printPanel("Auto-Simple Phases", phaseLines.join("\n"), "magenta");
|
|
468
521
|
}
|
|
469
522
|
function nextReviewIterationForTask(taskKey) {
|
|
@@ -483,13 +536,16 @@ function buildBaseConfig(command, options = {}) {
|
|
|
483
536
|
autoFromPhase: options.autoFromPhase ?? null,
|
|
484
537
|
mdLang: options.mdLang ?? null,
|
|
485
538
|
dryRun: options.dryRun ?? false,
|
|
539
|
+
dryRunFlow: options.dryRunFlow ?? false,
|
|
486
540
|
verbose: options.verbose ?? false,
|
|
487
541
|
...(options.doctorArgs !== undefined ? { doctorArgs: options.doctorArgs } : {}),
|
|
488
542
|
...(options.acceptPlaybookDraft !== undefined ? { acceptPlaybookDraft: options.acceptPlaybookDraft } : {}),
|
|
543
|
+
...(options.autoFlowSelection !== undefined ? { autoFlowSelection: options.autoFlowSelection } : {}),
|
|
489
544
|
};
|
|
490
545
|
}
|
|
491
546
|
function commandRequiresTask(command) {
|
|
492
|
-
return (command === "
|
|
547
|
+
return (command === "auto" ||
|
|
548
|
+
command === "plan-revise" ||
|
|
493
549
|
command === "bug-analyze" ||
|
|
494
550
|
command === "bug-fix" ||
|
|
495
551
|
command === "design-review" ||
|
|
@@ -501,6 +557,13 @@ function commandRequiresTask(command) {
|
|
|
501
557
|
command === "auto-status" ||
|
|
502
558
|
command === "auto-reset");
|
|
503
559
|
}
|
|
560
|
+
function commandSupportsManualTaskSource(command) {
|
|
561
|
+
return (command === "auto" ||
|
|
562
|
+
command === "auto-golang" ||
|
|
563
|
+
command === "auto-common-guided" ||
|
|
564
|
+
command === "auto-common" ||
|
|
565
|
+
command === "auto-simple");
|
|
566
|
+
}
|
|
504
567
|
function commandSupportsProjectScope(command) {
|
|
505
568
|
return (command === "plan" ||
|
|
506
569
|
command === "git-commit" ||
|
|
@@ -516,7 +579,21 @@ function commandSupportsProjectScope(command) {
|
|
|
516
579
|
command === "run-go-tests-loop" ||
|
|
517
580
|
command === "run-go-linter-loop");
|
|
518
581
|
}
|
|
519
|
-
|
|
582
|
+
function hasJiraConfig(config) {
|
|
583
|
+
return Boolean(config.scope.jiraRef && config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile);
|
|
584
|
+
}
|
|
585
|
+
function syncJiraEnv(config) {
|
|
586
|
+
if (hasJiraConfig(config)) {
|
|
587
|
+
process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
|
|
588
|
+
process.env.JIRA_API_URL = config.jiraApiUrl;
|
|
589
|
+
process.env.JIRA_TASK_FILE = config.jiraTaskFile;
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
delete process.env.JIRA_BROWSE_URL;
|
|
593
|
+
delete process.env.JIRA_API_URL;
|
|
594
|
+
delete process.env.JIRA_TASK_FILE;
|
|
595
|
+
}
|
|
596
|
+
async function resolveScopeForCommand(config, requestUserInput, launchMode) {
|
|
520
597
|
if (config.command === "instant-task") {
|
|
521
598
|
if (config.scopeName?.trim()) {
|
|
522
599
|
throw new TaskRunnerError("Command 'instant-task' rejects explicit scope overrides. The current branch-derived scope is the only supported lineage identity.");
|
|
@@ -534,13 +611,23 @@ async function resolveScopeForCommand(config, requestUserInput) {
|
|
|
534
611
|
}
|
|
535
612
|
if (commandRequiresTask(config.command)) {
|
|
536
613
|
try {
|
|
614
|
+
if (commandSupportsManualTaskSource(config.command)) {
|
|
615
|
+
if (launchMode === "resume" || launchMode === "continue") {
|
|
616
|
+
return resolveProjectScope(config.scopeName);
|
|
617
|
+
}
|
|
618
|
+
const jiraContext = await requestOptionalJiraContext(requestUserInput);
|
|
619
|
+
return resolveProjectScope(config.scopeName, jiraContext?.jiraRef ?? null);
|
|
620
|
+
}
|
|
537
621
|
const jiraContext = await requestJiraContext(requestUserInput);
|
|
538
622
|
return resolveProjectScope(config.scopeName, jiraContext.jiraRef);
|
|
539
623
|
}
|
|
540
624
|
catch (error) {
|
|
541
625
|
if (error instanceof TaskRunnerError && error.message.includes("no TTY is available")) {
|
|
542
|
-
throw new TaskRunnerError(
|
|
543
|
-
|
|
626
|
+
throw new TaskRunnerError(commandSupportsManualTaskSource(config.command)
|
|
627
|
+
? `Command '${config.command}' requires Jira input or a manual task description.\n` +
|
|
628
|
+
"Pass Jira issue key / browse URL as an argument, or run the command in an interactive terminal, leave Jira empty, and paste the task description in the next step."
|
|
629
|
+
: `Command '${config.command}' requires a Jira task.\n` +
|
|
630
|
+
"Pass Jira issue key / browse URL as an argument, or run the command in an interactive terminal.");
|
|
544
631
|
}
|
|
545
632
|
throw error;
|
|
546
633
|
}
|
|
@@ -608,9 +695,12 @@ function resolveExecutorPrerequisite(executor, registryContext) {
|
|
|
608
695
|
}
|
|
609
696
|
}
|
|
610
697
|
async function checkPrerequisites(config, launchProfile, executionRouting) {
|
|
698
|
+
const groups = await commandRoutingGroupsForPrerequisiteChecks(config.command, process.cwd());
|
|
699
|
+
await checkPrerequisitesForRoutingGroups(groups, launchProfile, executionRouting);
|
|
700
|
+
}
|
|
701
|
+
async function checkPrerequisitesForRoutingGroups(groups, launchProfile, executionRouting) {
|
|
611
702
|
const registryContext = await createPipelineRegistryContext(process.cwd());
|
|
612
703
|
const routing = routingForPrerequisites(launchProfile, executionRouting);
|
|
613
|
-
const groups = await commandRoutingGroupsForPrerequisiteChecks(config.command, process.cwd());
|
|
614
704
|
for (const executor of executorsForRoutingGroups(routing, groups)) {
|
|
615
705
|
resolveExecutorPrerequisite(executor, registryContext);
|
|
616
706
|
}
|
|
@@ -618,6 +708,124 @@ async function checkPrerequisites(config, launchProfile, executionRouting) {
|
|
|
618
708
|
async function checkAutoPrerequisites(config, launchProfile, executionRouting) {
|
|
619
709
|
await checkPrerequisites(config, launchProfile, executionRouting);
|
|
620
710
|
}
|
|
711
|
+
async function checkResolvedAutoPrerequisites(resolved, launchProfile, executionRouting) {
|
|
712
|
+
const groups = resolved.execution.kind === "built-in"
|
|
713
|
+
? await collectFlowRoutingGroups(await loadDeclarativeFlow({ source: "built-in", fileName: resolved.execution.specFile }), process.cwd())
|
|
714
|
+
: await collectFlowRoutingGroups(resolved.execution.flow, process.cwd(), new Set(), { inMemoryFlows: resolved.execution.inMemoryFlows });
|
|
715
|
+
await checkPrerequisitesForRoutingGroups(groups, launchProfile, executionRouting);
|
|
716
|
+
}
|
|
717
|
+
async function loadResolvedAutoFlowExecution(resolved) {
|
|
718
|
+
if (resolved.execution.kind === "built-in") {
|
|
719
|
+
return {
|
|
720
|
+
flow: await loadDeclarativeFlow({ source: "built-in", fileName: resolved.execution.specFile }),
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
flow: resolved.execution.flow,
|
|
725
|
+
inMemoryFlows: resolved.execution.inMemoryFlows,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
async function runResolvedAutoFlow(resolved, identity, config, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices, forceRefreshSummary = false) {
|
|
729
|
+
const { flow, inMemoryFlows } = await loadResolvedAutoFlowExecution(resolved);
|
|
730
|
+
const mergedFlowParams = {
|
|
731
|
+
...defaultDeclarativeFlowParams(config, false, overrides),
|
|
732
|
+
...autoFlowParams(config, forceRefreshSummary),
|
|
733
|
+
};
|
|
734
|
+
await runLoadedDeclarativeFlow(identity.flowId, flow, config, withCanonicalReviewLoopParams(flow.kind, mergedFlowParams), overrides, requestUserInput, setSummary, launchMode, runtime, inMemoryFlows);
|
|
735
|
+
}
|
|
736
|
+
function formatAutoFlowSource(resolved) {
|
|
737
|
+
const source = resolved.document.source;
|
|
738
|
+
if (source.type === "preset") {
|
|
739
|
+
return `preset ${source.preset}`;
|
|
740
|
+
}
|
|
741
|
+
const shadowed = source.shadowedUserPath ? ` (shadowed user config: ${source.shadowedUserPath})` : "";
|
|
742
|
+
return `${source.type} ${source.configName} at ${source.path}${shadowed}`;
|
|
743
|
+
}
|
|
744
|
+
function resolverArtifactStatusLines(resolved) {
|
|
745
|
+
const paths = resolved.document.artifactPolicy.artifactPaths;
|
|
746
|
+
if (!paths) {
|
|
747
|
+
return ["Resolver artifacts: not available for this scope"];
|
|
748
|
+
}
|
|
749
|
+
return [
|
|
750
|
+
"Resolver artifacts:",
|
|
751
|
+
` - flow-config.yaml: ${paths.flowConfigYaml}${existsSync(paths.flowConfigYaml) ? "" : " (missing)"}`,
|
|
752
|
+
` - resolved-flow.json: ${paths.resolvedFlowJson}${existsSync(paths.resolvedFlowJson) ? "" : " (missing)"}`,
|
|
753
|
+
` - resolved-flow-summary.json: ${paths.resolvedFlowSummaryJson}${existsSync(paths.resolvedFlowSummaryJson) ? "" : " (missing)"}`,
|
|
754
|
+
];
|
|
755
|
+
}
|
|
756
|
+
async function printConfigurableAutoStatus(config, resolved, identity) {
|
|
757
|
+
const stateFile = flowStateFile(config.scope.scopeKey, identity.flowId);
|
|
758
|
+
const state = loadFlowRunState(config.scope.scopeKey, identity.flowId);
|
|
759
|
+
const { flow } = await loadResolvedAutoFlowExecution(resolved);
|
|
760
|
+
const phaseLabels = new Map(resolved.document.phases.map((phase) => [phase.id, phase.label]));
|
|
761
|
+
const lines = [
|
|
762
|
+
`Issue: ${config.taskKey}`,
|
|
763
|
+
`Target: ${identity.displayLabel}`,
|
|
764
|
+
`Selected command: ${identity.selectedCommand}`,
|
|
765
|
+
`Effective flow ID: ${identity.flowId}`,
|
|
766
|
+
`Source: ${formatAutoFlowSource(resolved)}`,
|
|
767
|
+
`Base preset: ${resolved.document.basePreset}`,
|
|
768
|
+
`Execution target: ${resolved.document.executionTarget.kind}`,
|
|
769
|
+
`Resolver fingerprint: ${resolved.document.fingerprint}`,
|
|
770
|
+
`State file: ${stateFile}`,
|
|
771
|
+
];
|
|
772
|
+
if (!state) {
|
|
773
|
+
lines.push("", "Status: no saved state", "", "Phases:");
|
|
774
|
+
for (const phase of flow.phases) {
|
|
775
|
+
const label = phaseLabels.get(phase.id);
|
|
776
|
+
lines.push(` - ${phase.id}${label && label !== phase.id ? ` - ${label}` : ""}`);
|
|
777
|
+
}
|
|
778
|
+
lines.push("", ...resolverArtifactStatusLines(resolved));
|
|
779
|
+
printPanelSync("Auto Status", lines.join("\n"));
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
|
|
783
|
+
lines.push("", `Status: ${state.status}`, `Current step: ${currentStep}`, `Updated: ${state.updatedAt}`, `Continuation: ${state.continuation?.continueEligible ? "eligible" : "not eligible"}`);
|
|
784
|
+
if (state.continuation?.stopPhaseId && state.continuation?.stopStepId) {
|
|
785
|
+
lines.push(`Stopped at: ${state.continuation.stopPhaseId}:${state.continuation.stopStepId}`);
|
|
786
|
+
}
|
|
787
|
+
if (state.selectedRoutingPreset) {
|
|
788
|
+
lines.push(`Routing preset: ${state.selectedRoutingPreset.label}`);
|
|
789
|
+
}
|
|
790
|
+
if (state.executionRouting) {
|
|
791
|
+
lines.push(`Default route: ${state.executionRouting.defaultRoute.executor} / ${state.executionRouting.defaultRoute.model}`);
|
|
792
|
+
lines.push(`Routing fingerprint: ${state.executionRouting.fingerprint}`);
|
|
793
|
+
}
|
|
794
|
+
else if (state.launchProfile) {
|
|
795
|
+
lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
|
|
796
|
+
}
|
|
797
|
+
if (state.lastError) {
|
|
798
|
+
lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
|
|
799
|
+
}
|
|
800
|
+
lines.push("", "Phases:");
|
|
801
|
+
for (const phase of flow.phases) {
|
|
802
|
+
const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
|
|
803
|
+
const label = phaseLabels.get(phase.id);
|
|
804
|
+
lines.push(`[${phaseState?.status ?? "pending"}] ${phase.id}${label && label !== phase.id ? ` - ${label}` : ""}`);
|
|
805
|
+
for (const step of phase.steps) {
|
|
806
|
+
const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
|
|
807
|
+
lines.push(` - [${stepState?.status ?? "pending"}] ${step.id}`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (state.executionState.terminated) {
|
|
811
|
+
lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
|
|
812
|
+
}
|
|
813
|
+
lines.push("", ...resolverArtifactStatusLines(resolved));
|
|
814
|
+
printPanelSync("Auto Status", lines.join("\n"));
|
|
815
|
+
}
|
|
816
|
+
async function printConfigurableAutoReset(config, resolved, identity) {
|
|
817
|
+
const stateFile = flowStateFile(config.scope.scopeKey, identity.flowId);
|
|
818
|
+
const removed = resetFlowRunState(config.scope.scopeKey, identity.flowId);
|
|
819
|
+
const lines = [
|
|
820
|
+
`Issue: ${config.taskKey}`,
|
|
821
|
+
`Target: ${identity.displayLabel}`,
|
|
822
|
+
`Effective flow ID: ${identity.flowId}`,
|
|
823
|
+
`Source: ${formatAutoFlowSource(resolved)}`,
|
|
824
|
+
removed ? `State file ${stateFile} removed.` : `No flow state file found at ${stateFile}.`,
|
|
825
|
+
"Resolver artifacts were left unchanged.",
|
|
826
|
+
];
|
|
827
|
+
printPanelSync("Auto Reset", lines.join("\n"));
|
|
828
|
+
}
|
|
621
829
|
function autoFlowParams(config, forceRefreshSummary = false) {
|
|
622
830
|
return {
|
|
623
831
|
jiraApiUrl: config.jiraApiUrl,
|
|
@@ -708,6 +916,10 @@ function loadInstantTaskInputDefaults(taskKey) {
|
|
|
708
916
|
}
|
|
709
917
|
function interactiveFlowDefinition(entry) {
|
|
710
918
|
const flow = entry.flow;
|
|
919
|
+
const autoFlowSelection = autoFlowSelectionForFlowId(entry.id);
|
|
920
|
+
const autoFlow = autoFlowSelection?.kind === "preset"
|
|
921
|
+
? createPresetAutoFlowDefinition(autoFlowSelection.preset)
|
|
922
|
+
: null;
|
|
711
923
|
return {
|
|
712
924
|
id: entry.id,
|
|
713
925
|
label: entry.id,
|
|
@@ -715,6 +927,7 @@ function interactiveFlowDefinition(entry) {
|
|
|
715
927
|
source: entry.source,
|
|
716
928
|
treePath: [...entry.treePath],
|
|
717
929
|
...(entry.source !== "built-in" ? { sourcePath: entry.absolutePath } : {}),
|
|
930
|
+
...(autoFlow ? { autoFlow } : {}),
|
|
718
931
|
phases: flow.phases.map((phase) => ({
|
|
719
932
|
id: phase.id,
|
|
720
933
|
repeatVars: Object.fromEntries(Object.entries(phase.repeatVars).map(([key, value]) => [key, value])),
|
|
@@ -724,8 +937,40 @@ function interactiveFlowDefinition(entry) {
|
|
|
724
937
|
})),
|
|
725
938
|
};
|
|
726
939
|
}
|
|
727
|
-
function
|
|
728
|
-
|
|
940
|
+
function savedAutoFlowDefinitions(cwd) {
|
|
941
|
+
const definitions = [];
|
|
942
|
+
for (const item of listAutoFlowConfigs(cwd)) {
|
|
943
|
+
try {
|
|
944
|
+
const loaded = loadAutoFlowConfigByName(item.name, cwd);
|
|
945
|
+
definitions.push({
|
|
946
|
+
id: `auto-config:${loaded.config.name}`,
|
|
947
|
+
label: `auto-config:${loaded.config.name}`,
|
|
948
|
+
description: `Saved configurable auto-flow config '${loaded.config.name}'.`,
|
|
949
|
+
source: "built-in",
|
|
950
|
+
treePath: ["default", "auto-configs", loaded.config.name],
|
|
951
|
+
autoFlow: createConfigAutoFlowDefinition({
|
|
952
|
+
config: loaded.config,
|
|
953
|
+
source: {
|
|
954
|
+
type: loaded.source.type === "project" ? "project-config" : "user-config",
|
|
955
|
+
configName: loaded.config.name,
|
|
956
|
+
path: loaded.source.path,
|
|
957
|
+
...(loaded.source.shadowedUserPath ? { shadowedUserPath: loaded.source.shadowedUserPath } : {}),
|
|
958
|
+
},
|
|
959
|
+
}),
|
|
960
|
+
phases: [],
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
printError(error.message);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return definitions;
|
|
968
|
+
}
|
|
969
|
+
function interactiveFlowDefinitions(catalog, cwd = process.cwd()) {
|
|
970
|
+
return [
|
|
971
|
+
...catalog.map((entry) => interactiveFlowDefinition(entry)),
|
|
972
|
+
...savedAutoFlowDefinitions(cwd),
|
|
973
|
+
];
|
|
729
974
|
}
|
|
730
975
|
function publishFlowState(flowId, executionState) {
|
|
731
976
|
setFlowExecutionState(flowId, stripExecutionStatePayload(executionState));
|
|
@@ -763,7 +1008,7 @@ function findCurrentFlowExecutionStep(state) {
|
|
|
763
1008
|
}
|
|
764
1009
|
return null;
|
|
765
1010
|
}
|
|
766
|
-
async function
|
|
1011
|
+
async function runLoadedDeclarativeFlow(flowId, flow, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices, inMemoryFlows) {
|
|
767
1012
|
const context = await createPipelineContext({
|
|
768
1013
|
issueKey: config.taskKey,
|
|
769
1014
|
jiraRef: config.jiraRef,
|
|
@@ -774,8 +1019,8 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
|
|
|
774
1019
|
...(setSummary ? { setSummary } : {}),
|
|
775
1020
|
requestUserInput,
|
|
776
1021
|
...(overrides.executionRouting ? { executionRouting: overrides.executionRouting } : {}),
|
|
1022
|
+
...(inMemoryFlows ? { inMemoryFlows } : {}),
|
|
777
1023
|
});
|
|
778
|
-
const flow = await loadDeclarativeFlow(flowRef);
|
|
779
1024
|
const initialExecutionState = {
|
|
780
1025
|
flowKind: flow.kind,
|
|
781
1026
|
flowVersion: flow.version,
|
|
@@ -790,7 +1035,7 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
|
|
|
790
1035
|
if (persistedState && launchMode === "resume") {
|
|
791
1036
|
await validateDeclarativeFlowResumeState({
|
|
792
1037
|
id: flowId,
|
|
793
|
-
source: flow.source,
|
|
1038
|
+
source: flow.source === "generated" ? "built-in" : flow.source,
|
|
794
1039
|
fileName: flow.fileName,
|
|
795
1040
|
absolutePath: flow.absolutePath,
|
|
796
1041
|
treePath: [],
|
|
@@ -805,14 +1050,14 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
|
|
|
805
1050
|
persistedState = prepareFlowStateForContinue(persistedState, flow.phases);
|
|
806
1051
|
}
|
|
807
1052
|
else if (launchMode === "restart") {
|
|
808
|
-
if (existingStateForRestart) {
|
|
1053
|
+
if (existingStateForRestart && isRestartArchivingFlowId(flowId)) {
|
|
809
1054
|
archiveActiveAttempt(config.scope.scopeKey);
|
|
810
1055
|
}
|
|
811
1056
|
resetFlowRunState(config.scope.scopeKey, flowId);
|
|
812
1057
|
}
|
|
813
1058
|
const executionState = persistedState?.executionState ?? initialExecutionState;
|
|
814
1059
|
const state = persistedState
|
|
815
|
-
?? createFlowRunState(config.scope.scopeKey, flowId, executionState, config.jiraRef, overrides.launchProfile, overrides.executionRouting, overrides.selectedRoutingPreset);
|
|
1060
|
+
?? createFlowRunState(config.scope.scopeKey, flowId, executionState, config.scope.jiraRef ?? null, overrides.launchProfile, overrides.executionRouting, overrides.selectedRoutingPreset);
|
|
816
1061
|
if (overrides.executionRouting) {
|
|
817
1062
|
state.executionRouting = overrides.executionRouting;
|
|
818
1063
|
state.routingFingerprint = overrides.executionRouting.fingerprint;
|
|
@@ -874,6 +1119,10 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
|
|
|
874
1119
|
throw error;
|
|
875
1120
|
}
|
|
876
1121
|
}
|
|
1122
|
+
async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
|
|
1123
|
+
const flow = await loadDeclarativeFlow(flowRef);
|
|
1124
|
+
await runLoadedDeclarativeFlow(flowId, flow, config, flowParams, overrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1125
|
+
}
|
|
877
1126
|
async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
|
|
878
1127
|
const mergedFlowParams = {
|
|
879
1128
|
...defaultDeclarativeFlowParams(config, false, overrides),
|
|
@@ -1017,7 +1266,7 @@ async function summarizeBuildFailure(output) {
|
|
|
1017
1266
|
}), output);
|
|
1018
1267
|
}
|
|
1019
1268
|
function requireJiraConfig(config) {
|
|
1020
|
-
if (!config
|
|
1269
|
+
if (!hasJiraConfig(config)) {
|
|
1021
1270
|
throw new TaskRunnerError(`Command '${config.command}' requires Jira context in the current project scope.`);
|
|
1022
1271
|
}
|
|
1023
1272
|
}
|
|
@@ -1026,7 +1275,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
1026
1275
|
const exitCode = await runDoctorCommand(baseConfig.doctorArgs ?? []);
|
|
1027
1276
|
return exitCode === 0;
|
|
1028
1277
|
}
|
|
1029
|
-
const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput)));
|
|
1278
|
+
const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput, explicitLaunchMode)));
|
|
1030
1279
|
const flowOverrides = executionRouting
|
|
1031
1280
|
? {
|
|
1032
1281
|
launchProfile: executionRouting.defaultRoute,
|
|
@@ -1036,9 +1285,45 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
1036
1285
|
: launchProfile
|
|
1037
1286
|
? { launchProfile }
|
|
1038
1287
|
: {};
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1288
|
+
const configurableAutoSelection = config.autoFlowSelection
|
|
1289
|
+
?? (config.command === "auto" || config.command === "auto-status" || config.command === "auto-reset"
|
|
1290
|
+
? defaultAutoFlowSelection()
|
|
1291
|
+
: undefined);
|
|
1292
|
+
const resolvedConfigurableAuto = configurableAutoSelection
|
|
1293
|
+
? await resolveAutoFlow(configurableAutoSelection, {
|
|
1294
|
+
cwd: process.cwd(),
|
|
1295
|
+
scopeKey: config.scope.scopeKey,
|
|
1296
|
+
})
|
|
1297
|
+
: null;
|
|
1298
|
+
const configurableAutoIdentity = resolvedConfigurableAuto && configurableAutoSelection
|
|
1299
|
+
? autoFlowIdentityForSelection(configurableAutoSelection, resolvedConfigurableAuto)
|
|
1300
|
+
: null;
|
|
1301
|
+
if (resolvedConfigurableAuto && configurableAutoIdentity) {
|
|
1302
|
+
if (config.command !== "auto-status" && config.command !== "auto-reset") {
|
|
1303
|
+
config.command = configurableAutoIdentity.selectedCommand;
|
|
1304
|
+
}
|
|
1305
|
+
if (config.dryRunFlow) {
|
|
1306
|
+
writeStdoutSync(formatAutoFlowDryRunPreview(resolvedConfigurableAuto));
|
|
1307
|
+
return false;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
if (resolvedConfigurableAuto && configurableAutoIdentity && config.command === "auto-status") {
|
|
1311
|
+
await printConfigurableAutoStatus(config, resolvedConfigurableAuto, configurableAutoIdentity);
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
if (resolvedConfigurableAuto && configurableAutoIdentity && config.command === "auto-reset") {
|
|
1315
|
+
await printConfigurableAutoReset(config, resolvedConfigurableAuto, configurableAutoIdentity);
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
const launchFlowId = configurableAutoIdentity?.flowId ?? config.command;
|
|
1319
|
+
const launchMode = await chooseLaunchMode(launchFlowId, config.scope.scopeKey, explicitLaunchMode, requestUserInput);
|
|
1320
|
+
if (resolvedConfigurableAuto && configurableAutoIdentity) {
|
|
1321
|
+
syncJiraEnv(config);
|
|
1322
|
+
await checkResolvedAutoPrerequisites(resolvedConfigurableAuto, launchProfile, executionRouting);
|
|
1323
|
+
persistResolvedAutoFlowArtifacts(config.scope.scopeKey, resolvedConfigurableAuto);
|
|
1324
|
+
await runResolvedAutoFlow(resolvedConfigurableAuto, configurableAutoIdentity, config, flowOverrides, requestUserInput, setSummary, launchMode, runtime, forceRefreshSummary);
|
|
1325
|
+
return false;
|
|
1326
|
+
}
|
|
1042
1327
|
if (config.command === "instant-task") {
|
|
1043
1328
|
await checkPrerequisites(config, launchProfile, executionRouting);
|
|
1044
1329
|
const hasPersistedInstantTaskState = loadFlowRunState(config.scope.scopeKey, "instant-task") !== null;
|
|
@@ -1061,10 +1346,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
1061
1346
|
return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
|
|
1062
1347
|
}
|
|
1063
1348
|
if (config.command === "auto-golang") {
|
|
1064
|
-
|
|
1065
|
-
process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
|
|
1066
|
-
process.env.JIRA_API_URL = config.jiraApiUrl;
|
|
1067
|
-
process.env.JIRA_TASK_FILE = config.jiraTaskFile;
|
|
1349
|
+
syncJiraEnv(config);
|
|
1068
1350
|
let effectiveLaunchMode = launchMode;
|
|
1069
1351
|
let effectiveLaunchProfile = launchProfile;
|
|
1070
1352
|
let effectiveExecutionRouting = executionRouting;
|
|
@@ -1095,81 +1377,22 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
1095
1377
|
return false;
|
|
1096
1378
|
}
|
|
1097
1379
|
if (config.command === "auto-common" || config.command === "auto-common-guided") {
|
|
1098
|
-
requireJiraConfig(config);
|
|
1099
1380
|
await checkAutoPrerequisites(config, launchProfile, executionRouting);
|
|
1100
|
-
|
|
1101
|
-
process.env.JIRA_API_URL = config.jiraApiUrl;
|
|
1102
|
-
process.env.JIRA_TASK_FILE = config.jiraTaskFile;
|
|
1381
|
+
syncJiraEnv(config);
|
|
1103
1382
|
await runDeclarativeFlowBySpecFile(config.command === "auto-common-guided" ? "auto-common-guided.json" : "auto-common.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1104
1383
|
return false;
|
|
1105
1384
|
}
|
|
1106
1385
|
if (config.command === "auto-simple") {
|
|
1107
|
-
requireJiraConfig(config);
|
|
1108
1386
|
await checkAutoPrerequisites(config, launchProfile, executionRouting);
|
|
1109
|
-
|
|
1110
|
-
process.env.JIRA_API_URL = config.jiraApiUrl;
|
|
1111
|
-
process.env.JIRA_TASK_FILE = config.jiraTaskFile;
|
|
1387
|
+
syncJiraEnv(config);
|
|
1112
1388
|
await runDeclarativeFlowBySpecFile("auto-simple.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1113
1389
|
return false;
|
|
1114
1390
|
}
|
|
1115
|
-
if (config.command === "auto-status") {
|
|
1116
|
-
const state = loadFlowRunState(config.scope.scopeKey, "auto-golang");
|
|
1117
|
-
if (!state) {
|
|
1118
|
-
printPanel("Auto-Golang Status", `No flow state file found for ${config.taskKey}.`, "yellow");
|
|
1119
|
-
return false;
|
|
1120
|
-
}
|
|
1121
|
-
const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
|
|
1122
|
-
const phaseOrder = (await loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" })).phases;
|
|
1123
|
-
const lines = [
|
|
1124
|
-
`Issue: ${config.taskKey}`,
|
|
1125
|
-
`Status: ${state.status}`,
|
|
1126
|
-
`Current step: ${currentStep}`,
|
|
1127
|
-
`Updated: ${state.updatedAt}`,
|
|
1128
|
-
];
|
|
1129
|
-
if (state.executionRouting) {
|
|
1130
|
-
lines.push(`Default route: ${state.executionRouting.defaultRoute.executor} / ${state.executionRouting.defaultRoute.model}`);
|
|
1131
|
-
lines.push(`Routing fingerprint: ${state.executionRouting.fingerprint}`);
|
|
1132
|
-
}
|
|
1133
|
-
else if (state.launchProfile) {
|
|
1134
|
-
lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
|
|
1135
|
-
}
|
|
1136
|
-
if (state.lastError) {
|
|
1137
|
-
lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
|
|
1138
|
-
}
|
|
1139
|
-
lines.push("");
|
|
1140
|
-
for (const phase of phaseOrder) {
|
|
1141
|
-
const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
|
|
1142
|
-
lines.push(`[${phaseState?.status ?? "pending"}] ${phase.id}`);
|
|
1143
|
-
for (const step of phase.steps) {
|
|
1144
|
-
const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
|
|
1145
|
-
lines.push(` - [${stepState?.status ?? "pending"}] ${step.id}`);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
if (state.executionState.terminated) {
|
|
1149
|
-
lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
|
|
1150
|
-
}
|
|
1151
|
-
printPanel("Auto-Golang Status", lines.join("\n"), "cyan");
|
|
1152
|
-
return false;
|
|
1153
|
-
}
|
|
1154
|
-
if (config.command === "auto-reset") {
|
|
1155
|
-
const removed = resetFlowRunState(config.scope.scopeKey, "auto-golang");
|
|
1156
|
-
printPanel("Auto-Golang Reset", removed ? `State file ${flowStateFile(config.scope.scopeKey, "auto-golang")} removed.` : "No flow state file found.", "yellow");
|
|
1157
|
-
return false;
|
|
1158
|
-
}
|
|
1159
1391
|
await checkPrerequisites(config, launchProfile, executionRouting);
|
|
1160
|
-
|
|
1161
|
-
process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl ?? "";
|
|
1162
|
-
process.env.JIRA_API_URL = config.jiraApiUrl ?? "";
|
|
1163
|
-
process.env.JIRA_TASK_FILE = config.jiraTaskFile ?? "";
|
|
1164
|
-
}
|
|
1165
|
-
else {
|
|
1166
|
-
delete process.env.JIRA_BROWSE_URL;
|
|
1167
|
-
delete process.env.JIRA_API_URL;
|
|
1168
|
-
delete process.env.JIRA_TASK_FILE;
|
|
1169
|
-
}
|
|
1392
|
+
syncJiraEnv(config);
|
|
1170
1393
|
if (config.command === "plan") {
|
|
1171
1394
|
let taskContextIteration;
|
|
1172
|
-
if (config
|
|
1395
|
+
if (hasJiraConfig(config)) {
|
|
1173
1396
|
requireJiraConfig(config);
|
|
1174
1397
|
if (config.verbose) {
|
|
1175
1398
|
process.stdout.write(`Fetching Jira issue from browse URL: ${config.jiraBrowseUrl}\n`);
|
|
@@ -1188,7 +1411,21 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
1188
1411
|
}, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1189
1412
|
}
|
|
1190
1413
|
else {
|
|
1191
|
-
|
|
1414
|
+
const latestTaskContext = latestArtifactIteration(config.taskKey, "task-context", "json");
|
|
1415
|
+
if (latestTaskContext !== null) {
|
|
1416
|
+
taskContextIteration = latestTaskContext;
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
taskContextIteration = nextArtifactIteration(config.taskKey, "task-context", "json");
|
|
1420
|
+
await runDeclarativeFlowBySpecFile("task-source/manual-jira-input.json", config, {
|
|
1421
|
+
taskKey: config.taskKey,
|
|
1422
|
+
}, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1423
|
+
await runDeclarativeFlowBySpecFile("normalize-task-source.json", config, {
|
|
1424
|
+
taskKey: config.taskKey,
|
|
1425
|
+
iteration: taskContextIteration,
|
|
1426
|
+
extraPrompt: config.extraPrompt,
|
|
1427
|
+
}, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1428
|
+
}
|
|
1192
1429
|
}
|
|
1193
1430
|
await runDeclarativeFlowBySpecFile("plan.json", config, {
|
|
1194
1431
|
taskKey: config.taskKey,
|
|
@@ -1481,12 +1718,15 @@ async function parseCliArgs(argv) {
|
|
|
1481
1718
|
writeStderrSync(`${usage()}\n`);
|
|
1482
1719
|
process.exit(1);
|
|
1483
1720
|
}
|
|
1484
|
-
const
|
|
1485
|
-
if (!COMMANDS.includes(
|
|
1721
|
+
const rawCommand = argv[0];
|
|
1722
|
+
if (!COMMANDS.includes(rawCommand)) {
|
|
1486
1723
|
writeStderrSync(`${usage()}\n`);
|
|
1487
1724
|
process.exit(1);
|
|
1488
1725
|
}
|
|
1726
|
+
const isConfigurableAutoCommand = rawCommand === "auto";
|
|
1727
|
+
const supportsAutoFlowSelection = rawCommand === "auto" || rawCommand === "auto-status" || rawCommand === "auto-reset";
|
|
1489
1728
|
let dry = false;
|
|
1729
|
+
let dryRunFlow = false;
|
|
1490
1730
|
let verbose = false;
|
|
1491
1731
|
let prompt;
|
|
1492
1732
|
let autoFromPhase;
|
|
@@ -1497,9 +1737,26 @@ async function parseCliArgs(argv) {
|
|
|
1497
1737
|
let mdLang;
|
|
1498
1738
|
let launchMode;
|
|
1499
1739
|
let acceptPlaybookDraft = false;
|
|
1740
|
+
let autoPreset;
|
|
1741
|
+
let autoConfigName;
|
|
1500
1742
|
let webNoOpen = process.env.AGENTWEAVER_WEB_NO_OPEN === "1";
|
|
1501
1743
|
let webHost;
|
|
1502
1744
|
const doctorArgs = [];
|
|
1745
|
+
const readRequiredValue = (flag, index) => {
|
|
1746
|
+
const value = argv[index + 1]?.trim();
|
|
1747
|
+
if (!value || value.startsWith("-")) {
|
|
1748
|
+
writeStderrSync(`Error: ${flag} requires a value.\n`);
|
|
1749
|
+
process.exit(1);
|
|
1750
|
+
}
|
|
1751
|
+
return value;
|
|
1752
|
+
};
|
|
1753
|
+
const parsePresetValue = (value) => {
|
|
1754
|
+
if (value === "simple" || value === "standard") {
|
|
1755
|
+
return value;
|
|
1756
|
+
}
|
|
1757
|
+
writeStderrSync("Error: --preset accepts only 'simple' or 'standard'.\n");
|
|
1758
|
+
process.exit(1);
|
|
1759
|
+
};
|
|
1503
1760
|
for (let index = 1; index < argv.length; index += 1) {
|
|
1504
1761
|
const token = argv[index] ?? "";
|
|
1505
1762
|
if (token === "--dry") {
|
|
@@ -1510,6 +1767,58 @@ async function parseCliArgs(argv) {
|
|
|
1510
1767
|
verbose = true;
|
|
1511
1768
|
continue;
|
|
1512
1769
|
}
|
|
1770
|
+
if (token === "--dry-run-flow") {
|
|
1771
|
+
if (!isConfigurableAutoCommand) {
|
|
1772
|
+
writeStderrSync("Error: --dry-run-flow is only supported after the auto command.\n");
|
|
1773
|
+
process.exit(1);
|
|
1774
|
+
}
|
|
1775
|
+
dryRunFlow = true;
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
if (token === "--preset") {
|
|
1779
|
+
if (!supportsAutoFlowSelection) {
|
|
1780
|
+
writeStderrSync("Error: --preset is only supported after auto, auto-status, or auto-reset.\n");
|
|
1781
|
+
process.exit(1);
|
|
1782
|
+
}
|
|
1783
|
+
autoPreset = parsePresetValue(readRequiredValue("--preset", index));
|
|
1784
|
+
index += 1;
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
if (token.startsWith("--preset=")) {
|
|
1788
|
+
if (!supportsAutoFlowSelection) {
|
|
1789
|
+
writeStderrSync("Error: --preset is only supported after auto, auto-status, or auto-reset.\n");
|
|
1790
|
+
process.exit(1);
|
|
1791
|
+
}
|
|
1792
|
+
const value = token.slice("--preset=".length).trim();
|
|
1793
|
+
if (!value) {
|
|
1794
|
+
writeStderrSync("Error: --preset requires a value.\n");
|
|
1795
|
+
process.exit(1);
|
|
1796
|
+
}
|
|
1797
|
+
autoPreset = parsePresetValue(value);
|
|
1798
|
+
continue;
|
|
1799
|
+
}
|
|
1800
|
+
if (token === "--config") {
|
|
1801
|
+
if (!supportsAutoFlowSelection) {
|
|
1802
|
+
writeStderrSync("Error: --config is only supported after auto, auto-status, or auto-reset.\n");
|
|
1803
|
+
process.exit(1);
|
|
1804
|
+
}
|
|
1805
|
+
autoConfigName = readRequiredValue("--config", index);
|
|
1806
|
+
index += 1;
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
if (token.startsWith("--config=")) {
|
|
1810
|
+
if (!supportsAutoFlowSelection) {
|
|
1811
|
+
writeStderrSync("Error: --config is only supported after auto, auto-status, or auto-reset.\n");
|
|
1812
|
+
process.exit(1);
|
|
1813
|
+
}
|
|
1814
|
+
const value = token.slice("--config=".length).trim();
|
|
1815
|
+
if (!value) {
|
|
1816
|
+
writeStderrSync("Error: --config requires a value.\n");
|
|
1817
|
+
process.exit(1);
|
|
1818
|
+
}
|
|
1819
|
+
autoConfigName = value;
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1513
1822
|
if (token === "--help-phases") {
|
|
1514
1823
|
helpPhases = true;
|
|
1515
1824
|
continue;
|
|
@@ -1519,7 +1828,7 @@ async function parseCliArgs(argv) {
|
|
|
1519
1828
|
continue;
|
|
1520
1829
|
}
|
|
1521
1830
|
if (token === "--no-open") {
|
|
1522
|
-
if (
|
|
1831
|
+
if (rawCommand !== "web") {
|
|
1523
1832
|
writeStderrSync("Error: --no-open is only supported after the web command.\n");
|
|
1524
1833
|
process.exit(1);
|
|
1525
1834
|
}
|
|
@@ -1527,7 +1836,7 @@ async function parseCliArgs(argv) {
|
|
|
1527
1836
|
continue;
|
|
1528
1837
|
}
|
|
1529
1838
|
if (token === "--listen-all") {
|
|
1530
|
-
if (
|
|
1839
|
+
if (rawCommand !== "web") {
|
|
1531
1840
|
writeStderrSync("Error: --listen-all is only supported after the web command.\n");
|
|
1532
1841
|
process.exit(1);
|
|
1533
1842
|
}
|
|
@@ -1535,7 +1844,7 @@ async function parseCliArgs(argv) {
|
|
|
1535
1844
|
continue;
|
|
1536
1845
|
}
|
|
1537
1846
|
if (token === "--host") {
|
|
1538
|
-
if (
|
|
1847
|
+
if (rawCommand !== "web") {
|
|
1539
1848
|
writeStderrSync("Error: --host is only supported after the web command.\n");
|
|
1540
1849
|
process.exit(1);
|
|
1541
1850
|
}
|
|
@@ -1549,7 +1858,7 @@ async function parseCliArgs(argv) {
|
|
|
1549
1858
|
continue;
|
|
1550
1859
|
}
|
|
1551
1860
|
if (token.startsWith("--host=")) {
|
|
1552
|
-
if (
|
|
1861
|
+
if (rawCommand !== "web") {
|
|
1553
1862
|
writeStderrSync("Error: --host is only supported after the web command.\n");
|
|
1554
1863
|
process.exit(1);
|
|
1555
1864
|
}
|
|
@@ -1616,13 +1925,29 @@ async function parseCliArgs(argv) {
|
|
|
1616
1925
|
}
|
|
1617
1926
|
continue;
|
|
1618
1927
|
}
|
|
1619
|
-
if (
|
|
1928
|
+
if (rawCommand === "doctor") {
|
|
1620
1929
|
doctorArgs.push(token);
|
|
1621
1930
|
}
|
|
1622
1931
|
else {
|
|
1623
1932
|
jiraRef = token;
|
|
1624
1933
|
}
|
|
1625
1934
|
}
|
|
1935
|
+
if (autoPreset && autoConfigName) {
|
|
1936
|
+
writeStderrSync("Error: --preset and --config are mutually exclusive.\n");
|
|
1937
|
+
process.exit(1);
|
|
1938
|
+
}
|
|
1939
|
+
const autoFlowSelection = supportsAutoFlowSelection
|
|
1940
|
+
? autoConfigName
|
|
1941
|
+
? { kind: "config", name: autoConfigName }
|
|
1942
|
+
: autoPreset
|
|
1943
|
+
? { kind: "preset", preset: autoPreset }
|
|
1944
|
+
: defaultAutoFlowSelection()
|
|
1945
|
+
: undefined;
|
|
1946
|
+
const command = isConfigurableAutoCommand
|
|
1947
|
+
? autoFlowSelection?.kind === "preset" && autoFlowSelection.preset === "simple"
|
|
1948
|
+
? "auto-simple"
|
|
1949
|
+
: "auto-common"
|
|
1950
|
+
: rawCommand;
|
|
1626
1951
|
if (command === "auto-golang" && helpPhases) {
|
|
1627
1952
|
await printAutoPhasesHelp();
|
|
1628
1953
|
process.exit(0);
|
|
@@ -1638,6 +1963,7 @@ async function parseCliArgs(argv) {
|
|
|
1638
1963
|
return {
|
|
1639
1964
|
command: command,
|
|
1640
1965
|
dry,
|
|
1966
|
+
dryRunFlow,
|
|
1641
1967
|
verbose,
|
|
1642
1968
|
helpPhases,
|
|
1643
1969
|
...(jiraRef !== undefined ? { jiraRef } : {}),
|
|
@@ -1649,6 +1975,7 @@ async function parseCliArgs(argv) {
|
|
|
1649
1975
|
...(doctorArgs.length > 0 ? { doctorArgs } : {}),
|
|
1650
1976
|
...(launchMode !== undefined ? { launchMode } : {}),
|
|
1651
1977
|
...(acceptPlaybookDraft ? { acceptPlaybookDraft } : {}),
|
|
1978
|
+
...(autoFlowSelection !== undefined ? { autoFlowSelection } : {}),
|
|
1652
1979
|
...(command === "web" ? { webNoOpen } : {}),
|
|
1653
1980
|
...(command === "web" && webHost !== undefined ? { webHost } : {}),
|
|
1654
1981
|
};
|
|
@@ -1662,9 +1989,11 @@ function buildConfigFromArgs(args) {
|
|
|
1662
1989
|
...(args.autoFromPhase !== undefined ? { autoFromPhase: args.autoFromPhase } : {}),
|
|
1663
1990
|
...(args.mdLang !== undefined ? { mdLang: args.mdLang } : {}),
|
|
1664
1991
|
dryRun: args.dry,
|
|
1992
|
+
dryRunFlow: args.dryRunFlow,
|
|
1665
1993
|
verbose: args.verbose,
|
|
1666
1994
|
...(args.doctorArgs !== undefined ? { doctorArgs: args.doctorArgs } : {}),
|
|
1667
1995
|
...(args.acceptPlaybookDraft !== undefined ? { acceptPlaybookDraft: args.acceptPlaybookDraft } : {}),
|
|
1996
|
+
...(args.autoFlowSelection !== undefined ? { autoFlowSelection: args.autoFlowSelection } : {}),
|
|
1668
1997
|
});
|
|
1669
1998
|
}
|
|
1670
1999
|
async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRefresh = false, scopeName, installSignalCleanup = false) {
|
|
@@ -1724,6 +2053,10 @@ async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRef
|
|
|
1724
2053
|
flows: interactiveFlowDefinitions(flowCatalog),
|
|
1725
2054
|
getRunConfirmation: async (flowId) => {
|
|
1726
2055
|
refreshScopeFromGit("git scope refresh before launch confirmation");
|
|
2056
|
+
const autoFlowSelection = autoFlowSelectionForFlowId(flowId);
|
|
2057
|
+
if (autoFlowSelection) {
|
|
2058
|
+
return await lookupInteractiveAutoFlowResume(autoFlowSelection, currentScope);
|
|
2059
|
+
}
|
|
1727
2060
|
const flowEntry = findCatalogEntry(flowId, flowCatalog);
|
|
1728
2061
|
if (!flowEntry) {
|
|
1729
2062
|
throw new TaskRunnerError(`Unknown flow: ${flowId}`);
|
|
@@ -1737,6 +2070,19 @@ async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRef
|
|
|
1737
2070
|
activeAbortController = abortController;
|
|
1738
2071
|
activeFlowId = flowId;
|
|
1739
2072
|
try {
|
|
2073
|
+
const autoFlowSelection = autoFlowSelectionForFlowId(flowId);
|
|
2074
|
+
if (autoFlowSelection) {
|
|
2075
|
+
const previousScopeKey = currentScope.scopeKey;
|
|
2076
|
+
const baseConfig = buildInteractiveBaseConfig(flowId, currentScope);
|
|
2077
|
+
const nextScope = await resolveScopeForCommand(baseConfig, (form) => ui.requestUserInput(form), launchMode);
|
|
2078
|
+
currentScope = nextScope;
|
|
2079
|
+
ui.setScope(currentScope.scopeKey, currentScope.jiraIssueKey ?? null, currentScope.gitBranchName);
|
|
2080
|
+
if (previousScopeKey !== currentScope.scopeKey || currentScope.jiraIssueKey) {
|
|
2081
|
+
syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
|
|
2082
|
+
}
|
|
2083
|
+
await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode, undefined, undefined, undefined, createRuntimeServices(abortController.signal));
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
1740
2086
|
const flowEntry = findCatalogEntry(flowId, flowCatalog);
|
|
1741
2087
|
if (!flowEntry) {
|
|
1742
2088
|
throw new TaskRunnerError(`Unknown flow: ${flowId}`);
|
|
@@ -1761,7 +2107,7 @@ async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRef
|
|
|
1761
2107
|
const previousScopeKey = currentScope.scopeKey;
|
|
1762
2108
|
const baseConfig = buildInteractiveBaseConfig(flowId, currentScope);
|
|
1763
2109
|
if (flowEntry.source === "built-in" && isBuiltInCommandFlowId(flowId)) {
|
|
1764
|
-
const nextScope = await resolveScopeForCommand(baseConfig, (form) => ui.requestUserInput(form));
|
|
2110
|
+
const nextScope = await resolveScopeForCommand(baseConfig, (form) => ui.requestUserInput(form), launchMode);
|
|
1765
2111
|
currentScope = nextScope;
|
|
1766
2112
|
}
|
|
1767
2113
|
else if (flowRequiresTaskScope(flowEntry) && !currentScope.jiraRef) {
|