agentweaver 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +112 -23
  2. package/dist/artifacts.js +41 -0
  3. package/dist/index.js +258 -29
  4. package/dist/interactive/controller.js +323 -13
  5. package/dist/interactive/ink/index.js +2 -2
  6. package/dist/interactive/state.js +10 -0
  7. package/dist/interactive/web/index.js +326 -0
  8. package/dist/interactive/web/protocol.js +160 -0
  9. package/dist/interactive/web/server.js +1011 -0
  10. package/dist/interactive/web/static/app.js +1580 -0
  11. package/dist/interactive/web/static/index.html +114 -0
  12. package/dist/interactive/web/static/styles.css +2 -0
  13. package/dist/interactive/web/static/styles.input.css +849 -0
  14. package/dist/pipeline/flow-catalog.js +4 -0
  15. package/dist/pipeline/flow-specs/auto-common-guided.json +313 -0
  16. package/dist/pipeline/flow-specs/auto-common.json +3 -1
  17. package/dist/pipeline/flow-specs/design-review/design-review-loop.json +2 -0
  18. package/dist/pipeline/flow-specs/design-review.json +2 -0
  19. package/dist/pipeline/flow-specs/implement.json +3 -1
  20. package/dist/pipeline/flow-specs/plan.json +4 -0
  21. package/dist/pipeline/flow-specs/playbook-init.json +199 -0
  22. package/dist/pipeline/flow-specs/review/review-fix.json +3 -1
  23. package/dist/pipeline/flow-specs/review/review-loop.json +4 -0
  24. package/dist/pipeline/flow-specs/review/review.json +2 -0
  25. package/dist/pipeline/node-registry.js +45 -0
  26. package/dist/pipeline/nodes/flow-run-node.js +13 -1
  27. package/dist/pipeline/nodes/playbook-ensure-node.js +115 -0
  28. package/dist/pipeline/nodes/playbook-inventory-node.js +51 -0
  29. package/dist/pipeline/nodes/playbook-questions-form-node.js +166 -0
  30. package/dist/pipeline/nodes/playbook-write-node.js +243 -0
  31. package/dist/pipeline/nodes/project-guidance-node.js +69 -0
  32. package/dist/pipeline/prompt-registry.js +4 -1
  33. package/dist/pipeline/prompt-runtime.js +6 -2
  34. package/dist/pipeline/spec-types.js +19 -0
  35. package/dist/pipeline/value-resolver.js +39 -1
  36. package/dist/playbook/practice-candidates.js +12 -0
  37. package/dist/playbook/repo-inventory.js +208 -0
  38. package/dist/prompts.js +31 -0
  39. package/dist/runtime/artifact-catalog.js +379 -0
  40. package/dist/runtime/playbook.js +485 -0
  41. package/dist/runtime/project-guidance.js +339 -0
  42. package/dist/structured-artifact-schema-registry.js +8 -0
  43. package/dist/structured-artifact-schemas.json +235 -0
  44. package/dist/structured-artifacts.js +7 -1
  45. package/docs/declarative-workflows.md +565 -0
  46. package/docs/features.md +77 -0
  47. package/docs/playbook.md +327 -0
  48. package/package.json +8 -3
@@ -0,0 +1,1580 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ var state = {
5
+ viewModel: null,
6
+ connectionState: "connecting",
7
+ formValues: {},
8
+ modalSignature: null,
9
+ artifacts: {
10
+ signature: null,
11
+ loading: false,
12
+ catalog: null,
13
+ error: null,
14
+ preview: null,
15
+ selectedId: null,
16
+ actionStatus: null,
17
+ actionStatusFailed: false,
18
+ previewRequestId: 0,
19
+ viewerModes: {},
20
+ },
21
+ };
22
+
23
+ var elements = {
24
+ title: document.getElementById("app-title"),
25
+ header: document.getElementById("header-text"),
26
+ connection: document.getElementById("connection-state"),
27
+ status: document.getElementById("session-status"),
28
+ run: document.getElementById("run-button"),
29
+ interrupt: document.getElementById("interrupt-button"),
30
+ help: document.getElementById("help-button"),
31
+ flowsTitle: document.getElementById("flows-title"),
32
+ flows: document.getElementById("flows-list"),
33
+ description: document.getElementById("description-text"),
34
+ progressTitle: document.getElementById("progress-title"),
35
+ progress: document.getElementById("progress-text"),
36
+ summaryTitle: document.getElementById("summary-title"),
37
+ summary: document.getElementById("summary-text"),
38
+ logTitle: document.getElementById("log-title"),
39
+ log: document.getElementById("log-text"),
40
+ clearLog: document.getElementById("clear-log-button"),
41
+ artifactOpen: document.getElementById("artifact-open-button"),
42
+ artifactDrawer: document.getElementById("artifact-drawer"),
43
+ artifactClose: document.getElementById("artifact-close-button"),
44
+ artifactTitle: document.getElementById("artifact-title"),
45
+ artifactMeta: document.getElementById("artifact-meta"),
46
+ artifactMessage: document.getElementById("artifact-message"),
47
+ artifactList: document.getElementById("artifact-list"),
48
+ artifactSelectedTitle: document.getElementById("artifact-selected-title"),
49
+ artifactSelectedMeta: document.getElementById("artifact-selected-meta"),
50
+ artifactActionStatus: document.getElementById("artifact-action-status"),
51
+ artifactCopyContent: document.getElementById("artifact-copy-content-button"),
52
+ artifactCopyReference: document.getElementById("artifact-copy-reference-button"),
53
+ artifactOpenRaw: document.getElementById("artifact-open-raw-link"),
54
+ artifactDownload: document.getElementById("artifact-download-link"),
55
+ artifactToolbarClose: document.getElementById("artifact-toolbar-close-button"),
56
+ artifactPreview: document.getElementById("artifact-preview-text"),
57
+ helpPanel: document.getElementById("help-panel"),
58
+ helpText: document.getElementById("help-text"),
59
+ closeHelp: document.getElementById("close-help-button"),
60
+ modalRoot: document.getElementById("modal-root"),
61
+ };
62
+
63
+ function text(value, fallback) {
64
+ if (typeof value === "string" && value.length > 0) {
65
+ return value;
66
+ }
67
+ return fallback;
68
+ }
69
+
70
+ function actionId() {
71
+ return "web-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
72
+ }
73
+
74
+ function selectedFlow() {
75
+ var vm = state.viewModel;
76
+ if (!vm || !Array.isArray(vm.flowItems)) {
77
+ return null;
78
+ }
79
+ return vm.flowItems[vm.selectedFlowIndex] || null;
80
+ }
81
+
82
+ function isFolder(item) {
83
+ if (item && item.kind) {
84
+ return item.kind === "folder";
85
+ }
86
+ var key = String(item && item.key ? item.key : "");
87
+ var label = String(item && item.label ? item.label : "");
88
+ return key.indexOf("folder:") === 0 || key.indexOf("folder/") === 0 || label.trim().endsWith("/");
89
+ }
90
+
91
+ function flowMeta(item) {
92
+ var key = String(item && item.key ? item.key : "");
93
+ if (key.indexOf("project") >= 0) return "project";
94
+ if (key.indexOf("global") >= 0) return "global";
95
+ if (key.indexOf("built") >= 0 || key.indexOf("default") >= 0) return "built-in";
96
+ return key;
97
+ }
98
+
99
+ var api = {
100
+ socket: null,
101
+ send: function (message) {
102
+ if (!message.actionId) {
103
+ message.actionId = actionId();
104
+ }
105
+ if (!api.socket || api.socket.readyState !== WebSocket.OPEN) {
106
+ appendLog("[web] Cannot send action while disconnected: " + message.type);
107
+ return;
108
+ }
109
+ api.socket.send(JSON.stringify(message));
110
+ },
111
+ connect: function () {
112
+ setConnection("connecting");
113
+ var scheme = window.location.protocol === "https:" ? "wss://" : "ws://";
114
+ api.socket = new WebSocket(scheme + window.location.host + "/__agentweaver/ws");
115
+ api.socket.addEventListener("open", function () {
116
+ setConnection("connected");
117
+ });
118
+ api.socket.addEventListener("close", function () {
119
+ setConnection(state.connectionState === "closed" ? "closed" : "disconnected");
120
+ });
121
+ api.socket.addEventListener("error", function () {
122
+ setConnection("error");
123
+ });
124
+ api.socket.addEventListener("message", function (event) {
125
+ handleServerEvent(event.data);
126
+ });
127
+ },
128
+ selectFlow: function (index, key) {
129
+ api.send({ type: "flow.select", index: index, key: key });
130
+ },
131
+ toggleFolder: function (key) {
132
+ api.send({ type: "folder.toggle", key: key });
133
+ },
134
+ openRunConfirm: function () {
135
+ var flow = selectedFlow();
136
+ api.send({ type: "run.openConfirm", key: flow ? flow.key : undefined });
137
+ },
138
+ selectAndAcceptConfirmation: function (action) {
139
+ if (action === "cancel") {
140
+ api.send({ type: "confirm.cancel" });
141
+ return;
142
+ }
143
+ api.send({ type: "confirm.accept", action: action });
144
+ },
145
+ submitForm: function () {
146
+ api.send({ type: "form.submit", values: collectFormValues() });
147
+ },
148
+ cancelForm: function () {
149
+ api.send({ type: "form.cancel" });
150
+ },
151
+ openInterruptConfirm: function () {
152
+ api.send({ type: "interrupt.openConfirm" });
153
+ },
154
+ interruptFlow: function () {
155
+ api.send({ type: "flow.interrupt" });
156
+ },
157
+ clearLog: function () {
158
+ api.send({ type: "log.clear" });
159
+ },
160
+ openArtifactExplorer: function () {
161
+ api.send({ type: "artifactExplorer.open" });
162
+ },
163
+ closeArtifactExplorer: function () {
164
+ api.send({ type: "artifactExplorer.close" });
165
+ },
166
+ toggleHelp: function () {
167
+ api.send({ type: "help.toggle", visible: !(state.viewModel && state.viewModel.helpVisible) });
168
+ },
169
+ showHelp: function (visible) {
170
+ api.send({ type: "help.toggle", visible: visible });
171
+ },
172
+ scrollPane: function (pane, offset) {
173
+ api.send({ type: "scroll", pane: pane, offset: offset });
174
+ },
175
+ };
176
+
177
+ function setConnection(nextState) {
178
+ state.connectionState = nextState;
179
+ elements.connection.textContent = nextState.charAt(0).toUpperCase() + nextState.slice(1);
180
+ elements.connection.className = "connection connection-" + nextState;
181
+ }
182
+
183
+ function handleServerEvent(raw) {
184
+ var message;
185
+ try {
186
+ message = JSON.parse(raw);
187
+ } catch (error) {
188
+ appendLog("[web] Invalid server message: " + error.message);
189
+ return;
190
+ }
191
+
192
+ if (message.type === "snapshot") {
193
+ var uiState = captureUiState();
194
+ state.viewModel = message.viewModel || {};
195
+ state.formValues = state.viewModel.form ? Object.assign({}, state.viewModel.form.values || {}) : {};
196
+ render();
197
+ restoreUiState(uiState);
198
+ return;
199
+ }
200
+ if (message.type === "log.append") {
201
+ appendLog((message.appendedLines || []).join("\n"));
202
+ return;
203
+ }
204
+ if (message.type === "error") {
205
+ appendLog("[web] " + text(message.message, "Unknown protocol error."));
206
+ return;
207
+ }
208
+ if (message.type === "closed") {
209
+ setConnection("closed");
210
+ appendLog("[closed] " + text(message.reason, "Session closed."));
211
+ }
212
+ }
213
+
214
+ function appendLog(lines) {
215
+ if (!lines) return;
216
+ var wasPinned = elements.log.scrollTop + elements.log.clientHeight >= elements.log.scrollHeight - 8;
217
+ elements.log.textContent += (elements.log.textContent ? "\n" : "") + lines;
218
+ if (wasPinned) {
219
+ elements.log.scrollTop = elements.log.scrollHeight;
220
+ }
221
+ }
222
+
223
+ function render() {
224
+ var vm = state.viewModel || {};
225
+ elements.title.textContent = text(vm.title, "AgentWeaver");
226
+ elements.header.textContent = text(vm.header, "Local operator console");
227
+ elements.status.textContent = text(vm.statusText, "Idle");
228
+ elements.flowsTitle.textContent = text(vm.flowListTitle, "Flows");
229
+ elements.description.textContent = text(vm.descriptionText, "No flow selected.");
230
+ 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."));
234
+ elements.logTitle.textContent = text(vm.logTitle, "Activity");
235
+ setTextPreservingScroll(elements.log, text(vm.logText, ""));
236
+ elements.helpText.textContent = text(vm.helpText, "No help is available.");
237
+ elements.helpPanel.hidden = !vm.helpVisible;
238
+ elements.help.setAttribute("aria-pressed", vm.helpVisible ? "true" : "false");
239
+
240
+ renderFlows(vm);
241
+ renderModal(vm);
242
+ renderArtifactExplorer(vm);
243
+ }
244
+
245
+ function setTextPreservingScroll(element, value) {
246
+ if (element.textContent === value) {
247
+ return;
248
+ }
249
+ var previousScrollTop = element.scrollTop;
250
+ var wasPinned = previousScrollTop + element.clientHeight >= element.scrollHeight - 8;
251
+ element.textContent = value;
252
+ element.scrollTop = wasPinned ? element.scrollHeight : previousScrollTop;
253
+ }
254
+
255
+ function captureUiState() {
256
+ var active = document.activeElement;
257
+ var activeFieldId = active && active.dataset ? active.dataset.fieldId : null;
258
+ var activeFieldValue = active && active.dataset && active.dataset.fieldId && "value" in active ? active.value : null;
259
+ var selectionStart = null;
260
+ var selectionEnd = null;
261
+ var modalBody = currentModalBody();
262
+ if (activeFieldId && typeof active.selectionStart === "number" && typeof active.selectionEnd === "number") {
263
+ selectionStart = active.selectionStart;
264
+ selectionEnd = active.selectionEnd;
265
+ }
266
+ return {
267
+ progressScrollTop: elements.progress.scrollTop,
268
+ summaryScrollTop: elements.summary.scrollTop,
269
+ logScrollTop: elements.log.scrollTop,
270
+ helpScrollTop: elements.helpText.scrollTop,
271
+ modalScrollTop: elements.modalRoot.scrollTop,
272
+ modalBodyScrollTop: modalBody ? modalBody.scrollTop : 0,
273
+ activeFieldId: activeFieldId,
274
+ activeFieldValue: activeFieldValue,
275
+ selectionStart: selectionStart,
276
+ selectionEnd: selectionEnd,
277
+ formId: state.viewModel && state.viewModel.form ? state.viewModel.form.formId : null,
278
+ };
279
+ }
280
+
281
+ function restoreUiState(uiState) {
282
+ if (!uiState) return;
283
+ elements.progress.scrollTop = uiState.progressScrollTop;
284
+ elements.summary.scrollTop = uiState.summaryScrollTop;
285
+ elements.log.scrollTop = uiState.logScrollTop;
286
+ elements.helpText.scrollTop = uiState.helpScrollTop;
287
+ elements.modalRoot.scrollTop = uiState.modalScrollTop;
288
+ var modalBody = currentModalBody();
289
+ if (modalBody) {
290
+ modalBody.scrollTop = uiState.modalBodyScrollTop;
291
+ }
292
+
293
+ var currentFormId = state.viewModel && state.viewModel.form ? state.viewModel.form.formId : null;
294
+ if (!uiState.activeFieldId || uiState.formId !== currentFormId) {
295
+ return;
296
+ }
297
+ var selector = '[data-field-id="' + cssEscape(uiState.activeFieldId) + '"]';
298
+ if (uiState.activeFieldValue !== null) {
299
+ selector += '[value="' + cssEscape(uiState.activeFieldValue) + '"]';
300
+ }
301
+ var field = elements.modalRoot.querySelector(selector);
302
+ if (!field) {
303
+ field = elements.modalRoot.querySelector('[data-field-id="' + cssEscape(uiState.activeFieldId) + '"] input, [data-field-id="' + cssEscape(uiState.activeFieldId) + '"] textarea, input[data-field-id="' + cssEscape(uiState.activeFieldId) + '"], textarea[data-field-id="' + cssEscape(uiState.activeFieldId) + '"]');
304
+ }
305
+ if (!field || typeof field.focus !== "function") {
306
+ return;
307
+ }
308
+ field.focus();
309
+ if (typeof field.setSelectionRange === "function" && uiState.selectionStart !== null && uiState.selectionEnd !== null) {
310
+ var valueLength = typeof field.value === "string" ? field.value.length : 0;
311
+ field.setSelectionRange(Math.min(uiState.selectionStart, valueLength), Math.min(uiState.selectionEnd, valueLength));
312
+ }
313
+ }
314
+
315
+ function cssEscape(value) {
316
+ if (window.CSS && typeof window.CSS.escape === "function") {
317
+ return window.CSS.escape(value);
318
+ }
319
+ return String(value).replace(/["\\]/g, "\\$&");
320
+ }
321
+
322
+ function currentModalBody() {
323
+ return elements.modalRoot.querySelector(".modal-body");
324
+ }
325
+
326
+ function renderFlows(vm) {
327
+ elements.flows.innerHTML = "";
328
+ var items = Array.isArray(vm.flowItems) ? vm.flowItems : [];
329
+ if (items.length === 0) {
330
+ var empty = document.createElement("div");
331
+ empty.className = "flow-meta";
332
+ empty.textContent = "No flows are available.";
333
+ elements.flows.append(empty);
334
+ return;
335
+ }
336
+
337
+ items.forEach(function (item, index) {
338
+ var folder = isFolder(item);
339
+ var depth = Number.isFinite(item.depth) ? Math.max(0, item.depth) : 0;
340
+ var row = document.createElement("button");
341
+ row.type = "button";
342
+ row.className = "flow-row" + (folder ? " folder" : "") + (index === vm.selectedFlowIndex ? " selected" : "");
343
+ row.setAttribute("role", folder ? "treeitem" : "option");
344
+ row.style.paddingLeft = String(6 + depth * 18) + "px";
345
+ row.title = item.key || item.label || "";
346
+ row.addEventListener("click", function () {
347
+ if (folder) {
348
+ api.toggleFolder(item.key);
349
+ } else {
350
+ api.selectFlow(index, item.key);
351
+ }
352
+ });
353
+ row.addEventListener("dblclick", function () {
354
+ if (!folder) {
355
+ api.send({ type: "run.openConfirm", key: item.key });
356
+ }
357
+ });
358
+
359
+ var icon = document.createElement("span");
360
+ icon.className = "flow-icon";
361
+ icon.textContent = folder ? (item.expanded ? "▾" : "▸") : "•";
362
+ var label = document.createElement("span");
363
+ label.className = "flow-label";
364
+ label.textContent = text(item.name, item.label || item.key || "Untitled flow").replace(/^[\s▸▾•]+/, "");
365
+ var meta = document.createElement("span");
366
+ meta.className = "flow-meta";
367
+ meta.textContent = flowMeta(item);
368
+ row.append(icon, label, meta);
369
+ elements.flows.append(row);
370
+ });
371
+ }
372
+
373
+ function artifactState(vm) {
374
+ var explorer = vm && vm.artifactExplorer;
375
+ if (!explorer || typeof explorer !== "object") {
376
+ return {
377
+ available: false,
378
+ open: false,
379
+ scopeKey: null,
380
+ runId: null,
381
+ status: "unavailable",
382
+ label: "Artifact Explorer",
383
+ message: "",
384
+ };
385
+ }
386
+ return explorer;
387
+ }
388
+
389
+ function hasBlockingInput(vm) {
390
+ return Boolean(vm && (vm.confirmation || vm.confirmText || vm.form));
391
+ }
392
+
393
+ function artifactSignature(explorer) {
394
+ return [
395
+ explorer.scopeKey || "",
396
+ explorer.runId || "",
397
+ Array.isArray(explorer.runIds) ? explorer.runIds.join(",") : "",
398
+ explorer.status || "",
399
+ typeof explorer.artifactCount === "number" ? String(explorer.artifactCount) : "",
400
+ ].join("|");
401
+ }
402
+
403
+ function renderArtifactExplorer(vm) {
404
+ var explorer = artifactState(vm);
405
+ var blocked = hasBlockingInput(vm);
406
+ elements.artifactOpen.hidden = !explorer.available || explorer.open || blocked;
407
+ elements.artifactOpen.textContent = explorer.label || "Artifacts";
408
+ elements.artifactDrawer.hidden = !explorer.open;
409
+ elements.artifactTitle.textContent = explorer.label || "Artifact Explorer";
410
+ elements.artifactMeta.textContent = artifactMetaText(explorer);
411
+ elements.artifactMessage.textContent = explorer.message || "";
412
+
413
+ if (!explorer.open) {
414
+ return;
415
+ }
416
+
417
+ var signature = artifactSignature(explorer);
418
+ if (signature !== state.artifacts.signature) {
419
+ state.artifacts.signature = signature;
420
+ state.artifacts.loading = false;
421
+ state.artifacts.catalog = null;
422
+ state.artifacts.error = null;
423
+ state.artifacts.preview = null;
424
+ state.artifacts.selectedId = null;
425
+ state.artifacts.actionStatus = null;
426
+ state.artifacts.actionStatusFailed = false;
427
+ state.artifacts.previewRequestId += 1;
428
+ state.artifacts.viewerModes = {};
429
+ fetchArtifacts(explorer);
430
+ }
431
+ renderArtifactList(explorer);
432
+ renderArtifactPreview(explorer);
433
+ }
434
+
435
+ function artifactMetaText(explorer) {
436
+ var parts = [];
437
+ var count = typeof explorer.artifactCount === "number" ? explorer.artifactCount : null;
438
+ if (explorer.status === "completed") {
439
+ parts.push("Workflow completed. " + artifactCountText(count));
440
+ } else if (explorer.status === "failed") {
441
+ parts.push("Workflow failed. " + artifactCountText(count));
442
+ } else if (count !== null) {
443
+ parts.push(artifactCountText(count));
444
+ }
445
+ if (explorer.scopeKey) {
446
+ parts.push("Scope " + explorer.scopeKey);
447
+ }
448
+ if (Array.isArray(explorer.runIds) && explorer.runIds.length > 1) {
449
+ parts.push("Runs " + explorer.runIds.join(", "));
450
+ } else if (explorer.runId) {
451
+ parts.push("Run " + explorer.runId);
452
+ }
453
+ return parts.join(" | ");
454
+ }
455
+
456
+ function artifactCountText(count) {
457
+ if (typeof count !== "number") {
458
+ return "Artifacts are available.";
459
+ }
460
+ return String(count) + " artifact" + (count === 1 ? "" : "s") + " created.";
461
+ }
462
+
463
+ function artifactApiUrl(explorer, suffix) {
464
+ var base = "/__agentweaver/api/artifacts" + (suffix || "");
465
+ var params = new URLSearchParams();
466
+ if (explorer.scopeKey) {
467
+ params.set("scope", explorer.scopeKey);
468
+ }
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
+ var query = params.toString();
477
+ return query ? base + "?" + query : base;
478
+ }
479
+
480
+ function artifactGroups(catalog) {
481
+ if (catalog && Array.isArray(catalog.groups) && catalog.groups.length > 0) {
482
+ return catalog.groups.map(function (group) {
483
+ return {
484
+ title: group.title || group.phaseId || "Artifacts",
485
+ items: Array.isArray(group.items) ? group.items : [],
486
+ };
487
+ });
488
+ }
489
+ return [{
490
+ title: "Artifacts",
491
+ items: catalog && Array.isArray(catalog.items) ? catalog.items : [],
492
+ }];
493
+ }
494
+
495
+ function flattenArtifacts(catalog) {
496
+ return artifactGroups(catalog).reduce(function (items, group) {
497
+ return items.concat(group.items);
498
+ }, []);
499
+ }
500
+
501
+ function selectedArtifact() {
502
+ var items = state.artifacts.catalog ? flattenArtifacts(state.artifacts.catalog) : [];
503
+ return items.find(function (item) {
504
+ return item && item.id === state.artifacts.selectedId;
505
+ }) || null;
506
+ }
507
+
508
+ function artifactReference(item) {
509
+ return item && (item.relativePath || item.logicalKey || item.id) || "";
510
+ }
511
+
512
+ function artifactDisplayTitle(item) {
513
+ return item && (item.title || item.logicalKey || item.relativePath || item.id) || "Untitled artifact";
514
+ }
515
+
516
+ function formatArtifactBytes(value) {
517
+ if (!Number.isFinite(value)) {
518
+ return "Unknown size";
519
+ }
520
+ var bytes = Math.max(0, value);
521
+ if (bytes < 1024) {
522
+ return String(bytes) + " B";
523
+ }
524
+ var units = ["KB", "MB", "GB"];
525
+ var size = bytes / 1024;
526
+ var index = 0;
527
+ while (size >= 1024 && index < units.length - 1) {
528
+ size = size / 1024;
529
+ index += 1;
530
+ }
531
+ return (size >= 10 ? size.toFixed(0) : size.toFixed(1)).replace(/\.0$/, "") + " " + units[index];
532
+ }
533
+
534
+ function artifactKind(item) {
535
+ return item && item.kind ? String(item.kind) : "unknown";
536
+ }
537
+
538
+ function chooseDefaultArtifact(items) {
539
+ if (!Array.isArray(items) || items.length === 0) {
540
+ return null;
541
+ }
542
+ var best = null;
543
+ var bestScore = Infinity;
544
+ items.forEach(function (item, index) {
545
+ var score = artifactUsefulnessScore(item) * 1000 + index;
546
+ if (score < bestScore) {
547
+ best = item;
548
+ bestScore = score;
549
+ }
550
+ });
551
+ return best;
552
+ }
553
+
554
+ function artifactUsefulnessScore(item) {
555
+ var role = String(item && item.role || "").toLowerCase();
556
+ var haystack = [
557
+ item && item.logicalKey,
558
+ item && item.relativePath,
559
+ item && item.title,
560
+ item && item.id,
561
+ ].filter(Boolean).join(" ").toLowerCase();
562
+ if (role.indexOf("design") !== -1 || /\bdesign\b/.test(haystack)) return 0;
563
+ if (role.indexOf("plan") !== -1 || /\bplan\b/.test(haystack)) return 1;
564
+ if (role.indexOf("review") !== -1 || /\breview\b/.test(haystack)) return 2;
565
+ if (role === "qa" || role.indexOf("qa") !== -1 || /\bqa\b/.test(haystack)) return 3;
566
+ if (role.indexOf("ready-to-merge") !== -1 || haystack.indexOf("ready-to-merge") !== -1) return 4;
567
+ if (isDiagnosticArtifact(role, haystack)) return 100;
568
+ return 50;
569
+ }
570
+
571
+ function isDiagnosticArtifact(role, haystack) {
572
+ return role.indexOf("diagnostic") !== -1
573
+ || role.indexOf("internal") !== -1
574
+ || /\b(log|trace|debug|diagnostic|diagnostics|internal)\b/.test(haystack)
575
+ || haystack.indexOf("manifest-history") !== -1
576
+ || haystack.indexOf("restart-archives") !== -1
577
+ || haystack.indexOf("artifact-index") !== -1;
578
+ }
579
+
580
+ function fetchArtifacts(explorer) {
581
+ if (!explorer.scopeKey) {
582
+ state.artifacts.error = "Artifact scope is not available.";
583
+ renderArtifactList(explorer);
584
+ return;
585
+ }
586
+ state.artifacts.loading = true;
587
+ renderArtifactList(explorer);
588
+ fetch(artifactApiUrl(explorer))
589
+ .then(function (response) {
590
+ return response.json().then(function (body) {
591
+ if (!response.ok) {
592
+ throw new Error(body && body.message ? body.message : "Artifact catalog request failed.");
593
+ }
594
+ return body;
595
+ });
596
+ })
597
+ .then(function (catalog) {
598
+ state.artifacts.catalog = catalog;
599
+ state.artifacts.error = null;
600
+ state.artifacts.loading = false;
601
+ var items = flattenArtifacts(catalog);
602
+ var selectedStillExists = items.some(function (item) {
603
+ return item && item.id === state.artifacts.selectedId;
604
+ });
605
+ if (!selectedStillExists) {
606
+ var defaultItem = chooseDefaultArtifact(items);
607
+ state.artifacts.selectedId = defaultItem ? defaultItem.id : null;
608
+ state.artifacts.preview = null;
609
+ if (defaultItem) {
610
+ previewArtifact(explorer, defaultItem);
611
+ }
612
+ }
613
+ renderArtifactList(explorer);
614
+ renderArtifactPreview(explorer);
615
+ })
616
+ .catch(function (error) {
617
+ state.artifacts.catalog = null;
618
+ state.artifacts.error = error.message || "Artifact catalog request failed.";
619
+ state.artifacts.loading = false;
620
+ renderArtifactList(explorer);
621
+ renderArtifactPreview(explorer);
622
+ });
623
+ }
624
+
625
+ function renderArtifactList(explorer) {
626
+ elements.artifactList.innerHTML = "";
627
+ if (state.artifacts.loading) {
628
+ elements.artifactList.append(artifactEmpty("Loading artifacts..."));
629
+ return;
630
+ }
631
+ if (state.artifacts.error) {
632
+ elements.artifactList.append(artifactEmpty(state.artifacts.error));
633
+ return;
634
+ }
635
+ var catalog = state.artifacts.catalog;
636
+ if (!catalog) {
637
+ elements.artifactList.append(artifactEmpty("Artifacts have not been loaded yet."));
638
+ return;
639
+ }
640
+ var groups = artifactGroups(catalog);
641
+ var rendered = 0;
642
+ groups.forEach(function (group) {
643
+ var items = Array.isArray(group.items) ? group.items : [];
644
+ if (items.length === 0) {
645
+ return;
646
+ }
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
+ });
658
+ if (rendered === 0) {
659
+ elements.artifactList.append(artifactEmpty("No artifacts were found for the current scope or run."));
660
+ }
661
+ }
662
+
663
+ function renderArtifactRow(explorer, item) {
664
+ var row = document.createElement("article");
665
+ row.className = "artifact-row" + (state.artifacts.selectedId === item.id ? " selected" : "");
666
+ row.dataset.artifactId = item.id || "";
667
+ var details = document.createElement("button");
668
+ details.type = "button";
669
+ details.className = "artifact-row-main";
670
+ details.setAttribute("aria-pressed", state.artifacts.selectedId === item.id ? "true" : "false");
671
+ details.addEventListener("click", function () {
672
+ previewArtifact(explorer, item);
673
+ });
674
+ var title = document.createElement("strong");
675
+ title.textContent = artifactDisplayTitle(item);
676
+ var meta = document.createElement("span");
677
+ meta.className = "artifact-row-meta";
678
+ var kind = document.createElement("span");
679
+ kind.textContent = artifactKind(item);
680
+ var size = document.createElement("span");
681
+ size.textContent = formatArtifactBytes(item.sizeBytes);
682
+ var reference = document.createElement("span");
683
+ reference.textContent = artifactReference(item);
684
+ meta.append(kind, size, reference);
685
+ details.append(title, meta);
686
+
687
+ var actions = document.createElement("div");
688
+ actions.className = "artifact-row-actions";
689
+ var raw = document.createElement("a");
690
+ raw.href = artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/raw");
691
+ raw.target = "_blank";
692
+ raw.rel = "noopener noreferrer";
693
+ raw.textContent = "Raw";
694
+ var download = document.createElement("a");
695
+ download.href = artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/download");
696
+ download.textContent = "Download";
697
+ actions.append(raw, download);
698
+ row.append(details, actions);
699
+ return row;
700
+ }
701
+
702
+ function artifactEmpty(message) {
703
+ var empty = document.createElement("div");
704
+ empty.className = "artifact-empty";
705
+ empty.textContent = message;
706
+ return empty;
707
+ }
708
+
709
+ function previewArtifact(explorer, item) {
710
+ var requestId = state.artifacts.previewRequestId + 1;
711
+ state.artifacts.previewRequestId = requestId;
712
+ state.artifacts.selectedId = item.id;
713
+ state.artifacts.actionStatus = null;
714
+ state.artifacts.actionStatusFailed = false;
715
+ if (artifactKind(item) === "binary" || artifactKind(item) === "unknown") {
716
+ state.artifacts.preview = {
717
+ artifactId: item.id,
718
+ loading: false,
719
+ placeholder: true,
720
+ renderKind: artifactKind(item),
721
+ kind: artifactKind(item),
722
+ artifact: item,
723
+ sizeBytes: item.sizeBytes,
724
+ loadedBytes: 0,
725
+ content: "",
726
+ };
727
+ renderArtifactList(explorer);
728
+ renderArtifactPreview(explorer);
729
+ return;
730
+ }
731
+ state.artifacts.preview = {
732
+ artifactId: item.id,
733
+ loading: true,
734
+ content: "Loading preview...",
735
+ renderKind: artifactKind(item),
736
+ kind: artifactKind(item),
737
+ artifact: item,
738
+ };
739
+ renderArtifactList(explorer);
740
+ renderArtifactPreview(explorer);
741
+ fetch(artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/preview"))
742
+ .then(function (response) {
743
+ return response.json().then(function (body) {
744
+ if (!response.ok) {
745
+ throw new Error(body && body.message ? body.message : "Artifact preview request failed.");
746
+ }
747
+ return body;
748
+ });
749
+ })
750
+ .then(function (preview) {
751
+ if (requestId !== state.artifacts.previewRequestId || state.artifacts.selectedId !== item.id) {
752
+ return;
753
+ }
754
+ state.artifacts.preview = {
755
+ artifactId: item.id,
756
+ loading: false,
757
+ content: preview.content || "",
758
+ truncated: preview.truncated,
759
+ renderKind: preview.renderKind || preview.kind || artifactKind(item),
760
+ kind: preview.kind || preview.renderKind || artifactKind(item),
761
+ artifact: preview.artifact || item,
762
+ sizeBytes: Number.isFinite(preview.sizeBytes) ? preview.sizeBytes : item.sizeBytes,
763
+ loadedBytes: Number.isFinite(preview.loadedBytes) ? preview.loadedBytes : null,
764
+ jsonParseSafe: preview.jsonParseSafe,
765
+ title: preview.artifact && preview.artifact.title ? preview.artifact.title : item.title,
766
+ };
767
+ renderArtifactPreview(explorer);
768
+ })
769
+ .catch(function (error) {
770
+ if (requestId !== state.artifacts.previewRequestId || state.artifacts.selectedId !== item.id) {
771
+ return;
772
+ }
773
+ state.artifacts.preview = {
774
+ artifactId: item.id,
775
+ loading: false,
776
+ content: error.message || "Artifact preview request failed.",
777
+ renderKind: artifactKind(item),
778
+ kind: artifactKind(item),
779
+ artifact: item,
780
+ error: true,
781
+ };
782
+ renderArtifactPreview(explorer);
783
+ });
784
+ }
785
+
786
+ function renderArtifactPreview(explorer) {
787
+ var item = selectedArtifact();
788
+ renderArtifactToolbar(explorer, item);
789
+ var preview = state.artifacts.preview;
790
+ if (!item) {
791
+ elements.artifactSelectedTitle.textContent = "Preview";
792
+ elements.artifactSelectedMeta.textContent = "Select an artifact to preview it.";
793
+ renderPreviewMessage("Select an artifact to preview it.", false);
794
+ return;
795
+ }
796
+ elements.artifactSelectedTitle.textContent = artifactDisplayTitle(item);
797
+ elements.artifactSelectedMeta.textContent = [
798
+ artifactKind(item),
799
+ formatArtifactBytes(item.sizeBytes),
800
+ artifactReference(item),
801
+ ].filter(Boolean).join(" | ");
802
+ if (!preview) {
803
+ renderPreviewMessage("Select an artifact to preview it.", false);
804
+ return;
805
+ }
806
+ renderArtifactPreviewContent(explorer, item, preview);
807
+ }
808
+
809
+ function resetPreviewContainer() {
810
+ elements.artifactPreview.textContent = "";
811
+ elements.artifactPreview.className = "artifact-preview-content text-panel compact";
812
+ }
813
+
814
+ function renderPreviewMessage(message, failed) {
815
+ resetPreviewContainer();
816
+ elements.artifactPreview.classList.toggle("error-text", Boolean(failed));
817
+ elements.artifactPreview.textContent = message;
818
+ }
819
+
820
+ function renderArtifactPreviewContent(explorer, item, preview) {
821
+ if (preview.loading) {
822
+ renderPreviewMessage("Loading preview...", false);
823
+ return;
824
+ }
825
+ if (preview.error) {
826
+ renderPreviewMessage(preview.content || "Artifact preview request failed.", true);
827
+ return;
828
+ }
829
+
830
+ resetPreviewContainer();
831
+ var renderKind = String(preview.renderKind || preview.kind || artifactKind(item));
832
+ if (renderKind === "markdown") {
833
+ renderMarkdownPreview(elements.artifactPreview, preview);
834
+ } else if (renderKind === "json") {
835
+ renderJsonPreview(explorer, elements.artifactPreview, item, preview);
836
+ } else if (renderKind === "diff") {
837
+ renderTextPreview(elements.artifactPreview, preview, "Diff preview");
838
+ } else if (renderKind === "text") {
839
+ renderTextPreview(elements.artifactPreview, preview, "");
840
+ } else {
841
+ renderArtifactPlaceholder(explorer, elements.artifactPreview, item, preview);
842
+ }
843
+ renderTruncationStatus(elements.artifactPreview, preview);
844
+ }
845
+
846
+ function renderTextPreview(container, preview, label) {
847
+ if (label) {
848
+ var badge = document.createElement("div");
849
+ badge.className = "artifact-kind-label";
850
+ badge.textContent = label;
851
+ container.append(badge);
852
+ }
853
+ var block = document.createElement("pre");
854
+ block.className = "artifact-text-preview";
855
+ block.textContent = preview.content || "";
856
+ container.append(block);
857
+ }
858
+
859
+ function renderJsonPreview(explorer, container, item, preview) {
860
+ var viewer = document.createElement("div");
861
+ viewer.className = "artifact-json-viewer";
862
+ var controls = document.createElement("div");
863
+ controls.className = "artifact-preview-modes";
864
+ var artifactId = item.id || "";
865
+ var mode = state.artifacts.viewerModes[artifactId] || "pretty";
866
+ ["pretty", "raw"].forEach(function (nextMode) {
867
+ var button = document.createElement("button");
868
+ button.type = "button";
869
+ button.textContent = nextMode === "pretty" ? "Pretty" : "Raw";
870
+ button.className = mode === nextMode ? "primary" : "";
871
+ button.setAttribute("aria-pressed", mode === nextMode ? "true" : "false");
872
+ button.addEventListener("click", function () {
873
+ state.artifacts.viewerModes[artifactId] = nextMode;
874
+ renderArtifactPreview(explorer);
875
+ });
876
+ controls.append(button);
877
+ });
878
+ viewer.append(controls);
879
+
880
+ var block = document.createElement("pre");
881
+ block.className = "artifact-text-preview artifact-json-preview";
882
+ var raw = preview.content || "";
883
+ if (mode === "raw") {
884
+ block.textContent = raw;
885
+ viewer.append(block);
886
+ container.append(viewer);
887
+ return;
888
+ }
889
+
890
+ if (preview.truncated || preview.jsonParseSafe === false) {
891
+ viewer.append(previewWarning("JSON Pretty is unavailable for truncated or unsafe previews. Raw preview is shown."));
892
+ block.textContent = raw;
893
+ viewer.append(block);
894
+ container.append(viewer);
895
+ return;
896
+ }
897
+
898
+ try {
899
+ block.textContent = JSON.stringify(JSON.parse(raw), null, 2);
900
+ } catch (error) {
901
+ viewer.append(previewWarning("JSON parse error: " + (error && error.message ? error.message : "invalid JSON.")));
902
+ block.textContent = raw;
903
+ }
904
+ viewer.append(block);
905
+ container.append(viewer);
906
+ }
907
+
908
+ function previewWarning(message) {
909
+ var warning = document.createElement("div");
910
+ warning.className = "artifact-preview-warning";
911
+ warning.textContent = message;
912
+ return warning;
913
+ }
914
+
915
+ function renderArtifactPlaceholder(explorer, container, item, preview) {
916
+ var panel = document.createElement("div");
917
+ panel.className = "artifact-placeholder";
918
+ var title = document.createElement("strong");
919
+ title.textContent = artifactKind(item) === "binary" ? "Binary artifact" : "Preview unavailable";
920
+ var meta = document.createElement("dl");
921
+ appendPlaceholderMeta(meta, "Kind", artifactKind(item));
922
+ appendPlaceholderMeta(meta, "Path", artifactReference(item) || item.id || "Unknown");
923
+ appendPlaceholderMeta(meta, "Size", formatArtifactBytes(Number.isFinite(preview.sizeBytes) ? preview.sizeBytes : item.sizeBytes));
924
+ var actions = document.createElement("div");
925
+ actions.className = "artifact-placeholder-actions";
926
+ var raw = document.createElement("a");
927
+ raw.href = artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/raw");
928
+ raw.target = "_blank";
929
+ raw.rel = "noopener noreferrer";
930
+ raw.textContent = "Open raw";
931
+ var download = document.createElement("a");
932
+ download.href = artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/download");
933
+ download.textContent = "Download";
934
+ actions.append(raw, download);
935
+ panel.append(title, meta, actions);
936
+ container.append(panel);
937
+ }
938
+
939
+ function appendPlaceholderMeta(list, label, value) {
940
+ var term = document.createElement("dt");
941
+ term.textContent = label;
942
+ var detail = document.createElement("dd");
943
+ detail.textContent = value || "";
944
+ list.append(term, detail);
945
+ }
946
+
947
+ function renderTruncationStatus(container, preview) {
948
+ if (!preview.truncated) {
949
+ return;
950
+ }
951
+ var status = document.createElement("div");
952
+ status.className = "artifact-truncation";
953
+ var loaded = Number.isFinite(preview.loadedBytes) ? preview.loadedBytes : 0;
954
+ var total = Number.isFinite(preview.sizeBytes) ? preview.sizeBytes : loaded;
955
+ status.textContent = "Preview truncated: loaded " + formatArtifactBytes(loaded) + " of " + formatArtifactBytes(total) + ".";
956
+ container.append(status);
957
+ }
958
+
959
+ function renderMarkdownPreview(container, preview) {
960
+ var root = document.createElement("div");
961
+ root.className = "artifact-rendered-markdown";
962
+ appendMarkdownBlocks(root, preview.content || "");
963
+ container.append(root);
964
+ }
965
+
966
+ function appendMarkdownBlocks(container, markdown) {
967
+ var lines = String(markdown || "").replace(/\r\n?/g, "\n").split("\n");
968
+ var index = 0;
969
+ while (index < lines.length) {
970
+ var line = lines[index];
971
+ if (!line.trim()) {
972
+ index += 1;
973
+ continue;
974
+ }
975
+
976
+ var fence = line.match(/^\s*(```+|~~~+)\s*([^\s`]*)\s*$/);
977
+ if (fence) {
978
+ var marker = fence[1];
979
+ var codeLines = [];
980
+ index += 1;
981
+ while (index < lines.length && lines[index].trim() !== marker) {
982
+ codeLines.push(lines[index]);
983
+ index += 1;
984
+ }
985
+ if (index < lines.length) index += 1;
986
+ var pre = document.createElement("pre");
987
+ var code = document.createElement("code");
988
+ if (fence[2]) {
989
+ code.dataset.language = fence[2];
990
+ }
991
+ code.textContent = codeLines.join("\n");
992
+ pre.append(code);
993
+ container.append(pre);
994
+ continue;
995
+ }
996
+
997
+ if (isMarkdownTable(lines, index)) {
998
+ var tableResult = renderMarkdownTable(lines, index);
999
+ container.append(tableResult.table);
1000
+ index = tableResult.nextIndex;
1001
+ continue;
1002
+ }
1003
+
1004
+ var heading = line.match(/^\s{0,3}(#{1,6})\s+(.+?)\s*#*\s*$/);
1005
+ if (heading) {
1006
+ var headingNode = document.createElement("h" + heading[1].length);
1007
+ appendInlineMarkdown(headingNode, heading[2]);
1008
+ container.append(headingNode);
1009
+ index += 1;
1010
+ continue;
1011
+ }
1012
+
1013
+ var listMatch = line.match(/^\s{0,3}((?:[-*+])|(?:\d+[.)]))\s+(.+)$/);
1014
+ if (listMatch) {
1015
+ var ordered = /\d/.test(listMatch[1]);
1016
+ var list = document.createElement(ordered ? "ol" : "ul");
1017
+ while (index < lines.length) {
1018
+ var current = lines[index].match(/^\s{0,3}((?:[-*+])|(?:\d+[.)]))\s+(.+)$/);
1019
+ if (!current || /\d/.test(current[1]) !== ordered) {
1020
+ break;
1021
+ }
1022
+ var item = document.createElement("li");
1023
+ appendInlineMarkdown(item, current[2]);
1024
+ list.append(item);
1025
+ index += 1;
1026
+ }
1027
+ container.append(list);
1028
+ continue;
1029
+ }
1030
+
1031
+ var paragraphLines = [line.trim()];
1032
+ index += 1;
1033
+ while (index < lines.length && lines[index].trim() && !isMarkdownBlockStart(lines, index)) {
1034
+ paragraphLines.push(lines[index].trim());
1035
+ index += 1;
1036
+ }
1037
+ var paragraph = document.createElement("p");
1038
+ appendInlineMarkdown(paragraph, paragraphLines.join(" "));
1039
+ container.append(paragraph);
1040
+ }
1041
+ }
1042
+
1043
+ function isMarkdownBlockStart(lines, index) {
1044
+ var line = lines[index] || "";
1045
+ return /^\s*(```+|~~~+)/.test(line)
1046
+ || /^\s{0,3}#{1,6}\s+/.test(line)
1047
+ || /^\s{0,3}((?:[-*+])|(?:\d+[.)]))\s+/.test(line)
1048
+ || isMarkdownTable(lines, index);
1049
+ }
1050
+
1051
+ function isMarkdownTable(lines, index) {
1052
+ return index + 1 < lines.length
1053
+ && lines[index].indexOf("|") !== -1
1054
+ && /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(lines[index + 1] || "");
1055
+ }
1056
+
1057
+ function splitMarkdownTableRow(line) {
1058
+ var value = String(line || "").trim();
1059
+ if (value.charAt(0) === "|") value = value.slice(1);
1060
+ if (value.charAt(value.length - 1) === "|") value = value.slice(0, -1);
1061
+ return value.split("|").map(function (cell) {
1062
+ return cell.trim();
1063
+ });
1064
+ }
1065
+
1066
+ function renderMarkdownTable(lines, startIndex) {
1067
+ var table = document.createElement("table");
1068
+ var thead = document.createElement("thead");
1069
+ var tbody = document.createElement("tbody");
1070
+ var headerRow = document.createElement("tr");
1071
+ splitMarkdownTableRow(lines[startIndex]).forEach(function (cell) {
1072
+ var th = document.createElement("th");
1073
+ appendInlineMarkdown(th, cell);
1074
+ headerRow.append(th);
1075
+ });
1076
+ thead.append(headerRow);
1077
+ var index = startIndex + 2;
1078
+ while (index < lines.length && lines[index].indexOf("|") !== -1 && lines[index].trim()) {
1079
+ var row = document.createElement("tr");
1080
+ splitMarkdownTableRow(lines[index]).forEach(function (cell) {
1081
+ var td = document.createElement("td");
1082
+ appendInlineMarkdown(td, cell);
1083
+ row.append(td);
1084
+ });
1085
+ tbody.append(row);
1086
+ index += 1;
1087
+ }
1088
+ table.append(thead, tbody);
1089
+ return { table: table, nextIndex: index };
1090
+ }
1091
+
1092
+ function appendInlineMarkdown(parent, value) {
1093
+ var source = String(value || "");
1094
+ var pattern = /(`[^`]+`)|(\[([^\]]+)\]\(([^)\s]+)\))/g;
1095
+ var cursor = 0;
1096
+ var match;
1097
+ while ((match = pattern.exec(source))) {
1098
+ if (match.index > cursor) {
1099
+ parent.append(document.createTextNode(source.slice(cursor, match.index)));
1100
+ }
1101
+ if (match[1]) {
1102
+ var code = document.createElement("code");
1103
+ code.textContent = match[1].slice(1, -1);
1104
+ parent.append(code);
1105
+ } else {
1106
+ var href = safeMarkdownHref(match[4]);
1107
+ if (href) {
1108
+ var link = document.createElement("a");
1109
+ link.href = href;
1110
+ link.target = "_blank";
1111
+ link.rel = "noopener noreferrer";
1112
+ link.textContent = match[3];
1113
+ parent.append(link);
1114
+ } else {
1115
+ parent.append(document.createTextNode(match[0]));
1116
+ }
1117
+ }
1118
+ cursor = pattern.lastIndex;
1119
+ }
1120
+ if (cursor < source.length) {
1121
+ parent.append(document.createTextNode(source.slice(cursor)));
1122
+ }
1123
+ }
1124
+
1125
+ function safeMarkdownHref(value) {
1126
+ var raw = String(value || "").trim();
1127
+ try {
1128
+ var parsed = new URL(raw, window.location.href);
1129
+ if (parsed.protocol === "http:" || parsed.protocol === "https:" || parsed.protocol === "mailto:") {
1130
+ return parsed.href;
1131
+ }
1132
+ } catch {
1133
+ return null;
1134
+ }
1135
+ return null;
1136
+ }
1137
+
1138
+ function renderArtifactToolbar(explorer, item) {
1139
+ var preview = state.artifacts.preview;
1140
+ var hasItem = Boolean(item);
1141
+ var hasContent = hasItem && preview && !preview.loading && !preview.error && typeof preview.content === "string";
1142
+ elements.artifactCopyContent.disabled = !hasContent;
1143
+ elements.artifactCopyReference.disabled = !hasItem;
1144
+ setArtifactActionLink(elements.artifactOpenRaw, hasItem ? artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/raw") : "");
1145
+ setArtifactActionLink(elements.artifactDownload, hasItem ? artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/download") : "");
1146
+ elements.artifactActionStatus.textContent = state.artifacts.actionStatus || "";
1147
+ elements.artifactActionStatus.classList.toggle("error-text", Boolean(state.artifacts.actionStatusFailed));
1148
+ }
1149
+
1150
+ function setArtifactActionLink(link, href) {
1151
+ if (href) {
1152
+ link.href = href;
1153
+ link.classList.remove("disabled");
1154
+ link.setAttribute("aria-disabled", "false");
1155
+ } else {
1156
+ link.removeAttribute("href");
1157
+ link.classList.add("disabled");
1158
+ link.setAttribute("aria-disabled", "true");
1159
+ }
1160
+ }
1161
+
1162
+ function setArtifactActionStatus(message, failed) {
1163
+ state.artifacts.actionStatus = message;
1164
+ state.artifacts.actionStatusFailed = Boolean(failed);
1165
+ elements.artifactActionStatus.textContent = message || "";
1166
+ elements.artifactActionStatus.classList.toggle("error-text", Boolean(failed));
1167
+ if (failed && message) {
1168
+ appendLog("[artifacts] " + message);
1169
+ }
1170
+ }
1171
+
1172
+ function writeClipboard(value, successMessage) {
1173
+ if (typeof navigator === "undefined" || !navigator.clipboard || typeof navigator.clipboard.writeText !== "function") {
1174
+ setArtifactActionStatus("Clipboard is not available in this browser.", true);
1175
+ return Promise.resolve(false);
1176
+ }
1177
+ return navigator.clipboard.writeText(value).then(function () {
1178
+ setArtifactActionStatus(successMessage, false);
1179
+ return true;
1180
+ }).catch(function (error) {
1181
+ setArtifactActionStatus("Clipboard copy failed: " + (error && error.message ? error.message : "permission denied."), true);
1182
+ return false;
1183
+ });
1184
+ }
1185
+
1186
+ function copySelectedArtifactContent() {
1187
+ var preview = state.artifacts.preview;
1188
+ if (!preview || preview.loading || preview.error || typeof preview.content !== "string") {
1189
+ setArtifactActionStatus("Preview content is not ready to copy.", true);
1190
+ return;
1191
+ }
1192
+ writeClipboard(preview.content, "Copied artifact preview content.");
1193
+ }
1194
+
1195
+ function copySelectedArtifactReference() {
1196
+ var item = selectedArtifact();
1197
+ if (!item) {
1198
+ setArtifactActionStatus("No artifact is selected.", true);
1199
+ return;
1200
+ }
1201
+ writeClipboard(artifactReference(item), "Copied artifact path/reference.");
1202
+ }
1203
+
1204
+ function renderModal(vm) {
1205
+ var nextSignature = modalSignature(vm);
1206
+ if (state.modalSignature === nextSignature) {
1207
+ return;
1208
+ }
1209
+ state.modalSignature = nextSignature;
1210
+ elements.modalRoot.innerHTML = "";
1211
+ if (vm.confirmation) {
1212
+ elements.modalRoot.append(renderConfirmation(vm.confirmation));
1213
+ return;
1214
+ }
1215
+ if (vm.confirmText) {
1216
+ elements.modalRoot.append(renderConfirmation({
1217
+ kind: "run",
1218
+ text: vm.confirmText,
1219
+ actions: ["ok", "cancel"],
1220
+ selectedAction: "ok",
1221
+ }));
1222
+ return;
1223
+ }
1224
+ if (vm.form) {
1225
+ elements.modalRoot.append(renderForm(vm.form));
1226
+ }
1227
+ }
1228
+
1229
+ function modalSignature(vm) {
1230
+ if (vm.confirmation) {
1231
+ return stableJson({
1232
+ type: "confirmation",
1233
+ kind: vm.confirmation.kind,
1234
+ flowId: vm.confirmation.flowId,
1235
+ text: vm.confirmation.text,
1236
+ actions: vm.confirmation.actions,
1237
+ selectedAction: vm.confirmation.selectedAction,
1238
+ });
1239
+ }
1240
+ if (vm.confirmText) {
1241
+ return stableJson({
1242
+ type: "legacy-confirmation",
1243
+ text: vm.confirmText,
1244
+ });
1245
+ }
1246
+ if (vm.form) {
1247
+ var definition = vm.form.definition || {};
1248
+ return stableJson({
1249
+ type: "form",
1250
+ formId: vm.form.formId,
1251
+ title: vm.form.title || definition.title,
1252
+ description: definition.description || "",
1253
+ footer: vm.form.footer,
1254
+ error: vm.form.error,
1255
+ submitLabel: definition.submitLabel,
1256
+ preview: definition.preview,
1257
+ fields: vm.form.fields || definition.fields || [],
1258
+ });
1259
+ }
1260
+ return "none";
1261
+ }
1262
+
1263
+ function stableJson(value) {
1264
+ try {
1265
+ return JSON.stringify(value);
1266
+ } catch (error) {
1267
+ return String(Date.now());
1268
+ }
1269
+ }
1270
+
1271
+ function renderConfirmation(confirmation) {
1272
+ var modal = createModal("Confirm " + text(confirmation.kind, "action"), confirmation.text || "");
1273
+ var body = modal.querySelector(".modal-body");
1274
+ var selected = document.createElement("p");
1275
+ selected.className = "modal-note";
1276
+ selected.textContent = "Selected action: " + text(confirmation.selectedAction, "none");
1277
+ body.append(selected);
1278
+
1279
+ var actions = Array.isArray(confirmation.actions) && confirmation.actions.length > 0 ? confirmation.actions : ["ok", "cancel"];
1280
+ var footerActions = modal.querySelector(".modal-actions");
1281
+ actions.forEach(function (name) {
1282
+ var button = document.createElement("button");
1283
+ button.type = "button";
1284
+ button.className = name === confirmation.selectedAction ? "primary" : "";
1285
+ if (name === "stop") button.className = "danger";
1286
+ button.textContent = name === "ok" ? "OK" : name.charAt(0).toUpperCase() + name.slice(1);
1287
+ button.addEventListener("click", function () {
1288
+ api.selectAndAcceptConfirmation(name);
1289
+ });
1290
+ footerActions.append(button);
1291
+ });
1292
+ if (actions.indexOf("cancel") === -1) {
1293
+ var cancel = document.createElement("button");
1294
+ cancel.type = "button";
1295
+ cancel.textContent = "Cancel";
1296
+ cancel.addEventListener("click", function () {
1297
+ api.send({ type: "confirm.cancel" });
1298
+ });
1299
+ footerActions.append(cancel);
1300
+ }
1301
+ return modal;
1302
+ }
1303
+
1304
+ function createModal(title, note) {
1305
+ var modal = document.createElement("section");
1306
+ modal.className = "modal";
1307
+ modal.setAttribute("role", "dialog");
1308
+ modal.setAttribute("aria-modal", "true");
1309
+
1310
+ var header = document.createElement("div");
1311
+ header.className = "modal-header";
1312
+ var heading = document.createElement("h2");
1313
+ heading.textContent = title;
1314
+ var paragraph = document.createElement("p");
1315
+ paragraph.textContent = note || "";
1316
+ header.append(heading, paragraph);
1317
+
1318
+ var body = document.createElement("div");
1319
+ body.className = "modal-body";
1320
+ var footer = document.createElement("div");
1321
+ footer.className = "modal-footer";
1322
+ var footerNote = document.createElement("span");
1323
+ footerNote.className = "modal-note";
1324
+ footerNote.textContent = "";
1325
+ var actions = document.createElement("div");
1326
+ actions.className = "modal-actions";
1327
+ footer.append(footerNote, actions);
1328
+ modal.append(header, body, footer);
1329
+ return modal;
1330
+ }
1331
+
1332
+ function renderForm(formModel) {
1333
+ var definition = formModel.definition || {};
1334
+ var modal = createModal(text(formModel.title || definition.title, "Input required"), text(definition.description, ""));
1335
+ modal.classList.add("form-" + classNameToken(formModel.formId || definition.formId || "unknown"));
1336
+ var body = modal.querySelector(".modal-body");
1337
+ var footerNote = modal.querySelector(".modal-note");
1338
+ var footerActions = modal.querySelector(".modal-actions");
1339
+
1340
+ if (definition.preview) {
1341
+ var preview = document.createElement("pre");
1342
+ preview.className = "text-panel compact";
1343
+ preview.textContent = definition.preview;
1344
+ body.append(preview);
1345
+ }
1346
+
1347
+ if (formModel.error) {
1348
+ var error = document.createElement("div");
1349
+ error.className = "error-text";
1350
+ error.textContent = formModel.error;
1351
+ body.append(error);
1352
+ }
1353
+
1354
+ var fields = document.createElement("div");
1355
+ fields.className = "form-fields";
1356
+ if ((formModel.formId || definition.formId) === "flow-routing-editor") {
1357
+ fields.classList.add("route-fields");
1358
+ }
1359
+ appendRenderedFields(fields, formModel.fields || definition.fields || [], (formModel.formId || definition.formId) === "flow-routing-editor");
1360
+ body.append(fields);
1361
+
1362
+ footerNote.textContent = text(formModel.footer, "");
1363
+ var submit = document.createElement("button");
1364
+ submit.type = "button";
1365
+ submit.className = "primary";
1366
+ submit.textContent = text(definition.submitLabel, "Submit");
1367
+ submit.addEventListener("click", api.submitForm);
1368
+ var cancel = document.createElement("button");
1369
+ cancel.type = "button";
1370
+ cancel.textContent = "Cancel";
1371
+ cancel.addEventListener("click", api.cancelForm);
1372
+ footerActions.append(submit, cancel);
1373
+ return modal;
1374
+ }
1375
+
1376
+ function appendRenderedFields(container, fields, pairExecutorModelFields) {
1377
+ var index = 0;
1378
+ while (index < fields.length) {
1379
+ var field = fields[index];
1380
+ var next = fields[index + 1];
1381
+ if (pairExecutorModelFields && isExecutorModelPair(field, next)) {
1382
+ var pair = document.createElement("div");
1383
+ pair.className = "field-pair executor-model-pair";
1384
+ pair.dataset.routeKey = field.id.slice(0, -"executor".length).replace(/_$/, "");
1385
+ pair.append(renderField(field), renderField(next));
1386
+ container.append(pair);
1387
+ index += 2;
1388
+ continue;
1389
+ }
1390
+ container.append(renderField(field));
1391
+ index += 1;
1392
+ }
1393
+ }
1394
+
1395
+ function isExecutorModelPair(field, next) {
1396
+ return Boolean(
1397
+ field
1398
+ && next
1399
+ && field.type === "single-select"
1400
+ && next.type === "single-select"
1401
+ && typeof field.id === "string"
1402
+ && typeof next.id === "string"
1403
+ && field.id.endsWith("_executor")
1404
+ && next.id === field.id.slice(0, -"executor".length) + "model",
1405
+ );
1406
+ }
1407
+
1408
+ function classNameToken(value) {
1409
+ return String(value).toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
1410
+ }
1411
+
1412
+ function currentValue(field) {
1413
+ if (Object.prototype.hasOwnProperty.call(state.formValues, field.id)) {
1414
+ return state.formValues[field.id];
1415
+ }
1416
+ if (Object.prototype.hasOwnProperty.call(field, "default")) {
1417
+ return field.default;
1418
+ }
1419
+ if (field.type === "boolean") return false;
1420
+ if (field.type === "multi-select") return [];
1421
+ if (field.type === "single-select") return field.options && field.options[0] ? field.options[0].value : "";
1422
+ return "";
1423
+ }
1424
+
1425
+ function renderField(field) {
1426
+ var wrapper = document.createElement("div");
1427
+ wrapper.className = "field";
1428
+ wrapper.dataset.fieldId = field.id;
1429
+ wrapper.dataset.fieldType = field.type;
1430
+
1431
+ if (field.type === "boolean") {
1432
+ var checkRow = document.createElement("label");
1433
+ checkRow.className = "check-row";
1434
+ var checkbox = document.createElement("input");
1435
+ checkbox.type = "checkbox";
1436
+ checkbox.dataset.fieldId = field.id;
1437
+ checkbox.dataset.fieldType = field.type;
1438
+ checkbox.checked = currentValue(field) === true;
1439
+ checkbox.addEventListener("change", function () {
1440
+ updateField(field.id, checkbox.checked);
1441
+ });
1442
+ checkRow.append(checkbox, document.createTextNode(field.label + (field.required ? " *" : "")));
1443
+ wrapper.append(checkRow);
1444
+ } else {
1445
+ var label = document.createElement("label");
1446
+ label.setAttribute("for", "field-" + field.id);
1447
+ label.textContent = field.label + (field.required ? " *" : "");
1448
+ wrapper.append(label);
1449
+ if (field.type === "text") {
1450
+ var input = document.createElement(field.multiline ? "textarea" : "input");
1451
+ input.id = "field-" + field.id;
1452
+ if (!field.multiline) input.type = "text";
1453
+ if (field.placeholder) input.placeholder = field.placeholder;
1454
+ if (field.rows) input.rows = field.rows;
1455
+ input.dataset.fieldId = field.id;
1456
+ input.dataset.fieldType = field.type;
1457
+ input.value = String(currentValue(field) || "");
1458
+ input.addEventListener("input", function () {
1459
+ updateField(field.id, input.value);
1460
+ });
1461
+ wrapper.append(input);
1462
+ } else if (field.type === "single-select") {
1463
+ wrapper.append(renderSingleSelect(field));
1464
+ } else if (field.type === "multi-select") {
1465
+ wrapper.append(renderMultiSelect(field));
1466
+ }
1467
+ }
1468
+
1469
+ if (field.help) {
1470
+ var help = document.createElement("div");
1471
+ help.className = "field-help";
1472
+ help.textContent = field.help;
1473
+ wrapper.append(help);
1474
+ }
1475
+ return wrapper;
1476
+ }
1477
+
1478
+ function renderSingleSelect(field) {
1479
+ var list = document.createElement("div");
1480
+ list.className = "option-list";
1481
+ var value = String(currentValue(field) || "");
1482
+ (field.options || []).forEach(function (option) {
1483
+ var label = document.createElement("label");
1484
+ label.className = "field-option";
1485
+ var input = document.createElement("input");
1486
+ input.type = "radio";
1487
+ input.name = "field-" + field.id;
1488
+ input.value = option.value;
1489
+ input.dataset.fieldId = field.id;
1490
+ input.dataset.fieldType = field.type;
1491
+ input.checked = option.value === value;
1492
+ input.addEventListener("change", function () {
1493
+ if (input.checked) updateField(field.id, option.value);
1494
+ });
1495
+ var textWrap = document.createElement("span");
1496
+ textWrap.append(document.createTextNode(option.label));
1497
+ if (option.description) {
1498
+ var description = document.createElement("small");
1499
+ description.textContent = option.description;
1500
+ textWrap.append(document.createElement("br"), description);
1501
+ }
1502
+ label.append(input, textWrap);
1503
+ list.append(label);
1504
+ });
1505
+ return list;
1506
+ }
1507
+
1508
+ function renderMultiSelect(field) {
1509
+ var list = document.createElement("div");
1510
+ list.className = "option-list";
1511
+ var value = currentValue(field);
1512
+ var selected = Array.isArray(value) ? value : [];
1513
+ (field.options || []).forEach(function (option) {
1514
+ var label = document.createElement("label");
1515
+ label.className = "field-option";
1516
+ var input = document.createElement("input");
1517
+ input.type = "checkbox";
1518
+ input.value = option.value;
1519
+ input.dataset.fieldId = field.id;
1520
+ input.dataset.fieldType = field.type;
1521
+ input.checked = selected.indexOf(option.value) !== -1;
1522
+ input.addEventListener("change", function () {
1523
+ var next = collectMultiSelect(field.id);
1524
+ updateField(field.id, next);
1525
+ });
1526
+ var textWrap = document.createElement("span");
1527
+ textWrap.append(document.createTextNode(option.label));
1528
+ if (option.description) {
1529
+ var description = document.createElement("small");
1530
+ description.textContent = option.description;
1531
+ textWrap.append(document.createElement("br"), description);
1532
+ }
1533
+ label.append(input, textWrap);
1534
+ list.append(label);
1535
+ });
1536
+ return list;
1537
+ }
1538
+
1539
+ function updateField(fieldId, value) {
1540
+ state.formValues[fieldId] = value;
1541
+ api.send({ type: "form.fieldUpdate", fieldId: fieldId, value: value });
1542
+ }
1543
+
1544
+ function collectMultiSelect(fieldId) {
1545
+ return Array.prototype.slice.call(elements.modalRoot.querySelectorAll('[data-field-id="' + fieldId + '"] input[type="checkbox"]'))
1546
+ .filter(function (input) { return input.checked; })
1547
+ .map(function (input) { return input.value; });
1548
+ }
1549
+
1550
+ function collectFormValues() {
1551
+ return Object.assign({}, state.formValues);
1552
+ }
1553
+
1554
+ elements.run.addEventListener("click", api.openRunConfirm);
1555
+ elements.interrupt.addEventListener("click", api.openInterruptConfirm);
1556
+ elements.artifactOpen.addEventListener("click", api.openArtifactExplorer);
1557
+ elements.artifactClose.addEventListener("click", api.closeArtifactExplorer);
1558
+ elements.artifactToolbarClose.addEventListener("click", api.closeArtifactExplorer);
1559
+ elements.artifactCopyContent.addEventListener("click", copySelectedArtifactContent);
1560
+ elements.artifactCopyReference.addEventListener("click", copySelectedArtifactReference);
1561
+ elements.help.addEventListener("click", api.toggleHelp);
1562
+ elements.closeHelp.addEventListener("click", function () {
1563
+ api.showHelp(false);
1564
+ });
1565
+ elements.clearLog.addEventListener("click", api.clearLog);
1566
+ 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));
1571
+ });
1572
+ elements.log.addEventListener("scroll", function () {
1573
+ api.scrollPane("log", Math.round(elements.log.scrollTop));
1574
+ });
1575
+ elements.helpText.addEventListener("scroll", function () {
1576
+ api.scrollPane("help", Math.round(elements.helpText.scrollTop));
1577
+ });
1578
+
1579
+ api.connect();
1580
+ }());