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
@@ -14,6 +14,25 @@ const ACTION_TYPES = new Set([
14
14
  "log.clear",
15
15
  "artifactExplorer.open",
16
16
  "artifactExplorer.close",
17
+ "autoFlow.selectPreset",
18
+ "autoFlow.loadConfig",
19
+ "autoFlow.save",
20
+ "autoFlow.reset",
21
+ "autoFlow.toggleBlock",
22
+ "autoFlow.updateParam",
23
+ "autoFlow.insertBlock",
24
+ "autoFlow.removeBlock",
25
+ "git.refresh",
26
+ "git.createBranch",
27
+ "git.checkout",
28
+ "git.fetch",
29
+ "git.pullFfOnly",
30
+ "git.stage",
31
+ "git.unstage",
32
+ "git.updateCommitMessage",
33
+ "git.commit",
34
+ "git.push",
35
+ "settings.update",
17
36
  "help.toggle",
18
37
  "scroll",
19
38
  ]);
@@ -53,6 +72,62 @@ function optionalInteger(value, fieldName) {
53
72
  }
54
73
  return field;
55
74
  }
75
+ function optionalBoolean(value, fieldName) {
76
+ if (value[fieldName] === undefined) {
77
+ return undefined;
78
+ }
79
+ const field = value[fieldName];
80
+ if (typeof field !== "boolean") {
81
+ throw new Error(`${fieldName} must be a boolean.`);
82
+ }
83
+ return field;
84
+ }
85
+ function requireSettingsPatch(value) {
86
+ const settings = value.settings;
87
+ if (!isRecord(settings)) {
88
+ throw new Error("settings must be an object.");
89
+ }
90
+ const allowed = new Set(["theme", "autoFlowHeight", "workspaceSplit", "logAutoscroll"]);
91
+ for (const key of Object.keys(settings)) {
92
+ if (!allowed.has(key)) {
93
+ throw new Error(`Unsupported settings key: ${key}`);
94
+ }
95
+ }
96
+ const patch = {};
97
+ if ("theme" in settings) {
98
+ const theme = settings.theme;
99
+ if (theme !== "dark" && theme !== "light") {
100
+ throw new Error("settings.theme must be light or dark.");
101
+ }
102
+ patch.theme = theme;
103
+ }
104
+ if ("autoFlowHeight" in settings) {
105
+ const autoFlowHeight = settings.autoFlowHeight;
106
+ if (autoFlowHeight !== null
107
+ && (typeof autoFlowHeight !== "number" || !Number.isFinite(autoFlowHeight))) {
108
+ throw new Error("settings.autoFlowHeight must be a finite number or null.");
109
+ }
110
+ patch.autoFlowHeight = autoFlowHeight;
111
+ }
112
+ if ("workspaceSplit" in settings) {
113
+ const workspaceSplit = settings.workspaceSplit;
114
+ if (typeof workspaceSplit !== "number" || !Number.isFinite(workspaceSplit)) {
115
+ throw new Error("settings.workspaceSplit must be a finite number.");
116
+ }
117
+ patch.workspaceSplit = workspaceSplit;
118
+ }
119
+ if ("logAutoscroll" in settings) {
120
+ const logAutoscroll = settings.logAutoscroll;
121
+ if (typeof logAutoscroll !== "boolean") {
122
+ throw new Error("settings.logAutoscroll must be a boolean.");
123
+ }
124
+ patch.logAutoscroll = logAutoscroll;
125
+ }
126
+ if (Object.keys(patch).length === 0) {
127
+ throw new Error("settings.update requires at least one setting.");
128
+ }
129
+ return patch;
130
+ }
56
131
  function requireValues(value, fieldName = "values") {
57
132
  const values = value[fieldName];
58
133
  if (!isRecord(values)) {
@@ -60,6 +135,26 @@ function requireValues(value, fieldName = "values") {
60
135
  }
61
136
  return values;
62
137
  }
138
+ function requireStringArray(value, fieldName) {
139
+ const field = value[fieldName];
140
+ if (!Array.isArray(field) || field.some((item) => typeof item !== "string" || item.length === 0)) {
141
+ throw new Error(`${fieldName} must be an array of non-empty strings.`);
142
+ }
143
+ return field;
144
+ }
145
+ function optionalStringArray(value, fieldName) {
146
+ if (value[fieldName] === undefined) {
147
+ return undefined;
148
+ }
149
+ return requireStringArray(value, fieldName);
150
+ }
151
+ function requireCommitMessage(value) {
152
+ const message = value.message;
153
+ if (typeof message !== "string" || message.trim().length === 0) {
154
+ throw new Error("message must be a non-empty string.");
155
+ }
156
+ return message;
157
+ }
63
158
  export function parseClientAction(raw) {
64
159
  let parsed;
65
160
  try {
@@ -102,7 +197,11 @@ export function parseClientAction(raw) {
102
197
  || parsed.type === "form.cancel"
103
198
  || parsed.type === "log.clear"
104
199
  || parsed.type === "artifactExplorer.open"
105
- || parsed.type === "artifactExplorer.close") {
200
+ || parsed.type === "artifactExplorer.close"
201
+ || parsed.type === "git.refresh"
202
+ || parsed.type === "git.fetch"
203
+ || parsed.type === "git.pullFfOnly"
204
+ || parsed.type === "git.push") {
106
205
  return { type: parsed.type, ...(actionId ? { actionId } : {}) };
107
206
  }
108
207
  if (parsed.type === "form.update") {
@@ -135,12 +234,128 @@ export function parseClientAction(raw) {
135
234
  const flowId = optionalNonEmptyString(parsed, "flowId");
136
235
  return { type: "flow.interrupt", ...(flowId ? { flowId } : {}), ...(actionId ? { actionId } : {}) };
137
236
  }
237
+ if (parsed.type === "autoFlow.selectPreset") {
238
+ const preset = requireNonEmptyString(parsed, "preset");
239
+ if (preset !== "simple" && preset !== "standard") {
240
+ throw new Error("preset must be simple or standard.");
241
+ }
242
+ return { type: "autoFlow.selectPreset", preset, ...(actionId ? { actionId } : {}) };
243
+ }
244
+ if (parsed.type === "autoFlow.loadConfig") {
245
+ const flowId = optionalNonEmptyString(parsed, "flowId");
246
+ return {
247
+ type: "autoFlow.loadConfig",
248
+ name: requireNonEmptyString(parsed, "name"),
249
+ ...(flowId ? { flowId } : {}),
250
+ ...(actionId ? { actionId } : {}),
251
+ };
252
+ }
253
+ if (parsed.type === "autoFlow.save") {
254
+ const flowId = optionalNonEmptyString(parsed, "flowId");
255
+ const name = optionalNonEmptyString(parsed, "name");
256
+ const location = optionalNonEmptyString(parsed, "location");
257
+ if (location !== undefined && location !== "project" && location !== "user") {
258
+ throw new Error("location must be project or user.");
259
+ }
260
+ return {
261
+ type: "autoFlow.save",
262
+ ...(flowId ? { flowId } : {}),
263
+ ...(name ? { name } : {}),
264
+ ...(location ? { location } : {}),
265
+ ...(actionId ? { actionId } : {}),
266
+ };
267
+ }
268
+ if (parsed.type === "autoFlow.reset") {
269
+ const flowId = optionalNonEmptyString(parsed, "flowId");
270
+ return { type: "autoFlow.reset", ...(flowId ? { flowId } : {}), ...(actionId ? { actionId } : {}) };
271
+ }
272
+ if (parsed.type === "autoFlow.toggleBlock") {
273
+ const flowId = optionalNonEmptyString(parsed, "flowId");
274
+ const slotId = optionalNonEmptyString(parsed, "slotId");
275
+ const enabled = optionalBoolean(parsed, "enabled");
276
+ return {
277
+ type: "autoFlow.toggleBlock",
278
+ ...(flowId ? { flowId } : {}),
279
+ ...(slotId ? { slotId } : {}),
280
+ blockId: requireNonEmptyString(parsed, "blockId"),
281
+ ...(enabled !== undefined ? { enabled } : {}),
282
+ ...(actionId ? { actionId } : {}),
283
+ };
284
+ }
285
+ if (parsed.type === "autoFlow.updateParam") {
286
+ const flowId = optionalNonEmptyString(parsed, "flowId");
287
+ const slotId = optionalNonEmptyString(parsed, "slotId");
288
+ return {
289
+ type: "autoFlow.updateParam",
290
+ ...(flowId ? { flowId } : {}),
291
+ ...(slotId ? { slotId } : {}),
292
+ blockId: requireNonEmptyString(parsed, "blockId"),
293
+ paramName: requireNonEmptyString(parsed, "paramName"),
294
+ value: optionalInteger(parsed, "value") ?? (() => {
295
+ throw new Error("value must be an integer.");
296
+ })(),
297
+ ...(actionId ? { actionId } : {}),
298
+ };
299
+ }
300
+ if (parsed.type === "autoFlow.insertBlock") {
301
+ const flowId = optionalNonEmptyString(parsed, "flowId");
302
+ return {
303
+ type: "autoFlow.insertBlock",
304
+ ...(flowId ? { flowId } : {}),
305
+ slotId: requireNonEmptyString(parsed, "slotId"),
306
+ blockId: requireNonEmptyString(parsed, "blockId"),
307
+ ...(actionId ? { actionId } : {}),
308
+ };
309
+ }
310
+ if (parsed.type === "autoFlow.removeBlock") {
311
+ const flowId = optionalNonEmptyString(parsed, "flowId");
312
+ return {
313
+ type: "autoFlow.removeBlock",
314
+ ...(flowId ? { flowId } : {}),
315
+ slotId: requireNonEmptyString(parsed, "slotId"),
316
+ blockId: requireNonEmptyString(parsed, "blockId"),
317
+ ...(actionId ? { actionId } : {}),
318
+ };
319
+ }
138
320
  if (parsed.type === "help.toggle") {
139
321
  if (parsed.visible !== undefined && typeof parsed.visible !== "boolean") {
140
322
  throw new Error("visible must be a boolean when provided.");
141
323
  }
142
324
  return { type: "help.toggle", ...(parsed.visible !== undefined ? { visible: parsed.visible } : {}), ...(actionId ? { actionId } : {}) };
143
325
  }
326
+ if (parsed.type === "git.createBranch") {
327
+ if ("selectedBase" in parsed || "base" in parsed || "baseBranch" in parsed) {
328
+ throw new Error("git.createBranch does not accept a selected base in the MVP.");
329
+ }
330
+ return { type: "git.createBranch", branchName: requireNonEmptyString(parsed, "branchName"), ...(actionId ? { actionId } : {}) };
331
+ }
332
+ if (parsed.type === "git.checkout") {
333
+ return { type: "git.checkout", branchName: requireNonEmptyString(parsed, "branchName"), ...(actionId ? { actionId } : {}) };
334
+ }
335
+ if (parsed.type === "git.stage") {
336
+ return { type: "git.stage", paths: requireStringArray(parsed, "paths"), ...(actionId ? { actionId } : {}) };
337
+ }
338
+ if (parsed.type === "git.unstage") {
339
+ return { type: "git.unstage", paths: requireStringArray(parsed, "paths"), ...(actionId ? { actionId } : {}) };
340
+ }
341
+ if (parsed.type === "git.updateCommitMessage") {
342
+ if (typeof parsed.message !== "string") {
343
+ throw new Error("message must be a string.");
344
+ }
345
+ return { type: "git.updateCommitMessage", message: parsed.message, ...(actionId ? { actionId } : {}) };
346
+ }
347
+ if (parsed.type === "git.commit") {
348
+ const paths = optionalStringArray(parsed, "paths");
349
+ return {
350
+ type: "git.commit",
351
+ message: requireCommitMessage(parsed),
352
+ ...(paths !== undefined ? { paths } : {}),
353
+ ...(actionId ? { actionId } : {}),
354
+ };
355
+ }
356
+ if (parsed.type === "settings.update") {
357
+ return { type: "settings.update", settings: requireSettingsPatch(parsed), ...(actionId ? { actionId } : {}) };
358
+ }
144
359
  const pane = requireNonEmptyString(parsed, "pane");
145
360
  if (!SCROLL_PANES.has(pane)) {
146
361
  throw new Error("scroll pane must be one of flows, progress, summary, log, or help.");
@@ -7,6 +7,7 @@ import process from "node:process";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { parseArtifactReference } from "../../artifact-manifest.js";
9
9
  import { scopeArtifactsDir, scopeWorkspaceDir } from "../../artifacts.js";
10
+ import { GitDiffError } from "../../git/git-service.js";
10
11
  import { groupArtifactCatalog, inferArtifactRenderKind, inferArtifactRole, inferArtifactTitle, } from "../../runtime/artifact-catalog.js";
11
12
  import { createArtifactRegistry } from "../../runtime/artifact-registry.js";
12
13
  import { parseClientAction } from "./protocol.js";
@@ -22,6 +23,17 @@ const CONTENT_TYPES = new Map([
22
23
  ]);
23
24
  const BASIC_AUTH_REALM = "AgentWeaver Web UI";
24
25
  const ARTIFACT_API_PREFIX = "/__agentweaver/api/artifacts";
26
+ const GIT_DIFF_API_PATH = "/__agentweaver/api/git/diff";
27
+ const GIT_DIFF_ERROR_STATUSES = {
28
+ missing_path: 400,
29
+ invalid_path: 403,
30
+ invalid_mode: 400,
31
+ missing_provider: 503,
32
+ repository_unavailable: 503,
33
+ forbidden_path: 403,
34
+ read_failed: 500,
35
+ git_failed: 500,
36
+ };
25
37
  const ARTIFACT_ERROR_STATUSES = {
26
38
  invalid_id: 400,
27
39
  missing_scope: 400,
@@ -136,6 +148,9 @@ function writeArtifactApiError(response, code, message, artifact) {
136
148
  ...(artifact ? { artifact } : {}),
137
149
  });
138
150
  }
151
+ function writeGitDiffApiError(response, code, message) {
152
+ writeJson(response, GIT_DIFF_ERROR_STATUSES[code], { code, message });
153
+ }
139
154
  function safeArtifactMetadata(item) {
140
155
  return {
141
156
  id: item.id,
@@ -195,13 +210,9 @@ function parseArtifactApiRoute(requestUrl) {
195
210
  }
196
211
  return { kind: "content", artifactId: "", action: "preview", scopeKey, runIds };
197
212
  }
198
- function filterCatalog(catalog, scopeKey, runIds) {
199
- const runIdSet = new Set(runIds);
200
- let items = catalog.items.filter((item) => item.scopeKey === scopeKey && item.kind === "markdown");
201
- if (runIdSet.size > 0) {
202
- items = items.filter((item) => item.runId !== null && runIdSet.has(item.runId));
203
- }
204
- const sortedItems = items.slice();
213
+ function filterCatalog(catalog, scopeKey) {
214
+ const markdownItems = catalog.items.filter((item) => item.scopeKey === scopeKey && item.kind === "markdown");
215
+ const sortedItems = markdownItems.slice();
205
216
  return {
206
217
  scopeKey,
207
218
  items: sortedItems,
@@ -460,11 +471,8 @@ function handleArtifactApiRequest(request, response, options, auth) {
460
471
  writeArtifactApiError(response, "missing_scope", "A scope query parameter is required.");
461
472
  return true;
462
473
  }
463
- const primaryRunId = route.runIds[0];
464
474
  void loadArtifactCatalog(options, {
465
475
  scopeKey: route.scopeKey,
466
- ...(route.runIds.length === 1 && primaryRunId ? { runId: primaryRunId, runIds: route.runIds } : {}),
467
- ...(route.runIds.length > 1 ? { runIds: route.runIds } : {}),
468
476
  })
469
477
  .then((catalog) => {
470
478
  if (!catalog) {
@@ -475,7 +483,7 @@ function handleArtifactApiRequest(request, response, options, auth) {
475
483
  writeArtifactApiError(response, "scope_mismatch", "Requested scope does not match the active Web UI scope.");
476
484
  return;
477
485
  }
478
- writeJson(response, 200, filterCatalog(catalog, route.scopeKey, route.runIds));
486
+ writeJson(response, 200, filterCatalog(catalog, route.scopeKey));
479
487
  })
480
488
  .catch(() => {
481
489
  writeArtifactApiError(response, "read_failed", "Artifact catalog could not be loaded.");
@@ -486,11 +494,8 @@ function handleArtifactApiRequest(request, response, options, auth) {
486
494
  writeArtifactApiError(response, "invalid_id", "Artifact identifier is invalid.");
487
495
  return true;
488
496
  }
489
- const primaryRunId = route.runIds[0];
490
497
  void loadArtifactCatalog(options, {
491
498
  ...(route.scopeKey ? { scopeKey: route.scopeKey } : {}),
492
- ...(route.runIds.length === 1 && primaryRunId ? { runId: primaryRunId, runIds: route.runIds } : {}),
493
- ...(route.runIds.length > 1 ? { runIds: route.runIds } : {}),
494
499
  })
495
500
  .then((catalog) => {
496
501
  if (!catalog) {
@@ -521,6 +526,56 @@ function handleArtifactApiRequest(request, response, options, auth) {
521
526
  });
522
527
  return true;
523
528
  }
529
+ function isGitDiffMode(value) {
530
+ return value === "head" || value === "staged" || value === "worktree";
531
+ }
532
+ function gitDiffErrorCode(error) {
533
+ if (error instanceof GitDiffError && error.code in GIT_DIFF_ERROR_STATUSES) {
534
+ return error.code;
535
+ }
536
+ return "git_failed";
537
+ }
538
+ function handleGitDiffApiRequest(request, response, options, auth) {
539
+ const parsed = new URL(request.url ?? "/", "http://agentweaver.local");
540
+ if (parsed.pathname !== GIT_DIFF_API_PATH) {
541
+ return false;
542
+ }
543
+ if (request.method !== "GET") {
544
+ return false;
545
+ }
546
+ if (!isAuthorized(request, auth)) {
547
+ writeAuthRequired(response);
548
+ return true;
549
+ }
550
+ if (!options.gitService || !options.getGitWorkspaceSnapshot) {
551
+ writeGitDiffApiError(response, "missing_provider", "Git diff provider is not configured.");
552
+ return true;
553
+ }
554
+ const filePath = parsed.searchParams.get("path");
555
+ const mode = parsed.searchParams.get("mode");
556
+ if (!filePath) {
557
+ writeGitDiffApiError(response, "missing_path", "A path query parameter is required.");
558
+ return true;
559
+ }
560
+ if (!isGitDiffMode(mode)) {
561
+ writeGitDiffApiError(response, "invalid_mode", "Mode must be head, staged, or worktree.");
562
+ return true;
563
+ }
564
+ const snapshot = options.getGitWorkspaceSnapshot();
565
+ if (!snapshot || !snapshot.available || !snapshot.repositoryRoot) {
566
+ writeGitDiffApiError(response, "repository_unavailable", snapshot?.error ?? "Git repository is not available.");
567
+ return true;
568
+ }
569
+ void options.gitService.diffFile(filePath, mode, snapshot)
570
+ .then((diff) => {
571
+ writeJson(response, 200, diff);
572
+ })
573
+ .catch((error) => {
574
+ const code = gitDiffErrorCode(error);
575
+ writeGitDiffApiError(response, code, error.message || "Git diff request failed.");
576
+ });
577
+ return true;
578
+ }
524
579
  function htmlShell() {
525
580
  return `<!doctype html>
526
581
  <html lang="en">
@@ -843,6 +898,9 @@ export async function startWebServer(options) {
843
898
  writeJson(response, 200, { ok: true });
844
899
  return;
845
900
  }
901
+ if (handleGitDiffApiRequest(request, response, options, auth)) {
902
+ return;
903
+ }
846
904
  if (handleArtifactApiRequest(request, response, options, auth)) {
847
905
  return;
848
906
  }