@jterrats/open-orchestra 0.4.2-beta.2 → 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 (148) hide show
  1. package/README.md +29 -5
  2. package/dist/advisory-artifacts.d.ts +6 -0
  3. package/dist/advisory-artifacts.js +37 -1
  4. package/dist/advisory-artifacts.js.map +1 -1
  5. package/dist/assets/web-console.js +297 -4
  6. package/dist/cli.js +13 -118
  7. package/dist/cli.js.map +1 -1
  8. package/dist/command-manifest.d.ts +3 -0
  9. package/dist/command-manifest.js +132 -42
  10. package/dist/command-manifest.js.map +1 -1
  11. package/dist/command-utils.d.ts +5 -0
  12. package/dist/command-utils.js +23 -0
  13. package/dist/command-utils.js.map +1 -1
  14. package/dist/commands.d.ts +6 -42
  15. package/dist/commands.js +204 -1372
  16. package/dist/commands.js.map +1 -1
  17. package/dist/constants.js +1 -0
  18. package/dist/constants.js.map +1 -1
  19. package/dist/delivery-commands.d.ts +10 -0
  20. package/dist/delivery-commands.js +152 -0
  21. package/dist/delivery-commands.js.map +1 -0
  22. package/dist/github.d.ts +50 -1
  23. package/dist/github.js +234 -0
  24. package/dist/github.js.map +1 -1
  25. package/dist/health-checks.d.ts +1 -0
  26. package/dist/health-checks.js +11 -1
  27. package/dist/health-checks.js.map +1 -1
  28. package/dist/health-commands.js +2 -0
  29. package/dist/health-commands.js.map +1 -1
  30. package/dist/memory.d.ts +2 -1
  31. package/dist/memory.js +71 -10
  32. package/dist/memory.js.map +1 -1
  33. package/dist/package-update-check.d.ts +5 -1
  34. package/dist/package-update-check.js +20 -8
  35. package/dist/package-update-check.js.map +1 -1
  36. package/dist/planning-commands.d.ts +14 -0
  37. package/dist/planning-commands.js +372 -0
  38. package/dist/planning-commands.js.map +1 -0
  39. package/dist/release-candidate.d.ts +2 -0
  40. package/dist/release-candidate.js +9 -14
  41. package/dist/release-candidate.js.map +1 -1
  42. package/dist/release-commands.d.ts +2 -0
  43. package/dist/release-commands.js +58 -6
  44. package/dist/release-commands.js.map +1 -1
  45. package/dist/release-readiness.d.ts +49 -0
  46. package/dist/release-readiness.js +172 -0
  47. package/dist/release-readiness.js.map +1 -0
  48. package/dist/runtime-commands.js +2 -5
  49. package/dist/runtime-commands.js.map +1 -1
  50. package/dist/setup-agents-import.js +1 -3
  51. package/dist/setup-agents-import.js.map +1 -1
  52. package/dist/skills-catalog-service.d.ts +2 -0
  53. package/dist/skills-catalog-service.js +8 -0
  54. package/dist/skills-catalog-service.js.map +1 -0
  55. package/dist/skills-catalog.d.ts +2 -0
  56. package/dist/skills-catalog.js +389 -0
  57. package/dist/skills-catalog.js.map +1 -0
  58. package/dist/skills-commands.js +1 -11
  59. package/dist/skills-commands.js.map +1 -1
  60. package/dist/skills-events.d.ts +9 -0
  61. package/dist/skills-events.js +50 -0
  62. package/dist/skills-events.js.map +1 -0
  63. package/dist/skills-memory.d.ts +18 -0
  64. package/dist/skills-memory.js +127 -0
  65. package/dist/skills-memory.js.map +1 -0
  66. package/dist/skills-planning.d.ts +2 -0
  67. package/dist/skills-planning.js +87 -0
  68. package/dist/skills-planning.js.map +1 -0
  69. package/dist/skills-render.d.ts +14 -0
  70. package/dist/skills-render.js +83 -0
  71. package/dist/skills-render.js.map +1 -0
  72. package/dist/skills-validation.d.ts +2 -0
  73. package/dist/skills-validation.js +49 -0
  74. package/dist/skills-validation.js.map +1 -0
  75. package/dist/skills.d.ts +6 -42
  76. package/dist/skills.js +6 -773
  77. package/dist/skills.js.map +1 -1
  78. package/dist/task-graph-commands.d.ts +14 -0
  79. package/dist/task-graph-commands.js +367 -0
  80. package/dist/task-graph-commands.js.map +1 -0
  81. package/dist/types/context.d.ts +12 -0
  82. package/dist/types/context.js +2 -0
  83. package/dist/types/context.js.map +1 -0
  84. package/dist/types/metrics.d.ts +114 -0
  85. package/dist/types/metrics.js +2 -0
  86. package/dist/types/metrics.js.map +1 -0
  87. package/dist/types/model-config.d.ts +212 -0
  88. package/dist/types/model-config.js +2 -0
  89. package/dist/types/model-config.js.map +1 -0
  90. package/dist/types/runtime.d.ts +93 -0
  91. package/dist/types/runtime.js +2 -0
  92. package/dist/types/runtime.js.map +1 -0
  93. package/dist/types/skills.d.ts +147 -0
  94. package/dist/types/skills.js +2 -0
  95. package/dist/types/skills.js.map +1 -0
  96. package/dist/types/tasks.d.ts +171 -0
  97. package/dist/types/tasks.js +2 -0
  98. package/dist/types/tasks.js.map +1 -0
  99. package/dist/types/workflow-run.d.ts +79 -0
  100. package/dist/types/workflow-run.js +2 -0
  101. package/dist/types/workflow-run.js.map +1 -0
  102. package/dist/types.d.ts +12 -833
  103. package/dist/types.js +1 -1
  104. package/dist/types.js.map +1 -1
  105. package/dist/upgrade-commands.d.ts +2 -0
  106. package/dist/upgrade-commands.js +65 -0
  107. package/dist/upgrade-commands.js.map +1 -0
  108. package/dist/web-api-read-routes.d.ts +5 -0
  109. package/dist/web-api-read-routes.js +37 -0
  110. package/dist/web-api-read-routes.js.map +1 -0
  111. package/dist/web-api.d.ts +1 -3
  112. package/dist/web-api.js +23 -45
  113. package/dist/web-api.js.map +1 -1
  114. package/dist/web-console-sections.d.ts +2 -0
  115. package/dist/web-console-sections.js +7 -0
  116. package/dist/web-console-sections.js.map +1 -0
  117. package/dist/web-console.js +23 -3
  118. package/dist/web-console.js.map +1 -1
  119. package/dist/workflow-approval-service.d.ts +9 -0
  120. package/dist/workflow-approval-service.js +126 -0
  121. package/dist/workflow-approval-service.js.map +1 -0
  122. package/dist/workflow-approval-utils.d.ts +10 -0
  123. package/dist/workflow-approval-utils.js +82 -0
  124. package/dist/workflow-approval-utils.js.map +1 -0
  125. package/dist/workflow-budget-utils.d.ts +7 -0
  126. package/dist/workflow-budget-utils.js +96 -0
  127. package/dist/workflow-budget-utils.js.map +1 -0
  128. package/dist/workflow-evidence-service.d.ts +7 -0
  129. package/dist/workflow-evidence-service.js +100 -0
  130. package/dist/workflow-evidence-service.js.map +1 -0
  131. package/dist/workflow-run-commands.d.ts +8 -0
  132. package/dist/workflow-run-commands.js +479 -0
  133. package/dist/workflow-run-commands.js.map +1 -0
  134. package/dist/workflow-services.d.ts +5 -17
  135. package/dist/workflow-services.js +26 -479
  136. package/dist/workflow-services.js.map +1 -1
  137. package/dist/workflow-summary-service.d.ts +4 -0
  138. package/dist/workflow-summary-service.js +82 -0
  139. package/dist/workflow-summary-service.js.map +1 -0
  140. package/dist/workspace.d.ts +18 -1
  141. package/dist/workspace.js +66 -4
  142. package/dist/workspace.js.map +1 -1
  143. package/docs/orchestra-mvp.md +158 -114
  144. package/docs/package-naming.md +20 -0
  145. package/docs/persona-workflows.md +209 -0
  146. package/docs/runtime-adapters.md +15 -14
  147. package/docs/runtime-llm-flow.md +29 -28
  148. package/package.json +3 -2
@@ -1,28 +1,30 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
- import { readdir, readFile } from "node:fs/promises";
4
3
  import { FILES } from "./constants.js";
5
- import { exists, ensureDir, readJson, resolveWorkflowPath, updateJsonFile, writeJson, } from "./fs-utils.js";
4
+ import { removeUndefined } from "./command-utils.js";
5
+ import { ensureDir, readJson, resolveWorkflowPath, updateJsonFile, writeJson, } from "./fs-utils.js";
6
6
  import { assertProviderAllowed, assertVendorFallbackAllowed, createModelProvider, FakeModelProvider, InMemoryModelProviderRegistry, summarizeConfiguredProviders, } from "./model-providers.js";
7
7
  import { appendEvent, loadWorkspace, readEvents, writeArtifact, } from "./workspace.js";
8
- import { normalizeEvidenceType, normalizeReviewRole, validateEvidenceInput, validateReadiness, validateReviewInput, } from "./validation.js";
8
+ import { validateReadiness } from "./validation.js";
9
9
  import { getWorkflowGate } from "./workflow-gates.js";
10
- import { analyzePullRequestReview } from "./pr-review.js";
11
10
  import { planSkillsForTask } from "./skills.js";
12
11
  import { getTelemetryConsent } from "./telemetry.js";
13
12
  import { latestDelegationDecision } from "./delegation-decision.js";
14
13
  import { listAutonomousRuns } from "./autonomous-workflow.js";
15
- import { persistRun, readAutonomousRun } from "./autonomous-run-store.js";
16
- import { AUTONOMOUS_PHASE_SEQUENCE } from "./autonomous-workflow-constants.js";
17
14
  import { readEstimate } from "./benchmark.js";
18
- import { notifyWorkflowLifecycle, } from "./notifications.js";
19
15
  import { handoffFlowRequirements, recommendCollaborationFlow, } from "./collaboration-flows.js";
20
16
  import { queryMemory, recordMemoryEvent } from "./memory.js";
21
17
  import { applyContextBudget, DEFAULT_CONTEXT_TOKEN_BUDGET, } from "./context-budget.js";
22
18
  import { selectWorkflowTemplates } from "./workflow-templates.js";
19
+ import { findStoredApprovalForProposal } from "./workflow-approval-service.js";
20
+ import { aggregateUsage, aggregateUsageBy, budgetEstimateWarningsForBudgets, budgetViolations, emptyUsageBreakdown, projectUsageCost, } from "./workflow-budget-utils.js";
23
21
  import { SIZING_LABELS } from "./types.js";
22
+ export { addEvidence, addPlaywrightEvidence, listEvidence, listReviews, recordReview, } from "./workflow-evidence-service.js";
23
+ export { generatePlaywrightTestPlan, generatePullRequestSummary, getWorkflowSummary, } from "./workflow-summary-service.js";
24
+ export { approveApproval, approveWorkflowGate, listApprovals, rejectApproval, showApproval, } from "./workflow-approval-service.js";
24
25
  export async function getWorkflowStatus(root = process.cwd()) {
25
26
  const workspace = await loadWorkspace(root);
27
+ const config = await readJson(resolveWorkflowPath(root, FILES.config), {});
26
28
  const counts = Object.create(null);
27
29
  const blocked = [];
28
30
  for (const task of workspace.tasks) {
@@ -36,6 +38,7 @@ export async function getWorkflowStatus(root = process.cwd()) {
36
38
  }
37
39
  }
38
40
  return {
41
+ ...(config.mode ? { mode: config.mode } : {}),
39
42
  tasks: {
40
43
  total: workspace.tasks.length,
41
44
  byStatus: counts,
@@ -488,17 +491,7 @@ export async function checkReadiness(taskId, root = process.cwd()) {
488
491
  return { task, report };
489
492
  }
490
493
  export async function evaluateWorkflowGate(gateId, taskId, root = process.cwd()) {
491
- const workspace = await loadWorkspace(root);
492
- const task = workspace.tasks.find((candidate) => candidate.id === taskId);
493
- if (!task) {
494
- throw new Error(`unknown task: ${taskId}`);
495
- }
496
- const gate = getWorkflowGate(gateId);
497
- const result = gate.evaluate({
498
- task,
499
- events: await readEvents(root),
500
- locks: workspace.locks,
501
- });
494
+ const result = await previewWorkflowGate(gateId, taskId, root);
502
495
  await appendEvent(root, {
503
496
  type: result.passed ? "GATE_PASSED" : "GATE_BLOCKED",
504
497
  taskId,
@@ -513,6 +506,20 @@ export async function evaluateWorkflowGate(gateId, taskId, root = process.cwd())
513
506
  });
514
507
  return result;
515
508
  }
509
+ export async function previewWorkflowGate(gateId, taskId, root = process.cwd()) {
510
+ const workspace = await loadWorkspace(root);
511
+ const task = workspace.tasks.find((candidate) => candidate.id === taskId);
512
+ if (!task) {
513
+ throw new Error(`unknown task: ${taskId}`);
514
+ }
515
+ const gate = getWorkflowGate(gateId);
516
+ const result = gate.evaluate({
517
+ task,
518
+ events: await readEvents(root),
519
+ locks: workspace.locks,
520
+ });
521
+ return result;
522
+ }
516
523
  export async function createHandoff(input, root = process.cwd()) {
517
524
  const workspace = await loadWorkspace(root);
518
525
  if (!workspace.roleIds.has(input.from) || !workspace.roleIds.has(input.to)) {
@@ -623,6 +630,7 @@ export async function getTaskContext(taskId, root = process.cwd(), options = {})
623
630
  const memory = await queryMemory({
624
631
  taskId,
625
632
  hook: "before_implementation",
633
+ role: task.ownerRole,
626
634
  root,
627
635
  });
628
636
  await recordMemoryEvent(root, memory, "MEMORY_QUERIED");
@@ -957,128 +965,6 @@ function pathsConflict(taskPath, lockPath) {
957
965
  function normalizePath(value) {
958
966
  return value.replaceAll("\\", "/").replace(/\/+$/, "");
959
967
  }
960
- export async function listApprovals(taskId, root = process.cwd()) {
961
- const approvalDir = resolveWorkflowPath(root, "approvals");
962
- const fileNames = (await exists(approvalDir))
963
- ? (await readdir(approvalDir)).filter((fileName) => fileName.endsWith(".md"))
964
- : [];
965
- const events = await readEvents(root);
966
- const records = fileNames.map((fileName) => approvalRecordForArtifact(path.join(".agent-workflow", "approvals", fileName), events));
967
- records.push(...gateApprovalRecords(events));
968
- const filtered = taskId
969
- ? records.filter((record) => record.taskId === taskId)
970
- : records;
971
- return filtered.sort((left, right) => left.id.localeCompare(right.id));
972
- }
973
- export async function approveWorkflowGate(input, root = process.cwd()) {
974
- const run = await readAutonomousRun(root, input.runId);
975
- if (!run) {
976
- throw new Error(`workflow run not found: ${input.runId}`);
977
- }
978
- const phaseIndex = run.phases.findLastIndex((phase) => phase.status === "gate_paused");
979
- if (phaseIndex < 0) {
980
- throw new Error(`workflow run ${input.runId} has no paused gate`);
981
- }
982
- const phase = run.phases[phaseIndex];
983
- const gateId = phase.gateId ?? gateIdForPhase(run, phaseIndex);
984
- if (input.gateId !== gateId) {
985
- throw new Error(`wrong gate id: ${input.gateId}. Expected gate: ${gateId}`);
986
- }
987
- if (phase.approvedBy && phase.approvedAt) {
988
- return {
989
- run,
990
- gateId,
991
- approver: phase.approvedBy,
992
- approvedAt: phase.approvedAt,
993
- alreadyApproved: true,
994
- };
995
- }
996
- const approvedAt = new Date().toISOString();
997
- const approvedPhase = {
998
- ...phase,
999
- gateId,
1000
- approvedBy: input.approver,
1001
- approvedAt,
1002
- approvalRationale: input.rationale,
1003
- };
1004
- const updatedRun = {
1005
- ...run,
1006
- phases: run.phases.map((candidate, index) => index === phaseIndex ? approvedPhase : candidate),
1007
- updatedAt: approvedAt,
1008
- };
1009
- await persistRun(root, updatedRun);
1010
- await appendEvent(root, removeUndefined({
1011
- type: "GATE_APPROVED",
1012
- taskId: run.taskId,
1013
- actor: "parent",
1014
- summary: `Gate approved: ${gateId}`,
1015
- artifacts: phase.reviewArtifact ? [phase.reviewArtifact] : undefined,
1016
- metadata: {
1017
- runId: run.id,
1018
- gateId,
1019
- approver: input.approver,
1020
- rationale: input.rationale,
1021
- approvedAt,
1022
- },
1023
- }));
1024
- return {
1025
- run: updatedRun,
1026
- gateId,
1027
- approver: input.approver,
1028
- approvedAt,
1029
- alreadyApproved: false,
1030
- };
1031
- }
1032
- export async function showApproval(id, root = process.cwd()) {
1033
- const record = await getApprovalRecord(id, root);
1034
- const content = await readFile(path.join(root, record.artifact), "utf8");
1035
- return { ...record, content };
1036
- }
1037
- export async function approveApproval(input, root = process.cwd()) {
1038
- return recordApprovalDecision(input, "approved", root);
1039
- }
1040
- export async function rejectApproval(input, root = process.cwd()) {
1041
- return recordApprovalDecision(input, "rejected", root);
1042
- }
1043
- export async function recordReview(input, root = process.cwd(), notificationTransports = {}) {
1044
- const workspace = await loadWorkspace(root);
1045
- const reviewInput = removeUndefined({
1046
- ...input,
1047
- role: normalizeReviewRole(input.role, workspace.roleIds) ?? input.role,
1048
- });
1049
- validateReviewInput(reviewInput, workspace.roleIds);
1050
- const fileName = `${reviewInput.task}-${reviewInput.role}-review.md`;
1051
- const content = [
1052
- `# Review ${reviewInput.task}: ${reviewInput.role}`,
1053
- "",
1054
- `- Result: ${reviewInput.result}`,
1055
- `- Severity: ${reviewInput.severity}`,
1056
- `- Findings: ${reviewInput.findings}`,
1057
- `- Recommendation: ${reviewInput.recommendation}`,
1058
- "",
1059
- ].join("\n");
1060
- const artifact = await writeArtifact(root, "reviews", fileName, content);
1061
- await appendEvent(root, {
1062
- type: "REVIEW_RECORDED",
1063
- taskId: reviewInput.task,
1064
- actor: reviewInput.role,
1065
- summary: `Review recorded: ${reviewInput.result}`,
1066
- artifacts: [artifact],
1067
- metadata: { result: reviewInput.result, severity: reviewInput.severity },
1068
- });
1069
- if (reviewInput.result === "block" && reviewInput.severity === "critical") {
1070
- await notifyWorkflowLifecycle({
1071
- root,
1072
- taskId: reviewInput.task,
1073
- kind: "critical_blocking_review",
1074
- actor: reviewInput.role,
1075
- detail: reviewInput.findings,
1076
- artifact,
1077
- idempotencyKey: `critical_blocking_review:${reviewInput.task}:${artifact}`,
1078
- }, notificationTransports);
1079
- }
1080
- return { artifact, content };
1081
- }
1082
968
  function riskReviewSteps(impactAreas) {
1083
969
  const roleByImpact = {
1084
970
  security: "security",
@@ -1147,117 +1033,6 @@ async function writeTaskGraphBatchArtifact(root, batch) {
1147
1033
  "",
1148
1034
  ].join("\n"));
1149
1035
  }
1150
- export async function listReviews(taskId, root = process.cwd()) {
1151
- return filterEvents("REVIEW_RECORDED", taskId, root);
1152
- }
1153
- export async function addEvidence(input, root = process.cwd()) {
1154
- const evidenceInput = removeUndefined({
1155
- ...input,
1156
- type: normalizeEvidenceType(input.type) ?? input.type,
1157
- });
1158
- const workspace = await loadWorkspace(root);
1159
- validateEvidenceInput(evidenceInput, workspace.roleIds);
1160
- if (typeof evidenceInput.path === "string" &&
1161
- !(await exists(path.resolve(root, evidenceInput.path)))) {
1162
- throw new Error(`evidence path does not exist: ${evidenceInput.path}`);
1163
- }
1164
- const fileName = `${evidenceInput.task}-${Date.now()}-${randomUUID()}-${evidenceInput.type}.md`;
1165
- const content = [
1166
- `# Evidence ${evidenceInput.task}: ${evidenceInput.type}`,
1167
- "",
1168
- `- Role: ${evidenceInput.role}`,
1169
- `- Summary: ${evidenceInput.summary}`,
1170
- `- Path: ${evidenceInput.path ?? "not applicable"}`,
1171
- `- Command: ${evidenceInput.command ?? "not applicable"}`,
1172
- `- Exit code: ${evidenceInput.exitCode ?? "not applicable"}`,
1173
- "",
1174
- ].join("\n");
1175
- const artifact = await writeArtifact(root, "evidence", fileName, content);
1176
- await appendEvent(root, {
1177
- type: "EVIDENCE_ADDED",
1178
- taskId: evidenceInput.task,
1179
- actor: evidenceInput.role,
1180
- summary: evidenceInput.summary,
1181
- artifacts: [artifact],
1182
- metadata: { type: evidenceInput.type },
1183
- });
1184
- return { artifact, content };
1185
- }
1186
- export async function listEvidence(taskId, root = process.cwd()) {
1187
- return filterEvents("EVIDENCE_ADDED", taskId, root);
1188
- }
1189
- export async function getWorkflowSummary(root = process.cwd()) {
1190
- const workspace = await loadWorkspace(root);
1191
- const events = await readEvents(root);
1192
- return {
1193
- tasks: workspace.tasks,
1194
- locks: workspace.locks,
1195
- reviews: events.filter((event) => event.type === "REVIEW_RECORDED"),
1196
- evidence: events.filter((event) => event.type === "EVIDENCE_ADDED"),
1197
- blockers: workspace.tasks.filter((task) => task.status === "blocked"),
1198
- eventCount: events.length,
1199
- };
1200
- }
1201
- export async function generatePullRequestSummary(taskId, root = process.cwd()) {
1202
- const workspace = await loadWorkspace(root);
1203
- const task = workspace.tasks.find((candidate) => candidate.id === taskId);
1204
- if (!task) {
1205
- throw new Error(`unknown task: ${taskId}`);
1206
- }
1207
- const events = await readEvents(root);
1208
- const taskEvents = events.filter((event) => event.taskId === taskId);
1209
- const handoffs = taskEvents.filter((event) => event.type === "HANDOFF_READY");
1210
- const reviews = taskEvents.filter((event) => event.type === "REVIEW_RECORDED");
1211
- const evidence = taskEvents.filter((event) => event.type === "EVIDENCE_ADDED");
1212
- const gates = taskEvents.filter((event) => event.type === "GATE_PASSED" || event.type === "GATE_BLOCKED");
1213
- const locks = workspace.locks.filter((lock) => lock.taskId === taskId);
1214
- return {
1215
- task,
1216
- handoffs,
1217
- reviews,
1218
- evidence,
1219
- gates,
1220
- locks,
1221
- risks: task.risks ?? [],
1222
- rollout: "Use the project release process after required gates pass.",
1223
- rollback: "Revert the related commit or disable the changed behavior.",
1224
- review: analyzePullRequestReview({ task, reviews, evidence, gates, locks }),
1225
- };
1226
- }
1227
- export async function generatePlaywrightTestPlan(taskId, root = process.cwd()) {
1228
- const workspace = await loadWorkspace(root);
1229
- const task = workspace.tasks.find((candidate) => candidate.id === taskId);
1230
- if (!task) {
1231
- throw new Error(`unknown task: ${taskId}`);
1232
- }
1233
- const criteria = task.acceptanceCriteria && task.acceptanceCriteria.length > 0
1234
- ? task.acceptanceCriteria
1235
- : [task.goal ?? task.title];
1236
- return {
1237
- taskId: task.id,
1238
- title: task.title,
1239
- targetUser: "end user",
1240
- scenarios: criteria.map((criterion, index) => ({
1241
- name: `Scenario ${index + 1}: ${criterion}`,
1242
- source: criterion,
1243
- pageObject: `${toPascalCase(task.id)}Page`,
1244
- selectors: [
1245
- "Use role, label, and test-id selectors before CSS selectors",
1246
- "Keep selectors in page object methods",
1247
- ],
1248
- assertions: [
1249
- `Verify user-visible outcome: ${criterion}`,
1250
- "Verify no blocking error state is shown",
1251
- ],
1252
- evidence: ["screenshot", "trace-on-failure"],
1253
- })),
1254
- fixtures: ["authenticated user when the flow requires identity"],
1255
- notes: [
1256
- "Prefer mobile-first viewport coverage before desktop expansion",
1257
- "Attach trace, screenshot, or video when failures occur",
1258
- ],
1259
- };
1260
- }
1261
1036
  export async function getWorkflowConfig(root = process.cwd()) {
1262
1037
  return readJson(resolveWorkflowPath(root, FILES.config), {});
1263
1038
  }
@@ -1491,224 +1266,6 @@ async function createBudgetFallbackProposal(taskId, budget, root) {
1491
1266
  artifact,
1492
1267
  };
1493
1268
  }
1494
- async function getApprovalRecord(id, root) {
1495
- const artifact = approvalArtifactForId(id);
1496
- if (!(await exists(path.join(root, artifact)))) {
1497
- throw new Error(`unknown approval: ${id}`);
1498
- }
1499
- const events = await readEvents(root);
1500
- return approvalRecordForArtifact(artifact, events);
1501
- }
1502
- async function findStoredApprovalForProposal(proposal, root) {
1503
- if (!proposal.artifact) {
1504
- return undefined;
1505
- }
1506
- const events = await readEvents(root);
1507
- return approvalRecordForArtifact(proposal.artifact, events);
1508
- }
1509
- async function recordApprovalDecision(input, status, root) {
1510
- const current = await getApprovalRecord(input.id, root);
1511
- const requested = approvalEventsForArtifact(await readEvents(root), current.artifact).find((event) => event.type === "BUDGET_ESCALATION_REQUESTED");
1512
- await appendEvent(root, removeUndefined({
1513
- type: status === "approved"
1514
- ? "BUDGET_ESCALATION_APPROVED"
1515
- : "BUDGET_ESCALATION_REJECTED",
1516
- taskId: current.taskId,
1517
- actor: "parent",
1518
- summary: `Budget escalation ${status}: ${input.id}`,
1519
- artifacts: [current.artifact],
1520
- metadata: {
1521
- approvalId: input.id,
1522
- proposal: requested?.metadata.proposal,
1523
- approver: input.approver,
1524
- rationale: input.rationale,
1525
- },
1526
- }));
1527
- return getApprovalRecord(input.id, root);
1528
- }
1529
- function approvalRecordForArtifact(artifact, events) {
1530
- const id = approvalIdForArtifact(artifact);
1531
- const related = approvalEventsForArtifact(events, artifact);
1532
- const requested = related.find((event) => event.type === "BUDGET_ESCALATION_REQUESTED");
1533
- const decision = [...related]
1534
- .reverse()
1535
- .find((event) => ["BUDGET_ESCALATION_APPROVED", "BUDGET_ESCALATION_REJECTED"].includes(event.type));
1536
- const status = decisionStatus(decision);
1537
- return removeUndefined({
1538
- id,
1539
- taskId: String(requested?.taskId ?? decision?.taskId ?? "") || undefined,
1540
- artifact,
1541
- status,
1542
- summary: requested?.summary ?? `Approval proposal ${id}`,
1543
- requestedAt: requested?.timestamp,
1544
- decidedAt: decision?.timestamp,
1545
- approver: typeof decision?.metadata.approver === "string"
1546
- ? decision.metadata.approver
1547
- : undefined,
1548
- rationale: typeof decision?.metadata.rationale === "string"
1549
- ? decision.metadata.rationale
1550
- : undefined,
1551
- });
1552
- }
1553
- function gateApprovalRecords(events) {
1554
- return events
1555
- .filter((event) => event.type === "GATE_APPROVED")
1556
- .map((event) => {
1557
- const runId = typeof event.metadata.runId === "string" ? event.metadata.runId : "run";
1558
- const gateId = typeof event.metadata.gateId === "string"
1559
- ? event.metadata.gateId
1560
- : "gate";
1561
- return removeUndefined({
1562
- id: `gate-${runId}-${gateId}`.replace(/[^a-zA-Z0-9._-]/g, "-"),
1563
- taskId: event.taskId ?? undefined,
1564
- artifact: event.artifacts?.[0] ?? "events.jsonl",
1565
- status: "approved",
1566
- summary: event.summary,
1567
- requestedAt: event.timestamp,
1568
- decidedAt: typeof event.metadata.approvedAt === "string"
1569
- ? event.metadata.approvedAt
1570
- : event.timestamp,
1571
- approver: typeof event.metadata.approver === "string"
1572
- ? event.metadata.approver
1573
- : undefined,
1574
- rationale: typeof event.metadata.rationale === "string"
1575
- ? event.metadata.rationale
1576
- : undefined,
1577
- });
1578
- });
1579
- }
1580
- function gateIdForPhase(run, phaseIndex) {
1581
- const phase = run.phases[phaseIndex]?.phase;
1582
- const sequenceIndex = AUTONOMOUS_PHASE_SEQUENCE.findIndex((candidate) => candidate.phase === phase);
1583
- const next = AUTONOMOUS_PHASE_SEQUENCE[sequenceIndex + 1]?.phase ?? "end";
1584
- return `${phase}->${next}`;
1585
- }
1586
- function approvalEventsForArtifact(events, artifact) {
1587
- return events.filter((event) => event.artifacts?.includes(artifact));
1588
- }
1589
- function decisionStatus(decision) {
1590
- if (decision?.type === "BUDGET_ESCALATION_APPROVED") {
1591
- return "approved";
1592
- }
1593
- if (decision?.type === "BUDGET_ESCALATION_REJECTED") {
1594
- return "rejected";
1595
- }
1596
- return "pending";
1597
- }
1598
- function approvalArtifactForId(id) {
1599
- if (!/^[a-zA-Z0-9._-]+$/.test(id)) {
1600
- throw new Error(`invalid approval id: ${id}`);
1601
- }
1602
- return path.join(".agent-workflow", "approvals", `${id}.md`);
1603
- }
1604
- function approvalIdForArtifact(artifact) {
1605
- return path.basename(artifact, ".md");
1606
- }
1607
- export async function addPlaywrightEvidence(input, root = process.cwd()) {
1608
- return addEvidence(removeUndefined({
1609
- task: input.task,
1610
- role: "qa",
1611
- type: input.kind,
1612
- summary: input.summary,
1613
- path: input.path,
1614
- command: input.runId,
1615
- }), root);
1616
- }
1617
- function aggregateUsage(key, records) {
1618
- const inputTokens = records.reduce((sum, record) => sum + record.inputTokens, 0);
1619
- const outputTokens = records.reduce((sum, record) => sum + record.outputTokens, 0);
1620
- const estimatedCostUsd = records.reduce((sum, record) => sum + record.estimatedCostUsd, 0);
1621
- return {
1622
- key,
1623
- requests: records.length,
1624
- inputTokens,
1625
- outputTokens,
1626
- totalTokens: inputTokens + outputTokens,
1627
- estimatedCostUsd,
1628
- };
1629
- }
1630
- function aggregateUsageBy(records, keyFor) {
1631
- const groups = new Map();
1632
- for (const record of records) {
1633
- const key = keyFor(record);
1634
- groups.set(key, [...(groups.get(key) ?? []), record]);
1635
- }
1636
- return [...groups.entries()].map(([key, group]) => aggregateUsage(key, group));
1637
- }
1638
- function budgetViolations(scope, usage, budget) {
1639
- return [
1640
- budgetViolation(scope, "requests", usage.requests, budget.maxRequests),
1641
- budgetViolation(scope, "inputTokens", usage.inputTokens, budget.maxInputTokens),
1642
- budgetViolation(scope, "outputTokens", usage.outputTokens, budget.maxOutputTokens),
1643
- budgetViolation(scope, "totalTokens", usage.totalTokens, budget.maxTotalTokens),
1644
- budgetViolation(scope, "estimatedCostUsd", usage.estimatedCostUsd, budget.maxEstimatedCostUsd),
1645
- ].filter((violation) => Boolean(violation));
1646
- }
1647
- function budgetViolation(scope, metric, actual, limit) {
1648
- if (limit === undefined || actual <= limit) {
1649
- return undefined;
1650
- }
1651
- return {
1652
- scope,
1653
- metric,
1654
- actual,
1655
- limit,
1656
- };
1657
- }
1658
- function projectUsageCost(usage, ownerRole, estimatedCostUsd) {
1659
- const byRole = [...usage.byRole];
1660
- const roleIndex = byRole.findIndex((entry) => entry.key === ownerRole);
1661
- if (roleIndex >= 0) {
1662
- const roleUsage = byRole[roleIndex] ?? emptyUsageBreakdown(ownerRole);
1663
- byRole[roleIndex] = addEstimatedCost(roleUsage, estimatedCostUsd);
1664
- }
1665
- else {
1666
- byRole.push(addEstimatedCost(emptyUsageBreakdown(ownerRole), estimatedCostUsd));
1667
- }
1668
- return {
1669
- ...usage,
1670
- totals: addEstimatedCost(usage.totals, estimatedCostUsd),
1671
- byRole,
1672
- };
1673
- }
1674
- function addEstimatedCost(usage, estimatedCostUsd) {
1675
- return {
1676
- ...usage,
1677
- estimatedCostUsd: usage.estimatedCostUsd + estimatedCostUsd,
1678
- };
1679
- }
1680
- function emptyUsageBreakdown(key) {
1681
- return {
1682
- key,
1683
- requests: 0,
1684
- inputTokens: 0,
1685
- outputTokens: 0,
1686
- totalTokens: 0,
1687
- estimatedCostUsd: 0,
1688
- };
1689
- }
1690
- function budgetEstimateWarningsForBudgets(taskId, ownerRole, usage, budgets) {
1691
- const warnings = [];
1692
- pushCostWarning(warnings, "defaults", usage.totals, budgets?.defaults);
1693
- pushCostWarning(warnings, `task:${taskId}`, usage.totals, budgets?.byTask?.[taskId]);
1694
- pushCostWarning(warnings, `role:${ownerRole}`, usage.byRole.find((roleUsage) => roleUsage.key === ownerRole) ??
1695
- emptyUsageBreakdown(ownerRole), budgets?.byRole?.[ownerRole]);
1696
- return warnings;
1697
- }
1698
- function pushCostWarning(warnings, scope, usage, budget) {
1699
- const thresholdPercent = 80;
1700
- const limit = budget?.maxEstimatedCostUsd;
1701
- if (limit !== undefined &&
1702
- usage.estimatedCostUsd >= limit * (thresholdPercent / 100)) {
1703
- warnings.push({
1704
- scope,
1705
- metric: "estimatedCostUsd",
1706
- actual: usage.estimatedCostUsd,
1707
- limit,
1708
- thresholdPercent,
1709
- });
1710
- }
1711
- }
1712
1269
  async function writeBudgetEscalationProposal(root, taskId, proposal) {
1713
1270
  const content = [
1714
1271
  `# Budget Escalation Proposal: ${taskId}`,
@@ -1779,14 +1336,4 @@ function flowRequirementLines(requirements) {
1779
1336
  function createLockId() {
1780
1337
  return `lock-${Date.now()}-${randomUUID()}`;
1781
1338
  }
1782
- function removeUndefined(value) {
1783
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
1784
- }
1785
- function toPascalCase(value) {
1786
- return value
1787
- .split(/[^a-zA-Z0-9]+/)
1788
- .filter(Boolean)
1789
- .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
1790
- .join("");
1791
- }
1792
1339
  //# sourceMappingURL=workflow-services.js.map