agentweaver 0.1.2 → 0.1.4
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 +58 -23
- package/dist/artifacts.js +58 -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/executors/verify-build-executor.js +110 -9
- package/dist/index.js +466 -452
- package/dist/interactive-ui.js +538 -194
- package/dist/jira.js +3 -1
- package/dist/pipeline/auto-flow.js +9 -0
- package/dist/pipeline/checks.js +5 -0
- package/dist/pipeline/context.js +2 -0
- package/dist/pipeline/declarative-flow-runner.js +262 -0
- package/dist/pipeline/declarative-flows.js +24 -0
- package/dist/pipeline/flow-specs/auto.json +485 -0
- package/dist/pipeline/flow-specs/bug-analyze.json +140 -0
- package/dist/pipeline/flow-specs/bug-fix.json +44 -0
- package/dist/pipeline/flow-specs/implement.json +47 -0
- package/dist/pipeline/flow-specs/mr-description.json +61 -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/run-linter-loop.json +149 -0
- package/dist/pipeline/flow-specs/run-tests-loop.json +149 -0
- package/dist/pipeline/flow-specs/task-describe.json +61 -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 +74 -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/flow-run-node.js +40 -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/nodes/verify-build-node.js +1 -0
- package/dist/pipeline/prompt-registry.js +27 -0
- package/dist/pipeline/prompt-runtime.js +18 -0
- package/dist/pipeline/registry.js +0 -2
- package/dist/pipeline/spec-compiler.js +213 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-types.js +1 -0
- package/dist/pipeline/spec-validator.js +302 -0
- package/dist/pipeline/value-resolver.js +217 -0
- package/dist/prompts.js +22 -3
- package/dist/runtime/process-runner.js +24 -23
- package/dist/structured-artifacts.js +178 -0
- 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,326 @@ 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
|
+
const visiblePhases = item.phases.filter((phase) => this.shouldDisplayPhase(flow, flowState, phase));
|
|
487
|
+
if (visiblePhases.length === 0) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
lines.push(`${this.symbolForGroup(flow.id, flow, visiblePhases, flowState)} ${item.label}`);
|
|
491
|
+
for (const phase of visiblePhases) {
|
|
492
|
+
const phaseState = flowState?.phases.find((candidate) => candidate.id === phase.id);
|
|
493
|
+
const phaseStatus = this.displayStatusForPhase(flowState, flow, phase, phaseState?.status ?? null);
|
|
494
|
+
lines.push(` ${this.symbolForStatus(flow.id, phaseStatus)} ${this.displayPhaseId(phase)}`);
|
|
495
|
+
for (const step of phase.steps) {
|
|
496
|
+
const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
|
|
497
|
+
const stepStatus = this.displayStatusForStep(flowState, flow, phase, stepState?.status ?? null);
|
|
498
|
+
lines.push(` ${this.symbolForStatus(flow.id, stepStatus)} ${step.id}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
lines.push("");
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
const phase = item.phase;
|
|
505
|
+
if (!this.shouldDisplayPhase(flow, flowState, phase)) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const phaseState = flowState?.phases.find((candidate) => candidate.id === phase.id);
|
|
509
|
+
const phaseStatus = this.displayStatusForPhase(flowState, flow, phase, phaseState?.status ?? null);
|
|
510
|
+
lines.push(`${this.symbolForStatus(flow.id, phaseStatus)} ${this.displayPhaseId(phase)}`);
|
|
511
|
+
for (const step of phase.steps) {
|
|
512
|
+
const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
|
|
513
|
+
const stepStatus = this.displayStatusForStep(flowState, flow, phase, stepState?.status ?? null);
|
|
514
|
+
lines.push(` ${this.symbolForStatus(flow.id, stepStatus)} ${step.id}`);
|
|
515
|
+
}
|
|
516
|
+
lines.push("");
|
|
517
|
+
}
|
|
518
|
+
if (flowState?.terminated) {
|
|
519
|
+
lines.push(`✓ Flow completed successfully`);
|
|
520
|
+
lines.push(`Reason: ${flowState.terminationReason ?? "flow terminated"}`);
|
|
521
|
+
}
|
|
522
|
+
this.progress.setContent(lines.join("\n").trimEnd());
|
|
523
|
+
}
|
|
524
|
+
displayStatusForPhase(flowState, flow, phase, actualStatus) {
|
|
525
|
+
if (actualStatus) {
|
|
526
|
+
return actualStatus;
|
|
527
|
+
}
|
|
528
|
+
if (!flowState?.terminated) {
|
|
529
|
+
return "pending";
|
|
530
|
+
}
|
|
531
|
+
return this.isAfterTermination(flowState, flow, phase) ? "skipped" : "pending";
|
|
532
|
+
}
|
|
533
|
+
displayStatusForStep(flowState, flow, phase, actualStatus) {
|
|
534
|
+
if (actualStatus) {
|
|
535
|
+
return actualStatus;
|
|
536
|
+
}
|
|
537
|
+
if (!flowState?.terminated) {
|
|
538
|
+
return "pending";
|
|
539
|
+
}
|
|
540
|
+
return this.isAfterTermination(flowState, flow, phase) ? "skipped" : "pending";
|
|
541
|
+
}
|
|
542
|
+
symbolForStatus(flowId, status) {
|
|
543
|
+
if (status === "done") {
|
|
544
|
+
return "✓";
|
|
545
|
+
}
|
|
546
|
+
if (status === "skipped") {
|
|
547
|
+
return "·";
|
|
548
|
+
}
|
|
549
|
+
if (status === "running") {
|
|
550
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
551
|
+
return this.failedFlowId === flowId && !this.busy ? "×" : (frames[this.spinnerFrame] ?? "▶");
|
|
552
|
+
}
|
|
553
|
+
return "○";
|
|
554
|
+
}
|
|
555
|
+
symbolForGroup(flowId, flow, phases, flowState) {
|
|
556
|
+
const statuses = phases.map((phase) => this.displayStatusForPhase(flowState, flow, phase, flowState?.phases.find((candidate) => candidate.id === phase.id)?.status ?? null));
|
|
557
|
+
if (statuses.some((status) => status === "running")) {
|
|
558
|
+
return this.symbolForStatus(flowId, "running");
|
|
559
|
+
}
|
|
560
|
+
if (statuses.every((status) => status === "skipped")) {
|
|
561
|
+
return "·";
|
|
562
|
+
}
|
|
563
|
+
if (statuses.every((status) => status === "done" || status === "skipped")) {
|
|
564
|
+
return "✓";
|
|
565
|
+
}
|
|
566
|
+
return "○";
|
|
567
|
+
}
|
|
568
|
+
groupPhases(flow) {
|
|
569
|
+
const items = [];
|
|
570
|
+
let index = 0;
|
|
571
|
+
while (index < flow.phases.length) {
|
|
572
|
+
const phase = flow.phases[index];
|
|
573
|
+
if (!phase) {
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
const repeatLabel = this.repeatLabel(phase.repeatVars);
|
|
577
|
+
if (!repeatLabel) {
|
|
578
|
+
items.push({ kind: "phase", phase });
|
|
579
|
+
index += 1;
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
const phases = [phase];
|
|
583
|
+
let nextIndex = index + 1;
|
|
584
|
+
while (nextIndex < flow.phases.length) {
|
|
585
|
+
const candidate = flow.phases[nextIndex];
|
|
586
|
+
if (!candidate || this.repeatGroupKey(candidate.repeatVars) !== this.repeatGroupKey(phase.repeatVars)) {
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
phases.push(candidate);
|
|
590
|
+
nextIndex += 1;
|
|
591
|
+
}
|
|
592
|
+
items.push({ kind: "group", label: repeatLabel, phases, seriesKey: this.repeatSeriesKey(phases) });
|
|
593
|
+
index = nextIndex;
|
|
594
|
+
}
|
|
595
|
+
return items;
|
|
596
|
+
}
|
|
597
|
+
visiblePhaseItems(flow, flowState) {
|
|
598
|
+
const pendingSeries = new Set();
|
|
599
|
+
return this.groupPhases(flow).filter((item) => {
|
|
600
|
+
if (item.kind === "phase") {
|
|
601
|
+
return this.shouldDisplayPhase(flow, flowState, item.phase);
|
|
602
|
+
}
|
|
603
|
+
const visiblePhases = item.phases.filter((phase) => this.shouldDisplayPhase(flow, flowState, phase));
|
|
604
|
+
const hasState = visiblePhases.some((phase) => flowState?.phases.some((candidate) => candidate.id === phase.id));
|
|
605
|
+
if (visiblePhases.length === 0) {
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
if (hasState) {
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
if (pendingSeries.has(item.seriesKey)) {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
pendingSeries.add(item.seriesKey);
|
|
615
|
+
return true;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
repeatGroupKey(repeatVars) {
|
|
619
|
+
const entries = Object.entries(repeatVars).sort(([left], [right]) => left.localeCompare(right));
|
|
620
|
+
return JSON.stringify(entries);
|
|
621
|
+
}
|
|
622
|
+
shouldDisplayPhase(flow, flowState, phase) {
|
|
623
|
+
const phaseState = flowState?.phases.find((candidate) => candidate.id === phase.id) ?? null;
|
|
624
|
+
if (!flowState) {
|
|
625
|
+
if (Object.keys(phase.repeatVars).length > 0) {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
return !this.hasPreviousRepeatPhase(flow, phase);
|
|
629
|
+
}
|
|
630
|
+
if (Object.keys(phase.repeatVars).length === 0) {
|
|
631
|
+
if (!phaseState) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
if (phaseState?.status === "skipped" && flowState.terminated && this.isAfterTermination(flowState, flow, phase)) {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
if (!phaseState) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
if (phaseState.status === "skipped" && flowState.terminated && this.isAfterTermination(flowState, flow, phase)) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
647
|
+
hasPreviousRepeatPhase(flow, phase) {
|
|
648
|
+
for (const candidate of flow.phases) {
|
|
649
|
+
if (candidate.id === phase.id) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
if (Object.keys(candidate.repeatVars).length > 0) {
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
repeatSeriesKey(phases) {
|
|
659
|
+
const repeatVarNames = Object.keys(phases[0]?.repeatVars ?? {}).sort();
|
|
660
|
+
const phaseNames = phases.map((phase) => this.displayPhaseId(phase));
|
|
661
|
+
return JSON.stringify({
|
|
662
|
+
repeatVarNames,
|
|
663
|
+
phaseNames,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
repeatLabel(repeatVars) {
|
|
667
|
+
const entries = Object.entries(repeatVars).filter(([key]) => !key.endsWith("_minus_one"));
|
|
668
|
+
if (entries.length === 0) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
if (entries.length === 1) {
|
|
672
|
+
const [key, value] = entries[0] ?? ["repeat", ""];
|
|
673
|
+
return `${key} ${value}`;
|
|
674
|
+
}
|
|
675
|
+
return entries.map(([key, value]) => `${key}=${value}`).join(", ");
|
|
676
|
+
}
|
|
677
|
+
displayPhaseId(phase) {
|
|
678
|
+
let result = phase.id;
|
|
679
|
+
const values = Object.entries(phase.repeatVars)
|
|
680
|
+
.filter(([key]) => !key.endsWith("_minus_one"))
|
|
681
|
+
.map(([, value]) => value);
|
|
682
|
+
for (const value of values) {
|
|
683
|
+
const suffix = `_${String(value)}`;
|
|
684
|
+
if (result.endsWith(suffix)) {
|
|
685
|
+
result = result.slice(0, -suffix.length);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
isAfterTermination(flowState, flow, phase) {
|
|
691
|
+
const terminationReason = flowState.terminationReason ?? "";
|
|
692
|
+
const match = /^Stopped by ([^:]+):/.exec(terminationReason);
|
|
693
|
+
if (!match) {
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
const stoppedPhaseId = match[1];
|
|
697
|
+
const stoppedIndex = flow.phases.findIndex((candidate) => candidate.id === stoppedPhaseId);
|
|
698
|
+
const currentIndex = flow.phases.findIndex((candidate) => candidate.id === phase.id);
|
|
699
|
+
if (stoppedIndex < 0 || currentIndex < 0) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
return currentIndex > stoppedIndex;
|
|
703
|
+
}
|
|
704
|
+
openConfirm() {
|
|
705
|
+
const flow = this.flowMap.get(this.selectedFlowId);
|
|
706
|
+
if (!flow) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
this.confirm.setContent(`Run flow "${flow.label}"?\n\nEnter: yes Esc: no`);
|
|
710
|
+
this.confirm.show();
|
|
711
|
+
this.confirm.setFront();
|
|
712
|
+
this.confirm.focus();
|
|
713
|
+
this.requestRender();
|
|
714
|
+
}
|
|
715
|
+
closeConfirm() {
|
|
716
|
+
this.confirm.hide();
|
|
717
|
+
this.focusPane("flows");
|
|
718
|
+
this.requestRender();
|
|
719
|
+
}
|
|
431
720
|
mount() {
|
|
432
721
|
setOutputAdapter(this.createAdapter());
|
|
433
|
-
this.focusPane("
|
|
722
|
+
this.focusPane("flows");
|
|
434
723
|
}
|
|
435
724
|
destroy() {
|
|
725
|
+
this.flushPendingLogLines();
|
|
436
726
|
if (this.spinnerTimer) {
|
|
437
727
|
clearInterval(this.spinnerTimer);
|
|
438
728
|
this.spinnerTimer = null;
|
|
439
729
|
}
|
|
730
|
+
if (this.logFlushTimer) {
|
|
731
|
+
clearTimeout(this.logFlushTimer);
|
|
732
|
+
this.logFlushTimer = null;
|
|
733
|
+
}
|
|
440
734
|
setOutputAdapter(null);
|
|
441
735
|
this.screen.destroy();
|
|
442
736
|
}
|
|
443
|
-
setBusy(busy,
|
|
737
|
+
setBusy(busy, flowId) {
|
|
444
738
|
this.busy = busy;
|
|
445
|
-
this.
|
|
739
|
+
this.currentFlowId = flowId ?? (busy ? this.currentFlowId : this.currentFlowId);
|
|
446
740
|
if (busy && this.runningStartedAt === null) {
|
|
447
741
|
this.runningStartedAt = Date.now();
|
|
448
742
|
}
|
|
449
743
|
else if (!busy && this.currentNode === null && this.currentExecutor === null) {
|
|
450
744
|
this.runningStartedAt = null;
|
|
451
745
|
}
|
|
746
|
+
if (!busy && flowId === undefined) {
|
|
747
|
+
this.currentFlowId = this.currentFlowId ?? this.selectedFlowId;
|
|
748
|
+
}
|
|
452
749
|
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
750
|
this.updateRunningPanel();
|
|
456
|
-
this.
|
|
457
|
-
this.
|
|
751
|
+
this.renderProgress();
|
|
752
|
+
this.requestRender();
|
|
753
|
+
}
|
|
754
|
+
setFlowFailed(flowId) {
|
|
755
|
+
this.failedFlowId = flowId;
|
|
756
|
+
this.renderProgress();
|
|
757
|
+
this.requestRender();
|
|
758
|
+
}
|
|
759
|
+
clearFlowFailure(flowId) {
|
|
760
|
+
if (this.failedFlowId === flowId) {
|
|
761
|
+
this.failedFlowId = null;
|
|
762
|
+
}
|
|
458
763
|
}
|
|
459
764
|
setStatus(status) {
|
|
460
|
-
this.
|
|
765
|
+
this.currentFlowId = status;
|
|
461
766
|
this.updateHeader();
|
|
462
|
-
this.
|
|
767
|
+
this.requestRender();
|
|
463
768
|
}
|
|
464
769
|
setSummary(markdown) {
|
|
465
770
|
this.summaryText = markdown.trim();
|
|
466
771
|
this.renderSummary();
|
|
467
|
-
this.
|
|
772
|
+
this.requestRender();
|
|
468
773
|
}
|
|
469
774
|
appendLog(text) {
|
|
470
775
|
const normalized = text
|
|
@@ -473,15 +778,23 @@ export class InteractiveUi {
|
|
|
473
778
|
.join("\n")
|
|
474
779
|
.trimEnd();
|
|
475
780
|
if (!normalized) {
|
|
476
|
-
this.
|
|
781
|
+
this.pendingLogLines.push("");
|
|
477
782
|
}
|
|
478
783
|
else {
|
|
479
|
-
|
|
480
|
-
this.log.add(line);
|
|
481
|
-
}
|
|
784
|
+
this.pendingLogLines.push(...normalized.split("\n"));
|
|
482
785
|
}
|
|
483
|
-
this.
|
|
484
|
-
|
|
786
|
+
this.scheduleLogFlush();
|
|
787
|
+
}
|
|
788
|
+
setFlowDisplayState(flowId, executionState) {
|
|
789
|
+
this.flowState = {
|
|
790
|
+
flowId,
|
|
791
|
+
executionState,
|
|
792
|
+
};
|
|
793
|
+
if (flowId) {
|
|
794
|
+
this.currentFlowId = flowId;
|
|
795
|
+
}
|
|
796
|
+
this.renderProgress();
|
|
797
|
+
this.requestRender();
|
|
485
798
|
}
|
|
486
799
|
updateRunningPanel() {
|
|
487
800
|
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -493,7 +806,8 @@ export class InteractiveUi {
|
|
|
493
806
|
this.spinnerTimer = setInterval(() => {
|
|
494
807
|
this.spinnerFrame = (this.spinnerFrame + 1) % frames.length;
|
|
495
808
|
this.updateRunningPanel();
|
|
496
|
-
this.
|
|
809
|
+
this.renderProgress();
|
|
810
|
+
this.requestRender();
|
|
497
811
|
}, 120);
|
|
498
812
|
}
|
|
499
813
|
else if (!running && this.spinnerTimer) {
|
|
@@ -509,7 +823,7 @@ export class InteractiveUi {
|
|
|
509
823
|
const stateLine = `State: ${running ? `${spinner} running` : "idle"}`;
|
|
510
824
|
const elapsedLine = `Time: ${elapsed}`;
|
|
511
825
|
this.status.setContent([stateLine, elapsedLine, nodeLine, executorLine].join("\n"));
|
|
512
|
-
this.
|
|
826
|
+
this.requestRender();
|
|
513
827
|
}
|
|
514
828
|
formatElapsed(now) {
|
|
515
829
|
if (this.runningStartedAt === null || now === null) {
|
|
@@ -517,8 +831,38 @@ export class InteractiveUi {
|
|
|
517
831
|
}
|
|
518
832
|
const totalSeconds = Math.max(0, Math.floor((now - this.runningStartedAt) / 1000));
|
|
519
833
|
const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, "0");
|
|
520
|
-
const minutes = String(
|
|
834
|
+
const minutes = String((totalSeconds % 3600) / 60 | 0).padStart(2, "0");
|
|
521
835
|
const seconds = String(totalSeconds % 60).padStart(2, "0");
|
|
522
836
|
return `${hours}:${minutes}:${seconds}`;
|
|
523
837
|
}
|
|
838
|
+
scheduleLogFlush() {
|
|
839
|
+
if (this.logFlushTimer) {
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
this.logFlushTimer = setTimeout(() => {
|
|
843
|
+
this.logFlushTimer = null;
|
|
844
|
+
this.flushPendingLogLines();
|
|
845
|
+
}, 50);
|
|
846
|
+
}
|
|
847
|
+
flushPendingLogLines() {
|
|
848
|
+
if (this.pendingLogLines.length === 0) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
const lines = this.pendingLogLines.splice(0, this.pendingLogLines.length);
|
|
852
|
+
for (const line of lines) {
|
|
853
|
+
this.log.add(line);
|
|
854
|
+
}
|
|
855
|
+
this.log.setScrollPerc(100);
|
|
856
|
+
this.requestRender();
|
|
857
|
+
}
|
|
858
|
+
requestRender() {
|
|
859
|
+
if (this.renderScheduled) {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
this.renderScheduled = true;
|
|
863
|
+
setImmediate(() => {
|
|
864
|
+
this.renderScheduled = false;
|
|
865
|
+
this.screen.render();
|
|
866
|
+
});
|
|
867
|
+
}
|
|
524
868
|
}
|