cool-workflow 0.1.78
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/.claude-plugin/plugin.json +20 -0
- package/.codex-plugin/mcp.json +10 -0
- package/.codex-plugin/plugin.json +38 -0
- package/.mcp.json +10 -0
- package/LICENSE +24 -0
- package/README.md +638 -0
- package/apps/architecture-review/app.json +51 -0
- package/apps/architecture-review/workflow.js +116 -0
- package/apps/end-to-end-golden-path/app.json +30 -0
- package/apps/end-to-end-golden-path/workflow.js +33 -0
- package/apps/pr-review-fix-ci/app.json +59 -0
- package/apps/pr-review-fix-ci/workflow.js +90 -0
- package/apps/release-cut/app.json +54 -0
- package/apps/release-cut/workflow.js +82 -0
- package/apps/research-synthesis/app.json +50 -0
- package/apps/research-synthesis/workflow.js +76 -0
- package/apps/workflow-app-framework-demo/app.json +29 -0
- package/apps/workflow-app-framework-demo/workflow.js +44 -0
- package/dist/agent-config.js +223 -0
- package/dist/candidate-scoring.js +715 -0
- package/dist/capability-core.js +630 -0
- package/dist/capability-dispatcher.js +86 -0
- package/dist/capability-registry.js +523 -0
- package/dist/cli.js +1276 -0
- package/dist/collaboration.js +727 -0
- package/dist/commit.js +570 -0
- package/dist/contract-migration.js +234 -0
- package/dist/coordinator.js +1163 -0
- package/dist/daemon.js +44 -0
- package/dist/dispatch.js +201 -0
- package/dist/drive.js +503 -0
- package/dist/error-feedback.js +415 -0
- package/dist/evidence-grounding.js +179 -0
- package/dist/evidence-reasoning.js +733 -0
- package/dist/execution-backend.js +1279 -0
- package/dist/harness.js +61 -0
- package/dist/mcp-server.js +1615 -0
- package/dist/multi-agent-eval.js +857 -0
- package/dist/multi-agent-host.js +764 -0
- package/dist/multi-agent-operator-ux.js +537 -0
- package/dist/multi-agent-trust.js +366 -0
- package/dist/multi-agent.js +1173 -0
- package/dist/node-snapshot.js +270 -0
- package/dist/observability.js +922 -0
- package/dist/operator-ux.js +971 -0
- package/dist/orchestrator/audit-operations.js +182 -0
- package/dist/orchestrator/candidate-operations.js +117 -0
- package/dist/orchestrator/cli-options.js +288 -0
- package/dist/orchestrator/collaboration-operations.js +86 -0
- package/dist/orchestrator/feedback-operations.js +81 -0
- package/dist/orchestrator/host-operations.js +78 -0
- package/dist/orchestrator/lifecycle-operations.js +462 -0
- package/dist/orchestrator/migration-operations.js +44 -0
- package/dist/orchestrator/multi-agent-operations.js +362 -0
- package/dist/orchestrator/report.js +369 -0
- package/dist/orchestrator/topology-operations.js +84 -0
- package/dist/orchestrator.js +874 -0
- package/dist/pipeline-contract.js +92 -0
- package/dist/pipeline-runner.js +285 -0
- package/dist/reclamation.js +882 -0
- package/dist/result-normalize.js +194 -0
- package/dist/run-export.js +64 -0
- package/dist/run-registry.js +1347 -0
- package/dist/run-state-schema.js +67 -0
- package/dist/sandbox-profile.js +471 -0
- package/dist/scheduler.js +266 -0
- package/dist/scheduling.js +184 -0
- package/dist/schema-validate.js +98 -0
- package/dist/state-explosion.js +1213 -0
- package/dist/state-migrations.js +463 -0
- package/dist/state-node.js +301 -0
- package/dist/state.js +308 -0
- package/dist/telemetry-attestation.js +156 -0
- package/dist/telemetry-ledger.js +145 -0
- package/dist/topology.js +527 -0
- package/dist/triggers.js +159 -0
- package/dist/trust-audit.js +475 -0
- package/dist/types/blackboard.js +2 -0
- package/dist/types/boundary.js +29 -0
- package/dist/types/candidate.js +2 -0
- package/dist/types/collaboration.js +2 -0
- package/dist/types/core.js +2 -0
- package/dist/types/drive.js +10 -0
- package/dist/types/error-feedback.js +2 -0
- package/dist/types/evidence-reasoning.js +2 -0
- package/dist/types/execution-backend.js +2 -0
- package/dist/types/multi-agent.js +2 -0
- package/dist/types/observability.js +2 -0
- package/dist/types/pipeline.js +2 -0
- package/dist/types/reclamation.js +8 -0
- package/dist/types/result.js +2 -0
- package/dist/types/run-registry.js +2 -0
- package/dist/types/run.js +2 -0
- package/dist/types/sandbox.js +2 -0
- package/dist/types/schedule.js +2 -0
- package/dist/types/state-node.js +2 -0
- package/dist/types/topology.js +2 -0
- package/dist/types/trust.js +2 -0
- package/dist/types/workbench.js +2 -0
- package/dist/types/worker.js +2 -0
- package/dist/types/workflow-app.js +2 -0
- package/dist/types.js +43 -0
- package/dist/verifier-registry.js +46 -0
- package/dist/verifier.js +78 -0
- package/dist/version.js +8 -0
- package/dist/workbench-host.js +172 -0
- package/dist/workbench.js +190 -0
- package/dist/worker-isolation.js +1028 -0
- package/dist/workflow-api.js +98 -0
- package/dist/workflow-app-framework.js +626 -0
- package/docs/agent-delegation-drive.7.md +190 -0
- package/docs/agent-framework.md +176 -0
- package/docs/candidate-scoring.7.md +106 -0
- package/docs/canonical-workflow-apps.7.md +137 -0
- package/docs/capability-topology-registry.7.md +168 -0
- package/docs/cli-mcp-parity.7.md +373 -0
- package/docs/contract-migration-tooling.7.md +123 -0
- package/docs/control-plane-scheduling.7.md +110 -0
- package/docs/coordinator-blackboard.7.md +183 -0
- package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
- package/docs/dogfood-one-real-repo.7.md +168 -0
- package/docs/durable-state-and-locking.7.md +107 -0
- package/docs/end-to-end-golden-path.7.md +117 -0
- package/docs/error-feedback.7.md +153 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
- package/docs/execution-backends.7.md +300 -0
- package/docs/getting-started.md +99 -0
- package/docs/index.md +41 -0
- package/docs/mcp-app-surface.7.md +235 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
- package/docs/multi-agent-eval-replay-harness.7.md +302 -0
- package/docs/multi-agent-operator-ux.7.md +314 -0
- package/docs/multi-agent-runtime-core.7.md +231 -0
- package/docs/multi-agent-topologies.7.md +103 -0
- package/docs/multi-agent-trust-policy-audit.7.md +154 -0
- package/docs/node-snapshot-diff-replay.7.md +135 -0
- package/docs/observability-cost-accounting.7.md +194 -0
- package/docs/operator-ux.7.md +180 -0
- package/docs/pipeline-runner.7.md +136 -0
- package/docs/project-index.md +261 -0
- package/docs/real-execution-backends.7.md +142 -0
- package/docs/release-and-migration.7.md +280 -0
- package/docs/release-tooling.7.md +159 -0
- package/docs/routines.md +48 -0
- package/docs/run-registry-control-plane.7.md +312 -0
- package/docs/run-retention-reclamation.7.md +191 -0
- package/docs/sandbox-profiles.7.md +137 -0
- package/docs/scheduled-tasks.md +80 -0
- package/docs/security-trust-hardening.7.md +117 -0
- package/docs/state-explosion-management.7.md +264 -0
- package/docs/state-node.7.md +96 -0
- package/docs/team-collaboration.7.md +207 -0
- package/docs/unix-principles.md +192 -0
- package/docs/verifier-gated-commit.7.md +140 -0
- package/docs/web-desktop-workbench.7.md +215 -0
- package/docs/worker-isolation.7.md +167 -0
- package/docs/workflow-app-framework.7.md +274 -0
- package/manifest/README.md +43 -0
- package/manifest/plugin.manifest.json +316 -0
- package/manifest/pricing.policy.json +14 -0
- package/package.json +79 -0
- package/scripts/agents/claude-p-agent.js +104 -0
- package/scripts/agents/claude-p-agent.sh +9 -0
- package/scripts/agents/cw-attest-keygen.js +55 -0
- package/scripts/agents/cw-attest-wrap.js +143 -0
- package/scripts/block-unapproved-tag.sh +39 -0
- package/scripts/bump-version.js +249 -0
- package/scripts/canonical-apps.js +171 -0
- package/scripts/cw.js +4 -0
- package/scripts/dist-drift-check.js +79 -0
- package/scripts/dogfood-architecture-review.js +237 -0
- package/scripts/dogfood-release.js +624 -0
- package/scripts/forward-ref-docs.js +73 -0
- package/scripts/gen-manifests.js +232 -0
- package/scripts/golden-path.js +300 -0
- package/scripts/mcp-server.js +4 -0
- package/scripts/new-feature.js +121 -0
- package/scripts/parity-check.js +213 -0
- package/scripts/release-check.js +118 -0
- package/scripts/release-flow.js +272 -0
- package/scripts/release-gate.sh +85 -0
- package/scripts/sync-project-index.js +387 -0
- package/scripts/validate-run-state-schema.js +126 -0
- package/scripts/verify-container-selfref.js +64 -0
- package/scripts/version-sync-check.js +237 -0
- package/skills/cool-workflow/SKILL.md +162 -0
- package/skills/cool-workflow/references/commands.md +282 -0
- package/tsconfig.json +16 -0
- package/ui/workbench/app.css +76 -0
- package/ui/workbench/app.js +159 -0
- package/ui/workbench/index.html +32 -0
- package/workflows/architecture-review.workflow.js +84 -0
- package/workflows/research-synthesis.workflow.js +47 -0
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.WorkflowAppValidationError = exports.workflow = exports.slugify = exports.phase = exports.input = exports.createWorkflowApi = exports.artifact = exports.agent = void 0;
|
|
7
|
+
exports.defineWorkflowApp = defineWorkflowApp;
|
|
8
|
+
exports.validateWorkflowApp = validateWorkflowApp;
|
|
9
|
+
exports.assertValidWorkflowApp = assertValidWorkflowApp;
|
|
10
|
+
exports.validateWorkflowDefinition = validateWorkflowDefinition;
|
|
11
|
+
exports.loadWorkflowAppFromEntrypoint = loadWorkflowAppFromEntrypoint;
|
|
12
|
+
exports.loadWorkflowAppFromManifest = loadWorkflowAppFromManifest;
|
|
13
|
+
exports.summarizeWorkflowApp = summarizeWorkflowApp;
|
|
14
|
+
exports.workflowAppRunMetadata = workflowAppRunMetadata;
|
|
15
|
+
exports.createLegacyWorkflowApp = createLegacyWorkflowApp;
|
|
16
|
+
exports.renderWorkflowAppTemplate = renderWorkflowAppTemplate;
|
|
17
|
+
exports.renderWorkflowAppManifestTemplate = renderWorkflowAppManifestTemplate;
|
|
18
|
+
exports.renderWorkflowAppEntrypointTemplate = renderWorkflowAppEntrypointTemplate;
|
|
19
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
20
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
21
|
+
const workflow_api_1 = require("./workflow-api");
|
|
22
|
+
Object.defineProperty(exports, "agent", { enumerable: true, get: function () { return workflow_api_1.agent; } });
|
|
23
|
+
Object.defineProperty(exports, "artifact", { enumerable: true, get: function () { return workflow_api_1.artifact; } });
|
|
24
|
+
Object.defineProperty(exports, "createWorkflowApi", { enumerable: true, get: function () { return workflow_api_1.createWorkflowApi; } });
|
|
25
|
+
Object.defineProperty(exports, "input", { enumerable: true, get: function () { return workflow_api_1.input; } });
|
|
26
|
+
Object.defineProperty(exports, "phase", { enumerable: true, get: function () { return workflow_api_1.phase; } });
|
|
27
|
+
Object.defineProperty(exports, "slugify", { enumerable: true, get: function () { return workflow_api_1.slugify; } });
|
|
28
|
+
Object.defineProperty(exports, "workflow", { enumerable: true, get: function () { return workflow_api_1.workflow; } });
|
|
29
|
+
const sandbox_profile_1 = require("./sandbox-profile");
|
|
30
|
+
const version_1 = require("./version");
|
|
31
|
+
class WorkflowAppValidationError extends Error {
|
|
32
|
+
issues;
|
|
33
|
+
constructor(message, issues) {
|
|
34
|
+
super(`${message}: ${issues.map((issue) => issue.message).join("; ")}`);
|
|
35
|
+
this.name = "WorkflowAppValidationError";
|
|
36
|
+
this.issues = issues;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.WorkflowAppValidationError = WorkflowAppValidationError;
|
|
40
|
+
function defineWorkflowApp(definition) {
|
|
41
|
+
assertValidWorkflowApp(definition, { appPath: "inline" });
|
|
42
|
+
return definition;
|
|
43
|
+
}
|
|
44
|
+
function validateWorkflowApp(candidate, options = {}) {
|
|
45
|
+
const issues = [];
|
|
46
|
+
const appPath = options.appPath;
|
|
47
|
+
if (!isRecord(candidate)) {
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
appPath,
|
|
51
|
+
issues: [issue("workflow-app-invalid", "Workflow app must be an object", appPath)]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const app = candidate;
|
|
55
|
+
if (app.schemaVersion !== version_1.WORKFLOW_APP_SCHEMA_VERSION) {
|
|
56
|
+
issues.push(issue("workflow-app-schema-version", `Workflow app schemaVersion must be ${version_1.WORKFLOW_APP_SCHEMA_VERSION}`, joinPath(appPath, "schemaVersion")));
|
|
57
|
+
}
|
|
58
|
+
validateAppId(app.id, issues, joinPath(appPath, "id"));
|
|
59
|
+
if (!isNonEmptyString(app.title)) {
|
|
60
|
+
issues.push(issue("workflow-app-title", "Workflow app title is required", joinPath(appPath, "title")));
|
|
61
|
+
}
|
|
62
|
+
if (!isSemver(app.version)) {
|
|
63
|
+
issues.push(issue("workflow-app-version", "Workflow app version must be a semver string such as 0.1.0", joinPath(appPath, "version")));
|
|
64
|
+
}
|
|
65
|
+
validateAuthor(app.author, issues, joinPath(appPath, "author"));
|
|
66
|
+
validateInputDefinitions(app.inputs, issues, joinPath(appPath, "inputs"), { optional: true });
|
|
67
|
+
validateSandboxProfileReferences(app.sandboxProfiles, issues, joinPath(appPath, "sandboxProfiles"), { optional: true });
|
|
68
|
+
validateCompatibility(app.compatibility, issues, joinPath(appPath, "compatibility"));
|
|
69
|
+
const workflowValue = options.loadedWorkflow || app.workflow;
|
|
70
|
+
if (!workflowValue) {
|
|
71
|
+
issues.push(issue("workflow-app-workflow", "Workflow app workflow is required", joinPath(appPath, "workflow")));
|
|
72
|
+
}
|
|
73
|
+
else if (isWorkflowEntrypoint(workflowValue)) {
|
|
74
|
+
validateEntrypoint(workflowValue, issues, joinPath(appPath, "workflow"));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
validateWorkflowDefinition(workflowValue, issues, joinPath(appPath, "workflow"), {
|
|
78
|
+
appId: isNonEmptyString(app.id) ? app.id : undefined,
|
|
79
|
+
appTitle: isNonEmptyString(app.title) ? app.title : undefined,
|
|
80
|
+
appSandboxProfiles: app.sandboxProfiles
|
|
81
|
+
});
|
|
82
|
+
if (Array.isArray(app.inputs) && isWorkflowDefinition(workflowValue)) {
|
|
83
|
+
validateMatchingInputs(app.inputs, workflowValue.inputs || [], issues, joinPath(appPath, "inputs"));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
valid: issues.length === 0,
|
|
88
|
+
appId: isNonEmptyString(app.id) ? app.id : undefined,
|
|
89
|
+
appPath,
|
|
90
|
+
issues
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function assertValidWorkflowApp(candidate, options = {}) {
|
|
94
|
+
const result = validateWorkflowApp(candidate, options);
|
|
95
|
+
if (!result.valid) {
|
|
96
|
+
throw new WorkflowAppValidationError("Invalid workflow app", result.issues);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function validateWorkflowDefinition(candidate, issues = [], basePath = "workflow", options = {}) {
|
|
100
|
+
if (!isRecord(candidate)) {
|
|
101
|
+
issues.push(issue("workflow-invalid", "Workflow definition must be an object", basePath));
|
|
102
|
+
return issues;
|
|
103
|
+
}
|
|
104
|
+
const workflowDefinition = candidate;
|
|
105
|
+
validateWorkflowId(workflowDefinition.id, issues, joinPath(basePath, "id"));
|
|
106
|
+
if (!isNonEmptyString(workflowDefinition.title)) {
|
|
107
|
+
issues.push(issue("workflow-title", "Workflow title is required", joinPath(basePath, "title")));
|
|
108
|
+
}
|
|
109
|
+
if (options.appId && workflowDefinition.id !== options.appId) {
|
|
110
|
+
issues.push(issue("workflow-app-id-mismatch", `Workflow id must match app id ${options.appId}`, joinPath(basePath, "id")));
|
|
111
|
+
}
|
|
112
|
+
if (options.appTitle && workflowDefinition.title !== options.appTitle) {
|
|
113
|
+
issues.push(issue("workflow-app-title-mismatch", `Workflow title must match app title ${options.appTitle}`, joinPath(basePath, "title")));
|
|
114
|
+
}
|
|
115
|
+
validateLimits(workflowDefinition.limits, issues, joinPath(basePath, "limits"));
|
|
116
|
+
validateInputDefinitions(workflowDefinition.inputs, issues, joinPath(basePath, "inputs"), { optional: false });
|
|
117
|
+
validateSandboxProfileReferences(workflowDefinition.sandboxProfiles, issues, joinPath(basePath, "sandboxProfiles"), { optional: true });
|
|
118
|
+
validatePhases(workflowDefinition.phases, workflowDefinition.limits, issues, joinPath(basePath, "phases"), {
|
|
119
|
+
appSandboxProfiles: options.appSandboxProfiles || workflowDefinition.sandboxProfiles
|
|
120
|
+
});
|
|
121
|
+
return issues;
|
|
122
|
+
}
|
|
123
|
+
function loadWorkflowAppFromEntrypoint(file, options = {}) {
|
|
124
|
+
const entrypointPath = node_path_1.default.resolve(file);
|
|
125
|
+
const materialized = materializeModuleExport(entrypointPath, options.exportName);
|
|
126
|
+
const loaded = extractWorkflowAppExport(materialized, entrypointPath);
|
|
127
|
+
const source = {
|
|
128
|
+
kind: options.sourceKind || "workflow-file",
|
|
129
|
+
path: entrypointPath,
|
|
130
|
+
entrypointPath
|
|
131
|
+
};
|
|
132
|
+
if (loaded.app) {
|
|
133
|
+
const app = {
|
|
134
|
+
...loaded.app,
|
|
135
|
+
workflow: loaded.workflow
|
|
136
|
+
};
|
|
137
|
+
assertValidWorkflowApp(app, { appPath: entrypointPath });
|
|
138
|
+
return { app, source, legacy: false };
|
|
139
|
+
}
|
|
140
|
+
const app = createLegacyWorkflowApp(loaded.workflow, source);
|
|
141
|
+
assertValidWorkflowApp(app, { appPath: entrypointPath });
|
|
142
|
+
return { app, source, legacy: true };
|
|
143
|
+
}
|
|
144
|
+
function loadWorkflowAppFromManifest(manifestPath) {
|
|
145
|
+
const absoluteManifestPath = node_path_1.default.resolve(manifestPath);
|
|
146
|
+
if (!node_fs_1.default.existsSync(absoluteManifestPath)) {
|
|
147
|
+
throw new WorkflowAppValidationError("Invalid workflow app manifest", [
|
|
148
|
+
issue("workflow-app-manifest-not-found", `Workflow app manifest does not exist: ${absoluteManifestPath}`, absoluteManifestPath)
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
let manifest;
|
|
152
|
+
try {
|
|
153
|
+
manifest = JSON.parse(node_fs_1.default.readFileSync(absoluteManifestPath, "utf8"));
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw new WorkflowAppValidationError("Invalid workflow app manifest", [
|
|
157
|
+
issue("workflow-app-manifest-json", `Workflow app manifest is not valid JSON: ${messageOf(error)}`, absoluteManifestPath)
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
const manifestValidation = validateWorkflowApp(manifest, { appPath: absoluteManifestPath });
|
|
161
|
+
if (!manifestValidation.valid) {
|
|
162
|
+
throw new WorkflowAppValidationError("Invalid workflow app manifest", manifestValidation.issues);
|
|
163
|
+
}
|
|
164
|
+
if (!isWorkflowEntrypoint(manifest.workflow)) {
|
|
165
|
+
throw new WorkflowAppValidationError("Invalid workflow app manifest", [
|
|
166
|
+
issue("workflow-app-entrypoint", "Manifest workflow must be an entrypoint object", joinPath(absoluteManifestPath, "workflow"))
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
const entrypointPath = node_path_1.default.resolve(node_path_1.default.dirname(absoluteManifestPath), manifest.workflow.entrypoint);
|
|
170
|
+
const materialized = materializeModuleExport(entrypointPath, manifest.workflow.exportName);
|
|
171
|
+
const loaded = extractWorkflowAppExport(materialized, entrypointPath);
|
|
172
|
+
const workflowDefinition = loaded.workflow;
|
|
173
|
+
const source = {
|
|
174
|
+
kind: node_path_1.default.basename(absoluteManifestPath) === "app.json" ? "app-directory" : "app-manifest",
|
|
175
|
+
path: node_path_1.default.dirname(absoluteManifestPath),
|
|
176
|
+
manifestPath: absoluteManifestPath,
|
|
177
|
+
entrypointPath
|
|
178
|
+
};
|
|
179
|
+
if (loaded.app) {
|
|
180
|
+
validateEntrypointAppMatchesManifest(loaded.app, manifest, absoluteManifestPath);
|
|
181
|
+
}
|
|
182
|
+
const app = {
|
|
183
|
+
...manifest,
|
|
184
|
+
workflow: workflowDefinition
|
|
185
|
+
};
|
|
186
|
+
assertValidWorkflowApp(app, {
|
|
187
|
+
appPath: absoluteManifestPath,
|
|
188
|
+
loadedWorkflow: workflowDefinition
|
|
189
|
+
});
|
|
190
|
+
return { app, source, legacy: false };
|
|
191
|
+
}
|
|
192
|
+
function summarizeWorkflowApp(record) {
|
|
193
|
+
const workflowDefinition = record.app.workflow;
|
|
194
|
+
const sandboxProfiles = record.app.sandboxProfiles || workflowDefinition.sandboxProfiles || [];
|
|
195
|
+
const summary = {
|
|
196
|
+
id: record.app.id,
|
|
197
|
+
title: record.app.title,
|
|
198
|
+
summary: record.app.summary || workflowDefinition.summary || "",
|
|
199
|
+
version: record.app.version,
|
|
200
|
+
author: record.app.author,
|
|
201
|
+
file: record.source.entrypointPath || record.source.manifestPath || record.source.path,
|
|
202
|
+
sourceKind: record.source.kind,
|
|
203
|
+
legacy: record.legacy,
|
|
204
|
+
compatible: isAppCompatible(record.app),
|
|
205
|
+
inputs: record.app.inputs || workflowDefinition.inputs || [],
|
|
206
|
+
sandboxProfiles,
|
|
207
|
+
phases: workflowDefinition.phases.map((phaseDefinition) => ({
|
|
208
|
+
id: phaseDefinition.id,
|
|
209
|
+
name: phaseDefinition.name,
|
|
210
|
+
taskCount: phaseDefinition.tasks.length
|
|
211
|
+
})),
|
|
212
|
+
taskCount: workflowDefinition.phases.reduce((total, phaseDefinition) => total + phaseDefinition.tasks.length, 0)
|
|
213
|
+
};
|
|
214
|
+
return summary;
|
|
215
|
+
}
|
|
216
|
+
function workflowAppRunMetadata(record) {
|
|
217
|
+
return {
|
|
218
|
+
schemaVersion: version_1.WORKFLOW_APP_SCHEMA_VERSION,
|
|
219
|
+
id: record.app.id,
|
|
220
|
+
title: record.app.title,
|
|
221
|
+
summary: record.app.summary || record.app.workflow.summary || "",
|
|
222
|
+
version: record.app.version,
|
|
223
|
+
author: record.app.author,
|
|
224
|
+
compatibility: record.app.compatibility,
|
|
225
|
+
sandboxProfiles: record.app.sandboxProfiles || record.app.workflow.sandboxProfiles,
|
|
226
|
+
source: record.source,
|
|
227
|
+
metadata: record.app.metadata
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function createLegacyWorkflowApp(workflowDefinition, source) {
|
|
231
|
+
return {
|
|
232
|
+
schemaVersion: version_1.WORKFLOW_APP_SCHEMA_VERSION,
|
|
233
|
+
id: workflowDefinition.id,
|
|
234
|
+
title: workflowDefinition.title,
|
|
235
|
+
summary: workflowDefinition.summary || "",
|
|
236
|
+
version: "0.0.0",
|
|
237
|
+
workflow: workflowDefinition,
|
|
238
|
+
inputs: workflowDefinition.inputs || [],
|
|
239
|
+
sandboxProfiles: workflowDefinition.sandboxProfiles || collectWorkflowSandboxProfiles(workflowDefinition),
|
|
240
|
+
compatibility: {
|
|
241
|
+
maxVersion: version_1.CURRENT_COOL_WORKFLOW_VERSION,
|
|
242
|
+
notes: "Compatibility wrapper for legacy .workflow.js factory files."
|
|
243
|
+
},
|
|
244
|
+
metadata: {
|
|
245
|
+
legacyWorkflow: true,
|
|
246
|
+
sourcePath: source.path
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function renderWorkflowAppTemplate(id, title) {
|
|
251
|
+
return `const { defineWorkflowApp, workflow, phase, agent, artifact, input } = require("../dist/workflow-app-framework");\n\nconst inputs = [\n input("question", { required: true, description: "Question or task this workflow should answer." })\n];\n\nmodule.exports = defineWorkflowApp({\n schemaVersion: 1,\n id: ${JSON.stringify(id)},\n title: ${JSON.stringify(title)},\n summary: "Describe what this workflow app does.",\n version: "0.1.0",\n author: "COOLWHITE LLC",\n inputs,\n sandboxProfiles: ["readonly"],\n compatibility: {\n minVersion: "0.1.9"\n },\n workflow: workflow({\n id: ${JSON.stringify(id)},\n title: ${JSON.stringify(title)},\n summary: "Describe what this workflow app does.",\n limits: {\n maxAgents: 8,\n maxConcurrentAgents: 4\n },\n inputs,\n sandboxProfiles: ["readonly"],\n phases: [\n phase("Map", [\n agent("map:context", "Map the task context, constraints, and evidence needed for {{question}}.", { sandboxProfileId: "readonly" })\n ]),\n phase("Assess", [\n agent("assess:risks", "Assess risks, tradeoffs, and unknowns for {{question}}.", { sandboxProfileId: "readonly" })\n ]),\n phase("Synthesize", [\n artifact("synthesis:report", "Synthesize the final answer for {{question}}.", { requiresEvidence: true, sandboxProfileId: "readonly" })\n ])\n ]\n })\n});\n`;
|
|
252
|
+
}
|
|
253
|
+
function renderWorkflowAppManifestTemplate(id, title) {
|
|
254
|
+
return `${JSON.stringify({
|
|
255
|
+
schemaVersion: version_1.WORKFLOW_APP_SCHEMA_VERSION,
|
|
256
|
+
id,
|
|
257
|
+
title,
|
|
258
|
+
summary: "Describe what this workflow app does.",
|
|
259
|
+
version: "0.1.0",
|
|
260
|
+
author: "COOLWHITE LLC",
|
|
261
|
+
inputs: [
|
|
262
|
+
{
|
|
263
|
+
name: "question",
|
|
264
|
+
type: "string",
|
|
265
|
+
required: true,
|
|
266
|
+
description: "Question or task this workflow should answer."
|
|
267
|
+
}
|
|
268
|
+
],
|
|
269
|
+
sandboxProfiles: ["readonly"],
|
|
270
|
+
compatibility: {
|
|
271
|
+
minVersion: "0.1.9"
|
|
272
|
+
},
|
|
273
|
+
workflow: {
|
|
274
|
+
entrypoint: "workflow.js"
|
|
275
|
+
}
|
|
276
|
+
}, null, 2)}\n`;
|
|
277
|
+
}
|
|
278
|
+
function renderWorkflowAppEntrypointTemplate(id, title) {
|
|
279
|
+
return `module.exports = ({ workflow, phase, agent, artifact, input }) => {\n const inputs = [\n input("question", { type: "string", required: true, description: "Question or task this workflow should answer." })\n ];\n\n return workflow({\n id: ${JSON.stringify(id)},\n title: ${JSON.stringify(title)},\n summary: "Describe what this workflow app does.",\n limits: {\n maxAgents: 8,\n maxConcurrentAgents: 4\n },\n inputs,\n sandboxProfiles: ["readonly"],\n phases: [\n phase("Map", [\n agent("map:context", "Map the task context, constraints, and evidence needed for {{question}}.", { sandboxProfileId: "readonly" })\n ]),\n phase("Assess", [\n agent("assess:risks", "Assess risks, tradeoffs, and unknowns for {{question}}.", { sandboxProfileId: "readonly" })\n ]),\n phase("Synthesize", [\n artifact("synthesis:report", "Synthesize the final answer for {{question}}.", { requiresEvidence: true, sandboxProfileId: "readonly" })\n ])\n ]\n });\n};\n`;
|
|
280
|
+
}
|
|
281
|
+
function materializeModuleExport(file, exportName) {
|
|
282
|
+
if (!node_fs_1.default.existsSync(file)) {
|
|
283
|
+
throw new WorkflowAppValidationError("Invalid workflow app entrypoint", [
|
|
284
|
+
issue("workflow-app-entrypoint-not-found", `Workflow app entrypoint does not exist: ${file}`, file)
|
|
285
|
+
]);
|
|
286
|
+
}
|
|
287
|
+
// Workflow apps are plain runtime JavaScript, not TypeScript.
|
|
288
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
289
|
+
const rawExport = require(file);
|
|
290
|
+
const selectedExport = exportName && isRecord(rawExport) ? rawExport[exportName] : rawExport;
|
|
291
|
+
if (exportName && selectedExport === undefined) {
|
|
292
|
+
throw new WorkflowAppValidationError("Invalid workflow app entrypoint", [
|
|
293
|
+
issue("workflow-app-entrypoint-export", `Workflow app entrypoint does not export ${exportName}`, file)
|
|
294
|
+
]);
|
|
295
|
+
}
|
|
296
|
+
if (typeof selectedExport === "function") {
|
|
297
|
+
return selectedExport({
|
|
298
|
+
...(0, workflow_api_1.createWorkflowApi)(),
|
|
299
|
+
defineWorkflowApp
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return selectedExport;
|
|
303
|
+
}
|
|
304
|
+
function extractWorkflowAppExport(value, file) {
|
|
305
|
+
if (isWorkflowAppDefinition(value) && isWorkflowDefinition(value.workflow)) {
|
|
306
|
+
return {
|
|
307
|
+
app: value,
|
|
308
|
+
workflow: value.workflow
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (isWorkflowDefinition(value)) {
|
|
312
|
+
return { workflow: value };
|
|
313
|
+
}
|
|
314
|
+
throw new WorkflowAppValidationError("Invalid workflow app entrypoint", [
|
|
315
|
+
issue("workflow-app-entrypoint-export", "Workflow app entrypoint must export a workflow definition, workflow app, or factory", file)
|
|
316
|
+
]);
|
|
317
|
+
}
|
|
318
|
+
function validateEntrypointAppMatchesManifest(entrypointApp, manifest, manifestPath) {
|
|
319
|
+
const issues = [];
|
|
320
|
+
for (const key of ["schemaVersion", "id", "title", "version"]) {
|
|
321
|
+
if (entrypointApp[key] !== manifest[key]) {
|
|
322
|
+
issues.push(issue("workflow-app-manifest-mismatch", `Entrypoint app ${key} must match manifest ${key}`, joinPath(manifestPath, key)));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (issues.length)
|
|
326
|
+
throw new WorkflowAppValidationError("Invalid workflow app manifest", issues);
|
|
327
|
+
}
|
|
328
|
+
function validateAppId(value, issues, pathName) {
|
|
329
|
+
if (!isNonEmptyString(value)) {
|
|
330
|
+
issues.push(issue("workflow-app-id", "Workflow app id is required", pathName));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(value)) {
|
|
334
|
+
issues.push(issue("workflow-app-id", `Workflow app id is malformed: ${value}`, pathName));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function validateWorkflowId(value, issues, pathName) {
|
|
338
|
+
if (!isNonEmptyString(value)) {
|
|
339
|
+
issues.push(issue("workflow-id", "Workflow id is required", pathName));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(value)) {
|
|
343
|
+
issues.push(issue("workflow-id", `Workflow id is malformed: ${value}`, pathName));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function validateAuthor(value, issues, pathName) {
|
|
347
|
+
if (value === undefined)
|
|
348
|
+
return;
|
|
349
|
+
if (isNonEmptyString(value))
|
|
350
|
+
return;
|
|
351
|
+
if (isRecord(value) && isNonEmptyString(value.name))
|
|
352
|
+
return;
|
|
353
|
+
issues.push(issue("workflow-app-author", "Workflow app author must be a string or object with name", pathName));
|
|
354
|
+
}
|
|
355
|
+
function validateLimits(value, issues, pathName) {
|
|
356
|
+
if (!isRecord(value)) {
|
|
357
|
+
issues.push(issue("workflow-limits", "Workflow limits are required", pathName));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const maxAgents = Number(value.maxAgents);
|
|
361
|
+
const maxConcurrentAgents = Number(value.maxConcurrentAgents);
|
|
362
|
+
if (!Number.isInteger(maxAgents) || maxAgents < 1) {
|
|
363
|
+
issues.push(issue("workflow-limits", "Workflow limits.maxAgents must be a positive integer", joinPath(pathName, "maxAgents")));
|
|
364
|
+
}
|
|
365
|
+
if (!Number.isInteger(maxConcurrentAgents) || maxConcurrentAgents < 1) {
|
|
366
|
+
issues.push(issue("workflow-limits", "Workflow limits.maxConcurrentAgents must be a positive integer", joinPath(pathName, "maxConcurrentAgents")));
|
|
367
|
+
}
|
|
368
|
+
if (Number.isInteger(maxAgents) && Number.isInteger(maxConcurrentAgents) && maxConcurrentAgents > maxAgents) {
|
|
369
|
+
issues.push(issue("workflow-limits", "Workflow limits.maxConcurrentAgents must be less than or equal to maxAgents", joinPath(pathName, "maxConcurrentAgents")));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function validateInputDefinitions(inputs, issues, pathName, options = { optional: false }) {
|
|
373
|
+
if (inputs === undefined && options.optional)
|
|
374
|
+
return;
|
|
375
|
+
if (!Array.isArray(inputs)) {
|
|
376
|
+
issues.push(issue("workflow-inputs", "Workflow inputs must be an array", pathName));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const seen = new Set();
|
|
380
|
+
for (const [index, inputDefinition] of inputs.entries()) {
|
|
381
|
+
const inputPath = joinPath(pathName, String(index));
|
|
382
|
+
if (!isRecord(inputDefinition)) {
|
|
383
|
+
issues.push(issue("workflow-input", "Workflow input must be an object", inputPath));
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
const name = inputDefinition.name;
|
|
387
|
+
if (!isNonEmptyString(name) || !/^[A-Za-z][A-Za-z0-9_-]*$/.test(name)) {
|
|
388
|
+
issues.push(issue("workflow-input-name", `Workflow input name is malformed: ${String(name || "")}`, joinPath(inputPath, "name")));
|
|
389
|
+
}
|
|
390
|
+
else if (seen.has(name)) {
|
|
391
|
+
issues.push(issue("workflow-input-duplicate", `Duplicate workflow input name: ${name}`, joinPath(inputPath, "name")));
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
seen.add(name);
|
|
395
|
+
}
|
|
396
|
+
if (inputDefinition.type !== undefined && !["string", "number", "boolean", "path", "json"].includes(String(inputDefinition.type))) {
|
|
397
|
+
issues.push(issue("workflow-input-type", `Workflow input ${String(name || index)} has invalid type`, joinPath(inputPath, "type")));
|
|
398
|
+
}
|
|
399
|
+
if (inputDefinition.required !== undefined && typeof inputDefinition.required !== "boolean") {
|
|
400
|
+
issues.push(issue("workflow-input-required", `Workflow input ${String(name || index)} required must be boolean`, joinPath(inputPath, "required")));
|
|
401
|
+
}
|
|
402
|
+
if (inputDefinition.repeated !== undefined && typeof inputDefinition.repeated !== "boolean") {
|
|
403
|
+
issues.push(issue("workflow-input-repeated", `Workflow input ${String(name || index)} repeated must be boolean`, joinPath(inputPath, "repeated")));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function validateMatchingInputs(appInputs, workflowInputs, issues, pathName) {
|
|
408
|
+
if (JSON.stringify(appInputs) !== JSON.stringify(workflowInputs)) {
|
|
409
|
+
issues.push(issue("workflow-app-inputs-mismatch", "Workflow app inputs must match workflow.inputs when both are present", pathName));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function validateSandboxProfileReferences(profiles, issues, pathName, options = { optional: false }) {
|
|
413
|
+
if (profiles === undefined && options.optional)
|
|
414
|
+
return;
|
|
415
|
+
if (!Array.isArray(profiles)) {
|
|
416
|
+
issues.push(issue("workflow-sandbox-profiles", "Workflow sandboxProfiles must be an array", pathName));
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const seen = new Set();
|
|
420
|
+
const bundled = (0, sandbox_profile_1.bundledSandboxProfileIds)();
|
|
421
|
+
for (const [index, value] of profiles.entries()) {
|
|
422
|
+
const profilePath = joinPath(pathName, String(index));
|
|
423
|
+
if (!isNonEmptyString(value)) {
|
|
424
|
+
issues.push(issue("workflow-sandbox-profile", "Sandbox profile reference must be a non-empty string", profilePath));
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (seen.has(value)) {
|
|
428
|
+
issues.push(issue("workflow-sandbox-profile-duplicate", `Duplicate sandbox profile reference: ${value}`, profilePath));
|
|
429
|
+
}
|
|
430
|
+
seen.add(value);
|
|
431
|
+
if (!(0, sandbox_profile_1.isBundledSandboxProfileId)(value)) {
|
|
432
|
+
issues.push(issue("workflow-sandbox-profile-unknown", `Unknown sandbox profile ${value}; bundled profiles: ${bundled.join(", ")}`, profilePath));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function validateCompatibility(value, issues, pathName) {
|
|
437
|
+
if (value === undefined)
|
|
438
|
+
return;
|
|
439
|
+
if (!isRecord(value)) {
|
|
440
|
+
issues.push(issue("workflow-app-compatibility", "Workflow app compatibility must be an object", pathName));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (value.workflowSchemaVersion !== undefined && value.workflowSchemaVersion !== version_1.WORKFLOW_APP_SCHEMA_VERSION) {
|
|
444
|
+
issues.push(issue("workflow-app-compatibility", `Workflow schema version must be ${version_1.WORKFLOW_APP_SCHEMA_VERSION}`, joinPath(pathName, "workflowSchemaVersion")));
|
|
445
|
+
}
|
|
446
|
+
for (const key of ["coolWorkflow", "node", "notes"]) {
|
|
447
|
+
if (value[key] !== undefined && !isNonEmptyString(value[key])) {
|
|
448
|
+
issues.push(issue("workflow-app-compatibility", `Compatibility ${key} must be a string`, joinPath(pathName, key)));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (value.minVersion !== undefined) {
|
|
452
|
+
if (!isSemver(value.minVersion)) {
|
|
453
|
+
issues.push(issue("workflow-app-compatibility", "Compatibility minVersion must be semver", joinPath(pathName, "minVersion")));
|
|
454
|
+
}
|
|
455
|
+
else if (compareSemver(version_1.CURRENT_COOL_WORKFLOW_VERSION, value.minVersion) < 0) {
|
|
456
|
+
issues.push(issue("workflow-app-incompatible", `Workflow app requires Cool Workflow >= ${value.minVersion}; current is ${version_1.CURRENT_COOL_WORKFLOW_VERSION}`, joinPath(pathName, "minVersion")));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (value.maxVersion !== undefined) {
|
|
460
|
+
if (!isSemver(value.maxVersion)) {
|
|
461
|
+
issues.push(issue("workflow-app-compatibility", "Compatibility maxVersion must be semver", joinPath(pathName, "maxVersion")));
|
|
462
|
+
}
|
|
463
|
+
else if (compareSemver(version_1.CURRENT_COOL_WORKFLOW_VERSION, value.maxVersion) > 0) {
|
|
464
|
+
issues.push(issue("workflow-app-incompatible", `Workflow app supports Cool Workflow <= ${value.maxVersion}; current is ${version_1.CURRENT_COOL_WORKFLOW_VERSION}`, joinPath(pathName, "maxVersion")));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function validateEntrypoint(value, issues, pathName) {
|
|
469
|
+
if (!isNonEmptyString(value.entrypoint)) {
|
|
470
|
+
issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.entrypoint is required", joinPath(pathName, "entrypoint")));
|
|
471
|
+
}
|
|
472
|
+
if (value.entrypoint && node_path_1.default.isAbsolute(value.entrypoint)) {
|
|
473
|
+
issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.entrypoint must be relative", joinPath(pathName, "entrypoint")));
|
|
474
|
+
}
|
|
475
|
+
if (value.entrypoint && value.entrypoint.split(/[\\/]/).includes("..")) {
|
|
476
|
+
issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.entrypoint must not contain traversal", joinPath(pathName, "entrypoint")));
|
|
477
|
+
}
|
|
478
|
+
if (value.exportName !== undefined && !isNonEmptyString(value.exportName)) {
|
|
479
|
+
issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.exportName must be a string", joinPath(pathName, "exportName")));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function validatePhases(phases, limits, issues, pathName, options = {}) {
|
|
483
|
+
if (!Array.isArray(phases) || !phases.length) {
|
|
484
|
+
issues.push(issue("workflow-phases", "Workflow phases must be a non-empty array", pathName));
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const seenPhaseIds = new Set();
|
|
488
|
+
const seenTaskIds = new Set();
|
|
489
|
+
let taskCount = 0;
|
|
490
|
+
for (const [phaseIndex, phaseDefinition] of phases.entries()) {
|
|
491
|
+
const phasePath = joinPath(pathName, String(phaseIndex));
|
|
492
|
+
validatePhase(phaseDefinition, issues, phasePath, seenPhaseIds);
|
|
493
|
+
if (!isRecord(phaseDefinition) || !Array.isArray(phaseDefinition.tasks))
|
|
494
|
+
continue;
|
|
495
|
+
for (const [taskIndex, taskDefinition] of phaseDefinition.tasks.entries()) {
|
|
496
|
+
taskCount += 1;
|
|
497
|
+
validateTask(taskDefinition, issues, joinPath(joinPath(phasePath, "tasks"), String(taskIndex)), seenTaskIds, options);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (isRecord(limits) && Number.isInteger(Number(limits.maxAgents)) && taskCount > Number(limits.maxAgents)) {
|
|
501
|
+
issues.push(issue("workflow-limits", `Workflow defines ${taskCount} tasks but limits.maxAgents is ${String(limits.maxAgents)}`, joinPath(pathName, "limits.maxAgents")));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function validatePhase(phaseDefinition, issues, pathName, seenPhaseIds) {
|
|
505
|
+
if (!isRecord(phaseDefinition)) {
|
|
506
|
+
issues.push(issue("workflow-phase", "Workflow phase must be an object", pathName));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const phaseValue = phaseDefinition;
|
|
510
|
+
if (!isNonEmptyString(phaseValue.id) || !/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(phaseValue.id)) {
|
|
511
|
+
issues.push(issue("workflow-phase-id", `Workflow phase id is malformed: ${String(phaseValue.id || "")}`, joinPath(pathName, "id")));
|
|
512
|
+
}
|
|
513
|
+
else if (seenPhaseIds.has(phaseValue.id)) {
|
|
514
|
+
issues.push(issue("workflow-phase-duplicate", `Duplicate workflow phase id: ${phaseValue.id}`, joinPath(pathName, "id")));
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
seenPhaseIds.add(phaseValue.id);
|
|
518
|
+
}
|
|
519
|
+
if (!isNonEmptyString(phaseValue.name)) {
|
|
520
|
+
issues.push(issue("workflow-phase-name", "Workflow phase name is required", joinPath(pathName, "name")));
|
|
521
|
+
}
|
|
522
|
+
if (!Array.isArray(phaseValue.tasks) || !phaseValue.tasks.length) {
|
|
523
|
+
issues.push(issue("workflow-phase-tasks", `Workflow phase ${String(phaseValue.id || phaseValue.name || "")} must have tasks`, joinPath(pathName, "tasks")));
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function validateTask(taskDefinition, issues, pathName, seenTaskIds, options) {
|
|
527
|
+
if (!isRecord(taskDefinition)) {
|
|
528
|
+
issues.push(issue("workflow-task", "Workflow task must be an object", pathName));
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const taskValue = taskDefinition;
|
|
532
|
+
if (!isNonEmptyString(taskValue.id) || !/^[A-Za-z0-9][A-Za-z0-9_.:/-]*$/.test(taskValue.id)) {
|
|
533
|
+
issues.push(issue("workflow-task-id", `Workflow task id is malformed: ${String(taskValue.id || "")}`, joinPath(pathName, "id")));
|
|
534
|
+
}
|
|
535
|
+
else if (seenTaskIds.has(taskValue.id)) {
|
|
536
|
+
issues.push(issue("workflow-task-duplicate", `Duplicate workflow task id: ${taskValue.id}`, joinPath(pathName, "id")));
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
seenTaskIds.add(taskValue.id);
|
|
540
|
+
}
|
|
541
|
+
if (!["agent", "artifact"].includes(String(taskValue.kind))) {
|
|
542
|
+
issues.push(issue("workflow-task-kind", `Workflow task ${String(taskValue.id || "")} kind must be agent or artifact`, joinPath(pathName, "kind")));
|
|
543
|
+
}
|
|
544
|
+
if (!isNonEmptyString(taskValue.prompt)) {
|
|
545
|
+
issues.push(issue("workflow-task-prompt", `Workflow task ${String(taskValue.id || "")} prompt is required`, joinPath(pathName, "prompt")));
|
|
546
|
+
}
|
|
547
|
+
if (taskValue.requiresEvidence !== undefined && typeof taskValue.requiresEvidence !== "boolean") {
|
|
548
|
+
issues.push(issue("workflow-task-evidence", `Workflow task ${String(taskValue.id || "")} requiresEvidence must be boolean`, joinPath(pathName, "requiresEvidence")));
|
|
549
|
+
}
|
|
550
|
+
const sandboxProfileId = taskValue.sandboxProfileId;
|
|
551
|
+
if (sandboxProfileId !== undefined) {
|
|
552
|
+
if (!isNonEmptyString(sandboxProfileId)) {
|
|
553
|
+
issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandboxProfileId must be a string`, joinPath(pathName, "sandboxProfileId")));
|
|
554
|
+
}
|
|
555
|
+
else if (!(0, sandbox_profile_1.isBundledSandboxProfileId)(sandboxProfileId)) {
|
|
556
|
+
issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} references unknown sandbox profile ${sandboxProfileId}`, joinPath(pathName, "sandboxProfileId")));
|
|
557
|
+
}
|
|
558
|
+
else if (options.appSandboxProfiles && !options.appSandboxProfiles.includes(sandboxProfileId)) {
|
|
559
|
+
issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandbox profile ${sandboxProfileId} must be listed in app sandboxProfiles`, joinPath(pathName, "sandboxProfileId")));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function isWorkflowDefinition(value) {
|
|
564
|
+
return (isRecord(value) &&
|
|
565
|
+
isNonEmptyString(value.id) &&
|
|
566
|
+
isNonEmptyString(value.title) &&
|
|
567
|
+
Array.isArray(value.phases) &&
|
|
568
|
+
isRecord(value.limits) &&
|
|
569
|
+
Array.isArray(value.inputs));
|
|
570
|
+
}
|
|
571
|
+
function isWorkflowAppDefinition(value) {
|
|
572
|
+
return isRecord(value) && value.schemaVersion === version_1.WORKFLOW_APP_SCHEMA_VERSION && isNonEmptyString(value.id) && "workflow" in value;
|
|
573
|
+
}
|
|
574
|
+
function isWorkflowEntrypoint(value) {
|
|
575
|
+
return isRecord(value) && "entrypoint" in value && !("phases" in value);
|
|
576
|
+
}
|
|
577
|
+
function isAppCompatible(app) {
|
|
578
|
+
return !validateWorkflowApp(app).issues.some((compatIssue) => compatIssue.code === "workflow-app-incompatible");
|
|
579
|
+
}
|
|
580
|
+
function collectWorkflowSandboxProfiles(workflowDefinition) {
|
|
581
|
+
const profiles = new Set();
|
|
582
|
+
for (const phaseDefinition of workflowDefinition.phases) {
|
|
583
|
+
for (const taskDefinition of phaseDefinition.tasks) {
|
|
584
|
+
if (taskDefinition.sandboxProfileId)
|
|
585
|
+
profiles.add(taskDefinition.sandboxProfileId);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return [...profiles].sort();
|
|
589
|
+
}
|
|
590
|
+
function compareSemver(left, right) {
|
|
591
|
+
const leftParts = semverParts(left);
|
|
592
|
+
const rightParts = semverParts(right);
|
|
593
|
+
for (let index = 0; index < 3; index += 1) {
|
|
594
|
+
if (leftParts[index] !== rightParts[index])
|
|
595
|
+
return leftParts[index] < rightParts[index] ? -1 : 1;
|
|
596
|
+
}
|
|
597
|
+
return 0;
|
|
598
|
+
}
|
|
599
|
+
function semverParts(value) {
|
|
600
|
+
const [major, minor, patch] = value.split(/[+-]/)[0].split(".").map((part) => Number(part));
|
|
601
|
+
return [major || 0, minor || 0, patch || 0];
|
|
602
|
+
}
|
|
603
|
+
function isSemver(value) {
|
|
604
|
+
return typeof value === "string" && /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value);
|
|
605
|
+
}
|
|
606
|
+
function isNonEmptyString(value) {
|
|
607
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
608
|
+
}
|
|
609
|
+
function isRecord(value) {
|
|
610
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
611
|
+
}
|
|
612
|
+
function issue(code, message, pathName) {
|
|
613
|
+
return {
|
|
614
|
+
code,
|
|
615
|
+
message,
|
|
616
|
+
path: pathName
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function joinPath(basePath, segment) {
|
|
620
|
+
if (!basePath)
|
|
621
|
+
return segment;
|
|
622
|
+
return `${basePath}.${segment}`;
|
|
623
|
+
}
|
|
624
|
+
function messageOf(error) {
|
|
625
|
+
return error instanceof Error ? error.message : String(error);
|
|
626
|
+
}
|