agentweaver 0.1.17 → 0.1.18
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 +104 -23
- package/dist/artifacts.js +41 -0
- package/dist/index.js +252 -27
- package/dist/interactive/controller.js +249 -13
- package/dist/interactive/ink/index.js +2 -2
- package/dist/interactive/state.js +1 -0
- package/dist/interactive/web/index.js +179 -0
- package/dist/interactive/web/protocol.js +154 -0
- package/dist/interactive/web/server.js +575 -0
- package/dist/interactive/web/static/app.js +709 -0
- package/dist/interactive/web/static/index.html +77 -0
- package/dist/interactive/web/static/styles.css +2 -0
- package/dist/interactive/web/static/styles.input.css +469 -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/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
|
@@ -21,8 +21,13 @@ import { localScriptCheckNode } from "./nodes/local-script-check-node.js";
|
|
|
21
21
|
import { llmPromptNode } from "./nodes/llm-prompt-node.js";
|
|
22
22
|
import { opencodePromptNode } from "./nodes/opencode-prompt-node.js";
|
|
23
23
|
import { planCodexNode } from "./nodes/plan-codex-node.js";
|
|
24
|
+
import { playbookInventoryNode } from "./nodes/playbook-inventory-node.js";
|
|
25
|
+
import { playbookEnsureNode } from "./nodes/playbook-ensure-node.js";
|
|
26
|
+
import { playbookQuestionsFormNode } from "./nodes/playbook-questions-form-node.js";
|
|
27
|
+
import { playbookWriteNode } from "./nodes/playbook-write-node.js";
|
|
24
28
|
import { planningBundleNode } from "./nodes/planning-bundle-node.js";
|
|
25
29
|
import { planningQuestionsFormNode } from "./nodes/planning-questions-form-node.js";
|
|
30
|
+
import { projectGuidanceNode } from "./nodes/project-guidance-node.js";
|
|
26
31
|
import { readFileNode } from "./nodes/read-file-node.js";
|
|
27
32
|
import { reviewFindingsFormNode } from "./nodes/review-findings-form-node.js";
|
|
28
33
|
import { reviewVerdictNode } from "./nodes/review-verdict-node.js";
|
|
@@ -57,8 +62,13 @@ export const BUILT_IN_NODE_KINDS = [
|
|
|
57
62
|
"llm-prompt",
|
|
58
63
|
"opencode-prompt",
|
|
59
64
|
"plan-codex",
|
|
65
|
+
"playbook-inventory",
|
|
66
|
+
"playbook-ensure",
|
|
67
|
+
"playbook-questions-form",
|
|
68
|
+
"playbook-write",
|
|
60
69
|
"planning-bundle",
|
|
61
70
|
"planning-questions-form",
|
|
71
|
+
"project-guidance",
|
|
62
72
|
"read-file",
|
|
63
73
|
"review-findings-form",
|
|
64
74
|
"review-verdict",
|
|
@@ -93,8 +103,13 @@ const builtInNodes = {
|
|
|
93
103
|
"llm-prompt": llmPromptNode,
|
|
94
104
|
"opencode-prompt": opencodePromptNode,
|
|
95
105
|
"plan-codex": planCodexNode,
|
|
106
|
+
"playbook-inventory": playbookInventoryNode,
|
|
107
|
+
"playbook-ensure": playbookEnsureNode,
|
|
108
|
+
"playbook-questions-form": playbookQuestionsFormNode,
|
|
109
|
+
"playbook-write": playbookWriteNode,
|
|
96
110
|
"planning-bundle": planningBundleNode,
|
|
97
111
|
"planning-questions-form": planningQuestionsFormNode,
|
|
112
|
+
"project-guidance": projectGuidanceNode,
|
|
98
113
|
"read-file": readFileNode,
|
|
99
114
|
"review-findings-form": reviewFindingsFormNode,
|
|
100
115
|
"review-verdict": reviewVerdictNode,
|
|
@@ -228,6 +243,30 @@ const builtInNodeMetadata = {
|
|
|
228
243
|
requiredParams: ["prompt", "requiredArtifacts"],
|
|
229
244
|
executors: ["codex"],
|
|
230
245
|
},
|
|
246
|
+
"playbook-inventory": {
|
|
247
|
+
kind: "playbook-inventory",
|
|
248
|
+
version: 1,
|
|
249
|
+
prompt: "forbidden",
|
|
250
|
+
requiredParams: ["outputJsonFile", "outputFile"],
|
|
251
|
+
},
|
|
252
|
+
"playbook-ensure": {
|
|
253
|
+
kind: "playbook-ensure",
|
|
254
|
+
version: 1,
|
|
255
|
+
prompt: "forbidden",
|
|
256
|
+
requiredParams: ["writeResultJsonFile"],
|
|
257
|
+
},
|
|
258
|
+
"playbook-questions-form": {
|
|
259
|
+
kind: "playbook-questions-form",
|
|
260
|
+
version: 1,
|
|
261
|
+
prompt: "forbidden",
|
|
262
|
+
requiredParams: ["questionsJsonFile", "answersJsonFile", "formId", "title"],
|
|
263
|
+
},
|
|
264
|
+
"playbook-write": {
|
|
265
|
+
kind: "playbook-write",
|
|
266
|
+
version: 1,
|
|
267
|
+
prompt: "forbidden",
|
|
268
|
+
requiredParams: ["draftJsonFile", "answersJsonFile", "writeResultJsonFile"],
|
|
269
|
+
},
|
|
231
270
|
"planning-bundle": {
|
|
232
271
|
kind: "planning-bundle",
|
|
233
272
|
version: 1,
|
|
@@ -240,6 +279,12 @@ const builtInNodeMetadata = {
|
|
|
240
279
|
prompt: "forbidden",
|
|
241
280
|
requiredParams: ["planningQuestionsJsonFile", "formId", "title"],
|
|
242
281
|
},
|
|
282
|
+
"project-guidance": {
|
|
283
|
+
kind: "project-guidance",
|
|
284
|
+
version: 1,
|
|
285
|
+
prompt: "forbidden",
|
|
286
|
+
requiredParams: ["taskContextJsonFile", "phase", "outputJsonFile", "outputFile"],
|
|
287
|
+
},
|
|
243
288
|
"read-file": { kind: "read-file", version: 1, prompt: "forbidden", requiredParams: ["path"] },
|
|
244
289
|
"review-findings-form": {
|
|
245
290
|
kind: "review-findings-form",
|
|
@@ -64,6 +64,8 @@ export function resolveNestedFlowParams(flowKind, flowParams) {
|
|
|
64
64
|
hasTaskInputJsonFile: contract.hasTaskInputJsonFile,
|
|
65
65
|
taskInputJsonFilePath: contract.taskInputJsonFilePath,
|
|
66
66
|
taskInputJsonFile: contract.taskInputJsonFile,
|
|
67
|
+
projectGuidanceFile: flowParams["projectGuidanceFile"] ?? "not provided",
|
|
68
|
+
projectGuidanceJsonFile: flowParams["projectGuidanceJsonFile"] ?? "not provided",
|
|
67
69
|
}, {
|
|
68
70
|
"params.designFile": contract.designFile,
|
|
69
71
|
"params.designJsonFile": contract.designJsonFile,
|
|
@@ -135,6 +137,8 @@ export function resolveNestedFlowParams(flowKind, flowParams) {
|
|
|
135
137
|
hasTaskInputJsonFile: contract.hasTaskInputJsonFile,
|
|
136
138
|
taskInputJsonFilePath: contract.taskInputJsonFilePath,
|
|
137
139
|
taskInputJsonFile: contract.taskInputJsonFile,
|
|
140
|
+
projectGuidanceFile: flowParams["projectGuidanceFile"] ?? "not provided",
|
|
141
|
+
projectGuidanceJsonFile: flowParams["projectGuidanceJsonFile"] ?? "not provided",
|
|
138
142
|
}, {
|
|
139
143
|
"params.reviewFile": contract.reviewFile,
|
|
140
144
|
"params.reviewJsonFile": contract.reviewJsonFile,
|
|
@@ -188,6 +192,8 @@ export function resolveNestedFlowParams(flowKind, flowParams) {
|
|
|
188
192
|
hasTaskInputJsonFile: contract.hasTaskInputJsonFile,
|
|
189
193
|
taskInputJsonFilePath: contract.taskInputJsonFilePath,
|
|
190
194
|
taskInputJsonFile: contract.taskInputJsonFile,
|
|
195
|
+
projectGuidanceFile: flowParams["projectGuidanceFile"] ?? "not provided",
|
|
196
|
+
projectGuidanceJsonFile: flowParams["projectGuidanceJsonFile"] ?? "not provided",
|
|
191
197
|
}, {
|
|
192
198
|
"params.designFile": contract.designFile,
|
|
193
199
|
"params.designJsonFile": contract.designJsonFile,
|
|
@@ -218,7 +224,13 @@ export const flowRunNode = {
|
|
|
218
224
|
const flow = await loadNamedDeclarativeFlow(fileName, context.cwd, {
|
|
219
225
|
...(context.registryContext ? { registryContext: context.registryContext } : {}),
|
|
220
226
|
});
|
|
221
|
-
const resolvedFlowParams = resolveNestedFlowParams(flow.kind,
|
|
227
|
+
const resolvedFlowParams = resolveNestedFlowParams(flow.kind, {
|
|
228
|
+
projectGuidanceFile: "not provided",
|
|
229
|
+
projectGuidanceJsonFile: "not provided",
|
|
230
|
+
repairProjectGuidanceFile: "not provided",
|
|
231
|
+
repairProjectGuidanceJsonFile: "not provided",
|
|
232
|
+
...flowParams,
|
|
233
|
+
});
|
|
222
234
|
const resumeValue = isFlowRunResumeEnvelope(context.resumeStepValue)
|
|
223
235
|
&& context.resumeStepValue.flowKind === flow.kind
|
|
224
236
|
&& context.resumeStepValue.flowVersion === flow.version
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
|
|
4
|
+
import { loadProjectPlaybook, PLAYBOOK_DIR, PLAYBOOK_MANIFEST } from "../../runtime/playbook.js";
|
|
5
|
+
import { validateStructuredArtifactValue } from "../../structured-artifacts.js";
|
|
6
|
+
function readWriteStatus(filePath) {
|
|
7
|
+
if (!existsSync(filePath)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
12
|
+
return typeof parsed.status === "string" ? parsed.status : null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function writeResult(scopeKey, filePath, result) {
|
|
19
|
+
const artifact = {
|
|
20
|
+
status: result.status,
|
|
21
|
+
message: result.message,
|
|
22
|
+
written_files: result.written_files,
|
|
23
|
+
skipped_files: result.skipped_files,
|
|
24
|
+
existing_playbook_path: result.existing_playbook_path,
|
|
25
|
+
intended_files: result.intended_files,
|
|
26
|
+
blocked_paths: result.blocked_paths,
|
|
27
|
+
};
|
|
28
|
+
validateStructuredArtifactValue(artifact, "playbook-write-result/v1", filePath);
|
|
29
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
30
|
+
writeFileSync(filePath, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
kind: "artifact",
|
|
34
|
+
path: filePath,
|
|
35
|
+
required: true,
|
|
36
|
+
manifest: {
|
|
37
|
+
publish: true,
|
|
38
|
+
logicalKey: buildLogicalKeyForPayload(scopeKey, filePath),
|
|
39
|
+
payloadFamily: "structured-json",
|
|
40
|
+
schemaId: "playbook-write-result/v1",
|
|
41
|
+
schemaVersion: 1,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
export const playbookEnsureNode = {
|
|
47
|
+
kind: "playbook-ensure",
|
|
48
|
+
version: 1,
|
|
49
|
+
async run(context, params) {
|
|
50
|
+
const playbookRoot = path.join(context.cwd, PLAYBOOK_DIR);
|
|
51
|
+
const manifestPath = path.join(playbookRoot, PLAYBOOK_MANIFEST);
|
|
52
|
+
const intendedFiles = [manifestPath];
|
|
53
|
+
const priorStatus = params.verifyAfterInit ? readWriteStatus(params.writeResultJsonFile) : null;
|
|
54
|
+
if (!existsSync(manifestPath)) {
|
|
55
|
+
if (params.verifyAfterInit && priorStatus === "dry_run_written") {
|
|
56
|
+
const result = {
|
|
57
|
+
status: "dry_run_written",
|
|
58
|
+
message: "Dry-run playbook generation was accepted; manifest.yaml was not written in dry-run mode.",
|
|
59
|
+
written_files: [],
|
|
60
|
+
skipped_files: [],
|
|
61
|
+
existing_playbook_path: "",
|
|
62
|
+
intended_files: intendedFiles,
|
|
63
|
+
blocked_paths: [],
|
|
64
|
+
shouldRunPlaybookInit: false,
|
|
65
|
+
manifestPath,
|
|
66
|
+
};
|
|
67
|
+
return { value: result, outputs: writeResult(context.issueKey, params.writeResultJsonFile, result) };
|
|
68
|
+
}
|
|
69
|
+
const accepted = params.acceptPlaybookDraft === true;
|
|
70
|
+
const result = {
|
|
71
|
+
status: accepted ? "missing_playbook" : "blocked",
|
|
72
|
+
message: accepted
|
|
73
|
+
? `Playbook manifest is missing: ${manifestPath}. Running playbook-init because acceptPlaybookDraft is true.`
|
|
74
|
+
: `Playbook manifest is missing: ${manifestPath}. Run 'agentweaver playbook-init --accept-playbook-draft' first, or rerun 'agentweaver auto-common-guided --accept-playbook-draft <jira>' to explicitly accept generated playbook content before planning.`,
|
|
75
|
+
written_files: [],
|
|
76
|
+
skipped_files: [],
|
|
77
|
+
existing_playbook_path: "",
|
|
78
|
+
intended_files: intendedFiles,
|
|
79
|
+
blocked_paths: accepted ? [] : [manifestPath],
|
|
80
|
+
shouldRunPlaybookInit: accepted,
|
|
81
|
+
manifestPath,
|
|
82
|
+
};
|
|
83
|
+
return { value: result, outputs: writeResult(context.issueKey, params.writeResultJsonFile, result) };
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
loadProjectPlaybook(context.cwd);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const result = {
|
|
90
|
+
status: "invalid_manifest",
|
|
91
|
+
message: `Invalid project playbook ${manifestPath}: ${error.message}`,
|
|
92
|
+
written_files: [],
|
|
93
|
+
skipped_files: [],
|
|
94
|
+
existing_playbook_path: "",
|
|
95
|
+
intended_files: intendedFiles,
|
|
96
|
+
blocked_paths: [manifestPath],
|
|
97
|
+
shouldRunPlaybookInit: false,
|
|
98
|
+
manifestPath,
|
|
99
|
+
};
|
|
100
|
+
return { value: result, outputs: writeResult(context.issueKey, params.writeResultJsonFile, result) };
|
|
101
|
+
}
|
|
102
|
+
const result = {
|
|
103
|
+
status: "skipped_valid_existing",
|
|
104
|
+
message: `Valid project playbook manifest exists: ${manifestPath}.`,
|
|
105
|
+
written_files: [],
|
|
106
|
+
skipped_files: [manifestPath],
|
|
107
|
+
existing_playbook_path: manifestPath,
|
|
108
|
+
intended_files: intendedFiles,
|
|
109
|
+
blocked_paths: [],
|
|
110
|
+
shouldRunPlaybookInit: false,
|
|
111
|
+
manifestPath,
|
|
112
|
+
};
|
|
113
|
+
return { value: result, outputs: writeResult(context.issueKey, params.writeResultJsonFile, result) };
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
|
|
4
|
+
import { validateStructuredArtifactValue } from "../../structured-artifacts.js";
|
|
5
|
+
import { collectRepoInventory, renderRepoInventoryMarkdown } from "../../playbook/repo-inventory.js";
|
|
6
|
+
export const playbookInventoryNode = {
|
|
7
|
+
kind: "playbook-inventory",
|
|
8
|
+
version: 1,
|
|
9
|
+
async run(context, params) {
|
|
10
|
+
const inventory = collectRepoInventory(context.cwd);
|
|
11
|
+
validateStructuredArtifactValue(inventory, "repo-inventory/v1", params.outputJsonFile);
|
|
12
|
+
mkdirSync(path.dirname(params.outputJsonFile), { recursive: true });
|
|
13
|
+
mkdirSync(path.dirname(params.outputFile), { recursive: true });
|
|
14
|
+
writeFileSync(params.outputJsonFile, `${JSON.stringify(inventory, null, 2)}\n`, "utf8");
|
|
15
|
+
writeFileSync(params.outputFile, renderRepoInventoryMarkdown(inventory), "utf8");
|
|
16
|
+
return {
|
|
17
|
+
value: {
|
|
18
|
+
summary: inventory.summary,
|
|
19
|
+
outputJsonFile: params.outputJsonFile,
|
|
20
|
+
outputFile: params.outputFile,
|
|
21
|
+
evidenceCount: inventory.evidence.length,
|
|
22
|
+
},
|
|
23
|
+
outputs: [
|
|
24
|
+
{
|
|
25
|
+
kind: "artifact",
|
|
26
|
+
path: params.outputJsonFile,
|
|
27
|
+
required: true,
|
|
28
|
+
manifest: {
|
|
29
|
+
publish: true,
|
|
30
|
+
logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputJsonFile),
|
|
31
|
+
payloadFamily: "structured-json",
|
|
32
|
+
schemaId: "repo-inventory/v1",
|
|
33
|
+
schemaVersion: 1,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
kind: "artifact",
|
|
38
|
+
path: params.outputFile,
|
|
39
|
+
required: true,
|
|
40
|
+
manifest: {
|
|
41
|
+
publish: true,
|
|
42
|
+
logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputFile),
|
|
43
|
+
payloadFamily: "markdown",
|
|
44
|
+
schemaId: "markdown/v1",
|
|
45
|
+
schemaVersion: 1,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
|
|
4
|
+
import { TaskRunnerError } from "../../errors.js";
|
|
5
|
+
import { validateStructuredArtifactValue } from "../../structured-artifacts.js";
|
|
6
|
+
import { requestUserInputInTerminal } from "../../user-input.js";
|
|
7
|
+
function nowIso8601() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
function readQuestions(filePath) {
|
|
11
|
+
if (!existsSync(filePath)) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
16
|
+
return Array.isArray(parsed.questions) ? parsed.questions : [];
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new TaskRunnerError(`Failed to read playbook questions from ${filePath}: ${error.message}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function readExistingAnswers(filePath) {
|
|
23
|
+
if (!existsSync(filePath)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new TaskRunnerError(`Failed to read playbook answers from ${filePath}: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function fieldForQuestion(question, index) {
|
|
34
|
+
const text = typeof question.text === "string" ? question.text.trim() : "";
|
|
35
|
+
if (!text) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const id = typeof question.id === "string" && question.id.trim() ? question.id.trim() : `question_${index + 1}`;
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
type: "text",
|
|
42
|
+
label: text,
|
|
43
|
+
required: false,
|
|
44
|
+
multiline: true,
|
|
45
|
+
default: "",
|
|
46
|
+
...(typeof question.rationale === "string" && question.rationale.trim() ? { help: question.rationale.trim() } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function answersFromValues(fields, values) {
|
|
50
|
+
return fields.map((field) => {
|
|
51
|
+
const value = values[field.id];
|
|
52
|
+
return {
|
|
53
|
+
question_id: field.id,
|
|
54
|
+
answer: typeof value === "string" ? value : value === undefined ? "" : JSON.stringify(value),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function writeAnswers(filePath, artifact) {
|
|
59
|
+
validateStructuredArtifactValue(artifact, "playbook-answers/v1", filePath);
|
|
60
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
61
|
+
writeFileSync(filePath, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
|
|
62
|
+
}
|
|
63
|
+
export const playbookQuestionsFormNode = {
|
|
64
|
+
kind: "playbook-questions-form",
|
|
65
|
+
version: 1,
|
|
66
|
+
async run(context, params) {
|
|
67
|
+
const mode = params.mode ?? "clarifications";
|
|
68
|
+
const existing = readExistingAnswers(params.answersJsonFile);
|
|
69
|
+
if (mode === "acceptance") {
|
|
70
|
+
let finalWriteAccepted = params.acceptDraft === true;
|
|
71
|
+
const interactive = context.requestUserInput !== requestUserInputInTerminal || (process.stdin.isTTY && process.stdout.isTTY);
|
|
72
|
+
if (params.acceptDraft !== true && interactive) {
|
|
73
|
+
const result = await (context.requestUserInput ?? requestUserInputInTerminal)({
|
|
74
|
+
formId: params.formId,
|
|
75
|
+
title: params.title,
|
|
76
|
+
submitLabel: "Confirm",
|
|
77
|
+
fields: [
|
|
78
|
+
{
|
|
79
|
+
id: "final_write_accepted",
|
|
80
|
+
type: "boolean",
|
|
81
|
+
label: "Write final .agentweaver/playbook files if safety checks pass?",
|
|
82
|
+
required: true,
|
|
83
|
+
default: false,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
finalWriteAccepted = result.values.final_write_accepted === true;
|
|
88
|
+
}
|
|
89
|
+
const artifact = {
|
|
90
|
+
summary: finalWriteAccepted ? "Final playbook write was accepted." : "Final playbook write was not accepted.",
|
|
91
|
+
answered_at: nowIso8601(),
|
|
92
|
+
answers: existing?.answers ?? [],
|
|
93
|
+
final_write_accepted: finalWriteAccepted,
|
|
94
|
+
};
|
|
95
|
+
writeAnswers(params.answersJsonFile, artifact);
|
|
96
|
+
return {
|
|
97
|
+
value: {
|
|
98
|
+
formId: params.formId,
|
|
99
|
+
questionCount: existing?.answers.length ?? 0,
|
|
100
|
+
finalWriteAccepted,
|
|
101
|
+
outputFile: params.answersJsonFile,
|
|
102
|
+
},
|
|
103
|
+
outputs: [
|
|
104
|
+
{
|
|
105
|
+
kind: "artifact",
|
|
106
|
+
path: params.answersJsonFile,
|
|
107
|
+
required: true,
|
|
108
|
+
manifest: {
|
|
109
|
+
publish: true,
|
|
110
|
+
logicalKey: buildLogicalKeyForPayload(context.issueKey, params.answersJsonFile),
|
|
111
|
+
payloadFamily: "structured-json",
|
|
112
|
+
schemaId: "playbook-answers/v1",
|
|
113
|
+
schemaVersion: 1,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const fields = readQuestions(params.questionsJsonFile)
|
|
120
|
+
.map((question, index) => fieldForQuestion(question, index))
|
|
121
|
+
.filter((field) => field !== null);
|
|
122
|
+
let answers = [];
|
|
123
|
+
const interactive = fields.length === 0 || context.requestUserInput !== requestUserInputInTerminal || (process.stdin.isTTY && process.stdout.isTTY);
|
|
124
|
+
if (fields.length > 0 && !interactive) {
|
|
125
|
+
answers = fields.map((field) => ({ question_id: field.id, answer: "" }));
|
|
126
|
+
}
|
|
127
|
+
else if (fields.length > 0) {
|
|
128
|
+
const result = await (context.requestUserInput ?? requestUserInputInTerminal)({
|
|
129
|
+
formId: params.formId,
|
|
130
|
+
title: params.title,
|
|
131
|
+
submitLabel: "Continue",
|
|
132
|
+
fields,
|
|
133
|
+
});
|
|
134
|
+
answers = answersFromValues(fields, result.values);
|
|
135
|
+
}
|
|
136
|
+
const artifact = {
|
|
137
|
+
summary: fields.length === 0 ? "No clarification questions were required." : "Playbook clarification answers were recorded.",
|
|
138
|
+
answered_at: nowIso8601(),
|
|
139
|
+
answers,
|
|
140
|
+
final_write_accepted: params.acceptDraft === true,
|
|
141
|
+
};
|
|
142
|
+
writeAnswers(params.answersJsonFile, artifact);
|
|
143
|
+
return {
|
|
144
|
+
value: {
|
|
145
|
+
formId: params.formId,
|
|
146
|
+
questionCount: fields.length,
|
|
147
|
+
finalWriteAccepted: artifact.final_write_accepted,
|
|
148
|
+
outputFile: params.answersJsonFile,
|
|
149
|
+
},
|
|
150
|
+
outputs: [
|
|
151
|
+
{
|
|
152
|
+
kind: "artifact",
|
|
153
|
+
path: params.answersJsonFile,
|
|
154
|
+
required: true,
|
|
155
|
+
manifest: {
|
|
156
|
+
publish: true,
|
|
157
|
+
logicalKey: buildLogicalKeyForPayload(context.issueKey, params.answersJsonFile),
|
|
158
|
+
payloadFamily: "structured-json",
|
|
159
|
+
schemaId: "playbook-answers/v1",
|
|
160
|
+
schemaVersion: 1,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
};
|