agentweaver 0.1.19 → 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.
Files changed (50) hide show
  1. package/README.md +47 -7
  2. package/dist/artifacts.js +9 -0
  3. package/dist/executors/git-commit-executor.js +24 -6
  4. package/dist/flow-state.js +3 -8
  5. package/dist/git/git-diff-parser.js +223 -0
  6. package/dist/git/git-service.js +562 -0
  7. package/dist/git/git-stage-selection.js +24 -0
  8. package/dist/git/git-status-parser.js +171 -0
  9. package/dist/git/git-types.js +1 -0
  10. package/dist/index.js +450 -108
  11. package/dist/interactive/auto-flow.js +644 -0
  12. package/dist/interactive/controller.js +417 -9
  13. package/dist/interactive/progress.js +194 -1
  14. package/dist/interactive/state.js +25 -0
  15. package/dist/interactive/web/index.js +97 -12
  16. package/dist/interactive/web/protocol.js +216 -1
  17. package/dist/interactive/web/server.js +72 -14
  18. package/dist/interactive/web/static/app.js +1603 -49
  19. package/dist/interactive/web/static/index.html +76 -11
  20. package/dist/interactive/web/static/styles.css +1 -1
  21. package/dist/interactive/web/static/styles.input.css +901 -47
  22. package/dist/pipeline/auto-flow-blocks.js +307 -0
  23. package/dist/pipeline/auto-flow-config.js +273 -0
  24. package/dist/pipeline/auto-flow-identity.js +49 -0
  25. package/dist/pipeline/auto-flow-presets.js +52 -0
  26. package/dist/pipeline/auto-flow-resolver.js +830 -0
  27. package/dist/pipeline/auto-flow-types.js +17 -0
  28. package/dist/pipeline/context.js +1 -0
  29. package/dist/pipeline/declarative-flows.js +27 -1
  30. package/dist/pipeline/flow-specs/auto-common-guided.json +11 -0
  31. package/dist/pipeline/flow-specs/auto-golang.json +12 -1
  32. package/dist/pipeline/flow-specs/bugz/bug-analyze.json +54 -1
  33. package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +19 -1
  34. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +33 -1
  35. package/dist/pipeline/flow-specs/review/review-project.json +19 -1
  36. package/dist/pipeline/flow-specs/task-source/manual-jira-input.json +70 -0
  37. package/dist/pipeline/node-registry.js +9 -0
  38. package/dist/pipeline/nodes/codex-prompt-node.js +8 -1
  39. package/dist/pipeline/nodes/flow-run-node.js +5 -3
  40. package/dist/pipeline/nodes/git-status-node.js +2 -168
  41. package/dist/pipeline/nodes/manual-jira-task-input-node.js +146 -0
  42. package/dist/pipeline/nodes/opencode-prompt-node.js +8 -1
  43. package/dist/pipeline/nodes/plan-codex-node.js +8 -1
  44. package/dist/pipeline/spec-loader.js +14 -4
  45. package/dist/runtime/artifact-catalog.js +29 -5
  46. package/dist/runtime/settings.js +114 -0
  47. package/dist/scope.js +14 -4
  48. package/package.json +1 -1
  49. package/dist/pipeline/flow-specs/auto-common.json +0 -179
  50. 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));
@@ -40,5 +64,6 @@ export function createInitialInteractiveState(options) {
40
64
  label: "Artifact Explorer",
41
65
  message: "Artifacts are available after a Web UI workflow run completes.",
42
66
  },
67
+ gitWorkspace: createUnavailableGitWorkspace(),
43
68
  };
44
69
  }
@@ -3,6 +3,7 @@ import { writeSync } from "node:fs";
3
3
  import { FlowInterruptedError } from "../../errors.js";
4
4
  import { listArtifactCatalog } from "../../runtime/artifact-catalog.js";
5
5
  import { createArtifactRegistry } from "../../runtime/artifact-registry.js";
6
+ import { loadAgentWeaverSettings, updateWebUiSettings } from "../../runtime/settings.js";
6
7
  import { InteractiveSessionController } from "../controller.js";
7
8
  import { startWebServer, } from "./server.js";
8
9
  function actionId(action) {
@@ -16,6 +17,7 @@ export function createWebInteractiveSession(options, webOptions = {}) {
16
17
  let shuttingDown = false;
17
18
  let activeScopeKey = options.scopeKey;
18
19
  let artifactRestoreGeneration = 0;
20
+ let webUiSettings = loadAgentWeaverSettings().webUi;
19
21
  const artifactCatalogProvider = webOptions.getArtifactCatalog ?? ((input) => {
20
22
  const explorerScopeKey = controller.getViewModel().artifactExplorer.scopeKey;
21
23
  const requestedScopeKey = input?.scopeKey;
@@ -26,7 +28,7 @@ export function createWebInteractiveSession(options, webOptions = {}) {
26
28
  });
27
29
  });
28
30
  function snapshot() {
29
- return { type: "snapshot", viewModel: controller.getViewModel() };
31
+ return { type: "snapshot", viewModel: controller.getViewModel(), settings: webUiSettings };
30
32
  }
31
33
  function sendError(client, message, id) {
32
34
  const event = { type: "error", message, ...(id ? { actionId: id } : {}) };
@@ -68,33 +70,34 @@ export function createWebInteractiveSession(options, webOptions = {}) {
68
70
  try {
69
71
  const catalog = await artifactCatalogProvider({
70
72
  scopeKey,
71
- ...(candidates.length === 1 && preferredRunId ? { runId: preferredRunId, runIds: candidates } : {}),
72
- ...(candidates.length > 1 ? { runIds: candidates } : {}),
73
73
  });
74
74
  if (!catalog || catalog.scopeKey !== scopeKey) {
75
75
  return { runId: preferredRunId };
76
76
  }
77
+ const markdownArtifactCount = catalog.items.filter((item) => item.scopeKey === scopeKey && item.kind === "markdown").length;
77
78
  if (candidates.length === 0) {
78
79
  return {
79
80
  runId: null,
80
- artifactCount: catalog.items.filter((item) => item.scopeKey === scopeKey && item.kind === "markdown").length,
81
+ artifactCount: markdownArtifactCount,
81
82
  };
82
83
  }
83
84
  const matchingRunIds = candidates.filter((candidate) => (catalog.items.some((item) => item.scopeKey === scopeKey && item.runId === candidate && item.kind === "markdown")));
84
85
  if (matchingRunIds.length > 0) {
85
- const matchingRunIdSet = new Set(matchingRunIds);
86
86
  return {
87
87
  runId: matchingRunIds[0] ?? preferredRunId,
88
88
  ...(matchingRunIds.length > 1 ? { runIds: matchingRunIds } : {}),
89
- artifactCount: catalog.items.filter((item) => (item.scopeKey === scopeKey
90
- && item.kind === "markdown"
91
- && item.runId !== null
92
- && matchingRunIdSet.has(item.runId))).length,
89
+ artifactCount: markdownArtifactCount,
90
+ };
91
+ }
92
+ if (markdownArtifactCount > 0) {
93
+ return {
94
+ runId: null,
95
+ artifactCount: markdownArtifactCount,
93
96
  };
94
97
  }
95
98
  return {
96
99
  runId: preferredRunId,
97
- artifactCount: catalog.items.filter((item) => item.scopeKey === scopeKey && item.kind === "markdown" && item.runId === preferredRunId).length,
100
+ artifactCount: markdownArtifactCount,
98
101
  };
99
102
  }
100
103
  catch {
@@ -113,6 +116,7 @@ export function createWebInteractiveSession(options, webOptions = {}) {
113
116
  ...(artifactCount !== undefined ? { artifactCount } : {}),
114
117
  open: !controller.hasActiveInput(),
115
118
  });
119
+ await controller.refreshGitWorkspace();
116
120
  }
117
121
  async function restoreArtifactExplorerFromScope(scopeKey) {
118
122
  const generation = ++artifactRestoreGeneration;
@@ -223,6 +227,83 @@ export function createWebInteractiveSession(options, webOptions = {}) {
223
227
  controller.closeArtifactExplorer();
224
228
  return;
225
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
+ }
226
307
  if (action.type === "help.toggle") {
227
308
  controller.showHelp(action.visible ?? !controller.getViewModel().helpVisible);
228
309
  return;
@@ -253,7 +334,9 @@ export function createWebInteractiveSession(options, webOptions = {}) {
253
334
  }
254
335
  server?.broadcast(snapshot());
255
336
  });
256
- void startWebServer({
337
+ void controller.refreshGitWorkspace().catch((error) => {
338
+ controller.appendLog(`Git workspace refresh failed: ${error.message}`);
339
+ }).then(() => startWebServer({
257
340
  ...(webOptions.noOpen !== undefined ? { noOpen: webOptions.noOpen } : {}),
258
341
  ...(webOptions.host !== undefined ? { host: webOptions.host } : {}),
259
342
  ...(webOptions.auth !== undefined ? { auth: webOptions.auth } : {}),
@@ -263,6 +346,8 @@ export function createWebInteractiveSession(options, webOptions = {}) {
263
346
  },
264
347
  ...(webOptions.openBrowser ? { openBrowser: webOptions.openBrowser } : {}),
265
348
  getArtifactCatalog: (input) => artifactCatalogProvider(input),
349
+ gitService: controller.getGitService(),
350
+ getGitWorkspaceSnapshot: () => controller.getGitWorkspaceSnapshot(),
266
351
  onClientAction: (action, client) => {
267
352
  void dispatch(action, client);
268
353
  },
@@ -272,7 +357,7 @@ export function createWebInteractiveSession(options, webOptions = {}) {
272
357
  onExitRequested: () => {
273
358
  options.onExit();
274
359
  },
275
- }).then((started) => {
360
+ })).then((started) => {
276
361
  if (shuttingDown) {
277
362
  void started.close().catch((error) => {
278
363
  process.stderr.write(`Failed to close Web UI server: ${error.message}\n`);