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,9 +1,23 @@
1
1
  (function () {
2
2
  "use strict";
3
3
 
4
+ var AUTO_FLOW_HEIGHT_DEFAULT = 520;
5
+ var AUTO_FLOW_HEIGHT_MIN = 120;
6
+ var AUTO_FLOW_HEIGHT_MAX = 640;
7
+ var AUTO_FLOW_LOWER_PANELS_MIN = 180;
8
+ var WORKSPACE_SPLIT_DEFAULT = 36;
9
+ var WORKSPACE_SPLIT_MIN = 24;
10
+ var WORKSPACE_SPLIT_MAX = 58;
11
+ var autoFlowResizeDrag = null;
12
+ var workspaceResizeDrag = null;
13
+
4
14
  var state = {
5
15
  viewModel: null,
6
16
  connectionState: "connecting",
17
+ theme: "light",
18
+ autoFlowHeight: null,
19
+ workspaceSplit: WORKSPACE_SPLIT_DEFAULT,
20
+ logAutoscroll: true,
7
21
  formValues: {},
8
22
  modalSignature: null,
9
23
  artifacts: {
@@ -18,6 +32,26 @@
18
32
  previewRequestId: 0,
19
33
  viewerModes: {},
20
34
  },
35
+ gitSelectedPaths: [],
36
+ gitCommitMessage: "",
37
+ gitDiff: {
38
+ open: false,
39
+ selectedPath: null,
40
+ mode: "head",
41
+ loading: false,
42
+ error: null,
43
+ diff: null,
44
+ requestId: 0,
45
+ },
46
+ scrollSync: {
47
+ suppress: false,
48
+ releaseTimer: null,
49
+ sentOffsets: {
50
+ progress: null,
51
+ log: null,
52
+ help: null,
53
+ },
54
+ },
21
55
  };
22
56
 
23
57
  var elements = {
@@ -28,15 +62,45 @@
28
62
  run: document.getElementById("run-button"),
29
63
  interrupt: document.getElementById("interrupt-button"),
30
64
  help: document.getElementById("help-button"),
65
+ themeToggle: document.getElementById("theme-toggle-button"),
66
+ themeToggleLabel: document.getElementById("theme-toggle-label"),
31
67
  flowsTitle: document.getElementById("flows-title"),
32
68
  flows: document.getElementById("flows-list"),
33
- description: document.getElementById("description-text"),
69
+ autoFlowEditor: document.getElementById("auto-flow-editor"),
70
+ autoFlowResizer: document.getElementById("auto-flow-resizer"),
71
+ splitPanels: document.getElementById("split-panels"),
72
+ workspaceResizer: document.getElementById("workspace-resizer"),
34
73
  progressTitle: document.getElementById("progress-title"),
74
+ progressFlowLabel: document.getElementById("progress-flow-label"),
35
75
  progress: document.getElementById("progress-text"),
36
- summaryTitle: document.getElementById("summary-title"),
37
- summary: document.getElementById("summary-text"),
76
+ gitRefresh: document.getElementById("git-refresh-button"),
77
+ gitSummary: document.getElementById("git-summary"),
78
+ gitBranchInput: document.getElementById("git-branch-input"),
79
+ gitCreateBranch: document.getElementById("git-create-branch-button"),
80
+ gitCheckoutSelect: document.getElementById("git-checkout-select"),
81
+ gitCheckout: document.getElementById("git-checkout-button"),
82
+ gitFetch: document.getElementById("git-fetch-button"),
83
+ gitPull: document.getElementById("git-pull-button"),
84
+ gitFiles: document.getElementById("git-files"),
85
+ gitCommitMessage: document.getElementById("git-commit-message"),
86
+ gitStage: document.getElementById("git-stage-button"),
87
+ gitUnstage: document.getElementById("git-unstage-button"),
88
+ gitCommit: document.getElementById("git-commit-button"),
89
+ gitPush: document.getElementById("git-push-button"),
90
+ gitFeedback: document.getElementById("git-feedback"),
91
+ gitDiffDrawer: document.getElementById("git-diff-drawer"),
92
+ gitDiffClose: document.getElementById("git-diff-close-button"),
93
+ gitDiffTitle: document.getElementById("git-diff-title"),
94
+ gitDiffMeta: document.getElementById("git-diff-meta"),
95
+ gitDiffStatus: document.getElementById("git-diff-status"),
96
+ gitDiffFileList: document.getElementById("git-diff-file-list"),
97
+ gitDiffSelectedTitle: document.getElementById("git-diff-selected-title"),
98
+ gitDiffSelectedMeta: document.getElementById("git-diff-selected-meta"),
99
+ gitDiffModeControls: document.getElementById("git-diff-mode-controls"),
100
+ gitDiffBody: document.getElementById("git-diff-body"),
38
101
  logTitle: document.getElementById("log-title"),
39
102
  log: document.getElementById("log-text"),
103
+ logAutoscroll: document.getElementById("log-autoscroll-toggle"),
40
104
  clearLog: document.getElementById("clear-log-button"),
41
105
  artifactOpen: document.getElementById("artifact-open-button"),
42
106
  artifactDrawer: document.getElementById("artifact-drawer"),
@@ -67,10 +131,411 @@
67
131
  return fallback;
68
132
  }
69
133
 
134
+ function normalizeTheme(value) {
135
+ return value === "dark" ? "dark" : "light";
136
+ }
137
+
138
+ function isTheme(value) {
139
+ return value === "dark" || value === "light";
140
+ }
141
+
142
+ function persistThemePreference(theme) {
143
+ api.updateSettings({ theme: normalizeTheme(theme) });
144
+ }
145
+
146
+ function applyTheme(theme) {
147
+ var nextTheme = normalizeTheme(theme);
148
+ var root = document.documentElement || document.body;
149
+ state.theme = nextTheme;
150
+ if (root) {
151
+ root.setAttribute("data-theme", nextTheme);
152
+ }
153
+ if (elements.themeToggle) {
154
+ var dark = nextTheme === "dark";
155
+ elements.themeToggle.setAttribute("aria-pressed", dark ? "true" : "false");
156
+ elements.themeToggle.setAttribute("aria-label", dark ? "Switch to light theme" : "Switch to dark theme");
157
+ elements.themeToggle.title = dark ? "Switch to light theme" : "Switch to dark theme";
158
+ }
159
+ if (elements.themeToggleLabel) {
160
+ elements.themeToggleLabel.textContent = nextTheme === "dark" ? "Dark" : "Light";
161
+ }
162
+ }
163
+
164
+ function toggleTheme() {
165
+ var nextTheme = state.theme === "dark" ? "light" : "dark";
166
+ applyTheme(nextTheme);
167
+ persistThemePreference(nextTheme);
168
+ }
169
+
170
+ function persistAutoFlowHeight(height) {
171
+ var clamped = clampAutoFlowHeight(height);
172
+ api.updateSettings({ autoFlowHeight: clamped });
173
+ }
174
+
175
+ function autoFlowHeightBounds() {
176
+ var max = AUTO_FLOW_HEIGHT_MAX;
177
+ var detailsPane = elements && elements.autoFlowEditor ? elements.autoFlowEditor.parentNode : null;
178
+ if (detailsPane) {
179
+ var rectHeight = typeof detailsPane.getBoundingClientRect === "function"
180
+ ? detailsPane.getBoundingClientRect().height
181
+ : 0;
182
+ if (Number.isFinite(rectHeight) && rectHeight > AUTO_FLOW_HEIGHT_MIN + AUTO_FLOW_LOWER_PANELS_MIN) {
183
+ max = Math.min(max, rectHeight - AUTO_FLOW_LOWER_PANELS_MIN);
184
+ }
185
+ }
186
+ return {
187
+ min: AUTO_FLOW_HEIGHT_MIN,
188
+ max: Math.max(AUTO_FLOW_HEIGHT_MIN, max),
189
+ };
190
+ }
191
+
192
+ function clampAutoFlowHeight(value) {
193
+ var numeric = Number(value);
194
+ if (!Number.isFinite(numeric)) {
195
+ return null;
196
+ }
197
+ var bounds = autoFlowHeightBounds();
198
+ return Math.min(bounds.max, Math.max(bounds.min, Math.round(numeric)));
199
+ }
200
+
201
+ function getAutoFlowEditorHeight() {
202
+ if (!elements.autoFlowEditor) {
203
+ return state.autoFlowHeight || AUTO_FLOW_HEIGHT_DEFAULT;
204
+ }
205
+ if (typeof elements.autoFlowEditor.getBoundingClientRect === "function") {
206
+ var rect = elements.autoFlowEditor.getBoundingClientRect();
207
+ if (rect && Number.isFinite(rect.height) && rect.height > 0) {
208
+ return rect.height;
209
+ }
210
+ }
211
+ var inlineHeight = parseInt(elements.autoFlowEditor.style.height || "", 10);
212
+ return Number.isFinite(inlineHeight) ? inlineHeight : (state.autoFlowHeight || AUTO_FLOW_HEIGHT_DEFAULT);
213
+ }
214
+
215
+ function applyAutoFlowHeight(height) {
216
+ if (!elements.autoFlowEditor) {
217
+ return;
218
+ }
219
+ var clamped = clampAutoFlowHeight(height);
220
+ if (clamped === null) {
221
+ state.autoFlowHeight = null;
222
+ elements.autoFlowEditor.style.height = "";
223
+ updateAutoFlowResizerState(null);
224
+ return;
225
+ }
226
+ state.autoFlowHeight = clamped;
227
+ elements.autoFlowEditor.style.height = clamped + "px";
228
+ updateAutoFlowResizerState(clamped);
229
+ }
230
+
231
+ function updateAutoFlowResizerState(currentHeight) {
232
+ if (!elements.autoFlowResizer) {
233
+ return;
234
+ }
235
+ var bounds = autoFlowHeightBounds();
236
+ elements.autoFlowResizer.setAttribute("aria-valuemin", String(bounds.min));
237
+ elements.autoFlowResizer.setAttribute("aria-valuemax", String(bounds.max));
238
+ elements.autoFlowResizer.setAttribute("aria-valuenow", String(Math.round(currentHeight || getAutoFlowEditorHeight())));
239
+ }
240
+
241
+ function beginAutoFlowResize(event) {
242
+ if (!elements.autoFlowEditor || elements.autoFlowEditor.hidden) {
243
+ return;
244
+ }
245
+ if (event.button !== undefined && event.button !== 0) {
246
+ return;
247
+ }
248
+ if (event.preventDefault) {
249
+ event.preventDefault();
250
+ }
251
+ autoFlowResizeDrag = {
252
+ startY: Number.isFinite(Number(event.clientY)) ? Number(event.clientY) : 0,
253
+ startHeight: getAutoFlowEditorHeight(),
254
+ };
255
+ document.body.classList.add("auto-flow-resizing");
256
+ elements.autoFlowResizer.classList.add("resizing");
257
+ if (event.currentTarget && typeof event.currentTarget.setPointerCapture === "function" && event.pointerId !== undefined) {
258
+ try {
259
+ event.currentTarget.setPointerCapture(event.pointerId);
260
+ } catch {
261
+ // Pointer capture is a progressive enhancement for smoother dragging.
262
+ }
263
+ }
264
+ window.addEventListener("pointermove", updateAutoFlowResize);
265
+ window.addEventListener("pointerup", finishAutoFlowResize);
266
+ window.addEventListener("pointercancel", finishAutoFlowResize);
267
+ }
268
+
269
+ function updateAutoFlowResize(event) {
270
+ if (!autoFlowResizeDrag) {
271
+ return;
272
+ }
273
+ if (event.preventDefault) {
274
+ event.preventDefault();
275
+ }
276
+ var clientY = Number(event.clientY);
277
+ if (!Number.isFinite(clientY)) {
278
+ clientY = autoFlowResizeDrag.startY;
279
+ }
280
+ applyAutoFlowHeight(autoFlowResizeDrag.startHeight + clientY - autoFlowResizeDrag.startY);
281
+ }
282
+
283
+ function finishAutoFlowResize() {
284
+ if (!autoFlowResizeDrag) {
285
+ return;
286
+ }
287
+ autoFlowResizeDrag = null;
288
+ document.body.classList.remove("auto-flow-resizing");
289
+ elements.autoFlowResizer.classList.remove("resizing");
290
+ persistAutoFlowHeight(state.autoFlowHeight);
291
+ window.removeEventListener("pointermove", updateAutoFlowResize);
292
+ window.removeEventListener("pointerup", finishAutoFlowResize);
293
+ window.removeEventListener("pointercancel", finishAutoFlowResize);
294
+ }
295
+
296
+ function resetAutoFlowHeight() {
297
+ applyAutoFlowHeight(null);
298
+ persistAutoFlowHeight(null);
299
+ }
300
+
301
+ function handleAutoFlowResizerKeydown(event) {
302
+ var step = event.shiftKey ? 48 : 20;
303
+ var nextHeight = getAutoFlowEditorHeight();
304
+ if (event.key === "ArrowUp") {
305
+ nextHeight -= step;
306
+ } else if (event.key === "ArrowDown") {
307
+ nextHeight += step;
308
+ } else if (event.key === "Home") {
309
+ nextHeight = AUTO_FLOW_HEIGHT_MIN;
310
+ } else if (event.key === "End") {
311
+ nextHeight = AUTO_FLOW_HEIGHT_MAX;
312
+ } else {
313
+ return;
314
+ }
315
+ event.preventDefault();
316
+ applyAutoFlowHeight(nextHeight);
317
+ persistAutoFlowHeight(state.autoFlowHeight);
318
+ }
319
+
320
+ function persistWorkspaceSplit(split) {
321
+ var nextSplit = Number.isFinite(split) ? clampWorkspaceSplit(split) : WORKSPACE_SPLIT_DEFAULT;
322
+ api.updateSettings({ workspaceSplit: nextSplit });
323
+ }
324
+
325
+ function clampWorkspaceSplit(value) {
326
+ var numeric = Number(value);
327
+ if (!Number.isFinite(numeric)) {
328
+ return WORKSPACE_SPLIT_DEFAULT;
329
+ }
330
+ return Math.min(WORKSPACE_SPLIT_MAX, Math.max(WORKSPACE_SPLIT_MIN, Math.round(numeric)));
331
+ }
332
+
333
+ function applyWorkspaceSplit(split) {
334
+ if (!elements.splitPanels) {
335
+ return;
336
+ }
337
+ var clamped = clampWorkspaceSplit(split);
338
+ state.workspaceSplit = clamped;
339
+ if (elements.splitPanels.style && typeof elements.splitPanels.style.setProperty === "function") {
340
+ elements.splitPanels.style.setProperty("--aw-work-panel-width", clamped + "%");
341
+ } else if (elements.splitPanels.style) {
342
+ elements.splitPanels.style["--aw-work-panel-width"] = clamped + "%";
343
+ }
344
+ updateWorkspaceResizerState(clamped);
345
+ }
346
+
347
+ function workspacePanelWidth() {
348
+ if (!elements.splitPanels || typeof elements.splitPanels.getBoundingClientRect !== "function") {
349
+ return 0;
350
+ }
351
+ var rect = elements.splitPanels.getBoundingClientRect();
352
+ return rect && Number.isFinite(rect.width) ? rect.width : 0;
353
+ }
354
+
355
+ function updateWorkspaceResizerState(split) {
356
+ if (!elements.workspaceResizer) {
357
+ return;
358
+ }
359
+ elements.workspaceResizer.setAttribute("aria-valuemin", String(WORKSPACE_SPLIT_MIN));
360
+ elements.workspaceResizer.setAttribute("aria-valuemax", String(WORKSPACE_SPLIT_MAX));
361
+ elements.workspaceResizer.setAttribute("aria-valuenow", String(clampWorkspaceSplit(split)));
362
+ }
363
+
364
+ function beginWorkspaceResize(event) {
365
+ if (event.button !== undefined && event.button !== 0) {
366
+ return;
367
+ }
368
+ var width = workspacePanelWidth();
369
+ if (width <= 0) {
370
+ return;
371
+ }
372
+ if (event.preventDefault) {
373
+ event.preventDefault();
374
+ }
375
+ workspaceResizeDrag = {
376
+ startX: Number.isFinite(Number(event.clientX)) ? Number(event.clientX) : 0,
377
+ startSplit: state.workspaceSplit,
378
+ width: width,
379
+ };
380
+ document.body.classList.add("workspace-split-resizing");
381
+ elements.workspaceResizer.classList.add("resizing");
382
+ if (event.currentTarget && typeof event.currentTarget.setPointerCapture === "function" && event.pointerId !== undefined) {
383
+ try {
384
+ event.currentTarget.setPointerCapture(event.pointerId);
385
+ } catch {
386
+ // Pointer capture is a progressive enhancement for smoother dragging.
387
+ }
388
+ }
389
+ window.addEventListener("pointermove", updateWorkspaceResize);
390
+ window.addEventListener("pointerup", finishWorkspaceResize);
391
+ window.addEventListener("pointercancel", finishWorkspaceResize);
392
+ }
393
+
394
+ function updateWorkspaceResize(event) {
395
+ if (!workspaceResizeDrag) {
396
+ return;
397
+ }
398
+ if (event.preventDefault) {
399
+ event.preventDefault();
400
+ }
401
+ var clientX = Number(event.clientX);
402
+ if (!Number.isFinite(clientX)) {
403
+ clientX = workspaceResizeDrag.startX;
404
+ }
405
+ var nextSplit = workspaceResizeDrag.startSplit + ((clientX - workspaceResizeDrag.startX) / workspaceResizeDrag.width) * 100;
406
+ applyWorkspaceSplit(nextSplit);
407
+ }
408
+
409
+ function finishWorkspaceResize() {
410
+ if (!workspaceResizeDrag) {
411
+ return;
412
+ }
413
+ workspaceResizeDrag = null;
414
+ document.body.classList.remove("workspace-split-resizing");
415
+ elements.workspaceResizer.classList.remove("resizing");
416
+ persistWorkspaceSplit(state.workspaceSplit);
417
+ window.removeEventListener("pointermove", updateWorkspaceResize);
418
+ window.removeEventListener("pointerup", finishWorkspaceResize);
419
+ window.removeEventListener("pointercancel", finishWorkspaceResize);
420
+ }
421
+
422
+ function resetWorkspaceSplit() {
423
+ applyWorkspaceSplit(WORKSPACE_SPLIT_DEFAULT);
424
+ persistWorkspaceSplit(null);
425
+ }
426
+
427
+ function handleWorkspaceResizerKeydown(event) {
428
+ var step = event.shiftKey ? 6 : 2;
429
+ var nextSplit = state.workspaceSplit;
430
+ if (event.key === "ArrowLeft") {
431
+ nextSplit -= step;
432
+ } else if (event.key === "ArrowRight") {
433
+ nextSplit += step;
434
+ } else if (event.key === "Home") {
435
+ nextSplit = WORKSPACE_SPLIT_MIN;
436
+ } else if (event.key === "End") {
437
+ nextSplit = WORKSPACE_SPLIT_MAX;
438
+ } else {
439
+ return;
440
+ }
441
+ event.preventDefault();
442
+ applyWorkspaceSplit(nextSplit);
443
+ persistWorkspaceSplit(state.workspaceSplit);
444
+ }
445
+
446
+ function persistLogAutoscroll(enabled) {
447
+ api.updateSettings({ logAutoscroll: Boolean(enabled) });
448
+ }
449
+
450
+ function applyLogAutoscroll(enabled) {
451
+ state.logAutoscroll = Boolean(enabled);
452
+ if (elements.logAutoscroll) {
453
+ elements.logAutoscroll.checked = state.logAutoscroll;
454
+ }
455
+ if (state.logAutoscroll) {
456
+ scrollLogToBottom();
457
+ }
458
+ }
459
+
460
+ function applyWebUiSettings(settings) {
461
+ if (!settings || typeof settings !== "object") {
462
+ return;
463
+ }
464
+ if (isTheme(settings.theme)) {
465
+ applyTheme(settings.theme);
466
+ }
467
+ if ("autoFlowHeight" in settings) {
468
+ applyAutoFlowHeight(settings.autoFlowHeight);
469
+ }
470
+ if (Number.isFinite(Number(settings.workspaceSplit))) {
471
+ applyWorkspaceSplit(settings.workspaceSplit);
472
+ }
473
+ if (typeof settings.logAutoscroll === "boolean") {
474
+ applyLogAutoscroll(settings.logAutoscroll);
475
+ }
476
+ }
477
+
478
+ function scrollLogToBottom() {
479
+ if (!elements.log) {
480
+ return;
481
+ }
482
+ elements.log.scrollTop = elements.log.scrollHeight;
483
+ rememberScrollOffset("log", elements.log.scrollTop);
484
+ }
485
+
70
486
  function actionId() {
71
487
  return "web-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
72
488
  }
73
489
 
490
+ function controlId(prefix) {
491
+ var parts = Array.prototype.slice.call(arguments, 1).map(function (part) {
492
+ return String(part || "")
493
+ .trim()
494
+ .replace(/[^A-Za-z0-9_-]+/g, "-")
495
+ .replace(/^-+|-+$/g, "") || "value";
496
+ });
497
+ return [prefix].concat(parts).join("-");
498
+ }
499
+
500
+ function roundedScrollTop(element) {
501
+ return Math.round(Number(element.scrollTop) || 0);
502
+ }
503
+
504
+ function rememberScrollOffset(pane, offset) {
505
+ if (state.scrollSync.sentOffsets[pane] !== undefined) {
506
+ state.scrollSync.sentOffsets[pane] = Math.round(Number(offset) || 0);
507
+ }
508
+ }
509
+
510
+ function rememberCurrentScrollOffsets() {
511
+ rememberScrollOffset("progress", elements.progress ? elements.progress.scrollTop : 0);
512
+ rememberScrollOffset("log", elements.log ? elements.log.scrollTop : 0);
513
+ rememberScrollOffset("help", elements.helpText ? elements.helpText.scrollTop : 0);
514
+ }
515
+
516
+ function releaseScrollSuppressionSoon() {
517
+ if (state.scrollSync.releaseTimer) {
518
+ clearTimeout(state.scrollSync.releaseTimer);
519
+ }
520
+ state.scrollSync.releaseTimer = setTimeout(function () {
521
+ state.scrollSync.suppress = false;
522
+ state.scrollSync.releaseTimer = null;
523
+ rememberCurrentScrollOffsets();
524
+ }, 0);
525
+ }
526
+
527
+ function sendScrollPane(pane, element) {
528
+ if (state.scrollSync.suppress) {
529
+ return;
530
+ }
531
+ var offset = roundedScrollTop(element);
532
+ if (state.scrollSync.sentOffsets[pane] === offset) {
533
+ return;
534
+ }
535
+ rememberScrollOffset(pane, offset);
536
+ api.scrollPane(pane, offset);
537
+ }
538
+
74
539
  function selectedFlow() {
75
540
  var vm = state.viewModel;
76
541
  if (!vm || !Array.isArray(vm.flowItems)) {
@@ -172,6 +637,60 @@
172
637
  scrollPane: function (pane, offset) {
173
638
  api.send({ type: "scroll", pane: pane, offset: offset });
174
639
  },
640
+ refreshGit: function () {
641
+ api.send({ type: "git.refresh" });
642
+ },
643
+ createGitBranch: function () {
644
+ api.send({ type: "git.createBranch", branchName: elements.gitBranchInput.value });
645
+ },
646
+ checkoutGitBranch: function () {
647
+ api.send({ type: "git.checkout", branchName: elements.gitCheckoutSelect.value });
648
+ },
649
+ fetchGit: function () {
650
+ api.send({ type: "git.fetch" });
651
+ },
652
+ pullGit: function () {
653
+ api.send({ type: "git.pullFfOnly" });
654
+ },
655
+ stageGit: function () {
656
+ api.send({ type: "git.stage", paths: selectedGitPaths() });
657
+ },
658
+ unstageGit: function () {
659
+ api.send({ type: "git.unstage", paths: selectedGitPaths() });
660
+ },
661
+ commitGit: function () {
662
+ api.send({ type: "git.commit", paths: selectedGitPaths(), message: elements.gitCommitMessage.value });
663
+ },
664
+ pushGit: function () {
665
+ api.send({ type: "git.push" });
666
+ },
667
+ updateGitCommitMessage: function () {
668
+ api.send({ type: "git.updateCommitMessage", message: elements.gitCommitMessage.value });
669
+ },
670
+ updateSettings: function (settings) {
671
+ api.send({ type: "settings.update", settings: settings });
672
+ },
673
+ selectAutoFlowPreset: function (preset) {
674
+ api.send({ type: "autoFlow.selectPreset", preset: preset });
675
+ },
676
+ saveAutoFlow: function () {
677
+ api.send({ type: "autoFlow.save" });
678
+ },
679
+ resetAutoFlow: function () {
680
+ api.send({ type: "autoFlow.reset" });
681
+ },
682
+ toggleAutoFlowBlock: function (slotId, blockId, enabled) {
683
+ api.send({ type: "autoFlow.toggleBlock", slotId: slotId, blockId: blockId, enabled: enabled });
684
+ },
685
+ updateAutoFlowParam: function (slotId, blockId, paramName, value) {
686
+ api.send({ type: "autoFlow.updateParam", slotId: slotId, blockId: blockId, paramName: paramName, value: value });
687
+ },
688
+ insertAutoFlowBlock: function (slotId, blockId) {
689
+ api.send({ type: "autoFlow.insertBlock", slotId: slotId, blockId: blockId });
690
+ },
691
+ removeAutoFlowBlock: function (slotId, blockId) {
692
+ api.send({ type: "autoFlow.removeBlock", slotId: slotId, blockId: blockId });
693
+ },
175
694
  };
176
695
 
177
696
  function setConnection(nextState) {
@@ -190,11 +709,18 @@
190
709
  }
191
710
 
192
711
  if (message.type === "snapshot") {
712
+ applyWebUiSettings(message.settings);
193
713
  var uiState = captureUiState();
194
714
  state.viewModel = message.viewModel || {};
195
715
  state.formValues = state.viewModel.form ? Object.assign({}, state.viewModel.form.values || {}) : {};
716
+ state.gitSelectedPaths = state.viewModel.gitWorkspace && Array.isArray(state.viewModel.gitWorkspace.selectedPaths)
717
+ ? state.viewModel.gitWorkspace.selectedPaths.slice()
718
+ : [];
719
+ state.scrollSync.suppress = true;
196
720
  render();
197
721
  restoreUiState(uiState);
722
+ rememberCurrentScrollOffsets();
723
+ releaseScrollSuppressionSoon();
198
724
  return;
199
725
  }
200
726
  if (message.type === "log.append") {
@@ -215,8 +741,8 @@
215
741
  if (!lines) return;
216
742
  var wasPinned = elements.log.scrollTop + elements.log.clientHeight >= elements.log.scrollHeight - 8;
217
743
  elements.log.textContent += (elements.log.textContent ? "\n" : "") + lines;
218
- if (wasPinned) {
219
- elements.log.scrollTop = elements.log.scrollHeight;
744
+ if (state.logAutoscroll || wasPinned) {
745
+ scrollLogToBottom();
220
746
  }
221
747
  }
222
748
 
@@ -226,30 +752,967 @@
226
752
  elements.header.textContent = text(vm.header, "Local operator console");
227
753
  elements.status.textContent = text(vm.statusText, "Idle");
228
754
  elements.flowsTitle.textContent = text(vm.flowListTitle, "Flows");
229
- elements.description.textContent = text(vm.descriptionText, "No flow selected.");
755
+ renderAutoFlowEditor(vm);
230
756
  elements.progressTitle.textContent = text(vm.progressTitle, "Progress");
231
- setTextPreservingScroll(elements.progress, text(vm.progressText, "No progress yet."));
232
- elements.summaryTitle.textContent = text(vm.summaryTitle, "Task Summary");
233
- setTextPreservingScroll(elements.summary, vm.summaryVisible === false ? "Summary is hidden." : text(vm.summaryText, "No task summary yet."));
757
+ renderProgress(vm);
234
758
  elements.logTitle.textContent = text(vm.logTitle, "Activity");
235
- setTextPreservingScroll(elements.log, text(vm.logText, ""));
759
+ setTextPreservingScroll(elements.log, text(vm.logText, ""), state.logAutoscroll);
236
760
  elements.helpText.textContent = text(vm.helpText, "No help is available.");
237
761
  elements.helpPanel.hidden = !vm.helpVisible;
238
762
  elements.help.setAttribute("aria-pressed", vm.helpVisible ? "true" : "false");
239
763
 
240
764
  renderFlows(vm);
765
+ renderGitWorkspace(vm);
766
+ renderGitDiffDrawer(vm);
241
767
  renderModal(vm);
242
768
  renderArtifactExplorer(vm);
243
769
  }
244
770
 
245
- function setTextPreservingScroll(element, value) {
771
+ function gitWorkspace(vm) {
772
+ var workspace = vm && vm.gitWorkspace;
773
+ if (!workspace || typeof workspace !== "object") {
774
+ return {
775
+ available: false,
776
+ changedFiles: [],
777
+ branches: [],
778
+ remotes: [],
779
+ selectedPaths: [],
780
+ commitMessage: "",
781
+ operation: { status: "idle" },
782
+ };
783
+ }
784
+ return workspace;
785
+ }
786
+
787
+ function renderGitWorkspace(vm) {
788
+ var git = gitWorkspace(vm);
789
+ var blocked = hasBlockingInput(vm);
790
+ var changedFiles = Array.isArray(git.changedFiles) ? git.changedFiles : [];
791
+ var branches = Array.isArray(git.branches) ? git.branches : [];
792
+ var operation = git.operation || { status: "idle" };
793
+ var isRunning = operation.status === "running";
794
+
795
+ if (typeof git.commitMessage === "string" && document.activeElement !== elements.gitCommitMessage) {
796
+ state.gitCommitMessage = git.commitMessage;
797
+ elements.gitCommitMessage.value = git.commitMessage;
798
+ }
799
+
800
+ elements.gitSummary.innerHTML = "";
801
+ var summary = document.createElement("span");
802
+ if (!git.available) {
803
+ summary.textContent = git.error || "Git workspace is unavailable.";
804
+ } else {
805
+ summary.append(
806
+ document.createTextNode("Branch "),
807
+ strong(git.detachedHead ? "detached HEAD" : (git.branch || "-")),
808
+ document.createTextNode(git.clean ? " | clean" : " | dirty"),
809
+ document.createTextNode(" | upstream " + (git.upstream || "-")),
810
+ document.createTextNode(" | ahead " + (git.ahead || 0) + " behind " + (git.behind || 0)),
811
+ );
812
+ if (git.lastCommit && git.lastCommit.shortHash) {
813
+ summary.append(document.createTextNode(" | last " + git.lastCommit.shortHash + " " + (git.lastCommit.subject || "")));
814
+ }
815
+ if (git.refreshedAt) {
816
+ summary.append(document.createTextNode(" | refreshed " + git.refreshedAt));
817
+ }
818
+ }
819
+ elements.gitSummary.append(summary);
820
+
821
+ renderGitBranches(branches, git.branch);
822
+ renderGitFiles(changedFiles);
823
+
824
+ elements.gitCreateBranch.disabled = blocked || isRunning || !git.available;
825
+ elements.gitCheckout.disabled = blocked || isRunning || !git.available || !elements.gitCheckoutSelect.value;
826
+ elements.gitFetch.disabled = blocked || isRunning || !git.available;
827
+ elements.gitPull.disabled = blocked || isRunning || !git.available;
828
+ elements.gitStage.disabled = blocked || isRunning || !git.available || selectedStageableGitPaths(git).length === 0;
829
+ elements.gitUnstage.disabled = blocked || isRunning || !git.available || selectedUnstageableGitPaths(git).length === 0;
830
+ elements.gitCommit.disabled = blocked || isRunning || !git.available || elements.gitCommitMessage.value.trim().length === 0;
831
+ elements.gitPush.disabled = blocked || isRunning || !git.available || !git.canPush;
832
+ elements.gitPush.title = git.canPush ? "Push current branch" : (git.pushDisabledReason || "Push is not available.");
833
+
834
+ elements.gitFeedback.className = "git-feedback";
835
+ if (operation.status === "error") {
836
+ elements.gitFeedback.classList.add("error");
837
+ } else if (operation.status === "success") {
838
+ elements.gitFeedback.classList.add("success");
839
+ }
840
+ var feedback = operation.message || git.pushDisabledReason || "";
841
+ var warnings = Array.isArray(git.warnings) && git.warnings.length > 0 ? " " + git.warnings.join(" ") : "";
842
+ elements.gitFeedback.textContent = feedback + warnings;
843
+ }
844
+
845
+ function strong(value) {
846
+ var element = document.createElement("strong");
847
+ element.textContent = value;
848
+ return element;
849
+ }
850
+
851
+ function renderAutoFlowEditor(vm) {
852
+ var model = vm && vm.autoFlow;
853
+ if (!elements.autoFlowEditor) return;
854
+ elements.autoFlowEditor.innerHTML = "";
855
+ if (!model || !Array.isArray(model.slots)) {
856
+ elements.autoFlowEditor.hidden = true;
857
+ if (elements.autoFlowResizer) {
858
+ elements.autoFlowResizer.hidden = true;
859
+ }
860
+ return;
861
+ }
862
+ elements.autoFlowEditor.hidden = false;
863
+ if (elements.autoFlowResizer) {
864
+ elements.autoFlowResizer.hidden = false;
865
+ }
866
+ applyAutoFlowHeight(state.autoFlowHeight);
867
+ var blocked = hasBlockingInput(vm);
868
+
869
+ var toolbar = document.createElement("div");
870
+ toolbar.className = "auto-flow-toolbar";
871
+ var simple = document.createElement("button");
872
+ simple.type = "button";
873
+ simple.textContent = "Simple";
874
+ simple.disabled = blocked;
875
+ simple.className = model.basePreset === "simple" ? "primary" : "";
876
+ simple.addEventListener("click", function () {
877
+ api.selectAutoFlowPreset("simple");
878
+ });
879
+ var standard = document.createElement("button");
880
+ standard.type = "button";
881
+ standard.textContent = "Standard";
882
+ standard.disabled = blocked;
883
+ standard.className = model.basePreset === "standard" ? "primary" : "";
884
+ standard.addEventListener("click", function () {
885
+ api.selectAutoFlowPreset("standard");
886
+ });
887
+ var save = document.createElement("button");
888
+ save.type = "button";
889
+ save.textContent = "Save";
890
+ save.disabled = blocked || !model.status || !model.status.canSave;
891
+ save.addEventListener("click", api.saveAutoFlow);
892
+ var reset = document.createElement("button");
893
+ reset.type = "button";
894
+ reset.textContent = "Reset";
895
+ reset.disabled = blocked || !model.status || !model.status.canReset;
896
+ reset.title = "Discard unsaved auto-flow edits";
897
+ reset.addEventListener("click", api.resetAutoFlow);
898
+ var status = document.createElement("span");
899
+ status.className = "auto-flow-status " + (model.status && model.status.valid ? "valid" : "invalid");
900
+ status.textContent = (model.status && model.status.valid ? "valid" : "invalid") + " | " + text(model.status && model.status.sourceLabel, model.configName || "auto-flow");
901
+ toolbar.append(simple, standard, save, reset, status);
902
+ elements.autoFlowEditor.append(toolbar);
903
+
904
+ if (model.status && model.status.lastMessage) {
905
+ var message = document.createElement("div");
906
+ message.className = "auto-flow-message";
907
+ message.textContent = model.status.lastMessage;
908
+ elements.autoFlowEditor.append(message);
909
+ }
910
+
911
+ if (Array.isArray(model.diagnostics) && model.diagnostics.length > 0) {
912
+ var diagnostics = document.createElement("div");
913
+ diagnostics.className = "auto-flow-diagnostics";
914
+ model.diagnostics.forEach(function (diagnostic) {
915
+ var item = document.createElement("div");
916
+ item.textContent = diagnostic.message || "Invalid auto-flow configuration.";
917
+ diagnostics.append(item);
918
+ });
919
+ elements.autoFlowEditor.append(diagnostics);
920
+ }
921
+
922
+ var slots = document.createElement("div");
923
+ slots.className = "auto-flow-slots";
924
+ model.slots.forEach(function (slot) {
925
+ var slotRow = document.createElement("section");
926
+ slotRow.className = "auto-flow-slot status-" + slot.status;
927
+ slotRow.dataset.slotId = slot.slotId;
928
+ var title = document.createElement("div");
929
+ title.className = "auto-flow-slot-title";
930
+ title.append(strong(text(slot.title, slot.slotId)), document.createTextNode(" "), statusPill(slot.status));
931
+ var reason = document.createElement("div");
932
+ reason.className = "auto-flow-reason";
933
+ reason.textContent = text(slot.reason, "");
934
+ slotRow.append(title, reason);
935
+
936
+ var blockList = document.createElement("div");
937
+ blockList.className = "auto-flow-blocks";
938
+ if (!Array.isArray(slot.blocks) || slot.blocks.length === 0) {
939
+ var empty = document.createElement("div");
940
+ empty.className = "auto-flow-empty";
941
+ empty.textContent = "Empty slot.";
942
+ blockList.append(empty);
943
+ } else {
944
+ slot.blocks.forEach(function (block) {
945
+ blockList.append(renderAutoFlowBlock(block, blocked));
946
+ });
947
+ }
948
+ slotRow.append(blockList);
949
+ var insert = renderAutoFlowInsert(slot, model.availableBlocks || [], blocked);
950
+ if (insert) {
951
+ slotRow.append(insert);
952
+ }
953
+ slots.append(slotRow);
954
+ });
955
+ elements.autoFlowEditor.append(slots);
956
+ updateAutoFlowResizerState(state.autoFlowHeight);
957
+ }
958
+
959
+ function statusPill(status) {
960
+ var pill = document.createElement("span");
961
+ pill.className = "auto-flow-pill status-" + status;
962
+ pill.textContent = status || "pending";
963
+ return pill;
964
+ }
965
+
966
+ function renderAutoFlowBlock(block, blocked) {
967
+ var row = document.createElement("div");
968
+ row.className = "auto-flow-block status-" + block.status;
969
+ row.dataset.blockId = block.blockId;
970
+ row.dataset.slotId = block.slotId;
971
+ var enabled = document.createElement("input");
972
+ enabled.type = "checkbox";
973
+ enabled.id = controlId("auto-flow-enabled", block.slotId, block.blockId);
974
+ enabled.name = enabled.id;
975
+ enabled.checked = block.enabled !== false;
976
+ enabled.disabled = blocked || block.locked || !(block.actions && (block.actions.canEnable || block.actions.canDisable));
977
+ enabled.title = block.locked ? "Locked core block" : "Enable or disable block";
978
+ enabled.addEventListener("change", function () {
979
+ api.toggleAutoFlowBlock(block.slotId, block.blockId, enabled.checked);
980
+ });
981
+ var main = document.createElement("div");
982
+ main.className = "auto-flow-block-main";
983
+ var label = document.createElement("div");
984
+ label.className = "auto-flow-block-label";
985
+ label.append(strong(text(block.title, block.blockId)), document.createTextNode(" "), statusPill(block.status));
986
+ if (block.locked) {
987
+ var locked = document.createElement("span");
988
+ locked.className = "auto-flow-locked";
989
+ locked.textContent = "locked";
990
+ label.append(document.createTextNode(" "), locked);
991
+ }
992
+ var reason = document.createElement("div");
993
+ reason.className = "auto-flow-reason";
994
+ reason.textContent = text(block.reason, "");
995
+ main.append(label, reason);
996
+ if (Array.isArray(block.params) && block.params.length > 0) {
997
+ var params = document.createElement("div");
998
+ params.className = "auto-flow-params";
999
+ block.params.forEach(function (param) {
1000
+ var field = document.createElement("label");
1001
+ field.textContent = param.label + " ";
1002
+ var input = document.createElement("input");
1003
+ input.type = "number";
1004
+ input.id = controlId("auto-flow-param", block.slotId, block.blockId, param.name);
1005
+ input.name = input.id;
1006
+ input.min = String(param.min);
1007
+ input.max = String(param.max);
1008
+ input.step = "1";
1009
+ input.value = param.value === null || param.value === undefined ? "" : String(param.value);
1010
+ input.disabled = blocked || !(block.actions && block.actions.canEditParams);
1011
+ input.addEventListener("change", function () {
1012
+ var next = Number(input.value);
1013
+ if (Number.isInteger(next)) {
1014
+ api.updateAutoFlowParam(block.slotId, block.blockId, param.name, next);
1015
+ }
1016
+ });
1017
+ field.append(input);
1018
+ params.append(field);
1019
+ });
1020
+ main.append(params);
1021
+ }
1022
+ row.append(enabled, main);
1023
+ if (block.actions && block.actions.canRemove) {
1024
+ var remove = document.createElement("button");
1025
+ remove.type = "button";
1026
+ remove.className = "danger";
1027
+ remove.textContent = "Delete";
1028
+ remove.disabled = blocked;
1029
+ remove.title = "Remove block from this slot";
1030
+ remove.addEventListener("click", function (event) {
1031
+ if (event && typeof event.preventDefault === "function") {
1032
+ event.preventDefault();
1033
+ }
1034
+ api.removeAutoFlowBlock(block.slotId, block.blockId);
1035
+ });
1036
+ row.append(remove);
1037
+ }
1038
+ return row;
1039
+ }
1040
+
1041
+ function renderAutoFlowInsert(slot, availableBlocks, blocked) {
1042
+ var configured = new Set((Array.isArray(slot.blocks) ? slot.blocks : []).map(function (block) {
1043
+ return block.blockId;
1044
+ }));
1045
+ var candidates = availableBlocks.filter(function (block) {
1046
+ return Array.isArray(block.allowedSlots)
1047
+ && block.allowedSlots.indexOf(slot.slotId) !== -1
1048
+ && !configured.has(block.blockId);
1049
+ });
1050
+ if (candidates.length === 0) {
1051
+ return null;
1052
+ }
1053
+ var container = document.createElement("div");
1054
+ container.className = "auto-flow-insert";
1055
+ var select = document.createElement("select");
1056
+ select.id = controlId("auto-flow-insert", slot.slotId);
1057
+ select.name = select.id;
1058
+ select.value = "";
1059
+ var placeholder = document.createElement("option");
1060
+ placeholder.value = "";
1061
+ placeholder.textContent = "Add block...";
1062
+ placeholder.disabled = true;
1063
+ placeholder.selected = true;
1064
+ select.append(placeholder);
1065
+ candidates.forEach(function (block) {
1066
+ var option = document.createElement("option");
1067
+ option.value = block.blockId;
1068
+ option.textContent = block.title || block.blockId;
1069
+ select.append(option);
1070
+ });
1071
+ var button = document.createElement("button");
1072
+ button.type = "button";
1073
+ button.textContent = "Insert";
1074
+ button.disabled = blocked || !select.value;
1075
+ select.addEventListener("change", function () {
1076
+ button.disabled = blocked || !select.value;
1077
+ });
1078
+ button.addEventListener("click", function (event) {
1079
+ if (event && typeof event.preventDefault === "function") {
1080
+ event.preventDefault();
1081
+ }
1082
+ if (!select.value) {
1083
+ return;
1084
+ }
1085
+ api.insertAutoFlowBlock(slot.slotId, select.value);
1086
+ });
1087
+ container.append(select, button);
1088
+ return container;
1089
+ }
1090
+
1091
+ function renderGitBranches(branches, currentBranch) {
1092
+ elements.gitCheckoutSelect.innerHTML = "";
1093
+ branches.forEach(function (branch) {
1094
+ var option = document.createElement("option");
1095
+ option.value = branch.name;
1096
+ option.textContent = branch.current ? branch.name + " (current)" : branch.name;
1097
+ option.selected = branch.name === currentBranch;
1098
+ elements.gitCheckoutSelect.append(option);
1099
+ });
1100
+ }
1101
+
1102
+ function renderGitFiles(files) {
1103
+ elements.gitFiles.innerHTML = "";
1104
+ if (!Array.isArray(files) || files.length === 0) {
1105
+ var empty = document.createElement("div");
1106
+ empty.className = "git-empty";
1107
+ empty.textContent = "No changed files.";
1108
+ elements.gitFiles.append(empty);
1109
+ return;
1110
+ }
1111
+
1112
+ var tree = buildGitFileTree(files);
1113
+ elements.gitFiles.setAttribute("role", "tree");
1114
+ tree.forEach(function (root) {
1115
+ elements.gitFiles.append(renderGitTreeNode(root, 0));
1116
+ });
1117
+ }
1118
+
1119
+ function buildGitFileTree(files) {
1120
+ var roots = [
1121
+ createGitTreeNode("group", "modified", "modified", "modified"),
1122
+ createGitTreeNode("group", "untracked", "untracked", "untracked"),
1123
+ ];
1124
+ var rootByKey = { modified: roots[0], untracked: roots[1] };
1125
+ files.forEach(function (file) {
1126
+ var filePath = file.path || file.file || "";
1127
+ if (!filePath) return;
1128
+ var rootKey = gitRootKey(file);
1129
+ var parent = rootByKey[rootKey] || rootByKey.modified;
1130
+ var parts = filePath.split("/").filter(Boolean);
1131
+ var leafName = parts.pop() || filePath;
1132
+ parts.forEach(function (part) {
1133
+ parent = findOrCreateGitDirectory(parent, part);
1134
+ });
1135
+ var leaf = createGitTreeNode("file", leafName, filePath, rootKey);
1136
+ leaf.file = file;
1137
+ leaf.path = filePath;
1138
+ parent.children.push(leaf);
1139
+ });
1140
+ roots.forEach(sortGitTreeNode);
1141
+ return roots;
1142
+ }
1143
+
1144
+ function gitRootKey(file) {
1145
+ return file && (file.type === "untracked" || file.xy === "??") ? "untracked" : "modified";
1146
+ }
1147
+
1148
+ function createGitTreeNode(kind, name, label, rootKey) {
1149
+ return {
1150
+ kind: kind,
1151
+ name: name,
1152
+ label: label,
1153
+ rootKey: rootKey,
1154
+ children: [],
1155
+ file: null,
1156
+ path: null,
1157
+ };
1158
+ }
1159
+
1160
+ function findOrCreateGitDirectory(parent, name) {
1161
+ var existing = parent.children.find(function (child) {
1162
+ return child.kind === "dir" && child.name === name;
1163
+ });
1164
+ if (existing) return existing;
1165
+ var directory = createGitTreeNode("dir", name, name, parent.rootKey);
1166
+ parent.children.push(directory);
1167
+ return directory;
1168
+ }
1169
+
1170
+ function sortGitTreeNode(node) {
1171
+ node.children.sort(function (left, right) {
1172
+ if (left.kind === "dir" && right.kind !== "dir") return -1;
1173
+ if (left.kind !== "dir" && right.kind === "dir") return 1;
1174
+ return left.name.localeCompare(right.name);
1175
+ });
1176
+ node.children.forEach(sortGitTreeNode);
1177
+ }
1178
+
1179
+ function renderGitTreeNode(node, depth) {
1180
+ var paths = gitTreePaths(node);
1181
+ var selectedCount = paths.filter(function (filePath) {
1182
+ return state.gitSelectedPaths.indexOf(filePath) !== -1;
1183
+ }).length;
1184
+ var checked = paths.length > 0 && selectedCount === paths.length;
1185
+ var indeterminate = selectedCount > 0 && selectedCount < paths.length;
1186
+ var row = document.createElement("label");
1187
+ row.className = node.kind === "file" ? "git-file-row git-tree-row" : "git-tree-row git-tree-" + node.kind;
1188
+ row.style.paddingLeft = String(8 + depth * 18) + "px";
1189
+ row.setAttribute("role", "treeitem");
1190
+ row.setAttribute("aria-level", String(depth + 1));
1191
+ row.setAttribute("aria-checked", indeterminate ? "mixed" : String(checked));
1192
+ if (node.kind === "file" && node.path) {
1193
+ row.dataset.path = node.path;
1194
+ } else if (node.kind === "group") {
1195
+ row.dataset.gitRoot = node.rootKey;
1196
+ }
1197
+
1198
+ var checkbox = document.createElement("input");
1199
+ checkbox.type = "checkbox";
1200
+ checkbox.id = controlId("git-select", node.kind, node.rootKey, node.path || node.label || node.name);
1201
+ checkbox.name = checkbox.id;
1202
+ checkbox.checked = checked;
1203
+ checkbox.indeterminate = indeterminate;
1204
+ checkbox.disabled = paths.length === 0;
1205
+ checkbox.addEventListener("change", function () {
1206
+ setGitSelection(paths, checkbox.checked);
1207
+ renderGitWorkspace(state.viewModel || {});
1208
+ });
1209
+
1210
+ var typeLabel = gitTreeTypeLabel(node);
1211
+ var type = null;
1212
+ if (typeLabel) {
1213
+ type = document.createElement("span");
1214
+ type.className = "git-file-type" + (typeLabel === "staged" ? " staged" : "");
1215
+ type.textContent = typeLabel;
1216
+ } else {
1217
+ row.classList.add("without-type");
1218
+ }
1219
+
1220
+ var pathText = document.createElement("span");
1221
+ pathText.className = "git-file-path";
1222
+ pathText.textContent = gitTreeDisplayLabel(node);
1223
+
1224
+ var meta = document.createElement("span");
1225
+ meta.className = "git-file-meta";
1226
+ meta.textContent = node.kind === "file" ? gitFileMeta(node.file) : paths.length + " file" + (paths.length === 1 ? "" : "s");
1227
+
1228
+ row.append(checkbox);
1229
+ if (type) row.append(type);
1230
+ row.append(pathText, meta);
1231
+ if (node.kind === "file" && node.path) {
1232
+ var diffButton = document.createElement("button");
1233
+ diffButton.type = "button";
1234
+ diffButton.className = "git-diff-open";
1235
+ diffButton.textContent = "Diff";
1236
+ diffButton.setAttribute("aria-label", "Open diff for " + node.path);
1237
+ diffButton.addEventListener("click", function (event) {
1238
+ if (event && typeof event.preventDefault === "function") event.preventDefault();
1239
+ if (event && typeof event.stopPropagation === "function") event.stopPropagation();
1240
+ openGitDiff(node.path);
1241
+ });
1242
+ row.append(diffButton);
1243
+ }
1244
+ if (node.kind === "file") {
1245
+ return row;
1246
+ }
1247
+
1248
+ var container = document.createElement("div");
1249
+ container.className = "git-tree-node";
1250
+ container.append(row);
1251
+ node.children.forEach(function (child) {
1252
+ container.append(renderGitTreeNode(child, depth + 1));
1253
+ });
1254
+ return container;
1255
+ }
1256
+
1257
+ function gitTreeTypeLabel(node) {
1258
+ if (node.kind === "group") return node.rootKey;
1259
+ if (node.kind === "dir") return "";
1260
+ if (node.file && isGitFileStaged(node.file) && !needsGitFileStage(node.file)) return "staged";
1261
+ return (node.file && (node.file.type || node.file.xy)) || "changed";
1262
+ }
1263
+
1264
+ function gitTreeDisplayLabel(node) {
1265
+ if (node.kind !== "file") return node.label;
1266
+ var file = node.file || {};
1267
+ return file.originalPath || file.originalFile
1268
+ ? (file.originalPath || file.originalFile) + " -> " + (node.path || "")
1269
+ : (node.path || node.label);
1270
+ }
1271
+
1272
+ function gitFileMeta(file) {
1273
+ file = file || {};
1274
+ if (file.type === "untracked" || file.xy === "??") return "untracked";
1275
+ if (isGitFileStaged(file) && needsGitFileStage(file)) return "staged+unstaged";
1276
+ if (isGitFileStaged(file)) return "staged";
1277
+ if (needsGitFileStage(file)) return "unstaged";
1278
+ return (file.xy || "").trim() || "changed";
1279
+ }
1280
+
1281
+ function gitTreePaths(node) {
1282
+ if (node.kind === "file") return node.path ? [node.path] : [];
1283
+ return node.children.reduce(function (paths, child) {
1284
+ return paths.concat(gitTreePaths(child));
1285
+ }, []);
1286
+ }
1287
+
1288
+ function setGitSelection(paths, selected) {
1289
+ if (selected) {
1290
+ paths.forEach(function (filePath) {
1291
+ if (state.gitSelectedPaths.indexOf(filePath) === -1) {
1292
+ state.gitSelectedPaths.push(filePath);
1293
+ }
1294
+ });
1295
+ return;
1296
+ }
1297
+ state.gitSelectedPaths = state.gitSelectedPaths.filter(function (filePath) {
1298
+ return paths.indexOf(filePath) === -1;
1299
+ });
1300
+ }
1301
+
1302
+ function selectedGitPaths() {
1303
+ return state.gitSelectedPaths.slice();
1304
+ }
1305
+
1306
+ function gitChangedFiles() {
1307
+ var git = gitWorkspace(state.viewModel || {});
1308
+ return Array.isArray(git.changedFiles) ? git.changedFiles : [];
1309
+ }
1310
+
1311
+ function gitFilePath(file) {
1312
+ return file && (file.path || file.file) || "";
1313
+ }
1314
+
1315
+ function findGitChangedFile(filePath) {
1316
+ return gitChangedFiles().find(function (file) {
1317
+ return gitFilePath(file) === filePath || file.file === filePath;
1318
+ }) || null;
1319
+ }
1320
+
1321
+ function openGitDiff(filePath) {
1322
+ state.gitDiff.open = true;
1323
+ state.gitDiff.selectedPath = filePath;
1324
+ state.gitDiff.error = null;
1325
+ state.gitDiff.diff = null;
1326
+ fetchGitDiff();
1327
+ renderGitDiffDrawer(state.viewModel || {});
1328
+ }
1329
+
1330
+ function closeGitDiff() {
1331
+ state.gitDiff.open = false;
1332
+ state.gitDiff.requestId += 1;
1333
+ state.gitDiff.loading = false;
1334
+ renderGitDiffDrawer(state.viewModel || {});
1335
+ }
1336
+
1337
+ function setGitDiffMode(mode) {
1338
+ if (state.gitDiff.mode === mode) {
1339
+ return;
1340
+ }
1341
+ state.gitDiff.mode = mode;
1342
+ state.gitDiff.error = null;
1343
+ state.gitDiff.diff = null;
1344
+ if (state.gitDiff.open && state.gitDiff.selectedPath) {
1345
+ fetchGitDiff();
1346
+ }
1347
+ renderGitDiffDrawer(state.viewModel || {});
1348
+ }
1349
+
1350
+ function gitDiffApiUrl(filePath, mode) {
1351
+ var params = new URLSearchParams();
1352
+ params.set("path", filePath);
1353
+ params.set("mode", mode);
1354
+ return "/__agentweaver/api/git/diff?" + params.toString();
1355
+ }
1356
+
1357
+ function fetchGitDiff() {
1358
+ var filePath = state.gitDiff.selectedPath;
1359
+ if (!filePath) {
1360
+ return;
1361
+ }
1362
+ var requestId = state.gitDiff.requestId + 1;
1363
+ state.gitDiff.requestId = requestId;
1364
+ state.gitDiff.loading = true;
1365
+ state.gitDiff.error = null;
1366
+ state.gitDiff.diff = null;
1367
+ fetch(gitDiffApiUrl(filePath, state.gitDiff.mode))
1368
+ .then(function (response) {
1369
+ return response.json().then(function (body) {
1370
+ if (!response.ok) {
1371
+ throw new Error(body && body.message ? body.message : "Git diff request failed.");
1372
+ }
1373
+ return body;
1374
+ });
1375
+ })
1376
+ .then(function (diff) {
1377
+ if (requestId !== state.gitDiff.requestId) {
1378
+ return;
1379
+ }
1380
+ state.gitDiff.loading = false;
1381
+ state.gitDiff.diff = diff;
1382
+ state.gitDiff.error = null;
1383
+ renderGitDiffDrawer(state.viewModel || {});
1384
+ })
1385
+ .catch(function (error) {
1386
+ if (requestId !== state.gitDiff.requestId) {
1387
+ return;
1388
+ }
1389
+ state.gitDiff.loading = false;
1390
+ state.gitDiff.diff = null;
1391
+ state.gitDiff.error = error.message || "Git diff request failed.";
1392
+ renderGitDiffDrawer(state.viewModel || {});
1393
+ });
1394
+ }
1395
+
1396
+ function renderGitDiffDrawer(vm) {
1397
+ var files = gitChangedFiles();
1398
+ var selectedPath = state.gitDiff.selectedPath;
1399
+ var selectedPathChanged = false;
1400
+ if (selectedPath && !findGitChangedFile(selectedPath)) {
1401
+ selectedPath = files[0] ? gitFilePath(files[0]) : null;
1402
+ state.gitDiff.selectedPath = selectedPath;
1403
+ state.gitDiff.diff = null;
1404
+ state.gitDiff.error = null;
1405
+ state.gitDiff.loading = false;
1406
+ selectedPathChanged = Boolean(selectedPath);
1407
+ }
1408
+ elements.gitDiffDrawer.hidden = !state.gitDiff.open;
1409
+ elements.gitDiffTitle.textContent = "Git Diff Viewer";
1410
+ elements.gitDiffMeta.textContent = files.length === 0
1411
+ ? "No changed files are available."
1412
+ : String(files.length) + " changed file" + (files.length === 1 ? "" : "s") + ".";
1413
+ if (!state.gitDiff.open) {
1414
+ return;
1415
+ }
1416
+ if (selectedPathChanged) {
1417
+ fetchGitDiff();
1418
+ }
1419
+ renderGitDiffFileList(files);
1420
+ renderGitDiffModeControls();
1421
+ renderGitDiffPreview(vm);
1422
+ }
1423
+
1424
+ function renderGitDiffFileList(files) {
1425
+ elements.gitDiffFileList.innerHTML = "";
1426
+ if (!Array.isArray(files) || files.length === 0) {
1427
+ var empty = document.createElement("div");
1428
+ empty.className = "git-diff-empty";
1429
+ empty.textContent = "No changed files.";
1430
+ elements.gitDiffFileList.append(empty);
1431
+ return;
1432
+ }
1433
+ files.forEach(function (file) {
1434
+ var filePath = gitFilePath(file);
1435
+ var button = document.createElement("button");
1436
+ button.type = "button";
1437
+ button.className = "git-diff-file" + (filePath === state.gitDiff.selectedPath ? " selected" : "");
1438
+ button.dataset.path = filePath;
1439
+ button.setAttribute("aria-pressed", filePath === state.gitDiff.selectedPath ? "true" : "false");
1440
+ button.addEventListener("click", function () {
1441
+ if (state.gitDiff.selectedPath !== filePath) {
1442
+ openGitDiff(filePath);
1443
+ }
1444
+ });
1445
+ var title = document.createElement("strong");
1446
+ title.textContent = file.originalPath || file.originalFile
1447
+ ? (file.originalPath || file.originalFile) + " -> " + filePath
1448
+ : filePath;
1449
+ var meta = document.createElement("span");
1450
+ meta.textContent = gitFileMeta(file);
1451
+ button.append(title, meta);
1452
+ elements.gitDiffFileList.append(button);
1453
+ });
1454
+ }
1455
+
1456
+ function renderGitDiffModeControls() {
1457
+ ensureGitDiffModeButtons();
1458
+ Array.prototype.slice.call(elements.gitDiffModeControls.querySelectorAll("button")).forEach(function (button) {
1459
+ var mode = button.dataset.gitDiffMode || "head";
1460
+ var active = state.gitDiff.mode === mode;
1461
+ button.className = active ? "primary" : "";
1462
+ button.setAttribute("aria-pressed", active ? "true" : "false");
1463
+ });
1464
+ }
1465
+
1466
+ function ensureGitDiffModeButtons() {
1467
+ if (elements.gitDiffModeControls.querySelectorAll("button").length > 0) {
1468
+ return;
1469
+ }
1470
+ [
1471
+ ["head", "All"],
1472
+ ["staged", "Staged"],
1473
+ ["worktree", "Unstaged"],
1474
+ ].forEach(function (entry) {
1475
+ var button = document.createElement("button");
1476
+ button.type = "button";
1477
+ button.dataset.gitDiffMode = entry[0];
1478
+ button.textContent = entry[1];
1479
+ elements.gitDiffModeControls.append(button);
1480
+ });
1481
+ }
1482
+
1483
+ function renderGitDiffPreview(vm) {
1484
+ var selected = state.gitDiff.selectedPath ? findGitChangedFile(state.gitDiff.selectedPath) : null;
1485
+ if (!selected) {
1486
+ elements.gitDiffSelectedTitle.textContent = "Diff";
1487
+ elements.gitDiffSelectedMeta.textContent = "No file selected.";
1488
+ elements.gitDiffStatus.textContent = "";
1489
+ renderGitDiffMessage("Select a changed file to inspect its diff.", false);
1490
+ return;
1491
+ }
1492
+ var displayPath = selected.originalPath || selected.originalFile
1493
+ ? (selected.originalPath || selected.originalFile) + " -> " + gitFilePath(selected)
1494
+ : gitFilePath(selected);
1495
+ elements.gitDiffSelectedTitle.textContent = displayPath;
1496
+ elements.gitDiffSelectedMeta.textContent = [gitFileMeta(selected), gitModeLabel(state.gitDiff.mode)].join(" | ");
1497
+ if (state.gitDiff.loading) {
1498
+ elements.gitDiffStatus.textContent = "Loading diff...";
1499
+ renderGitDiffMessage("Loading diff...", false);
1500
+ return;
1501
+ }
1502
+ if (state.gitDiff.error) {
1503
+ elements.gitDiffStatus.textContent = state.gitDiff.error;
1504
+ renderGitDiffMessage(state.gitDiff.error, true);
1505
+ return;
1506
+ }
1507
+ var diff = state.gitDiff.diff;
1508
+ if (!diff) {
1509
+ elements.gitDiffStatus.textContent = "Diff has not been loaded yet.";
1510
+ renderGitDiffMessage("Diff has not been loaded yet.", false);
1511
+ return;
1512
+ }
1513
+ elements.gitDiffStatus.textContent = diff.message || "";
1514
+ renderGitDiffContent(diff);
1515
+ }
1516
+
1517
+ function gitModeLabel(mode) {
1518
+ if (mode === "staged") return "Staged";
1519
+ if (mode === "worktree") return "Unstaged";
1520
+ return "All";
1521
+ }
1522
+
1523
+ function renderGitDiffMessage(message, failed) {
1524
+ elements.gitDiffBody.textContent = "";
1525
+ elements.gitDiffBody.className = "git-diff-body" + (failed ? " error-text" : "");
1526
+ elements.gitDiffBody.textContent = message;
1527
+ }
1528
+
1529
+ function renderGitDiffContent(diff) {
1530
+ elements.gitDiffBody.textContent = "";
1531
+ elements.gitDiffBody.className = "git-diff-body";
1532
+ if (diff.binary) {
1533
+ renderGitDiffMessage(diff.message || "Binary file diff is not displayed.", false);
1534
+ return;
1535
+ }
1536
+ if (diff.tooLarge) {
1537
+ renderGitDiffMessage(diff.message || "Diff is too large to display.", false);
1538
+ return;
1539
+ }
1540
+ if (diff.empty || !Array.isArray(diff.hunks) || diff.hunks.length === 0) {
1541
+ renderGitDiffMessage(diff.message || "No diff is available for this mode.", false);
1542
+ return;
1543
+ }
1544
+ var table = document.createElement("div");
1545
+ table.className = "git-diff-table";
1546
+ diff.hunks.forEach(function (hunk) {
1547
+ var hunkRow = document.createElement("div");
1548
+ hunkRow.className = "git-diff-hunk";
1549
+ hunkRow.textContent = hunk.header || "@@";
1550
+ table.append(hunkRow);
1551
+ (Array.isArray(hunk.rows) ? hunk.rows : []).forEach(function (row) {
1552
+ table.append(renderGitDiffRow(row));
1553
+ });
1554
+ });
1555
+ elements.gitDiffBody.append(table);
1556
+ }
1557
+
1558
+ function renderGitDiffRow(row) {
1559
+ var element = document.createElement("div");
1560
+ var kind = row && row.kind ? row.kind : "context";
1561
+ element.className = "git-diff-row row-" + kind;
1562
+ var leftNumber = document.createElement("span");
1563
+ leftNumber.className = "git-diff-line-number";
1564
+ leftNumber.textContent = row.leftLineNumber === null || row.leftLineNumber === undefined ? "" : String(row.leftLineNumber);
1565
+ var leftText = document.createElement("code");
1566
+ leftText.className = "git-diff-code left";
1567
+ leftText.textContent = row.leftText || "";
1568
+ var rightNumber = document.createElement("span");
1569
+ rightNumber.className = "git-diff-line-number";
1570
+ rightNumber.textContent = row.rightLineNumber === null || row.rightLineNumber === undefined ? "" : String(row.rightLineNumber);
1571
+ var rightText = document.createElement("code");
1572
+ rightText.className = "git-diff-code right";
1573
+ rightText.textContent = row.rightText || "";
1574
+ element.append(leftNumber, leftText, rightNumber, rightText);
1575
+ return element;
1576
+ }
1577
+
1578
+ function selectedStageableGitPaths(git) {
1579
+ return selectedGitPathsFor(git, needsGitFileStage);
1580
+ }
1581
+
1582
+ function selectedUnstageableGitPaths(git) {
1583
+ return selectedGitPathsFor(git, isGitFileStaged);
1584
+ }
1585
+
1586
+ function selectedGitPathsFor(git, predicate) {
1587
+ git = git || gitWorkspace(state.viewModel || {});
1588
+ var selected = selectedGitPaths();
1589
+ var selectedSet = new Set(selected);
1590
+ var files = Array.isArray(git.changedFiles) ? git.changedFiles : [];
1591
+ return files.filter(function (file) {
1592
+ var filePath = file.path || file.file || "";
1593
+ return selectedSet.has(filePath) && predicate(file);
1594
+ }).map(function (file) {
1595
+ return file.path || file.file || "";
1596
+ });
1597
+ }
1598
+
1599
+ function needsGitFileStage(file) {
1600
+ if (!file) return false;
1601
+ if (file.type === "untracked" || file.xy === "??") return true;
1602
+ var workTreeStatus = typeof file.workTreeStatus === "string" ? file.workTreeStatus : (file.xy || " ")[1];
1603
+ return workTreeStatus !== " " && workTreeStatus !== undefined;
1604
+ }
1605
+
1606
+ function isGitFileStaged(file) {
1607
+ if (!file) return false;
1608
+ var indexStatus = typeof file.indexStatus === "string" ? file.indexStatus : (file.xy || " ")[0];
1609
+ return indexStatus !== " " && indexStatus !== "?";
1610
+ }
1611
+
1612
+ function renderProgress(vm) {
1613
+ var progress = vm && vm.progress;
1614
+ var items = progress && Array.isArray(progress.items) ? progress.items.filter(isProgressItem) : [];
1615
+ renderProgressFlowLabel(progress && progress.flow ? text(progress.flow.label, progress.flow.id || "") : "");
1616
+ if (!progress || !progress.flow || items.length === 0) {
1617
+ var fallbackText = text(vm && vm.progressText, "No progress yet.");
1618
+ elements.progress.className = "progress-tree fallback";
1619
+ if (elements.progress.children.length > 0) {
1620
+ var previousFallbackScrollTop = elements.progress.scrollTop;
1621
+ var fallbackWasPinned = previousFallbackScrollTop + elements.progress.clientHeight >= elements.progress.scrollHeight - 8;
1622
+ elements.progress.textContent = fallbackText;
1623
+ elements.progress.scrollTop = fallbackWasPinned ? elements.progress.scrollHeight : previousFallbackScrollTop;
1624
+ } else {
1625
+ setTextPreservingScroll(elements.progress, fallbackText);
1626
+ }
1627
+ return;
1628
+ }
1629
+
1630
+ var previousScrollTop = elements.progress.scrollTop;
1631
+ var wasPinned = previousScrollTop + elements.progress.clientHeight >= elements.progress.scrollHeight - 8;
1632
+ elements.progress.className = "progress-tree";
1633
+ elements.progress.innerHTML = "";
1634
+
1635
+ items.forEach(function (item, index) {
1636
+ elements.progress.append(renderProgressRow(item, index));
1637
+ });
1638
+ elements.progress.scrollTop = wasPinned ? elements.progress.scrollHeight : previousScrollTop;
1639
+ }
1640
+
1641
+ function renderProgressFlowLabel(label) {
1642
+ if (!elements.progressFlowLabel) {
1643
+ return;
1644
+ }
1645
+ var value = text(label, "");
1646
+ elements.progressFlowLabel.textContent = value;
1647
+ elements.progressFlowLabel.hidden = value.length === 0;
1648
+ }
1649
+
1650
+ function isProgressItem(item) {
1651
+ if (!item || typeof item !== "object") return false;
1652
+ if (["group", "phase", "step", "slot", "block", "termination"].indexOf(item.kind) === -1) return false;
1653
+ if (["pending", "running", "done", "success", "failed", "stopped", "skipped", "waiting-user", "disabled", "blocked", "invalid", "empty"].indexOf(item.status) === -1) return false;
1654
+ return typeof item.label === "string" && Number.isFinite(item.depth);
1655
+ }
1656
+
1657
+ function renderProgressRow(item, index) {
1658
+ var depth = Math.max(0, Math.min(12, Math.floor(item.depth)));
1659
+ var row = document.createElement("div");
1660
+ row.className = "progress-row kind-" + item.kind + " status-" + item.status;
1661
+ row.dataset.kind = item.kind;
1662
+ row.dataset.status = item.status;
1663
+ row.dataset.depth = String(depth);
1664
+ row.setAttribute("role", "treeitem");
1665
+ row.setAttribute("aria-level", String(depth + 1));
1666
+ row.setAttribute("aria-current", item.status === "running" ? "step" : "false");
1667
+ row.style.paddingLeft = String(8 + depth * 18) + "px";
1668
+ row.title = item.kind + ": " + item.status;
1669
+
1670
+ var marker = document.createElement("span");
1671
+ marker.className = "progress-marker";
1672
+ marker.setAttribute("aria-hidden", "true");
1673
+ marker.textContent = progressMarker(item.status);
1674
+
1675
+ var body = document.createElement("span");
1676
+ body.className = "progress-row-body";
1677
+
1678
+ var label = document.createElement("span");
1679
+ label.className = "progress-label";
1680
+ label.textContent = text(item.label, "Untitled progress item");
1681
+ body.append(label);
1682
+
1683
+ if (typeof item.detail === "string" && item.detail.length > 0) {
1684
+ var detail = document.createElement("span");
1685
+ detail.className = "progress-detail";
1686
+ detail.textContent = item.detail;
1687
+ body.append(detail);
1688
+ }
1689
+
1690
+ row.append(marker, body);
1691
+ row.dataset.index = String(index);
1692
+ return row;
1693
+ }
1694
+
1695
+ function progressMarker(status) {
1696
+ if (status === "done" || status === "success") return "✓";
1697
+ if (status === "failed" || status === "invalid") return "×";
1698
+ if (status === "stopped" || status === "blocked") return "■";
1699
+ if (status === "running" || status === "waiting-user") return "●";
1700
+ if (status === "skipped") return "↷";
1701
+ if (status === "disabled" || status === "empty") return "·";
1702
+ return "○";
1703
+ }
1704
+
1705
+ function setTextPreservingScroll(element, value, forceBottom) {
246
1706
  if (element.textContent === value) {
1707
+ if (forceBottom) {
1708
+ element.scrollTop = element.scrollHeight;
1709
+ }
247
1710
  return;
248
1711
  }
249
1712
  var previousScrollTop = element.scrollTop;
250
1713
  var wasPinned = previousScrollTop + element.clientHeight >= element.scrollHeight - 8;
251
1714
  element.textContent = value;
252
- element.scrollTop = wasPinned ? element.scrollHeight : previousScrollTop;
1715
+ element.scrollTop = forceBottom || wasPinned ? element.scrollHeight : previousScrollTop;
253
1716
  }
254
1717
 
255
1718
  function captureUiState() {
@@ -265,7 +1728,6 @@
265
1728
  }
266
1729
  return {
267
1730
  progressScrollTop: elements.progress.scrollTop,
268
- summaryScrollTop: elements.summary.scrollTop,
269
1731
  logScrollTop: elements.log.scrollTop,
270
1732
  helpScrollTop: elements.helpText.scrollTop,
271
1733
  modalScrollTop: elements.modalRoot.scrollTop,
@@ -281,8 +1743,7 @@
281
1743
  function restoreUiState(uiState) {
282
1744
  if (!uiState) return;
283
1745
  elements.progress.scrollTop = uiState.progressScrollTop;
284
- elements.summary.scrollTop = uiState.summaryScrollTop;
285
- elements.log.scrollTop = uiState.logScrollTop;
1746
+ elements.log.scrollTop = state.logAutoscroll ? elements.log.scrollHeight : uiState.logScrollTop;
286
1747
  elements.helpText.scrollTop = uiState.helpScrollTop;
287
1748
  elements.modalRoot.scrollTop = uiState.modalScrollTop;
288
1749
  var modalBody = currentModalBody();
@@ -446,9 +1907,9 @@
446
1907
  parts.push("Scope " + explorer.scopeKey);
447
1908
  }
448
1909
  if (Array.isArray(explorer.runIds) && explorer.runIds.length > 1) {
449
- parts.push("Runs " + explorer.runIds.join(", "));
1910
+ parts.push("Current runs " + explorer.runIds.join(", "));
450
1911
  } else if (explorer.runId) {
451
- parts.push("Run " + explorer.runId);
1912
+ parts.push("Current run " + explorer.runId);
452
1913
  }
453
1914
  return parts.join(" | ");
454
1915
  }
@@ -457,7 +1918,7 @@
457
1918
  if (typeof count !== "number") {
458
1919
  return "Artifacts are available.";
459
1920
  }
460
- return String(count) + " artifact" + (count === 1 ? "" : "s") + " created.";
1921
+ return String(count) + " artifact" + (count === 1 ? "" : "s") + " in scope.";
461
1922
  }
462
1923
 
463
1924
  function artifactApiUrl(explorer, suffix) {
@@ -466,13 +1927,6 @@
466
1927
  if (explorer.scopeKey) {
467
1928
  params.set("scope", explorer.scopeKey);
468
1929
  }
469
- if (Array.isArray(explorer.runIds) && explorer.runIds.length > 0) {
470
- explorer.runIds.forEach(function (runId) {
471
- params.append("runId", runId);
472
- });
473
- } else if (explorer.runId) {
474
- params.set("runId", explorer.runId);
475
- }
476
1930
  var query = params.toString();
477
1931
  return query ? base + "?" + query : base;
478
1932
  }
@@ -483,6 +1937,7 @@
483
1937
  return {
484
1938
  title: group.title || group.phaseId || "Artifacts",
485
1939
  items: Array.isArray(group.items) ? group.items : [],
1940
+ groups: Array.isArray(group.groups) ? artifactGroups({ groups: group.groups }) : [],
486
1941
  };
487
1942
  });
488
1943
  }
@@ -493,8 +1948,23 @@
493
1948
  }
494
1949
 
495
1950
  function flattenArtifacts(catalog) {
1951
+ function flattenGroup(group) {
1952
+ var nested = Array.isArray(group.groups) ? group.groups.reduce(function (items, child) {
1953
+ return items.concat(flattenGroup(child));
1954
+ }, []) : [];
1955
+ var ownItems = Array.isArray(group.items) ? group.items : [];
1956
+ if (nested.length > 0) {
1957
+ var nestedIds = new Set(nested.map(function (item) {
1958
+ return item && item.id;
1959
+ }));
1960
+ ownItems = ownItems.filter(function (item) {
1961
+ return item && !nestedIds.has(item.id);
1962
+ });
1963
+ }
1964
+ return nested.concat(ownItems);
1965
+ }
496
1966
  return artifactGroups(catalog).reduce(function (items, group) {
497
- return items.concat(group.items);
1967
+ return items.concat(flattenGroup(group));
498
1968
  }, []);
499
1969
  }
500
1970
 
@@ -535,14 +2005,23 @@
535
2005
  return item && item.kind ? String(item.kind) : "unknown";
536
2006
  }
537
2007
 
538
- function chooseDefaultArtifact(items) {
2008
+ function currentArtifactRunIds(explorer) {
2009
+ if (explorer && Array.isArray(explorer.runIds) && explorer.runIds.length > 0) {
2010
+ return explorer.runIds.filter(Boolean);
2011
+ }
2012
+ return explorer && explorer.runId ? [explorer.runId] : [];
2013
+ }
2014
+
2015
+ function chooseDefaultArtifact(items, explorer) {
539
2016
  if (!Array.isArray(items) || items.length === 0) {
540
2017
  return null;
541
2018
  }
2019
+ var currentRunIds = new Set(currentArtifactRunIds(explorer));
542
2020
  var best = null;
543
2021
  var bestScore = Infinity;
544
2022
  items.forEach(function (item, index) {
545
- var score = artifactUsefulnessScore(item) * 1000 + index;
2023
+ var runScore = currentRunIds.size > 0 && item && currentRunIds.has(item.runId) ? 0 : 1;
2024
+ var score = runScore * 100000 + artifactUsefulnessScore(item) * 1000 + index;
546
2025
  if (score < bestScore) {
547
2026
  best = item;
548
2027
  bestScore = score;
@@ -603,7 +2082,7 @@
603
2082
  return item && item.id === state.artifacts.selectedId;
604
2083
  });
605
2084
  if (!selectedStillExists) {
606
- var defaultItem = chooseDefaultArtifact(items);
2085
+ var defaultItem = chooseDefaultArtifact(items, explorer);
607
2086
  state.artifacts.selectedId = defaultItem ? defaultItem.id : null;
608
2087
  state.artifacts.preview = null;
609
2088
  if (defaultItem) {
@@ -640,26 +2119,48 @@
640
2119
  var groups = artifactGroups(catalog);
641
2120
  var rendered = 0;
642
2121
  groups.forEach(function (group) {
643
- var items = Array.isArray(group.items) ? group.items : [];
644
- if (items.length === 0) {
645
- return;
2122
+ var renderedGroup = renderArtifactGroup(explorer, group, 0);
2123
+ rendered += renderedGroup.count;
2124
+ if (renderedGroup.element) {
2125
+ elements.artifactList.append(renderedGroup.element);
646
2126
  }
647
- var section = document.createElement("section");
648
- section.className = "artifact-group";
649
- var title = document.createElement("h3");
650
- title.textContent = group.title || "Artifacts";
651
- section.append(title);
652
- items.forEach(function (item) {
653
- rendered += 1;
654
- section.append(renderArtifactRow(explorer, item));
655
- });
656
- elements.artifactList.append(section);
657
2127
  });
658
2128
  if (rendered === 0) {
659
- elements.artifactList.append(artifactEmpty("No artifacts were found for the current scope or run."));
2129
+ elements.artifactList.append(artifactEmpty("No artifacts were found for the current scope."));
660
2130
  }
661
2131
  }
662
2132
 
2133
+ function renderArtifactGroup(explorer, group, depth) {
2134
+ var childGroups = Array.isArray(group.groups) ? group.groups : [];
2135
+ var nestedIds = new Set();
2136
+ childGroups.forEach(function (child) {
2137
+ (Array.isArray(child.items) ? child.items : []).forEach(function (item) {
2138
+ if (item && item.id) nestedIds.add(item.id);
2139
+ });
2140
+ });
2141
+ var items = (Array.isArray(group.items) ? group.items : []).filter(function (item) {
2142
+ return item && !nestedIds.has(item.id);
2143
+ });
2144
+ var section = document.createElement("section");
2145
+ section.className = depth > 0 ? "artifact-group artifact-subgroup" : "artifact-group";
2146
+ var title = document.createElement(depth > 0 ? "h4" : "h3");
2147
+ title.textContent = group.title || "Artifacts";
2148
+ section.append(title);
2149
+ var count = 0;
2150
+ childGroups.forEach(function (child) {
2151
+ var renderedChild = renderArtifactGroup(explorer, child, depth + 1);
2152
+ count += renderedChild.count;
2153
+ if (renderedChild.element) {
2154
+ section.append(renderedChild.element);
2155
+ }
2156
+ });
2157
+ items.forEach(function (item) {
2158
+ count += 1;
2159
+ section.append(renderArtifactRow(explorer, item));
2160
+ });
2161
+ return { element: count > 0 ? section : null, count: count };
2162
+ }
2163
+
663
2164
  function renderArtifactRow(explorer, item) {
664
2165
  var row = document.createElement("article");
665
2166
  row.className = "artifact-row" + (state.artifacts.selectedId === item.id ? " selected" : "");
@@ -1433,6 +2934,8 @@
1433
2934
  checkRow.className = "check-row";
1434
2935
  var checkbox = document.createElement("input");
1435
2936
  checkbox.type = "checkbox";
2937
+ checkbox.id = "field-" + field.id;
2938
+ checkbox.name = field.id;
1436
2939
  checkbox.dataset.fieldId = field.id;
1437
2940
  checkbox.dataset.fieldType = field.type;
1438
2941
  checkbox.checked = currentValue(field) === true;
@@ -1450,6 +2953,7 @@
1450
2953
  var input = document.createElement(field.multiline ? "textarea" : "input");
1451
2954
  input.id = "field-" + field.id;
1452
2955
  if (!field.multiline) input.type = "text";
2956
+ input.name = field.id;
1453
2957
  if (field.placeholder) input.placeholder = field.placeholder;
1454
2958
  if (field.rows) input.rows = field.rows;
1455
2959
  input.dataset.fieldId = field.id;
@@ -1485,6 +2989,7 @@
1485
2989
  var input = document.createElement("input");
1486
2990
  input.type = "radio";
1487
2991
  input.name = "field-" + field.id;
2992
+ input.id = controlId("field", field.id, option.value);
1488
2993
  input.value = option.value;
1489
2994
  input.dataset.fieldId = field.id;
1490
2995
  input.dataset.fieldType = field.type;
@@ -1515,6 +3020,8 @@
1515
3020
  label.className = "field-option";
1516
3021
  var input = document.createElement("input");
1517
3022
  input.type = "checkbox";
3023
+ input.id = controlId("field", field.id, option.value);
3024
+ input.name = field.id;
1518
3025
  input.value = option.value;
1519
3026
  input.dataset.fieldId = field.id;
1520
3027
  input.dataset.fieldType = field.type;
@@ -1551,6 +3058,36 @@
1551
3058
  return Object.assign({}, state.formValues);
1552
3059
  }
1553
3060
 
3061
+ applyTheme(state.theme);
3062
+ if (elements.themeToggle) {
3063
+ elements.themeToggle.addEventListener("click", toggleTheme);
3064
+ }
3065
+ if (elements.autoFlowResizer) {
3066
+ elements.autoFlowResizer.setAttribute("role", "separator");
3067
+ elements.autoFlowResizer.setAttribute("aria-orientation", "horizontal");
3068
+ elements.autoFlowResizer.setAttribute("aria-label", "Resize flow editor");
3069
+ elements.autoFlowResizer.setAttribute("tabindex", "0");
3070
+ elements.autoFlowResizer.addEventListener("pointerdown", beginAutoFlowResize);
3071
+ elements.autoFlowResizer.addEventListener("dblclick", resetAutoFlowHeight);
3072
+ elements.autoFlowResizer.addEventListener("keydown", handleAutoFlowResizerKeydown);
3073
+ }
3074
+ applyWorkspaceSplit(state.workspaceSplit);
3075
+ if (elements.workspaceResizer) {
3076
+ elements.workspaceResizer.setAttribute("role", "separator");
3077
+ elements.workspaceResizer.setAttribute("aria-orientation", "vertical");
3078
+ elements.workspaceResizer.setAttribute("aria-label", "Resize workspace panels");
3079
+ elements.workspaceResizer.setAttribute("tabindex", "0");
3080
+ elements.workspaceResizer.addEventListener("pointerdown", beginWorkspaceResize);
3081
+ elements.workspaceResizer.addEventListener("dblclick", resetWorkspaceSplit);
3082
+ elements.workspaceResizer.addEventListener("keydown", handleWorkspaceResizerKeydown);
3083
+ }
3084
+ applyLogAutoscroll(state.logAutoscroll);
3085
+ if (elements.logAutoscroll) {
3086
+ elements.logAutoscroll.addEventListener("change", function () {
3087
+ applyLogAutoscroll(elements.logAutoscroll.checked);
3088
+ persistLogAutoscroll(state.logAutoscroll);
3089
+ });
3090
+ }
1554
3091
  elements.run.addEventListener("click", api.openRunConfirm);
1555
3092
  elements.interrupt.addEventListener("click", api.openInterruptConfirm);
1556
3093
  elements.artifactOpen.addEventListener("click", api.openArtifactExplorer);
@@ -1558,22 +3095,39 @@
1558
3095
  elements.artifactToolbarClose.addEventListener("click", api.closeArtifactExplorer);
1559
3096
  elements.artifactCopyContent.addEventListener("click", copySelectedArtifactContent);
1560
3097
  elements.artifactCopyReference.addEventListener("click", copySelectedArtifactReference);
3098
+ ensureGitDiffModeButtons();
3099
+ elements.gitDiffClose.addEventListener("click", closeGitDiff);
3100
+ Array.prototype.slice.call(elements.gitDiffModeControls.querySelectorAll("button")).forEach(function (button) {
3101
+ button.addEventListener("click", function () {
3102
+ setGitDiffMode(button.dataset.gitDiffMode || "head");
3103
+ });
3104
+ });
3105
+ elements.gitRefresh.addEventListener("click", api.refreshGit);
3106
+ elements.gitCreateBranch.addEventListener("click", api.createGitBranch);
3107
+ elements.gitCheckout.addEventListener("click", api.checkoutGitBranch);
3108
+ elements.gitFetch.addEventListener("click", api.fetchGit);
3109
+ elements.gitPull.addEventListener("click", api.pullGit);
3110
+ elements.gitStage.addEventListener("click", api.stageGit);
3111
+ elements.gitUnstage.addEventListener("click", api.unstageGit);
3112
+ elements.gitCommit.addEventListener("click", api.commitGit);
3113
+ elements.gitPush.addEventListener("click", api.pushGit);
3114
+ elements.gitCommitMessage.addEventListener("input", function () {
3115
+ state.gitCommitMessage = elements.gitCommitMessage.value;
3116
+ });
3117
+ elements.gitCommitMessage.addEventListener("change", api.updateGitCommitMessage);
1561
3118
  elements.help.addEventListener("click", api.toggleHelp);
1562
3119
  elements.closeHelp.addEventListener("click", function () {
1563
3120
  api.showHelp(false);
1564
3121
  });
1565
3122
  elements.clearLog.addEventListener("click", api.clearLog);
1566
3123
  elements.progress.addEventListener("scroll", function () {
1567
- api.scrollPane("progress", Math.round(elements.progress.scrollTop));
1568
- });
1569
- elements.summary.addEventListener("scroll", function () {
1570
- api.scrollPane("summary", Math.round(elements.summary.scrollTop));
3124
+ sendScrollPane("progress", elements.progress);
1571
3125
  });
1572
3126
  elements.log.addEventListener("scroll", function () {
1573
- api.scrollPane("log", Math.round(elements.log.scrollTop));
3127
+ sendScrollPane("log", elements.log);
1574
3128
  });
1575
3129
  elements.helpText.addEventListener("scroll", function () {
1576
- api.scrollPane("help", Math.round(elements.helpText.scrollTop));
3130
+ sendScrollPane("help", elements.helpText);
1577
3131
  });
1578
3132
 
1579
3133
  api.connect();