cool-workflow 0.1.97 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +11 -2
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/cli/dispatch.js +236 -0
- package/dist/cli/entry.js +120 -0
- package/dist/cli/io.js +21 -4
- package/dist/cli/parseargv.js +157 -0
- package/dist/cli.js +6 -33
- package/dist/core/capability-table.js +3534 -0
- package/dist/core/format/help.js +314 -0
- package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
- package/dist/core/hash.js +137 -0
- package/dist/core/multi-agent/candidate-scoring.js +219 -0
- package/dist/core/multi-agent/collaboration.js +481 -0
- package/dist/core/multi-agent/coordinator.js +515 -0
- package/dist/core/multi-agent/eval-replay.js +306 -0
- package/dist/core/multi-agent/runtime.js +929 -0
- package/dist/core/multi-agent/topology.js +298 -0
- package/dist/core/multi-agent/trust-policy.js +197 -0
- package/dist/core/pipeline/commit-gate.js +320 -0
- package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
- package/dist/core/pipeline/dispatch.js +103 -0
- package/dist/core/pipeline/drive-decide.js +227 -0
- package/dist/core/pipeline/error-feedback.js +161 -0
- package/dist/core/pipeline/loop-expansion.js +124 -0
- package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
- package/dist/core/pipeline/runner.js +230 -0
- package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
- package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
- package/dist/core/state/node-projection.js +95 -0
- package/dist/core/state/node-snapshot.js +230 -0
- package/dist/core/state/run-paths.js +93 -0
- package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
- package/dist/core/state/schema.js +50 -0
- package/dist/core/state/state-explosion/digest.js +243 -0
- package/dist/core/state/state-explosion/graph.js +527 -0
- package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
- package/dist/core/state/state-explosion/report.js +187 -0
- package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
- package/dist/{state-node.js → core/state/state-node.js} +90 -113
- package/dist/core/state/types.js +19 -0
- package/dist/{validation.js → core/state/validation.js} +64 -131
- package/dist/core/trust/evidence-grounding.js +134 -0
- package/dist/core/trust/ledger.js +199 -0
- package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
- package/dist/core/trust/telemetry-ledger.js +127 -0
- package/dist/core/types.js +20 -0
- package/dist/core/version.js +16 -0
- package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
- package/dist/mcp/dispatch.js +104 -0
- package/dist/mcp/server.js +144 -0
- package/dist/mcp-server.js +6 -84
- package/dist/{agent-config.js → shell/agent-config.js} +118 -71
- package/dist/shell/app-run-cli.js +88 -0
- package/dist/shell/audit-cli.js +259 -0
- package/dist/shell/audit-provenance.js +83 -0
- package/dist/shell/candidate-scoring-io.js +459 -0
- package/dist/shell/collaboration-io.js +264 -0
- package/dist/shell/commit-summary.js +104 -0
- package/dist/shell/commit.js +290 -0
- package/dist/shell/coordinator-io.js +476 -0
- package/dist/shell/demo-cli.js +19 -0
- package/dist/shell/dispatch.js +162 -0
- package/dist/{doctor.js → shell/doctor.js} +123 -56
- package/dist/shell/drive.js +873 -0
- package/dist/shell/error-feedback-io.js +305 -0
- package/dist/shell/eval-io.js +473 -0
- package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
- package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
- package/dist/shell/exec-backend-cli.js +88 -0
- package/dist/shell/execution-backend/agent.js +475 -0
- package/dist/shell/execution-backend/ci.js +15 -0
- package/dist/shell/execution-backend/container.js +69 -0
- package/dist/shell/execution-backend/envelopes.js +55 -0
- package/dist/shell/execution-backend/local.js +113 -0
- package/dist/shell/execution-backend/probes.js +175 -0
- package/dist/shell/execution-backend/registry.js +402 -0
- package/dist/shell/execution-backend/remote.js +128 -0
- package/dist/shell/execution-backend/types.js +11 -0
- package/dist/shell/feedback-cli.js +81 -0
- package/dist/shell/feedback-operations.js +48 -0
- package/dist/shell/fs-atomic.js +276 -0
- package/dist/shell/harness.js +98 -0
- package/dist/shell/ledger-cli.js +212 -0
- package/dist/shell/ledger-io.js +169 -0
- package/dist/shell/man-cli.js +89 -0
- package/dist/shell/metrics-cli.js +98 -0
- package/dist/shell/multi-agent-cli.js +1002 -0
- package/dist/shell/multi-agent-host.js +563 -0
- package/dist/shell/multi-agent-io.js +387 -0
- package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
- package/dist/shell/node-store.js +124 -0
- package/dist/{observability/format.js → shell/observability-format.js} +7 -1
- package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
- package/dist/{observability.js → shell/observability.js} +159 -332
- package/dist/{onramp.js → shell/onramp.js} +11 -0
- package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
- package/dist/shell/operator-ux.js +431 -0
- package/dist/shell/orchestrator.js +231 -0
- package/dist/shell/pipeline-cli.js +667 -0
- package/dist/shell/pipeline.js +217 -0
- package/dist/shell/reclamation-io.js +1366 -0
- package/dist/shell/registry-cli.js +329 -0
- package/dist/{remote-source.js → shell/remote-source.js} +12 -5
- package/dist/shell/report-cli.js +101 -0
- package/dist/shell/report-view-cli.js +117 -0
- package/dist/{orchestrator → shell}/report.js +289 -282
- package/dist/shell/reporter.js +62 -0
- package/dist/shell/run-export-cli.js +106 -0
- package/dist/shell/run-export.js +680 -0
- package/dist/shell/run-registry-io.js +1014 -0
- package/dist/shell/run-store.js +164 -0
- package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
- package/dist/{scheduler.js → shell/scheduler-io.js} +248 -48
- package/dist/shell/scheduling-io.js +311 -0
- package/dist/shell/state-cli.js +181 -0
- package/dist/shell/state-explosion-cli.js +197 -0
- package/dist/shell/telemetry-cli.js +85 -0
- package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
- package/dist/shell/telemetry-ledger-io.js +132 -0
- package/dist/{term.js → shell/term.js} +36 -46
- package/dist/shell/topology-io.js +361 -0
- package/dist/shell/trust-audit.js +471 -0
- package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
- package/dist/shell/verifier.js +48 -0
- package/dist/shell/workbench-host.js +250 -0
- package/dist/shell/workbench-text.js +18 -0
- package/dist/shell/workbench.js +175 -0
- package/dist/shell/worker-cli.js +124 -0
- package/dist/shell/worker-isolation.js +852 -0
- package/dist/shell/workflow-app-loader.js +650 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/cli-mcp-parity.7.md +284 -211
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/cross-agent-ledger.7.md +217 -0
- package/docs/designs/handoff-ledger.md +145 -0
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/handoff-setup.md +120 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/project-index.md +143 -71
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +4 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +25 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +9 -13
- package/package.json +1 -1
- package/scripts/agents/codex-agent.js +34 -4
- package/scripts/agents/cw-attest-wrap.js +2 -2
- package/scripts/bump-version.js +4 -3
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +30 -10
- package/scripts/dogfood-architecture-review.js +2 -3
- package/scripts/dogfood-release.js +3 -3
- package/scripts/gen-parity-doc.js +15 -2
- package/scripts/golden-path.js +4 -4
- package/scripts/onramp-check.js +1 -1
- package/scripts/parity-check.js +38 -21
- package/scripts/release-flow.js +9 -3
- package/scripts/sync-project-index.js +51 -27
- package/scripts/validate-run-state-schema.js +2 -2
- package/scripts/version-sync-check.js +30 -30
- package/dist/candidate-scoring.js +0 -729
- package/dist/capability-core.js +0 -1186
- package/dist/capability-registry.js +0 -879
- package/dist/cli/command-surface.js +0 -490
- package/dist/cli/format.js +0 -56
- package/dist/cli/handlers/audit.js +0 -82
- package/dist/cli/handlers/blackboard.js +0 -81
- package/dist/cli/handlers/candidate.js +0 -40
- package/dist/cli/handlers/clones.js +0 -34
- package/dist/cli/handlers/collaboration.js +0 -61
- package/dist/cli/handlers/eval.js +0 -40
- package/dist/cli/handlers/maintenance.js +0 -107
- package/dist/cli/handlers/multi-agent.js +0 -165
- package/dist/cli/handlers/node.js +0 -41
- package/dist/cli/handlers/operational.js +0 -155
- package/dist/cli/handlers/operator.js +0 -146
- package/dist/cli/handlers/registry.js +0 -68
- package/dist/cli/handlers/run.js +0 -153
- package/dist/cli/handlers/scheduling.js +0 -132
- package/dist/cli/handlers/workbench.js +0 -41
- package/dist/cli/handlers/worker.js +0 -45
- package/dist/cli/run-summary.js +0 -45
- package/dist/clones.js +0 -162
- package/dist/collaboration.js +0 -726
- package/dist/commit.js +0 -592
- package/dist/compare.js +0 -18
- package/dist/coordinator/classify.js +0 -45
- package/dist/coordinator/paths.js +0 -42
- package/dist/coordinator/util.js +0 -126
- package/dist/coordinator.js +0 -990
- package/dist/daemon.js +0 -44
- package/dist/dispatch.js +0 -250
- package/dist/drive.js +0 -827
- package/dist/error-feedback.js +0 -419
- package/dist/evidence-grounding.js +0 -184
- package/dist/execution-backend/agent.js +0 -294
- package/dist/execution-backend/probes.js +0 -112
- package/dist/execution-backend/util.js +0 -47
- package/dist/execution-backend.js +0 -961
- package/dist/gates.js +0 -48
- package/dist/harness.js +0 -61
- package/dist/loop-expansion.js +0 -60
- package/dist/mcp/tool-call.js +0 -434
- package/dist/mcp/tool-definitions.js +0 -1040
- package/dist/mcp-surface.js +0 -30
- package/dist/multi-agent/graph.js +0 -84
- package/dist/multi-agent/helpers.js +0 -141
- package/dist/multi-agent/ids.js +0 -20
- package/dist/multi-agent/paths.js +0 -22
- package/dist/multi-agent-eval/normalize.js +0 -51
- package/dist/multi-agent-eval.js +0 -678
- package/dist/multi-agent-host.js +0 -777
- package/dist/multi-agent.js +0 -984
- package/dist/node-projection.js +0 -59
- package/dist/node-snapshot.js +0 -260
- package/dist/operator-ux.js +0 -631
- package/dist/orchestrator/app-operations.js +0 -211
- package/dist/orchestrator/audit-operations.js +0 -182
- package/dist/orchestrator/candidate-operations.js +0 -117
- package/dist/orchestrator/cli-options.js +0 -294
- package/dist/orchestrator/collaboration-operations.js +0 -86
- package/dist/orchestrator/feedback-operations.js +0 -81
- package/dist/orchestrator/host-operations.js +0 -78
- package/dist/orchestrator/lifecycle-operations.js +0 -626
- package/dist/orchestrator/migration-operations.js +0 -44
- package/dist/orchestrator/multi-agent-operations.js +0 -362
- package/dist/orchestrator/topology-operations.js +0 -84
- package/dist/orchestrator.js +0 -917
- package/dist/pipeline-runner.js +0 -285
- package/dist/reclamation/hash.js +0 -72
- package/dist/reclamation.js +0 -812
- package/dist/reporter.js +0 -67
- package/dist/run-export.js +0 -784
- package/dist/run-registry/derive.js +0 -175
- package/dist/run-registry/format.js +0 -124
- package/dist/run-registry/gc.js +0 -251
- package/dist/run-registry/policy.js +0 -16
- package/dist/run-registry/queue.js +0 -115
- package/dist/run-registry.js +0 -850
- package/dist/run-state-schema.js +0 -68
- package/dist/scheduling.js +0 -184
- package/dist/state-explosion.js +0 -1014
- package/dist/state.js +0 -367
- package/dist/telemetry-ledger.js +0 -196
- package/dist/topology.js +0 -565
- package/dist/triggers.js +0 -184
- package/dist/trust-audit.js +0 -644
- package/dist/types/blackboard.js +0 -2
- package/dist/types/candidate.js +0 -2
- package/dist/types/collaboration.js +0 -2
- package/dist/types/core.js +0 -2
- package/dist/types/drive.js +0 -10
- package/dist/types/error-feedback.js +0 -2
- package/dist/types/evidence-reasoning.js +0 -2
- package/dist/types/execution-backend.js +0 -2
- package/dist/types/multi-agent.js +0 -2
- package/dist/types/observability.js +0 -2
- package/dist/types/pipeline.js +0 -2
- package/dist/types/reclamation.js +0 -8
- package/dist/types/report-bundle.js +0 -6
- package/dist/types/result.js +0 -2
- package/dist/types/run-registry.js +0 -2
- package/dist/types/run.js +0 -2
- package/dist/types/sandbox.js +0 -2
- package/dist/types/schedule.js +0 -2
- package/dist/types/state-node.js +0 -2
- package/dist/types/topology.js +0 -2
- package/dist/types/trust.js +0 -2
- package/dist/types/workbench.js +0 -2
- package/dist/types/worker.js +0 -2
- package/dist/types/workflow-app.js +0 -2
- package/dist/types.js +0 -44
- package/dist/util/fingerprint.js +0 -19
- package/dist/util/fingerprint.test.js +0 -27
- package/dist/verifier.js +0 -78
- package/dist/version.js +0 -8
- package/dist/workbench-host.js +0 -182
- package/dist/workbench.js +0 -192
- package/dist/worker-accept/acceptance.js +0 -114
- package/dist/worker-accept/blackboard-fanout.js +0 -80
- package/dist/worker-accept/blackboard-linkage.js +0 -19
- package/dist/worker-accept/context.js +0 -2
- package/dist/worker-accept/telemetry-ledger.js +0 -126
- package/dist/worker-accept/validation.js +0 -77
- package/dist/worker-accept/verifier-completion.js +0 -73
- package/dist/worker-isolation/helpers.js +0 -51
- package/dist/worker-isolation/paths.js +0 -46
- package/dist/worker-isolation.js +0 -656
- package/dist/workflow-api.js +0 -131
- /package/dist/{types → core/types}/boundary.js +0 -0
|
@@ -1,329 +1,191 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
// core/workflow-apps/app-schema.ts — workflow-app manifest + workflow DSL
|
|
3
|
+
// types, a pure builder API (`workflow`, `phase`, `parallel`, `loop`,
|
|
4
|
+
// `agent`, `artifact`, `subWorkflow`, `input`), and full manifest
|
|
5
|
+
// validation (`validateWorkflowApp`).
|
|
6
|
+
//
|
|
7
|
+
// MILESTONE 12 (v2/PLAN.md build order; workflow-apps.md). Milestone 6+7
|
|
8
|
+
// built the minimal real subset needed to load ONE app and drive it
|
|
9
|
+
// end to end (see git history / the old header note this replaces).
|
|
10
|
+
// This milestone adds, on top of that, WITHOUT changing any existing
|
|
11
|
+
// exported function's signature or behavior:
|
|
12
|
+
// - `loop()` — sugar over `phase()` that sets a bounded dynamic loop
|
|
13
|
+
// spec, byte-exact to src/workflow-api.ts.
|
|
14
|
+
// - `subWorkflow()` — sugar over `agent()` that adds a `subWorkflow`
|
|
15
|
+
// field, byte-exact to src/workflow-api.ts.
|
|
16
|
+
// - `validateWorkflowApp`/`assertValidWorkflowApp`/
|
|
17
|
+
// `validateWorkflowDefinition` — the full WorkflowAppValidationIssue
|
|
18
|
+
// taxonomy `cw app validate` needs, ported byte-for-byte from
|
|
19
|
+
// src/workflow-app-framework.ts.
|
|
20
|
+
// - `WorkflowAppValidationError` — thrown by a fail-closed manifest
|
|
21
|
+
// load (see shell/workflow-app-loader.ts's discovery functions).
|
|
22
|
+
//
|
|
23
|
+
// Pure factory + validation functions (this file, no `fs`/`child_process`/
|
|
24
|
+
// `process.env`); the actual `require()`/fs read of an app's manifest +
|
|
25
|
+
// entrypoint, and `cw app list/show/validate/init/package` discovery,
|
|
26
|
+
// live in shell/ (see shell/workflow-app-loader.ts), per the core/shell
|
|
27
|
+
// split.
|
|
5
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.WorkflowAppValidationError =
|
|
7
|
-
exports.
|
|
29
|
+
exports.WorkflowAppValidationError = void 0;
|
|
30
|
+
exports.slugify = slugify;
|
|
31
|
+
exports.workflow = workflow;
|
|
32
|
+
exports.phase = phase;
|
|
33
|
+
exports.parallel = parallel;
|
|
34
|
+
exports.loop = loop;
|
|
35
|
+
exports.agent = agent;
|
|
36
|
+
exports.subWorkflow = subWorkflow;
|
|
37
|
+
exports.artifact = artifact;
|
|
38
|
+
exports.input = input;
|
|
39
|
+
exports.createWorkflowApi = createWorkflowApi;
|
|
40
|
+
exports.validateWorkflowDefinition = validateWorkflowDefinition;
|
|
8
41
|
exports.validateWorkflowApp = validateWorkflowApp;
|
|
9
42
|
exports.assertValidWorkflowApp = assertValidWorkflowApp;
|
|
10
|
-
exports.
|
|
11
|
-
exports.
|
|
12
|
-
exports.loadWorkflowAppFromManifest = loadWorkflowAppFromManifest;
|
|
13
|
-
exports.summarizeWorkflowApp = summarizeWorkflowApp;
|
|
43
|
+
exports.isWorkflowAppCompatible = isWorkflowAppCompatible;
|
|
44
|
+
exports.validationIssuesFromError = validationIssuesFromError;
|
|
14
45
|
exports.workflowAppRunMetadata = workflowAppRunMetadata;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
46
|
+
function slugify(value) {
|
|
47
|
+
return String(value)
|
|
48
|
+
.trim()
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
51
|
+
.replace(/^-|-$/g, "")
|
|
52
|
+
.replace(/-{2,}/g, "-");
|
|
53
|
+
}
|
|
54
|
+
function workflow(definition) {
|
|
55
|
+
if (!definition.id)
|
|
56
|
+
throw new Error("workflow.id is required");
|
|
57
|
+
if (!definition.title)
|
|
58
|
+
throw new Error("workflow.title is required");
|
|
59
|
+
if (!Array.isArray(definition.phases))
|
|
60
|
+
throw new Error("workflow.phases must be an array");
|
|
61
|
+
return {
|
|
62
|
+
limits: { maxAgents: 20, maxConcurrentAgents: 4, ...(definition.limits || {}) },
|
|
63
|
+
inputs: [],
|
|
64
|
+
summary: "",
|
|
65
|
+
...definition,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function phase(name, tasks, options = {}) {
|
|
69
|
+
if (!name)
|
|
70
|
+
throw new Error("phase name is required");
|
|
71
|
+
if (!Array.isArray(tasks))
|
|
72
|
+
throw new Error(`phase ${name} tasks must be an array`);
|
|
73
|
+
return { id: slugify(name), name, status: "pending", tasks, ...options };
|
|
74
|
+
}
|
|
75
|
+
function parallel(name, tasks, options = {}) {
|
|
76
|
+
return phase(name, tasks, { mode: "parallel", ...options });
|
|
77
|
+
}
|
|
78
|
+
/** A BOUNDED DYNAMIC LOOP phase: `tasks` are a per-round template. After each
|
|
79
|
+
* round completes, the registered `until` predicate decides whether to run
|
|
80
|
+
* another round (a fresh appended phase with the same tasks, round-suffixed
|
|
81
|
+
* ids) or stop; capped at `maxRounds`. Sugar over phase() that sets `loop`;
|
|
82
|
+
* plain phases are unaffected. Byte-exact to src/workflow-api.ts's loop(). */
|
|
83
|
+
function loop(name, tasks, spec, options = {}) {
|
|
84
|
+
if (!spec || typeof spec.maxRounds !== "number" || spec.maxRounds < 1) {
|
|
85
|
+
throw new Error(`loop ${name} requires a positive integer maxRounds`);
|
|
86
|
+
}
|
|
87
|
+
const until = spec.until;
|
|
88
|
+
const valid = until &&
|
|
89
|
+
((until.kind === "predicate" && Boolean(until.ref)) ||
|
|
90
|
+
(until.kind === "budget-target" && typeof until.target === "number" && until.target > 0));
|
|
91
|
+
if (!valid) {
|
|
92
|
+
throw new Error(`loop ${name} requires until: { kind: "predicate", ref } or { kind: "budget-target", target }`);
|
|
93
|
+
}
|
|
94
|
+
return phase(name, tasks, { loop: { maxRounds: Math.floor(spec.maxRounds), until }, ...options });
|
|
95
|
+
}
|
|
96
|
+
function task(kind, id, prompt, options) {
|
|
97
|
+
if (!id)
|
|
98
|
+
throw new Error(`${kind} task id is required`);
|
|
99
|
+
if (!prompt)
|
|
100
|
+
throw new Error(`${kind} task ${id} prompt is required`);
|
|
101
|
+
return {
|
|
102
|
+
id,
|
|
103
|
+
kind,
|
|
104
|
+
prompt,
|
|
105
|
+
status: "pending",
|
|
106
|
+
sandboxProfileId: typeof options.sandboxProfileId === "string" ? options.sandboxProfileId : undefined,
|
|
107
|
+
...options,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function agent(id, prompt, options = {}) {
|
|
111
|
+
return task("agent", id, prompt, options);
|
|
112
|
+
}
|
|
113
|
+
/** A task fulfilled by an inline SUB-WORKFLOW: instead of spawning an agent,
|
|
114
|
+
* the drive plans + drives the child `appId` and binds its report back as
|
|
115
|
+
* this task's result. The prompt is recorded for provenance but is not sent
|
|
116
|
+
* to an agent. Byte-exact to src/workflow-api.ts's subWorkflow(). */
|
|
117
|
+
function subWorkflow(id, appId, options = {}) {
|
|
118
|
+
if (!appId)
|
|
119
|
+
throw new Error(`subWorkflow task ${id} requires an appId`);
|
|
120
|
+
const { inputs, bindResult, prompt, ...rest } = options;
|
|
121
|
+
return task("agent", id, prompt || `Delegate to sub-workflow app: ${appId}`, {
|
|
122
|
+
...rest,
|
|
123
|
+
subWorkflow: { appId, ...(inputs ? { inputs } : {}), ...(bindResult ? { bindResult } : {}) },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function artifact(id, prompt, options = {}) {
|
|
127
|
+
return task("artifact", id, prompt, options);
|
|
128
|
+
}
|
|
129
|
+
function input(name, options = {}) {
|
|
130
|
+
if (!name)
|
|
131
|
+
throw new Error("input name is required");
|
|
132
|
+
return { name, ...options };
|
|
133
|
+
}
|
|
134
|
+
function createWorkflowApi() {
|
|
135
|
+
return { workflow, phase, parallel, loop, agent, artifact, subWorkflow, input };
|
|
136
|
+
}
|
|
31
137
|
class WorkflowAppValidationError extends Error {
|
|
32
138
|
issues;
|
|
33
139
|
constructor(message, issues) {
|
|
34
|
-
super(`${message}: ${issues.map((
|
|
140
|
+
super(`${message}: ${issues.map((one) => one.message).join("; ")}`);
|
|
35
141
|
this.name = "WorkflowAppValidationError";
|
|
36
142
|
this.issues = issues;
|
|
37
143
|
}
|
|
38
144
|
}
|
|
39
145
|
exports.WorkflowAppValidationError = WorkflowAppValidationError;
|
|
40
|
-
function
|
|
41
|
-
|
|
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
|
-
}
|
|
146
|
+
function issue(code, message, pathName) {
|
|
147
|
+
return { code, message, path: pathName };
|
|
98
148
|
}
|
|
99
|
-
function
|
|
100
|
-
if (!
|
|
101
|
-
|
|
102
|
-
|
|
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;
|
|
149
|
+
function joinPath(basePath, segment) {
|
|
150
|
+
if (!basePath)
|
|
151
|
+
return segment;
|
|
152
|
+
return `${basePath}.${segment}`;
|
|
122
153
|
}
|
|
123
|
-
function
|
|
124
|
-
|
|
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;
|
|
154
|
+
function isRecord(value) {
|
|
155
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
215
156
|
}
|
|
216
|
-
function
|
|
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
|
-
};
|
|
157
|
+
function isNonEmptyString(value) {
|
|
158
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
229
159
|
}
|
|
230
|
-
function
|
|
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
|
-
};
|
|
160
|
+
function isSemver(value) {
|
|
161
|
+
return typeof value === "string" && /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value);
|
|
249
162
|
}
|
|
250
|
-
function
|
|
251
|
-
|
|
163
|
+
function semverParts(value) {
|
|
164
|
+
const [major, minor, patch] = value
|
|
165
|
+
.split(/[+-]/)[0]
|
|
166
|
+
.split(".")
|
|
167
|
+
.map((part) => Number(part));
|
|
168
|
+
return [major || 0, minor || 0, patch || 0];
|
|
252
169
|
}
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
});
|
|
170
|
+
function compareSemver(left, right) {
|
|
171
|
+
const leftParts = semverParts(left);
|
|
172
|
+
const rightParts = semverParts(right);
|
|
173
|
+
for (let index = 0; index < 3; index += 1) {
|
|
174
|
+
if (leftParts[index] !== rightParts[index])
|
|
175
|
+
return leftParts[index] < rightParts[index] ? -1 : 1;
|
|
301
176
|
}
|
|
302
|
-
return
|
|
177
|
+
return 0;
|
|
303
178
|
}
|
|
304
|
-
function
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
]);
|
|
179
|
+
function isWorkflowDefinition(value) {
|
|
180
|
+
return (isRecord(value) &&
|
|
181
|
+
isNonEmptyString(value.id) &&
|
|
182
|
+
isNonEmptyString(value.title) &&
|
|
183
|
+
Array.isArray(value.phases) &&
|
|
184
|
+
isRecord(value.limits) &&
|
|
185
|
+
Array.isArray(value.inputs));
|
|
317
186
|
}
|
|
318
|
-
function
|
|
319
|
-
|
|
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);
|
|
187
|
+
function isWorkflowEntrypoint(value) {
|
|
188
|
+
return isRecord(value) && "entrypoint" in value && !("phases" in value);
|
|
327
189
|
}
|
|
328
190
|
function validateAppId(value, issues, pathName) {
|
|
329
191
|
if (!isNonEmptyString(value)) {
|
|
@@ -409,7 +271,7 @@ function validateMatchingInputs(appInputs, workflowInputs, issues, pathName) {
|
|
|
409
271
|
issues.push(issue("workflow-app-inputs-mismatch", "Workflow app inputs must match workflow.inputs when both are present", pathName));
|
|
410
272
|
}
|
|
411
273
|
}
|
|
412
|
-
function validateSandboxProfileReferences(profiles, issues, pathName, options = { optional: false }) {
|
|
274
|
+
function validateSandboxProfileReferences(profiles, issues, pathName, bundledIds, options = { optional: false }) {
|
|
413
275
|
if (profiles === undefined && options.optional)
|
|
414
276
|
return;
|
|
415
277
|
if (!Array.isArray(profiles)) {
|
|
@@ -417,7 +279,6 @@ function validateSandboxProfileReferences(profiles, issues, pathName, options =
|
|
|
417
279
|
return;
|
|
418
280
|
}
|
|
419
281
|
const seen = new Set();
|
|
420
|
-
const bundled = (0, sandbox_profile_1.bundledSandboxProfileIds)();
|
|
421
282
|
for (const [index, value] of profiles.entries()) {
|
|
422
283
|
const profilePath = joinPath(pathName, String(index));
|
|
423
284
|
if (!isNonEmptyString(value)) {
|
|
@@ -428,20 +289,20 @@ function validateSandboxProfileReferences(profiles, issues, pathName, options =
|
|
|
428
289
|
issues.push(issue("workflow-sandbox-profile-duplicate", `Duplicate sandbox profile reference: ${value}`, profilePath));
|
|
429
290
|
}
|
|
430
291
|
seen.add(value);
|
|
431
|
-
if (!
|
|
432
|
-
issues.push(issue("workflow-sandbox-profile-unknown", `Unknown sandbox profile ${value}; bundled profiles: ${
|
|
292
|
+
if (!bundledIds.includes(value)) {
|
|
293
|
+
issues.push(issue("workflow-sandbox-profile-unknown", `Unknown sandbox profile ${value}; bundled profiles: ${bundledIds.join(", ")}`, profilePath));
|
|
433
294
|
}
|
|
434
295
|
}
|
|
435
296
|
}
|
|
436
|
-
function validateCompatibility(value, issues, pathName) {
|
|
297
|
+
function validateCompatibility(value, issues, pathName, currentVersion) {
|
|
437
298
|
if (value === undefined)
|
|
438
299
|
return;
|
|
439
300
|
if (!isRecord(value)) {
|
|
440
301
|
issues.push(issue("workflow-app-compatibility", "Workflow app compatibility must be an object", pathName));
|
|
441
302
|
return;
|
|
442
303
|
}
|
|
443
|
-
if (value.workflowSchemaVersion !== undefined && value.workflowSchemaVersion !==
|
|
444
|
-
issues.push(issue("workflow-app-compatibility",
|
|
304
|
+
if (value.workflowSchemaVersion !== undefined && value.workflowSchemaVersion !== 1) {
|
|
305
|
+
issues.push(issue("workflow-app-compatibility", "Workflow schema version must be 1", joinPath(pathName, "workflowSchemaVersion")));
|
|
445
306
|
}
|
|
446
307
|
for (const key of ["coolWorkflow", "node", "notes"]) {
|
|
447
308
|
if (value[key] !== undefined && !isNonEmptyString(value[key])) {
|
|
@@ -452,16 +313,16 @@ function validateCompatibility(value, issues, pathName) {
|
|
|
452
313
|
if (!isSemver(value.minVersion)) {
|
|
453
314
|
issues.push(issue("workflow-app-compatibility", "Compatibility minVersion must be semver", joinPath(pathName, "minVersion")));
|
|
454
315
|
}
|
|
455
|
-
else if (compareSemver(
|
|
456
|
-
issues.push(issue("workflow-app-incompatible", `Workflow app requires Cool Workflow >= ${value.minVersion}; current is ${
|
|
316
|
+
else if (compareSemver(currentVersion, value.minVersion) < 0) {
|
|
317
|
+
issues.push(issue("workflow-app-incompatible", `Workflow app requires Cool Workflow >= ${value.minVersion}; current is ${currentVersion}`, joinPath(pathName, "minVersion")));
|
|
457
318
|
}
|
|
458
319
|
}
|
|
459
320
|
if (value.maxVersion !== undefined) {
|
|
460
321
|
if (!isSemver(value.maxVersion)) {
|
|
461
322
|
issues.push(issue("workflow-app-compatibility", "Compatibility maxVersion must be semver", joinPath(pathName, "maxVersion")));
|
|
462
323
|
}
|
|
463
|
-
else if (compareSemver(
|
|
464
|
-
issues.push(issue("workflow-app-incompatible", `Workflow app supports Cool Workflow <= ${value.maxVersion}; current is ${
|
|
324
|
+
else if (compareSemver(currentVersion, value.maxVersion) > 0) {
|
|
325
|
+
issues.push(issue("workflow-app-incompatible", `Workflow app supports Cool Workflow <= ${value.maxVersion}; current is ${currentVersion}`, joinPath(pathName, "maxVersion")));
|
|
465
326
|
}
|
|
466
327
|
}
|
|
467
328
|
}
|
|
@@ -469,7 +330,7 @@ function validateEntrypoint(value, issues, pathName) {
|
|
|
469
330
|
if (!isNonEmptyString(value.entrypoint)) {
|
|
470
331
|
issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.entrypoint is required", joinPath(pathName, "entrypoint")));
|
|
471
332
|
}
|
|
472
|
-
if (value.entrypoint &&
|
|
333
|
+
if (value.entrypoint && /^([a-zA-Z]:)?[/\\]/.test(value.entrypoint)) {
|
|
473
334
|
issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.entrypoint must be relative", joinPath(pathName, "entrypoint")));
|
|
474
335
|
}
|
|
475
336
|
if (value.entrypoint && value.entrypoint.split(/[\\/]/).includes("..")) {
|
|
@@ -479,26 +340,41 @@ function validateEntrypoint(value, issues, pathName) {
|
|
|
479
340
|
issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.exportName must be a string", joinPath(pathName, "exportName")));
|
|
480
341
|
}
|
|
481
342
|
}
|
|
482
|
-
function
|
|
483
|
-
if (!
|
|
484
|
-
issues.push(issue("workflow-
|
|
343
|
+
function validateTask(taskDefinition, issues, pathName, seenTaskIds, options) {
|
|
344
|
+
if (!isRecord(taskDefinition)) {
|
|
345
|
+
issues.push(issue("workflow-task", "Workflow task must be an object", pathName));
|
|
485
346
|
return;
|
|
486
347
|
}
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
}
|
|
348
|
+
const taskValue = taskDefinition;
|
|
349
|
+
if (!isNonEmptyString(taskValue.id) || !/^[A-Za-z0-9][A-Za-z0-9_.:/-]*$/.test(taskValue.id)) {
|
|
350
|
+
issues.push(issue("workflow-task-id", `Workflow task id is malformed: ${String(taskValue.id || "")}`, joinPath(pathName, "id")));
|
|
499
351
|
}
|
|
500
|
-
if (
|
|
501
|
-
issues.push(issue("workflow-
|
|
352
|
+
else if (seenTaskIds.has(taskValue.id)) {
|
|
353
|
+
issues.push(issue("workflow-task-duplicate", `Duplicate workflow task id: ${taskValue.id}`, joinPath(pathName, "id")));
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
seenTaskIds.add(taskValue.id);
|
|
357
|
+
}
|
|
358
|
+
if (!["agent", "artifact"].includes(String(taskValue.kind))) {
|
|
359
|
+
issues.push(issue("workflow-task-kind", `Workflow task ${String(taskValue.id || "")} kind must be agent or artifact`, joinPath(pathName, "kind")));
|
|
360
|
+
}
|
|
361
|
+
if (!isNonEmptyString(taskValue.prompt)) {
|
|
362
|
+
issues.push(issue("workflow-task-prompt", `Workflow task ${String(taskValue.id || "")} prompt is required`, joinPath(pathName, "prompt")));
|
|
363
|
+
}
|
|
364
|
+
if (taskValue.requiresEvidence !== undefined && typeof taskValue.requiresEvidence !== "boolean") {
|
|
365
|
+
issues.push(issue("workflow-task-evidence", `Workflow task ${String(taskValue.id || "")} requiresEvidence must be boolean`, joinPath(pathName, "requiresEvidence")));
|
|
366
|
+
}
|
|
367
|
+
const sandboxProfileId = taskValue.sandboxProfileId;
|
|
368
|
+
if (sandboxProfileId !== undefined) {
|
|
369
|
+
if (!isNonEmptyString(sandboxProfileId)) {
|
|
370
|
+
issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandboxProfileId must be a string`, joinPath(pathName, "sandboxProfileId")));
|
|
371
|
+
}
|
|
372
|
+
else if (!options.bundledIds.includes(sandboxProfileId)) {
|
|
373
|
+
issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} references unknown sandbox profile ${sandboxProfileId}`, joinPath(pathName, "sandboxProfileId")));
|
|
374
|
+
}
|
|
375
|
+
else if (options.appSandboxProfiles && !options.appSandboxProfiles.includes(sandboxProfileId)) {
|
|
376
|
+
issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandbox profile ${sandboxProfileId} must be listed in app sandboxProfiles`, joinPath(pathName, "sandboxProfileId")));
|
|
377
|
+
}
|
|
502
378
|
}
|
|
503
379
|
}
|
|
504
380
|
function validatePhase(phaseDefinition, issues, pathName, seenPhaseIds) {
|
|
@@ -522,125 +398,160 @@ function validatePhase(phaseDefinition, issues, pathName, seenPhaseIds) {
|
|
|
522
398
|
if (!Array.isArray(phaseValue.tasks) || !phaseValue.tasks.length) {
|
|
523
399
|
issues.push(issue("workflow-phase-tasks", `Workflow phase ${String(phaseValue.id || phaseValue.name || "")} must have tasks`, joinPath(pathName, "tasks")));
|
|
524
400
|
}
|
|
525
|
-
// Bounded dynamic loop spec (fail-closed shape check; the predicate ref need not be
|
|
526
|
-
// registered at validation time — the expander stops fail-closed if it is missing).
|
|
527
401
|
if (phaseValue.loop !== undefined) {
|
|
528
|
-
const
|
|
529
|
-
if (!isRecord(
|
|
402
|
+
const loopValue = phaseValue.loop;
|
|
403
|
+
if (!isRecord(loopValue)) {
|
|
530
404
|
issues.push(issue("workflow-phase-loop", "Workflow phase loop must be an object", joinPath(pathName, "loop")));
|
|
531
405
|
}
|
|
532
406
|
else {
|
|
533
|
-
if (typeof
|
|
407
|
+
if (typeof loopValue.maxRounds !== "number" || !Number.isInteger(loopValue.maxRounds) || loopValue.maxRounds < 1) {
|
|
534
408
|
issues.push(issue("workflow-phase-loop-maxrounds", "loop.maxRounds must be a positive integer", joinPath(pathName, "loop.maxRounds")));
|
|
535
409
|
}
|
|
536
|
-
const until =
|
|
537
|
-
const validUntil = isRecord(until)
|
|
538
|
-
|
|
539
|
-
|
|
410
|
+
const until = loopValue.until;
|
|
411
|
+
const validUntil = isRecord(until) &&
|
|
412
|
+
((until.kind === "predicate" && isNonEmptyString(until.ref)) ||
|
|
413
|
+
(until.kind === "budget-target" && typeof until.target === "number" && until.target > 0));
|
|
540
414
|
if (!validUntil) {
|
|
541
415
|
issues.push(issue("workflow-phase-loop-until", 'loop.until must be { kind: "predicate", ref } or { kind: "budget-target", target }', joinPath(pathName, "loop.until")));
|
|
542
416
|
}
|
|
543
417
|
}
|
|
544
418
|
}
|
|
545
419
|
}
|
|
546
|
-
function
|
|
547
|
-
if (!
|
|
548
|
-
issues.push(issue("workflow-
|
|
420
|
+
function validatePhases(phases, limits, issues, pathName, options) {
|
|
421
|
+
if (!Array.isArray(phases) || !phases.length) {
|
|
422
|
+
issues.push(issue("workflow-phases", "Workflow phases must be a non-empty array", pathName));
|
|
549
423
|
return;
|
|
550
424
|
}
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
425
|
+
const seenPhaseIds = new Set();
|
|
426
|
+
const seenTaskIds = new Set();
|
|
427
|
+
let taskCount = 0;
|
|
428
|
+
for (const [phaseIndex, phaseDefinition] of phases.entries()) {
|
|
429
|
+
const phasePath = joinPath(pathName, String(phaseIndex));
|
|
430
|
+
validatePhase(phaseDefinition, issues, phasePath, seenPhaseIds);
|
|
431
|
+
if (!isRecord(phaseDefinition) || !Array.isArray(phaseDefinition.tasks))
|
|
432
|
+
continue;
|
|
433
|
+
for (const [taskIndex, taskDefinition] of phaseDefinition.tasks.entries()) {
|
|
434
|
+
taskCount += 1;
|
|
435
|
+
validateTask(taskDefinition, issues, joinPath(joinPath(phasePath, "tasks"), String(taskIndex)), seenTaskIds, options);
|
|
436
|
+
}
|
|
557
437
|
}
|
|
558
|
-
|
|
559
|
-
|
|
438
|
+
if (isRecord(limits) && Number.isInteger(Number(limits.maxAgents)) && taskCount > Number(limits.maxAgents)) {
|
|
439
|
+
issues.push(issue("workflow-limits", `Workflow defines ${taskCount} tasks but limits.maxAgents is ${String(limits.maxAgents)}`, joinPath(pathName, "limits.maxAgents")));
|
|
560
440
|
}
|
|
561
|
-
|
|
562
|
-
|
|
441
|
+
}
|
|
442
|
+
function validateWorkflowDefinition(candidate, context, issues = [], basePath = "workflow", options = {}) {
|
|
443
|
+
if (!isRecord(candidate)) {
|
|
444
|
+
issues.push(issue("workflow-invalid", "Workflow definition must be an object", basePath));
|
|
445
|
+
return issues;
|
|
563
446
|
}
|
|
564
|
-
|
|
565
|
-
|
|
447
|
+
const workflowDefinition = candidate;
|
|
448
|
+
validateWorkflowId(workflowDefinition.id, issues, joinPath(basePath, "id"));
|
|
449
|
+
if (!isNonEmptyString(workflowDefinition.title)) {
|
|
450
|
+
issues.push(issue("workflow-title", "Workflow title is required", joinPath(basePath, "title")));
|
|
566
451
|
}
|
|
567
|
-
if (
|
|
568
|
-
issues.push(issue("workflow-
|
|
452
|
+
if (options.appId && workflowDefinition.id !== options.appId) {
|
|
453
|
+
issues.push(issue("workflow-app-id-mismatch", `Workflow id must match app id ${options.appId}`, joinPath(basePath, "id")));
|
|
569
454
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (!isNonEmptyString(sandboxProfileId)) {
|
|
573
|
-
issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandboxProfileId must be a string`, joinPath(pathName, "sandboxProfileId")));
|
|
574
|
-
}
|
|
575
|
-
else if (!(0, sandbox_profile_1.isBundledSandboxProfileId)(sandboxProfileId)) {
|
|
576
|
-
issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} references unknown sandbox profile ${sandboxProfileId}`, joinPath(pathName, "sandboxProfileId")));
|
|
577
|
-
}
|
|
578
|
-
else if (options.appSandboxProfiles && !options.appSandboxProfiles.includes(sandboxProfileId)) {
|
|
579
|
-
issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandbox profile ${sandboxProfileId} must be listed in app sandboxProfiles`, joinPath(pathName, "sandboxProfileId")));
|
|
580
|
-
}
|
|
455
|
+
if (options.appTitle && workflowDefinition.title !== options.appTitle) {
|
|
456
|
+
issues.push(issue("workflow-app-title-mismatch", `Workflow title must match app title ${options.appTitle}`, joinPath(basePath, "title")));
|
|
581
457
|
}
|
|
458
|
+
validateLimits(workflowDefinition.limits, issues, joinPath(basePath, "limits"));
|
|
459
|
+
validateInputDefinitions(workflowDefinition.inputs, issues, joinPath(basePath, "inputs"), { optional: false });
|
|
460
|
+
validateSandboxProfileReferences(workflowDefinition.sandboxProfiles, issues, joinPath(basePath, "sandboxProfiles"), context.bundledSandboxProfileIds, {
|
|
461
|
+
optional: true,
|
|
462
|
+
});
|
|
463
|
+
validatePhases(workflowDefinition.phases, workflowDefinition.limits, issues, joinPath(basePath, "phases"), {
|
|
464
|
+
appSandboxProfiles: options.appSandboxProfiles || workflowDefinition.sandboxProfiles,
|
|
465
|
+
bundledIds: context.bundledSandboxProfileIds,
|
|
466
|
+
});
|
|
467
|
+
return issues;
|
|
582
468
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
469
|
+
/** The full `cw app validate` check: byte-exact issue codes/messages to
|
|
470
|
+
* src/workflow-app-framework.ts's `validateWorkflowApp`. Never throws. */
|
|
471
|
+
function validateWorkflowApp(candidate, context, options = {}) {
|
|
472
|
+
const issues = [];
|
|
473
|
+
const appPath = options.appPath;
|
|
474
|
+
if (!isRecord(candidate)) {
|
|
475
|
+
return { valid: false, appPath, issues: [issue("workflow-app-invalid", "Workflow app must be an object", appPath)] };
|
|
476
|
+
}
|
|
477
|
+
const app = candidate;
|
|
478
|
+
if (app.schemaVersion !== 1) {
|
|
479
|
+
issues.push(issue("workflow-app-schema-version", "Workflow app schemaVersion must be 1", joinPath(appPath, "schemaVersion")));
|
|
480
|
+
}
|
|
481
|
+
validateAppId(app.id, issues, joinPath(appPath, "id"));
|
|
482
|
+
if (!isNonEmptyString(app.title)) {
|
|
483
|
+
issues.push(issue("workflow-app-title", "Workflow app title is required", joinPath(appPath, "title")));
|
|
484
|
+
}
|
|
485
|
+
if (!isSemver(app.version)) {
|
|
486
|
+
issues.push(issue("workflow-app-version", "Workflow app version must be a semver string such as 0.1.0", joinPath(appPath, "version")));
|
|
487
|
+
}
|
|
488
|
+
validateAuthor(app.author, issues, joinPath(appPath, "author"));
|
|
489
|
+
validateInputDefinitions(app.inputs, issues, joinPath(appPath, "inputs"), { optional: true });
|
|
490
|
+
validateSandboxProfileReferences(app.sandboxProfiles, issues, joinPath(appPath, "sandboxProfiles"), context.bundledSandboxProfileIds, { optional: true });
|
|
491
|
+
validateCompatibility(app.compatibility, issues, joinPath(appPath, "compatibility"), context.currentCoolWorkflowVersion);
|
|
492
|
+
const workflowValue = options.loadedWorkflow || app.workflow;
|
|
493
|
+
if (!workflowValue) {
|
|
494
|
+
issues.push(issue("workflow-app-workflow", "Workflow app workflow is required", joinPath(appPath, "workflow")));
|
|
495
|
+
}
|
|
496
|
+
else if (isWorkflowEntrypoint(workflowValue)) {
|
|
497
|
+
validateEntrypoint(workflowValue, issues, joinPath(appPath, "workflow"));
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
validateWorkflowDefinition(workflowValue, context, issues, joinPath(appPath, "workflow"), {
|
|
501
|
+
appId: isNonEmptyString(app.id) ? app.id : undefined,
|
|
502
|
+
appTitle: isNonEmptyString(app.title) ? app.title : undefined,
|
|
503
|
+
appSandboxProfiles: app.sandboxProfiles,
|
|
504
|
+
});
|
|
505
|
+
if (Array.isArray(app.inputs) && isWorkflowDefinition(workflowValue)) {
|
|
506
|
+
validateMatchingInputs(app.inputs, workflowValue.inputs || [], issues, joinPath(appPath, "inputs"));
|
|
606
507
|
}
|
|
607
508
|
}
|
|
608
|
-
return
|
|
509
|
+
return {
|
|
510
|
+
valid: issues.length === 0,
|
|
511
|
+
appId: isNonEmptyString(app.id) ? app.id : undefined,
|
|
512
|
+
appPath,
|
|
513
|
+
issues,
|
|
514
|
+
};
|
|
609
515
|
}
|
|
610
|
-
function
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if (leftParts[index] !== rightParts[index])
|
|
615
|
-
return leftParts[index] < rightParts[index] ? -1 : 1;
|
|
516
|
+
function assertValidWorkflowApp(candidate, context, options = {}) {
|
|
517
|
+
const result = validateWorkflowApp(candidate, context, options);
|
|
518
|
+
if (!result.valid) {
|
|
519
|
+
throw new WorkflowAppValidationError("Invalid workflow app", result.issues);
|
|
616
520
|
}
|
|
617
|
-
return 0;
|
|
618
521
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
function
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
522
|
+
/** `isAppCompatible` (WorkflowAppSummary.compatible): re-validate and check
|
|
523
|
+
* ONLY for a `workflow-app-incompatible` issue — see PLAN.md rebuild risk
|
|
524
|
+
* #3. */
|
|
525
|
+
function isWorkflowAppCompatible(app, context) {
|
|
526
|
+
return !validateWorkflowApp(app, context).issues.some((one) => one.code === "workflow-app-incompatible");
|
|
527
|
+
}
|
|
528
|
+
/** `validationIssuesFromError` — turns any thrown error (typically a
|
|
529
|
+
* `WorkflowAppValidationError`) into an issues array for the
|
|
530
|
+
* `{valid:false, issues}` catch-branch shape `cw app validate` needs on
|
|
531
|
+
* an unresolvable target. */
|
|
532
|
+
function validationIssuesFromError(error) {
|
|
533
|
+
if (error instanceof WorkflowAppValidationError)
|
|
534
|
+
return error.issues;
|
|
535
|
+
return [{ code: "workflow-app-invalid", message: error instanceof Error ? error.message : String(error) }];
|
|
536
|
+
}
|
|
537
|
+
/** Byte-exact render of the workflow-app run-metadata block that lands in
|
|
538
|
+
* `run.workflow.app` (report.md's "Workflow App:" line reads
|
|
539
|
+
* `run.workflow.app.id`/`.version`). */
|
|
540
|
+
function workflowAppRunMetadata(app) {
|
|
633
541
|
return {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
542
|
+
schemaVersion: 1,
|
|
543
|
+
id: app.id,
|
|
544
|
+
title: app.title,
|
|
545
|
+
summary: app.summary,
|
|
546
|
+
version: app.version,
|
|
547
|
+
author: app.author,
|
|
548
|
+
// compatibility + metadata ride into run.workflow.app so report.md can
|
|
549
|
+
// render the domain-gated "- Source:" label (metadata.domain ===
|
|
550
|
+
// "research") and downstream tools can read the app's compatibility
|
|
551
|
+
// window. Byte-behavior port of the old build's workflowAppRunMetadata.
|
|
552
|
+
compatibility: app.compatibility,
|
|
553
|
+
sandboxProfiles: app.sandboxProfiles,
|
|
554
|
+
source: { manifestPath: app.sourcePath },
|
|
555
|
+
metadata: app.metadata,
|
|
637
556
|
};
|
|
638
557
|
}
|
|
639
|
-
function joinPath(basePath, segment) {
|
|
640
|
-
if (!basePath)
|
|
641
|
-
return segment;
|
|
642
|
-
return `${basePath}.${segment}`;
|
|
643
|
-
}
|
|
644
|
-
function messageOf(error) {
|
|
645
|
-
return error instanceof Error ? error.message : String(error);
|
|
646
|
-
}
|