agentweaver 0.1.17 → 0.1.19

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 (48) hide show
  1. package/README.md +112 -23
  2. package/dist/artifacts.js +41 -0
  3. package/dist/index.js +258 -29
  4. package/dist/interactive/controller.js +323 -13
  5. package/dist/interactive/ink/index.js +2 -2
  6. package/dist/interactive/state.js +10 -0
  7. package/dist/interactive/web/index.js +326 -0
  8. package/dist/interactive/web/protocol.js +160 -0
  9. package/dist/interactive/web/server.js +1011 -0
  10. package/dist/interactive/web/static/app.js +1580 -0
  11. package/dist/interactive/web/static/index.html +114 -0
  12. package/dist/interactive/web/static/styles.css +2 -0
  13. package/dist/interactive/web/static/styles.input.css +849 -0
  14. package/dist/pipeline/flow-catalog.js +4 -0
  15. package/dist/pipeline/flow-specs/auto-common-guided.json +313 -0
  16. package/dist/pipeline/flow-specs/auto-common.json +3 -1
  17. package/dist/pipeline/flow-specs/design-review/design-review-loop.json +2 -0
  18. package/dist/pipeline/flow-specs/design-review.json +2 -0
  19. package/dist/pipeline/flow-specs/implement.json +3 -1
  20. package/dist/pipeline/flow-specs/plan.json +4 -0
  21. package/dist/pipeline/flow-specs/playbook-init.json +199 -0
  22. package/dist/pipeline/flow-specs/review/review-fix.json +3 -1
  23. package/dist/pipeline/flow-specs/review/review-loop.json +4 -0
  24. package/dist/pipeline/flow-specs/review/review.json +2 -0
  25. package/dist/pipeline/node-registry.js +45 -0
  26. package/dist/pipeline/nodes/flow-run-node.js +13 -1
  27. package/dist/pipeline/nodes/playbook-ensure-node.js +115 -0
  28. package/dist/pipeline/nodes/playbook-inventory-node.js +51 -0
  29. package/dist/pipeline/nodes/playbook-questions-form-node.js +166 -0
  30. package/dist/pipeline/nodes/playbook-write-node.js +243 -0
  31. package/dist/pipeline/nodes/project-guidance-node.js +69 -0
  32. package/dist/pipeline/prompt-registry.js +4 -1
  33. package/dist/pipeline/prompt-runtime.js +6 -2
  34. package/dist/pipeline/spec-types.js +19 -0
  35. package/dist/pipeline/value-resolver.js +39 -1
  36. package/dist/playbook/practice-candidates.js +12 -0
  37. package/dist/playbook/repo-inventory.js +208 -0
  38. package/dist/prompts.js +31 -0
  39. package/dist/runtime/artifact-catalog.js +379 -0
  40. package/dist/runtime/playbook.js +485 -0
  41. package/dist/runtime/project-guidance.js +339 -0
  42. package/dist/structured-artifact-schema-registry.js +8 -0
  43. package/dist/structured-artifact-schemas.json +235 -0
  44. package/dist/structured-artifacts.js +7 -1
  45. package/docs/declarative-workflows.md +565 -0
  46. package/docs/features.md +77 -0
  47. package/docs/playbook.md +327 -0
  48. package/package.json +8 -3
@@ -182,6 +182,7 @@ export class InteractiveSessionController {
182
182
  currentOptionIndex: initialOptionIndex,
183
183
  currentTextCursorIndex: initialCursorIndex,
184
184
  previewScrollOffset: 0,
185
+ validationError: null,
185
186
  resolve,
186
187
  reject,
187
188
  };
@@ -206,9 +207,12 @@ export class InteractiveSessionController {
206
207
  }
207
208
  this.emitChange();
208
209
  }
209
- setScope(scopeKey, jiraIssueKey) {
210
+ setScope(scopeKey, jiraIssueKey, gitBranchName) {
210
211
  this.state.scopeKey = scopeKey;
211
212
  this.state.jiraIssueKey = jiraIssueKey ?? null;
213
+ if (gitBranchName !== undefined) {
214
+ this.state.gitBranchName = gitBranchName;
215
+ }
212
216
  this.emitChange();
213
217
  }
214
218
  appendLog(text) {
@@ -306,7 +310,7 @@ export class InteractiveSessionController {
306
310
  selectFlowIndex(index) {
307
311
  const selectedItem = this.visibleFlowItems[index];
308
312
  if (!selectedItem) {
309
- return;
313
+ throw new Error(`Invalid flow index: ${index}`);
310
314
  }
311
315
  this.state.selectedFlowItemKey = selectedItem.key;
312
316
  if (selectedItem.kind === "flow") {
@@ -314,6 +318,283 @@ export class InteractiveSessionController {
314
318
  }
315
319
  this.emitChange();
316
320
  }
321
+ selectFlowKey(key) {
322
+ const selectedItem = this.visibleFlowItems.find((item) => item.key === key);
323
+ if (!selectedItem) {
324
+ throw new Error(`Unknown visible flow item key: ${key}`);
325
+ }
326
+ this.state.selectedFlowItemKey = selectedItem.key;
327
+ if (selectedItem.kind === "flow") {
328
+ this.state.selectedFlowId = selectedItem.flow.id;
329
+ }
330
+ this.emitChange();
331
+ }
332
+ selectFlowId(flowId) {
333
+ const selectedItem = this.visibleFlowItems.find((item) => item.kind === "flow" && item.flow.id === flowId);
334
+ if (!selectedItem) {
335
+ throw new Error(`Unknown visible flow: ${flowId}`);
336
+ }
337
+ this.state.selectedFlowItemKey = selectedItem.key;
338
+ this.state.selectedFlowId = selectedItem.flow.id;
339
+ this.emitChange();
340
+ }
341
+ toggleFolderKey(key) {
342
+ const item = this.visibleFlowItems.find((candidate) => candidate.key === key);
343
+ if (!item || item.kind !== "folder") {
344
+ throw new Error(`Unknown visible folder key: ${key}`);
345
+ }
346
+ this.toggleFlowFolder(key);
347
+ }
348
+ toggleFolder(key) {
349
+ this.toggleFolderKey(key);
350
+ }
351
+ async openRunConfirm(flowId, key) {
352
+ if (flowId) {
353
+ if (!this.visibleFlowItems.some((item) => item.kind === "flow" && item.flow.id === flowId)) {
354
+ throw new Error(`Unknown visible flow: ${flowId}`);
355
+ }
356
+ this.selectFlowId(flowId);
357
+ }
358
+ else if (key) {
359
+ const keyedItem = this.visibleFlowItems.find((item) => item.key === key);
360
+ if (!keyedItem || keyedItem.kind !== "flow") {
361
+ throw new Error(`Unknown visible flow item key: ${key}`);
362
+ }
363
+ this.selectFlowKey(key);
364
+ }
365
+ const selectedItem = this.selectedFlowTreeItem();
366
+ if (!selectedItem || selectedItem.kind !== "flow") {
367
+ throw new Error("A flow must be selected before opening run confirmation.");
368
+ }
369
+ await this.openConfirm();
370
+ }
371
+ selectConfirmAction(action) {
372
+ if (!this.confirmSession) {
373
+ throw new Error("No confirmation is active.");
374
+ }
375
+ const actions = this.confirmActions();
376
+ if (!actions.includes(action)) {
377
+ throw new Error(`Invalid confirmation action: ${action}`);
378
+ }
379
+ this.confirmSession.selectedAction = action;
380
+ this.emitChange();
381
+ }
382
+ async acceptConfirmation() {
383
+ await this.acceptConfirm();
384
+ }
385
+ async acceptConfirm() {
386
+ if (!this.confirmSession) {
387
+ throw new Error("No confirmation is active.");
388
+ }
389
+ await this.acceptActiveConfirm();
390
+ }
391
+ cancelConfirmation() {
392
+ this.cancelConfirm();
393
+ }
394
+ cancelConfirm() {
395
+ if (!this.confirmSession) {
396
+ throw new Error("No confirmation is active.");
397
+ }
398
+ this.confirmSession = null;
399
+ this.emitChange();
400
+ }
401
+ updateActiveFormValues(values) {
402
+ const session = this.activeFormSession;
403
+ if (!session) {
404
+ throw new Error("No form is active.");
405
+ }
406
+ const fieldIds = new Set(session.form.fields.map((field) => field.id));
407
+ const nextValues = { ...session.values };
408
+ let changed = false;
409
+ for (const [fieldId, value] of Object.entries(values)) {
410
+ if (!fieldIds.has(fieldId)) {
411
+ continue;
412
+ }
413
+ nextValues[fieldId] = value;
414
+ changed = true;
415
+ }
416
+ if (!changed) {
417
+ return;
418
+ }
419
+ session.values = nextValues;
420
+ for (const field of session.form.fields) {
421
+ normalizeUserInputFieldValue(field, session.values);
422
+ }
423
+ session.validationError = null;
424
+ const field = this.currentFormField();
425
+ if (field?.type === "text") {
426
+ session.currentTextCursorIndex = String(session.values[field.id] ?? "").length;
427
+ }
428
+ else if (field?.type === "single-select" || field?.type === "multi-select") {
429
+ session.currentOptionIndex = this.selectedOptionIndexForField(field);
430
+ }
431
+ this.emitChange();
432
+ }
433
+ updateFormField(fieldId, value) {
434
+ const session = this.activeFormSession;
435
+ if (!session) {
436
+ throw new Error("No form is active.");
437
+ }
438
+ const fieldIndex = session.form.fields.findIndex((candidate) => candidate.id === fieldId);
439
+ const baseField = session.form.fields[fieldIndex];
440
+ if (!baseField) {
441
+ throw new Error(`Unknown form field: ${fieldId}`);
442
+ }
443
+ const field = resolveFieldDefinition(baseField, session.values);
444
+ if (field.type === "boolean" && typeof value !== "boolean") {
445
+ throw new Error(`Field '${field.label}' must be a boolean.`);
446
+ }
447
+ if (field.type === "text" && typeof value !== "string") {
448
+ throw new Error(`Field '${field.label}' must be a string.`);
449
+ }
450
+ if (field.type === "single-select" && typeof value !== "string") {
451
+ throw new Error(`Field '${field.label}' must be a string.`);
452
+ }
453
+ if (field.type === "multi-select"
454
+ && (!Array.isArray(value) || value.some((item) => typeof item !== "string"))) {
455
+ throw new Error(`Field '${field.label}' must be a string array.`);
456
+ }
457
+ session.currentFieldIndex = fieldIndex;
458
+ this.updateActiveFormValues({ [fieldId]: value });
459
+ }
460
+ submitActiveFormValues(values) {
461
+ if (!this.activeFormSession) {
462
+ throw new Error("No form is active.");
463
+ }
464
+ if (values) {
465
+ this.updateActiveFormValues(values);
466
+ }
467
+ this.submitActiveForm();
468
+ if (this.activeFormSession) {
469
+ throw new Error("Form validation failed. See session log for details.");
470
+ }
471
+ }
472
+ submitForm(values) {
473
+ this.submitActiveFormValues(values);
474
+ }
475
+ cancelForm() {
476
+ if (!this.activeFormSession) {
477
+ throw new Error("No form is active.");
478
+ }
479
+ this.cancelActiveForm();
480
+ }
481
+ async interruptFlow(flowId) {
482
+ const hadActiveForm = this.activeFormSession !== null;
483
+ if (this.activeFormSession) {
484
+ this.interruptActiveForm();
485
+ }
486
+ const targetFlowId = flowId ?? this.state.currentFlowId;
487
+ if (!targetFlowId && hadActiveForm) {
488
+ return;
489
+ }
490
+ if (!targetFlowId) {
491
+ throw new Error("No running flow is available to interrupt.");
492
+ }
493
+ await this.options.onInterrupt(targetFlowId);
494
+ }
495
+ async interruptCurrentFlow(flowId) {
496
+ await this.interruptFlow(flowId);
497
+ }
498
+ toggleHelp(visible) {
499
+ this.helpVisible = visible ?? !this.helpVisible;
500
+ this.emitChange();
501
+ }
502
+ showHelp(visible) {
503
+ this.toggleHelp(visible);
504
+ }
505
+ scrollPane(panel, options) {
506
+ if (panel === "flows") {
507
+ if (options.delta !== undefined) {
508
+ this.moveSelectedFlow(options.delta);
509
+ return;
510
+ }
511
+ if (options.offset !== undefined) {
512
+ this.selectFlowIndex(options.offset);
513
+ return;
514
+ }
515
+ throw new Error("Flow scroll requires delta or offset.");
516
+ }
517
+ const scrollPanel = panel;
518
+ const maxOffset = this.panelMaxScroll(scrollPanel);
519
+ const current = this.scrollOffsetFor(scrollPanel);
520
+ this.applyScrollOffset(scrollPanel, options.offset ?? current + (options.delta ?? 0), maxOffset);
521
+ }
522
+ setScrollOffset(panel, offset) {
523
+ this.applyScrollOffset(panel, offset, this.panelMaxScroll(panel));
524
+ }
525
+ getCurrentFlowExecutionState() {
526
+ return this.state.flowState.executionState;
527
+ }
528
+ hasActiveInput() {
529
+ return this.confirmSession !== null || this.activeFormSession !== null;
530
+ }
531
+ setArtifactExplorerAvailability(input) {
532
+ const count = input.artifactCount;
533
+ const hasCount = typeof count === "number";
534
+ const failed = input.status === "failed";
535
+ const label = input.label
536
+ ?? (failed
537
+ ? hasCount && count > 0 ? "Run failed; artifacts available" : "Run failed"
538
+ : hasCount && count === 0 ? "Run completed; no artifacts found" : "Artifacts ready");
539
+ const message = input.message
540
+ ?? (failed
541
+ ? hasCount && count > 0
542
+ ? "The workflow failed, but artifacts are available for review."
543
+ : "The workflow failed. The explorer can check for any artifacts written before failure."
544
+ : hasCount && count === 0
545
+ ? "The workflow completed, but no artifacts were found for this run yet."
546
+ : "The workflow completed and artifacts are available for review.");
547
+ this.state.artifactExplorer = {
548
+ available: true,
549
+ open: Boolean(input.open) && !this.hasActiveInput(),
550
+ scopeKey: input.scopeKey,
551
+ runId: input.runId ?? null,
552
+ ...(input.runIds && input.runIds.length > 1 ? { runIds: input.runIds } : {}),
553
+ status: input.status,
554
+ label,
555
+ ...(hasCount ? { artifactCount: count } : {}),
556
+ message,
557
+ };
558
+ this.emitChange();
559
+ }
560
+ setArtifactExplorerUnavailable(message = "Artifacts are available after a Web UI workflow run completes.") {
561
+ if (!this.state.artifactExplorer.available && !this.state.artifactExplorer.open) {
562
+ return;
563
+ }
564
+ this.state.artifactExplorer = {
565
+ available: false,
566
+ open: false,
567
+ scopeKey: null,
568
+ runId: null,
569
+ status: "unavailable",
570
+ label: "Artifact Explorer",
571
+ message,
572
+ };
573
+ this.emitChange();
574
+ }
575
+ closeArtifactExplorer() {
576
+ if (!this.state.artifactExplorer.open) {
577
+ return;
578
+ }
579
+ this.state.artifactExplorer = {
580
+ ...this.state.artifactExplorer,
581
+ open: false,
582
+ };
583
+ this.emitChange();
584
+ }
585
+ openArtifactExplorer() {
586
+ if (!this.state.artifactExplorer.available || this.hasActiveInput()) {
587
+ return;
588
+ }
589
+ if (this.state.artifactExplorer.open) {
590
+ return;
591
+ }
592
+ this.state.artifactExplorer = {
593
+ ...this.state.artifactExplorer,
594
+ open: true,
595
+ };
596
+ this.emitChange();
597
+ }
317
598
  getViewModel(layout) {
318
599
  const selectedItem = this.selectedFlowTreeItem();
319
600
  const activeFlowId = this.activeFlowId();
@@ -337,6 +618,10 @@ export class InteractiveSessionController {
337
618
  flowItems: this.visibleFlowItems.map((item) => ({
338
619
  key: item.key,
339
620
  label: this.renderFlowTreeLabel(item),
621
+ kind: item.kind,
622
+ name: item.name,
623
+ depth: item.depth,
624
+ ...(item.kind === "folder" ? { expanded: this.expandedFlowFolders.has(item.key) } : {}),
340
625
  })),
341
626
  selectedFlowIndex: Math.max(0, this.visibleFlowItems.findIndex((item) => item.key === this.state.selectedFlowItemKey)),
342
627
  progressTitle: this.panelTitle("Current Flow", "progress"),
@@ -352,7 +637,9 @@ export class InteractiveSessionController {
352
637
  logText: this.logText,
353
638
  logScrollOffset: this.state.logScrollOffset,
354
639
  confirmText: this.renderConfirmText(),
640
+ confirmation: this.renderConfirmationView(),
355
641
  form: this.renderFormView(layout),
642
+ artifactExplorer: { ...this.state.artifactExplorer },
356
643
  };
357
644
  }
358
645
  emitChange(event = { type: "render" }) {
@@ -395,7 +682,7 @@ export class InteractiveSessionController {
395
682
  const current = this.state.currentFlowId ?? selectHeaderLabel(this.selectedFlowTreeItem(), this.state.selectedFlowId);
396
683
  const pathParts = this.options.cwd.split(path.sep).filter(Boolean);
397
684
  const folderName = pathParts.slice(-3).join("/") || this.options.cwd;
398
- const branchLabel = this.options.gitBranchName ? this.options.gitBranchName : "detached-head";
685
+ const branchLabel = this.state.gitBranchName ? this.state.gitBranchName : "detached-head";
399
686
  const runningSuffix = this.state.busy ? " [running]" : "";
400
687
  const versionLabel = this.state.version ? ` | Version ${this.state.version}` : "";
401
688
  const jiraLabel = this.state.jiraIssueKey ? ` | Jira ${this.state.jiraIssueKey}` : "";
@@ -512,6 +799,20 @@ export class InteractiveSessionController {
512
799
  lines.push("", actionLabels, "", "Left/Right or Tab: choose Enter: confirm Esc: cancel");
513
800
  return lines.join("\n");
514
801
  }
802
+ renderConfirmationView() {
803
+ const session = this.confirmSession;
804
+ const text = this.renderConfirmText();
805
+ if (!session || !text) {
806
+ return null;
807
+ }
808
+ return {
809
+ kind: session.kind,
810
+ flowId: session.flowId,
811
+ text,
812
+ actions: this.confirmActions(),
813
+ selectedAction: session.selectedAction,
814
+ };
815
+ }
515
816
  renderFormView(layout) {
516
817
  const session = this.activeFormSession;
517
818
  const field = this.currentFormField();
@@ -573,6 +874,12 @@ export class InteractiveSessionController {
573
874
  title: "User Input",
574
875
  content: lines.join("\n"),
575
876
  footer,
877
+ formId: session.form.formId,
878
+ definition: session.form,
879
+ values: { ...session.values },
880
+ fields: session.form.fields.map((candidate) => resolveFieldDefinition(candidate, session.values)),
881
+ currentFieldId: field.id,
882
+ error: session.validationError,
576
883
  };
577
884
  }
578
885
  currentFormField() {
@@ -609,7 +916,7 @@ export class InteractiveSessionController {
609
916
  return;
610
917
  }
611
918
  if (key.name === "enter") {
612
- await this.acceptConfirm();
919
+ await this.acceptActiveConfirm();
613
920
  }
614
921
  }
615
922
  async handleFlowKey(key) {
@@ -661,27 +968,27 @@ export class InteractiveSessionController {
661
968
  const maxOffset = this.panelMaxScroll(panel);
662
969
  const current = this.scrollOffsetFor(panel);
663
970
  if (key.name === "up") {
664
- this.setScrollOffset(panel, current - 1, maxOffset);
971
+ this.applyScrollOffset(panel, current - 1, maxOffset);
665
972
  return;
666
973
  }
667
974
  if (key.name === "down") {
668
- this.setScrollOffset(panel, current + 1, maxOffset);
975
+ this.applyScrollOffset(panel, current + 1, maxOffset);
669
976
  return;
670
977
  }
671
978
  if (key.name === "pageup") {
672
- this.setScrollOffset(panel, current - 10, maxOffset);
979
+ this.applyScrollOffset(panel, current - 10, maxOffset);
673
980
  return;
674
981
  }
675
982
  if (key.name === "pagedown") {
676
- this.setScrollOffset(panel, current + 10, maxOffset);
983
+ this.applyScrollOffset(panel, current + 10, maxOffset);
677
984
  return;
678
985
  }
679
986
  if (key.name === "home") {
680
- this.setScrollOffset(panel, 0, maxOffset);
987
+ this.applyScrollOffset(panel, 0, maxOffset);
681
988
  return;
682
989
  }
683
990
  if (key.name === "end") {
684
- this.setScrollOffset(panel, maxOffset, maxOffset);
991
+ this.applyScrollOffset(panel, maxOffset, maxOffset);
685
992
  }
686
993
  }
687
994
  moveSelectedFlow(delta) {
@@ -853,7 +1160,7 @@ export class InteractiveSessionController {
853
1160
  this.confirmSession.selectedAction = (actions[nextIndex] ?? "cancel");
854
1161
  this.emitChange();
855
1162
  }
856
- async acceptConfirm() {
1163
+ async acceptActiveConfirm() {
857
1164
  const session = this.confirmSession;
858
1165
  if (!session) {
859
1166
  return;
@@ -978,7 +1285,7 @@ export class InteractiveSessionController {
978
1285
  }
979
1286
  return this.state.logScrollOffset;
980
1287
  }
981
- setScrollOffset(panel, value, maxOffset) {
1288
+ applyScrollOffset(panel, value, maxOffset) {
982
1289
  const next = clamp(value, 0, maxOffset);
983
1290
  if (panel === "progress") {
984
1291
  this.state.progressScrollOffset = next;
@@ -1100,6 +1407,7 @@ export class InteractiveSessionController {
1100
1407
  }
1101
1408
  this.syncActiveSelectFieldValue();
1102
1409
  try {
1410
+ session.validationError = null;
1103
1411
  validateUserInputValues(session.form, session.values);
1104
1412
  const result = {
1105
1413
  formId: session.form.formId,
@@ -1112,7 +1420,9 @@ export class InteractiveSessionController {
1112
1420
  this.emitChange();
1113
1421
  }
1114
1422
  catch (error) {
1115
- this.appendLog(error.message);
1423
+ session.validationError = error.message;
1424
+ this.appendLog(session.validationError);
1425
+ this.emitChange();
1116
1426
  }
1117
1427
  }
1118
1428
  cancelActiveForm() {
@@ -562,8 +562,8 @@ class InkInteractiveSession {
562
562
  clearSummary() {
563
563
  this.controller.clearSummary();
564
564
  }
565
- setScope(scopeKey, jiraIssueKey) {
566
- this.controller.setScope(scopeKey, jiraIssueKey);
565
+ setScope(scopeKey, jiraIssueKey, gitBranchName) {
566
+ this.controller.setScope(scopeKey, jiraIssueKey, gitBranchName);
567
567
  }
568
568
  appendLog(text) {
569
569
  this.controller.appendLog(text);
@@ -8,6 +8,7 @@ export function createInitialInteractiveState(options) {
8
8
  return {
9
9
  scopeKey: options.scopeKey,
10
10
  jiraIssueKey: options.jiraIssueKey ?? null,
11
+ gitBranchName: options.gitBranchName,
11
12
  summaryText: options.summaryText.trim(),
12
13
  version: options.version ?? "",
13
14
  flowTreeKeys: flowTree.map((node) => node.key),
@@ -30,5 +31,14 @@ export function createInitialInteractiveState(options) {
30
31
  summaryScrollOffset: 0,
31
32
  logScrollOffset: 0,
32
33
  helpScrollOffset: 0,
34
+ artifactExplorer: {
35
+ available: false,
36
+ open: false,
37
+ scopeKey: null,
38
+ runId: null,
39
+ status: "unavailable",
40
+ label: "Artifact Explorer",
41
+ message: "Artifacts are available after a Web UI workflow run completes.",
42
+ },
33
43
  };
34
44
  }