agentweaver 0.1.15 → 0.1.16
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 +26 -9
- package/dist/artifact-manifest.js +219 -0
- package/dist/artifacts.js +15 -0
- package/dist/doctor/checks/env-diagnostics.js +25 -0
- package/dist/doctor/checks/flow-readiness.js +15 -18
- package/dist/flow-state.js +75 -15
- package/dist/index.js +391 -175
- package/dist/interactive/blessed-session.js +361 -0
- package/dist/interactive/controller.js +1293 -0
- package/dist/interactive/create-interactive-session.js +5 -0
- package/dist/interactive/ink/index.js +576 -0
- package/dist/interactive/progress.js +245 -0
- package/dist/interactive/selectors.js +14 -0
- package/dist/interactive/session.js +1 -0
- package/dist/interactive/state.js +34 -0
- package/dist/interactive/tree.js +155 -0
- package/dist/interactive/types.js +1 -0
- package/dist/interactive/view-model.js +1 -0
- package/dist/interactive-ui.js +159 -194
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/declarative-flow-runner.js +212 -6
- package/dist/pipeline/declarative-flows.js +27 -0
- package/dist/pipeline/execution-routing-config.js +15 -0
- package/dist/pipeline/flow-catalog.js +19 -3
- package/dist/pipeline/flow-run-resume.js +29 -0
- package/dist/pipeline/flow-specs/auto-common.json +89 -360
- package/dist/pipeline/flow-specs/auto-golang.json +58 -363
- package/dist/pipeline/flow-specs/auto-simple.json +141 -0
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +2 -0
- package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +304 -0
- package/dist/pipeline/flow-specs/design-review.json +10 -0
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +11 -0
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +2 -0
- package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
- package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +2 -0
- package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +2 -0
- package/dist/pipeline/flow-specs/implement.json +13 -6
- package/dist/pipeline/flow-specs/instant-task.json +177 -0
- package/dist/pipeline/flow-specs/normalize-task-source.json +311 -0
- package/dist/pipeline/flow-specs/plan-revise.json +7 -1
- package/dist/pipeline/flow-specs/plan.json +48 -70
- package/dist/pipeline/flow-specs/review/review-fix.json +24 -4
- package/dist/pipeline/flow-specs/review/review-loop.json +351 -45
- package/dist/pipeline/flow-specs/review/review-project-loop.json +590 -0
- package/dist/pipeline/flow-specs/review/review-project.json +12 -0
- package/dist/pipeline/flow-specs/review/review.json +37 -31
- package/dist/pipeline/flow-specs/task-describe.json +2 -0
- package/dist/pipeline/flow-specs/task-source/jira-fetch.json +70 -0
- package/dist/pipeline/flow-specs/task-source/manual-input.json +216 -0
- package/dist/pipeline/node-registry.js +41 -1
- package/dist/pipeline/node-runner.js +3 -2
- package/dist/pipeline/nodes/build-review-fix-prompt-node.js +5 -1
- package/dist/pipeline/nodes/clear-ready-to-merge-node.js +11 -0
- package/dist/pipeline/nodes/commit-message-form-node.js +8 -0
- package/dist/pipeline/nodes/design-review-verdict-node.js +36 -0
- package/dist/pipeline/nodes/ensure-summary-json-node.js +13 -2
- package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +19 -2
- package/dist/pipeline/nodes/fetch-gitlab-review-node.js +19 -2
- package/dist/pipeline/nodes/flow-run-node.js +226 -7
- package/dist/pipeline/nodes/git-commit-form-node.js +8 -0
- package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +19 -2
- package/dist/pipeline/nodes/jira-fetch-node.js +50 -4
- package/dist/pipeline/nodes/llm-prompt-node.js +32 -12
- package/dist/pipeline/nodes/planning-bundle-node.js +10 -0
- package/dist/pipeline/nodes/review-verdict-node.js +86 -0
- package/dist/pipeline/nodes/select-files-form-node.js +8 -0
- package/dist/pipeline/nodes/structured-summary-node.js +24 -0
- package/dist/pipeline/nodes/user-input-node.js +38 -3
- package/dist/pipeline/nodes/write-selection-file-node.js +20 -4
- package/dist/pipeline/prompt-registry.js +3 -1
- package/dist/pipeline/prompt-runtime.js +4 -1
- package/dist/pipeline/review-iteration.js +26 -0
- package/dist/pipeline/spec-compiler.js +2 -0
- package/dist/pipeline/spec-types.js +3 -0
- package/dist/pipeline/spec-validator.js +14 -0
- package/dist/pipeline/value-resolver.js +74 -1
- package/dist/prompts.js +36 -14
- package/dist/review-severity.js +45 -0
- package/dist/runtime/artifact-registry.js +402 -0
- package/dist/runtime/design-review-input-contract.js +17 -16
- package/dist/runtime/env-loader.js +3 -0
- package/dist/runtime/execution-routing-store.js +134 -0
- package/dist/runtime/execution-routing.js +227 -0
- package/dist/runtime/interactive-execution-routing.js +462 -0
- package/dist/runtime/plan-revise-input-contract.js +35 -32
- package/dist/runtime/planning-bundle.js +123 -0
- package/dist/runtime/ready-to-merge.js +22 -1
- package/dist/runtime/review-input-contract.js +100 -0
- package/dist/structured-artifact-schema-registry.js +9 -0
- package/dist/structured-artifact-schemas.json +140 -1
- package/dist/structured-artifacts.js +77 -6
- package/dist/user-input.js +70 -3
- package/package.json +6 -3
package/dist/interactive-ui.js
CHANGED
|
@@ -3,21 +3,18 @@ import blessed from "neo-blessed";
|
|
|
3
3
|
import { renderMarkdownToTerminal } from "./markdown.js";
|
|
4
4
|
import { setOutputAdapter, stripAnsi } from "./tui.js";
|
|
5
5
|
import { FlowInterruptedError, TaskRunnerError } from "./errors.js";
|
|
6
|
-
import {
|
|
6
|
+
import { buildProgressViewModel } from "./interactive/progress.js";
|
|
7
|
+
import { selectHeaderLabel } from "./interactive/selectors.js";
|
|
8
|
+
import { buildFlowTree, computeVisibleFlowItems, makeFlowKey, makeFolderKey } from "./interactive/tree.js";
|
|
9
|
+
import { buildInitialUserInputValues, normalizeUserInputFieldValue, resolveFieldDefinition, validateUserInputValues, } from "./user-input.js";
|
|
7
10
|
const CONFIRM_MIN_WIDTH = 44;
|
|
8
11
|
const CONFIRM_MIN_HEIGHT = 8;
|
|
9
|
-
function compareTreeNames(left, right) {
|
|
10
|
-
return left.localeCompare(right, "ru");
|
|
11
|
-
}
|
|
12
|
-
function makeFolderKey(pathSegments) {
|
|
13
|
-
return `folder:${pathSegments.join("/")}`;
|
|
14
|
-
}
|
|
15
|
-
function makeFlowKey(flowId) {
|
|
16
|
-
return `flow:${flowId}`;
|
|
17
|
-
}
|
|
18
12
|
function escapeBlessedTags(text) {
|
|
19
13
|
return text.replace(/[{}]/g, (ch) => (ch === "{" ? "{open}" : "{close}"));
|
|
20
14
|
}
|
|
15
|
+
function stripBlessedTags(text) {
|
|
16
|
+
return text.replace(/\{[^}]+\}/g, "");
|
|
17
|
+
}
|
|
21
18
|
function textIndexToLineColumn(value, index) {
|
|
22
19
|
const boundedIndex = Math.max(0, Math.min(value.length, index));
|
|
23
20
|
const beforeCursor = value.slice(0, boundedIndex);
|
|
@@ -37,91 +34,6 @@ function textLineColumnToIndex(value, line, column) {
|
|
|
37
34
|
const targetLine = lines[boundedLine] ?? "";
|
|
38
35
|
return index + Math.max(0, Math.min(targetLine.length, column));
|
|
39
36
|
}
|
|
40
|
-
function buildFlowTree(flows) {
|
|
41
|
-
const roots = new Map();
|
|
42
|
-
const ensureFolder = (pathSegments) => {
|
|
43
|
-
const firstSegment = pathSegments[0];
|
|
44
|
-
if (!firstSegment) {
|
|
45
|
-
throw new Error("Flow tree folder path cannot be empty.");
|
|
46
|
-
}
|
|
47
|
-
const rootFolder = roots.get(firstSegment);
|
|
48
|
-
let currentFolder;
|
|
49
|
-
if (rootFolder) {
|
|
50
|
-
currentFolder = rootFolder;
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
currentFolder = {
|
|
54
|
-
kind: "folder",
|
|
55
|
-
key: makeFolderKey([firstSegment]),
|
|
56
|
-
name: firstSegment,
|
|
57
|
-
pathSegments: [firstSegment],
|
|
58
|
-
children: [],
|
|
59
|
-
};
|
|
60
|
-
roots.set(firstSegment, currentFolder);
|
|
61
|
-
}
|
|
62
|
-
for (let index = 1; index < pathSegments.length; index += 1) {
|
|
63
|
-
const segment = pathSegments[index] ?? "";
|
|
64
|
-
const folderPath = pathSegments.slice(0, index + 1);
|
|
65
|
-
let nextFolder = currentFolder.children.find((child) => child.kind === "folder" && child.name === segment);
|
|
66
|
-
if (!nextFolder) {
|
|
67
|
-
nextFolder = {
|
|
68
|
-
kind: "folder",
|
|
69
|
-
key: makeFolderKey(folderPath),
|
|
70
|
-
name: segment,
|
|
71
|
-
pathSegments: folderPath,
|
|
72
|
-
children: [],
|
|
73
|
-
};
|
|
74
|
-
currentFolder.children.push(nextFolder);
|
|
75
|
-
}
|
|
76
|
-
currentFolder = nextFolder;
|
|
77
|
-
}
|
|
78
|
-
return currentFolder;
|
|
79
|
-
};
|
|
80
|
-
for (const flow of flows) {
|
|
81
|
-
if (flow.treePath.length === 0) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const folderPath = flow.treePath.slice(0, -1);
|
|
85
|
-
const leafName = flow.treePath[flow.treePath.length - 1] ?? flow.id;
|
|
86
|
-
const parent = ensureFolder(folderPath);
|
|
87
|
-
parent.children.push({
|
|
88
|
-
kind: "flow",
|
|
89
|
-
key: makeFlowKey(flow.id),
|
|
90
|
-
name: leafName,
|
|
91
|
-
pathSegments: [...flow.treePath],
|
|
92
|
-
flow,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
const sortNodes = (nodes) => [...nodes]
|
|
96
|
-
.sort((left, right) => {
|
|
97
|
-
if (left.kind !== right.kind) {
|
|
98
|
-
return left.kind === "folder" ? -1 : 1;
|
|
99
|
-
}
|
|
100
|
-
return compareTreeNames(left.name, right.name);
|
|
101
|
-
})
|
|
102
|
-
.map((node) => node.kind === "folder"
|
|
103
|
-
? {
|
|
104
|
-
...node,
|
|
105
|
-
children: sortNodes(node.children),
|
|
106
|
-
}
|
|
107
|
-
: node);
|
|
108
|
-
const orderedRootNames = ["custom", "default"];
|
|
109
|
-
const sortedRoots = [...roots.values()].sort((left, right) => {
|
|
110
|
-
const leftIndex = orderedRootNames.indexOf(left.name);
|
|
111
|
-
const rightIndex = orderedRootNames.indexOf(right.name);
|
|
112
|
-
if (leftIndex !== -1 || rightIndex !== -1) {
|
|
113
|
-
if (leftIndex === -1) {
|
|
114
|
-
return 1;
|
|
115
|
-
}
|
|
116
|
-
if (rightIndex === -1) {
|
|
117
|
-
return -1;
|
|
118
|
-
}
|
|
119
|
-
return leftIndex - rightIndex;
|
|
120
|
-
}
|
|
121
|
-
return compareTreeNames(left.name, right.name);
|
|
122
|
-
});
|
|
123
|
-
return sortNodes(sortedRoots);
|
|
124
|
-
}
|
|
125
37
|
export class InteractiveUi {
|
|
126
38
|
options;
|
|
127
39
|
screen;
|
|
@@ -136,6 +48,7 @@ export class InteractiveUi {
|
|
|
136
48
|
help;
|
|
137
49
|
confirm;
|
|
138
50
|
formModal;
|
|
51
|
+
formPreviewBox;
|
|
139
52
|
formLineInput;
|
|
140
53
|
formTextInput;
|
|
141
54
|
formBooleanInput;
|
|
@@ -324,6 +237,29 @@ export class InteractiveUi {
|
|
|
324
237
|
fg: "white",
|
|
325
238
|
},
|
|
326
239
|
});
|
|
240
|
+
this.formPreviewBox = blessed.box({
|
|
241
|
+
parent: this.formModal,
|
|
242
|
+
top: 0,
|
|
243
|
+
left: 0,
|
|
244
|
+
width: "100%-2",
|
|
245
|
+
height: 8,
|
|
246
|
+
hidden: true,
|
|
247
|
+
tags: true,
|
|
248
|
+
mouse: true,
|
|
249
|
+
keys: true,
|
|
250
|
+
vi: true,
|
|
251
|
+
scrollable: true,
|
|
252
|
+
alwaysScroll: true,
|
|
253
|
+
border: "line",
|
|
254
|
+
scrollbar: {
|
|
255
|
+
ch: " ",
|
|
256
|
+
inverse: true,
|
|
257
|
+
},
|
|
258
|
+
style: {
|
|
259
|
+
border: { fg: "cyan" },
|
|
260
|
+
fg: "white",
|
|
261
|
+
},
|
|
262
|
+
});
|
|
327
263
|
this.formTextInput = blessed.box({
|
|
328
264
|
parent: this.formModal,
|
|
329
265
|
top: 0,
|
|
@@ -854,7 +790,12 @@ export class InteractiveUi {
|
|
|
854
790
|
if (!this.activeFormSession) {
|
|
855
791
|
return null;
|
|
856
792
|
}
|
|
857
|
-
|
|
793
|
+
const field = this.activeFormSession.form.fields[this.activeFormSession.currentFieldIndex] ?? null;
|
|
794
|
+
if (!field) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
normalizeUserInputFieldValue(field, this.activeFormSession.values);
|
|
798
|
+
return resolveFieldDefinition(field, this.activeFormSession.values);
|
|
858
799
|
}
|
|
859
800
|
formModalInnerHeight() {
|
|
860
801
|
const rawHeight = typeof this.formModal.height === "number" ? this.formModal.height : this.formModal?.lpos?.yi
|
|
@@ -872,9 +813,17 @@ export class InteractiveUi {
|
|
|
872
813
|
const paddingRight = Number(this.formModal.padding?.right ?? 0);
|
|
873
814
|
return Math.max(24, rawWidth - 2 - paddingLeft - paddingRight);
|
|
874
815
|
}
|
|
816
|
+
renderedFormLineCount(lines) {
|
|
817
|
+
const width = Math.max(1, this.formModalInnerWidth() - 2);
|
|
818
|
+
return lines.reduce((total, line) => {
|
|
819
|
+
const visible = stripAnsi(stripBlessedTags(line));
|
|
820
|
+
return total + Math.max(1, Math.ceil(visible.length / width));
|
|
821
|
+
}, 0);
|
|
822
|
+
}
|
|
875
823
|
renderActiveForm() {
|
|
876
824
|
if (!this.activeFormSession) {
|
|
877
825
|
this.formModal.hide();
|
|
826
|
+
this.hideFormPreviewBox();
|
|
878
827
|
this.hideFormLineInput();
|
|
879
828
|
this.hideFormTextInput();
|
|
880
829
|
this.hideFormBooleanInput();
|
|
@@ -907,6 +856,7 @@ export class InteractiveUi {
|
|
|
907
856
|
let footerHint = ` Form: Enter ${isLastField ? "submit" : "confirm"} | Tab next | Shift+Tab prev | Esc cancel `;
|
|
908
857
|
let hintLines = [];
|
|
909
858
|
if (field.type === "boolean") {
|
|
859
|
+
this.hideFormPreviewBox();
|
|
910
860
|
this.hideFormLineInput();
|
|
911
861
|
this.hideFormTextInput();
|
|
912
862
|
this.hideFormSelectInput();
|
|
@@ -927,6 +877,7 @@ export class InteractiveUi {
|
|
|
927
877
|
footerHint = ` Form: Space toggle | Enter ${isLastField ? "submit" : "next"} | Tab switch | Esc cancel `;
|
|
928
878
|
}
|
|
929
879
|
else if (field.type === "text") {
|
|
880
|
+
this.hideFormPreviewBox();
|
|
930
881
|
this.hideFormBooleanInput();
|
|
931
882
|
this.hideFormSelectInput();
|
|
932
883
|
helperLines.push("{cyan-fg}Use the standard editor below.{/cyan-fg}");
|
|
@@ -958,8 +909,31 @@ export class InteractiveUi {
|
|
|
958
909
|
this.hideFormLineInput();
|
|
959
910
|
this.hideFormTextInput();
|
|
960
911
|
this.hideFormBooleanInput();
|
|
961
|
-
|
|
962
|
-
|
|
912
|
+
const previewContent = session.form.preview?.trim() ?? "";
|
|
913
|
+
let previewScrollable = false;
|
|
914
|
+
if (previewContent.length > 0) {
|
|
915
|
+
this.hideFormPreviewBox();
|
|
916
|
+
helperLines.push("{cyan-fg}Review the content above, then choose an action below.{/cyan-fg}");
|
|
917
|
+
lines.push(...helperLines);
|
|
918
|
+
const previewLines = previewContent.split("\n");
|
|
919
|
+
const viewportHeight = this.formPreviewViewportHeight(this.renderedFormLineCount(lines), 0);
|
|
920
|
+
const maxOffset = Math.max(0, previewLines.length - viewportHeight);
|
|
921
|
+
session.previewScrollOffset = Math.max(0, Math.min(session.previewScrollOffset, maxOffset));
|
|
922
|
+
const visibleLines = previewLines.slice(session.previewScrollOffset, session.previewScrollOffset + viewportHeight);
|
|
923
|
+
lines.push("", ...visibleLines);
|
|
924
|
+
previewScrollable = maxOffset > 0;
|
|
925
|
+
if (previewScrollable) {
|
|
926
|
+
const firstVisibleLine = session.previewScrollOffset + 1;
|
|
927
|
+
const lastVisibleLine = session.previewScrollOffset + visibleLines.length;
|
|
928
|
+
lines.push("", `{gray-fg}Preview ${firstVisibleLine}-${lastVisibleLine} of ${previewLines.length}. Use PageUp/PageDown to scroll.{/gray-fg}`);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
this.hideFormPreviewBox();
|
|
933
|
+
helperLines.push("{cyan-fg}Use the list below.{/cyan-fg}");
|
|
934
|
+
lines.push(...helperLines);
|
|
935
|
+
}
|
|
936
|
+
this.ensureFormSelectInputVisible(this.renderedFormLineCount(lines), 0);
|
|
963
937
|
const preferredOptionIndex = field.type === "single-select"
|
|
964
938
|
? this.selectedOptionIndexForField(field)
|
|
965
939
|
: session.currentOptionIndex;
|
|
@@ -977,11 +951,17 @@ export class InteractiveUi {
|
|
|
977
951
|
this.formSelectInput.select(currentOptionIndex);
|
|
978
952
|
hintLines = [
|
|
979
953
|
`Up/Down: move | Space: ${field.type === "single-select" ? "pick" : "toggle"} | Enter: ${isLastField ? "submit" : "confirm and next"}`,
|
|
980
|
-
|
|
954
|
+
previewScrollable
|
|
955
|
+
? "PageUp/PageDown: scroll preview | Tab/Shift+Tab: switch field | Esc: cancel"
|
|
956
|
+
: "Tab/Shift+Tab: switch field | Esc: cancel",
|
|
981
957
|
];
|
|
982
|
-
footerHint =
|
|
958
|
+
footerHint = previewScrollable
|
|
959
|
+
? ` Form: Up/Down move | PageUp/PageDown preview | Enter ${isLastField ? "submit" : "next"} | Tab switch | Esc cancel `
|
|
960
|
+
: ` Form: Up/Down move | Enter ${isLastField ? "submit" : "next"} | Tab switch | Esc cancel `;
|
|
961
|
+
}
|
|
962
|
+
if (field.type !== "single-select" && field.type !== "multi-select") {
|
|
963
|
+
lines.push(...helperLines);
|
|
983
964
|
}
|
|
984
|
-
lines.push(...helperLines);
|
|
985
965
|
this.formModal.setContent(lines.join("\n"));
|
|
986
966
|
this.formModal.setScroll(0);
|
|
987
967
|
this.formModal.show();
|
|
@@ -1018,14 +998,17 @@ export class InteractiveUi {
|
|
|
1018
998
|
const current = String(this.activeFormSession.values[nextField.id] ?? "");
|
|
1019
999
|
this.activeFormSession.currentTextCursorIndex = current.length;
|
|
1020
1000
|
this.activeFormSession.currentOptionIndex = 0;
|
|
1001
|
+
this.activeFormSession.previewScrollOffset = 0;
|
|
1021
1002
|
}
|
|
1022
1003
|
else if (nextField?.type === "single-select" || nextField?.type === "multi-select") {
|
|
1023
1004
|
this.activeFormSession.currentTextCursorIndex = 0;
|
|
1024
1005
|
this.activeFormSession.currentOptionIndex = this.selectedOptionIndexForField(nextField);
|
|
1006
|
+
this.activeFormSession.previewScrollOffset = 0;
|
|
1025
1007
|
}
|
|
1026
1008
|
else {
|
|
1027
1009
|
this.activeFormSession.currentTextCursorIndex = 0;
|
|
1028
1010
|
this.activeFormSession.currentOptionIndex = 0;
|
|
1011
|
+
this.activeFormSession.previewScrollOffset = 0;
|
|
1029
1012
|
}
|
|
1030
1013
|
this.renderActiveForm();
|
|
1031
1014
|
}
|
|
@@ -1145,17 +1128,39 @@ export class InteractiveUi {
|
|
|
1145
1128
|
this.positionFormHint(this.formBooleanInput.top + this.formBooleanInput.height);
|
|
1146
1129
|
}
|
|
1147
1130
|
ensureFormSelectInputVisible(headerLineCount, footerLineCount) {
|
|
1148
|
-
const
|
|
1131
|
+
const previewVisible = this.formPreviewBox.visible;
|
|
1132
|
+
const reservedTop = previewVisible
|
|
1133
|
+
? Number(this.formPreviewBox.top ?? 1) + Number(this.formPreviewBox.height ?? 6) + 1
|
|
1134
|
+
: Math.max(1, headerLineCount + 2);
|
|
1149
1135
|
const availableHeight = Math.max(6, this.formModalInnerHeight() - reservedTop - footerLineCount - 2);
|
|
1150
1136
|
this.formSelectInput.top = reservedTop;
|
|
1151
1137
|
this.formSelectInput.left = 1;
|
|
1152
1138
|
this.formSelectInput.width = Math.max(24, this.formModalInnerWidth() - 4);
|
|
1153
|
-
this.formSelectInput.height =
|
|
1139
|
+
this.formSelectInput.height = previewVisible
|
|
1140
|
+
? Math.max(5, availableHeight - 1)
|
|
1141
|
+
: Math.max(4, availableHeight - 2);
|
|
1154
1142
|
this.formSelectInput.show();
|
|
1155
1143
|
this.formSelectInput.setFront();
|
|
1156
1144
|
this.formSelectInput.focus();
|
|
1157
1145
|
this.positionFormHint(this.formSelectInput.top + this.formSelectInput.height);
|
|
1158
1146
|
}
|
|
1147
|
+
ensureFormPreviewBoxVisible(headerLineCount, footerLineCount) {
|
|
1148
|
+
const reservedTop = Math.max(1, headerLineCount + 2);
|
|
1149
|
+
const hintHeight = 2;
|
|
1150
|
+
const selectHeight = Math.max(5, Math.min(10, Math.floor(this.formModalInnerHeight() * 0.32)));
|
|
1151
|
+
const availableHeight = Math.max(6, this.formModalInnerHeight() - reservedTop - footerLineCount - selectHeight - hintHeight - 3);
|
|
1152
|
+
this.formPreviewBox.top = reservedTop;
|
|
1153
|
+
this.formPreviewBox.left = 1;
|
|
1154
|
+
this.formPreviewBox.width = Math.max(24, this.formModalInnerWidth() - 4);
|
|
1155
|
+
this.formPreviewBox.height = Math.max(6, Math.min(12, availableHeight));
|
|
1156
|
+
}
|
|
1157
|
+
formPreviewViewportHeight(headerLineCount, footerLineCount) {
|
|
1158
|
+
const reservedTop = Math.max(1, headerLineCount + 2);
|
|
1159
|
+
const hintHeight = 2;
|
|
1160
|
+
const selectHeight = Math.max(5, Math.min(10, Math.floor(this.formModalInnerHeight() * 0.32)));
|
|
1161
|
+
const availableHeight = Math.max(4, this.formModalInnerHeight() - reservedTop - footerLineCount - selectHeight - hintHeight - 1);
|
|
1162
|
+
return Math.max(4, Math.min(10, availableHeight));
|
|
1163
|
+
}
|
|
1159
1164
|
positionFormHint(top) {
|
|
1160
1165
|
this.formHint.top = top;
|
|
1161
1166
|
this.formHint.left = 1;
|
|
@@ -1179,6 +1184,12 @@ export class InteractiveUi {
|
|
|
1179
1184
|
this.formTextInput.blur();
|
|
1180
1185
|
}
|
|
1181
1186
|
}
|
|
1187
|
+
hideFormPreviewBox() {
|
|
1188
|
+
this.formPreviewBox.hide();
|
|
1189
|
+
if (typeof this.formPreviewBox.blur === "function") {
|
|
1190
|
+
this.formPreviewBox.blur();
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1182
1193
|
hideFormBooleanInput() {
|
|
1183
1194
|
this.formBooleanInput.hide();
|
|
1184
1195
|
if (typeof this.formBooleanInput.blur === "function") {
|
|
@@ -1269,10 +1280,12 @@ export class InteractiveUi {
|
|
|
1269
1280
|
};
|
|
1270
1281
|
this.activeFormSession = null;
|
|
1271
1282
|
this.formModal.hide();
|
|
1283
|
+
this.hideFormPreviewBox();
|
|
1272
1284
|
this.hideFormLineInput();
|
|
1273
1285
|
this.hideFormTextInput();
|
|
1274
1286
|
this.hideFormBooleanInput();
|
|
1275
1287
|
this.hideFormSelectInput();
|
|
1288
|
+
this.hideFormHint();
|
|
1276
1289
|
this.focusPane("flows");
|
|
1277
1290
|
session.resolve(result);
|
|
1278
1291
|
this.renderActiveForm();
|
|
@@ -1289,10 +1302,12 @@ export class InteractiveUi {
|
|
|
1289
1302
|
const session = this.activeFormSession;
|
|
1290
1303
|
this.activeFormSession = null;
|
|
1291
1304
|
this.formModal.hide();
|
|
1305
|
+
this.hideFormPreviewBox();
|
|
1292
1306
|
this.hideFormLineInput();
|
|
1293
1307
|
this.hideFormTextInput();
|
|
1294
1308
|
this.hideFormBooleanInput();
|
|
1295
1309
|
this.hideFormSelectInput();
|
|
1310
|
+
this.hideFormHint();
|
|
1296
1311
|
this.focusPane("flows");
|
|
1297
1312
|
session.reject(new TaskRunnerError(`User cancelled form '${session.form.formId}'.`));
|
|
1298
1313
|
this.renderActiveForm();
|
|
@@ -1304,10 +1319,12 @@ export class InteractiveUi {
|
|
|
1304
1319
|
const session = this.activeFormSession;
|
|
1305
1320
|
this.activeFormSession = null;
|
|
1306
1321
|
this.formModal.hide();
|
|
1322
|
+
this.hideFormPreviewBox();
|
|
1307
1323
|
this.hideFormLineInput();
|
|
1308
1324
|
this.hideFormTextInput();
|
|
1309
1325
|
this.hideFormBooleanInput();
|
|
1310
1326
|
this.hideFormSelectInput();
|
|
1327
|
+
this.hideFormHint();
|
|
1311
1328
|
this.focusPane("flows");
|
|
1312
1329
|
session.reject(new FlowInterruptedError(message));
|
|
1313
1330
|
this.renderActiveForm();
|
|
@@ -1503,6 +1520,32 @@ export class InteractiveUi {
|
|
|
1503
1520
|
this.requestRender();
|
|
1504
1521
|
return;
|
|
1505
1522
|
}
|
|
1523
|
+
if (key.name === "pageup" || key.name === "pagedown") {
|
|
1524
|
+
const session = this.activeFormSession;
|
|
1525
|
+
const previewContent = session?.form.preview?.trim() ?? "";
|
|
1526
|
+
if (session && previewContent.length > 0) {
|
|
1527
|
+
const previewLines = previewContent.split("\n");
|
|
1528
|
+
const headerLines = [`{bold}${session.form.title}{/bold}`];
|
|
1529
|
+
if (session.form.description?.trim()) {
|
|
1530
|
+
headerLines.push("");
|
|
1531
|
+
headerLines.push(session.form.description.trim());
|
|
1532
|
+
}
|
|
1533
|
+
headerLines.push("");
|
|
1534
|
+
headerLines.push(`Field ${session.currentFieldIndex + 1}/${session.form.fields.length}`);
|
|
1535
|
+
headerLines.push(`{yellow-fg}${field.label}{/yellow-fg}`);
|
|
1536
|
+
if (field.help?.trim()) {
|
|
1537
|
+
headerLines.push(field.help.trim());
|
|
1538
|
+
}
|
|
1539
|
+
headerLines.push("");
|
|
1540
|
+
headerLines.push("{cyan-fg}Review the content above, then choose an action below.{/cyan-fg}");
|
|
1541
|
+
const viewportHeight = this.formPreviewViewportHeight(this.renderedFormLineCount(headerLines), 0);
|
|
1542
|
+
if (previewLines.length > viewportHeight) {
|
|
1543
|
+
session.previewScrollOffset = Math.max(0, Math.min(previewLines.length - viewportHeight, session.previewScrollOffset + (key.name === "pageup" ? -viewportHeight : viewportHeight)));
|
|
1544
|
+
this.renderActiveForm();
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1506
1549
|
if (key.name === "space") {
|
|
1507
1550
|
this.syncActiveSelectFieldValue();
|
|
1508
1551
|
this.toggleActiveFormValue();
|
|
@@ -1587,70 +1630,22 @@ export class InteractiveUi {
|
|
|
1587
1630
|
: this.currentFlowId === flow.id
|
|
1588
1631
|
? this.flowState.executionState
|
|
1589
1632
|
: null;
|
|
1633
|
+
const progressViewModel = buildProgressViewModel(flow, flowState);
|
|
1590
1634
|
const lines = [`{bold}${flow.label}{/bold}`, ""];
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
sawExecutedItem = true;
|
|
1597
|
-
return;
|
|
1598
|
-
}
|
|
1599
|
-
if (status === "done" || status === "skipped") {
|
|
1600
|
-
sawExecutedItem = true;
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
|
-
if (status === "pending" && sawExecutedItem && anchorLine === null) {
|
|
1604
|
-
anchorLine = lines.length;
|
|
1605
|
-
}
|
|
1606
|
-
};
|
|
1607
|
-
for (const item of this.visiblePhaseItems(flow, flowState)) {
|
|
1608
|
-
if (item.kind === "group") {
|
|
1609
|
-
const visiblePhases = item.phases.filter((phase) => this.shouldDisplayPhase(flow, flowState, phase));
|
|
1610
|
-
if (visiblePhases.length === 0) {
|
|
1611
|
-
continue;
|
|
1612
|
-
}
|
|
1613
|
-
const groupStatus = this.statusForGroup(flow, visiblePhases, flowState);
|
|
1614
|
-
rememberAnchor(groupStatus);
|
|
1615
|
-
lines.push(`${this.symbolForGroup(flow.id, flow, visiblePhases, flowState)} ${this.colorizeProgressLabel(item.label, groupStatus)}`);
|
|
1616
|
-
for (const phase of visiblePhases) {
|
|
1617
|
-
const phaseState = flowState?.phases.find((candidate) => candidate.id === phase.id);
|
|
1618
|
-
const phaseStatus = this.displayStatusForPhase(flowState, flow, phase, phaseState?.status ?? null);
|
|
1619
|
-
rememberAnchor(phaseStatus);
|
|
1620
|
-
lines.push(` ${this.symbolForStatus(flow.id, phaseStatus)} ${this.colorizeProgressLabel(this.displayPhaseId(phase), phaseStatus)}`);
|
|
1621
|
-
for (const step of phase.steps) {
|
|
1622
|
-
const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
|
|
1623
|
-
const stepStatus = this.displayStatusForStep(flowState, flow, phase, stepState?.status ?? null);
|
|
1624
|
-
rememberAnchor(stepStatus);
|
|
1625
|
-
lines.push(` ${this.symbolForStatus(flow.id, stepStatus)} ${this.colorizeProgressLabel(step.id, stepStatus)}`);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
lines.push("");
|
|
1629
|
-
continue;
|
|
1630
|
-
}
|
|
1631
|
-
const phase = item.phase;
|
|
1632
|
-
if (!this.shouldDisplayPhase(flow, flowState, phase)) {
|
|
1635
|
+
for (const item of progressViewModel.items) {
|
|
1636
|
+
if (item.kind === "termination") {
|
|
1637
|
+
const symbol = item.status === "done" ? "{green-fg}✓{/green-fg}" : "{yellow-fg}■{/yellow-fg}";
|
|
1638
|
+
lines.push(`${symbol} ${this.colorizeProgressLabel(item.label, item.status)}`);
|
|
1639
|
+
lines.push(`{gray-fg}${item.detail}{/gray-fg}`);
|
|
1633
1640
|
continue;
|
|
1634
1641
|
}
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1637
|
-
rememberAnchor(phaseStatus);
|
|
1638
|
-
lines.push(`${this.symbolForStatus(flow.id, phaseStatus)} ${this.colorizeProgressLabel(this.displayPhaseId(phase), phaseStatus)}`);
|
|
1639
|
-
for (const step of phase.steps) {
|
|
1640
|
-
const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
|
|
1641
|
-
const stepStatus = this.displayStatusForStep(flowState, flow, phase, stepState?.status ?? null);
|
|
1642
|
-
rememberAnchor(stepStatus);
|
|
1643
|
-
lines.push(` ${this.symbolForStatus(flow.id, stepStatus)} ${this.colorizeProgressLabel(step.id, stepStatus)}`);
|
|
1644
|
-
}
|
|
1645
|
-
lines.push("");
|
|
1646
|
-
}
|
|
1647
|
-
if (flowState?.terminated) {
|
|
1648
|
-
lines.push(`{green-fg}✓{/green-fg} {green-fg}Flow completed successfully{/green-fg}`);
|
|
1649
|
-
lines.push(`{gray-fg}Reason: ${flowState.terminationReason ?? "flow terminated"}{/gray-fg}`);
|
|
1642
|
+
const indent = " ".repeat(item.depth);
|
|
1643
|
+
lines.push(`${indent}${this.symbolForStatus(flow.id, item.status)} ${this.colorizeProgressLabel(item.label, item.status)}`);
|
|
1650
1644
|
}
|
|
1651
1645
|
this.progress.setContent(lines.join("\n").trimEnd());
|
|
1652
|
-
if (this.busy && this.activeFlowId() === flow.id &&
|
|
1646
|
+
if (this.busy && this.activeFlowId() === flow.id && progressViewModel.anchorIndex !== null) {
|
|
1653
1647
|
const viewportHeight = Math.max(1, Number(this.progress.height) - 2);
|
|
1648
|
+
const anchorLine = progressViewModel.anchorIndex + 2;
|
|
1654
1649
|
const targetScroll = Math.max(0, anchorLine - Math.floor(viewportHeight / 2));
|
|
1655
1650
|
this.progress.setScroll(targetScroll);
|
|
1656
1651
|
}
|
|
@@ -1969,6 +1964,7 @@ export class InteractiveUi {
|
|
|
1969
1964
|
currentFieldIndex: 0,
|
|
1970
1965
|
currentOptionIndex: initialOptionIndex,
|
|
1971
1966
|
currentTextCursorIndex: initialCursorIndex,
|
|
1967
|
+
previewScrollOffset: 0,
|
|
1972
1968
|
resolve,
|
|
1973
1969
|
reject,
|
|
1974
1970
|
};
|
|
@@ -2083,44 +2079,13 @@ export class InteractiveUi {
|
|
|
2083
2079
|
this.requestRender();
|
|
2084
2080
|
}
|
|
2085
2081
|
computeVisibleFlowItems() {
|
|
2086
|
-
|
|
2087
|
-
const walk = (nodes, depth) => {
|
|
2088
|
-
for (const node of nodes) {
|
|
2089
|
-
if (node.kind === "folder") {
|
|
2090
|
-
items.push({
|
|
2091
|
-
kind: "folder",
|
|
2092
|
-
key: node.key,
|
|
2093
|
-
name: node.name,
|
|
2094
|
-
depth,
|
|
2095
|
-
pathSegments: [...node.pathSegments],
|
|
2096
|
-
});
|
|
2097
|
-
if (this.expandedFlowFolders.has(node.key)) {
|
|
2098
|
-
walk(node.children, depth + 1);
|
|
2099
|
-
}
|
|
2100
|
-
continue;
|
|
2101
|
-
}
|
|
2102
|
-
items.push({
|
|
2103
|
-
kind: "flow",
|
|
2104
|
-
key: node.key,
|
|
2105
|
-
name: node.name,
|
|
2106
|
-
depth,
|
|
2107
|
-
pathSegments: [...node.pathSegments],
|
|
2108
|
-
flow: node.flow,
|
|
2109
|
-
});
|
|
2110
|
-
}
|
|
2111
|
-
};
|
|
2112
|
-
walk(this.flowTree, 0);
|
|
2113
|
-
return items;
|
|
2082
|
+
return computeVisibleFlowItems(this.flowTree, this.expandedFlowFolders);
|
|
2114
2083
|
}
|
|
2115
2084
|
selectedFlowTreeItem() {
|
|
2116
2085
|
return this.visibleFlowItems.find((item) => item.key === this.selectedFlowItemKey);
|
|
2117
2086
|
}
|
|
2118
2087
|
selectedHeaderLabel() {
|
|
2119
|
-
|
|
2120
|
-
if (!selectedItem) {
|
|
2121
|
-
return this.selectedFlowId;
|
|
2122
|
-
}
|
|
2123
|
-
return selectedItem.kind === "folder" ? selectedItem.pathSegments.join("/") : selectedItem.flow.label;
|
|
2088
|
+
return selectHeaderLabel(this.selectedFlowTreeItem(), this.selectedFlowId);
|
|
2124
2089
|
}
|
|
2125
2090
|
refreshVisibleFlowItems() {
|
|
2126
2091
|
this.visibleFlowItems = this.computeVisibleFlowItems();
|
package/dist/pipeline/context.js
CHANGED
|
@@ -17,5 +17,6 @@ export function createPipelineContext(input) {
|
|
|
17
17
|
nodes: createNodeRegistry(),
|
|
18
18
|
...(input.setSummary ? { setSummary: input.setSummary } : {}),
|
|
19
19
|
...(input.requestUserInput ? { requestUserInput: input.requestUserInput } : {}),
|
|
20
|
+
...(input.executionRouting ? { executionRouting: input.executionRouting } : {}),
|
|
20
21
|
};
|
|
21
22
|
}
|