@jterrats/open-orchestra 0.4.2-beta.1 → 0.5.0-beta.0

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 (170) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +29 -5
  3. package/dist/advisory-artifacts.d.ts +20 -0
  4. package/dist/advisory-artifacts.js +136 -0
  5. package/dist/advisory-artifacts.js.map +1 -0
  6. package/dist/assets/web-console.js +436 -4
  7. package/dist/cli.js +16 -117
  8. package/dist/cli.js.map +1 -1
  9. package/dist/command-manifest.d.ts +6 -0
  10. package/dist/command-manifest.js +141 -43
  11. package/dist/command-manifest.js.map +1 -1
  12. package/dist/command-utils.d.ts +5 -0
  13. package/dist/command-utils.js +23 -0
  14. package/dist/command-utils.js.map +1 -1
  15. package/dist/commands.d.ts +7 -42
  16. package/dist/commands.js +214 -1356
  17. package/dist/commands.js.map +1 -1
  18. package/dist/constants.js +3 -0
  19. package/dist/constants.js.map +1 -1
  20. package/dist/context-budget.d.ts +4 -0
  21. package/dist/context-budget.js +119 -0
  22. package/dist/context-budget.js.map +1 -0
  23. package/dist/delivery-commands.d.ts +10 -0
  24. package/dist/delivery-commands.js +152 -0
  25. package/dist/delivery-commands.js.map +1 -0
  26. package/dist/github.d.ts +50 -1
  27. package/dist/github.js +234 -0
  28. package/dist/github.js.map +1 -1
  29. package/dist/health-checks.d.ts +1 -0
  30. package/dist/health-checks.js +11 -1
  31. package/dist/health-checks.js.map +1 -1
  32. package/dist/health-commands.js +2 -0
  33. package/dist/health-commands.js.map +1 -1
  34. package/dist/mcp-oauth-proxy.d.ts +32 -0
  35. package/dist/mcp-oauth-proxy.js +120 -0
  36. package/dist/mcp-oauth-proxy.js.map +1 -1
  37. package/dist/memory.d.ts +2 -1
  38. package/dist/memory.js +71 -10
  39. package/dist/memory.js.map +1 -1
  40. package/dist/package-update-check.d.ts +5 -1
  41. package/dist/package-update-check.js +20 -8
  42. package/dist/package-update-check.js.map +1 -1
  43. package/dist/planning-commands.d.ts +14 -0
  44. package/dist/planning-commands.js +372 -0
  45. package/dist/planning-commands.js.map +1 -0
  46. package/dist/release-candidate.d.ts +2 -0
  47. package/dist/release-candidate.js +9 -14
  48. package/dist/release-candidate.js.map +1 -1
  49. package/dist/release-commands.d.ts +2 -0
  50. package/dist/release-commands.js +58 -6
  51. package/dist/release-commands.js.map +1 -1
  52. package/dist/release-readiness.d.ts +49 -0
  53. package/dist/release-readiness.js +172 -0
  54. package/dist/release-readiness.js.map +1 -0
  55. package/dist/runtime-commands.js +11 -4
  56. package/dist/runtime-commands.js.map +1 -1
  57. package/dist/runtime-execution-renderer.js +2 -0
  58. package/dist/runtime-execution-renderer.js.map +1 -1
  59. package/dist/runtime-execution.d.ts +4 -2
  60. package/dist/runtime-execution.js +11 -4
  61. package/dist/runtime-execution.js.map +1 -1
  62. package/dist/setup-agents-import.d.ts +42 -0
  63. package/dist/setup-agents-import.js +335 -0
  64. package/dist/setup-agents-import.js.map +1 -0
  65. package/dist/skills-catalog-service.d.ts +2 -0
  66. package/dist/skills-catalog-service.js +8 -0
  67. package/dist/skills-catalog-service.js.map +1 -0
  68. package/dist/skills-catalog.d.ts +2 -0
  69. package/dist/skills-catalog.js +389 -0
  70. package/dist/skills-catalog.js.map +1 -0
  71. package/dist/skills-commands.js +1 -11
  72. package/dist/skills-commands.js.map +1 -1
  73. package/dist/skills-events.d.ts +9 -0
  74. package/dist/skills-events.js +50 -0
  75. package/dist/skills-events.js.map +1 -0
  76. package/dist/skills-memory.d.ts +18 -0
  77. package/dist/skills-memory.js +127 -0
  78. package/dist/skills-memory.js.map +1 -0
  79. package/dist/skills-planning.d.ts +2 -0
  80. package/dist/skills-planning.js +87 -0
  81. package/dist/skills-planning.js.map +1 -0
  82. package/dist/skills-render.d.ts +14 -0
  83. package/dist/skills-render.js +83 -0
  84. package/dist/skills-render.js.map +1 -0
  85. package/dist/skills-validation.d.ts +2 -0
  86. package/dist/skills-validation.js +49 -0
  87. package/dist/skills-validation.js.map +1 -0
  88. package/dist/skills.d.ts +6 -42
  89. package/dist/skills.js +6 -773
  90. package/dist/skills.js.map +1 -1
  91. package/dist/task-graph-commands.d.ts +14 -0
  92. package/dist/task-graph-commands.js +367 -0
  93. package/dist/task-graph-commands.js.map +1 -0
  94. package/dist/tool-commands.js +8 -0
  95. package/dist/tool-commands.js.map +1 -1
  96. package/dist/types/context.d.ts +12 -0
  97. package/dist/types/context.js +2 -0
  98. package/dist/types/context.js.map +1 -0
  99. package/dist/types/metrics.d.ts +114 -0
  100. package/dist/types/metrics.js +2 -0
  101. package/dist/types/metrics.js.map +1 -0
  102. package/dist/types/model-config.d.ts +212 -0
  103. package/dist/types/model-config.js +2 -0
  104. package/dist/types/model-config.js.map +1 -0
  105. package/dist/types/runtime.d.ts +93 -0
  106. package/dist/types/runtime.js +2 -0
  107. package/dist/types/runtime.js.map +1 -0
  108. package/dist/types/skills.d.ts +147 -0
  109. package/dist/types/skills.js +2 -0
  110. package/dist/types/skills.js.map +1 -0
  111. package/dist/types/tasks.d.ts +171 -0
  112. package/dist/types/tasks.js +2 -0
  113. package/dist/types/tasks.js.map +1 -0
  114. package/dist/types/workflow-run.d.ts +79 -0
  115. package/dist/types/workflow-run.js +2 -0
  116. package/dist/types/workflow-run.js.map +1 -0
  117. package/dist/types.d.ts +13 -798
  118. package/dist/types.js +1 -1
  119. package/dist/types.js.map +1 -1
  120. package/dist/upgrade-commands.d.ts +2 -0
  121. package/dist/upgrade-commands.js +65 -0
  122. package/dist/upgrade-commands.js.map +1 -0
  123. package/dist/web-api-read-routes.d.ts +5 -0
  124. package/dist/web-api-read-routes.js +37 -0
  125. package/dist/web-api-read-routes.js.map +1 -0
  126. package/dist/web-api.d.ts +1 -3
  127. package/dist/web-api.js +145 -44
  128. package/dist/web-api.js.map +1 -1
  129. package/dist/web-console-sections.d.ts +2 -0
  130. package/dist/web-console-sections.js +7 -0
  131. package/dist/web-console-sections.js.map +1 -0
  132. package/dist/web-console.js +23 -3
  133. package/dist/web-console.js.map +1 -1
  134. package/dist/workflow-approval-service.d.ts +9 -0
  135. package/dist/workflow-approval-service.js +126 -0
  136. package/dist/workflow-approval-service.js.map +1 -0
  137. package/dist/workflow-approval-utils.d.ts +10 -0
  138. package/dist/workflow-approval-utils.js +82 -0
  139. package/dist/workflow-approval-utils.js.map +1 -0
  140. package/dist/workflow-budget-utils.d.ts +7 -0
  141. package/dist/workflow-budget-utils.js +96 -0
  142. package/dist/workflow-budget-utils.js.map +1 -0
  143. package/dist/workflow-evidence-service.d.ts +7 -0
  144. package/dist/workflow-evidence-service.js +100 -0
  145. package/dist/workflow-evidence-service.js.map +1 -0
  146. package/dist/workflow-run-commands.d.ts +8 -0
  147. package/dist/workflow-run-commands.js +479 -0
  148. package/dist/workflow-run-commands.js.map +1 -0
  149. package/dist/workflow-services.d.ts +8 -18
  150. package/dist/workflow-services.js +30 -481
  151. package/dist/workflow-services.js.map +1 -1
  152. package/dist/workflow-summary-service.d.ts +4 -0
  153. package/dist/workflow-summary-service.js +82 -0
  154. package/dist/workflow-summary-service.js.map +1 -0
  155. package/dist/workflow-templates.d.ts +1 -0
  156. package/dist/workflow-templates.js +1 -0
  157. package/dist/workflow-templates.js.map +1 -1
  158. package/dist/workspace.d.ts +18 -1
  159. package/dist/workspace.js +72 -4
  160. package/dist/workspace.js.map +1 -1
  161. package/docs/command-contracts.md +22 -0
  162. package/docs/mcp-oauth-proxy-evaluation.md +14 -0
  163. package/docs/orchestra-mvp.md +158 -114
  164. package/docs/package-naming.md +20 -0
  165. package/docs/persona-workflows.md +209 -0
  166. package/docs/runtime-adapters.md +19 -14
  167. package/docs/runtime-llm-flow.md +29 -28
  168. package/docs/setup-agents-bridge.md +61 -0
  169. package/docs/traceability-flow.md +89 -0
  170. package/package.json +9 -7
@@ -25,6 +25,7 @@ var endpoints = [
25
25
  ["usage", "/api/usage"],
26
26
  ["budget", "/api/budget"],
27
27
  ["tasks", "/api/tasks"],
28
+ ["workflowRuns", "/api/workflow/runs"],
28
29
  ["evidenceView", "/api/evidence/view"],
29
30
  ["roleActivation", "/api/roles/activation"],
30
31
  ["skills", "/api/skills"],
@@ -35,11 +36,15 @@ var endpoints = [
35
36
  ];
36
37
  var statePill = document.querySelector("#load-state");
37
38
  var lastUpdated = document.querySelector("#last-updated");
39
+ var adoptionGuide = document.querySelector("#adoption-guide");
40
+ var releaseReadiness = document.querySelector("#release-readiness");
38
41
  var metrics = document.querySelector("#metrics");
39
42
  var validation = document.querySelector("#validation");
40
43
  var workspaceView = document.querySelector("#workspace-view");
41
44
  var graph = document.querySelector("#graph");
42
45
  var actions = document.querySelector("#actions");
46
+ var taskDetailSelect = document.querySelector("#task-detail-select");
47
+ var taskDetail = document.querySelector("#task-detail");
43
48
  var taskOwner = document.querySelector("#task-owner");
44
49
  var charts = document.querySelector("#charts");
45
50
  var evidenceTask = document.querySelector("#evidence-task");
@@ -47,6 +52,8 @@ var evidenceType = document.querySelector("#evidence-type");
47
52
  var evidenceList = document.querySelector("#evidence-list");
48
53
  var rolesView = document.querySelector("#roles-view");
49
54
  var planningView = document.querySelector("#planning-view");
55
+ var workflowTask = document.querySelector("#workflow-task");
56
+ var workflowRuns = document.querySelector("#workflow-runs");
50
57
  var costView = document.querySelector("#cost-view");
51
58
  var playwrightTask = document.querySelector("#playwright-task");
52
59
  var playwrightPlan = document.querySelector("#playwright-plan");
@@ -79,6 +86,10 @@ document.querySelector("#skills-render").addEventListener("click", loadSkillRend
79
86
  document.querySelector("#lesson-add").addEventListener("click", addLesson);
80
87
  document.querySelector("#lesson-promote").addEventListener("click", promoteLessons);
81
88
  document.querySelector("#task-add").addEventListener("click", addTaskFromWeb);
89
+ document.querySelector("#task-detail-refresh").addEventListener("click", loadTaskDetail);
90
+ taskDetailSelect.addEventListener("change", loadTaskDetail);
91
+ document.querySelector("#workflow-start").addEventListener("click", startWorkflowFromWeb);
92
+ workflowRuns.addEventListener("click", handleWorkflowAction);
82
93
  loadDashboard();
83
94
  function applyStoredTheme() {
84
95
  const stored = localStorage.getItem("open-orchestra-theme");
@@ -129,15 +140,20 @@ function render(data) {
129
140
  metric(data.validation.valid ? "Valid" : "Needs work", "Workspace")
130
141
  ]);
131
142
  renderValidation(data.validation);
143
+ renderAdoptionGuide(data);
144
+ renderReleaseReadiness(data);
132
145
  renderWorkspace(data.workspaceClassification, data.runtimeAdapters);
133
146
  renderGraph(data.graph);
134
147
  renderActions(data);
148
+ renderTaskDetailControls(data.tasks);
135
149
  renderTaskWriteControls(data.roles);
136
150
  renderCharts(data);
137
151
  renderEvidenceControls(data);
138
152
  renderEvidence(data.evidenceView);
139
153
  renderRoles(data.roleActivation);
140
154
  renderPlanning(data.roleActivation);
155
+ renderWorkflowControls(data.tasks, data.workflowRuns);
156
+ renderWorkflowRuns(data.workflowRuns);
141
157
  renderCost(data.usage, data.budget);
142
158
  renderPlaywrightTasks(data.tasks);
143
159
  renderSkillControls(data);
@@ -146,6 +162,149 @@ function render(data) {
146
162
  renderLessons(data.lessons);
147
163
  loadPlaywrightPlan();
148
164
  loadSkillPlan();
165
+ loadTaskDetail();
166
+ }
167
+ function renderTaskDetailControls(tasks) {
168
+ const currentValue = taskDetailSelect.value;
169
+ replace(
170
+ taskDetailSelect,
171
+ tasks.length > 0 ? tasks.map(function(task) {
172
+ return option(task.id, task.id + " - " + task.title);
173
+ }) : [option("", "No tasks")]
174
+ );
175
+ if (currentValue) {
176
+ taskDetailSelect.value = currentValue;
177
+ }
178
+ }
179
+ async function loadTaskDetail() {
180
+ if (!taskDetailSelect.value) {
181
+ replace(taskDetail, [
182
+ empty(
183
+ "No task selected",
184
+ "Create or select a task to inspect acceptance, evidence, and gates."
185
+ )
186
+ ]);
187
+ return;
188
+ }
189
+ try {
190
+ const response = await fetch(
191
+ "/api/task/detail?task=" + encodeURIComponent(taskDetailSelect.value)
192
+ );
193
+ if (!response.ok) {
194
+ throw new Error("task detail returned " + response.status);
195
+ }
196
+ renderTaskDetail(await response.json());
197
+ } catch (error) {
198
+ replace(taskDetail, [
199
+ empty("Task detail unavailable", String(error.message || error))
200
+ ]);
201
+ }
202
+ }
203
+ function renderTaskDetail(detail) {
204
+ const context = detail.context;
205
+ const task = context.task;
206
+ const releaseReadiness2 = detail.releaseReadiness;
207
+ const rows = document.createElement("div");
208
+ rows.className = "detail-grid";
209
+ rows.append(
210
+ detailBlock("Summary", [
211
+ task.id + " [" + task.status + "] " + task.title,
212
+ "Owner: " + task.ownerRole,
213
+ "Backlog: " + (task.backlogItem || "missing")
214
+ ]),
215
+ detailBlock("Acceptance Criteria", task.acceptanceCriteria || []),
216
+ detailBlock("Definition of Ready", readinessLines(context)),
217
+ detailBlock("Definition of Done", releaseReadinessLines(releaseReadiness2)),
218
+ detailBlock(
219
+ "AC Coverage",
220
+ releaseCoverageLines(detail.releaseReadinessReport)
221
+ ),
222
+ detailBlock("Risks", task.risks || []),
223
+ detailBlock("Handoffs", eventLines(context.handoffs)),
224
+ detailBlock("Reviews", eventLines(context.reviews)),
225
+ detailBlock("Evidence", eventLines(context.evidence)),
226
+ detailBlock("Gates", eventLines(context.gates)),
227
+ detailBlock("Next Actions", taskNextActions(context, releaseReadiness2))
228
+ );
229
+ replace(taskDetail, [rows]);
230
+ }
231
+ function releaseCoverageLines(report) {
232
+ if (!report || !Array.isArray(report.acceptanceCoverage)) {
233
+ return ["Coverage unavailable"];
234
+ }
235
+ if (report.acceptanceCoverage.length === 0) {
236
+ return ["No acceptance criteria"];
237
+ }
238
+ return report.acceptanceCoverage.map(function(coverage) {
239
+ const status = coverage.covered ? "covered" : "missing";
240
+ const suffix = coverage.missing && coverage.missing.length > 0 ? " (" + coverage.missing.join(", ") + ")" : "";
241
+ return coverage.criterion + ": " + status + suffix;
242
+ });
243
+ }
244
+ function readinessLines(context) {
245
+ const missing = [];
246
+ [
247
+ "backlogItem",
248
+ "goal",
249
+ "scope",
250
+ "acceptanceCriteria",
251
+ "assumptions",
252
+ "risks",
253
+ "testStrategy"
254
+ ].forEach(function(field) {
255
+ const value = context.task[field];
256
+ if (Array.isArray(value) ? value.length === 0 : !value) {
257
+ missing.push(field);
258
+ }
259
+ });
260
+ return missing.length === 0 ? ["Ready for implementation"] : missing.map(function(field) {
261
+ return "Missing " + field;
262
+ });
263
+ }
264
+ function releaseReadinessLines(releaseReadiness2) {
265
+ if (releaseReadiness2.passed) {
266
+ return ["Release readiness passed"];
267
+ }
268
+ return releaseReadiness2.missing.map(function(field) {
269
+ return "Missing " + field;
270
+ });
271
+ }
272
+ function taskNextActions(context, releaseReadiness2) {
273
+ const actions2 = [];
274
+ if ((context.task.acceptanceCriteria || []).length === 0) {
275
+ actions2.push("Add acceptance criteria before implementation.");
276
+ }
277
+ if (context.handoffs.length === 0) {
278
+ actions2.push("Create the next role handoff when work changes owner.");
279
+ }
280
+ if (context.evidence.length === 0) {
281
+ actions2.push(
282
+ "Attach command, report, screenshot, trace, or video evidence."
283
+ );
284
+ }
285
+ if (!releaseReadiness2.passed) {
286
+ actions2.push("Resolve release-readiness missing fields.");
287
+ }
288
+ return actions2.length > 0 ? actions2 : ["No task-specific action required."];
289
+ }
290
+ function eventLines(events) {
291
+ return events.length > 0 ? events.map(function(event) {
292
+ return event.actor + ": " + event.summary;
293
+ }) : ["none"];
294
+ }
295
+ function detailBlock(title, lines) {
296
+ const block = document.createElement("section");
297
+ block.className = "detail-block";
298
+ const heading = document.createElement("h3");
299
+ heading.textContent = title;
300
+ const list = document.createElement("ul");
301
+ (lines.length > 0 ? lines : ["none"]).forEach(function(line) {
302
+ const item = document.createElement("li");
303
+ item.textContent = line;
304
+ list.append(item);
305
+ });
306
+ block.append(heading, list);
307
+ return block;
149
308
  }
150
309
  function renderTaskWriteControls(roles) {
151
310
  replace(
@@ -187,6 +346,135 @@ async function addTaskFromWeb() {
187
346
  });
188
347
  await loadDashboard();
189
348
  }
349
+ function renderAdoptionGuide(data) {
350
+ const next = determinePrimaryNextAction(data);
351
+ const container = document.createElement("div");
352
+ const lead = document.createElement("p");
353
+ lead.textContent = next.summary;
354
+ const commands = document.createElement("div");
355
+ commands.className = "command-list";
356
+ next.commands.forEach(function(command) {
357
+ const chip = document.createElement("code");
358
+ chip.className = "command-chip";
359
+ chip.textContent = command;
360
+ commands.append(chip);
361
+ });
362
+ const steps = document.createElement("ol");
363
+ steps.className = "step-list";
364
+ next.steps.forEach(function(step, index) {
365
+ const item = document.createElement("li");
366
+ const number = document.createElement("b");
367
+ number.textContent = String(index + 1);
368
+ const text = document.createElement("span");
369
+ text.textContent = step;
370
+ item.append(number, text);
371
+ steps.append(item);
372
+ });
373
+ container.append(lead, commands, steps);
374
+ replace(adoptionGuide, [container]);
375
+ }
376
+ function determinePrimaryNextAction(data) {
377
+ if (!data.validation.valid) {
378
+ return {
379
+ summary: "Resolve workspace validation before starting or releasing work.",
380
+ commands: ["orchestra validate", "orchestra health --json"],
381
+ steps: [
382
+ "Inspect validation errors and warnings.",
383
+ "Fix missing workflow files, invalid task references, or stale locks.",
384
+ "Refresh the console after validation passes."
385
+ ]
386
+ };
387
+ }
388
+ if (data.status.tasks.total === 0) {
389
+ return {
390
+ summary: "Create the first governed task with an owner and acceptance criteria.",
391
+ commands: [
392
+ 'orchestra task add --id STORY-001 --title "First story" --owner developer --acceptance "verified outcome"',
393
+ "orchestra context --task STORY-001 --json"
394
+ ],
395
+ steps: [
396
+ "Add a task from the console or CLI.",
397
+ "Confirm the owner role and acceptance criteria.",
398
+ "Render context before implementation starts."
399
+ ]
400
+ };
401
+ }
402
+ if (data.graph.ready.length > 0) {
403
+ const task = data.graph.ready[0];
404
+ return {
405
+ summary: task.id + " is ready. Start with context, memory, and planning.",
406
+ commands: [
407
+ "orchestra context --task " + task.id + " --json",
408
+ "orchestra memory hook --point before_plan --task " + task.id + " --json",
409
+ "orchestra workflow run --task " + task.id + " --dry-run --gates phase"
410
+ ],
411
+ steps: [
412
+ "Review task context and relevant memory.",
413
+ "Confirm role plan and workflow gates.",
414
+ "Start the workflow when the plan matches the work."
415
+ ]
416
+ };
417
+ }
418
+ if (data.approvals.length > 0) {
419
+ return {
420
+ summary: "There are approval artifacts waiting for a release or workflow decision.",
421
+ commands: ["orchestra approvals list --json", "orchestra summary --json"],
422
+ steps: [
423
+ "Review each pending approval artifact.",
424
+ "Approve, reject, or capture accepted risk.",
425
+ "Resume the paused workflow after approval."
426
+ ]
427
+ };
428
+ }
429
+ return {
430
+ summary: "The workspace has no immediate blocking action.",
431
+ commands: ["orchestra summary --json", "orchestra release check --json"],
432
+ steps: [
433
+ "Review summary for stale evidence or locks.",
434
+ "Run release check before tagging.",
435
+ "Record smoke and rollback evidence when preparing a release."
436
+ ]
437
+ };
438
+ }
439
+ function renderReleaseReadiness(data) {
440
+ const evidenceCount = data.evidence.length;
441
+ const approvalCount = data.approvals.length;
442
+ const rows = [
443
+ readinessRow(
444
+ data.validation.valid,
445
+ "Workspace validation",
446
+ data.validation.valid ? "Valid" : "Needs fixes"
447
+ ),
448
+ readinessRow(
449
+ data.graph.blocked.length === 0 && data.graph.locked.length === 0,
450
+ "Task flow",
451
+ data.graph.blocked.length + " blocked, " + data.graph.locked.length + " locked"
452
+ ),
453
+ readinessRow(evidenceCount > 0, "Evidence", evidenceCount + " artifact(s)"),
454
+ readinessRow(
455
+ approvalCount === 0,
456
+ "Approvals",
457
+ approvalCount + " pending artifact(s)"
458
+ ),
459
+ readinessRow(
460
+ data.budget.passed,
461
+ "Budget",
462
+ data.budget.passed ? "Within configured limits" : data.budget.violations.length + " violation(s)"
463
+ )
464
+ ];
465
+ replace(releaseReadiness, rows);
466
+ }
467
+ function readinessRow(ok, title, detail) {
468
+ const node = listItem(title, detail);
469
+ const tag = document.createElement("span");
470
+ tag.className = ok ? "tag ok" : "tag warn";
471
+ tag.textContent = ok ? "ready" : "attention";
472
+ const row = document.createElement("div");
473
+ row.className = "tag-row";
474
+ row.append(tag);
475
+ node.append(row);
476
+ return node;
477
+ }
190
478
  function renderWorkspace(classification, adapters) {
191
479
  const adapterNames = adapters.map(function(adapter) {
192
480
  return adapter.label;
@@ -273,6 +561,138 @@ function renderChart(current, canvasId, type, slices) {
273
561
  }
274
562
  });
275
563
  }
564
+ function renderWorkflowControls(tasks, runs) {
565
+ const currentValue = workflowTask.value;
566
+ replace(
567
+ workflowTask,
568
+ tasks.length > 0 ? tasks.map(function(task) {
569
+ return option(task.id, task.id);
570
+ }) : [option("", "No tasks")]
571
+ );
572
+ if (currentValue) {
573
+ workflowTask.value = currentValue;
574
+ }
575
+ const runningTaskIds = new Set(
576
+ runs.filter(function(run) {
577
+ return run.status === "running" || run.status === "paused";
578
+ }).map(function(run) {
579
+ return run.taskId;
580
+ })
581
+ );
582
+ document.querySelector("#workflow-start").disabled = !workflowTask.value || runningTaskIds.has(workflowTask.value);
583
+ }
584
+ function renderWorkflowRuns(runs) {
585
+ replace(
586
+ workflowRuns,
587
+ runs.length > 0 ? runs.slice(0, 8).map(workflowRunItem) : [
588
+ empty(
589
+ "No workflow runs yet",
590
+ "Start a task workflow to track phases, gates, and handoffs."
591
+ )
592
+ ]
593
+ );
594
+ }
595
+ function workflowRunItem(run) {
596
+ const phases = (run.phases || []).map(function(phase) {
597
+ return phase.phase + ":" + phase.status;
598
+ }).join(" -> ");
599
+ const node = listItem(
600
+ run.id + " [" + run.status + "]",
601
+ run.taskId + " - gates " + run.gates + (phases ? " - " + phases : "")
602
+ );
603
+ const pausedGate = [...run.phases || []].reverse().find(function(phase) {
604
+ return phase.status === "gate_paused";
605
+ });
606
+ const row = document.createElement("div");
607
+ row.className = "tag-row";
608
+ if (pausedGate) {
609
+ const approve = document.createElement("button");
610
+ approve.className = "text-button";
611
+ approve.type = "button";
612
+ approve.dataset.workflowAction = "approve";
613
+ approve.dataset.run = run.id;
614
+ approve.dataset.task = run.taskId;
615
+ approve.dataset.gate = pausedGate.gateId || "";
616
+ approve.textContent = "Approve Gate";
617
+ row.append(approve);
618
+ }
619
+ if (run.status === "paused") {
620
+ const resume = document.createElement("button");
621
+ resume.className = "text-button";
622
+ resume.type = "button";
623
+ resume.dataset.workflowAction = "resume";
624
+ resume.dataset.run = run.id;
625
+ resume.dataset.task = run.taskId;
626
+ resume.textContent = "Resume";
627
+ row.append(resume);
628
+ }
629
+ node.append(row);
630
+ return node;
631
+ }
632
+ async function startWorkflowFromWeb() {
633
+ if (!workflowTask.value) {
634
+ setLoading("Select a task to start workflow", "warn");
635
+ return;
636
+ }
637
+ const response = await fetch("/api/workflow/start", {
638
+ method: "POST",
639
+ headers: { "content-type": "application/json" },
640
+ body: JSON.stringify({
641
+ task: workflowTask.value,
642
+ gates: document.querySelector("#workflow-gates").value
643
+ })
644
+ });
645
+ if (!response.ok) {
646
+ setLoading("Workflow start failed", "warn");
647
+ return;
648
+ }
649
+ await loadDashboard();
650
+ }
651
+ async function handleWorkflowAction(event) {
652
+ const button = event.target.closest("[data-workflow-action]");
653
+ if (!button) {
654
+ return;
655
+ }
656
+ if (button.dataset.workflowAction === "approve") {
657
+ await approveWorkflowGateFromWeb(button);
658
+ return;
659
+ }
660
+ if (button.dataset.workflowAction === "resume") {
661
+ await resumeWorkflowFromWeb(button);
662
+ }
663
+ }
664
+ async function approveWorkflowGateFromWeb(button) {
665
+ const response = await fetch("/api/workflow/gate-approve", {
666
+ method: "POST",
667
+ headers: { "content-type": "application/json" },
668
+ body: JSON.stringify({
669
+ run: button.dataset.run,
670
+ gate: button.dataset.gate,
671
+ approver: document.querySelector("#workflow-approver").value,
672
+ rationale: document.querySelector("#workflow-rationale").value
673
+ })
674
+ });
675
+ if (!response.ok) {
676
+ setLoading("Gate approval failed", "warn");
677
+ return;
678
+ }
679
+ await loadDashboard();
680
+ }
681
+ async function resumeWorkflowFromWeb(button) {
682
+ const response = await fetch("/api/workflow/resume", {
683
+ method: "POST",
684
+ headers: { "content-type": "application/json" },
685
+ body: JSON.stringify({
686
+ task: button.dataset.task,
687
+ run: button.dataset.run
688
+ })
689
+ });
690
+ if (!response.ok) {
691
+ setLoading("Workflow resume failed", "warn");
692
+ return;
693
+ }
694
+ await loadDashboard();
695
+ }
276
696
  function renderCost(usage, budget) {
277
697
  const rows = [
278
698
  listItem("Requests", String(usage.totals.requests)),
@@ -723,10 +1143,7 @@ function renderGraph(plan) {
723
1143
  ["Complete", plan.complete]
724
1144
  ].flatMap(function(group) {
725
1145
  return group[1].slice(0, 4).map(function(item) {
726
- return listItem(
727
- item.title,
728
- group[0] + " - " + item.id + " - " + item.ownerRole
729
- );
1146
+ return listItem(item.title, graphItemDetail(group[0], item));
730
1147
  });
731
1148
  });
732
1149
  replace(
@@ -739,6 +1156,20 @@ function renderGraph(plan) {
739
1156
  ]
740
1157
  );
741
1158
  }
1159
+ function graphItemDetail(group, item) {
1160
+ const base = group + " - " + item.id + " - " + item.ownerRole;
1161
+ if (group === "Blocked" && item.incomplete?.length > 0) {
1162
+ return base + " - blockers: " + item.incomplete.map(function(dependency) {
1163
+ return dependency.id + " [" + dependency.status + "]";
1164
+ }).join(", ");
1165
+ }
1166
+ if (group === "Locked" && item.locks?.length > 0) {
1167
+ return base + " - locks: " + item.locks.map(function(lock) {
1168
+ return lock.path + " (" + lock.reason + ")";
1169
+ }).join(", ");
1170
+ }
1171
+ return base;
1172
+ }
742
1173
  function renderActions(data) {
743
1174
  const next = [];
744
1175
  if (!data.validation.valid) {
@@ -826,6 +1257,7 @@ function renderEmpty(title, detail) {
826
1257
  replace(workspaceView, []);
827
1258
  replace(graph, []);
828
1259
  replace(actions, []);
1260
+ replace(taskDetail, []);
829
1261
  }
830
1262
  function setLoading(label, tone) {
831
1263
  statePill.textContent = label;