@jterrats/open-orchestra 0.5.5 → 1.0.1
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.
- package/AGENTS.md +9 -8
- package/CLAUDE.md +13 -11
- package/README.md +78 -11
- package/dist/assets/web-console.js +203 -36
- package/dist/automation-evidence.d.ts +23 -0
- package/dist/automation-evidence.js +97 -0
- package/dist/automation-evidence.js.map +1 -0
- package/dist/autonomous-run-state.d.ts +4 -1
- package/dist/autonomous-run-state.js +8 -2
- package/dist/autonomous-run-state.js.map +1 -1
- package/dist/autonomous-run-store.d.ts +3 -1
- package/dist/autonomous-run-store.js +9 -3
- package/dist/autonomous-run-store.js.map +1 -1
- package/dist/autonomous-workflow-constants.js +5 -1
- package/dist/autonomous-workflow-constants.js.map +1 -1
- package/dist/benchmark.d.ts +4 -1
- package/dist/benchmark.js +140 -19
- package/dist/benchmark.js.map +1 -1
- package/dist/cli.js +88 -2
- package/dist/cli.js.map +1 -1
- package/dist/collaboration-flows.js +5 -19
- package/dist/collaboration-flows.js.map +1 -1
- package/dist/collection-utils.d.ts +3 -0
- package/dist/collection-utils.js +10 -0
- package/dist/collection-utils.js.map +1 -0
- package/dist/command-manifest.d.ts +12 -1
- package/dist/command-manifest.js +218 -10
- package/dist/command-manifest.js.map +1 -1
- package/dist/commands.d.ts +14 -6
- package/dist/commands.js +78 -28
- package/dist/commands.js.map +1 -1
- package/dist/config-migrations.d.ts +24 -0
- package/dist/config-migrations.js +102 -0
- package/dist/config-migrations.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +26 -0
- package/dist/constants.js.map +1 -1
- package/dist/cursor-canvas.d.ts +20 -0
- package/dist/cursor-canvas.js +119 -0
- package/dist/cursor-canvas.js.map +1 -0
- package/dist/dashboard-commands.d.ts +2 -0
- package/dist/dashboard-commands.js +14 -0
- package/dist/dashboard-commands.js.map +1 -0
- package/dist/defaults.d.ts +13 -0
- package/dist/defaults.js +13 -0
- package/dist/defaults.js.map +1 -1
- package/dist/delegation-decision.js +23 -8
- package/dist/delegation-decision.js.map +1 -1
- package/dist/delivery-commands.js +5 -0
- package/dist/delivery-commands.js.map +1 -1
- package/dist/delivery-dashboard-charts.d.ts +4 -0
- package/dist/delivery-dashboard-charts.js +156 -0
- package/dist/delivery-dashboard-charts.js.map +1 -0
- package/dist/delivery-dashboard-html.d.ts +2 -0
- package/dist/delivery-dashboard-html.js +115 -0
- package/dist/delivery-dashboard-html.js.map +1 -0
- package/dist/delivery-dashboard-types.d.ts +78 -0
- package/dist/delivery-dashboard-types.js +2 -0
- package/dist/delivery-dashboard-types.js.map +1 -0
- package/dist/delivery-dashboard.d.ts +8 -0
- package/dist/delivery-dashboard.js +124 -0
- package/dist/delivery-dashboard.js.map +1 -0
- package/dist/doc-sync.d.ts +25 -0
- package/dist/doc-sync.js +79 -0
- package/dist/doc-sync.js.map +1 -0
- package/dist/effort-classification.d.ts +7 -0
- package/dist/effort-classification.js +72 -0
- package/dist/effort-classification.js.map +1 -0
- package/dist/extension-commands.d.ts +3 -0
- package/dist/extension-commands.js +40 -0
- package/dist/extension-commands.js.map +1 -0
- package/dist/extensions.d.ts +22 -0
- package/dist/extensions.js +126 -0
- package/dist/extensions.js.map +1 -0
- package/dist/gemini-provider.d.ts +3 -6
- package/dist/gemini-provider.js +8 -17
- package/dist/gemini-provider.js.map +1 -1
- package/dist/github.d.ts +2 -0
- package/dist/github.js +15 -3
- package/dist/github.js.map +1 -1
- package/dist/health-checks.js +51 -0
- package/dist/health-checks.js.map +1 -1
- package/dist/lucid-story-map.d.ts +73 -0
- package/dist/lucid-story-map.js +112 -0
- package/dist/lucid-story-map.js.map +1 -0
- package/dist/mcp-integrations.d.ts +19 -0
- package/dist/mcp-integrations.js +58 -0
- package/dist/mcp-integrations.js.map +1 -0
- package/dist/mcp-tool-adapter.d.ts +21 -0
- package/dist/mcp-tool-adapter.js +56 -0
- package/dist/mcp-tool-adapter.js.map +1 -0
- package/dist/metrics-commands.js +47 -13
- package/dist/metrics-commands.js.map +1 -1
- package/dist/model-commands.d.ts +5 -0
- package/dist/model-commands.js +95 -1
- package/dist/model-commands.js.map +1 -1
- package/dist/model-providers.d.ts +5 -12
- package/dist/model-providers.js +30 -43
- package/dist/model-providers.js.map +1 -1
- package/dist/network-policy.d.ts +2 -0
- package/dist/network-policy.js +6 -0
- package/dist/network-policy.js.map +1 -0
- package/dist/ollama-provider.d.ts +3 -6
- package/dist/ollama-provider.js +7 -16
- package/dist/ollama-provider.js.map +1 -1
- package/dist/package-update-check.d.ts +19 -0
- package/dist/package-update-check.js +24 -0
- package/dist/package-update-check.js.map +1 -1
- package/dist/phase-executor.d.ts +1 -0
- package/dist/phase-executor.js +401 -9
- package/dist/phase-executor.js.map +1 -1
- package/dist/phase-playbooks.d.ts +18 -1
- package/dist/phase-playbooks.js +146 -2
- package/dist/phase-playbooks.js.map +1 -1
- package/dist/planning-commands.d.ts +1 -0
- package/dist/planning-commands.js +36 -36
- package/dist/planning-commands.js.map +1 -1
- package/dist/policy-commands.d.ts +2 -0
- package/dist/policy-commands.js +29 -0
- package/dist/policy-commands.js.map +1 -0
- package/dist/policy-defaults.d.ts +2 -0
- package/dist/policy-defaults.js +42 -0
- package/dist/policy-defaults.js.map +1 -0
- package/dist/policy.d.ts +20 -0
- package/dist/policy.js +155 -0
- package/dist/policy.js.map +1 -0
- package/dist/project-detection.js +9 -7
- package/dist/project-detection.js.map +1 -1
- package/dist/prompt-registry-update.d.ts +2 -0
- package/dist/prompt-registry-update.js +5 -1
- package/dist/prompt-registry-update.js.map +1 -1
- package/dist/prompt-registry-validation.d.ts +3 -0
- package/dist/prompt-registry-validation.js +61 -21
- package/dist/prompt-registry-validation.js.map +1 -1
- package/dist/provider-utils.d.ts +11 -0
- package/dist/provider-utils.js +14 -0
- package/dist/provider-utils.js.map +1 -1
- package/dist/qa-commands.d.ts +2 -0
- package/dist/qa-commands.js +18 -0
- package/dist/qa-commands.js.map +1 -0
- package/dist/qa-coverage.d.ts +24 -0
- package/dist/qa-coverage.js +189 -0
- package/dist/qa-coverage.js.map +1 -0
- package/dist/qa-readiness.d.ts +5 -0
- package/dist/qa-readiness.js +26 -0
- package/dist/qa-readiness.js.map +1 -0
- package/dist/refresh-generated.d.ts +32 -0
- package/dist/refresh-generated.js +180 -0
- package/dist/refresh-generated.js.map +1 -0
- package/dist/release-candidate.d.ts +9 -1
- package/dist/release-candidate.js +52 -1
- package/dist/release-candidate.js.map +1 -1
- package/dist/release-commands.js +161 -8
- package/dist/release-commands.js.map +1 -1
- package/dist/release-readiness.d.ts +33 -0
- package/dist/release-readiness.js +187 -3
- package/dist/release-readiness.js.map +1 -1
- package/dist/runtime-adapters.d.ts +2 -1
- package/dist/runtime-adapters.js +16 -0
- package/dist/runtime-adapters.js.map +1 -1
- package/dist/runtime-bootstrap.js +1 -1
- package/dist/runtime-bootstrap.js.map +1 -1
- package/dist/runtime-commands.d.ts +2 -0
- package/dist/runtime-commands.js +85 -3
- package/dist/runtime-commands.js.map +1 -1
- package/dist/runtime-execution-adapters.js +40 -0
- package/dist/runtime-execution-adapters.js.map +1 -1
- package/dist/runtime-execution-renderer.d.ts +3 -2
- package/dist/runtime-execution-renderer.js +46 -8
- package/dist/runtime-execution-renderer.js.map +1 -1
- package/dist/runtime-execution.d.ts +8 -2
- package/dist/runtime-execution.js +109 -11
- package/dist/runtime-execution.js.map +1 -1
- package/dist/runtime-guardrails.d.ts +26 -0
- package/dist/runtime-guardrails.js +168 -0
- package/dist/runtime-guardrails.js.map +1 -0
- package/dist/setup-agents-import.js +5 -3
- package/dist/setup-agents-import.js.map +1 -1
- package/dist/skills-catalog.js +1 -0
- package/dist/skills-catalog.js.map +1 -1
- package/dist/skills-commands.d.ts +5 -0
- package/dist/skills-commands.js +79 -2
- package/dist/skills-commands.js.map +1 -1
- package/dist/skills-memory.d.ts +36 -2
- package/dist/skills-memory.js +165 -6
- package/dist/skills-memory.js.map +1 -1
- package/dist/skills-planning.js +9 -22
- package/dist/skills-planning.js.map +1 -1
- package/dist/skills-render.js +2 -4
- package/dist/skills-render.js.map +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +1 -1
- package/dist/skills.js.map +1 -1
- package/dist/sprint-commands.js +2 -1
- package/dist/sprint-commands.js.map +1 -1
- package/dist/subagent-protocol.js +3 -5
- package/dist/subagent-protocol.js.map +1 -1
- package/dist/support-commands.d.ts +2 -0
- package/dist/support-commands.js +18 -0
- package/dist/support-commands.js.map +1 -0
- package/dist/support-diagnostics.d.ts +49 -0
- package/dist/support-diagnostics.js +86 -0
- package/dist/support-diagnostics.js.map +1 -0
- package/dist/task-graph-commands.js +6 -14
- package/dist/task-graph-commands.js.map +1 -1
- package/dist/task-text.d.ts +8 -0
- package/dist/task-text.js +18 -0
- package/dist/task-text.js.map +1 -0
- package/dist/telemetry-redaction.js +8 -1
- package/dist/telemetry-redaction.js.map +1 -1
- package/dist/tool-commands.d.ts +3 -0
- package/dist/tool-commands.js +62 -0
- package/dist/tool-commands.js.map +1 -1
- package/dist/tracker-adapters.d.ts +71 -0
- package/dist/tracker-adapters.js +186 -0
- package/dist/tracker-adapters.js.map +1 -0
- package/dist/tracker-commands.d.ts +2 -0
- package/dist/tracker-commands.js +119 -0
- package/dist/tracker-commands.js.map +1 -0
- package/dist/types/metrics.d.ts +25 -1
- package/dist/types/model-config.d.ts +51 -4
- package/dist/types/runtime.d.ts +83 -0
- package/dist/types/skills.d.ts +2 -0
- package/dist/types/tasks.d.ts +10 -0
- package/dist/types/workflow-run.d.ts +35 -0
- package/dist/types.d.ts +12 -4
- package/dist/types.js.map +1 -1
- package/dist/upgrade-commands.js +13 -4
- package/dist/upgrade-commands.js.map +1 -1
- package/dist/validation.js +2 -2
- package/dist/validation.js.map +1 -1
- package/dist/visual-validation.d.ts +81 -0
- package/dist/visual-validation.js +290 -0
- package/dist/visual-validation.js.map +1 -0
- package/dist/web-action-security.d.ts +11 -0
- package/dist/web-action-security.js +45 -0
- package/dist/web-action-security.js.map +1 -0
- package/dist/web-api-read-routes.js +115 -3
- package/dist/web-api-read-routes.js.map +1 -1
- package/dist/web-api.js +507 -5
- package/dist/web-api.js.map +1 -1
- package/dist/web-artifacts.d.ts +55 -0
- package/dist/web-artifacts.js +222 -0
- package/dist/web-artifacts.js.map +1 -0
- package/dist/web-console/assets/index-C9lx-V42.css +1 -0
- package/dist/web-console/assets/index-M3S0g1GK.js +11 -0
- package/dist/web-console/index.html +13 -0
- package/dist/web-console.js +9 -3
- package/dist/web-console.js.map +1 -1
- package/dist/web-recovery.d.ts +30 -0
- package/dist/web-recovery.js +163 -0
- package/dist/web-recovery.js.map +1 -0
- package/dist/web-workflow-progress.d.ts +41 -0
- package/dist/web-workflow-progress.js +114 -0
- package/dist/web-workflow-progress.js.map +1 -0
- package/dist/workflow-approval-service.d.ts +2 -1
- package/dist/workflow-approval-service.js +83 -4
- package/dist/workflow-approval-service.js.map +1 -1
- package/dist/workflow-approval-utils.js +13 -3
- package/dist/workflow-approval-utils.js.map +1 -1
- package/dist/workflow-event-query.d.ts +2 -0
- package/dist/workflow-event-query.js +6 -0
- package/dist/workflow-event-query.js.map +1 -0
- package/dist/workflow-evidence-service.js +18 -9
- package/dist/workflow-evidence-service.js.map +1 -1
- package/dist/workflow-gates.d.ts +2 -0
- package/dist/workflow-gates.js +103 -0
- package/dist/workflow-gates.js.map +1 -1
- package/dist/workflow-markdown.d.ts +6 -0
- package/dist/workflow-markdown.js +25 -0
- package/dist/workflow-markdown.js.map +1 -0
- package/dist/workflow-phase-planner.d.ts +19 -0
- package/dist/workflow-phase-planner.js +133 -0
- package/dist/workflow-phase-planner.js.map +1 -0
- package/dist/workflow-run-commands.d.ts +1 -0
- package/dist/workflow-run-commands.js +247 -20
- package/dist/workflow-run-commands.js.map +1 -1
- package/dist/workflow-services.d.ts +21 -12
- package/dist/workflow-services.js +376 -260
- package/dist/workflow-services.js.map +1 -1
- package/dist/workflow-task-service.d.ts +11 -0
- package/dist/workflow-task-service.js +242 -0
- package/dist/workflow-task-service.js.map +1 -0
- package/dist/workflow-templates.js +2 -14
- package/dist/workflow-templates.js.map +1 -1
- package/dist/workspace-validator.js +133 -5
- package/dist/workspace-validator.js.map +1 -1
- package/dist/workspace.js +10 -2
- package/dist/workspace.js.map +1 -1
- package/docs/adoption-guide.md +147 -0
- package/docs/autonomous-workflow.md +146 -28
- package/docs/benchmark.md +17 -9
- package/docs/command-contracts.md +18 -1
- package/docs/core-command-surface.md +62 -13
- package/docs/end-to-end-demo.md +1 -0
- package/docs/extension-contracts.md +83 -0
- package/docs/orchestra-mvp.md +86 -3
- package/docs/persona-workflows.md +32 -0
- package/docs/release-test-matrix.md +42 -0
- package/docs/runtime-adapters.md +113 -0
- package/docs/runtime-llm-flow.md +13 -0
- package/docs/setup-agents-applicability-review.md +173 -0
- package/docs/skill-loading-strategy.md +1 -0
- package/docs/source-of-truth-and-agent-learning.md +14 -0
- package/docs/traceability-flow.md +5 -1
- package/docs/tracker-adapter-contract.md +10 -1
- package/docs/web-console-qa.md +35 -0
- package/package.json +12 -6
- package/rules/development-engineering.mdc +66 -0
- package/skills/doc-sync/SKILL.md +2 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { uniqueStrings } from "./collection-utils.js";
|
|
3
4
|
import { FILES } from "./constants.js";
|
|
4
5
|
import { removeUndefined } from "./command-utils.js";
|
|
5
|
-
import { ensureDir, readJson, resolveWorkflowPath, updateJsonFile, writeJson, } from "./fs-utils.js";
|
|
6
|
-
import { assertProviderAllowed, assertVendorFallbackAllowed, createModelProvider, defaultApiKeyFileEnv, defaultBaseUrlEnv, FakeModelProvider, InMemoryModelProviderRegistry, providerEnvFromCredentialConfig, summarizeConfiguredProviders, } from "./model-providers.js";
|
|
6
|
+
import { ensureDir, exists, readJson, resolveWorkflowPath, updateJsonFile, writeJson, } from "./fs-utils.js";
|
|
7
|
+
import { assertProviderAllowed, assertVendorFallbackAllowed, createModelProvider, defaultApiKeyEnv, defaultApiKeyFileEnv, defaultBaseUrlEnv, FakeModelProvider, InMemoryModelProviderRegistry, providerEnvFromCredentialConfig, summarizeConfiguredProviders, } from "./model-providers.js";
|
|
7
8
|
import { appendEvent, loadWorkspace, readEvents, writeArtifact, } from "./workspace.js";
|
|
8
9
|
import { validateReadiness } from "./validation.js";
|
|
9
10
|
import { getWorkflowGate } from "./workflow-gates.js";
|
|
@@ -13,143 +14,20 @@ import { getTelemetryConsent } from "./telemetry.js";
|
|
|
13
14
|
import { latestDelegationDecision } from "./delegation-decision.js";
|
|
14
15
|
import { listAutonomousRuns } from "./autonomous-workflow.js";
|
|
15
16
|
import { readEstimate } from "./benchmark.js";
|
|
17
|
+
import { qaPlanReadinessBlocker } from "./qa-readiness.js";
|
|
16
18
|
import { handoffFlowRequirements, recommendCollaborationFlow, } from "./collaboration-flows.js";
|
|
17
19
|
import { queryMemory, recordMemoryEvent } from "./memory.js";
|
|
18
20
|
import { applyContextBudget, DEFAULT_CONTEXT_TOKEN_BUDGET, } from "./context-budget.js";
|
|
19
21
|
import { selectWorkflowTemplates } from "./workflow-templates.js";
|
|
20
22
|
import { findStoredApprovalForProposal } from "./workflow-approval-service.js";
|
|
23
|
+
import { listWorkflowEventsByType } from "./workflow-event-query.js";
|
|
21
24
|
import { aggregateUsage, aggregateUsageBy, budgetEstimateWarningsForBudgets, budgetViolations, emptyUsageBreakdown, projectUsageCost, } from "./workflow-budget-utils.js";
|
|
25
|
+
import { listTasks as listWorkflowTasks, updateTask as updateWorkflowTask, } from "./workflow-task-service.js";
|
|
22
26
|
import { SIZING_LABELS } from "./types.js";
|
|
27
|
+
export { addTask, archiveTask, deleteTask, getWorkflowStatus, listTasks, updateTask, } from "./workflow-task-service.js";
|
|
23
28
|
export { addEvidence, addPlaywrightEvidence, listEvidence, listReviews, recordReview, } from "./workflow-evidence-service.js";
|
|
24
29
|
export { generatePlaywrightTestPlan, generatePullRequestSummary, getWorkflowSummary, } from "./workflow-summary-service.js";
|
|
25
|
-
export { approveApproval, approveWorkflowGate, listApprovals, rejectApproval, showApproval, } from "./workflow-approval-service.js";
|
|
26
|
-
export async function getWorkflowStatus(root = process.cwd()) {
|
|
27
|
-
const workspace = await loadWorkspace(root);
|
|
28
|
-
const config = await readJson(resolveWorkflowPath(root, FILES.config), {});
|
|
29
|
-
const counts = Object.create(null);
|
|
30
|
-
const blocked = [];
|
|
31
|
-
for (const task of workspace.tasks) {
|
|
32
|
-
counts[task.status] = (counts[task.status] ?? 0) + 1;
|
|
33
|
-
if (task.status === "blocked") {
|
|
34
|
-
blocked.push({
|
|
35
|
-
id: task.id,
|
|
36
|
-
title: task.title,
|
|
37
|
-
reason: task.blockedReason ?? "not specified",
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
...(config.mode ? { mode: config.mode } : {}),
|
|
43
|
-
tasks: {
|
|
44
|
-
total: workspace.tasks.length,
|
|
45
|
-
byStatus: counts,
|
|
46
|
-
blocked,
|
|
47
|
-
},
|
|
48
|
-
locks: {
|
|
49
|
-
total: workspace.locks.length,
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
export async function addTask(input, root = process.cwd()) {
|
|
54
|
-
const workspace = await loadWorkspace(root);
|
|
55
|
-
if (!workspace.roleIds.has(input.ownerRole)) {
|
|
56
|
-
throw new Error(`unknown owner role: ${input.ownerRole}`);
|
|
57
|
-
}
|
|
58
|
-
const now = new Date().toISOString();
|
|
59
|
-
const task = removeUndefined({
|
|
60
|
-
...input,
|
|
61
|
-
status: "pending",
|
|
62
|
-
createdAt: now,
|
|
63
|
-
updatedAt: now,
|
|
64
|
-
});
|
|
65
|
-
await mutateTasks(workspace.base, (tasks) => {
|
|
66
|
-
if (tasks.some((candidate) => candidate.id === input.id)) {
|
|
67
|
-
throw new Error(`task already exists: ${input.id}`);
|
|
68
|
-
}
|
|
69
|
-
return [...tasks, task];
|
|
70
|
-
});
|
|
71
|
-
await appendEvent(root, {
|
|
72
|
-
type: "TASK_ASSIGNED",
|
|
73
|
-
taskId: input.id,
|
|
74
|
-
actor: "parent",
|
|
75
|
-
summary: `Task assigned to ${input.ownerRole}`,
|
|
76
|
-
metadata: { title: input.title, ownerRole: input.ownerRole },
|
|
77
|
-
});
|
|
78
|
-
return task;
|
|
79
|
-
}
|
|
80
|
-
export async function listTasks(root = process.cwd()) {
|
|
81
|
-
const workspace = await loadWorkspace(root);
|
|
82
|
-
return workspace.tasks;
|
|
83
|
-
}
|
|
84
|
-
export async function deleteTask(taskId, options = {}, root = process.cwd()) {
|
|
85
|
-
const workspace = await loadWorkspace(root);
|
|
86
|
-
let deleted;
|
|
87
|
-
await mutateTasks(workspace.base, (tasks) => {
|
|
88
|
-
const taskIndex = tasks.findIndex((task) => task.id === taskId);
|
|
89
|
-
if (taskIndex < 0) {
|
|
90
|
-
throw new Error(`unknown task: ${taskId}`);
|
|
91
|
-
}
|
|
92
|
-
const current = tasks[taskIndex];
|
|
93
|
-
if (!current) {
|
|
94
|
-
throw new Error(`unknown task: ${taskId}`);
|
|
95
|
-
}
|
|
96
|
-
assertTaskCanBeRemoved(current, tasks, options.force ?? false);
|
|
97
|
-
deleted = current;
|
|
98
|
-
return tasks.filter((task) => task.id !== taskId);
|
|
99
|
-
});
|
|
100
|
-
if (!deleted) {
|
|
101
|
-
throw new Error(`unknown task: ${taskId}`);
|
|
102
|
-
}
|
|
103
|
-
await appendEvent(root, {
|
|
104
|
-
type: "TASK_DELETED",
|
|
105
|
-
taskId,
|
|
106
|
-
actor: "parent",
|
|
107
|
-
summary: `Task deleted: ${taskId}`,
|
|
108
|
-
metadata: {
|
|
109
|
-
title: deleted.title,
|
|
110
|
-
status: deleted.status,
|
|
111
|
-
forced: Boolean(options.force),
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
return deleted;
|
|
115
|
-
}
|
|
116
|
-
export async function archiveTask(taskId, options = {}, root = process.cwd()) {
|
|
117
|
-
const workspace = await loadWorkspace(root);
|
|
118
|
-
let archived;
|
|
119
|
-
await mutateTasks(workspace.base, (tasks) => {
|
|
120
|
-
const taskIndex = tasks.findIndex((task) => task.id === taskId);
|
|
121
|
-
if (taskIndex < 0) {
|
|
122
|
-
throw new Error(`unknown task: ${taskId}`);
|
|
123
|
-
}
|
|
124
|
-
const current = tasks[taskIndex];
|
|
125
|
-
if (!current) {
|
|
126
|
-
throw new Error(`unknown task: ${taskId}`);
|
|
127
|
-
}
|
|
128
|
-
assertTaskCanBeRemoved(current, tasks, options.force ?? false);
|
|
129
|
-
archived = {
|
|
130
|
-
...current,
|
|
131
|
-
status: "archived",
|
|
132
|
-
updatedAt: new Date().toISOString(),
|
|
133
|
-
};
|
|
134
|
-
const nextTasks = [...tasks];
|
|
135
|
-
nextTasks[taskIndex] = archived;
|
|
136
|
-
return nextTasks;
|
|
137
|
-
});
|
|
138
|
-
if (!archived) {
|
|
139
|
-
throw new Error(`unknown task: ${taskId}`);
|
|
140
|
-
}
|
|
141
|
-
await appendEvent(root, {
|
|
142
|
-
type: "TASK_ARCHIVED",
|
|
143
|
-
taskId,
|
|
144
|
-
actor: "parent",
|
|
145
|
-
summary: `Task archived: ${taskId}`,
|
|
146
|
-
metadata: {
|
|
147
|
-
title: archived.title,
|
|
148
|
-
forced: Boolean(options.force),
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
return archived;
|
|
152
|
-
}
|
|
30
|
+
export { approveApproval, approveWorkflowGate, listApprovals, recordWorkflowGateDecision, rejectApproval, showApproval, } from "./workflow-approval-service.js";
|
|
153
31
|
export async function listRoles(root = process.cwd()) {
|
|
154
32
|
const workspace = await loadWorkspace(root);
|
|
155
33
|
return workspace.roles;
|
|
@@ -197,72 +75,6 @@ export async function validatePreRun(taskId, options = {}, root = process.cwd())
|
|
|
197
75
|
bypassDecisionArtifact,
|
|
198
76
|
});
|
|
199
77
|
}
|
|
200
|
-
export async function updateTask(input, root = process.cwd()) {
|
|
201
|
-
const workspace = await loadWorkspace(root);
|
|
202
|
-
if (input.ownerRole && !workspace.roleIds.has(input.ownerRole)) {
|
|
203
|
-
throw new Error(`unknown owner role: ${input.ownerRole}`);
|
|
204
|
-
}
|
|
205
|
-
let updated;
|
|
206
|
-
let changedFields = [];
|
|
207
|
-
await mutateTasks(workspace.base, (tasks) => {
|
|
208
|
-
const taskIndex = tasks.findIndex((task) => task.id === input.id);
|
|
209
|
-
if (taskIndex < 0) {
|
|
210
|
-
throw new Error(`unknown task: ${input.id}`);
|
|
211
|
-
}
|
|
212
|
-
const current = tasks[taskIndex];
|
|
213
|
-
if (!current) {
|
|
214
|
-
throw new Error(`unknown task: ${input.id}`);
|
|
215
|
-
}
|
|
216
|
-
const next = { ...current };
|
|
217
|
-
const changes = new Set();
|
|
218
|
-
applyTaskUpdate(next, current, input, changes);
|
|
219
|
-
changedFields = [...changes].sort();
|
|
220
|
-
updated = { ...next, updatedAt: new Date().toISOString() };
|
|
221
|
-
const nextTasks = [...tasks];
|
|
222
|
-
nextTasks[taskIndex] = updated;
|
|
223
|
-
return nextTasks;
|
|
224
|
-
});
|
|
225
|
-
if (!updated) {
|
|
226
|
-
throw new Error(`unknown task: ${input.id}`);
|
|
227
|
-
}
|
|
228
|
-
await appendEvent(root, {
|
|
229
|
-
type: "TASK_UPDATED",
|
|
230
|
-
taskId: input.id,
|
|
231
|
-
actor: "parent",
|
|
232
|
-
summary: `Task updated: ${input.id}`,
|
|
233
|
-
metadata: { status: updated.status, changedFields },
|
|
234
|
-
});
|
|
235
|
-
return updated;
|
|
236
|
-
}
|
|
237
|
-
function applyTaskUpdate(next, current, input, changedFields) {
|
|
238
|
-
setTaskField(next, current, "title", input.title, changedFields);
|
|
239
|
-
setTaskField(next, current, "ownerRole", input.ownerRole, changedFields);
|
|
240
|
-
setTaskField(next, current, "goal", input.goal, changedFields);
|
|
241
|
-
setTaskField(next, current, "scope", input.scope, changedFields);
|
|
242
|
-
setTaskField(next, current, "paths", input.paths, changedFields);
|
|
243
|
-
setTaskField(next, current, "testStrategy", input.testStrategy, changedFields);
|
|
244
|
-
setTaskField(next, current, "status", input.status, changedFields);
|
|
245
|
-
setTaskField(next, current, "blockedReason", input.blockedReason, changedFields);
|
|
246
|
-
appendTaskField(next, "acceptanceCriteria", input.acceptanceCriteria, changedFields);
|
|
247
|
-
appendTaskField(next, "assumptions", input.assumptions, changedFields);
|
|
248
|
-
appendTaskField(next, "risks", input.risks, changedFields);
|
|
249
|
-
}
|
|
250
|
-
function setTaskField(next, current, key, value, changedFields) {
|
|
251
|
-
if (value === undefined) {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
if (JSON.stringify(current[key]) !== JSON.stringify(value)) {
|
|
255
|
-
changedFields.add(String(key));
|
|
256
|
-
}
|
|
257
|
-
next[key] = value;
|
|
258
|
-
}
|
|
259
|
-
function appendTaskField(next, key, values, changedFields) {
|
|
260
|
-
if (!values || values.length === 0) {
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
next[key] = [...(next[key] ?? []), ...values];
|
|
264
|
-
changedFields.add(key);
|
|
265
|
-
}
|
|
266
78
|
export async function checkTaskDependencies(taskId, root = process.cwd()) {
|
|
267
79
|
const workspace = await loadWorkspace(root);
|
|
268
80
|
const task = workspace.tasks.find((candidate) => candidate.id === taskId);
|
|
@@ -289,7 +101,7 @@ export async function checkTaskDependencies(taskId, root = process.cwd()) {
|
|
|
289
101
|
};
|
|
290
102
|
}
|
|
291
103
|
export async function generateTaskGraphPlan(root = process.cwd()) {
|
|
292
|
-
const tasks = await
|
|
104
|
+
const tasks = await listWorkflowTasks(root);
|
|
293
105
|
const locks = await listLocks(root);
|
|
294
106
|
const ready = [];
|
|
295
107
|
const blocked = [];
|
|
@@ -311,6 +123,14 @@ export async function generateTaskGraphPlan(root = process.cwd()) {
|
|
|
311
123
|
}
|
|
312
124
|
const dependencies = await checkTaskDependencies(task.id, root);
|
|
313
125
|
if (dependencies.isSatisfied) {
|
|
126
|
+
const qaBlocker = qaPlanReadinessBlocker(task);
|
|
127
|
+
if (qaBlocker) {
|
|
128
|
+
blocked.push({
|
|
129
|
+
...item,
|
|
130
|
+
incomplete: [qaBlocker],
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
314
134
|
const taskLocks = locksForTask(task, locks);
|
|
315
135
|
if (taskLocks.length > 0) {
|
|
316
136
|
locked.push({
|
|
@@ -557,7 +377,7 @@ export async function createHandoff(input, root = process.cwd()) {
|
|
|
557
377
|
metadata: { to: input.to, updateOwner: Boolean(input.updateOwner) },
|
|
558
378
|
});
|
|
559
379
|
if (input.updateOwner) {
|
|
560
|
-
await
|
|
380
|
+
await updateWorkflowTask({ id: input.task, ownerRole: input.to }, root);
|
|
561
381
|
}
|
|
562
382
|
return { artifact, content };
|
|
563
383
|
}
|
|
@@ -587,8 +407,9 @@ export async function recordDecision(input, root = process.cwd()) {
|
|
|
587
407
|
"",
|
|
588
408
|
].join("\n");
|
|
589
409
|
const artifact = await writeArtifact(root, "decisions", fileName, content);
|
|
590
|
-
// Extract sizing/points from decision text
|
|
591
|
-
//
|
|
410
|
+
// Extract sizing/points from decision text for estimation decisions.
|
|
411
|
+
// Architect format: "<sizing> [N points]" e.g. "m [5 points]" or just "m".
|
|
412
|
+
// Developer format: "N points" for implementation effort.
|
|
592
413
|
const sizingMetadata = {};
|
|
593
414
|
if (input.owner === "architect") {
|
|
594
415
|
const sizingMatch = /^\s*(xs|s|m|l|xl)\b/i.exec(input.decision);
|
|
@@ -603,6 +424,12 @@ export async function recordDecision(input, root = process.cwd()) {
|
|
|
603
424
|
}
|
|
604
425
|
}
|
|
605
426
|
}
|
|
427
|
+
else if (input.owner === "developer") {
|
|
428
|
+
const pointsMatch = /\b(\d+)\s*points?\b/i.exec(input.decision);
|
|
429
|
+
if (pointsMatch) {
|
|
430
|
+
sizingMetadata.points = parseInt(pointsMatch[1], 10);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
606
433
|
await appendEvent(root, {
|
|
607
434
|
type: "DECISION_RECORDED",
|
|
608
435
|
taskId: input.task,
|
|
@@ -612,13 +439,15 @@ export async function recordDecision(input, root = process.cwd()) {
|
|
|
612
439
|
metadata: {
|
|
613
440
|
status: input.status,
|
|
614
441
|
title: input.title,
|
|
442
|
+
decision: input.decision,
|
|
443
|
+
consequences: input.consequences,
|
|
615
444
|
...sizingMetadata,
|
|
616
445
|
},
|
|
617
446
|
});
|
|
618
447
|
return { ...input, artifact };
|
|
619
448
|
}
|
|
620
449
|
export async function listDecisions(taskId, root = process.cwd()) {
|
|
621
|
-
return
|
|
450
|
+
return listWorkflowEventsByType("DECISION_RECORDED", taskId, root);
|
|
622
451
|
}
|
|
623
452
|
export async function getTaskContext(taskId, root = process.cwd(), options = {}) {
|
|
624
453
|
const workspace = await loadWorkspace(root);
|
|
@@ -1027,6 +856,146 @@ export async function getWorkflowConfig(root = process.cwd()) {
|
|
|
1027
856
|
export async function listConfiguredModelProviders(root = process.cwd()) {
|
|
1028
857
|
return summarizeConfiguredProviders(await getWorkflowConfig(root));
|
|
1029
858
|
}
|
|
859
|
+
export async function listProviderRuntimeProfiles(root = process.cwd()) {
|
|
860
|
+
const config = await getWorkflowConfig(root);
|
|
861
|
+
const activeProfile = config.providers?.activeProfile;
|
|
862
|
+
return Object.entries(config.providers?.profiles ?? {})
|
|
863
|
+
.map(([name, profile]) => removeUndefined({
|
|
864
|
+
name,
|
|
865
|
+
active: name === activeProfile,
|
|
866
|
+
description: profile.description,
|
|
867
|
+
roles: Object.keys(profile.byRole ?? {}).sort(),
|
|
868
|
+
defaultProvider: profile.defaults?.provider,
|
|
869
|
+
defaultModel: profile.defaults?.model,
|
|
870
|
+
requiredEnv: profile.requiredEnv ?? [],
|
|
871
|
+
}))
|
|
872
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
873
|
+
}
|
|
874
|
+
export async function saveProviderRuntimeProfile(input, root = process.cwd()) {
|
|
875
|
+
const workspace = await loadWorkspace(root);
|
|
876
|
+
validateProfileName(input.name);
|
|
877
|
+
for (const role of input.roles) {
|
|
878
|
+
if (!workspace.roleIds.has(role)) {
|
|
879
|
+
throw new Error(`unknown role: ${role}`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (input.roles.length === 0) {
|
|
883
|
+
throw new Error("at least one role is required");
|
|
884
|
+
}
|
|
885
|
+
validateProfileRouting(input.routing);
|
|
886
|
+
const configPath = resolveWorkflowPath(root, FILES.config);
|
|
887
|
+
const config = await readJson(configPath, {});
|
|
888
|
+
const profile = removeUndefined({
|
|
889
|
+
description: input.description,
|
|
890
|
+
byRole: Object.fromEntries(input.roles.map((role) => [role, input.routing])),
|
|
891
|
+
requiredEnv: input.requiredEnv,
|
|
892
|
+
});
|
|
893
|
+
config.providers = {
|
|
894
|
+
defaults: config.providers?.defaults,
|
|
895
|
+
byRole: config.providers?.byRole ?? {},
|
|
896
|
+
profiles: {
|
|
897
|
+
...(config.providers?.profiles ?? {}),
|
|
898
|
+
[input.name]: profile,
|
|
899
|
+
},
|
|
900
|
+
...(input.activate ? { activeProfile: input.name } : {}),
|
|
901
|
+
};
|
|
902
|
+
await writeJson(configPath, config);
|
|
903
|
+
if (input.apply || input.activate) {
|
|
904
|
+
await applyProviderRuntimeProfile(input.name, root);
|
|
905
|
+
}
|
|
906
|
+
await appendEvent(root, {
|
|
907
|
+
type: "MODEL_PROFILE_SAVED",
|
|
908
|
+
actor: "parent",
|
|
909
|
+
summary: `Provider runtime profile saved: ${input.name}`,
|
|
910
|
+
metadata: {
|
|
911
|
+
profile: input.name,
|
|
912
|
+
roles: input.roles,
|
|
913
|
+
provider: input.routing.provider,
|
|
914
|
+
model: input.routing.model,
|
|
915
|
+
},
|
|
916
|
+
});
|
|
917
|
+
const summaries = await listProviderRuntimeProfiles(root);
|
|
918
|
+
return summaries.find((summary) => summary.name === input.name);
|
|
919
|
+
}
|
|
920
|
+
export async function applyProviderRuntimeProfile(name, root = process.cwd()) {
|
|
921
|
+
validateProfileName(name);
|
|
922
|
+
const workspace = await loadWorkspace(root);
|
|
923
|
+
const configPath = resolveWorkflowPath(root, FILES.config);
|
|
924
|
+
const config = await readJson(configPath, {});
|
|
925
|
+
const profile = config.providers?.profiles?.[name];
|
|
926
|
+
if (!profile) {
|
|
927
|
+
throw new Error(`unknown provider runtime profile: ${name}`);
|
|
928
|
+
}
|
|
929
|
+
for (const role of Object.keys(profile.byRole ?? {})) {
|
|
930
|
+
if (!workspace.roleIds.has(role)) {
|
|
931
|
+
throw new Error(`profile ${name} references unknown role: ${role}`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
for (const routing of Object.values(profile.byRole ?? {})) {
|
|
935
|
+
validateProfileRouting(routing);
|
|
936
|
+
}
|
|
937
|
+
if (profile.defaults) {
|
|
938
|
+
validateProfileRouting(profile.defaults);
|
|
939
|
+
}
|
|
940
|
+
config.providers = {
|
|
941
|
+
defaults: profile.defaults ?? config.providers.defaults,
|
|
942
|
+
byRole: {
|
|
943
|
+
...(config.providers.byRole ?? {}),
|
|
944
|
+
...(profile.byRole ?? {}),
|
|
945
|
+
},
|
|
946
|
+
profiles: config.providers.profiles ?? {},
|
|
947
|
+
activeProfile: name,
|
|
948
|
+
};
|
|
949
|
+
if (profile.budgets) {
|
|
950
|
+
config.budgets = profile.budgets;
|
|
951
|
+
}
|
|
952
|
+
if (profile.providerPolicy) {
|
|
953
|
+
config.providerPolicy = {
|
|
954
|
+
...(config.providerPolicy ?? {}),
|
|
955
|
+
...profile.providerPolicy,
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
await writeJson(configPath, config);
|
|
959
|
+
await appendEvent(root, {
|
|
960
|
+
type: "MODEL_PROFILE_APPLIED",
|
|
961
|
+
actor: "parent",
|
|
962
|
+
summary: `Provider runtime profile applied: ${name}`,
|
|
963
|
+
metadata: { profile: name, roles: Object.keys(profile.byRole ?? {}) },
|
|
964
|
+
});
|
|
965
|
+
const summaries = await listProviderRuntimeProfiles(root);
|
|
966
|
+
return summaries.find((summary) => summary.name === name);
|
|
967
|
+
}
|
|
968
|
+
export async function smokeProviderRuntimeProfile(name, root = process.cwd(), env = process.env) {
|
|
969
|
+
validateProfileName(name);
|
|
970
|
+
const config = await getWorkflowConfig(root);
|
|
971
|
+
const profile = config.providers?.profiles?.[name];
|
|
972
|
+
if (!profile) {
|
|
973
|
+
throw new Error(`unknown provider runtime profile: ${name}`);
|
|
974
|
+
}
|
|
975
|
+
const checks = [];
|
|
976
|
+
for (const envName of profile.requiredEnv ?? []) {
|
|
977
|
+
checks.push({
|
|
978
|
+
scope: `env:${envName}`,
|
|
979
|
+
provider: "environment",
|
|
980
|
+
model: "not-applicable",
|
|
981
|
+
status: env[envName]?.trim() ? "pass" : "fail",
|
|
982
|
+
detail: env[envName]?.trim()
|
|
983
|
+
? "environment variable is configured"
|
|
984
|
+
: `missing required environment variable ${envName}`,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
const routes = Object.entries(profile.byRole ?? {}).map(([role, routing]) => [`role:${role}`, routing]);
|
|
988
|
+
if (profile.defaults)
|
|
989
|
+
routes.unshift(["defaults", profile.defaults]);
|
|
990
|
+
for (const [scope, routing] of routes) {
|
|
991
|
+
checks.push(...(await smokeRouting(scope, routing, config, root, env)));
|
|
992
|
+
}
|
|
993
|
+
return {
|
|
994
|
+
profile: name,
|
|
995
|
+
passed: checks.every((check) => check.status === "pass"),
|
|
996
|
+
checks,
|
|
997
|
+
};
|
|
998
|
+
}
|
|
1030
999
|
export async function completeWithProviderFallback(routing, prompt, { failingProviders = [], root = process.cwd(), taskId, role = "parent", jsonMode = false, providerMode = "fake", } = {}) {
|
|
1031
1000
|
const registry = new InMemoryModelProviderRegistry();
|
|
1032
1001
|
const config = await getWorkflowConfig(root);
|
|
@@ -1049,44 +1018,125 @@ export async function completeWithProviderFallback(routing, prompt, { failingPro
|
|
|
1049
1018
|
if (providerMode === "real" && index > 0) {
|
|
1050
1019
|
assertVendorFallbackAllowed(providerId, config.providerPolicy);
|
|
1051
1020
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1021
|
+
let attempts = 0;
|
|
1022
|
+
const maxAttempts = Math.max(1, 1 + routing.retries);
|
|
1023
|
+
while (attempts < maxAttempts) {
|
|
1024
|
+
attempts += 1;
|
|
1025
|
+
try {
|
|
1026
|
+
const provider = registry.get(providerId);
|
|
1027
|
+
const response = await provider.complete({
|
|
1028
|
+
model: routing.model,
|
|
1029
|
+
jsonMode,
|
|
1030
|
+
timeoutMs: routing.timeoutMs,
|
|
1031
|
+
messages: [{ role: "user", content: prompt }],
|
|
1032
|
+
});
|
|
1033
|
+
if (index > 0) {
|
|
1034
|
+
await appendEvent(root, removeUndefined({
|
|
1035
|
+
type: "MODEL_FALLBACK_USED",
|
|
1036
|
+
actor: role,
|
|
1037
|
+
taskId,
|
|
1038
|
+
summary: `Fallback provider used: ${providerId}`,
|
|
1039
|
+
metadata: { provider: providerId, failedProviders },
|
|
1040
|
+
}));
|
|
1041
|
+
}
|
|
1042
|
+
return {
|
|
1043
|
+
provider: providerId,
|
|
1044
|
+
model: routing.model,
|
|
1045
|
+
response,
|
|
1046
|
+
fallbackUsed: index > 0,
|
|
1047
|
+
failedProviders,
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
catch (error) {
|
|
1051
|
+
const failure = providerFailureFromError(providerId, error, attempts);
|
|
1052
|
+
if (failure.code === "timeout") {
|
|
1053
|
+
throw error;
|
|
1054
|
+
}
|
|
1055
|
+
if (failure.retryable && attempts < maxAttempts) {
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
failedProviders.push(failure);
|
|
1059
|
+
break;
|
|
1068
1060
|
}
|
|
1069
|
-
return {
|
|
1070
|
-
provider: providerId,
|
|
1071
|
-
model: routing.model,
|
|
1072
|
-
response,
|
|
1073
|
-
fallbackUsed: index > 0,
|
|
1074
|
-
failedProviders,
|
|
1075
|
-
};
|
|
1076
1061
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1062
|
+
}
|
|
1063
|
+
throw new ProviderFallbackError(failedProviders);
|
|
1064
|
+
}
|
|
1065
|
+
export class ProviderFallbackError extends Error {
|
|
1066
|
+
failures;
|
|
1067
|
+
constructor(failures) {
|
|
1068
|
+
super(providerFailureSummary(failures));
|
|
1069
|
+
this.name = "ProviderFallbackError";
|
|
1070
|
+
this.failures = failures;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
export function providerFailuresFromError(error) {
|
|
1074
|
+
return error instanceof ProviderFallbackError ? error.failures : [];
|
|
1075
|
+
}
|
|
1076
|
+
function providerFailureSummary(failures) {
|
|
1077
|
+
const providers = failures.map((failure) => failure.provider).join(", ");
|
|
1078
|
+
const details = failures
|
|
1079
|
+
.map((failure) => `${failure.provider}: ${failure.reason} (${failure.code}, attempts=${failure.attempts})`)
|
|
1080
|
+
.join("; ");
|
|
1081
|
+
return `all providers failed: ${providers}${details ? ` (${details})` : ""}`;
|
|
1082
|
+
}
|
|
1083
|
+
function providerFailureFromError(providerId, error, attempts) {
|
|
1084
|
+
const code = providerFailureCode(error);
|
|
1085
|
+
return {
|
|
1086
|
+
provider: providerId,
|
|
1087
|
+
code,
|
|
1088
|
+
reason: sanitizeProviderError(error),
|
|
1089
|
+
retryable: code === "provider_error",
|
|
1090
|
+
attempts,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
function providerFailureCode(error) {
|
|
1094
|
+
const message = errorMessage(error).toLowerCase();
|
|
1095
|
+
if (isProviderTimeoutError(error))
|
|
1096
|
+
return "timeout";
|
|
1097
|
+
if (/permission|denied|forbidden|unauthorized/.test(message)) {
|
|
1098
|
+
return "permission_denied";
|
|
1099
|
+
}
|
|
1100
|
+
if (/policy|not allowed|blocked/.test(message))
|
|
1101
|
+
return "policy_blocked";
|
|
1102
|
+
return "provider_error";
|
|
1103
|
+
}
|
|
1104
|
+
function sanitizeProviderError(error) {
|
|
1105
|
+
const messages = [];
|
|
1106
|
+
if (error instanceof Error) {
|
|
1107
|
+
messages.push(error.message);
|
|
1108
|
+
const cause = error.cause;
|
|
1109
|
+
if (cause instanceof Error && cause.message !== error.message) {
|
|
1110
|
+
messages.push(cause.message);
|
|
1111
|
+
}
|
|
1112
|
+
else if (isErrorCauseRecord(cause)) {
|
|
1113
|
+
const code = typeof cause.code === "string" && cause.code.trim()
|
|
1114
|
+
? cause.code
|
|
1115
|
+
: undefined;
|
|
1116
|
+
const hostname = typeof cause.hostname === "string" && cause.hostname.trim()
|
|
1117
|
+
? cause.hostname
|
|
1118
|
+
: undefined;
|
|
1119
|
+
if (code || hostname) {
|
|
1120
|
+
messages.push([code, hostname].filter(Boolean).join(" "));
|
|
1080
1121
|
}
|
|
1081
|
-
failedProviders.push({
|
|
1082
|
-
provider: providerId,
|
|
1083
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
1084
|
-
});
|
|
1085
1122
|
}
|
|
1086
1123
|
}
|
|
1087
|
-
|
|
1088
|
-
.
|
|
1089
|
-
|
|
1124
|
+
else {
|
|
1125
|
+
messages.push(String(error));
|
|
1126
|
+
}
|
|
1127
|
+
return redactProviderError(messages.filter(Boolean).join(": "));
|
|
1128
|
+
}
|
|
1129
|
+
function errorMessage(error) {
|
|
1130
|
+
return error instanceof Error ? error.message : String(error);
|
|
1131
|
+
}
|
|
1132
|
+
function isErrorCauseRecord(value) {
|
|
1133
|
+
return typeof value === "object" && value !== null;
|
|
1134
|
+
}
|
|
1135
|
+
function redactProviderError(message) {
|
|
1136
|
+
return message
|
|
1137
|
+
.replace(/(x-goog-api-key|api[_-]?key|authorization)\s*[:=]\s*\S+/gi, "$1=[REDACTED]")
|
|
1138
|
+
.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/g, "Bearer [REDACTED]")
|
|
1139
|
+
.replace(/AIza[0-9A-Za-z_-]{20,}/g, "[REDACTED_API_KEY]");
|
|
1090
1140
|
}
|
|
1091
1141
|
function isProviderTimeoutError(error) {
|
|
1092
1142
|
if (!(error instanceof Error)) {
|
|
@@ -1109,6 +1159,10 @@ export async function setRoleModelProvider(role, routing, root = process.cwd())
|
|
|
1109
1159
|
...(config.providers.byRole ?? {}),
|
|
1110
1160
|
[role]: routing,
|
|
1111
1161
|
},
|
|
1162
|
+
profiles: config.providers.profiles ?? {},
|
|
1163
|
+
...(config.providers.activeProfile
|
|
1164
|
+
? { activeProfile: config.providers.activeProfile }
|
|
1165
|
+
: {}),
|
|
1112
1166
|
};
|
|
1113
1167
|
await writeJson(configPath, config);
|
|
1114
1168
|
await appendEvent(root, {
|
|
@@ -1145,6 +1199,10 @@ export async function connectModelProvider(input, root = process.cwd()) {
|
|
|
1145
1199
|
config.providers = {
|
|
1146
1200
|
defaults: config.providers?.defaults ?? routing,
|
|
1147
1201
|
byRole,
|
|
1202
|
+
profiles: config.providers?.profiles ?? {},
|
|
1203
|
+
...(config.providers?.activeProfile
|
|
1204
|
+
? { activeProfile: config.providers.activeProfile }
|
|
1205
|
+
: {}),
|
|
1148
1206
|
};
|
|
1149
1207
|
const credential = removeUndefined({
|
|
1150
1208
|
apiKeyFile: input.apiKeyFile,
|
|
@@ -1165,7 +1223,7 @@ export async function connectModelProvider(input, root = process.cwd()) {
|
|
|
1165
1223
|
if (input.allowDirectProviderApi) {
|
|
1166
1224
|
config.providerPolicy = {
|
|
1167
1225
|
...(config.providerPolicy ?? {}),
|
|
1168
|
-
allowedProviders:
|
|
1226
|
+
allowedProviders: uniqueStrings([
|
|
1169
1227
|
...(config.providerPolicy?.allowedProviders ?? []),
|
|
1170
1228
|
input.provider,
|
|
1171
1229
|
]),
|
|
@@ -1177,6 +1235,9 @@ export async function connectModelProvider(input, root = process.cwd()) {
|
|
|
1177
1235
|
delegation: {
|
|
1178
1236
|
mode: config.runtimePolicy?.delegation?.mode ?? "runtime-native",
|
|
1179
1237
|
allowDirectProviderApi: true,
|
|
1238
|
+
...(config.runtimePolicy?.delegation?.guardrails
|
|
1239
|
+
? { guardrails: config.runtimePolicy.delegation.guardrails }
|
|
1240
|
+
: {}),
|
|
1180
1241
|
},
|
|
1181
1242
|
};
|
|
1182
1243
|
}
|
|
@@ -1201,8 +1262,85 @@ export async function connectModelProvider(input, root = process.cwd()) {
|
|
|
1201
1262
|
allowDirectProviderApi: input.allowDirectProviderApi,
|
|
1202
1263
|
});
|
|
1203
1264
|
}
|
|
1204
|
-
function
|
|
1205
|
-
|
|
1265
|
+
function validateProfileName(name) {
|
|
1266
|
+
if (!/^[a-z0-9][a-z0-9._-]{1,62}$/i.test(name)) {
|
|
1267
|
+
throw new Error("profile name must be 2-63 characters and contain only letters, numbers, dots, underscores, or dashes");
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
function validateProfileRouting(routing) {
|
|
1271
|
+
if (!routing.provider?.trim()) {
|
|
1272
|
+
throw new Error("profile routing requires provider");
|
|
1273
|
+
}
|
|
1274
|
+
if (!routing.model?.trim()) {
|
|
1275
|
+
throw new Error("profile routing requires model");
|
|
1276
|
+
}
|
|
1277
|
+
if (new Set([routing.provider, ...(routing.fallbacks ?? [])]).size !==
|
|
1278
|
+
[routing.provider, ...(routing.fallbacks ?? [])].length) {
|
|
1279
|
+
throw new Error("profile routing fallback chain contains duplicates");
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
async function smokeRouting(scope, routing, config, root, env) {
|
|
1283
|
+
const checks = [];
|
|
1284
|
+
const providerIds = [routing.provider, ...(routing.fallbacks ?? [])];
|
|
1285
|
+
for (const [index, providerId] of providerIds.entries()) {
|
|
1286
|
+
const providerScope = index === 0 ? scope : `${scope}:fallback:${index}`;
|
|
1287
|
+
const policyError = providerPolicySmokeError(providerId, config, index);
|
|
1288
|
+
if (policyError) {
|
|
1289
|
+
checks.push({
|
|
1290
|
+
scope: providerScope,
|
|
1291
|
+
provider: providerId,
|
|
1292
|
+
model: routing.model,
|
|
1293
|
+
status: "fail",
|
|
1294
|
+
detail: policyError,
|
|
1295
|
+
});
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
const credentialError = await providerCredentialSmokeError(providerId, config.providerCredentials?.byProvider?.[providerId], root, env);
|
|
1299
|
+
checks.push({
|
|
1300
|
+
scope: providerScope,
|
|
1301
|
+
provider: providerId,
|
|
1302
|
+
model: routing.model,
|
|
1303
|
+
status: credentialError ? "fail" : "pass",
|
|
1304
|
+
detail: credentialError ?? "provider configuration is present",
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
return checks;
|
|
1308
|
+
}
|
|
1309
|
+
function providerPolicySmokeError(providerId, config, fallbackIndex) {
|
|
1310
|
+
try {
|
|
1311
|
+
assertProviderAllowed(providerId, config.providerPolicy);
|
|
1312
|
+
if (fallbackIndex > 0) {
|
|
1313
|
+
assertVendorFallbackAllowed(providerId, config.providerPolicy);
|
|
1314
|
+
}
|
|
1315
|
+
return undefined;
|
|
1316
|
+
}
|
|
1317
|
+
catch (error) {
|
|
1318
|
+
return error instanceof Error ? error.message : String(error);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
async function providerCredentialSmokeError(providerId, credential, root, env) {
|
|
1322
|
+
if (providerId === "none" ||
|
|
1323
|
+
providerId === "fake" ||
|
|
1324
|
+
providerId === "ollama") {
|
|
1325
|
+
return undefined;
|
|
1326
|
+
}
|
|
1327
|
+
const apiKeyEnv = credential?.apiKeyEnv ?? defaultApiKeyEnv(providerId);
|
|
1328
|
+
const apiKeyFileEnv = credential?.apiKeyFileEnv ?? defaultApiKeyFileEnv(providerId);
|
|
1329
|
+
const effectiveEnv = providerEnvFromCredentialConfig(providerId, credential, env);
|
|
1330
|
+
if (apiKeyEnv && effectiveEnv[apiKeyEnv]?.trim()) {
|
|
1331
|
+
return undefined;
|
|
1332
|
+
}
|
|
1333
|
+
if (apiKeyFileEnv && effectiveEnv[apiKeyFileEnv]?.trim()) {
|
|
1334
|
+
const keyFile = effectiveEnv[apiKeyFileEnv].trim();
|
|
1335
|
+
if (!path.isAbsolute(keyFile)) {
|
|
1336
|
+
return `${apiKeyFileEnv} must reference an absolute path`;
|
|
1337
|
+
}
|
|
1338
|
+
if (!(await exists(keyFile))) {
|
|
1339
|
+
return `${apiKeyFileEnv} references an unreadable secret file`;
|
|
1340
|
+
}
|
|
1341
|
+
return undefined;
|
|
1342
|
+
}
|
|
1343
|
+
return `${apiKeyEnv ?? "provider API key"} or ${apiKeyFileEnv ?? "provider API key file"} is required`;
|
|
1206
1344
|
}
|
|
1207
1345
|
export async function recordModelProvenance(input, root = process.cwd()) {
|
|
1208
1346
|
const workspace = await loadWorkspace(root);
|
|
@@ -1224,7 +1362,7 @@ export async function recordModelProvenance(input, root = process.cwd()) {
|
|
|
1224
1362
|
return record;
|
|
1225
1363
|
}
|
|
1226
1364
|
export async function listModelProvenance(taskId, root = process.cwd()) {
|
|
1227
|
-
const events = await
|
|
1365
|
+
const events = await listWorkflowEventsByType("MODEL_PROVENANCE_RECORDED", taskId, root);
|
|
1228
1366
|
return events.map((event) => event.metadata);
|
|
1229
1367
|
}
|
|
1230
1368
|
export async function getUsageReport(taskId, root = process.cwd()) {
|
|
@@ -1378,31 +1516,9 @@ async function writeBudgetEscalationProposal(root, taskId, proposal) {
|
|
|
1378
1516
|
].join("\n");
|
|
1379
1517
|
return writeArtifact(root, "approvals", `${taskId}-budget-fallback.md`, content);
|
|
1380
1518
|
}
|
|
1381
|
-
async function filterEvents(type, taskId, root) {
|
|
1382
|
-
const events = (await readEvents(root)).filter((event) => event.type === type);
|
|
1383
|
-
return taskId ? events.filter((event) => event.taskId === taskId) : events;
|
|
1384
|
-
}
|
|
1385
|
-
async function mutateTasks(base, update) {
|
|
1386
|
-
return updateJsonFile(path.join(base, FILES.tasks), [], update);
|
|
1387
|
-
}
|
|
1388
1519
|
async function mutateLocks(base, update) {
|
|
1389
1520
|
return updateJsonFile(path.join(base, FILES.locks), [], update);
|
|
1390
1521
|
}
|
|
1391
|
-
function assertTaskCanBeRemoved(task, tasks, isForced) {
|
|
1392
|
-
if (!isForced &&
|
|
1393
|
-
(task.status === "in_progress" || task.status === "blocked")) {
|
|
1394
|
-
throw new Error(`task ${task.id} is ${task.status}; use --force to remove it`);
|
|
1395
|
-
}
|
|
1396
|
-
const dependent = tasks.find((candidate) => candidate.id !== task.id &&
|
|
1397
|
-
!isTerminalTaskStatus(candidate.status) &&
|
|
1398
|
-
candidate.dependencies.includes(task.id));
|
|
1399
|
-
if (dependent) {
|
|
1400
|
-
throw new Error(`task ${task.id} is a dependency of active task ${dependent.id}`);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
function isTerminalTaskStatus(status) {
|
|
1404
|
-
return ["done", "canceled", "rejected", "archived"].includes(status);
|
|
1405
|
-
}
|
|
1406
1522
|
function flowRequirementLines(requirements) {
|
|
1407
1523
|
return requirements.length > 0
|
|
1408
1524
|
? requirements.map((requirement) => `- ${requirement}`)
|