agentweaver 0.1.17 → 0.1.18

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 (47) hide show
  1. package/README.md +104 -23
  2. package/dist/artifacts.js +41 -0
  3. package/dist/index.js +252 -27
  4. package/dist/interactive/controller.js +249 -13
  5. package/dist/interactive/ink/index.js +2 -2
  6. package/dist/interactive/state.js +1 -0
  7. package/dist/interactive/web/index.js +179 -0
  8. package/dist/interactive/web/protocol.js +154 -0
  9. package/dist/interactive/web/server.js +575 -0
  10. package/dist/interactive/web/static/app.js +709 -0
  11. package/dist/interactive/web/static/index.html +77 -0
  12. package/dist/interactive/web/static/styles.css +2 -0
  13. package/dist/interactive/web/static/styles.input.css +469 -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/playbook.js +485 -0
  40. package/dist/runtime/project-guidance.js +339 -0
  41. package/dist/structured-artifact-schema-registry.js +8 -0
  42. package/dist/structured-artifact-schemas.json +235 -0
  43. package/dist/structured-artifacts.js +7 -1
  44. package/docs/declarative-workflows.md +565 -0
  45. package/docs/features.md +77 -0
  46. package/docs/playbook.md +327 -0
  47. package/package.json +8 -3
@@ -0,0 +1,709 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ var state = {
5
+ viewModel: null,
6
+ connectionState: "connecting",
7
+ formValues: {},
8
+ modalSignature: null,
9
+ };
10
+
11
+ var elements = {
12
+ title: document.getElementById("app-title"),
13
+ header: document.getElementById("header-text"),
14
+ connection: document.getElementById("connection-state"),
15
+ status: document.getElementById("session-status"),
16
+ run: document.getElementById("run-button"),
17
+ interrupt: document.getElementById("interrupt-button"),
18
+ help: document.getElementById("help-button"),
19
+ flowsTitle: document.getElementById("flows-title"),
20
+ flows: document.getElementById("flows-list"),
21
+ description: document.getElementById("description-text"),
22
+ progressTitle: document.getElementById("progress-title"),
23
+ progress: document.getElementById("progress-text"),
24
+ summaryTitle: document.getElementById("summary-title"),
25
+ summary: document.getElementById("summary-text"),
26
+ logTitle: document.getElementById("log-title"),
27
+ log: document.getElementById("log-text"),
28
+ clearLog: document.getElementById("clear-log-button"),
29
+ helpPanel: document.getElementById("help-panel"),
30
+ helpText: document.getElementById("help-text"),
31
+ closeHelp: document.getElementById("close-help-button"),
32
+ modalRoot: document.getElementById("modal-root"),
33
+ };
34
+
35
+ function text(value, fallback) {
36
+ if (typeof value === "string" && value.length > 0) {
37
+ return value;
38
+ }
39
+ return fallback;
40
+ }
41
+
42
+ function actionId() {
43
+ return "web-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
44
+ }
45
+
46
+ function selectedFlow() {
47
+ var vm = state.viewModel;
48
+ if (!vm || !Array.isArray(vm.flowItems)) {
49
+ return null;
50
+ }
51
+ return vm.flowItems[vm.selectedFlowIndex] || null;
52
+ }
53
+
54
+ function isFolder(item) {
55
+ if (item && item.kind) {
56
+ return item.kind === "folder";
57
+ }
58
+ var key = String(item && item.key ? item.key : "");
59
+ var label = String(item && item.label ? item.label : "");
60
+ return key.indexOf("folder:") === 0 || key.indexOf("folder/") === 0 || label.trim().endsWith("/");
61
+ }
62
+
63
+ function flowMeta(item) {
64
+ var key = String(item && item.key ? item.key : "");
65
+ if (key.indexOf("project") >= 0) return "project";
66
+ if (key.indexOf("global") >= 0) return "global";
67
+ if (key.indexOf("built") >= 0 || key.indexOf("default") >= 0) return "built-in";
68
+ return key;
69
+ }
70
+
71
+ var api = {
72
+ socket: null,
73
+ send: function (message) {
74
+ if (!message.actionId) {
75
+ message.actionId = actionId();
76
+ }
77
+ if (!api.socket || api.socket.readyState !== WebSocket.OPEN) {
78
+ appendLog("[web] Cannot send action while disconnected: " + message.type);
79
+ return;
80
+ }
81
+ api.socket.send(JSON.stringify(message));
82
+ },
83
+ connect: function () {
84
+ setConnection("connecting");
85
+ var scheme = window.location.protocol === "https:" ? "wss://" : "ws://";
86
+ api.socket = new WebSocket(scheme + window.location.host + "/__agentweaver/ws");
87
+ api.socket.addEventListener("open", function () {
88
+ setConnection("connected");
89
+ });
90
+ api.socket.addEventListener("close", function () {
91
+ setConnection(state.connectionState === "closed" ? "closed" : "disconnected");
92
+ });
93
+ api.socket.addEventListener("error", function () {
94
+ setConnection("error");
95
+ });
96
+ api.socket.addEventListener("message", function (event) {
97
+ handleServerEvent(event.data);
98
+ });
99
+ },
100
+ selectFlow: function (index, key) {
101
+ api.send({ type: "flow.select", index: index, key: key });
102
+ },
103
+ toggleFolder: function (key) {
104
+ api.send({ type: "folder.toggle", key: key });
105
+ },
106
+ openRunConfirm: function () {
107
+ var flow = selectedFlow();
108
+ api.send({ type: "run.openConfirm", key: flow ? flow.key : undefined });
109
+ },
110
+ selectAndAcceptConfirmation: function (action) {
111
+ if (action === "cancel") {
112
+ api.send({ type: "confirm.cancel" });
113
+ return;
114
+ }
115
+ api.send({ type: "confirm.accept", action: action });
116
+ },
117
+ submitForm: function () {
118
+ api.send({ type: "form.submit", values: collectFormValues() });
119
+ },
120
+ cancelForm: function () {
121
+ api.send({ type: "form.cancel" });
122
+ },
123
+ openInterruptConfirm: function () {
124
+ api.send({ type: "interrupt.openConfirm" });
125
+ },
126
+ interruptFlow: function () {
127
+ api.send({ type: "flow.interrupt" });
128
+ },
129
+ clearLog: function () {
130
+ api.send({ type: "log.clear" });
131
+ },
132
+ toggleHelp: function () {
133
+ api.send({ type: "help.toggle", visible: !(state.viewModel && state.viewModel.helpVisible) });
134
+ },
135
+ showHelp: function (visible) {
136
+ api.send({ type: "help.toggle", visible: visible });
137
+ },
138
+ scrollPane: function (pane, offset) {
139
+ api.send({ type: "scroll", pane: pane, offset: offset });
140
+ },
141
+ };
142
+
143
+ function setConnection(nextState) {
144
+ state.connectionState = nextState;
145
+ elements.connection.textContent = nextState.charAt(0).toUpperCase() + nextState.slice(1);
146
+ elements.connection.className = "connection connection-" + nextState;
147
+ }
148
+
149
+ function handleServerEvent(raw) {
150
+ var message;
151
+ try {
152
+ message = JSON.parse(raw);
153
+ } catch (error) {
154
+ appendLog("[web] Invalid server message: " + error.message);
155
+ return;
156
+ }
157
+
158
+ if (message.type === "snapshot") {
159
+ var uiState = captureUiState();
160
+ state.viewModel = message.viewModel || {};
161
+ state.formValues = state.viewModel.form ? Object.assign({}, state.viewModel.form.values || {}) : {};
162
+ render();
163
+ restoreUiState(uiState);
164
+ return;
165
+ }
166
+ if (message.type === "log.append") {
167
+ appendLog((message.appendedLines || []).join("\n"));
168
+ return;
169
+ }
170
+ if (message.type === "error") {
171
+ appendLog("[web] " + text(message.message, "Unknown protocol error."));
172
+ return;
173
+ }
174
+ if (message.type === "closed") {
175
+ setConnection("closed");
176
+ appendLog("[closed] " + text(message.reason, "Session closed."));
177
+ }
178
+ }
179
+
180
+ function appendLog(lines) {
181
+ if (!lines) return;
182
+ var wasPinned = elements.log.scrollTop + elements.log.clientHeight >= elements.log.scrollHeight - 8;
183
+ elements.log.textContent += (elements.log.textContent ? "\n" : "") + lines;
184
+ if (wasPinned) {
185
+ elements.log.scrollTop = elements.log.scrollHeight;
186
+ }
187
+ }
188
+
189
+ function render() {
190
+ var vm = state.viewModel || {};
191
+ elements.title.textContent = text(vm.title, "AgentWeaver");
192
+ elements.header.textContent = text(vm.header, "Local operator console");
193
+ elements.status.textContent = text(vm.statusText, "Idle");
194
+ elements.flowsTitle.textContent = text(vm.flowListTitle, "Flows");
195
+ elements.description.textContent = text(vm.descriptionText, "No flow selected.");
196
+ elements.progressTitle.textContent = text(vm.progressTitle, "Progress");
197
+ setTextPreservingScroll(elements.progress, text(vm.progressText, "No progress yet."));
198
+ elements.summaryTitle.textContent = text(vm.summaryTitle, "Task Summary");
199
+ setTextPreservingScroll(elements.summary, vm.summaryVisible === false ? "Summary is hidden." : text(vm.summaryText, "No task summary yet."));
200
+ elements.logTitle.textContent = text(vm.logTitle, "Activity");
201
+ setTextPreservingScroll(elements.log, text(vm.logText, ""));
202
+ elements.helpText.textContent = text(vm.helpText, "No help is available.");
203
+ elements.helpPanel.hidden = !vm.helpVisible;
204
+ elements.help.setAttribute("aria-pressed", vm.helpVisible ? "true" : "false");
205
+
206
+ renderFlows(vm);
207
+ renderModal(vm);
208
+ }
209
+
210
+ function setTextPreservingScroll(element, value) {
211
+ if (element.textContent === value) {
212
+ return;
213
+ }
214
+ var previousScrollTop = element.scrollTop;
215
+ var wasPinned = previousScrollTop + element.clientHeight >= element.scrollHeight - 8;
216
+ element.textContent = value;
217
+ element.scrollTop = wasPinned ? element.scrollHeight : previousScrollTop;
218
+ }
219
+
220
+ function captureUiState() {
221
+ var active = document.activeElement;
222
+ var activeFieldId = active && active.dataset ? active.dataset.fieldId : null;
223
+ var activeFieldValue = active && active.dataset && active.dataset.fieldId && "value" in active ? active.value : null;
224
+ var selectionStart = null;
225
+ var selectionEnd = null;
226
+ var modalBody = currentModalBody();
227
+ if (activeFieldId && typeof active.selectionStart === "number" && typeof active.selectionEnd === "number") {
228
+ selectionStart = active.selectionStart;
229
+ selectionEnd = active.selectionEnd;
230
+ }
231
+ return {
232
+ progressScrollTop: elements.progress.scrollTop,
233
+ summaryScrollTop: elements.summary.scrollTop,
234
+ logScrollTop: elements.log.scrollTop,
235
+ helpScrollTop: elements.helpText.scrollTop,
236
+ modalScrollTop: elements.modalRoot.scrollTop,
237
+ modalBodyScrollTop: modalBody ? modalBody.scrollTop : 0,
238
+ activeFieldId: activeFieldId,
239
+ activeFieldValue: activeFieldValue,
240
+ selectionStart: selectionStart,
241
+ selectionEnd: selectionEnd,
242
+ formId: state.viewModel && state.viewModel.form ? state.viewModel.form.formId : null,
243
+ };
244
+ }
245
+
246
+ function restoreUiState(uiState) {
247
+ if (!uiState) return;
248
+ elements.progress.scrollTop = uiState.progressScrollTop;
249
+ elements.summary.scrollTop = uiState.summaryScrollTop;
250
+ elements.log.scrollTop = uiState.logScrollTop;
251
+ elements.helpText.scrollTop = uiState.helpScrollTop;
252
+ elements.modalRoot.scrollTop = uiState.modalScrollTop;
253
+ var modalBody = currentModalBody();
254
+ if (modalBody) {
255
+ modalBody.scrollTop = uiState.modalBodyScrollTop;
256
+ }
257
+
258
+ var currentFormId = state.viewModel && state.viewModel.form ? state.viewModel.form.formId : null;
259
+ if (!uiState.activeFieldId || uiState.formId !== currentFormId) {
260
+ return;
261
+ }
262
+ var selector = '[data-field-id="' + cssEscape(uiState.activeFieldId) + '"]';
263
+ if (uiState.activeFieldValue !== null) {
264
+ selector += '[value="' + cssEscape(uiState.activeFieldValue) + '"]';
265
+ }
266
+ var field = elements.modalRoot.querySelector(selector);
267
+ if (!field) {
268
+ 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) + '"]');
269
+ }
270
+ if (!field || typeof field.focus !== "function") {
271
+ return;
272
+ }
273
+ field.focus();
274
+ if (typeof field.setSelectionRange === "function" && uiState.selectionStart !== null && uiState.selectionEnd !== null) {
275
+ var valueLength = typeof field.value === "string" ? field.value.length : 0;
276
+ field.setSelectionRange(Math.min(uiState.selectionStart, valueLength), Math.min(uiState.selectionEnd, valueLength));
277
+ }
278
+ }
279
+
280
+ function cssEscape(value) {
281
+ if (window.CSS && typeof window.CSS.escape === "function") {
282
+ return window.CSS.escape(value);
283
+ }
284
+ return String(value).replace(/["\\]/g, "\\$&");
285
+ }
286
+
287
+ function currentModalBody() {
288
+ return elements.modalRoot.querySelector(".modal-body");
289
+ }
290
+
291
+ function renderFlows(vm) {
292
+ elements.flows.innerHTML = "";
293
+ var items = Array.isArray(vm.flowItems) ? vm.flowItems : [];
294
+ if (items.length === 0) {
295
+ var empty = document.createElement("div");
296
+ empty.className = "flow-meta";
297
+ empty.textContent = "No flows are available.";
298
+ elements.flows.append(empty);
299
+ return;
300
+ }
301
+
302
+ items.forEach(function (item, index) {
303
+ var folder = isFolder(item);
304
+ var depth = Number.isFinite(item.depth) ? Math.max(0, item.depth) : 0;
305
+ var row = document.createElement("button");
306
+ row.type = "button";
307
+ row.className = "flow-row" + (folder ? " folder" : "") + (index === vm.selectedFlowIndex ? " selected" : "");
308
+ row.setAttribute("role", folder ? "treeitem" : "option");
309
+ row.style.paddingLeft = String(6 + depth * 18) + "px";
310
+ row.title = item.key || item.label || "";
311
+ row.addEventListener("click", function () {
312
+ if (folder) {
313
+ api.toggleFolder(item.key);
314
+ } else {
315
+ api.selectFlow(index, item.key);
316
+ }
317
+ });
318
+ row.addEventListener("dblclick", function () {
319
+ if (!folder) {
320
+ api.send({ type: "run.openConfirm", key: item.key });
321
+ }
322
+ });
323
+
324
+ var icon = document.createElement("span");
325
+ icon.className = "flow-icon";
326
+ icon.textContent = folder ? (item.expanded ? "▾" : "▸") : "•";
327
+ var label = document.createElement("span");
328
+ label.className = "flow-label";
329
+ label.textContent = text(item.name, item.label || item.key || "Untitled flow").replace(/^[\s▸▾•]+/, "");
330
+ var meta = document.createElement("span");
331
+ meta.className = "flow-meta";
332
+ meta.textContent = flowMeta(item);
333
+ row.append(icon, label, meta);
334
+ elements.flows.append(row);
335
+ });
336
+ }
337
+
338
+ function renderModal(vm) {
339
+ var nextSignature = modalSignature(vm);
340
+ if (state.modalSignature === nextSignature) {
341
+ return;
342
+ }
343
+ state.modalSignature = nextSignature;
344
+ elements.modalRoot.innerHTML = "";
345
+ if (vm.confirmation) {
346
+ elements.modalRoot.append(renderConfirmation(vm.confirmation));
347
+ return;
348
+ }
349
+ if (vm.confirmText) {
350
+ elements.modalRoot.append(renderConfirmation({
351
+ kind: "run",
352
+ text: vm.confirmText,
353
+ actions: ["ok", "cancel"],
354
+ selectedAction: "ok",
355
+ }));
356
+ return;
357
+ }
358
+ if (vm.form) {
359
+ elements.modalRoot.append(renderForm(vm.form));
360
+ }
361
+ }
362
+
363
+ function modalSignature(vm) {
364
+ if (vm.confirmation) {
365
+ return stableJson({
366
+ type: "confirmation",
367
+ kind: vm.confirmation.kind,
368
+ flowId: vm.confirmation.flowId,
369
+ text: vm.confirmation.text,
370
+ actions: vm.confirmation.actions,
371
+ selectedAction: vm.confirmation.selectedAction,
372
+ });
373
+ }
374
+ if (vm.confirmText) {
375
+ return stableJson({
376
+ type: "legacy-confirmation",
377
+ text: vm.confirmText,
378
+ });
379
+ }
380
+ if (vm.form) {
381
+ var definition = vm.form.definition || {};
382
+ return stableJson({
383
+ type: "form",
384
+ formId: vm.form.formId,
385
+ title: vm.form.title || definition.title,
386
+ content: definition.description || vm.form.content,
387
+ footer: vm.form.footer,
388
+ error: vm.form.error,
389
+ submitLabel: definition.submitLabel,
390
+ preview: definition.preview,
391
+ fields: vm.form.fields || definition.fields || [],
392
+ });
393
+ }
394
+ return "none";
395
+ }
396
+
397
+ function stableJson(value) {
398
+ try {
399
+ return JSON.stringify(value);
400
+ } catch (error) {
401
+ return String(Date.now());
402
+ }
403
+ }
404
+
405
+ function renderConfirmation(confirmation) {
406
+ var modal = createModal("Confirm " + text(confirmation.kind, "action"), confirmation.text || "");
407
+ var body = modal.querySelector(".modal-body");
408
+ var selected = document.createElement("p");
409
+ selected.className = "modal-note";
410
+ selected.textContent = "Selected action: " + text(confirmation.selectedAction, "none");
411
+ body.append(selected);
412
+
413
+ var actions = Array.isArray(confirmation.actions) && confirmation.actions.length > 0 ? confirmation.actions : ["ok", "cancel"];
414
+ var footerActions = modal.querySelector(".modal-actions");
415
+ actions.forEach(function (name) {
416
+ var button = document.createElement("button");
417
+ button.type = "button";
418
+ button.className = name === confirmation.selectedAction ? "primary" : "";
419
+ if (name === "stop") button.className = "danger";
420
+ button.textContent = name === "ok" ? "OK" : name.charAt(0).toUpperCase() + name.slice(1);
421
+ button.addEventListener("click", function () {
422
+ api.selectAndAcceptConfirmation(name);
423
+ });
424
+ footerActions.append(button);
425
+ });
426
+ if (actions.indexOf("cancel") === -1) {
427
+ var cancel = document.createElement("button");
428
+ cancel.type = "button";
429
+ cancel.textContent = "Cancel";
430
+ cancel.addEventListener("click", function () {
431
+ api.send({ type: "confirm.cancel" });
432
+ });
433
+ footerActions.append(cancel);
434
+ }
435
+ return modal;
436
+ }
437
+
438
+ function createModal(title, note) {
439
+ var modal = document.createElement("section");
440
+ modal.className = "modal";
441
+ modal.setAttribute("role", "dialog");
442
+ modal.setAttribute("aria-modal", "true");
443
+
444
+ var header = document.createElement("div");
445
+ header.className = "modal-header";
446
+ var heading = document.createElement("h2");
447
+ heading.textContent = title;
448
+ var paragraph = document.createElement("p");
449
+ paragraph.textContent = note || "";
450
+ header.append(heading, paragraph);
451
+
452
+ var body = document.createElement("div");
453
+ body.className = "modal-body";
454
+ var footer = document.createElement("div");
455
+ footer.className = "modal-footer";
456
+ var footerNote = document.createElement("span");
457
+ footerNote.className = "modal-note";
458
+ footerNote.textContent = "";
459
+ var actions = document.createElement("div");
460
+ actions.className = "modal-actions";
461
+ footer.append(footerNote, actions);
462
+ modal.append(header, body, footer);
463
+ return modal;
464
+ }
465
+
466
+ function renderForm(formModel) {
467
+ var definition = formModel.definition || {};
468
+ var modal = createModal(text(formModel.title || definition.title, "Input required"), text(definition.description || formModel.content, ""));
469
+ modal.classList.add("form-" + classNameToken(formModel.formId || definition.formId || "unknown"));
470
+ var body = modal.querySelector(".modal-body");
471
+ var footerNote = modal.querySelector(".modal-note");
472
+ var footerActions = modal.querySelector(".modal-actions");
473
+
474
+ if (definition.preview) {
475
+ var preview = document.createElement("pre");
476
+ preview.className = "text-panel compact";
477
+ preview.textContent = definition.preview;
478
+ body.append(preview);
479
+ }
480
+
481
+ if (formModel.error) {
482
+ var error = document.createElement("div");
483
+ error.className = "error-text";
484
+ error.textContent = formModel.error;
485
+ body.append(error);
486
+ }
487
+
488
+ var fields = document.createElement("div");
489
+ fields.className = "form-fields";
490
+ if ((formModel.formId || definition.formId) === "flow-routing-editor") {
491
+ fields.classList.add("route-fields");
492
+ }
493
+ appendRenderedFields(fields, formModel.fields || definition.fields || [], (formModel.formId || definition.formId) === "flow-routing-editor");
494
+ body.append(fields);
495
+
496
+ footerNote.textContent = text(formModel.footer, "");
497
+ var submit = document.createElement("button");
498
+ submit.type = "button";
499
+ submit.className = "primary";
500
+ submit.textContent = text(definition.submitLabel, "Submit");
501
+ submit.addEventListener("click", api.submitForm);
502
+ var cancel = document.createElement("button");
503
+ cancel.type = "button";
504
+ cancel.textContent = "Cancel";
505
+ cancel.addEventListener("click", api.cancelForm);
506
+ footerActions.append(submit, cancel);
507
+ return modal;
508
+ }
509
+
510
+ function appendRenderedFields(container, fields, pairExecutorModelFields) {
511
+ var index = 0;
512
+ while (index < fields.length) {
513
+ var field = fields[index];
514
+ var next = fields[index + 1];
515
+ if (pairExecutorModelFields && isExecutorModelPair(field, next)) {
516
+ var pair = document.createElement("div");
517
+ pair.className = "field-pair executor-model-pair";
518
+ pair.dataset.routeKey = field.id.slice(0, -"executor".length).replace(/_$/, "");
519
+ pair.append(renderField(field), renderField(next));
520
+ container.append(pair);
521
+ index += 2;
522
+ continue;
523
+ }
524
+ container.append(renderField(field));
525
+ index += 1;
526
+ }
527
+ }
528
+
529
+ function isExecutorModelPair(field, next) {
530
+ return Boolean(
531
+ field
532
+ && next
533
+ && field.type === "single-select"
534
+ && next.type === "single-select"
535
+ && typeof field.id === "string"
536
+ && typeof next.id === "string"
537
+ && field.id.endsWith("_executor")
538
+ && next.id === field.id.slice(0, -"executor".length) + "model",
539
+ );
540
+ }
541
+
542
+ function classNameToken(value) {
543
+ return String(value).toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
544
+ }
545
+
546
+ function currentValue(field) {
547
+ if (Object.prototype.hasOwnProperty.call(state.formValues, field.id)) {
548
+ return state.formValues[field.id];
549
+ }
550
+ if (Object.prototype.hasOwnProperty.call(field, "default")) {
551
+ return field.default;
552
+ }
553
+ if (field.type === "boolean") return false;
554
+ if (field.type === "multi-select") return [];
555
+ if (field.type === "single-select") return field.options && field.options[0] ? field.options[0].value : "";
556
+ return "";
557
+ }
558
+
559
+ function renderField(field) {
560
+ var wrapper = document.createElement("div");
561
+ wrapper.className = "field";
562
+ wrapper.dataset.fieldId = field.id;
563
+ wrapper.dataset.fieldType = field.type;
564
+
565
+ if (field.type === "boolean") {
566
+ var checkRow = document.createElement("label");
567
+ checkRow.className = "check-row";
568
+ var checkbox = document.createElement("input");
569
+ checkbox.type = "checkbox";
570
+ checkbox.dataset.fieldId = field.id;
571
+ checkbox.dataset.fieldType = field.type;
572
+ checkbox.checked = currentValue(field) === true;
573
+ checkbox.addEventListener("change", function () {
574
+ updateField(field.id, checkbox.checked);
575
+ });
576
+ checkRow.append(checkbox, document.createTextNode(field.label + (field.required ? " *" : "")));
577
+ wrapper.append(checkRow);
578
+ } else {
579
+ var label = document.createElement("label");
580
+ label.setAttribute("for", "field-" + field.id);
581
+ label.textContent = field.label + (field.required ? " *" : "");
582
+ wrapper.append(label);
583
+ if (field.type === "text") {
584
+ var input = document.createElement(field.multiline ? "textarea" : "input");
585
+ input.id = "field-" + field.id;
586
+ if (!field.multiline) input.type = "text";
587
+ if (field.placeholder) input.placeholder = field.placeholder;
588
+ if (field.rows) input.rows = field.rows;
589
+ input.dataset.fieldId = field.id;
590
+ input.dataset.fieldType = field.type;
591
+ input.value = String(currentValue(field) || "");
592
+ input.addEventListener("input", function () {
593
+ updateField(field.id, input.value);
594
+ });
595
+ wrapper.append(input);
596
+ } else if (field.type === "single-select") {
597
+ wrapper.append(renderSingleSelect(field));
598
+ } else if (field.type === "multi-select") {
599
+ wrapper.append(renderMultiSelect(field));
600
+ }
601
+ }
602
+
603
+ if (field.help) {
604
+ var help = document.createElement("div");
605
+ help.className = "field-help";
606
+ help.textContent = field.help;
607
+ wrapper.append(help);
608
+ }
609
+ return wrapper;
610
+ }
611
+
612
+ function renderSingleSelect(field) {
613
+ var list = document.createElement("div");
614
+ list.className = "option-list";
615
+ var value = String(currentValue(field) || "");
616
+ (field.options || []).forEach(function (option) {
617
+ var label = document.createElement("label");
618
+ label.className = "field-option";
619
+ var input = document.createElement("input");
620
+ input.type = "radio";
621
+ input.name = "field-" + field.id;
622
+ input.value = option.value;
623
+ input.dataset.fieldId = field.id;
624
+ input.dataset.fieldType = field.type;
625
+ input.checked = option.value === value;
626
+ input.addEventListener("change", function () {
627
+ if (input.checked) updateField(field.id, option.value);
628
+ });
629
+ var textWrap = document.createElement("span");
630
+ textWrap.append(document.createTextNode(option.label));
631
+ if (option.description) {
632
+ var description = document.createElement("small");
633
+ description.textContent = option.description;
634
+ textWrap.append(document.createElement("br"), description);
635
+ }
636
+ label.append(input, textWrap);
637
+ list.append(label);
638
+ });
639
+ return list;
640
+ }
641
+
642
+ function renderMultiSelect(field) {
643
+ var list = document.createElement("div");
644
+ list.className = "option-list";
645
+ var value = currentValue(field);
646
+ var selected = Array.isArray(value) ? value : [];
647
+ (field.options || []).forEach(function (option) {
648
+ var label = document.createElement("label");
649
+ label.className = "field-option";
650
+ var input = document.createElement("input");
651
+ input.type = "checkbox";
652
+ input.value = option.value;
653
+ input.dataset.fieldId = field.id;
654
+ input.dataset.fieldType = field.type;
655
+ input.checked = selected.indexOf(option.value) !== -1;
656
+ input.addEventListener("change", function () {
657
+ var next = collectMultiSelect(field.id);
658
+ updateField(field.id, next);
659
+ });
660
+ var textWrap = document.createElement("span");
661
+ textWrap.append(document.createTextNode(option.label));
662
+ if (option.description) {
663
+ var description = document.createElement("small");
664
+ description.textContent = option.description;
665
+ textWrap.append(document.createElement("br"), description);
666
+ }
667
+ label.append(input, textWrap);
668
+ list.append(label);
669
+ });
670
+ return list;
671
+ }
672
+
673
+ function updateField(fieldId, value) {
674
+ state.formValues[fieldId] = value;
675
+ api.send({ type: "form.fieldUpdate", fieldId: fieldId, value: value });
676
+ }
677
+
678
+ function collectMultiSelect(fieldId) {
679
+ return Array.prototype.slice.call(elements.modalRoot.querySelectorAll('[data-field-id="' + fieldId + '"] input[type="checkbox"]'))
680
+ .filter(function (input) { return input.checked; })
681
+ .map(function (input) { return input.value; });
682
+ }
683
+
684
+ function collectFormValues() {
685
+ return Object.assign({}, state.formValues);
686
+ }
687
+
688
+ elements.run.addEventListener("click", api.openRunConfirm);
689
+ elements.interrupt.addEventListener("click", api.openInterruptConfirm);
690
+ elements.help.addEventListener("click", api.toggleHelp);
691
+ elements.closeHelp.addEventListener("click", function () {
692
+ api.showHelp(false);
693
+ });
694
+ elements.clearLog.addEventListener("click", api.clearLog);
695
+ elements.progress.addEventListener("scroll", function () {
696
+ api.scrollPane("progress", Math.round(elements.progress.scrollTop));
697
+ });
698
+ elements.summary.addEventListener("scroll", function () {
699
+ api.scrollPane("summary", Math.round(elements.summary.scrollTop));
700
+ });
701
+ elements.log.addEventListener("scroll", function () {
702
+ api.scrollPane("log", Math.round(elements.log.scrollTop));
703
+ });
704
+ elements.helpText.addEventListener("scroll", function () {
705
+ api.scrollPane("help", Math.round(elements.helpText.scrollTop));
706
+ });
707
+
708
+ api.connect();
709
+ }());