agentweaver 0.1.17 → 0.1.19
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 +112 -23
- package/dist/artifacts.js +41 -0
- package/dist/index.js +258 -29
- package/dist/interactive/controller.js +323 -13
- package/dist/interactive/ink/index.js +2 -2
- package/dist/interactive/state.js +10 -0
- package/dist/interactive/web/index.js +326 -0
- package/dist/interactive/web/protocol.js +160 -0
- package/dist/interactive/web/server.js +1011 -0
- package/dist/interactive/web/static/app.js +1580 -0
- package/dist/interactive/web/static/index.html +114 -0
- package/dist/interactive/web/static/styles.css +2 -0
- package/dist/interactive/web/static/styles.input.css +849 -0
- package/dist/pipeline/flow-catalog.js +4 -0
- package/dist/pipeline/flow-specs/auto-common-guided.json +313 -0
- package/dist/pipeline/flow-specs/auto-common.json +3 -1
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +2 -0
- package/dist/pipeline/flow-specs/design-review.json +2 -0
- package/dist/pipeline/flow-specs/implement.json +3 -1
- package/dist/pipeline/flow-specs/plan.json +4 -0
- package/dist/pipeline/flow-specs/playbook-init.json +199 -0
- package/dist/pipeline/flow-specs/review/review-fix.json +3 -1
- package/dist/pipeline/flow-specs/review/review-loop.json +4 -0
- package/dist/pipeline/flow-specs/review/review.json +2 -0
- package/dist/pipeline/node-registry.js +45 -0
- package/dist/pipeline/nodes/flow-run-node.js +13 -1
- package/dist/pipeline/nodes/playbook-ensure-node.js +115 -0
- package/dist/pipeline/nodes/playbook-inventory-node.js +51 -0
- package/dist/pipeline/nodes/playbook-questions-form-node.js +166 -0
- package/dist/pipeline/nodes/playbook-write-node.js +243 -0
- package/dist/pipeline/nodes/project-guidance-node.js +69 -0
- package/dist/pipeline/prompt-registry.js +4 -1
- package/dist/pipeline/prompt-runtime.js +6 -2
- package/dist/pipeline/spec-types.js +19 -0
- package/dist/pipeline/value-resolver.js +39 -1
- package/dist/playbook/practice-candidates.js +12 -0
- package/dist/playbook/repo-inventory.js +208 -0
- package/dist/prompts.js +31 -0
- package/dist/runtime/artifact-catalog.js +379 -0
- package/dist/runtime/playbook.js +485 -0
- package/dist/runtime/project-guidance.js +339 -0
- package/dist/structured-artifact-schema-registry.js +8 -0
- package/dist/structured-artifact-schemas.json +235 -0
- package/dist/structured-artifacts.js +7 -1
- package/docs/declarative-workflows.md +565 -0
- package/docs/features.md +77 -0
- package/docs/playbook.md +327 -0
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, writeSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -32,12 +32,15 @@ import { clearReadyToMergeFile } from "./runtime/ready-to-merge.js";
|
|
|
32
32
|
import { describeExecutionRouting, executorsForRoutingGroups, resolveExecutionRouting, } from "./runtime/execution-routing.js";
|
|
33
33
|
import { requestInteractiveExecutionRouting } from "./runtime/interactive-execution-routing.js";
|
|
34
34
|
import { createInteractiveSession } from "./interactive/create-interactive-session.js";
|
|
35
|
+
import { createWebInteractiveSession } from "./interactive/web/index.js";
|
|
35
36
|
import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
|
|
36
37
|
import { requestUserInputInTerminal } from "./user-input.js";
|
|
37
38
|
import { runDoctorCommand } from "./doctor/index.js";
|
|
38
|
-
import {
|
|
39
|
+
import { requestJiraContext, resolveProjectScope, } from "./scope.js";
|
|
39
40
|
const COMMANDS = [
|
|
41
|
+
"auto",
|
|
40
42
|
"auto-golang",
|
|
43
|
+
"auto-common-guided",
|
|
41
44
|
"auto-common",
|
|
42
45
|
"auto-simple",
|
|
43
46
|
"auto-status",
|
|
@@ -53,7 +56,9 @@ const COMMANDS = [
|
|
|
53
56
|
"mr-description",
|
|
54
57
|
"plan",
|
|
55
58
|
"plan-revise",
|
|
59
|
+
"playbook-init",
|
|
56
60
|
"task-describe",
|
|
61
|
+
"web",
|
|
57
62
|
"implement",
|
|
58
63
|
"review",
|
|
59
64
|
"review-fix",
|
|
@@ -61,8 +66,17 @@ const COMMANDS = [
|
|
|
61
66
|
"run-go-tests-loop",
|
|
62
67
|
"run-go-linter-loop",
|
|
63
68
|
];
|
|
69
|
+
const INTERACTIVE_SCOPE_WATCH_INTERVAL_MS = 1500;
|
|
70
|
+
const WEB_AUTH_USERNAME_ENV = "AGENTWEAVER_WEB_USERNAME";
|
|
71
|
+
const WEB_AUTH_PASSWORD_ENV = "AGENTWEAVER_WEB_PASSWORD";
|
|
64
72
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
65
73
|
const PACKAGE_ROOT = path.resolve(MODULE_DIR, "..");
|
|
74
|
+
function writeStdoutSync(text) {
|
|
75
|
+
writeSync(process.stdout.fd, text);
|
|
76
|
+
}
|
|
77
|
+
function writeStderrSync(text) {
|
|
78
|
+
writeSync(process.stderr.fd, text);
|
|
79
|
+
}
|
|
66
80
|
function createRuntimeServices(signal) {
|
|
67
81
|
return {
|
|
68
82
|
resolveCmd,
|
|
@@ -71,6 +85,37 @@ function createRuntimeServices(signal) {
|
|
|
71
85
|
};
|
|
72
86
|
}
|
|
73
87
|
const runtimeServices = createRuntimeServices();
|
|
88
|
+
function isExternalWebHost(host) {
|
|
89
|
+
const normalized = (host?.trim() || "127.0.0.1").toLowerCase();
|
|
90
|
+
if (normalized === "127.0.0.1" || normalized === "::1" || normalized === "localhost") {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const unbracketed = normalized.startsWith("[") && normalized.endsWith("]") ? normalized.slice(1, -1) : normalized;
|
|
94
|
+
if (unbracketed === "::1") {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
function resolveWebAuthConfig() {
|
|
100
|
+
const username = process.env[WEB_AUTH_USERNAME_ENV]?.trim() ?? "";
|
|
101
|
+
const password = process.env[WEB_AUTH_PASSWORD_ENV] ?? "";
|
|
102
|
+
const hasUsername = username.length > 0;
|
|
103
|
+
const hasPassword = password.length > 0;
|
|
104
|
+
if (hasUsername !== hasPassword) {
|
|
105
|
+
throw new TaskRunnerError(`Web UI auth requires both ${WEB_AUTH_USERNAME_ENV} and ${WEB_AUTH_PASSWORD_ENV}.`);
|
|
106
|
+
}
|
|
107
|
+
if (!hasUsername || !hasPassword) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
return { username, password };
|
|
111
|
+
}
|
|
112
|
+
function requireWebAuthForHost(host, auth) {
|
|
113
|
+
if (!isExternalWebHost(host) || auth) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
throw new TaskRunnerError(`External Web UI binding requires ${WEB_AUTH_USERNAME_ENV} and ${WEB_AUTH_PASSWORD_ENV}. ` +
|
|
117
|
+
"Use localhost for no-auth local access, or configure credentials before using --listen-all or --host with an external interface.");
|
|
118
|
+
}
|
|
74
119
|
function buildFailureOutputPreview(output) {
|
|
75
120
|
const normalized = stripAnsi(output).replace(/\r\n/g, "\n").trim();
|
|
76
121
|
if (!normalized) {
|
|
@@ -110,6 +155,7 @@ function usage() {
|
|
|
110
155
|
agentweaver
|
|
111
156
|
agentweaver <jira-browse-url|jira-issue-key>
|
|
112
157
|
agentweaver --force <jira-browse-url|jira-issue-key>
|
|
158
|
+
agentweaver web [--no-open] [--host <host>|--listen-all] [<jira-browse-url|jira-issue-key>]
|
|
113
159
|
agentweaver git-commit [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
114
160
|
agentweaver gitlab-diff-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
|
|
115
161
|
agentweaver gitlab-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
|
|
@@ -121,6 +167,7 @@ function usage() {
|
|
|
121
167
|
agentweaver mr-description [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
122
168
|
agentweaver plan [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
|
|
123
169
|
agentweaver plan-revise [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
170
|
+
agentweaver playbook-init [--dry] [--verbose] [--prompt <text>] [--accept-playbook-draft] [--scope <name>]
|
|
124
171
|
agentweaver task-describe [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
|
|
125
172
|
agentweaver implement [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
126
173
|
agentweaver review [--dry] [--verbose] [--prompt <text>] [--scope <name>] [--blocking-severities <list>] [<jira-browse-url|jira-issue-key>]
|
|
@@ -128,9 +175,12 @@ function usage() {
|
|
|
128
175
|
agentweaver review-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [--blocking-severities <list>] [<jira-browse-url|jira-issue-key>]
|
|
129
176
|
agentweaver run-go-tests-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
130
177
|
agentweaver run-go-linter-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
|
|
178
|
+
agentweaver auto [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
|
|
179
|
+
agentweaver auto --help-phases
|
|
131
180
|
agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
|
|
132
181
|
agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] --from <phase> [<jira-browse-url|jira-issue-key>]
|
|
133
182
|
agentweaver auto-golang --help-phases
|
|
183
|
+
agentweaver auto-common-guided [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [--accept-playbook-draft] <jira-browse-url|jira-issue-key>
|
|
134
184
|
agentweaver auto-common [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
|
|
135
185
|
agentweaver auto-common --help-phases
|
|
136
186
|
agentweaver auto-simple [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
|
|
@@ -146,6 +196,9 @@ Interactive Mode:
|
|
|
146
196
|
Flags:
|
|
147
197
|
--version Show package version
|
|
148
198
|
--force In interactive mode, regenerate task summary in Jira-backed flows
|
|
199
|
+
--no-open Web command only: print the Web UI URL without opening a browser
|
|
200
|
+
--host Web command only: bind Web UI to this host (default: 127.0.0.1)
|
|
201
|
+
--listen-all Web command only: bind Web UI to 0.0.0.0
|
|
149
202
|
--dry Fetch Jira task, but print codex/opencode commands instead of executing them
|
|
150
203
|
--verbose Show live stdout/stderr of launched commands
|
|
151
204
|
--scope Explicit workflow scope name for non-Jira runs except instant-task
|
|
@@ -154,7 +207,8 @@ Flags:
|
|
|
154
207
|
--continue Continue a terminated iterative run when valid
|
|
155
208
|
--restart Archive the active attempt and start a fresh run
|
|
156
209
|
--blocking-severities Comma-separated severities that block merge and drive review-fix auto-selection
|
|
157
|
-
--md-lang Language for markdown
|
|
210
|
+
--md-lang Language for workflow markdown artifacts only: en (English) or ru (Russian, default)
|
|
211
|
+
--accept-playbook-draft Non-interactively accept generated playbook content for playbook-init or auto-common-guided missing-manifest runs
|
|
158
212
|
|
|
159
213
|
Required environment variables:
|
|
160
214
|
JIRA_API_KEY Jira API token used for Jira-backed flows (Bearer by default, or Basic with Jira Cloud)
|
|
@@ -170,9 +224,15 @@ Optional environment variables:
|
|
|
170
224
|
CODEX_MODEL
|
|
171
225
|
OPENCODE_BIN
|
|
172
226
|
OPENCODE_MODEL
|
|
227
|
+
AGENTWEAVER_WEB_NO_OPEN Set to 1 to disable browser auto-open for agentweaver web
|
|
228
|
+
${WEB_AUTH_USERNAME_ENV} Web UI Basic auth username; required for external Web UI binding
|
|
229
|
+
${WEB_AUTH_PASSWORD_ENV} Web UI Basic auth password; required for external Web UI binding
|
|
173
230
|
|
|
174
231
|
Notes:
|
|
175
232
|
- 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.
|
|
233
|
+
- 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.
|
|
234
|
+
- 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}.
|
|
235
|
+
- Web UI Basic auth over plain HTTP is suitable only on trusted networks; use TLS termination or a reverse proxy on untrusted networks.
|
|
176
236
|
- instant-task always uses the current branch-derived project scope and rejects explicit scope overrides or Jira arguments.
|
|
177
237
|
- All flow state and artifacts are stored in the current project scope by default.
|
|
178
238
|
- gitlab-review and gitlab-diff-review ask for GitLab merge request URL via user-input.
|
|
@@ -393,13 +453,13 @@ async function printAutoPhasesHelp() {
|
|
|
393
453
|
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
454
|
printPanel("Auto-Golang Phases", phaseLines.join("\n"), "magenta");
|
|
395
455
|
}
|
|
396
|
-
async function autoCommonPhaseIds() {
|
|
397
|
-
return (await loadDeclarativeFlow({ source: "built-in", fileName
|
|
456
|
+
async function autoCommonPhaseIds(fileName = "auto-common.json") {
|
|
457
|
+
return (await loadDeclarativeFlow({ source: "built-in", fileName })).phases.map((phase) => phase.id);
|
|
398
458
|
}
|
|
399
|
-
async function printAutoCommonPhasesHelp() {
|
|
400
|
-
const phaseLines = [
|
|
401
|
-
phaseLines.push("",
|
|
402
|
-
printPanel("Auto-Common Phases", phaseLines.join("\n"), "magenta");
|
|
459
|
+
async function printAutoCommonPhasesHelp(command = "auto-common", fileName = "auto-common.json") {
|
|
460
|
+
const phaseLines = [`Available ${command} phases:`, "", ...(await autoCommonPhaseIds(fileName))];
|
|
461
|
+
phaseLines.push("", `You can run ${command} with:`, `agentweaver ${command} <jira>`);
|
|
462
|
+
printPanel(command === "auto-common-guided" ? "Auto-Common Guided Phases" : "Auto-Common Phases", phaseLines.join("\n"), "magenta");
|
|
403
463
|
}
|
|
404
464
|
async function autoSimplePhaseIds() {
|
|
405
465
|
return (await loadDeclarativeFlow({ source: "built-in", fileName: "auto-simple.json" })).phases.map((phase) => phase.id);
|
|
@@ -428,6 +488,7 @@ function buildBaseConfig(command, options = {}) {
|
|
|
428
488
|
dryRun: options.dryRun ?? false,
|
|
429
489
|
verbose: options.verbose ?? false,
|
|
430
490
|
...(options.doctorArgs !== undefined ? { doctorArgs: options.doctorArgs } : {}),
|
|
491
|
+
...(options.acceptPlaybookDraft !== undefined ? { acceptPlaybookDraft: options.acceptPlaybookDraft } : {}),
|
|
431
492
|
};
|
|
432
493
|
}
|
|
433
494
|
function commandRequiresTask(command) {
|
|
@@ -437,6 +498,7 @@ function commandRequiresTask(command) {
|
|
|
437
498
|
command === "design-review" ||
|
|
438
499
|
command === "mr-description" ||
|
|
439
500
|
command === "auto-golang" ||
|
|
501
|
+
command === "auto-common-guided" ||
|
|
440
502
|
command === "auto-common" ||
|
|
441
503
|
command === "auto-simple" ||
|
|
442
504
|
command === "auto-status" ||
|
|
@@ -448,6 +510,7 @@ function commandSupportsProjectScope(command) {
|
|
|
448
510
|
command === "gitlab-diff-review" ||
|
|
449
511
|
command === "gitlab-review" ||
|
|
450
512
|
command === "instant-task" ||
|
|
513
|
+
command === "playbook-init" ||
|
|
451
514
|
command === "task-describe" ||
|
|
452
515
|
command === "implement" ||
|
|
453
516
|
command === "review" ||
|
|
@@ -572,6 +635,8 @@ function autoFlowParams(config, forceRefreshSummary = false) {
|
|
|
572
635
|
reviewBlockingSeverities: config.reviewBlockingSeverities,
|
|
573
636
|
forceRefresh: forceRefreshSummary,
|
|
574
637
|
mdLang: config.mdLang,
|
|
638
|
+
acceptPlaybookDraft: config.command === "auto-common-guided" ? config.acceptPlaybookDraft === true : false,
|
|
639
|
+
launchMode: config.command === "auto-common-guided" ? config.autoFromPhase ?? "restart" : undefined,
|
|
575
640
|
runGoTestsScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_tests.py"),
|
|
576
641
|
runGoLinterScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_linter.py"),
|
|
577
642
|
runGoTestsIteration: nextArtifactIteration(config.taskKey, "run-go-tests-result", "json"),
|
|
@@ -849,6 +914,10 @@ function defaultDeclarativeFlowParams(config, forceRefreshSummary = false, overr
|
|
|
849
914
|
mdLang: config.mdLang,
|
|
850
915
|
llmExecutor: launchProfile.executor,
|
|
851
916
|
llmModel: launchProfile.model,
|
|
917
|
+
projectGuidanceFile: "not provided",
|
|
918
|
+
projectGuidanceJsonFile: "not provided",
|
|
919
|
+
repairProjectGuidanceFile: "not provided",
|
|
920
|
+
repairProjectGuidanceJsonFile: "not provided",
|
|
852
921
|
launchProfile,
|
|
853
922
|
executionRouting,
|
|
854
923
|
iteration,
|
|
@@ -1028,13 +1097,13 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
1028
1097
|
: {}, requestUserInput, setSummary, effectiveLaunchMode, runtime);
|
|
1029
1098
|
return false;
|
|
1030
1099
|
}
|
|
1031
|
-
if (config.command === "auto-common") {
|
|
1100
|
+
if (config.command === "auto-common" || config.command === "auto-common-guided") {
|
|
1032
1101
|
requireJiraConfig(config);
|
|
1033
1102
|
await checkAutoPrerequisites(config, launchProfile, executionRouting);
|
|
1034
1103
|
process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
|
|
1035
1104
|
process.env.JIRA_API_URL = config.jiraApiUrl;
|
|
1036
1105
|
process.env.JIRA_TASK_FILE = config.jiraTaskFile;
|
|
1037
|
-
await runDeclarativeFlowBySpecFile("auto-common.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1106
|
+
await runDeclarativeFlowBySpecFile(config.command === "auto-common-guided" ? "auto-common-guided.json" : "auto-common.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1038
1107
|
return false;
|
|
1039
1108
|
}
|
|
1040
1109
|
if (config.command === "auto-simple") {
|
|
@@ -1135,6 +1204,14 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
1135
1204
|
}, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1136
1205
|
return false;
|
|
1137
1206
|
}
|
|
1207
|
+
if (config.command === "playbook-init") {
|
|
1208
|
+
await runDeclarativeFlowBySpecFile("playbook-init.json", config, {
|
|
1209
|
+
taskKey: config.taskKey,
|
|
1210
|
+
extraPrompt: config.extraPrompt,
|
|
1211
|
+
acceptPlaybookDraft: config.acceptPlaybookDraft === true,
|
|
1212
|
+
}, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1138
1215
|
if (config.command === "bug-analyze") {
|
|
1139
1216
|
requireJiraConfig(config);
|
|
1140
1217
|
if (config.verbose) {
|
|
@@ -1396,22 +1473,23 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
1396
1473
|
}
|
|
1397
1474
|
async function parseCliArgs(argv) {
|
|
1398
1475
|
if (argv.includes("--version") || argv.includes("-v")) {
|
|
1399
|
-
|
|
1476
|
+
writeStdoutSync(`${packageVersion()}\n`);
|
|
1400
1477
|
process.exit(0);
|
|
1401
1478
|
}
|
|
1402
1479
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
1403
|
-
|
|
1480
|
+
writeStdoutSync(`${usage()}\n`);
|
|
1404
1481
|
process.exit(0);
|
|
1405
1482
|
}
|
|
1406
1483
|
if (argv.length === 0) {
|
|
1407
|
-
|
|
1484
|
+
writeStderrSync(`${usage()}\n`);
|
|
1408
1485
|
process.exit(1);
|
|
1409
1486
|
}
|
|
1410
|
-
const
|
|
1411
|
-
if (!COMMANDS.includes(
|
|
1412
|
-
|
|
1487
|
+
const rawCommand = argv[0];
|
|
1488
|
+
if (!COMMANDS.includes(rawCommand)) {
|
|
1489
|
+
writeStderrSync(`${usage()}\n`);
|
|
1413
1490
|
process.exit(1);
|
|
1414
1491
|
}
|
|
1492
|
+
const command = rawCommand === "auto" ? "auto-common" : rawCommand;
|
|
1415
1493
|
let dry = false;
|
|
1416
1494
|
let verbose = false;
|
|
1417
1495
|
let prompt;
|
|
@@ -1422,6 +1500,9 @@ async function parseCliArgs(argv) {
|
|
|
1422
1500
|
let jiraRef;
|
|
1423
1501
|
let mdLang;
|
|
1424
1502
|
let launchMode;
|
|
1503
|
+
let acceptPlaybookDraft = false;
|
|
1504
|
+
let webNoOpen = process.env.AGENTWEAVER_WEB_NO_OPEN === "1";
|
|
1505
|
+
let webHost;
|
|
1425
1506
|
const doctorArgs = [];
|
|
1426
1507
|
for (let index = 1; index < argv.length; index += 1) {
|
|
1427
1508
|
const token = argv[index] ?? "";
|
|
@@ -1437,9 +1518,56 @@ async function parseCliArgs(argv) {
|
|
|
1437
1518
|
helpPhases = true;
|
|
1438
1519
|
continue;
|
|
1439
1520
|
}
|
|
1521
|
+
if (token === "--accept-playbook-draft") {
|
|
1522
|
+
acceptPlaybookDraft = true;
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
if (token === "--no-open") {
|
|
1526
|
+
if (command !== "web") {
|
|
1527
|
+
writeStderrSync("Error: --no-open is only supported after the web command.\n");
|
|
1528
|
+
process.exit(1);
|
|
1529
|
+
}
|
|
1530
|
+
webNoOpen = true;
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
if (token === "--listen-all") {
|
|
1534
|
+
if (command !== "web") {
|
|
1535
|
+
writeStderrSync("Error: --listen-all is only supported after the web command.\n");
|
|
1536
|
+
process.exit(1);
|
|
1537
|
+
}
|
|
1538
|
+
webHost = "0.0.0.0";
|
|
1539
|
+
continue;
|
|
1540
|
+
}
|
|
1541
|
+
if (token === "--host") {
|
|
1542
|
+
if (command !== "web") {
|
|
1543
|
+
writeStderrSync("Error: --host is only supported after the web command.\n");
|
|
1544
|
+
process.exit(1);
|
|
1545
|
+
}
|
|
1546
|
+
const hostValue = argv[index + 1]?.trim();
|
|
1547
|
+
if (!hostValue || hostValue.startsWith("-")) {
|
|
1548
|
+
writeStderrSync("Error: --host requires a host value.\n");
|
|
1549
|
+
process.exit(1);
|
|
1550
|
+
}
|
|
1551
|
+
webHost = hostValue;
|
|
1552
|
+
index += 1;
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
if (token.startsWith("--host=")) {
|
|
1556
|
+
if (command !== "web") {
|
|
1557
|
+
writeStderrSync("Error: --host is only supported after the web command.\n");
|
|
1558
|
+
process.exit(1);
|
|
1559
|
+
}
|
|
1560
|
+
const hostValue = token.slice("--host=".length).trim();
|
|
1561
|
+
if (!hostValue) {
|
|
1562
|
+
writeStderrSync("Error: --host requires a host value.\n");
|
|
1563
|
+
process.exit(1);
|
|
1564
|
+
}
|
|
1565
|
+
webHost = hostValue;
|
|
1566
|
+
continue;
|
|
1567
|
+
}
|
|
1440
1568
|
if (token === "--resume" || token === "--continue" || token === "--restart") {
|
|
1441
1569
|
if (launchMode) {
|
|
1442
|
-
|
|
1570
|
+
writeStderrSync("Error: --resume, --continue, and --restart are mutually exclusive.\n");
|
|
1443
1571
|
process.exit(1);
|
|
1444
1572
|
}
|
|
1445
1573
|
launchMode = token.slice(2);
|
|
@@ -1475,7 +1603,7 @@ async function parseCliArgs(argv) {
|
|
|
1475
1603
|
mdLang = langValue;
|
|
1476
1604
|
}
|
|
1477
1605
|
else {
|
|
1478
|
-
|
|
1606
|
+
writeStderrSync("Error: --md-lang accepts only 'en' or 'ru' as values.\n");
|
|
1479
1607
|
process.exit(1);
|
|
1480
1608
|
}
|
|
1481
1609
|
index += 1;
|
|
@@ -1487,7 +1615,7 @@ async function parseCliArgs(argv) {
|
|
|
1487
1615
|
mdLang = langValue;
|
|
1488
1616
|
}
|
|
1489
1617
|
else {
|
|
1490
|
-
|
|
1618
|
+
writeStderrSync("Error: --md-lang accepts only 'en' or 'ru' as values.\n");
|
|
1491
1619
|
process.exit(1);
|
|
1492
1620
|
}
|
|
1493
1621
|
continue;
|
|
@@ -1503,8 +1631,8 @@ async function parseCliArgs(argv) {
|
|
|
1503
1631
|
await printAutoPhasesHelp();
|
|
1504
1632
|
process.exit(0);
|
|
1505
1633
|
}
|
|
1506
|
-
if (command === "auto-common" && helpPhases) {
|
|
1507
|
-
await printAutoCommonPhasesHelp();
|
|
1634
|
+
if ((command === "auto-common" || command === "auto-common-guided") && helpPhases) {
|
|
1635
|
+
await printAutoCommonPhasesHelp(command, command === "auto-common-guided" ? "auto-common-guided.json" : "auto-common.json");
|
|
1508
1636
|
process.exit(0);
|
|
1509
1637
|
}
|
|
1510
1638
|
if (command === "auto-simple" && helpPhases) {
|
|
@@ -1524,6 +1652,9 @@ async function parseCliArgs(argv) {
|
|
|
1524
1652
|
...(mdLang !== undefined ? { mdLang } : {}),
|
|
1525
1653
|
...(doctorArgs.length > 0 ? { doctorArgs } : {}),
|
|
1526
1654
|
...(launchMode !== undefined ? { launchMode } : {}),
|
|
1655
|
+
...(acceptPlaybookDraft ? { acceptPlaybookDraft } : {}),
|
|
1656
|
+
...(command === "web" ? { webNoOpen } : {}),
|
|
1657
|
+
...(command === "web" && webHost !== undefined ? { webHost } : {}),
|
|
1527
1658
|
};
|
|
1528
1659
|
}
|
|
1529
1660
|
function buildConfigFromArgs(args) {
|
|
@@ -1537,24 +1668,66 @@ function buildConfigFromArgs(args) {
|
|
|
1537
1668
|
dryRun: args.dry,
|
|
1538
1669
|
verbose: args.verbose,
|
|
1539
1670
|
...(args.doctorArgs !== undefined ? { doctorArgs: args.doctorArgs } : {}),
|
|
1671
|
+
...(args.acceptPlaybookDraft !== undefined ? { acceptPlaybookDraft: args.acceptPlaybookDraft } : {}),
|
|
1540
1672
|
});
|
|
1541
1673
|
}
|
|
1542
|
-
async function
|
|
1674
|
+
async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRefresh = false, scopeName, installSignalCleanup = false) {
|
|
1543
1675
|
let currentScope = resolveProjectScope(scopeName, jiraRef);
|
|
1544
|
-
const gitBranchName = detectGitBranchName();
|
|
1545
1676
|
const flowCatalog = await loadInteractiveFlowCatalog(process.cwd());
|
|
1546
1677
|
let activeAbortController = null;
|
|
1547
1678
|
let activeFlowId = null;
|
|
1679
|
+
let pendingScopeSwitch = null;
|
|
1680
|
+
const autoScopeSwitchEnabled = !scopeName?.trim() && !jiraRef?.trim();
|
|
1681
|
+
let lastObservedGitScope = currentScope;
|
|
1682
|
+
let ui;
|
|
1548
1683
|
let exiting = false;
|
|
1549
|
-
const
|
|
1684
|
+
const applyScopeSwitch = (nextScope, reason) => {
|
|
1685
|
+
const previousScope = currentScope;
|
|
1686
|
+
currentScope = nextScope;
|
|
1687
|
+
ui.setScope(currentScope.scopeKey, currentScope.jiraIssueKey ?? null, currentScope.gitBranchName);
|
|
1688
|
+
syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
|
|
1689
|
+
ui.appendLog(`[scope] ${reason}: ${previousScope.scopeKey} -> ${currentScope.scopeKey}`);
|
|
1690
|
+
};
|
|
1691
|
+
const handleObservedScope = (observedScope, reason) => {
|
|
1692
|
+
if (observedScope.scopeKey === currentScope.scopeKey
|
|
1693
|
+
&& observedScope.gitBranchName === currentScope.gitBranchName) {
|
|
1694
|
+
pendingScopeSwitch = null;
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
if (activeAbortController) {
|
|
1698
|
+
if (!pendingScopeSwitch
|
|
1699
|
+
|| pendingScopeSwitch.scopeKey !== observedScope.scopeKey
|
|
1700
|
+
|| pendingScopeSwitch.gitBranchName !== observedScope.gitBranchName) {
|
|
1701
|
+
pendingScopeSwitch = observedScope;
|
|
1702
|
+
ui.appendLog(`[scope] ${reason}: switch to ${observedScope.scopeKey} pending until current flow finishes`);
|
|
1703
|
+
}
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
pendingScopeSwitch = null;
|
|
1707
|
+
applyScopeSwitch(observedScope, reason);
|
|
1708
|
+
};
|
|
1709
|
+
const refreshScopeFromGit = (reason) => {
|
|
1710
|
+
if (!autoScopeSwitchEnabled) {
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
const observedScope = resolveProjectScope(null, null);
|
|
1714
|
+
if (observedScope.scopeKey === lastObservedGitScope.scopeKey
|
|
1715
|
+
&& observedScope.gitBranchName === lastObservedGitScope.gitBranchName) {
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
lastObservedGitScope = observedScope;
|
|
1719
|
+
handleObservedScope(observedScope, reason);
|
|
1720
|
+
};
|
|
1721
|
+
ui = createSession({
|
|
1550
1722
|
scopeKey: currentScope.scopeKey,
|
|
1551
1723
|
jiraIssueKey: currentScope.jiraIssueKey ?? null,
|
|
1552
1724
|
summaryText: "",
|
|
1553
1725
|
cwd: process.cwd(),
|
|
1554
|
-
gitBranchName,
|
|
1726
|
+
gitBranchName: currentScope.gitBranchName,
|
|
1555
1727
|
version: packageVersion(),
|
|
1556
1728
|
flows: interactiveFlowDefinitions(flowCatalog),
|
|
1557
1729
|
getRunConfirmation: async (flowId) => {
|
|
1730
|
+
refreshScopeFromGit("git scope refresh before launch confirmation");
|
|
1558
1731
|
const flowEntry = findCatalogEntry(flowId, flowCatalog);
|
|
1559
1732
|
if (!flowEntry) {
|
|
1560
1733
|
throw new TaskRunnerError(`Unknown flow: ${flowId}`);
|
|
@@ -1563,6 +1736,7 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1563
1736
|
return resumeLookup;
|
|
1564
1737
|
},
|
|
1565
1738
|
onRun: async (flowId, launchMode) => {
|
|
1739
|
+
refreshScopeFromGit("git scope refresh before flow launch");
|
|
1566
1740
|
const abortController = new AbortController();
|
|
1567
1741
|
activeAbortController = abortController;
|
|
1568
1742
|
activeFlowId = flowId;
|
|
@@ -1598,7 +1772,7 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1598
1772
|
const jiraContext = await requestJiraContext((form) => ui.requestUserInput(form));
|
|
1599
1773
|
currentScope = resolveProjectScope(null, jiraContext.jiraRef);
|
|
1600
1774
|
}
|
|
1601
|
-
ui.setScope(currentScope.scopeKey, currentScope.jiraIssueKey ?? null);
|
|
1775
|
+
ui.setScope(currentScope.scopeKey, currentScope.jiraIssueKey ?? null, currentScope.gitBranchName);
|
|
1602
1776
|
if (previousScopeKey !== currentScope.scopeKey || currentScope.jiraIssueKey) {
|
|
1603
1777
|
syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
|
|
1604
1778
|
}
|
|
@@ -1640,6 +1814,11 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1640
1814
|
if (activeAbortController === abortController) {
|
|
1641
1815
|
activeAbortController = null;
|
|
1642
1816
|
activeFlowId = null;
|
|
1817
|
+
if (pendingScopeSwitch && !exiting) {
|
|
1818
|
+
const nextScope = pendingScopeSwitch;
|
|
1819
|
+
pendingScopeSwitch = null;
|
|
1820
|
+
applyScopeSwitch(nextScope, "git scope refresh after flow completion");
|
|
1821
|
+
}
|
|
1643
1822
|
}
|
|
1644
1823
|
}
|
|
1645
1824
|
},
|
|
@@ -1651,6 +1830,10 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1651
1830
|
activeAbortController.abort();
|
|
1652
1831
|
},
|
|
1653
1832
|
onExit: () => {
|
|
1833
|
+
if (activeAbortController) {
|
|
1834
|
+
ui.interruptActiveForm();
|
|
1835
|
+
activeAbortController.abort();
|
|
1836
|
+
}
|
|
1654
1837
|
exiting = true;
|
|
1655
1838
|
},
|
|
1656
1839
|
});
|
|
@@ -1661,13 +1844,45 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1661
1844
|
ui.appendLog("[scope] project scope active; task summary will appear after a Jira-backed flow runs");
|
|
1662
1845
|
}
|
|
1663
1846
|
syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
|
|
1847
|
+
const scopeWatchInterval = autoScopeSwitchEnabled
|
|
1848
|
+
? setInterval(() => {
|
|
1849
|
+
if (!exiting) {
|
|
1850
|
+
refreshScopeFromGit("git branch changed");
|
|
1851
|
+
}
|
|
1852
|
+
}, INTERACTIVE_SCOPE_WATCH_INTERVAL_MS)
|
|
1853
|
+
: null;
|
|
1664
1854
|
return await new Promise((resolve, reject) => {
|
|
1855
|
+
let cleanupStarted = false;
|
|
1856
|
+
const requestExit = () => {
|
|
1857
|
+
if (activeAbortController) {
|
|
1858
|
+
ui.interruptActiveForm();
|
|
1859
|
+
activeAbortController.abort();
|
|
1860
|
+
}
|
|
1861
|
+
exiting = true;
|
|
1862
|
+
};
|
|
1863
|
+
const onSigint = () => requestExit();
|
|
1864
|
+
const onSigterm = () => requestExit();
|
|
1865
|
+
if (installSignalCleanup) {
|
|
1866
|
+
process.once("SIGINT", onSigint);
|
|
1867
|
+
process.once("SIGTERM", onSigterm);
|
|
1868
|
+
}
|
|
1665
1869
|
const interval = setInterval(() => {
|
|
1666
1870
|
if (!exiting) {
|
|
1667
1871
|
return;
|
|
1668
1872
|
}
|
|
1669
1873
|
clearInterval(interval);
|
|
1670
1874
|
try {
|
|
1875
|
+
if (cleanupStarted) {
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
cleanupStarted = true;
|
|
1879
|
+
if (scopeWatchInterval) {
|
|
1880
|
+
clearInterval(scopeWatchInterval);
|
|
1881
|
+
}
|
|
1882
|
+
if (installSignalCleanup) {
|
|
1883
|
+
process.off("SIGINT", onSigint);
|
|
1884
|
+
process.off("SIGTERM", onSigterm);
|
|
1885
|
+
}
|
|
1671
1886
|
ui.destroy();
|
|
1672
1887
|
bye();
|
|
1673
1888
|
resolve(0);
|
|
@@ -1678,6 +1893,12 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1678
1893
|
}, 100);
|
|
1679
1894
|
});
|
|
1680
1895
|
}
|
|
1896
|
+
async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
1897
|
+
return await runInteractiveWithSessionFactory(createInteractiveSession, jiraRef, forceRefresh, scopeName);
|
|
1898
|
+
}
|
|
1899
|
+
async function runWebInteractive(jiraRef, forceRefresh = false, noOpen = false, host, auth) {
|
|
1900
|
+
return await runInteractiveWithSessionFactory((options) => createWebInteractiveSession(options, { noOpen, ...(host ? { host } : {}), ...(auth ? { auth } : {}), printInfo }), jiraRef, forceRefresh, null, true);
|
|
1901
|
+
}
|
|
1681
1902
|
export async function main(argv = process.argv.slice(2)) {
|
|
1682
1903
|
loadTieredEnv(process.cwd());
|
|
1683
1904
|
let forceRefresh = false;
|
|
@@ -1687,6 +1908,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1687
1908
|
args.shift();
|
|
1688
1909
|
}
|
|
1689
1910
|
try {
|
|
1911
|
+
if (args[0] === "--no-open") {
|
|
1912
|
+
throw new TaskRunnerError("--no-open is only supported after the web command.");
|
|
1913
|
+
}
|
|
1690
1914
|
if (args.length === 0) {
|
|
1691
1915
|
return await runInteractive(undefined, forceRefresh);
|
|
1692
1916
|
}
|
|
@@ -1694,6 +1918,11 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1694
1918
|
return await runInteractive(args[0] ?? "", forceRefresh);
|
|
1695
1919
|
}
|
|
1696
1920
|
const parsedArgs = await parseCliArgs(args);
|
|
1921
|
+
if (parsedArgs.command === "web") {
|
|
1922
|
+
const webAuth = resolveWebAuthConfig();
|
|
1923
|
+
requireWebAuthForHost(parsedArgs.webHost, webAuth);
|
|
1924
|
+
return await runWebInteractive(parsedArgs.jiraRef, forceRefresh, parsedArgs.webNoOpen === true, parsedArgs.webHost, webAuth);
|
|
1925
|
+
}
|
|
1697
1926
|
const commandCompleted = await executeCommand(buildConfigFromArgs(parsedArgs), true, requestUserInputInTerminal, undefined, undefined, false, parsedArgs.launchMode);
|
|
1698
1927
|
if (parsedArgs.command === "doctor") {
|
|
1699
1928
|
return commandCompleted ? 0 : 1;
|
|
@@ -1702,12 +1931,12 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1702
1931
|
}
|
|
1703
1932
|
catch (error) {
|
|
1704
1933
|
if (error instanceof TaskRunnerError) {
|
|
1705
|
-
|
|
1934
|
+
writeStderrSync(`Error: ${error.message}\n`);
|
|
1706
1935
|
return 1;
|
|
1707
1936
|
}
|
|
1708
1937
|
const returnCode = Number(error.returnCode);
|
|
1709
1938
|
if (!Number.isNaN(returnCode)) {
|
|
1710
|
-
|
|
1939
|
+
writeStderrSync(`Error: ${formatProcessFailure(error)}\n`);
|
|
1711
1940
|
return returnCode || 1;
|
|
1712
1941
|
}
|
|
1713
1942
|
throw error;
|