agentweaver 0.1.2 → 0.1.3
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 +11 -10
- package/dist/artifacts.js +24 -2
- package/dist/executors/claude-executor.js +12 -2
- package/dist/executors/claude-summary-executor.js +1 -1
- package/dist/executors/codex-docker-executor.js +1 -1
- package/dist/executors/codex-local-executor.js +1 -1
- package/dist/executors/configs/claude-config.js +2 -1
- package/dist/index.js +388 -451
- package/dist/interactive-ui.js +451 -194
- package/dist/jira.js +3 -1
- package/dist/pipeline/auto-flow.js +9 -0
- package/dist/pipeline/context.js +2 -0
- package/dist/pipeline/declarative-flow-runner.js +246 -0
- package/dist/pipeline/declarative-flows.js +24 -0
- package/dist/pipeline/flow-specs/auto.json +471 -0
- package/dist/pipeline/flow-specs/implement.json +47 -0
- package/dist/pipeline/flow-specs/plan.json +88 -0
- package/dist/pipeline/flow-specs/preflight.json +174 -0
- package/dist/pipeline/flow-specs/review-fix.json +76 -0
- package/dist/pipeline/flow-specs/review.json +233 -0
- package/dist/pipeline/flow-specs/test-fix.json +24 -0
- package/dist/pipeline/flow-specs/test-linter-fix.json +24 -0
- package/dist/pipeline/flow-specs/test.json +19 -0
- package/dist/pipeline/flows/implement-flow.js +3 -4
- package/dist/pipeline/flows/preflight-flow.js +17 -57
- package/dist/pipeline/flows/review-fix-flow.js +3 -4
- package/dist/pipeline/flows/review-flow.js +8 -4
- package/dist/pipeline/flows/test-fix-flow.js +3 -4
- package/dist/pipeline/node-registry.js +71 -0
- package/dist/pipeline/node-runner.js +9 -3
- package/dist/pipeline/nodes/build-failure-summary-node.js +4 -4
- package/dist/pipeline/nodes/claude-prompt-node.js +54 -0
- package/dist/pipeline/nodes/claude-summary-node.js +12 -6
- package/dist/pipeline/nodes/codex-docker-prompt-node.js +1 -0
- package/dist/pipeline/nodes/codex-local-prompt-node.js +32 -0
- package/dist/pipeline/nodes/file-check-node.js +15 -0
- package/dist/pipeline/nodes/summary-file-load-node.js +16 -0
- package/dist/pipeline/nodes/task-summary-node.js +12 -6
- package/dist/pipeline/prompt-registry.js +22 -0
- package/dist/pipeline/prompt-runtime.js +18 -0
- package/dist/pipeline/registry.js +0 -2
- package/dist/pipeline/spec-compiler.js +200 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-types.js +1 -0
- package/dist/pipeline/spec-validator.js +290 -0
- package/dist/pipeline/value-resolver.js +199 -0
- package/dist/prompts.js +1 -3
- package/dist/runtime/process-runner.js +24 -23
- package/dist/tui.js +39 -0
- package/package.json +2 -2
package/dist/interactive-ui.js
CHANGED
|
@@ -5,28 +5,40 @@ export class InteractiveUi {
|
|
|
5
5
|
options;
|
|
6
6
|
screen;
|
|
7
7
|
header;
|
|
8
|
-
|
|
8
|
+
progress;
|
|
9
|
+
flowList;
|
|
9
10
|
status;
|
|
10
|
-
|
|
11
|
+
summary;
|
|
11
12
|
log;
|
|
12
|
-
input;
|
|
13
13
|
footer;
|
|
14
14
|
help;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
confirm;
|
|
16
|
+
flowMap;
|
|
17
17
|
busy = false;
|
|
18
|
-
|
|
18
|
+
currentFlowId = null;
|
|
19
|
+
selectedFlowId;
|
|
19
20
|
summaryText = "";
|
|
20
|
-
focusedPane = "
|
|
21
|
+
focusedPane = "flows";
|
|
21
22
|
currentNode = null;
|
|
22
23
|
currentExecutor = null;
|
|
23
24
|
spinnerFrame = 0;
|
|
24
25
|
spinnerTimer = null;
|
|
25
26
|
runningStartedAt = null;
|
|
26
|
-
|
|
27
|
+
renderScheduled = false;
|
|
28
|
+
logFlushTimer = null;
|
|
29
|
+
pendingLogLines = [];
|
|
30
|
+
flowState = {
|
|
31
|
+
flowId: null,
|
|
32
|
+
executionState: null,
|
|
33
|
+
};
|
|
34
|
+
failedFlowId = null;
|
|
35
|
+
constructor(options) {
|
|
27
36
|
this.options = options;
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
if (options.flows.length === 0) {
|
|
38
|
+
throw new Error("Interactive UI requires at least one flow.");
|
|
39
|
+
}
|
|
40
|
+
this.flowMap = new Map(options.flows.map((flow) => [flow.id, flow]));
|
|
41
|
+
this.selectedFlowId = options.flows[0]?.id ?? "auto";
|
|
30
42
|
this.screen = blessed.screen({
|
|
31
43
|
smartCSR: true,
|
|
32
44
|
fullUnicode: true,
|
|
@@ -51,14 +63,14 @@ export class InteractiveUi {
|
|
|
51
63
|
fg: "white",
|
|
52
64
|
},
|
|
53
65
|
});
|
|
54
|
-
this.
|
|
66
|
+
this.progress = blessed.box({
|
|
55
67
|
parent: this.screen,
|
|
56
68
|
top: 3,
|
|
57
69
|
left: 0,
|
|
58
|
-
width: "
|
|
59
|
-
|
|
70
|
+
width: "34%",
|
|
71
|
+
height: "50%-1",
|
|
60
72
|
tags: true,
|
|
61
|
-
label: "
|
|
73
|
+
label: " Current Flow ",
|
|
62
74
|
padding: {
|
|
63
75
|
left: 1,
|
|
64
76
|
right: 1,
|
|
@@ -69,18 +81,21 @@ export class InteractiveUi {
|
|
|
69
81
|
keys: true,
|
|
70
82
|
vi: true,
|
|
71
83
|
style: {
|
|
72
|
-
border: { fg: "
|
|
84
|
+
border: { fg: "green" },
|
|
73
85
|
fg: "white",
|
|
74
86
|
},
|
|
75
87
|
});
|
|
76
|
-
this.
|
|
88
|
+
this.flowList = blessed.list({
|
|
77
89
|
parent: this.screen,
|
|
78
|
-
top:
|
|
79
|
-
left:
|
|
80
|
-
width: "
|
|
81
|
-
|
|
90
|
+
top: "50%+2",
|
|
91
|
+
left: 0,
|
|
92
|
+
width: "34%",
|
|
93
|
+
bottom: 10,
|
|
94
|
+
keys: true,
|
|
95
|
+
vi: true,
|
|
96
|
+
mouse: true,
|
|
82
97
|
tags: true,
|
|
83
|
-
label: "
|
|
98
|
+
label: " Flows ",
|
|
84
99
|
padding: {
|
|
85
100
|
left: 1,
|
|
86
101
|
right: 1,
|
|
@@ -88,18 +103,47 @@ export class InteractiveUi {
|
|
|
88
103
|
border: "line",
|
|
89
104
|
scrollable: true,
|
|
90
105
|
alwaysScroll: true,
|
|
106
|
+
style: {
|
|
107
|
+
border: { fg: "cyan" },
|
|
108
|
+
fg: "white",
|
|
109
|
+
selected: {
|
|
110
|
+
fg: "black",
|
|
111
|
+
bg: "green",
|
|
112
|
+
bold: true,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
this.confirm = blessed.box({
|
|
117
|
+
parent: this.screen,
|
|
118
|
+
top: "center",
|
|
119
|
+
left: "center",
|
|
120
|
+
width: 44,
|
|
121
|
+
height: 8,
|
|
122
|
+
hidden: true,
|
|
123
|
+
tags: true,
|
|
124
|
+
label: " Confirm ",
|
|
125
|
+
padding: {
|
|
126
|
+
left: 1,
|
|
127
|
+
right: 1,
|
|
128
|
+
top: 1,
|
|
129
|
+
bottom: 1,
|
|
130
|
+
},
|
|
131
|
+
border: "line",
|
|
91
132
|
keys: true,
|
|
92
133
|
vi: true,
|
|
93
134
|
style: {
|
|
94
|
-
border: { fg: "
|
|
135
|
+
border: { fg: "yellow" },
|
|
136
|
+
bg: undefined,
|
|
95
137
|
fg: "white",
|
|
96
138
|
},
|
|
139
|
+
align: "center",
|
|
140
|
+
valign: "middle",
|
|
97
141
|
});
|
|
98
142
|
this.status = blessed.box({
|
|
99
143
|
parent: this.screen,
|
|
100
144
|
bottom: 4,
|
|
101
145
|
left: 0,
|
|
102
|
-
width: "
|
|
146
|
+
width: "34%",
|
|
103
147
|
height: 6,
|
|
104
148
|
tags: true,
|
|
105
149
|
label: " Status ",
|
|
@@ -113,12 +157,34 @@ export class InteractiveUi {
|
|
|
113
157
|
fg: "white",
|
|
114
158
|
},
|
|
115
159
|
});
|
|
160
|
+
this.summary = blessed.box({
|
|
161
|
+
parent: this.screen,
|
|
162
|
+
top: 3,
|
|
163
|
+
left: "34%",
|
|
164
|
+
width: "66%",
|
|
165
|
+
height: 12,
|
|
166
|
+
tags: true,
|
|
167
|
+
label: " Task Summary ",
|
|
168
|
+
padding: {
|
|
169
|
+
left: 1,
|
|
170
|
+
right: 1,
|
|
171
|
+
},
|
|
172
|
+
border: "line",
|
|
173
|
+
scrollable: true,
|
|
174
|
+
alwaysScroll: true,
|
|
175
|
+
keys: true,
|
|
176
|
+
vi: true,
|
|
177
|
+
style: {
|
|
178
|
+
border: { fg: "green" },
|
|
179
|
+
fg: "white",
|
|
180
|
+
},
|
|
181
|
+
});
|
|
116
182
|
this.log = blessed.log({
|
|
117
183
|
parent: this.screen,
|
|
118
|
-
top:
|
|
184
|
+
top: 15,
|
|
119
185
|
bottom: 4,
|
|
120
|
-
left: "
|
|
121
|
-
width: "
|
|
186
|
+
left: "34%",
|
|
187
|
+
width: "66%",
|
|
122
188
|
tags: false,
|
|
123
189
|
label: " Activity ",
|
|
124
190
|
padding: {
|
|
@@ -140,28 +206,6 @@ export class InteractiveUi {
|
|
|
140
206
|
fg: "white",
|
|
141
207
|
},
|
|
142
208
|
});
|
|
143
|
-
this.input = blessed.textbox({
|
|
144
|
-
parent: this.screen,
|
|
145
|
-
bottom: 1,
|
|
146
|
-
left: 0,
|
|
147
|
-
width: "100%",
|
|
148
|
-
height: 3,
|
|
149
|
-
keys: true,
|
|
150
|
-
inputOnFocus: true,
|
|
151
|
-
mouse: true,
|
|
152
|
-
label: " command ",
|
|
153
|
-
padding: {
|
|
154
|
-
left: 1,
|
|
155
|
-
},
|
|
156
|
-
border: "line",
|
|
157
|
-
style: {
|
|
158
|
-
border: { fg: "magenta" },
|
|
159
|
-
fg: "white",
|
|
160
|
-
focus: {
|
|
161
|
-
border: { fg: "magenta" },
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
209
|
this.footer = blessed.box({
|
|
166
210
|
parent: this.screen,
|
|
167
211
|
bottom: 0,
|
|
@@ -175,8 +219,8 @@ export class InteractiveUi {
|
|
|
175
219
|
parent: this.screen,
|
|
176
220
|
top: "center",
|
|
177
221
|
left: "center",
|
|
178
|
-
width: "
|
|
179
|
-
height: "
|
|
222
|
+
width: "64%",
|
|
223
|
+
height: "52%",
|
|
180
224
|
hidden: true,
|
|
181
225
|
tags: true,
|
|
182
226
|
label: " Help ",
|
|
@@ -202,212 +246,198 @@ export class InteractiveUi {
|
|
|
202
246
|
this.screen.key(["C-c", "q"], () => {
|
|
203
247
|
this.options.onExit();
|
|
204
248
|
});
|
|
205
|
-
this.screen.key(["f1", "?"], () => {
|
|
249
|
+
this.screen.key(["f1", "h", "?"], () => {
|
|
250
|
+
if (this.confirm.visible) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
206
253
|
this.help.hidden = !this.help.hidden;
|
|
207
254
|
if (!this.help.hidden) {
|
|
208
255
|
this.help.focus();
|
|
209
256
|
}
|
|
210
257
|
else {
|
|
211
|
-
this.
|
|
258
|
+
this.focusPane("flows");
|
|
212
259
|
}
|
|
213
|
-
this.
|
|
260
|
+
this.requestRender();
|
|
214
261
|
});
|
|
215
262
|
this.screen.key(["escape"], () => {
|
|
216
|
-
this.help.
|
|
217
|
-
|
|
218
|
-
|
|
263
|
+
if (!this.help.hidden) {
|
|
264
|
+
this.help.hide();
|
|
265
|
+
this.focusPane("flows");
|
|
266
|
+
this.requestRender();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (this.confirm.visible) {
|
|
270
|
+
this.closeConfirm();
|
|
271
|
+
}
|
|
219
272
|
});
|
|
220
273
|
this.screen.key(["C-l"], () => {
|
|
221
274
|
this.log.setContent("");
|
|
222
275
|
this.appendLog("Log cleared.");
|
|
223
276
|
});
|
|
277
|
+
this.screen.key(["tab"], () => {
|
|
278
|
+
if (this.confirm.visible || !this.help.hidden) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
this.cycleFocus(1);
|
|
282
|
+
});
|
|
224
283
|
this.screen.key(["S-tab"], () => {
|
|
284
|
+
if (this.confirm.visible || !this.help.hidden) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
225
287
|
this.cycleFocus(-1);
|
|
226
288
|
});
|
|
227
|
-
this.
|
|
228
|
-
|
|
229
|
-
|
|
289
|
+
this.flowList.on("select item", (_item, index) => {
|
|
290
|
+
const flow = this.options.flows[index];
|
|
291
|
+
if (!flow) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
this.selectedFlowId = flow.id;
|
|
295
|
+
this.renderProgress();
|
|
296
|
+
this.requestRender();
|
|
297
|
+
});
|
|
298
|
+
this.flowList.key(["enter"], () => {
|
|
299
|
+
if (this.busy || this.confirm.visible || !this.help.hidden) {
|
|
300
|
+
return;
|
|
230
301
|
}
|
|
302
|
+
this.openConfirm();
|
|
231
303
|
});
|
|
232
|
-
this.
|
|
233
|
-
this.
|
|
304
|
+
this.flowList.key(["pageup"], () => {
|
|
305
|
+
this.flowList.scroll(-(this.flowList.height - 2));
|
|
306
|
+
this.requestRender();
|
|
234
307
|
});
|
|
235
|
-
this.
|
|
236
|
-
this.
|
|
308
|
+
this.flowList.key(["pagedown"], () => {
|
|
309
|
+
this.flowList.scroll(this.flowList.height - 2);
|
|
310
|
+
this.requestRender();
|
|
237
311
|
});
|
|
238
312
|
this.log.key(["up"], () => {
|
|
239
313
|
this.log.scroll(-1);
|
|
240
|
-
this.
|
|
314
|
+
this.requestRender();
|
|
241
315
|
});
|
|
242
316
|
this.log.key(["down"], () => {
|
|
243
317
|
this.log.scroll(1);
|
|
244
|
-
this.
|
|
318
|
+
this.requestRender();
|
|
245
319
|
});
|
|
246
320
|
this.log.key(["pageup"], () => {
|
|
247
321
|
this.log.scroll(-(this.log.height - 2));
|
|
248
|
-
this.
|
|
322
|
+
this.requestRender();
|
|
249
323
|
});
|
|
250
324
|
this.log.key(["pagedown"], () => {
|
|
251
325
|
this.log.scroll(this.log.height - 2);
|
|
252
|
-
this.
|
|
326
|
+
this.requestRender();
|
|
253
327
|
});
|
|
254
328
|
this.log.key(["home"], () => {
|
|
255
329
|
this.log.setScroll(0);
|
|
256
|
-
this.
|
|
330
|
+
this.requestRender();
|
|
257
331
|
});
|
|
258
332
|
this.log.key(["end"], () => {
|
|
259
333
|
this.log.setScrollPerc(100);
|
|
260
|
-
this.
|
|
334
|
+
this.requestRender();
|
|
261
335
|
});
|
|
262
336
|
this.summary.key(["pageup"], () => {
|
|
263
337
|
this.summary.scroll(-(this.summary.height - 2));
|
|
264
|
-
this.
|
|
338
|
+
this.requestRender();
|
|
265
339
|
});
|
|
266
340
|
this.summary.key(["pagedown"], () => {
|
|
267
341
|
this.summary.scroll(this.summary.height - 2);
|
|
268
|
-
this.
|
|
342
|
+
this.requestRender();
|
|
269
343
|
});
|
|
270
|
-
this.
|
|
271
|
-
this.
|
|
272
|
-
this.
|
|
344
|
+
this.progress.key(["pageup"], () => {
|
|
345
|
+
this.progress.scroll(-(this.progress.height - 2));
|
|
346
|
+
this.requestRender();
|
|
273
347
|
});
|
|
274
|
-
this.
|
|
275
|
-
this.
|
|
276
|
-
this.
|
|
348
|
+
this.progress.key(["pagedown"], () => {
|
|
349
|
+
this.progress.scroll(this.progress.height - 2);
|
|
350
|
+
this.requestRender();
|
|
277
351
|
});
|
|
278
|
-
this.
|
|
279
|
-
if (this.
|
|
352
|
+
this.confirm.key(["enter"], async () => {
|
|
353
|
+
if (this.busy || this.confirm.hidden) {
|
|
280
354
|
return;
|
|
281
355
|
}
|
|
282
|
-
|
|
283
|
-
this.
|
|
284
|
-
this.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
356
|
+
const flowId = this.selectedFlowId;
|
|
357
|
+
this.closeConfirm();
|
|
358
|
+
this.setBusy(true, flowId);
|
|
359
|
+
this.clearFlowFailure(flowId);
|
|
360
|
+
this.setFlowDisplayState(flowId, null);
|
|
361
|
+
try {
|
|
362
|
+
await this.options.onRun(flowId);
|
|
289
363
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
});
|
|
294
|
-
this.input.key(["tab"], () => {
|
|
295
|
-
const current = String(this.input.getValue() ?? "");
|
|
296
|
-
const hit = this.options.commands.find((item) => item.startsWith(current.trim()));
|
|
297
|
-
if (hit) {
|
|
298
|
-
this.input.setValue(hit);
|
|
299
|
-
this.screen.render();
|
|
364
|
+
finally {
|
|
365
|
+
this.setBusy(false);
|
|
366
|
+
this.focusPane("flows");
|
|
300
367
|
}
|
|
301
368
|
});
|
|
302
|
-
this.
|
|
303
|
-
this.
|
|
304
|
-
});
|
|
305
|
-
this.input.key(["C-u"], () => {
|
|
306
|
-
this.focusPane("summary");
|
|
307
|
-
});
|
|
308
|
-
this.input.key(["C-h"], () => {
|
|
309
|
-
this.focusPane("sidebar");
|
|
310
|
-
});
|
|
311
|
-
this.input.key(["S-tab"], () => {
|
|
312
|
-
this.cycleFocus(-1);
|
|
313
|
-
});
|
|
314
|
-
this.log.key(["tab"], () => {
|
|
315
|
-
this.cycleFocus(1);
|
|
316
|
-
});
|
|
317
|
-
this.log.key(["S-tab"], () => {
|
|
318
|
-
this.cycleFocus(-1);
|
|
319
|
-
});
|
|
320
|
-
this.summary.key(["tab"], () => {
|
|
321
|
-
this.cycleFocus(1);
|
|
322
|
-
});
|
|
323
|
-
this.summary.key(["S-tab"], () => {
|
|
324
|
-
this.cycleFocus(-1);
|
|
325
|
-
});
|
|
326
|
-
this.sidebar.key(["tab"], () => {
|
|
327
|
-
this.cycleFocus(1);
|
|
328
|
-
});
|
|
329
|
-
this.sidebar.key(["S-tab"], () => {
|
|
330
|
-
this.cycleFocus(-1);
|
|
331
|
-
});
|
|
332
|
-
this.input.on("submit", async (value) => {
|
|
333
|
-
const line = value.trim();
|
|
334
|
-
this.input.clearValue();
|
|
335
|
-
this.screen.render();
|
|
336
|
-
if (!line || this.busy) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
this.history.push(line);
|
|
340
|
-
this.historyIndex = this.history.length;
|
|
341
|
-
this.appendLog(`> ${line}`);
|
|
342
|
-
await this.options.onSubmit(line);
|
|
343
|
-
this.focusPane("input");
|
|
369
|
+
this.confirm.key(["escape"], () => {
|
|
370
|
+
this.closeConfirm();
|
|
344
371
|
});
|
|
345
372
|
}
|
|
346
373
|
cycleFocus(direction) {
|
|
347
|
-
const panes = ["
|
|
374
|
+
const panes = ["flows", "progress", "summary", "log"];
|
|
348
375
|
const currentIndex = panes.indexOf(this.focusedPane);
|
|
349
376
|
const nextIndex = (currentIndex + direction + panes.length) % panes.length;
|
|
350
|
-
this.focusPane(panes[nextIndex] ?? "
|
|
377
|
+
this.focusPane(panes[nextIndex] ?? "flows");
|
|
351
378
|
}
|
|
352
379
|
focusPane(pane) {
|
|
353
380
|
this.focusedPane = pane;
|
|
354
|
-
this.
|
|
355
|
-
this.
|
|
381
|
+
this.flowList.style.border.fg = pane === "flows" ? "brightCyan" : "cyan";
|
|
382
|
+
this.progress.style.border.fg = pane === "progress" ? "brightGreen" : "green";
|
|
356
383
|
this.summary.style.border.fg = pane === "summary" ? "brightGreen" : "green";
|
|
357
|
-
this.
|
|
358
|
-
this.
|
|
359
|
-
|
|
360
|
-
|
|
384
|
+
this.log.style.border.fg = pane === "log" ? "brightYellow" : "yellow";
|
|
385
|
+
this.flowList.setLabel(pane === "flows" ? " ▶ Flows " : " Flows ");
|
|
386
|
+
this.progress.setLabel(pane === "progress" ? " ▶ Current Flow " : " Current Flow ");
|
|
387
|
+
this.summary.setLabel(pane === "summary" ? " ▶ Task Summary " : " Task Summary ");
|
|
388
|
+
this.log.setLabel(pane === "log" ? " ▶ Activity " : " Activity ");
|
|
389
|
+
if (pane === "flows") {
|
|
390
|
+
if (this.confirm.visible) {
|
|
391
|
+
this.confirm.focus();
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
this.flowList.focus();
|
|
395
|
+
}
|
|
361
396
|
}
|
|
362
|
-
else if (pane === "
|
|
363
|
-
this.
|
|
397
|
+
else if (pane === "progress") {
|
|
398
|
+
this.progress.focus();
|
|
364
399
|
}
|
|
365
400
|
else if (pane === "summary") {
|
|
366
401
|
this.summary.focus();
|
|
367
402
|
}
|
|
368
403
|
else {
|
|
369
|
-
this.
|
|
404
|
+
this.log.focus();
|
|
370
405
|
}
|
|
371
|
-
this.footer.setContent(` Focus: ${pane} |
|
|
372
|
-
this.
|
|
406
|
+
this.footer.setContent(` Focus: ${pane} | Up/Down: select flow | Enter: confirm run | h: help | Esc: close | Tab: switch pane | q: exit `);
|
|
407
|
+
this.requestRender();
|
|
373
408
|
}
|
|
374
409
|
renderStaticContent() {
|
|
375
410
|
this.summaryText = this.options.summaryText.trim();
|
|
376
411
|
this.updateHeader();
|
|
412
|
+
this.flowList.setItems(this.options.flows.map((flow) => flow.label));
|
|
413
|
+
this.flowList.select(this.options.flows.findIndex((flow) => flow.id === this.selectedFlowId));
|
|
377
414
|
this.renderSummary();
|
|
378
|
-
this.
|
|
379
|
-
this.options.commands.join("\n"),
|
|
380
|
-
"",
|
|
381
|
-
"Keys:",
|
|
382
|
-
"? / F1 help",
|
|
383
|
-
"Ctrl+L clear log",
|
|
384
|
-
"Tab complete",
|
|
385
|
-
"q / Ctrl+C exit",
|
|
386
|
-
].join("\n"));
|
|
415
|
+
this.renderProgress();
|
|
387
416
|
this.help.setContent(renderMarkdownToTerminal([
|
|
388
417
|
"AgentWeaver interactive mode",
|
|
389
418
|
"",
|
|
390
|
-
"
|
|
391
|
-
|
|
419
|
+
"Клавиши:",
|
|
420
|
+
"Up / Down выбрать flow",
|
|
421
|
+
"Enter открыть подтверждение запуска",
|
|
422
|
+
"Enter подтвердить запуск в модалке",
|
|
423
|
+
"Esc закрыть help или модалку",
|
|
424
|
+
"h / F1 открыть или закрыть help",
|
|
425
|
+
"Tab переключить pane",
|
|
426
|
+
"Ctrl+L очистить лог",
|
|
427
|
+
"q / Ctrl+C выйти",
|
|
392
428
|
"",
|
|
393
|
-
"
|
|
394
|
-
|
|
395
|
-
"Up/Down history",
|
|
396
|
-
"Ctrl+L clear log",
|
|
397
|
-
"? or F1 toggle help",
|
|
398
|
-
"Esc close help",
|
|
399
|
-
"q / Ctrl+C exit",
|
|
429
|
+
"Доступные flow:",
|
|
430
|
+
...this.options.flows.map((flow) => flow.label),
|
|
400
431
|
].join("\n")));
|
|
401
|
-
this.footer.setContent("
|
|
432
|
+
this.footer.setContent(" Up/Down: select flow | Enter: confirm run | h: help | Tab: switch pane | q: exit ");
|
|
402
433
|
}
|
|
403
434
|
updateHeader() {
|
|
435
|
+
const current = this.currentFlowId ?? this.selectedFlowId;
|
|
404
436
|
this.header.setContent(`{bold}AgentWeaver{/bold} {green-fg}${this.options.issueKey}{/green-fg}\n` +
|
|
405
|
-
`cwd: ${this.options.cwd} current: ${this.
|
|
437
|
+
`cwd: ${this.options.cwd} current: ${current}${this.busy ? " {yellow-fg}[running]{/yellow-fg}" : ""}`);
|
|
406
438
|
}
|
|
407
439
|
renderSummary() {
|
|
408
|
-
const summaryBody = this.summaryText
|
|
409
|
-
? this.summaryText
|
|
410
|
-
: "Task summary is not available yet.";
|
|
440
|
+
const summaryBody = this.summaryText || "Task summary is not available yet.";
|
|
411
441
|
this.summary.setContent(renderMarkdownToTerminal(stripAnsi(summaryBody)));
|
|
412
442
|
}
|
|
413
443
|
createAdapter() {
|
|
@@ -420,51 +450,239 @@ export class InteractiveUi {
|
|
|
420
450
|
},
|
|
421
451
|
supportsTransientStatus: false,
|
|
422
452
|
supportsPassthrough: false,
|
|
423
|
-
renderAuxiliaryOutput:
|
|
453
|
+
renderAuxiliaryOutput: true,
|
|
454
|
+
renderPanelsAsPlainText: true,
|
|
424
455
|
setExecutionState: (state) => {
|
|
425
456
|
this.currentNode = state.node;
|
|
426
457
|
this.currentExecutor = state.executor;
|
|
427
458
|
this.updateRunningPanel();
|
|
428
459
|
},
|
|
460
|
+
setFlowState: (state) => {
|
|
461
|
+
this.setFlowDisplayState(state.flowId, state.executionState);
|
|
462
|
+
},
|
|
429
463
|
};
|
|
430
464
|
}
|
|
465
|
+
activeFlowId() {
|
|
466
|
+
return this.currentFlowId ?? this.selectedFlowId;
|
|
467
|
+
}
|
|
468
|
+
progressFlowDefinition() {
|
|
469
|
+
const preferredFlowId = this.busy ? this.activeFlowId() : this.selectedFlowId;
|
|
470
|
+
return this.flowMap.get(preferredFlowId);
|
|
471
|
+
}
|
|
472
|
+
renderProgress() {
|
|
473
|
+
const flow = this.progressFlowDefinition();
|
|
474
|
+
if (!flow) {
|
|
475
|
+
this.progress.setContent("Flow structure is not available.");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const flowState = this.flowState.flowId === flow.id
|
|
479
|
+
? this.flowState.executionState
|
|
480
|
+
: this.currentFlowId === flow.id
|
|
481
|
+
? this.flowState.executionState
|
|
482
|
+
: null;
|
|
483
|
+
const lines = [flow.label, ""];
|
|
484
|
+
for (const item of this.visiblePhaseItems(flow, flowState)) {
|
|
485
|
+
if (item.kind === "group") {
|
|
486
|
+
lines.push(`${this.symbolForGroup(flow.id, item.phases, flowState)} ${item.label}`);
|
|
487
|
+
for (const phase of item.phases) {
|
|
488
|
+
const phaseState = flowState?.phases.find((candidate) => candidate.id === phase.id);
|
|
489
|
+
lines.push(` ${this.symbolForStatus(flow.id, phaseState?.status ?? "pending")} ${this.displayPhaseId(phase)}`);
|
|
490
|
+
for (const step of phase.steps) {
|
|
491
|
+
const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
|
|
492
|
+
lines.push(` ${this.symbolForStatus(flow.id, stepState?.status ?? "pending")} ${step.id}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
lines.push("");
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const phase = item.phase;
|
|
499
|
+
const phaseState = flowState?.phases.find((candidate) => candidate.id === phase.id);
|
|
500
|
+
lines.push(`${this.symbolForStatus(flow.id, phaseState?.status ?? "pending")} ${phase.id}`);
|
|
501
|
+
for (const step of phase.steps) {
|
|
502
|
+
const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
|
|
503
|
+
lines.push(` ${this.symbolForStatus(flow.id, stepState?.status ?? "pending")} ${step.id}`);
|
|
504
|
+
}
|
|
505
|
+
lines.push("");
|
|
506
|
+
}
|
|
507
|
+
if (flowState?.terminated) {
|
|
508
|
+
lines.push(`Stopped: ${flowState.terminationReason ?? "flow terminated"}`);
|
|
509
|
+
}
|
|
510
|
+
this.progress.setContent(lines.join("\n").trimEnd());
|
|
511
|
+
}
|
|
512
|
+
symbolForStatus(flowId, status) {
|
|
513
|
+
if (status === "done") {
|
|
514
|
+
return "✓";
|
|
515
|
+
}
|
|
516
|
+
if (status === "skipped") {
|
|
517
|
+
return "·";
|
|
518
|
+
}
|
|
519
|
+
if (status === "running") {
|
|
520
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
521
|
+
return this.failedFlowId === flowId && !this.busy ? "×" : (frames[this.spinnerFrame] ?? "▶");
|
|
522
|
+
}
|
|
523
|
+
return "○";
|
|
524
|
+
}
|
|
525
|
+
symbolForGroup(flowId, phases, flowState) {
|
|
526
|
+
const statuses = phases.map((phase) => flowState?.phases.find((candidate) => candidate.id === phase.id)?.status ?? "pending");
|
|
527
|
+
if (statuses.some((status) => status === "running")) {
|
|
528
|
+
return this.symbolForStatus(flowId, "running");
|
|
529
|
+
}
|
|
530
|
+
if (statuses.every((status) => status === "skipped")) {
|
|
531
|
+
return "·";
|
|
532
|
+
}
|
|
533
|
+
if (statuses.every((status) => status === "done" || status === "skipped")) {
|
|
534
|
+
return "✓";
|
|
535
|
+
}
|
|
536
|
+
return "○";
|
|
537
|
+
}
|
|
538
|
+
groupPhases(flow) {
|
|
539
|
+
const items = [];
|
|
540
|
+
let index = 0;
|
|
541
|
+
while (index < flow.phases.length) {
|
|
542
|
+
const phase = flow.phases[index];
|
|
543
|
+
if (!phase) {
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
const repeatLabel = this.repeatLabel(phase.repeatVars);
|
|
547
|
+
if (!repeatLabel) {
|
|
548
|
+
items.push({ kind: "phase", phase });
|
|
549
|
+
index += 1;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const phases = [phase];
|
|
553
|
+
let nextIndex = index + 1;
|
|
554
|
+
while (nextIndex < flow.phases.length) {
|
|
555
|
+
const candidate = flow.phases[nextIndex];
|
|
556
|
+
if (!candidate || this.repeatGroupKey(candidate.repeatVars) !== this.repeatGroupKey(phase.repeatVars)) {
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
phases.push(candidate);
|
|
560
|
+
nextIndex += 1;
|
|
561
|
+
}
|
|
562
|
+
items.push({ kind: "group", label: repeatLabel, phases, seriesKey: this.repeatSeriesKey(phases) });
|
|
563
|
+
index = nextIndex;
|
|
564
|
+
}
|
|
565
|
+
return items;
|
|
566
|
+
}
|
|
567
|
+
visiblePhaseItems(flow, flowState) {
|
|
568
|
+
const pendingSeries = new Set();
|
|
569
|
+
return this.groupPhases(flow).filter((item) => {
|
|
570
|
+
if (item.kind === "phase") {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
const hasState = item.phases.some((phase) => flowState?.phases.some((candidate) => candidate.id === phase.id));
|
|
574
|
+
if (hasState) {
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
if (pendingSeries.has(item.seriesKey)) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
pendingSeries.add(item.seriesKey);
|
|
581
|
+
return true;
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
repeatGroupKey(repeatVars) {
|
|
585
|
+
const entries = Object.entries(repeatVars).sort(([left], [right]) => left.localeCompare(right));
|
|
586
|
+
return JSON.stringify(entries);
|
|
587
|
+
}
|
|
588
|
+
repeatSeriesKey(phases) {
|
|
589
|
+
const repeatVarNames = Object.keys(phases[0]?.repeatVars ?? {}).sort();
|
|
590
|
+
const phaseNames = phases.map((phase) => this.displayPhaseId(phase));
|
|
591
|
+
return JSON.stringify({
|
|
592
|
+
repeatVarNames,
|
|
593
|
+
phaseNames,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
repeatLabel(repeatVars) {
|
|
597
|
+
const entries = Object.entries(repeatVars);
|
|
598
|
+
if (entries.length === 0) {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
if (entries.length === 1) {
|
|
602
|
+
const [key, value] = entries[0] ?? ["repeat", ""];
|
|
603
|
+
return `${key} ${value}`;
|
|
604
|
+
}
|
|
605
|
+
return entries.map(([key, value]) => `${key}=${value}`).join(", ");
|
|
606
|
+
}
|
|
607
|
+
displayPhaseId(phase) {
|
|
608
|
+
let result = phase.id;
|
|
609
|
+
for (const value of Object.values(phase.repeatVars)) {
|
|
610
|
+
const suffix = `_${String(value)}`;
|
|
611
|
+
if (result.endsWith(suffix)) {
|
|
612
|
+
result = result.slice(0, -suffix.length);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return result;
|
|
616
|
+
}
|
|
617
|
+
openConfirm() {
|
|
618
|
+
const flow = this.flowMap.get(this.selectedFlowId);
|
|
619
|
+
if (!flow) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
this.confirm.setContent(`Run flow "${flow.label}"?\n\nEnter: yes Esc: no`);
|
|
623
|
+
this.confirm.show();
|
|
624
|
+
this.confirm.setFront();
|
|
625
|
+
this.confirm.focus();
|
|
626
|
+
this.requestRender();
|
|
627
|
+
}
|
|
628
|
+
closeConfirm() {
|
|
629
|
+
this.confirm.hide();
|
|
630
|
+
this.focusPane("flows");
|
|
631
|
+
this.requestRender();
|
|
632
|
+
}
|
|
431
633
|
mount() {
|
|
432
634
|
setOutputAdapter(this.createAdapter());
|
|
433
|
-
this.focusPane("
|
|
635
|
+
this.focusPane("flows");
|
|
434
636
|
}
|
|
435
637
|
destroy() {
|
|
638
|
+
this.flushPendingLogLines();
|
|
436
639
|
if (this.spinnerTimer) {
|
|
437
640
|
clearInterval(this.spinnerTimer);
|
|
438
641
|
this.spinnerTimer = null;
|
|
439
642
|
}
|
|
643
|
+
if (this.logFlushTimer) {
|
|
644
|
+
clearTimeout(this.logFlushTimer);
|
|
645
|
+
this.logFlushTimer = null;
|
|
646
|
+
}
|
|
440
647
|
setOutputAdapter(null);
|
|
441
648
|
this.screen.destroy();
|
|
442
649
|
}
|
|
443
|
-
setBusy(busy,
|
|
650
|
+
setBusy(busy, flowId) {
|
|
444
651
|
this.busy = busy;
|
|
445
|
-
this.
|
|
652
|
+
this.currentFlowId = flowId ?? (busy ? this.currentFlowId : this.currentFlowId);
|
|
446
653
|
if (busy && this.runningStartedAt === null) {
|
|
447
654
|
this.runningStartedAt = Date.now();
|
|
448
655
|
}
|
|
449
656
|
else if (!busy && this.currentNode === null && this.currentExecutor === null) {
|
|
450
657
|
this.runningStartedAt = null;
|
|
451
658
|
}
|
|
659
|
+
if (!busy && flowId === undefined) {
|
|
660
|
+
this.currentFlowId = this.currentFlowId ?? this.selectedFlowId;
|
|
661
|
+
}
|
|
452
662
|
this.updateHeader();
|
|
453
|
-
this.header.setContent(`{bold}AgentWeaver{/bold} {green-fg}${this.options.issueKey}{/green-fg}\n` +
|
|
454
|
-
`cwd: ${this.options.cwd} current: ${this.currentCommand}${busy ? " {yellow-fg}[running]{/yellow-fg}" : ""}`);
|
|
455
663
|
this.updateRunningPanel();
|
|
456
|
-
this.
|
|
457
|
-
this.
|
|
664
|
+
this.renderProgress();
|
|
665
|
+
this.requestRender();
|
|
666
|
+
}
|
|
667
|
+
setFlowFailed(flowId) {
|
|
668
|
+
this.failedFlowId = flowId;
|
|
669
|
+
this.renderProgress();
|
|
670
|
+
this.requestRender();
|
|
671
|
+
}
|
|
672
|
+
clearFlowFailure(flowId) {
|
|
673
|
+
if (this.failedFlowId === flowId) {
|
|
674
|
+
this.failedFlowId = null;
|
|
675
|
+
}
|
|
458
676
|
}
|
|
459
677
|
setStatus(status) {
|
|
460
|
-
this.
|
|
678
|
+
this.currentFlowId = status;
|
|
461
679
|
this.updateHeader();
|
|
462
|
-
this.
|
|
680
|
+
this.requestRender();
|
|
463
681
|
}
|
|
464
682
|
setSummary(markdown) {
|
|
465
683
|
this.summaryText = markdown.trim();
|
|
466
684
|
this.renderSummary();
|
|
467
|
-
this.
|
|
685
|
+
this.requestRender();
|
|
468
686
|
}
|
|
469
687
|
appendLog(text) {
|
|
470
688
|
const normalized = text
|
|
@@ -473,15 +691,23 @@ export class InteractiveUi {
|
|
|
473
691
|
.join("\n")
|
|
474
692
|
.trimEnd();
|
|
475
693
|
if (!normalized) {
|
|
476
|
-
this.
|
|
694
|
+
this.pendingLogLines.push("");
|
|
477
695
|
}
|
|
478
696
|
else {
|
|
479
|
-
|
|
480
|
-
this.log.add(line);
|
|
481
|
-
}
|
|
697
|
+
this.pendingLogLines.push(...normalized.split("\n"));
|
|
482
698
|
}
|
|
483
|
-
this.
|
|
484
|
-
|
|
699
|
+
this.scheduleLogFlush();
|
|
700
|
+
}
|
|
701
|
+
setFlowDisplayState(flowId, executionState) {
|
|
702
|
+
this.flowState = {
|
|
703
|
+
flowId,
|
|
704
|
+
executionState,
|
|
705
|
+
};
|
|
706
|
+
if (flowId) {
|
|
707
|
+
this.currentFlowId = flowId;
|
|
708
|
+
}
|
|
709
|
+
this.renderProgress();
|
|
710
|
+
this.requestRender();
|
|
485
711
|
}
|
|
486
712
|
updateRunningPanel() {
|
|
487
713
|
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -493,7 +719,8 @@ export class InteractiveUi {
|
|
|
493
719
|
this.spinnerTimer = setInterval(() => {
|
|
494
720
|
this.spinnerFrame = (this.spinnerFrame + 1) % frames.length;
|
|
495
721
|
this.updateRunningPanel();
|
|
496
|
-
this.
|
|
722
|
+
this.renderProgress();
|
|
723
|
+
this.requestRender();
|
|
497
724
|
}, 120);
|
|
498
725
|
}
|
|
499
726
|
else if (!running && this.spinnerTimer) {
|
|
@@ -509,7 +736,7 @@ export class InteractiveUi {
|
|
|
509
736
|
const stateLine = `State: ${running ? `${spinner} running` : "idle"}`;
|
|
510
737
|
const elapsedLine = `Time: ${elapsed}`;
|
|
511
738
|
this.status.setContent([stateLine, elapsedLine, nodeLine, executorLine].join("\n"));
|
|
512
|
-
this.
|
|
739
|
+
this.requestRender();
|
|
513
740
|
}
|
|
514
741
|
formatElapsed(now) {
|
|
515
742
|
if (this.runningStartedAt === null || now === null) {
|
|
@@ -517,8 +744,38 @@ export class InteractiveUi {
|
|
|
517
744
|
}
|
|
518
745
|
const totalSeconds = Math.max(0, Math.floor((now - this.runningStartedAt) / 1000));
|
|
519
746
|
const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, "0");
|
|
520
|
-
const minutes = String(
|
|
747
|
+
const minutes = String((totalSeconds % 3600) / 60 | 0).padStart(2, "0");
|
|
521
748
|
const seconds = String(totalSeconds % 60).padStart(2, "0");
|
|
522
749
|
return `${hours}:${minutes}:${seconds}`;
|
|
523
750
|
}
|
|
751
|
+
scheduleLogFlush() {
|
|
752
|
+
if (this.logFlushTimer) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
this.logFlushTimer = setTimeout(() => {
|
|
756
|
+
this.logFlushTimer = null;
|
|
757
|
+
this.flushPendingLogLines();
|
|
758
|
+
}, 50);
|
|
759
|
+
}
|
|
760
|
+
flushPendingLogLines() {
|
|
761
|
+
if (this.pendingLogLines.length === 0) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const lines = this.pendingLogLines.splice(0, this.pendingLogLines.length);
|
|
765
|
+
for (const line of lines) {
|
|
766
|
+
this.log.add(line);
|
|
767
|
+
}
|
|
768
|
+
this.log.setScrollPerc(100);
|
|
769
|
+
this.requestRender();
|
|
770
|
+
}
|
|
771
|
+
requestRender() {
|
|
772
|
+
if (this.renderScheduled) {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
this.renderScheduled = true;
|
|
776
|
+
setImmediate(() => {
|
|
777
|
+
this.renderScheduled = false;
|
|
778
|
+
this.screen.render();
|
|
779
|
+
});
|
|
780
|
+
}
|
|
524
781
|
}
|