@jterrats/open-orchestra 0.5.7 → 1.0.2

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 (260) hide show
  1. package/AGENTS.md +9 -8
  2. package/CLAUDE.md +13 -11
  3. package/README.md +78 -11
  4. package/dist/assets/web-console.js +169 -32
  5. package/dist/automation-evidence.d.ts +23 -0
  6. package/dist/automation-evidence.js +97 -0
  7. package/dist/automation-evidence.js.map +1 -0
  8. package/dist/autonomous-run-store.js +3 -3
  9. package/dist/autonomous-run-store.js.map +1 -1
  10. package/dist/benchmark.d.ts +4 -1
  11. package/dist/benchmark.js +93 -4
  12. package/dist/benchmark.js.map +1 -1
  13. package/dist/cli.js +73 -2
  14. package/dist/cli.js.map +1 -1
  15. package/dist/collaboration-flows.js +3 -5
  16. package/dist/collaboration-flows.js.map +1 -1
  17. package/dist/collection-utils.d.ts +3 -0
  18. package/dist/collection-utils.js +10 -0
  19. package/dist/collection-utils.js.map +1 -0
  20. package/dist/command-manifest.d.ts +12 -1
  21. package/dist/command-manifest.js +213 -10
  22. package/dist/command-manifest.js.map +1 -1
  23. package/dist/commands.d.ts +10 -5
  24. package/dist/commands.js +16 -6
  25. package/dist/commands.js.map +1 -1
  26. package/dist/config-migrations.d.ts +24 -0
  27. package/dist/config-migrations.js +102 -0
  28. package/dist/config-migrations.js.map +1 -0
  29. package/dist/constants.d.ts +2 -0
  30. package/dist/constants.js +23 -0
  31. package/dist/constants.js.map +1 -1
  32. package/dist/dashboard-commands.d.ts +2 -0
  33. package/dist/dashboard-commands.js +14 -0
  34. package/dist/dashboard-commands.js.map +1 -0
  35. package/dist/defaults.d.ts +13 -0
  36. package/dist/defaults.js +13 -0
  37. package/dist/defaults.js.map +1 -1
  38. package/dist/delegation-decision.js +23 -8
  39. package/dist/delegation-decision.js.map +1 -1
  40. package/dist/delivery-commands.js +5 -0
  41. package/dist/delivery-commands.js.map +1 -1
  42. package/dist/delivery-dashboard-charts.d.ts +4 -0
  43. package/dist/delivery-dashboard-charts.js +156 -0
  44. package/dist/delivery-dashboard-charts.js.map +1 -0
  45. package/dist/delivery-dashboard-html.d.ts +2 -0
  46. package/dist/delivery-dashboard-html.js +115 -0
  47. package/dist/delivery-dashboard-html.js.map +1 -0
  48. package/dist/delivery-dashboard-types.d.ts +78 -0
  49. package/dist/delivery-dashboard-types.js +2 -0
  50. package/dist/delivery-dashboard-types.js.map +1 -0
  51. package/dist/delivery-dashboard.d.ts +8 -0
  52. package/dist/delivery-dashboard.js +124 -0
  53. package/dist/delivery-dashboard.js.map +1 -0
  54. package/dist/effort-classification.d.ts +7 -0
  55. package/dist/effort-classification.js +72 -0
  56. package/dist/effort-classification.js.map +1 -0
  57. package/dist/extension-commands.d.ts +3 -0
  58. package/dist/extension-commands.js +40 -0
  59. package/dist/extension-commands.js.map +1 -0
  60. package/dist/extensions.d.ts +22 -0
  61. package/dist/extensions.js +126 -0
  62. package/dist/extensions.js.map +1 -0
  63. package/dist/github.d.ts +2 -0
  64. package/dist/github.js +15 -3
  65. package/dist/github.js.map +1 -1
  66. package/dist/health-checks.js +51 -0
  67. package/dist/health-checks.js.map +1 -1
  68. package/dist/lucid-story-map.d.ts +73 -0
  69. package/dist/lucid-story-map.js +112 -0
  70. package/dist/lucid-story-map.js.map +1 -0
  71. package/dist/mcp-integrations.d.ts +19 -0
  72. package/dist/mcp-integrations.js +58 -0
  73. package/dist/mcp-integrations.js.map +1 -0
  74. package/dist/mcp-tool-adapter.d.ts +21 -0
  75. package/dist/mcp-tool-adapter.js +56 -0
  76. package/dist/mcp-tool-adapter.js.map +1 -0
  77. package/dist/memory.js +18 -8
  78. package/dist/memory.js.map +1 -1
  79. package/dist/metrics-commands.js +47 -13
  80. package/dist/metrics-commands.js.map +1 -1
  81. package/dist/model-commands.d.ts +5 -0
  82. package/dist/model-commands.js +101 -3
  83. package/dist/model-commands.js.map +1 -1
  84. package/dist/model-providers.js +13 -1
  85. package/dist/model-providers.js.map +1 -1
  86. package/dist/package-update-check.d.ts +18 -0
  87. package/dist/package-update-check.js +20 -0
  88. package/dist/package-update-check.js.map +1 -1
  89. package/dist/phase-executor.d.ts +1 -0
  90. package/dist/phase-executor.js +118 -14
  91. package/dist/phase-executor.js.map +1 -1
  92. package/dist/phase-playbooks.d.ts +15 -0
  93. package/dist/phase-playbooks.js +82 -0
  94. package/dist/phase-playbooks.js.map +1 -1
  95. package/dist/planning-commands.d.ts +1 -0
  96. package/dist/planning-commands.js +24 -1
  97. package/dist/planning-commands.js.map +1 -1
  98. package/dist/project-detection.js +9 -7
  99. package/dist/project-detection.js.map +1 -1
  100. package/dist/prompt-registry-update.d.ts +2 -0
  101. package/dist/prompt-registry-update.js +25 -1
  102. package/dist/prompt-registry-update.js.map +1 -1
  103. package/dist/prompt-registry-validation.js +39 -2
  104. package/dist/prompt-registry-validation.js.map +1 -1
  105. package/dist/qa-commands.d.ts +2 -0
  106. package/dist/qa-commands.js +18 -0
  107. package/dist/qa-commands.js.map +1 -0
  108. package/dist/qa-coverage.d.ts +24 -0
  109. package/dist/qa-coverage.js +198 -0
  110. package/dist/qa-coverage.js.map +1 -0
  111. package/dist/qa-readiness.d.ts +5 -0
  112. package/dist/qa-readiness.js +26 -0
  113. package/dist/qa-readiness.js.map +1 -0
  114. package/dist/refresh-generated.d.ts +10 -1
  115. package/dist/refresh-generated.js +83 -6
  116. package/dist/refresh-generated.js.map +1 -1
  117. package/dist/release-candidate.d.ts +9 -1
  118. package/dist/release-candidate.js +52 -1
  119. package/dist/release-candidate.js.map +1 -1
  120. package/dist/release-commands.js +202 -12
  121. package/dist/release-commands.js.map +1 -1
  122. package/dist/release-readiness.d.ts +36 -1
  123. package/dist/release-readiness.js +217 -6
  124. package/dist/release-readiness.js.map +1 -1
  125. package/dist/runtime-bootstrap.js +1 -1
  126. package/dist/runtime-bootstrap.js.map +1 -1
  127. package/dist/runtime-commands.d.ts +2 -0
  128. package/dist/runtime-commands.js +77 -0
  129. package/dist/runtime-commands.js.map +1 -1
  130. package/dist/runtime-execution-renderer.d.ts +3 -2
  131. package/dist/runtime-execution-renderer.js +19 -1
  132. package/dist/runtime-execution-renderer.js.map +1 -1
  133. package/dist/runtime-execution.d.ts +2 -1
  134. package/dist/runtime-execution.js +71 -11
  135. package/dist/runtime-execution.js.map +1 -1
  136. package/dist/runtime-guardrails.d.ts +26 -0
  137. package/dist/runtime-guardrails.js +168 -0
  138. package/dist/runtime-guardrails.js.map +1 -0
  139. package/dist/setup-agents-import.js +5 -3
  140. package/dist/setup-agents-import.js.map +1 -1
  141. package/dist/skills-catalog.js +63 -0
  142. package/dist/skills-catalog.js.map +1 -1
  143. package/dist/skills-commands.d.ts +4 -0
  144. package/dist/skills-commands.js +55 -2
  145. package/dist/skills-commands.js.map +1 -1
  146. package/dist/skills-memory.d.ts +36 -2
  147. package/dist/skills-memory.js +165 -6
  148. package/dist/skills-memory.js.map +1 -1
  149. package/dist/skills-planning.js +2 -4
  150. package/dist/skills-planning.js.map +1 -1
  151. package/dist/skills-render.js +2 -4
  152. package/dist/skills-render.js.map +1 -1
  153. package/dist/skills.d.ts +1 -1
  154. package/dist/skills.js +1 -1
  155. package/dist/skills.js.map +1 -1
  156. package/dist/sprint-commands.js +2 -1
  157. package/dist/sprint-commands.js.map +1 -1
  158. package/dist/subagent-protocol.js +3 -5
  159. package/dist/subagent-protocol.js.map +1 -1
  160. package/dist/support-commands.d.ts +2 -0
  161. package/dist/support-commands.js +18 -0
  162. package/dist/support-commands.js.map +1 -0
  163. package/dist/support-diagnostics.d.ts +49 -0
  164. package/dist/support-diagnostics.js +86 -0
  165. package/dist/support-diagnostics.js.map +1 -0
  166. package/dist/task-graph-commands.js +5 -3
  167. package/dist/task-graph-commands.js.map +1 -1
  168. package/dist/telemetry-redaction.js +8 -1
  169. package/dist/telemetry-redaction.js.map +1 -1
  170. package/dist/tool-commands.d.ts +3 -0
  171. package/dist/tool-commands.js +62 -0
  172. package/dist/tool-commands.js.map +1 -1
  173. package/dist/tracker-adapters.d.ts +71 -0
  174. package/dist/tracker-adapters.js +186 -0
  175. package/dist/tracker-adapters.js.map +1 -0
  176. package/dist/tracker-commands.d.ts +2 -0
  177. package/dist/tracker-commands.js +119 -0
  178. package/dist/tracker-commands.js.map +1 -0
  179. package/dist/types/metrics.d.ts +24 -0
  180. package/dist/types/model-config.d.ts +39 -0
  181. package/dist/types/runtime.d.ts +56 -0
  182. package/dist/types/skills.d.ts +2 -0
  183. package/dist/types/tasks.d.ts +6 -0
  184. package/dist/types/workflow-run.d.ts +17 -0
  185. package/dist/types.d.ts +4 -4
  186. package/dist/types.js.map +1 -1
  187. package/dist/upgrade-commands.js +13 -4
  188. package/dist/upgrade-commands.js.map +1 -1
  189. package/dist/validation.js +2 -2
  190. package/dist/validation.js.map +1 -1
  191. package/dist/visual-validation.d.ts +81 -0
  192. package/dist/visual-validation.js +290 -0
  193. package/dist/visual-validation.js.map +1 -0
  194. package/dist/web-action-security.d.ts +11 -0
  195. package/dist/web-action-security.js +45 -0
  196. package/dist/web-action-security.js.map +1 -0
  197. package/dist/web-api-read-routes.js +101 -1
  198. package/dist/web-api-read-routes.js.map +1 -1
  199. package/dist/web-api.js +507 -5
  200. package/dist/web-api.js.map +1 -1
  201. package/dist/web-artifacts.d.ts +55 -0
  202. package/dist/web-artifacts.js +222 -0
  203. package/dist/web-artifacts.js.map +1 -0
  204. package/dist/web-console/assets/index-BNESIVvk.js +11 -0
  205. package/dist/web-console/assets/index-jxCY5eEc.css +1 -0
  206. package/dist/web-console/index.html +13 -0
  207. package/dist/web-console.js +9 -3
  208. package/dist/web-console.js.map +1 -1
  209. package/dist/web-recovery.d.ts +30 -0
  210. package/dist/web-recovery.js +163 -0
  211. package/dist/web-recovery.js.map +1 -0
  212. package/dist/web-workflow-progress.d.ts +41 -0
  213. package/dist/web-workflow-progress.js +114 -0
  214. package/dist/web-workflow-progress.js.map +1 -0
  215. package/dist/workflow-approval-service.d.ts +2 -1
  216. package/dist/workflow-approval-service.js +72 -0
  217. package/dist/workflow-approval-service.js.map +1 -1
  218. package/dist/workflow-evidence-service.js +8 -1
  219. package/dist/workflow-evidence-service.js.map +1 -1
  220. package/dist/workflow-gates.d.ts +2 -0
  221. package/dist/workflow-gates.js +221 -0
  222. package/dist/workflow-gates.js.map +1 -1
  223. package/dist/workflow-run-commands.js +13 -1
  224. package/dist/workflow-run-commands.js.map +1 -1
  225. package/dist/workflow-services.d.ts +16 -12
  226. package/dist/workflow-services.js +313 -253
  227. package/dist/workflow-services.js.map +1 -1
  228. package/dist/workflow-task-service.d.ts +11 -0
  229. package/dist/workflow-task-service.js +242 -0
  230. package/dist/workflow-task-service.js.map +1 -0
  231. package/dist/workspace-validator.js +109 -3
  232. package/dist/workspace-validator.js.map +1 -1
  233. package/dist/workspace.js +8 -2
  234. package/dist/workspace.js.map +1 -1
  235. package/docs/adoption-guide.md +147 -0
  236. package/docs/autonomous-workflow.md +118 -27
  237. package/docs/benchmark.md +15 -7
  238. package/docs/command-contracts.md +18 -1
  239. package/docs/core-command-surface.md +59 -13
  240. package/docs/end-to-end-demo.md +1 -0
  241. package/docs/extension-contracts.md +83 -0
  242. package/docs/orchestra-mvp.md +83 -3
  243. package/docs/persona-workflows.md +32 -0
  244. package/docs/release-test-matrix.md +42 -0
  245. package/docs/runtime-adapters.md +92 -0
  246. package/docs/runtime-llm-flow.md +13 -0
  247. package/docs/setup-agents-applicability-review.md +173 -0
  248. package/docs/skill-loading-strategy.md +1 -0
  249. package/docs/source-of-truth-and-agent-learning.md +14 -0
  250. package/docs/traceability-flow.md +16 -1
  251. package/docs/tracker-adapter-contract.md +10 -1
  252. package/docs/web-console-qa.md +35 -0
  253. package/package.json +12 -6
  254. package/rules/development-engineering.mdc +68 -0
  255. package/rules/devops-tooling.mdc +1 -0
  256. package/rules/dry-clean-code.mdc +1 -0
  257. package/rules/performance-reliability.mdc +1 -0
  258. package/rules/testing-discipline.mdc +4 -1
  259. package/skills/collection-standards/SKILL.md +63 -0
  260. package/skills/collection-standards/manifest.json +69 -0
package/dist/web-api.js CHANGED
@@ -1,19 +1,24 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { readFile, stat } from "node:fs/promises";
2
2
  import { execFile } from "node:child_process";
3
3
  import http from "node:http";
4
- import { dirname, join } from "node:path";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { promisify } from "node:util";
7
- import { listAutonomousRuns } from "./autonomous-workflow.js";
7
+ import { cancelRun, listAutonomousRuns } from "./autonomous-workflow.js";
8
8
  import { parsePromotionTarget, parseSkillRenderTarget, removeUndefined, } from "./command-utils.js";
9
- import { addEvidence, addTask, approveWorkflowGate, getTaskContext, listTasks, previewWorkflowGate, recordReview, updateTask, } from "./workflow-services.js";
9
+ import { addEvidence, addTask, approveWorkflowGate, getWorkflowConfig, getTaskContext, listTasks, previewWorkflowGate, recordWorkflowGateDecision, recordReview, setRoleModelProvider, updateTask, } from "./workflow-services.js";
10
10
  import { listWebEvidence, readWorkspaceArtifact } from "./web-evidence.js";
11
+ import { attachArtifactEvidence, previewWebArtifact } from "./web-artifacts.js";
11
12
  import { attachWebPlaywrightEvidence, getWebPlaywrightPlan, } from "./web-playwright.js";
13
+ import { authorizeWebAction } from "./web-action-security.js";
12
14
  import { getWebRoleActivation } from "./web-roles.js";
15
+ import { generateQaAutomationCoverage } from "./qa-coverage.js";
16
+ import { repairRecoveryItem } from "./web-recovery.js";
13
17
  import { renderWebConsoleHtml } from "./web-console.js";
14
18
  import { renderSubagentProtocol } from "./subagent-protocol.js";
15
19
  import { recommendCollaborationFlow } from "./collaboration-flows.js";
16
20
  import { renderWorkflowTemplates, selectWorkflowTemplates, } from "./workflow-templates.js";
21
+ import { listWorkflowEvents } from "./web-workflow-progress.js";
17
22
  import { loadPhasePlaybook } from "./phase-playbooks.js";
18
23
  import { decideTaskDelegation } from "./delegation-decision.js";
19
24
  import { addAgentLesson, planSkillsForTask, promoteAgentLessons, renderSkills, } from "./skills.js";
@@ -25,6 +30,8 @@ const DEFAULT_WEB_PORT = 3717;
25
30
  const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
26
31
  const CHART_JS_PATH = join(PACKAGE_ROOT, "node_modules/chart.js/dist/chart.umd.js");
27
32
  const WEB_CONSOLE_BUNDLE_PATH = join(PACKAGE_ROOT, "dist/assets/web-console.js");
33
+ const REACT_WEB_CONSOLE_DIR = join(PACKAGE_ROOT, "dist/web-console");
34
+ const REACT_WEB_CONSOLE_INDEX_PATH = join(REACT_WEB_CONSOLE_DIR, "index.html");
28
35
  const ORCHESTRA_CLI_PATH = join(PACKAGE_ROOT, "bin/orchestra.js");
29
36
  const execFileAsync = promisify(execFile);
30
37
  const routes = createWebReadRoutes();
@@ -52,6 +59,10 @@ const requestRoutes = {
52
59
  status: 200,
53
60
  body: await getWebPlaywrightPlan(root, requiredParam(url, "task")),
54
61
  }),
62
+ "/api/qa/coverage": async ({ root, url }) => ({
63
+ status: 200,
64
+ body: await generateQaAutomationCoverage(requiredParam(url, "task"), root),
65
+ }),
55
66
  "/api/delegation/decide": async ({ root, url }) => ({
56
67
  status: 200,
57
68
  body: await decideTaskDelegation(requiredParam(url, "task"), root, {
@@ -98,6 +109,15 @@ const requestRoutes = {
98
109
  }
99
110
  return { status: 200, body: selectWorkflowTemplates(task) };
100
111
  },
112
+ "/api/workflow/events": async ({ root, url }) => ({
113
+ status: 200,
114
+ body: await listWorkflowEvents(removeUndefined({
115
+ root,
116
+ task: stringParam(url, "task"),
117
+ run: stringParam(url, "run"),
118
+ limit: positiveIntegerParam(url, "limit") ?? 20,
119
+ })),
120
+ }),
101
121
  "/api/workflow/render": async ({ root, url }) => {
102
122
  const taskId = requiredParam(url, "task");
103
123
  const task = (await listTasks(root)).find((candidate) => candidate.id === taskId);
@@ -117,6 +137,10 @@ const requestRoutes = {
117
137
  }),
118
138
  };
119
139
  },
140
+ "/api/release/readiness": async ({ root }) => ({
141
+ status: 200,
142
+ body: await releaseReadinessView(root),
143
+ }),
120
144
  };
121
145
  export async function handleWebApiRequest(pathname, context) {
122
146
  const handler = routes[pathname];
@@ -163,11 +187,28 @@ export async function startWebApiServer(options = {}) {
163
187
  writeJson(response, result.status, result.body);
164
188
  return;
165
189
  }
190
+ if (request.method === "POST" &&
191
+ url.pathname === "/api/artifacts/evidence") {
192
+ const result = await handleArtifactEvidencePost(request, root);
193
+ writeJson(response, result.status, result.body);
194
+ return;
195
+ }
166
196
  if (request.method === "POST" && url.pathname === "/api/reviews/add") {
167
197
  const result = await handleReviewAddPost(request, root);
168
198
  writeJson(response, result.status, result.body);
169
199
  return;
170
200
  }
201
+ if (request.method === "POST" &&
202
+ url.pathname === "/api/provider/set-role") {
203
+ const result = await handleProviderSetRolePost(request, root);
204
+ writeJson(response, result.status, result.body);
205
+ return;
206
+ }
207
+ if (request.method === "POST" && url.pathname === "/api/recovery/repair") {
208
+ const result = await handleRecoveryRepairPost(request, root);
209
+ writeJson(response, result.status, result.body);
210
+ return;
211
+ }
171
212
  if (request.method === "POST" && url.pathname === "/api/workflow/start") {
172
213
  const result = await handleWorkflowStartPost(request, root);
173
214
  writeJson(response, result.status, result.body);
@@ -178,12 +219,23 @@ export async function startWebApiServer(options = {}) {
178
219
  writeJson(response, result.status, result.body);
179
220
  return;
180
221
  }
222
+ if (request.method === "POST" && url.pathname === "/api/workflow/cancel") {
223
+ const result = await handleWorkflowCancelPost(request, root);
224
+ writeJson(response, result.status, result.body);
225
+ return;
226
+ }
181
227
  if (request.method === "POST" &&
182
228
  url.pathname === "/api/workflow/gate-approve") {
183
229
  const result = await handleWorkflowGateApprovePost(request, root);
184
230
  writeJson(response, result.status, result.body);
185
231
  return;
186
232
  }
233
+ if (request.method === "POST" &&
234
+ url.pathname === "/api/workflow/gate-decision") {
235
+ const result = await handleWorkflowGateDecisionPost(request, root);
236
+ writeJson(response, result.status, result.body);
237
+ return;
238
+ }
187
239
  if (request.method === "POST" && url.pathname === "/api/lessons/add") {
188
240
  const result = await handleLessonAddPost(request, root);
189
241
  writeJson(response, result.status, result.body);
@@ -202,7 +254,7 @@ export async function startWebApiServer(options = {}) {
202
254
  return;
203
255
  }
204
256
  if (url.pathname === "/") {
205
- writeHtml(response, renderWebConsoleHtml());
257
+ await writeConsoleIndex(response);
206
258
  return;
207
259
  }
208
260
  if (url.pathname === "/vendor/chart.umd.js") {
@@ -213,10 +265,22 @@ export async function startWebApiServer(options = {}) {
213
265
  await writeJavaScript(response, WEB_CONSOLE_BUNDLE_PATH);
214
266
  return;
215
267
  }
268
+ if (url.pathname.startsWith("/assets/")) {
269
+ await writeReactConsoleAsset(response, url.pathname);
270
+ return;
271
+ }
216
272
  if (url.pathname === "/api/artifacts") {
217
273
  await writeArtifactResponse(response, root, requiredParam(url, "path"));
218
274
  return;
219
275
  }
276
+ if (url.pathname === "/api/artifacts/preview") {
277
+ const result = await safeRequestRoute(async ({ root: requestRoot, url: requestUrl }) => ({
278
+ status: 200,
279
+ body: await previewWebArtifact(requestRoot, requiredParam(requestUrl, "path")),
280
+ }), { root, url, request });
281
+ writeJson(response, result.status, result.body);
282
+ return;
283
+ }
220
284
  const requestHandler = requestRoutes[url.pathname];
221
285
  const result = requestHandler
222
286
  ? await safeRequestRoute(requestHandler, { root, url, request })
@@ -236,6 +300,13 @@ async function handleTaskAddPost(request, root) {
236
300
  try {
237
301
  const input = (await readJsonBody(request));
238
302
  validateTaskAddInput(input);
303
+ await authorizeWebAction({
304
+ root,
305
+ taskId: input.id,
306
+ actor: "web-console",
307
+ action: "web task add",
308
+ paths: input.paths ?? [],
309
+ });
239
310
  return { status: 200, body: await addTask(input, root) };
240
311
  }
241
312
  catch (error) {
@@ -248,6 +319,13 @@ async function handleTaskUpdatePost(request, root) {
248
319
  if (!input.id) {
249
320
  throw new Error("id is required");
250
321
  }
322
+ await authorizeWebAction({
323
+ root,
324
+ taskId: input.id,
325
+ actor: "web-console",
326
+ action: "web task update",
327
+ paths: input.paths ?? [],
328
+ });
251
329
  return {
252
330
  status: 200,
253
331
  body: await updateTask({ ...input, id: input.id }, root),
@@ -261,22 +339,114 @@ async function handleEvidenceAddPost(request, root) {
261
339
  try {
262
340
  const input = (await readJsonBody(request));
263
341
  validateEvidencePostInput(input);
342
+ await authorizeWebAction(removeUndefined({
343
+ root,
344
+ taskId: input.task,
345
+ actor: input.role,
346
+ action: "web evidence add",
347
+ command: stringOrUndefined(input.command),
348
+ paths: stringArray(input.path),
349
+ }));
264
350
  return { status: 200, body: await addEvidence(input, root) };
265
351
  }
266
352
  catch (error) {
267
353
  return errorResult(error);
268
354
  }
269
355
  }
356
+ async function handleArtifactEvidencePost(request, root) {
357
+ try {
358
+ const input = (await readJsonBody(request));
359
+ validateArtifactEvidenceInput(input);
360
+ await authorizeWebAction({
361
+ root,
362
+ taskId: input.task,
363
+ actor: input.role,
364
+ action: "web artifact evidence attach",
365
+ paths: [input.path],
366
+ });
367
+ return {
368
+ status: 200,
369
+ body: await attachArtifactEvidence(root, removeUndefined({
370
+ task: input.task,
371
+ role: input.role,
372
+ type: input.type,
373
+ path: input.path,
374
+ summary: input.summary,
375
+ runId: input.runId,
376
+ })),
377
+ };
378
+ }
379
+ catch (error) {
380
+ return errorResult(error);
381
+ }
382
+ }
270
383
  async function handleReviewAddPost(request, root) {
271
384
  try {
272
385
  const input = (await readJsonBody(request));
273
386
  validateReviewPostInput(input);
387
+ await authorizeWebAction({
388
+ root,
389
+ taskId: input.task,
390
+ actor: input.role,
391
+ action: "web review add",
392
+ });
274
393
  return { status: 200, body: await recordReview(input, root) };
275
394
  }
276
395
  catch (error) {
277
396
  return errorResult(error);
278
397
  }
279
398
  }
399
+ async function handleProviderSetRolePost(request, root) {
400
+ try {
401
+ const input = (await readJsonBody(request));
402
+ validateProviderSetRoleInput(input);
403
+ await authorizeWebAction({
404
+ root,
405
+ actor: "web-console",
406
+ action: "web provider role routing update",
407
+ command: `${input.role} ${input.provider}/${input.model}`,
408
+ });
409
+ const config = await getWorkflowConfig(root);
410
+ const current = config.providers?.byRole?.[input.role] ?? config.providers?.defaults;
411
+ const routing = {
412
+ provider: input.provider,
413
+ model: input.model,
414
+ fallbacks: input.fallbacks ?? current?.fallbacks ?? [],
415
+ maxTokens: input.maxTokens ?? current?.maxTokens ?? 8000,
416
+ maxCostUsd: input.maxCostUsd ?? current?.maxCostUsd ?? 1,
417
+ timeoutMs: input.timeoutMs ?? current?.timeoutMs ?? 120000,
418
+ retries: input.retries ?? current?.retries ?? 0,
419
+ requiredCapabilities: input.requiredCapabilities ?? current?.requiredCapabilities ?? [],
420
+ };
421
+ return {
422
+ status: 200,
423
+ body: await setRoleModelProvider(input.role, routing, root),
424
+ };
425
+ }
426
+ catch (error) {
427
+ return errorResult(error);
428
+ }
429
+ }
430
+ async function handleRecoveryRepairPost(request, root) {
431
+ try {
432
+ const input = (await readJsonBody(request));
433
+ validateRecoveryRepairInput(input);
434
+ await authorizeWebAction({
435
+ root,
436
+ actor: "web-console",
437
+ action: `web recovery ${input.action}`,
438
+ command: input.target,
439
+ confirmed: input.confirmed,
440
+ });
441
+ return {
442
+ status: 200,
443
+ body: await repairRecoveryItem(input, root),
444
+ };
445
+ }
446
+ catch (error) {
447
+ return errorResult(error);
448
+ }
449
+ }
280
450
  async function handleWorkflowStartPost(request, root) {
281
451
  try {
282
452
  const input = (await readJsonBody(request));
@@ -284,6 +454,12 @@ async function handleWorkflowStartPost(request, root) {
284
454
  throw new Error("task is required");
285
455
  }
286
456
  const gates = parseWorkflowGates(input.gates);
457
+ await authorizeWebAction({
458
+ root,
459
+ taskId: input.task,
460
+ actor: "web-console",
461
+ action: "web workflow start",
462
+ });
287
463
  const execution = await runOrchestraCli(root, [
288
464
  "workflow",
289
465
  "run",
@@ -310,6 +486,12 @@ async function handleWorkflowResumePost(request, root) {
310
486
  if (!input.task || !input.run) {
311
487
  throw new Error("task and run are required");
312
488
  }
489
+ await authorizeWebAction({
490
+ root,
491
+ taskId: input.task,
492
+ actor: "web-console",
493
+ action: "web workflow resume",
494
+ });
313
495
  const execution = await runOrchestraCli(root, [
314
496
  "workflow",
315
497
  "run",
@@ -330,12 +512,38 @@ async function handleWorkflowResumePost(request, root) {
330
512
  return errorResult(error);
331
513
  }
332
514
  }
515
+ async function handleWorkflowCancelPost(request, root) {
516
+ try {
517
+ const input = (await readJsonBody(request));
518
+ if (!input.run) {
519
+ throw new Error("run is required");
520
+ }
521
+ await authorizeWebAction(removeUndefined({
522
+ root,
523
+ actor: "web-console",
524
+ action: "web workflow cancel",
525
+ command: input.reason,
526
+ }));
527
+ return {
528
+ status: 200,
529
+ body: await cancelRun(root, input.run, input.reason ?? "Canceled from web console"),
530
+ };
531
+ }
532
+ catch (error) {
533
+ return errorResult(error);
534
+ }
535
+ }
333
536
  async function handleWorkflowGateApprovePost(request, root) {
334
537
  try {
335
538
  const input = (await readJsonBody(request));
336
539
  if (!input.run || !input.gate || !input.approver || !input.rationale) {
337
540
  throw new Error("run, gate, approver, and rationale are required");
338
541
  }
542
+ await authorizeWebAction({
543
+ root,
544
+ actor: input.approver,
545
+ action: "web workflow gate approve",
546
+ });
339
547
  return {
340
548
  status: 200,
341
549
  body: await approveWorkflowGate({
@@ -350,6 +558,39 @@ async function handleWorkflowGateApprovePost(request, root) {
350
558
  return errorResult(error);
351
559
  }
352
560
  }
561
+ async function handleWorkflowGateDecisionPost(request, root) {
562
+ try {
563
+ const input = (await readJsonBody(request));
564
+ if (!input.run || !input.gate || !input.reviewer || !input.rationale) {
565
+ throw new Error("run, gate, reviewer, and rationale are required");
566
+ }
567
+ if (!isWorkflowGateDecisionResult(input.result)) {
568
+ throw new Error("result must be one of: approve, block, changes");
569
+ }
570
+ await authorizeWebAction({
571
+ root,
572
+ actor: input.reviewer,
573
+ action: `web workflow gate ${input.result}`,
574
+ command: input.rationale,
575
+ });
576
+ return {
577
+ status: 200,
578
+ body: await recordWorkflowGateDecision({
579
+ runId: input.run,
580
+ gateId: input.gate,
581
+ reviewer: input.reviewer,
582
+ rationale: input.rationale,
583
+ result: input.result,
584
+ }, root),
585
+ };
586
+ }
587
+ catch (error) {
588
+ return errorResult(error);
589
+ }
590
+ }
591
+ function isWorkflowGateDecisionResult(result) {
592
+ return result === "approve" || result === "block" || result === "changes";
593
+ }
353
594
  export function getWebServerAddress(server) {
354
595
  const address = server.address();
355
596
  if (!address) {
@@ -376,6 +617,13 @@ function writeHtml(response, body) {
376
617
  });
377
618
  response.end(body);
378
619
  }
620
+ async function writeConsoleIndex(response) {
621
+ if (await fileExists(REACT_WEB_CONSOLE_INDEX_PATH)) {
622
+ writeHtml(response, await readFile(REACT_WEB_CONSOLE_INDEX_PATH, "utf8"));
623
+ return;
624
+ }
625
+ writeHtml(response, renderWebConsoleHtml());
626
+ }
379
627
  async function writeJavaScript(response, filePath) {
380
628
  const bundle = await readFile(filePath, "utf8");
381
629
  response.writeHead(200, {
@@ -384,6 +632,52 @@ async function writeJavaScript(response, filePath) {
384
632
  });
385
633
  response.end(bundle);
386
634
  }
635
+ async function writeReactConsoleAsset(response, pathname) {
636
+ const assetPath = resolve(REACT_WEB_CONSOLE_DIR, pathname.replace(/^\//, ""));
637
+ if (isOutsideDirectory(REACT_WEB_CONSOLE_DIR, assetPath)) {
638
+ writeJson(response, 400, {
639
+ error: "invalid_asset_path",
640
+ message: "asset path must stay inside the web console build directory",
641
+ });
642
+ return;
643
+ }
644
+ if (!(await fileExists(assetPath))) {
645
+ writeJson(response, 404, {
646
+ error: "not_found",
647
+ message: `unknown asset: ${pathname}`,
648
+ });
649
+ return;
650
+ }
651
+ const body = await readFile(assetPath);
652
+ response.writeHead(200, {
653
+ "content-type": contentTypeForPath(assetPath),
654
+ "cache-control": "no-store",
655
+ });
656
+ response.end(body);
657
+ }
658
+ function isOutsideDirectory(root, candidate) {
659
+ const pathFromRoot = relative(root, candidate);
660
+ return pathFromRoot.startsWith("..") || pathFromRoot === "";
661
+ }
662
+ async function fileExists(filePath) {
663
+ try {
664
+ return (await stat(filePath)).isFile();
665
+ }
666
+ catch {
667
+ return false;
668
+ }
669
+ }
670
+ function contentTypeForPath(filePath) {
671
+ if (filePath.endsWith(".js"))
672
+ return "text/javascript; charset=utf-8";
673
+ if (filePath.endsWith(".css"))
674
+ return "text/css; charset=utf-8";
675
+ if (filePath.endsWith(".svg"))
676
+ return "image/svg+xml";
677
+ if (filePath.endsWith(".json"))
678
+ return "application/json; charset=utf-8";
679
+ return "application/octet-stream";
680
+ }
387
681
  async function safeRequestRoute(handler, context) {
388
682
  try {
389
683
  return await handler(context);
@@ -396,6 +690,13 @@ async function handlePlaywrightEvidencePost(request, root) {
396
690
  try {
397
691
  const input = (await readJsonBody(request));
398
692
  validatePlaywrightEvidenceInput(input);
693
+ await authorizeWebAction({
694
+ root,
695
+ taskId: input.task,
696
+ actor: "qa",
697
+ action: "web playwright evidence attach",
698
+ paths: [input.path],
699
+ });
399
700
  return {
400
701
  status: 200,
401
702
  body: await attachWebPlaywrightEvidence(root, input),
@@ -434,6 +735,26 @@ function validateEvidencePostInput(input) {
434
735
  throw new Error("task, role, type, and summary are required");
435
736
  }
436
737
  }
738
+ function validateArtifactEvidenceInput(input) {
739
+ if (!input.task ||
740
+ !input.role ||
741
+ !input.type ||
742
+ !input.path ||
743
+ !input.summary) {
744
+ throw new Error("task, role, type, path, and summary are required");
745
+ }
746
+ if (![
747
+ "command",
748
+ "file",
749
+ "screenshot",
750
+ "trace",
751
+ "video",
752
+ "log",
753
+ "report",
754
+ ].includes(input.type)) {
755
+ throw new Error("invalid evidence type: " + input.type);
756
+ }
757
+ }
437
758
  function validateReviewPostInput(input) {
438
759
  if (!input.task ||
439
760
  !input.role ||
@@ -446,6 +767,46 @@ function validateReviewPostInput(input) {
446
767
  input.severity = "low";
447
768
  }
448
769
  }
770
+ function validateProviderSetRoleInput(input) {
771
+ if (!input.role || !input.provider || !input.model) {
772
+ throw new Error("role, provider, and model are required");
773
+ }
774
+ if (input.fallbacks && !Array.isArray(input.fallbacks)) {
775
+ throw new Error("fallbacks must be an array");
776
+ }
777
+ if (input.requiredCapabilities &&
778
+ !Array.isArray(input.requiredCapabilities)) {
779
+ throw new Error("requiredCapabilities must be an array");
780
+ }
781
+ }
782
+ function validateRecoveryRepairInput(input) {
783
+ if (input.action !== "release-lock" &&
784
+ input.action !== "remove-temp-file" &&
785
+ input.action !== "remove-file-lock") {
786
+ throw new Error("action must be one of: release-lock, remove-temp-file, remove-file-lock");
787
+ }
788
+ if (!input.target) {
789
+ throw new Error("target is required");
790
+ }
791
+ if (!input.confirmed) {
792
+ throw new Error("confirmed is required");
793
+ }
794
+ }
795
+ function stringOrUndefined(value) {
796
+ if (typeof value === "string" && value.trim() !== "") {
797
+ return value;
798
+ }
799
+ return undefined;
800
+ }
801
+ function stringArray(value) {
802
+ if (typeof value === "string" && value.trim() !== "") {
803
+ return [value];
804
+ }
805
+ if (Array.isArray(value)) {
806
+ return value.filter((item) => typeof item === "string");
807
+ }
808
+ return [];
809
+ }
449
810
  function parseWorkflowGates(value) {
450
811
  const gates = value ?? "phase";
451
812
  if (!["none", "phase", "all"].includes(gates)) {
@@ -551,6 +912,14 @@ function stringParam(url, name) {
551
912
  const value = url.searchParams.get(name);
552
913
  return value && value.trim() !== "" ? value : undefined;
553
914
  }
915
+ function positiveIntegerParam(url, name) {
916
+ const value = stringParam(url, name);
917
+ if (!value) {
918
+ return undefined;
919
+ }
920
+ const parsed = Number.parseInt(value, 10);
921
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
922
+ }
554
923
  function webEvidenceFilters(url) {
555
924
  const filters = {};
556
925
  const task = stringParam(url, "task");
@@ -567,6 +936,139 @@ async function taskReleaseReadinessReport(root, taskId) {
567
936
  const report = await generateReleaseReadinessReport(root);
568
937
  return report.tasks.find((task) => task.taskId === taskId) ?? null;
569
938
  }
939
+ async function releaseReadinessView(root) {
940
+ const [report, tasks, packageInfo, commits] = await Promise.all([
941
+ generateReleaseReadinessReport(root),
942
+ listTasks(root),
943
+ readPackageInfo(),
944
+ recentCommits(root),
945
+ ]);
946
+ const closedTasks = tasks
947
+ .filter((task) => ["approved", "done"].includes(task.status))
948
+ .map((task) => ({
949
+ id: task.id,
950
+ title: task.title,
951
+ status: task.status,
952
+ ownerRole: task.ownerRole,
953
+ }));
954
+ const pendingTasks = tasks
955
+ .filter((task) => ["pending", "ready", "in_progress", "blocked", "review"].includes(task.status))
956
+ .map((task) => ({
957
+ id: task.id,
958
+ title: task.title,
959
+ status: task.status,
960
+ }));
961
+ const checks = releaseChecks(report, pendingTasks.length);
962
+ return {
963
+ version: String(packageInfo.version ?? "unknown"),
964
+ commits,
965
+ closedTasks,
966
+ pendingTasks,
967
+ checks,
968
+ criticalBlockers: checks
969
+ .filter((check) => check.blocking && !check.passed)
970
+ .map((check) => check.label),
971
+ acceptedRisks: report.acceptedRisks,
972
+ summary: renderReleaseSummary({
973
+ version: String(packageInfo.version ?? "unknown"),
974
+ report,
975
+ closedTaskCount: closedTasks.length,
976
+ pendingTaskCount: pendingTasks.length,
977
+ checks,
978
+ }),
979
+ report,
980
+ };
981
+ }
982
+ async function readPackageInfo() {
983
+ return JSON.parse(await readFile(join(PACKAGE_ROOT, "package.json"), "utf8"));
984
+ }
985
+ async function recentCommits(root) {
986
+ try {
987
+ const { stdout } = await execFileAsync("git", ["log", "-5", "--pretty=format:%h %s"], { cwd: root });
988
+ return stdout.split("\n").filter(Boolean);
989
+ }
990
+ catch {
991
+ return [];
992
+ }
993
+ }
994
+ function releaseChecks(report, pendingTaskCount) {
995
+ return [
996
+ {
997
+ id: "tests",
998
+ label: "Tests evidence",
999
+ passed: !report.knownGaps.some((gap) => /test|QA automation/i.test(gap)),
1000
+ blocking: true,
1001
+ },
1002
+ {
1003
+ id: "e2e",
1004
+ label: "E2E evidence",
1005
+ passed: report.tasks.some((task) => [
1006
+ ...task.qaAutomationCoverage.commands,
1007
+ ...task.qaAutomationCoverage.pageObjects,
1008
+ ].some((item) => /e2e|playwright/i.test(item))),
1009
+ blocking: false,
1010
+ },
1011
+ {
1012
+ id: "docs",
1013
+ label: "Docs or changelog evidence",
1014
+ passed: !report.missingDocumentationEvidence,
1015
+ blocking: false,
1016
+ },
1017
+ {
1018
+ id: "site",
1019
+ label: "Site or public documentation evidence",
1020
+ passed: !report.missingDocumentationEvidence,
1021
+ blocking: false,
1022
+ },
1023
+ {
1024
+ id: "extension",
1025
+ label: "Extension compatibility evidence",
1026
+ passed: !report.knownGaps.some((gap) => /extension/i.test(gap)),
1027
+ blocking: false,
1028
+ },
1029
+ {
1030
+ id: "security",
1031
+ label: "Security evidence",
1032
+ passed: report.gaReadiness.criteria.some((criterion) => criterion.id === "security-evidence" && criterion.passed),
1033
+ blocking: true,
1034
+ },
1035
+ {
1036
+ id: "pending-issues",
1037
+ label: "Pending local work",
1038
+ passed: pendingTaskCount === 0,
1039
+ blocking: false,
1040
+ },
1041
+ {
1042
+ id: "critical-gates",
1043
+ label: "Critical release gates",
1044
+ passed: report.gaReadiness.blockers.length === 0 && report.passed,
1045
+ blocking: true,
1046
+ },
1047
+ ];
1048
+ }
1049
+ function renderReleaseSummary({ version, report, closedTaskCount, pendingTaskCount, checks, }) {
1050
+ return [
1051
+ "# Release Readiness Summary",
1052
+ "",
1053
+ "- Version: " + version,
1054
+ "- Decision: " + report.gaReadiness.decision,
1055
+ "- Closed tasks: " + closedTaskCount,
1056
+ "- Pending local work: " + pendingTaskCount,
1057
+ "- Accepted risks: " + report.acceptedRisks.length,
1058
+ "",
1059
+ "## Checks",
1060
+ ...checks.map((check) => "- " +
1061
+ (check.passed ? "PASS" : "BLOCKED") +
1062
+ " " +
1063
+ check.label +
1064
+ (check.blocking ? " (blocking)" : "")),
1065
+ "",
1066
+ "## Known Gaps",
1067
+ ...(report.knownGaps.length > 0
1068
+ ? report.knownGaps.slice(0, 20).map((gap) => "- " + gap)
1069
+ : ["- none"]),
1070
+ ].join("\n");
1071
+ }
570
1072
  async function writeArtifactResponse(response, root, artifactPath) {
571
1073
  try {
572
1074
  const artifact = await readWorkspaceArtifact(root, artifactPath);