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.
- package/README.md +112 -23
- package/dist/artifacts.js +41 -0
- package/dist/index.js +258 -29
- package/dist/interactive/controller.js +323 -13
- package/dist/interactive/ink/index.js +2 -2
- package/dist/interactive/state.js +10 -0
- package/dist/interactive/web/index.js +326 -0
- package/dist/interactive/web/protocol.js +160 -0
- package/dist/interactive/web/server.js +1011 -0
- package/dist/interactive/web/static/app.js +1580 -0
- package/dist/interactive/web/static/index.html +114 -0
- package/dist/interactive/web/static/styles.css +2 -0
- package/dist/interactive/web/static/styles.input.css +849 -0
- package/dist/pipeline/flow-catalog.js +4 -0
- package/dist/pipeline/flow-specs/auto-common-guided.json +313 -0
- package/dist/pipeline/flow-specs/auto-common.json +3 -1
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +2 -0
- package/dist/pipeline/flow-specs/design-review.json +2 -0
- package/dist/pipeline/flow-specs/implement.json +3 -1
- package/dist/pipeline/flow-specs/plan.json +4 -0
- package/dist/pipeline/flow-specs/playbook-init.json +199 -0
- package/dist/pipeline/flow-specs/review/review-fix.json +3 -1
- package/dist/pipeline/flow-specs/review/review-loop.json +4 -0
- package/dist/pipeline/flow-specs/review/review.json +2 -0
- package/dist/pipeline/node-registry.js +45 -0
- package/dist/pipeline/nodes/flow-run-node.js +13 -1
- package/dist/pipeline/nodes/playbook-ensure-node.js +115 -0
- package/dist/pipeline/nodes/playbook-inventory-node.js +51 -0
- package/dist/pipeline/nodes/playbook-questions-form-node.js +166 -0
- package/dist/pipeline/nodes/playbook-write-node.js +243 -0
- package/dist/pipeline/nodes/project-guidance-node.js +69 -0
- package/dist/pipeline/prompt-registry.js +4 -1
- package/dist/pipeline/prompt-runtime.js +6 -2
- package/dist/pipeline/spec-types.js +19 -0
- package/dist/pipeline/value-resolver.js +39 -1
- package/dist/playbook/practice-candidates.js +12 -0
- package/dist/playbook/repo-inventory.js +208 -0
- package/dist/prompts.js +31 -0
- package/dist/runtime/artifact-catalog.js +379 -0
- package/dist/runtime/playbook.js +485 -0
- package/dist/runtime/project-guidance.js +339 -0
- package/dist/structured-artifact-schema-registry.js +8 -0
- package/dist/structured-artifact-schemas.json +235 -0
- package/dist/structured-artifacts.js +7 -1
- package/docs/declarative-workflows.md +565 -0
- package/docs/features.md +77 -0
- package/docs/playbook.md +327 -0
- 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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
971
|
+
this.applyScrollOffset(panel, current - 1, maxOffset);
|
|
665
972
|
return;
|
|
666
973
|
}
|
|
667
974
|
if (key.name === "down") {
|
|
668
|
-
this.
|
|
975
|
+
this.applyScrollOffset(panel, current + 1, maxOffset);
|
|
669
976
|
return;
|
|
670
977
|
}
|
|
671
978
|
if (key.name === "pageup") {
|
|
672
|
-
this.
|
|
979
|
+
this.applyScrollOffset(panel, current - 10, maxOffset);
|
|
673
980
|
return;
|
|
674
981
|
}
|
|
675
982
|
if (key.name === "pagedown") {
|
|
676
|
-
this.
|
|
983
|
+
this.applyScrollOffset(panel, current + 10, maxOffset);
|
|
677
984
|
return;
|
|
678
985
|
}
|
|
679
986
|
if (key.name === "home") {
|
|
680
|
-
this.
|
|
987
|
+
this.applyScrollOffset(panel, 0, maxOffset);
|
|
681
988
|
return;
|
|
682
989
|
}
|
|
683
990
|
if (key.name === "end") {
|
|
684
|
-
this.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|