agentweaver 0.1.18 → 0.1.20
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 +54 -6
- package/dist/artifacts.js +9 -0
- package/dist/executors/git-commit-executor.js +24 -6
- package/dist/flow-state.js +3 -8
- package/dist/git/git-diff-parser.js +223 -0
- package/dist/git/git-service.js +562 -0
- package/dist/git/git-stage-selection.js +24 -0
- package/dist/git/git-status-parser.js +171 -0
- package/dist/git/git-types.js +1 -0
- package/dist/index.js +454 -108
- package/dist/interactive/auto-flow.js +644 -0
- package/dist/interactive/controller.js +489 -7
- package/dist/interactive/progress.js +194 -1
- package/dist/interactive/state.js +34 -0
- package/dist/interactive/web/index.js +237 -5
- package/dist/interactive/web/protocol.js +222 -1
- package/dist/interactive/web/server.js +497 -3
- package/dist/interactive/web/static/app.js +2462 -37
- package/dist/interactive/web/static/index.html +113 -11
- package/dist/interactive/web/static/styles.css +1 -1
- package/dist/interactive/web/static/styles.input.css +1383 -149
- package/dist/pipeline/auto-flow-blocks.js +307 -0
- package/dist/pipeline/auto-flow-config.js +273 -0
- package/dist/pipeline/auto-flow-identity.js +49 -0
- package/dist/pipeline/auto-flow-presets.js +52 -0
- package/dist/pipeline/auto-flow-resolver.js +830 -0
- package/dist/pipeline/auto-flow-types.js +17 -0
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/declarative-flows.js +27 -1
- package/dist/pipeline/flow-specs/auto-common-guided.json +11 -0
- package/dist/pipeline/flow-specs/auto-golang.json +12 -1
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +54 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +19 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +33 -1
- package/dist/pipeline/flow-specs/review/review-project.json +19 -1
- package/dist/pipeline/flow-specs/task-source/manual-jira-input.json +70 -0
- package/dist/pipeline/node-registry.js +9 -0
- package/dist/pipeline/nodes/codex-prompt-node.js +8 -1
- package/dist/pipeline/nodes/flow-run-node.js +5 -3
- package/dist/pipeline/nodes/git-status-node.js +2 -168
- package/dist/pipeline/nodes/manual-jira-task-input-node.js +146 -0
- package/dist/pipeline/nodes/opencode-prompt-node.js +8 -1
- package/dist/pipeline/nodes/plan-codex-node.js +8 -1
- package/dist/pipeline/spec-loader.js +14 -4
- package/dist/runtime/artifact-catalog.js +403 -0
- package/dist/runtime/settings.js +114 -0
- package/dist/scope.js +14 -4
- package/package.json +1 -1
- package/dist/pipeline/flow-specs/auto-common.json +0 -179
- package/dist/pipeline/flow-specs/auto-simple.json +0 -141
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildAutoFlowEditorViewModel } from "./auto-flow.js";
|
|
1
2
|
export function displayPhaseId(phase) {
|
|
2
3
|
let result = phase.id;
|
|
3
4
|
const values = Object.entries(phase.repeatVars)
|
|
@@ -139,7 +140,7 @@ export function visiblePhaseItems(flow, flowState) {
|
|
|
139
140
|
return true;
|
|
140
141
|
});
|
|
141
142
|
}
|
|
142
|
-
export function buildProgressViewModel(flow, flowState) {
|
|
143
|
+
export function buildProgressViewModel(flow, flowState, options = {}) {
|
|
143
144
|
if (!flow) {
|
|
144
145
|
return {
|
|
145
146
|
flow: null,
|
|
@@ -147,6 +148,9 @@ export function buildProgressViewModel(flow, flowState) {
|
|
|
147
148
|
anchorIndex: null,
|
|
148
149
|
};
|
|
149
150
|
}
|
|
151
|
+
if (flow.autoFlow) {
|
|
152
|
+
return buildAutoFlowProgressViewModel(flow, flowState, options);
|
|
153
|
+
}
|
|
150
154
|
const items = [];
|
|
151
155
|
let anchorIndex = null;
|
|
152
156
|
let sawExecutedItem = false;
|
|
@@ -243,3 +247,192 @@ export function buildProgressViewModel(flow, flowState) {
|
|
|
243
247
|
anchorIndex,
|
|
244
248
|
};
|
|
245
249
|
}
|
|
250
|
+
function phaseOrder(flow) {
|
|
251
|
+
return new Map(flow.phases.map((phase, index) => [phase.id, index]));
|
|
252
|
+
}
|
|
253
|
+
function stoppedPhaseId(flowState) {
|
|
254
|
+
const terminationReason = flowState?.terminationReason ?? "";
|
|
255
|
+
const match = /^Stopped by ([^:]+):/.exec(terminationReason);
|
|
256
|
+
return match?.[1] ?? null;
|
|
257
|
+
}
|
|
258
|
+
function mapRuntimeStatus(status) {
|
|
259
|
+
if (status === "done") {
|
|
260
|
+
return "success";
|
|
261
|
+
}
|
|
262
|
+
return status;
|
|
263
|
+
}
|
|
264
|
+
function lastRuntimeBlockId(slots, flow, flowState) {
|
|
265
|
+
const order = phaseOrder(flow);
|
|
266
|
+
let result = null;
|
|
267
|
+
for (const slot of slots) {
|
|
268
|
+
for (const block of slot.blocks) {
|
|
269
|
+
if (!block.phaseId) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const phaseState = flowState?.phases.find((phase) => phase.id === block.phaseId);
|
|
273
|
+
if (!phaseState || phaseState.status === "pending" || phaseState.status === "skipped") {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const phaseIndex = order.get(block.phaseId) ?? -1;
|
|
277
|
+
if (!result || phaseIndex >= result.phaseIndex) {
|
|
278
|
+
result = { blockId: block.blockId, phaseIndex };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return result?.blockId ?? null;
|
|
283
|
+
}
|
|
284
|
+
function runtimeStatusForBlock(block, input) {
|
|
285
|
+
if (block.status === "invalid" || block.status === "disabled" || block.status === "blocked" || block.status === "empty") {
|
|
286
|
+
return block.status;
|
|
287
|
+
}
|
|
288
|
+
if (!block.phaseId) {
|
|
289
|
+
return block.status;
|
|
290
|
+
}
|
|
291
|
+
const phaseState = input.flowState?.phases.find((phase) => phase.id === block.phaseId) ?? null;
|
|
292
|
+
const failed = input.failedFlowId === input.flow.id;
|
|
293
|
+
if (failed && (phaseState?.status === "running" || input.fallbackFailedBlockId === block.blockId)) {
|
|
294
|
+
return "failed";
|
|
295
|
+
}
|
|
296
|
+
if (input.waitingForUserInput && phaseState?.status === "running") {
|
|
297
|
+
return "waiting-user";
|
|
298
|
+
}
|
|
299
|
+
const stoppedId = input.flowState?.terminationOutcome === "stopped" ? stoppedPhaseId(input.flowState) : null;
|
|
300
|
+
if (stoppedId && stoppedId === block.phaseId) {
|
|
301
|
+
return "stopped";
|
|
302
|
+
}
|
|
303
|
+
if (phaseState) {
|
|
304
|
+
return mapRuntimeStatus(phaseState.status);
|
|
305
|
+
}
|
|
306
|
+
if (input.flowState?.terminated && stoppedId) {
|
|
307
|
+
const order = phaseOrder(input.flow);
|
|
308
|
+
const stoppedIndex = order.get(stoppedId) ?? -1;
|
|
309
|
+
const currentIndex = order.get(block.phaseId) ?? -1;
|
|
310
|
+
if (stoppedIndex >= 0 && currentIndex > stoppedIndex) {
|
|
311
|
+
return "skipped";
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return block.status;
|
|
315
|
+
}
|
|
316
|
+
function aggregateSlotStatus(slot, blockStatuses) {
|
|
317
|
+
if (slot.status === "invalid" || blockStatuses.some((status) => status === "invalid")) {
|
|
318
|
+
return "invalid";
|
|
319
|
+
}
|
|
320
|
+
if (slot.blocks.length === 0) {
|
|
321
|
+
return "empty";
|
|
322
|
+
}
|
|
323
|
+
for (const status of ["failed", "stopped", "waiting-user", "running"]) {
|
|
324
|
+
if (blockStatuses.includes(status)) {
|
|
325
|
+
return status;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (blockStatuses.every((status) => status === "disabled")) {
|
|
329
|
+
return "disabled";
|
|
330
|
+
}
|
|
331
|
+
const executableStatuses = blockStatuses.filter((status) => status !== "disabled");
|
|
332
|
+
if (executableStatuses.length > 0 && executableStatuses.every((status) => status === "success" || status === "skipped")) {
|
|
333
|
+
return executableStatuses.every((status) => status === "skipped") ? "skipped" : "success";
|
|
334
|
+
}
|
|
335
|
+
if (blockStatuses.some((status) => status === "blocked")) {
|
|
336
|
+
return "blocked";
|
|
337
|
+
}
|
|
338
|
+
return "pending";
|
|
339
|
+
}
|
|
340
|
+
function rememberProgressAnchor(items, status, state) {
|
|
341
|
+
if (status === "running" || status === "waiting-user" || status === "failed" || status === "stopped" || status === "invalid") {
|
|
342
|
+
state.anchorIndex = items.length;
|
|
343
|
+
state.sawExecutedItem = true;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (status === "done" || status === "success" || status === "skipped" || status === "disabled") {
|
|
347
|
+
state.sawExecutedItem = true;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (status === "pending" && state.sawExecutedItem && state.anchorIndex === null) {
|
|
351
|
+
state.anchorIndex = items.length;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function buildAutoFlowProgressViewModel(flow, flowState, options) {
|
|
355
|
+
if (!flow.autoFlow) {
|
|
356
|
+
return {
|
|
357
|
+
flow,
|
|
358
|
+
items: [],
|
|
359
|
+
anchorIndex: null,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const editor = buildAutoFlowEditorViewModel(flow.autoFlow, {
|
|
363
|
+
...(flow.autoFlow.diagnostics && flow.autoFlow.diagnostics.length > 0 ? { diagnostics: flow.autoFlow.diagnostics } : {}),
|
|
364
|
+
...(flow.autoFlow.lastMessage ? { lastMessage: flow.autoFlow.lastMessage } : {}),
|
|
365
|
+
});
|
|
366
|
+
const items = [];
|
|
367
|
+
const anchorState = { anchorIndex: null, sawExecutedItem: false };
|
|
368
|
+
const fallbackFailedBlockId = options.failedFlowId === flow.id
|
|
369
|
+
? lastRuntimeBlockId(editor.slots, flow, flowState)
|
|
370
|
+
: null;
|
|
371
|
+
for (const slot of editor.slots) {
|
|
372
|
+
const blockRows = slot.blocks.map((block) => ({
|
|
373
|
+
block,
|
|
374
|
+
status: runtimeStatusForBlock(block, {
|
|
375
|
+
flow,
|
|
376
|
+
flowState,
|
|
377
|
+
...(options.failedFlowId !== undefined ? { failedFlowId: options.failedFlowId } : {}),
|
|
378
|
+
...(options.waitingForUserInput !== undefined ? { waitingForUserInput: options.waitingForUserInput } : {}),
|
|
379
|
+
fallbackFailedBlockId,
|
|
380
|
+
}),
|
|
381
|
+
}));
|
|
382
|
+
const status = aggregateSlotStatus(slot, blockRows.map((row) => row.status));
|
|
383
|
+
rememberProgressAnchor(items, status, anchorState);
|
|
384
|
+
items.push({
|
|
385
|
+
kind: "slot",
|
|
386
|
+
label: slot.title,
|
|
387
|
+
depth: 0,
|
|
388
|
+
status,
|
|
389
|
+
detail: slot.reason,
|
|
390
|
+
slotId: slot.slotId,
|
|
391
|
+
});
|
|
392
|
+
for (const { block, status: blockRuntimeStatus } of blockRows) {
|
|
393
|
+
rememberProgressAnchor(items, blockRuntimeStatus, anchorState);
|
|
394
|
+
items.push({
|
|
395
|
+
kind: "block",
|
|
396
|
+
label: block.title,
|
|
397
|
+
depth: 1,
|
|
398
|
+
status: blockRuntimeStatus,
|
|
399
|
+
detail: block.reason,
|
|
400
|
+
slotId: block.slotId,
|
|
401
|
+
blockId: block.blockId,
|
|
402
|
+
locked: block.locked,
|
|
403
|
+
enabled: block.enabled,
|
|
404
|
+
});
|
|
405
|
+
for (const diagnostic of block.diagnostics) {
|
|
406
|
+
rememberProgressAnchor(items, "invalid", anchorState);
|
|
407
|
+
const detail = diagnostic.paramName ? `${diagnostic.blockId ?? "flow"}.${diagnostic.paramName}` : diagnostic.blockId;
|
|
408
|
+
items.push({
|
|
409
|
+
kind: "block",
|
|
410
|
+
label: diagnostic.message,
|
|
411
|
+
depth: 2,
|
|
412
|
+
status: "invalid",
|
|
413
|
+
...(detail ? { detail } : {}),
|
|
414
|
+
slotId: block.slotId,
|
|
415
|
+
blockId: block.blockId,
|
|
416
|
+
locked: block.locked,
|
|
417
|
+
enabled: block.enabled,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (flowState?.terminated) {
|
|
423
|
+
const terminationOutcome = flowState.terminationOutcome ?? "success";
|
|
424
|
+
const status = terminationOutcome === "stopped" ? "stopped" : "success";
|
|
425
|
+
items.push({
|
|
426
|
+
kind: "termination",
|
|
427
|
+
label: terminationOutcome === "stopped" ? "Flow stopped before completion" : "Flow completed successfully",
|
|
428
|
+
detail: `Reason: ${flowState.terminationReason ?? "flow terminated"}`,
|
|
429
|
+
depth: 0,
|
|
430
|
+
status,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
flow,
|
|
435
|
+
items,
|
|
436
|
+
anchorIndex: anchorState.anchorIndex,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
import { buildFlowTree, collectInitiallyExpandedFolderKeys, computeVisibleFlowItems, makeFlowKey } from "./tree.js";
|
|
2
|
+
export function createUnavailableGitWorkspace(message = "Git workspace has not been refreshed yet.") {
|
|
3
|
+
return {
|
|
4
|
+
available: false,
|
|
5
|
+
repositoryRoot: null,
|
|
6
|
+
branch: null,
|
|
7
|
+
detachedHead: false,
|
|
8
|
+
clean: true,
|
|
9
|
+
upstream: null,
|
|
10
|
+
ahead: 0,
|
|
11
|
+
behind: 0,
|
|
12
|
+
lastCommit: null,
|
|
13
|
+
changedFiles: [],
|
|
14
|
+
branches: [],
|
|
15
|
+
remotes: [],
|
|
16
|
+
canPush: false,
|
|
17
|
+
pushDisabledReason: "Git repository is not available.",
|
|
18
|
+
warnings: [],
|
|
19
|
+
error: message,
|
|
20
|
+
refreshedAt: null,
|
|
21
|
+
selectedPaths: [],
|
|
22
|
+
commitMessage: "",
|
|
23
|
+
operation: { status: "idle" },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
2
26
|
export function createInitialInteractiveState(options) {
|
|
3
27
|
const flowTree = buildFlowTree(options.flows);
|
|
4
28
|
const expandedFlowFolders = new Set(collectInitiallyExpandedFolderKeys(flowTree));
|
|
@@ -31,5 +55,15 @@ export function createInitialInteractiveState(options) {
|
|
|
31
55
|
summaryScrollOffset: 0,
|
|
32
56
|
logScrollOffset: 0,
|
|
33
57
|
helpScrollOffset: 0,
|
|
58
|
+
artifactExplorer: {
|
|
59
|
+
available: false,
|
|
60
|
+
open: false,
|
|
61
|
+
scopeKey: null,
|
|
62
|
+
runId: null,
|
|
63
|
+
status: "unavailable",
|
|
64
|
+
label: "Artifact Explorer",
|
|
65
|
+
message: "Artifacts are available after a Web UI workflow run completes.",
|
|
66
|
+
},
|
|
67
|
+
gitWorkspace: createUnavailableGitWorkspace(),
|
|
34
68
|
};
|
|
35
69
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import { writeSync } from "node:fs";
|
|
3
3
|
import { FlowInterruptedError } from "../../errors.js";
|
|
4
|
+
import { listArtifactCatalog } from "../../runtime/artifact-catalog.js";
|
|
5
|
+
import { createArtifactRegistry } from "../../runtime/artifact-registry.js";
|
|
6
|
+
import { loadAgentWeaverSettings, updateWebUiSettings } from "../../runtime/settings.js";
|
|
4
7
|
import { InteractiveSessionController } from "../controller.js";
|
|
5
|
-
import { startWebServer } from "./server.js";
|
|
8
|
+
import { startWebServer, } from "./server.js";
|
|
6
9
|
function actionId(action) {
|
|
7
10
|
return "actionId" in action ? action.actionId : undefined;
|
|
8
11
|
}
|
|
@@ -12,8 +15,20 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
12
15
|
let unsubscribe = null;
|
|
13
16
|
let mounted = false;
|
|
14
17
|
let shuttingDown = false;
|
|
18
|
+
let activeScopeKey = options.scopeKey;
|
|
19
|
+
let artifactRestoreGeneration = 0;
|
|
20
|
+
let webUiSettings = loadAgentWeaverSettings().webUi;
|
|
21
|
+
const artifactCatalogProvider = webOptions.getArtifactCatalog ?? ((input) => {
|
|
22
|
+
const explorerScopeKey = controller.getViewModel().artifactExplorer.scopeKey;
|
|
23
|
+
const requestedScopeKey = input?.scopeKey;
|
|
24
|
+
const scopeKey = requestedScopeKey && requestedScopeKey === explorerScopeKey ? requestedScopeKey : activeScopeKey;
|
|
25
|
+
return listArtifactCatalog({
|
|
26
|
+
scopeKey,
|
|
27
|
+
artifactRegistry: createArtifactRegistry(),
|
|
28
|
+
});
|
|
29
|
+
});
|
|
15
30
|
function snapshot() {
|
|
16
|
-
return { type: "snapshot", viewModel: controller.getViewModel() };
|
|
31
|
+
return { type: "snapshot", viewModel: controller.getViewModel(), settings: webUiSettings };
|
|
17
32
|
}
|
|
18
33
|
function sendError(client, message, id) {
|
|
19
34
|
const event = { type: "error", message, ...(id ? { actionId: id } : {}) };
|
|
@@ -24,7 +39,119 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
24
39
|
server?.broadcast(event);
|
|
25
40
|
}
|
|
26
41
|
}
|
|
42
|
+
function uniqueStrings(values) {
|
|
43
|
+
return values.filter((value, index, allValues) => (typeof value === "string" && value.length > 0 && allValues.indexOf(value) === index));
|
|
44
|
+
}
|
|
45
|
+
function collectPublishedArtifactRunIds(executionState) {
|
|
46
|
+
const runIds = [];
|
|
47
|
+
for (const phase of executionState?.phases ?? []) {
|
|
48
|
+
for (const step of phase.steps) {
|
|
49
|
+
for (const artifact of step.publishedArtifacts ?? []) {
|
|
50
|
+
const runId = artifact.manifest?.run_id;
|
|
51
|
+
if (runId) {
|
|
52
|
+
runIds.push(runId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return uniqueStrings(runIds);
|
|
58
|
+
}
|
|
59
|
+
function candidateRunIds() {
|
|
60
|
+
const executionState = controller.getCurrentFlowExecutionState();
|
|
61
|
+
return uniqueStrings([
|
|
62
|
+
executionState?.runId ?? null,
|
|
63
|
+
executionState?.publicationRunId ?? null,
|
|
64
|
+
...collectPublishedArtifactRunIds(executionState),
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
async function resolveArtifactExplorerRunMetadata(scopeKey) {
|
|
68
|
+
const candidates = candidateRunIds();
|
|
69
|
+
const preferredRunId = candidates[0] ?? null;
|
|
70
|
+
try {
|
|
71
|
+
const catalog = await artifactCatalogProvider({
|
|
72
|
+
scopeKey,
|
|
73
|
+
});
|
|
74
|
+
if (!catalog || catalog.scopeKey !== scopeKey) {
|
|
75
|
+
return { runId: preferredRunId };
|
|
76
|
+
}
|
|
77
|
+
const markdownArtifactCount = catalog.items.filter((item) => item.scopeKey === scopeKey && item.kind === "markdown").length;
|
|
78
|
+
if (candidates.length === 0) {
|
|
79
|
+
return {
|
|
80
|
+
runId: null,
|
|
81
|
+
artifactCount: markdownArtifactCount,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const matchingRunIds = candidates.filter((candidate) => (catalog.items.some((item) => item.scopeKey === scopeKey && item.runId === candidate && item.kind === "markdown")));
|
|
85
|
+
if (matchingRunIds.length > 0) {
|
|
86
|
+
return {
|
|
87
|
+
runId: matchingRunIds[0] ?? preferredRunId,
|
|
88
|
+
...(matchingRunIds.length > 1 ? { runIds: matchingRunIds } : {}),
|
|
89
|
+
artifactCount: markdownArtifactCount,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (markdownArtifactCount > 0) {
|
|
93
|
+
return {
|
|
94
|
+
runId: null,
|
|
95
|
+
artifactCount: markdownArtifactCount,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
runId: preferredRunId,
|
|
100
|
+
artifactCount: markdownArtifactCount,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return { runId: preferredRunId };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function markArtifactExplorerForCompletedRun(status) {
|
|
108
|
+
artifactRestoreGeneration += 1;
|
|
109
|
+
const scopeKey = activeScopeKey;
|
|
110
|
+
const { runId, runIds, artifactCount } = await resolveArtifactExplorerRunMetadata(scopeKey);
|
|
111
|
+
controller.setArtifactExplorerAvailability({
|
|
112
|
+
scopeKey,
|
|
113
|
+
runId,
|
|
114
|
+
...(runIds ? { runIds } : {}),
|
|
115
|
+
status,
|
|
116
|
+
...(artifactCount !== undefined ? { artifactCount } : {}),
|
|
117
|
+
open: !controller.hasActiveInput(),
|
|
118
|
+
});
|
|
119
|
+
await controller.refreshGitWorkspace();
|
|
120
|
+
}
|
|
121
|
+
async function restoreArtifactExplorerFromScope(scopeKey) {
|
|
122
|
+
const generation = ++artifactRestoreGeneration;
|
|
123
|
+
try {
|
|
124
|
+
const catalog = await artifactCatalogProvider({ scopeKey });
|
|
125
|
+
if (generation !== artifactRestoreGeneration || shuttingDown || activeScopeKey !== scopeKey) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!catalog || catalog.scopeKey !== scopeKey) {
|
|
129
|
+
controller.setArtifactExplorerUnavailable();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const artifactCount = catalog.items.filter((item) => item.scopeKey === scopeKey && item.kind === "markdown").length;
|
|
133
|
+
if (artifactCount === 0) {
|
|
134
|
+
controller.setArtifactExplorerUnavailable("No markdown artifacts were found for the current scope.");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
controller.setArtifactExplorerAvailability({
|
|
138
|
+
scopeKey,
|
|
139
|
+
runId: null,
|
|
140
|
+
status: "completed",
|
|
141
|
+
artifactCount,
|
|
142
|
+
open: false,
|
|
143
|
+
label: "Artifacts available",
|
|
144
|
+
message: "Markdown artifacts from this scope are available for review.",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
if (generation === artifactRestoreGeneration && !shuttingDown && activeScopeKey === scopeKey) {
|
|
149
|
+
controller.setArtifactExplorerUnavailable("Artifact Explorer could not inspect the current scope.");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
27
153
|
async function dispatch(action, client) {
|
|
154
|
+
let acceptedRunConfirmation = false;
|
|
28
155
|
try {
|
|
29
156
|
if (action.type === "flow.select") {
|
|
30
157
|
if (action.key) {
|
|
@@ -48,10 +175,16 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
48
175
|
return;
|
|
49
176
|
}
|
|
50
177
|
if (action.type === "confirm.accept") {
|
|
178
|
+
const confirmation = controller.getViewModel().confirmation;
|
|
179
|
+
const acceptedAction = action.action ?? confirmation?.selectedAction;
|
|
180
|
+
acceptedRunConfirmation = confirmation?.kind === "run" && acceptedAction !== "cancel";
|
|
51
181
|
if (action.action) {
|
|
52
182
|
controller.selectConfirmAction(action.action);
|
|
53
183
|
}
|
|
54
184
|
await controller.acceptConfirmation();
|
|
185
|
+
if (acceptedRunConfirmation) {
|
|
186
|
+
await markArtifactExplorerForCompletedRun("completed");
|
|
187
|
+
}
|
|
55
188
|
return;
|
|
56
189
|
}
|
|
57
190
|
if (action.type === "confirm.cancel") {
|
|
@@ -86,6 +219,91 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
86
219
|
controller.clearLog();
|
|
87
220
|
return;
|
|
88
221
|
}
|
|
222
|
+
if (action.type === "artifactExplorer.open") {
|
|
223
|
+
controller.openArtifactExplorer();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (action.type === "artifactExplorer.close") {
|
|
227
|
+
controller.closeArtifactExplorer();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (action.type === "autoFlow.selectPreset") {
|
|
231
|
+
controller.selectAutoFlowPreset(action.preset);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (action.type === "autoFlow.loadConfig") {
|
|
235
|
+
controller.loadAutoFlowConfig(action.name, action.flowId);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (action.type === "autoFlow.save") {
|
|
239
|
+
controller.saveAutoFlowConfig(action.flowId, action.name, action.location);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (action.type === "autoFlow.reset") {
|
|
243
|
+
controller.resetAutoFlowConfig(action.flowId);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (action.type === "autoFlow.toggleBlock") {
|
|
247
|
+
controller.toggleAutoFlowBlock(action.flowId, action.blockId, action.enabled, action.slotId);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (action.type === "autoFlow.updateParam") {
|
|
251
|
+
controller.updateAutoFlowParameter(action.flowId, action.blockId, action.paramName, action.value, action.slotId);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (action.type === "autoFlow.insertBlock") {
|
|
255
|
+
controller.insertAutoFlowBlock(action.flowId, action.slotId, action.blockId);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (action.type === "autoFlow.removeBlock") {
|
|
259
|
+
controller.removeAutoFlowBlock(action.flowId, action.slotId, action.blockId);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (action.type === "git.refresh") {
|
|
263
|
+
await controller.refreshGitWorkspace();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (action.type === "git.createBranch") {
|
|
267
|
+
await controller.createGitBranch(action.branchName);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (action.type === "git.checkout") {
|
|
271
|
+
await controller.checkoutGitBranch(action.branchName);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (action.type === "git.fetch") {
|
|
275
|
+
await controller.fetchGitWorkspace();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (action.type === "git.pullFfOnly") {
|
|
279
|
+
await controller.pullGitWorkspaceFfOnly();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (action.type === "git.stage") {
|
|
283
|
+
await controller.stageGitPaths(action.paths);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (action.type === "git.unstage") {
|
|
287
|
+
await controller.unstageGitPaths(action.paths);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (action.type === "git.updateCommitMessage") {
|
|
291
|
+
controller.updateGitCommitMessage(action.message);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (action.type === "git.commit") {
|
|
295
|
+
await controller.commitGitChanges(action.paths, action.message);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (action.type === "git.push") {
|
|
299
|
+
await controller.pushGitWorkspace();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (action.type === "settings.update") {
|
|
303
|
+
webUiSettings = updateWebUiSettings(action.settings);
|
|
304
|
+
server?.broadcast(snapshot());
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
89
307
|
if (action.type === "help.toggle") {
|
|
90
308
|
controller.showHelp(action.visible ?? !controller.getViewModel().helpVisible);
|
|
91
309
|
return;
|
|
@@ -93,6 +311,9 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
93
311
|
controller.scrollPane(action.pane, { ...(action.delta !== undefined ? { delta: action.delta } : {}), ...(action.offset !== undefined ? { offset: action.offset } : {}) });
|
|
94
312
|
}
|
|
95
313
|
catch (error) {
|
|
314
|
+
if (acceptedRunConfirmation) {
|
|
315
|
+
await markArtifactExplorerForCompletedRun("failed");
|
|
316
|
+
}
|
|
96
317
|
const message = error.message;
|
|
97
318
|
controller.appendLog(`Web action failed: ${message}`);
|
|
98
319
|
sendError(client, message, actionId(action));
|
|
@@ -105,6 +326,7 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
105
326
|
}
|
|
106
327
|
mounted = true;
|
|
107
328
|
controller.mount();
|
|
329
|
+
void restoreArtifactExplorerFromScope(activeScopeKey);
|
|
108
330
|
unsubscribe = controller.subscribe((event) => {
|
|
109
331
|
if (event.type === "log") {
|
|
110
332
|
server?.broadcast({ type: "log.append", appendedLines: event.appendedLines });
|
|
@@ -112,7 +334,9 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
112
334
|
}
|
|
113
335
|
server?.broadcast(snapshot());
|
|
114
336
|
});
|
|
115
|
-
void
|
|
337
|
+
void controller.refreshGitWorkspace().catch((error) => {
|
|
338
|
+
controller.appendLog(`Git workspace refresh failed: ${error.message}`);
|
|
339
|
+
}).then(() => startWebServer({
|
|
116
340
|
...(webOptions.noOpen !== undefined ? { noOpen: webOptions.noOpen } : {}),
|
|
117
341
|
...(webOptions.host !== undefined ? { host: webOptions.host } : {}),
|
|
118
342
|
...(webOptions.auth !== undefined ? { auth: webOptions.auth } : {}),
|
|
@@ -121,6 +345,9 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
121
345
|
controller.appendLog(message);
|
|
122
346
|
},
|
|
123
347
|
...(webOptions.openBrowser ? { openBrowser: webOptions.openBrowser } : {}),
|
|
348
|
+
getArtifactCatalog: (input) => artifactCatalogProvider(input),
|
|
349
|
+
gitService: controller.getGitService(),
|
|
350
|
+
getGitWorkspaceSnapshot: () => controller.getGitWorkspaceSnapshot(),
|
|
124
351
|
onClientAction: (action, client) => {
|
|
125
352
|
void dispatch(action, client);
|
|
126
353
|
},
|
|
@@ -130,7 +357,7 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
130
357
|
onExitRequested: () => {
|
|
131
358
|
options.onExit();
|
|
132
359
|
},
|
|
133
|
-
}).then((started) => {
|
|
360
|
+
})).then((started) => {
|
|
134
361
|
if (shuttingDown) {
|
|
135
362
|
void started.close().catch((error) => {
|
|
136
363
|
process.stderr.write(`Failed to close Web UI server: ${error.message}\n`);
|
|
@@ -166,7 +393,12 @@ export function createWebInteractiveSession(options, webOptions = {}) {
|
|
|
166
393
|
requestUserInput: (form) => controller.requestUserInput(form),
|
|
167
394
|
setSummary: (markdown) => controller.setSummary(markdown),
|
|
168
395
|
clearSummary: () => controller.clearSummary(),
|
|
169
|
-
setScope: (scopeKey, jiraIssueKey, gitBranchName) =>
|
|
396
|
+
setScope: (scopeKey, jiraIssueKey, gitBranchName) => {
|
|
397
|
+
activeScopeKey = scopeKey;
|
|
398
|
+
controller.setScope(scopeKey, jiraIssueKey, gitBranchName);
|
|
399
|
+
controller.setArtifactExplorerUnavailable();
|
|
400
|
+
void restoreArtifactExplorerFromScope(scopeKey);
|
|
401
|
+
},
|
|
170
402
|
appendLog: (text) => controller.appendLog(text),
|
|
171
403
|
setFlowFailed: (flowId) => controller.setFlowFailed(flowId),
|
|
172
404
|
interruptActiveForm: (message = "Flow interrupted by user.") => {
|