cool-workflow 0.1.98 → 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} +2 -2
- 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 +2 -0
- package/docs/cli-mcp-parity.7.md +280 -219
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/durable-state-and-locking.7.md +2 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +132 -71
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +2 -0
- package/docs/release-tooling.7.md +2 -0
- package/docs/run-registry-control-plane.7.md +2 -0
- package/docs/run-retention-reclamation.7.md +23 -0
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/web-desktop-workbench.7.md +2 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +9 -13
- package/package.json +1 -1
- 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/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 +2 -2
- 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 -1189
- package/dist/capability-registry.js +0 -885
- package/dist/cli/command-surface.js +0 -494
- 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/ledger.js +0 -169
- 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 -864
- package/dist/error-feedback.js +0 -419
- package/dist/evidence-grounding.js +0 -184
- package/dist/execution-backend/agent.js +0 -354
- 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/ledger.js +0 -313
- package/dist/loop-expansion.js +0 -60
- package/dist/mcp/tool-call.js +0 -470
- package/dist/mcp/tool-definitions.js +0 -1066
- 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 -650
- 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 -925
- 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 -815
- 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 -192
- 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
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// shell/worker-isolation.ts — worker scope allocation, recordWorkerOutput's
|
|
3
|
+
// accept pipeline.
|
|
4
|
+
//
|
|
5
|
+
// MILESTONE 6+7 (combined). Byte-exact port of the old build's
|
|
6
|
+
// src/worker-isolation.ts's real-execution-path (allocateWorkerScope,
|
|
7
|
+
// writeWorkerManifest, recordWorkerOutput, recordWorkerFailure,
|
|
8
|
+
// recordWorkerRetryAttempt, the worker index) — the multi-agent/
|
|
9
|
+
// blackboard cross-linking (worker-accept/blackboard-*.ts) is milestone
|
|
10
|
+
// 9's scope and is a no-op here (no case in this milestone's gate
|
|
11
|
+
// exercises multi-agent linkage). The accept-path ORDER matches the old
|
|
12
|
+
// build: validate -> attest delegation -> accept -> verify -> completion.
|
|
13
|
+
//
|
|
14
|
+
// Evidence: SPEC/pipeline-run.md's worker-isolation references;
|
|
15
|
+
// exechard-evidence-triple-hygiene.case.js, exechard-model-attestation-
|
|
16
|
+
// unreported.case.js, exec-agent-secret-redaction.case.js pin the exact
|
|
17
|
+
// shapes here.
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.WORKER_ISOLATION_SCHEMA_VERSION = void 0;
|
|
53
|
+
exports.getWorkerScope = getWorkerScope;
|
|
54
|
+
exports.writeWorkerManifest = writeWorkerManifest;
|
|
55
|
+
exports.allocateWorkerScope = allocateWorkerScope;
|
|
56
|
+
exports.validateWorkerBoundary = validateWorkerBoundary;
|
|
57
|
+
exports.recordWorkerOutput = recordWorkerOutput;
|
|
58
|
+
exports.recordWorkerFailure = recordWorkerFailure;
|
|
59
|
+
exports.recordWorkerRetryAttempt = recordWorkerRetryAttempt;
|
|
60
|
+
exports.showWorkerManifest = showWorkerManifest;
|
|
61
|
+
exports.listWorkerScopes = listWorkerScopes;
|
|
62
|
+
exports.summarizeWorkers = summarizeWorkers;
|
|
63
|
+
exports.formatWorkerSummaryText = formatWorkerSummaryText;
|
|
64
|
+
const fs = __importStar(require("node:fs"));
|
|
65
|
+
const path = __importStar(require("node:path"));
|
|
66
|
+
const fs_atomic_1 = require("./fs-atomic");
|
|
67
|
+
const node_store_1 = require("./node-store");
|
|
68
|
+
const state_node_1 = require("../core/state/state-node");
|
|
69
|
+
const validation_1 = require("../core/state/validation");
|
|
70
|
+
const contract_1 = require("../core/pipeline/contract");
|
|
71
|
+
const result_normalize_1 = require("../core/pipeline/result-normalize");
|
|
72
|
+
const evidence_grounding_1 = require("../core/trust/evidence-grounding");
|
|
73
|
+
const trust_audit_1 = require("./trust-audit");
|
|
74
|
+
const sandbox_profile_1 = require("./sandbox-profile");
|
|
75
|
+
const registry_1 = require("./execution-backend/registry");
|
|
76
|
+
const error_feedback_io_1 = require("./error-feedback-io");
|
|
77
|
+
const run_store_1 = require("./run-store");
|
|
78
|
+
const multi_agent_io_1 = require("./multi-agent-io");
|
|
79
|
+
const runtime_1 = require("../core/multi-agent/runtime");
|
|
80
|
+
/** The blackboard coordination block on a worker manifest, derived from the
|
|
81
|
+
* worker's AgentMembership linkage (undefined for a non-blackboard worker).
|
|
82
|
+
* Byte-behavior port of the old build's blackboardManifest. */
|
|
83
|
+
function workerBlackboardManifest(run, task) {
|
|
84
|
+
const membershipId = task?.multiAgent?.membershipId;
|
|
85
|
+
const membership = membershipId ? (0, runtime_1.getAgentMembership)(run, membershipId) : undefined;
|
|
86
|
+
const blackboardId = membership?.blackboardId;
|
|
87
|
+
if (!blackboardId)
|
|
88
|
+
return undefined;
|
|
89
|
+
const root = run.paths.blackboardDir || path.join(run.paths.runDir, "blackboard");
|
|
90
|
+
return {
|
|
91
|
+
id: blackboardId,
|
|
92
|
+
topicIds: membership?.topicIds || [],
|
|
93
|
+
indexPath: path.join(root, "index.json"),
|
|
94
|
+
messagesPath: path.join(root, "messages.jsonl"),
|
|
95
|
+
topicsDir: path.join(root, "topics"),
|
|
96
|
+
contextsDir: path.join(root, "contexts"),
|
|
97
|
+
artifactsDir: path.join(root, "artifacts"),
|
|
98
|
+
instructions: [
|
|
99
|
+
"Use the blackboard as shared coordination context.",
|
|
100
|
+
"Read index.json and the relevant topic/context/artifact files before synthesizing.",
|
|
101
|
+
"Cite blackboard artifact refs or message refs in result evidence when relevant.",
|
|
102
|
+
"Do not edit blackboard files directly; CW records accepted worker output into the blackboard.",
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const verifier_1 = require("./verifier");
|
|
107
|
+
const runner_1 = require("../core/pipeline/runner");
|
|
108
|
+
const hash_1 = require("../core/hash");
|
|
109
|
+
const telemetry_attestation_1 = require("../core/trust/telemetry-attestation");
|
|
110
|
+
const telemetry_ledger_io_1 = require("./telemetry-ledger-io");
|
|
111
|
+
exports.WORKER_ISOLATION_SCHEMA_VERSION = 1;
|
|
112
|
+
function workerRoot(run) {
|
|
113
|
+
return run.paths.workersDir || path.join(run.paths.runDir, "workers");
|
|
114
|
+
}
|
|
115
|
+
/** Record a resolved sandbox policy into run.sandboxProfiles (upsert by id) so
|
|
116
|
+
* the run state carries every profile a worker ran under — reports and
|
|
117
|
+
* operators read it. Byte-exact to the old sandbox-profile.ts helper v2
|
|
118
|
+
* dropped. */
|
|
119
|
+
function upsertRunSandboxProfile(run, policy) {
|
|
120
|
+
const profiles = run.sandboxProfiles || [];
|
|
121
|
+
const index = profiles.findIndex((candidate) => candidate.id === policy.id);
|
|
122
|
+
run.sandboxProfiles = (index >= 0 ? profiles.map((candidate) => (candidate.id === policy.id ? policy : candidate)) : [...profiles, policy]);
|
|
123
|
+
}
|
|
124
|
+
function ensureWorkerState(run) {
|
|
125
|
+
run.paths.workersDir = run.paths.workersDir || path.join(run.paths.runDir, "workers");
|
|
126
|
+
fs.mkdirSync(run.paths.workersDir, { recursive: true });
|
|
127
|
+
run.workers = run.workers || [];
|
|
128
|
+
}
|
|
129
|
+
function manifestPath(scope) {
|
|
130
|
+
return path.join(scope.workerDir, "manifest.json");
|
|
131
|
+
}
|
|
132
|
+
function scopePath(scope) {
|
|
133
|
+
return path.join(scope.workerDir, "worker.json");
|
|
134
|
+
}
|
|
135
|
+
/** Deterministic worker id: the task plus a PER-TASK sequence (count of
|
|
136
|
+
* worker scopes already allocated for THIS task + 1) — byte-exact port
|
|
137
|
+
* of the old build's src/worker-isolation/paths.ts:38-42. Re-running the
|
|
138
|
+
* same workflow yields byte-identical worker ids while retries of the
|
|
139
|
+
* SAME task still get a fresh, unique id (workerId is excluded from the
|
|
140
|
+
* snapshot source fingerprint, so this does not change replay digests). */
|
|
141
|
+
function createWorkerId(run, taskId) {
|
|
142
|
+
const prefix = `worker-${(0, fs_atomic_1.safeFileName)(taskId)}-`;
|
|
143
|
+
const seq = (run.workers || []).filter((scope) => scope.id.startsWith(prefix)).length + 1;
|
|
144
|
+
return `${prefix}${String(seq).padStart(4, "0")}`;
|
|
145
|
+
}
|
|
146
|
+
/** Full fail-closed shape guard for a worker.json overlay: delegates to the
|
|
147
|
+
* core WorkerScope guard (schemaVersion, every required string field, the
|
|
148
|
+
* status enum, allowedPaths/feedbackIds/errors shapes) and casts the
|
|
149
|
+
* result to this module's richer WorkerScope, a structural superset of the
|
|
150
|
+
* core WorkerScopeShape. A syntactically-invalid file throws from
|
|
151
|
+
* JSON.parse before this runs; a wrong-shape (but parseable) file throws a
|
|
152
|
+
* RecordValidationError naming the exact broken field. */
|
|
153
|
+
function validateWorkerScope(value) {
|
|
154
|
+
return (0, validation_1.validateWorkerScope)(value);
|
|
155
|
+
}
|
|
156
|
+
function getWorkerScope(run, workerId) {
|
|
157
|
+
ensureWorkerState(run);
|
|
158
|
+
const existing = (run.workers || []).find((s) => s.id === workerId);
|
|
159
|
+
if (existing)
|
|
160
|
+
return existing;
|
|
161
|
+
const file = path.join(workerRoot(run), (0, fs_atomic_1.safeFileName)(workerId), "worker.json");
|
|
162
|
+
if (!fs.existsSync(file))
|
|
163
|
+
return undefined;
|
|
164
|
+
let scope;
|
|
165
|
+
try {
|
|
166
|
+
scope = validateWorkerScope(JSON.parse(fs.readFileSync(file, "utf8")));
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
// A present-but-corrupt scope fails closed with context, not a raw
|
|
170
|
+
// SyntaxError/validation throw bubbling up from deep in the call stack.
|
|
171
|
+
throw new Error(`Corrupt worker scope ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
172
|
+
}
|
|
173
|
+
upsertWorkerScope(run, scope);
|
|
174
|
+
return scope;
|
|
175
|
+
}
|
|
176
|
+
/** Load every worker.json under the run's workers dir, skipping (with a
|
|
177
|
+
* stderr diagnostic) any one that is corrupt/partially-written so a single
|
|
178
|
+
* bad file cannot blank the whole listing. Byte-exact to the old build's
|
|
179
|
+
* loadWorkerScopesFromDisk. */
|
|
180
|
+
function loadWorkerScopesFromDisk(run) {
|
|
181
|
+
const root = workerRoot(run);
|
|
182
|
+
if (!fs.existsSync(root))
|
|
183
|
+
return [];
|
|
184
|
+
return fs
|
|
185
|
+
.readdirSync(root, { withFileTypes: true })
|
|
186
|
+
.filter((entry) => entry.isDirectory())
|
|
187
|
+
.map((entry) => path.join(root, entry.name, "worker.json"))
|
|
188
|
+
.filter((file) => fs.existsSync(file))
|
|
189
|
+
.map((file) => {
|
|
190
|
+
try {
|
|
191
|
+
return validateWorkerScope(JSON.parse(fs.readFileSync(file, "utf8")));
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
process.stderr.write(`cw: skipping unreadable worker scope ${file}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
.filter((scope) => scope !== undefined);
|
|
199
|
+
}
|
|
200
|
+
/** Overlay disk-loaded scopes onto the in-memory list, keyed by id (disk
|
|
201
|
+
* wins), preserving first-seen order. */
|
|
202
|
+
function mergeScopes(existing, loaded) {
|
|
203
|
+
const byId = new Map();
|
|
204
|
+
for (const scope of existing)
|
|
205
|
+
byId.set(scope.id, scope);
|
|
206
|
+
for (const scope of loaded)
|
|
207
|
+
byId.set(scope.id, scope);
|
|
208
|
+
return [...byId.values()];
|
|
209
|
+
}
|
|
210
|
+
function upsertWorkerScope(run, scope) {
|
|
211
|
+
ensureWorkerState(run);
|
|
212
|
+
const scopes = run.workers || [];
|
|
213
|
+
const index = scopes.findIndex((s) => s.id === scope.id);
|
|
214
|
+
run.workers = (index >= 0 ? scopes.map((s) => (s.id === scope.id ? scope : s)) : [...scopes, scope]);
|
|
215
|
+
(0, fs_atomic_1.writeJson)(scopePath(scope), scope);
|
|
216
|
+
return scope;
|
|
217
|
+
}
|
|
218
|
+
function writeWorkerIndex(run) {
|
|
219
|
+
ensureWorkerState(run);
|
|
220
|
+
(0, fs_atomic_1.writeJson)(path.join(workerRoot(run), "index.json"), {
|
|
221
|
+
schemaVersion: exports.WORKER_ISOLATION_SCHEMA_VERSION,
|
|
222
|
+
runId: run.id,
|
|
223
|
+
workers: (run.workers || []).map((scope) => ({
|
|
224
|
+
id: scope.id,
|
|
225
|
+
taskId: scope.taskId,
|
|
226
|
+
dispatchId: scope.dispatchId,
|
|
227
|
+
status: scope.status,
|
|
228
|
+
workerDir: scope.workerDir,
|
|
229
|
+
manifestPath: manifestPath(scope),
|
|
230
|
+
resultPath: scope.resultPath,
|
|
231
|
+
sandboxProfileId: scope.sandboxProfileId,
|
|
232
|
+
backendId: scope.backendId,
|
|
233
|
+
feedbackIds: scope.feedbackIds,
|
|
234
|
+
})),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function writeWorkerManifest(run, scope) {
|
|
238
|
+
const task = run.tasks.find((t) => t.id === scope.taskId);
|
|
239
|
+
const sandboxProfileId = scope.sandboxProfileId;
|
|
240
|
+
const manifest = {
|
|
241
|
+
schemaVersion: exports.WORKER_ISOLATION_SCHEMA_VERSION,
|
|
242
|
+
id: scope.id,
|
|
243
|
+
runId: scope.runId,
|
|
244
|
+
taskId: scope.taskId,
|
|
245
|
+
dispatchId: scope.dispatchId,
|
|
246
|
+
createdAt: scope.createdAt,
|
|
247
|
+
updatedAt: scope.updatedAt,
|
|
248
|
+
status: scope.status,
|
|
249
|
+
workerDir: scope.workerDir,
|
|
250
|
+
scopePath: scopePath(scope),
|
|
251
|
+
manifestPath: manifestPath(scope),
|
|
252
|
+
inputPath: scope.inputPath,
|
|
253
|
+
resultPath: scope.resultPath,
|
|
254
|
+
artifactsDir: scope.artifactsDir,
|
|
255
|
+
logsDir: scope.logsDir,
|
|
256
|
+
allowedPaths: scope.allowedPaths,
|
|
257
|
+
sandboxProfileId,
|
|
258
|
+
sandboxPolicy: scope.sandboxPolicy,
|
|
259
|
+
sandbox: scope.sandboxPolicy
|
|
260
|
+
? { profileId: scope.sandboxPolicy.id, policy: scope.sandboxPolicy, enforcedByCW: scope.sandboxPolicy.enforcement.enforcedByCW, hostRequired: scope.sandboxPolicy.enforcement.hostRequired }
|
|
261
|
+
: undefined,
|
|
262
|
+
backendId: scope.backendId,
|
|
263
|
+
backendAttestation: scope.backendAttestation,
|
|
264
|
+
multiAgent: task?.multiAgent,
|
|
265
|
+
blackboard: workerBlackboardManifest(run, task),
|
|
266
|
+
backend: scope.backendId && scope.backendAttestation
|
|
267
|
+
? {
|
|
268
|
+
id: scope.backendId,
|
|
269
|
+
locality: scope.backendAttestation.locality,
|
|
270
|
+
kind: scope.backendAttestation.kind,
|
|
271
|
+
enforces: scope.backendAttestation.enforced,
|
|
272
|
+
attests: scope.backendAttestation.attested,
|
|
273
|
+
attestation: scope.backendAttestation,
|
|
274
|
+
}
|
|
275
|
+
: undefined,
|
|
276
|
+
retryCount: scope.retryCount,
|
|
277
|
+
instructions: [
|
|
278
|
+
"Read input.md before doing work.",
|
|
279
|
+
"Write the final Markdown result to result.md.",
|
|
280
|
+
"Write worker-local artifacts under artifacts/ and logs under logs/.",
|
|
281
|
+
`Sandbox profile: ${sandboxProfileId}.`,
|
|
282
|
+
"CW enforces profile validation and worker result acceptance only.",
|
|
283
|
+
"The agent host must enforce OS file access, process execution, network access, and environment filtering.",
|
|
284
|
+
"Do not edit shared run state files directly; CW records accepted results.",
|
|
285
|
+
],
|
|
286
|
+
taskPath: task?.taskPath,
|
|
287
|
+
prompt: task?.prompt,
|
|
288
|
+
stateNodeId: scope.stateNodeId,
|
|
289
|
+
resultNodeId: scope.resultNodeId,
|
|
290
|
+
feedbackIds: scope.feedbackIds,
|
|
291
|
+
errors: scope.errors,
|
|
292
|
+
output: scope.output,
|
|
293
|
+
metadata: scope.metadata,
|
|
294
|
+
};
|
|
295
|
+
(0, fs_atomic_1.writeJson)(manifestPath(scope), manifest);
|
|
296
|
+
return manifest;
|
|
297
|
+
}
|
|
298
|
+
function writeWorkerInput(run, task, scope) {
|
|
299
|
+
const lines = [
|
|
300
|
+
`# Worker ${scope.id}`,
|
|
301
|
+
"",
|
|
302
|
+
`- Run: ${run.id}`,
|
|
303
|
+
`- Task: ${task.id}`,
|
|
304
|
+
`- Dispatch: ${scope.dispatchId || ""}`,
|
|
305
|
+
`- Result: ${scope.resultPath}`,
|
|
306
|
+
`- Artifacts: ${scope.artifactsDir}`,
|
|
307
|
+
`- Logs: ${scope.logsDir}`,
|
|
308
|
+
`- Sandbox Profile: ${scope.sandboxProfileId || sandbox_profile_1.DEFAULT_SANDBOX_PROFILE_ID}`,
|
|
309
|
+
"",
|
|
310
|
+
"## Task",
|
|
311
|
+
"",
|
|
312
|
+
task.prompt,
|
|
313
|
+
"",
|
|
314
|
+
"## Boundary",
|
|
315
|
+
"",
|
|
316
|
+
"- Write the final Markdown result to result.md.",
|
|
317
|
+
"- Keep extra files under artifacts/ or logs/.",
|
|
318
|
+
`- Read paths: ${(scope.sandboxPolicy?.readPaths || []).join(", ") || "none"}.`,
|
|
319
|
+
`- Write paths: ${(0, sandbox_profile_1.effectiveSandboxWritePaths)(scope.sandboxPolicy).join(", ") || "none"}.`,
|
|
320
|
+
"- CW enforces result acceptance. The host is responsible for OS/process/network/environment sandbox enforcement.",
|
|
321
|
+
"- Do not mutate state.json, nodes/, feedback/, dispatches/, or commits/ directly.",
|
|
322
|
+
"",
|
|
323
|
+
];
|
|
324
|
+
fs.writeFileSync(scope.inputPath, lines.join("\n"), "utf8");
|
|
325
|
+
}
|
|
326
|
+
function allocateWorkerScope(run, task, options = {}) {
|
|
327
|
+
ensureWorkerState(run);
|
|
328
|
+
const existing = task.workerId ? getWorkerScope(run, String(task.workerId)) : undefined;
|
|
329
|
+
if (existing) {
|
|
330
|
+
if (existing.status === "failed" || existing.status === "orphaned") {
|
|
331
|
+
existing.retryCount = (existing.retryCount || 0) + 1;
|
|
332
|
+
existing.updatedAt = new Date().toISOString();
|
|
333
|
+
existing.status = options.status || "allocated";
|
|
334
|
+
existing.errors = [];
|
|
335
|
+
upsertWorkerScope(run, existing);
|
|
336
|
+
writeWorkerIndex(run);
|
|
337
|
+
}
|
|
338
|
+
return existing;
|
|
339
|
+
}
|
|
340
|
+
const now = new Date().toISOString();
|
|
341
|
+
const workerId = options.workerId || createWorkerId(run, task.id);
|
|
342
|
+
const workerDir = path.join(workerRoot(run), (0, fs_atomic_1.safeFileName)(workerId));
|
|
343
|
+
const inputPath = path.join(workerDir, "input.md");
|
|
344
|
+
const resultPath = path.join(workerDir, "result.md");
|
|
345
|
+
const artifactsDir = path.join(workerDir, "artifacts");
|
|
346
|
+
const logsDir = path.join(workerDir, "logs");
|
|
347
|
+
const sandboxProfileId = options.sandboxProfileId || sandbox_profile_1.DEFAULT_SANDBOX_PROFILE_ID;
|
|
348
|
+
const sandboxPolicy = (0, sandbox_profile_1.sandboxPolicyForWorker)(sandboxProfileId, {
|
|
349
|
+
cwd: run.cwd,
|
|
350
|
+
runDir: run.paths.runDir,
|
|
351
|
+
workerDir,
|
|
352
|
+
inputPath,
|
|
353
|
+
resultPath,
|
|
354
|
+
artifactsDir,
|
|
355
|
+
logsDir,
|
|
356
|
+
customProfiles: run.customSandboxProfiles,
|
|
357
|
+
});
|
|
358
|
+
const allowedPaths = (0, sandbox_profile_1.effectiveSandboxWritePaths)(sandboxPolicy);
|
|
359
|
+
upsertRunSandboxProfile(run, sandboxPolicy);
|
|
360
|
+
// Execution-backend selection (mechanism vs policy): when a backend was
|
|
361
|
+
// explicitly selected, record its sandbox attestation. The dispatch path is a
|
|
362
|
+
// delegate-host execution (the host runs the worker), so the backend enforces
|
|
363
|
+
// only CW's own worker-output acceptance and attests the rest.
|
|
364
|
+
const backendAttestation = options.backendId
|
|
365
|
+
? (0, registry_1.attestSandbox)((0, registry_1.getBackendDescriptor)(options.backendId), sandboxPolicy, { mode: "delegate-host" })
|
|
366
|
+
: undefined;
|
|
367
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
368
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
369
|
+
const scope = {
|
|
370
|
+
schemaVersion: exports.WORKER_ISOLATION_SCHEMA_VERSION,
|
|
371
|
+
id: workerId,
|
|
372
|
+
runId: run.id,
|
|
373
|
+
taskId: task.id,
|
|
374
|
+
dispatchId: options.dispatchId || task.dispatchId,
|
|
375
|
+
createdAt: now,
|
|
376
|
+
updatedAt: now,
|
|
377
|
+
status: options.status || "allocated",
|
|
378
|
+
workerDir,
|
|
379
|
+
inputPath,
|
|
380
|
+
resultPath,
|
|
381
|
+
artifactsDir,
|
|
382
|
+
logsDir,
|
|
383
|
+
allowedPaths,
|
|
384
|
+
sandboxProfileId: sandboxPolicy.id,
|
|
385
|
+
sandboxPolicy,
|
|
386
|
+
backendId: options.backendId,
|
|
387
|
+
backendAttestation,
|
|
388
|
+
stateNodeId: task.stateNodeId,
|
|
389
|
+
feedbackIds: [],
|
|
390
|
+
errors: [],
|
|
391
|
+
metadata: options.metadata,
|
|
392
|
+
};
|
|
393
|
+
writeWorkerInput(run, task, scope);
|
|
394
|
+
writeWorkerManifest(run, scope);
|
|
395
|
+
upsertWorkerScope(run, scope);
|
|
396
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
397
|
+
kind: "worker.sandbox-profile",
|
|
398
|
+
decision: "recorded",
|
|
399
|
+
source: "runtime-derived",
|
|
400
|
+
workerId: scope.id,
|
|
401
|
+
taskId: task.id,
|
|
402
|
+
sandboxProfileId: sandboxPolicy.id,
|
|
403
|
+
policySnapshot: sandboxPolicy,
|
|
404
|
+
metadata: { dispatchId: scope.dispatchId, workerDir: scope.workerDir, allowedPaths },
|
|
405
|
+
});
|
|
406
|
+
if (options.backendId && backendAttestation) {
|
|
407
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
408
|
+
kind: "worker.backend",
|
|
409
|
+
decision: backendAttestation.status === "refused" ? "denied" : "recorded",
|
|
410
|
+
source: "runtime-derived",
|
|
411
|
+
workerId: scope.id,
|
|
412
|
+
taskId: task.id,
|
|
413
|
+
sandboxProfileId: sandboxPolicy.id,
|
|
414
|
+
policySnapshot: sandboxPolicy,
|
|
415
|
+
metadata: {
|
|
416
|
+
backendId: options.backendId,
|
|
417
|
+
attestationStatus: backendAttestation.status,
|
|
418
|
+
enforced: backendAttestation.enforced,
|
|
419
|
+
attested: backendAttestation.attested,
|
|
420
|
+
unenforceable: backendAttestation.unenforceable,
|
|
421
|
+
dispatchId: scope.dispatchId,
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
task.workerId = scope.id;
|
|
426
|
+
task.workerManifestPath = manifestPath(scope);
|
|
427
|
+
task.sandboxProfileId = sandboxPolicy.id;
|
|
428
|
+
task.sandboxPolicy = sandboxPolicy;
|
|
429
|
+
task.backendId = options.backendId;
|
|
430
|
+
task.backendAttestation = backendAttestation;
|
|
431
|
+
writeWorkerIndex(run);
|
|
432
|
+
return scope;
|
|
433
|
+
}
|
|
434
|
+
function requireWorkerScope(run, workerId) {
|
|
435
|
+
const scope = getWorkerScope(run, workerId);
|
|
436
|
+
if (!scope)
|
|
437
|
+
throw new Error(`Unknown worker for run ${run.id}: ${workerId}`);
|
|
438
|
+
return scope;
|
|
439
|
+
}
|
|
440
|
+
function requireWorkerTask(run, scope) {
|
|
441
|
+
const task = run.tasks.find((t) => t.id === scope.taskId);
|
|
442
|
+
if (!task)
|
|
443
|
+
throw new Error(`Unknown task for worker ${scope.id}: ${scope.taskId}`);
|
|
444
|
+
return task;
|
|
445
|
+
}
|
|
446
|
+
/** recordWorkerOutput — the accept-path orchestrator. Order: validate ->
|
|
447
|
+
* attest delegation -> accept -> verify -> completion (byte-exact to the
|
|
448
|
+
* old build; multi-agent fan-out is a no-op here). */
|
|
449
|
+
/** Record the worker.sandbox-boundary trust-audit event: a successful write-path
|
|
450
|
+
* check documents transparently what CW enforced (write paths) vs what is
|
|
451
|
+
* delegated to the host (execute/network/env). Byte-exact to the old build's
|
|
452
|
+
* event emitted inside validateWorkerBoundary. */
|
|
453
|
+
function recordSandboxBoundaryEvent(run, scope) {
|
|
454
|
+
const policy = scope.sandboxPolicy;
|
|
455
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
456
|
+
kind: "worker.sandbox-boundary",
|
|
457
|
+
decision: "allowed",
|
|
458
|
+
source: "cw-validated",
|
|
459
|
+
workerId: scope.id,
|
|
460
|
+
taskId: scope.taskId,
|
|
461
|
+
sandboxProfileId: policy.id,
|
|
462
|
+
policyRef: `execute=${policy.execute.mode} network=${policy.network.mode} env.inherit=${policy.env.inherit}`,
|
|
463
|
+
command: policy.execute.mode,
|
|
464
|
+
networkTarget: policy.network.mode,
|
|
465
|
+
policySnapshot: policy,
|
|
466
|
+
metadata: {
|
|
467
|
+
enforced_by_cw: ["write-paths"],
|
|
468
|
+
delegated_to_host: ["execute", "network", "env"],
|
|
469
|
+
env_inherit: policy.env.inherit,
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
/** `cw worker validate <run-id> <worker-id> [target-file]` — re-run the
|
|
474
|
+
* write-path boundary check for a worker (default target = its result file).
|
|
475
|
+
* Returns the violation, or null when the write path is allowed (also
|
|
476
|
+
* recording the sandbox-boundary transparency event on success). */
|
|
477
|
+
function validateWorkerBoundary(run, workerId, options = {}) {
|
|
478
|
+
const scope = requireWorkerScope(run, workerId);
|
|
479
|
+
const rawPath = path.resolve(String(options.path || scope.resultPath));
|
|
480
|
+
const violation = (0, sandbox_profile_1.validateSandboxWrite)(scope.sandboxPolicy, rawPath, workerId);
|
|
481
|
+
if (!violation)
|
|
482
|
+
recordSandboxBoundaryEvent(run, scope);
|
|
483
|
+
return violation;
|
|
484
|
+
}
|
|
485
|
+
function recordWorkerOutput(run, workerId, resultPath, options = {}) {
|
|
486
|
+
const scope = requireWorkerScope(run, workerId);
|
|
487
|
+
const task = requireWorkerTask(run, scope);
|
|
488
|
+
const absoluteResultPath = path.resolve(resultPath);
|
|
489
|
+
// Step 1: sandbox boundary + result-file existence + envelope contract.
|
|
490
|
+
const violation = (0, sandbox_profile_1.validateSandboxWrite)(scope.sandboxPolicy, absoluteResultPath, workerId);
|
|
491
|
+
if (violation) {
|
|
492
|
+
(0, trust_audit_1.recordSandboxPathDecision)(run, { workerId, taskId: task.id, sandboxProfileId: scope.sandboxProfileId, policySnapshot: scope.sandboxPolicy, target: absoluteResultPath, decision: "denied", metadata: { code: violation.code } });
|
|
493
|
+
recordWorkerFailure(run, workerId, violation.message, { code: violation.code, retryable: false });
|
|
494
|
+
throw new Error(violation.message);
|
|
495
|
+
}
|
|
496
|
+
// Write path enforced by CW; record the enforced-vs-delegated policy split.
|
|
497
|
+
recordSandboxBoundaryEvent(run, scope);
|
|
498
|
+
if (!fs.existsSync(absoluteResultPath)) {
|
|
499
|
+
recordWorkerFailure(run, workerId, `Worker result file does not exist: ${absoluteResultPath}`, { code: "worker-result-missing", retryable: true });
|
|
500
|
+
throw new Error(`Worker result file does not exist: ${absoluteResultPath}`);
|
|
501
|
+
}
|
|
502
|
+
const rawResult = fs.readFileSync(absoluteResultPath, "utf8");
|
|
503
|
+
let parsedResult;
|
|
504
|
+
try {
|
|
505
|
+
parsedResult = (0, result_normalize_1.normalizeResultEnvelope)(rawResult);
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
509
|
+
recordWorkerFailure(run, workerId, message, { code: "result-parse-error", retryable: false });
|
|
510
|
+
throw error;
|
|
511
|
+
}
|
|
512
|
+
if ((0, verifier_1.taskRequiresEvidence)(task) && !parsedResult.evidence.some((e) => (0, evidence_grounding_1.isGroundedEvidence)(e))) {
|
|
513
|
+
const message = `Task ${task.id} requires grounded cw:result evidence (a path-like locator, URL, or namespace:value token — not free text)`;
|
|
514
|
+
recordWorkerFailure(run, workerId, message, { code: "missing-required-evidence", retryable: false });
|
|
515
|
+
throw new Error(message);
|
|
516
|
+
}
|
|
517
|
+
// Step 2: attest delegation (the agent-hop provenance). Track 1: verify
|
|
518
|
+
// the agent's signed telemetry BEFORE recording it — CW holds only the
|
|
519
|
+
// operator's PUBLIC key, so this verifies attribution, never measures
|
|
520
|
+
// usage. resultDigest binds the agent's findings into the signature:
|
|
521
|
+
// CW recomputes the digest from the ACCEPTED result bytes so a result
|
|
522
|
+
// edited after signing fails verification; a signer that did not
|
|
523
|
+
// cover the result still verifies (4-field back-compat).
|
|
524
|
+
const delegation = options.agentDelegation;
|
|
525
|
+
const telemetry = delegation
|
|
526
|
+
? (0, telemetry_attestation_1.verifyTelemetryAttestation)(delegation.reportedUsage, delegation.usageSignature, (0, telemetry_attestation_1.resolveTrustPublicKey)(delegation.usageTrustPublicKey), {
|
|
527
|
+
runId: run.id,
|
|
528
|
+
taskId: task.id,
|
|
529
|
+
promptDigest: delegation.promptDigest,
|
|
530
|
+
resultDigest: (0, hash_1.sha256)(rawResult),
|
|
531
|
+
})
|
|
532
|
+
: undefined;
|
|
533
|
+
// Opt-in fail-closed gate (default off): when the operator requires
|
|
534
|
+
// attested telemetry, a delegated hop whose verdict is not `attested`
|
|
535
|
+
// is REJECTED here — BEFORE any accept-side state mutation — so the
|
|
536
|
+
// drive parks it instead of recording unverifiable usage.
|
|
537
|
+
if (options.requireAttestedTelemetry && telemetry && telemetry.status !== "attested") {
|
|
538
|
+
const message = `Worker ${workerId} telemetry is ${telemetry.status} (${telemetry.reason || "unverified"}) and require-attested-telemetry is enabled — refusing to accept a hop whose usage cannot be cryptographically verified`;
|
|
539
|
+
recordWorkerFailure(run, workerId, message, { code: "telemetry-unattested-blocked", path: absoluteResultPath, retryable: false });
|
|
540
|
+
throw new Error(message);
|
|
541
|
+
}
|
|
542
|
+
const agentDelegationMeta = delegation
|
|
543
|
+
? {
|
|
544
|
+
schemaVersion: 1,
|
|
545
|
+
backendId: "agent",
|
|
546
|
+
handle: delegation.handle,
|
|
547
|
+
model: delegation.model,
|
|
548
|
+
promptDigest: delegation.promptDigest,
|
|
549
|
+
resultDigest: (0, hash_1.sha256)(rawResult),
|
|
550
|
+
command: delegation.command,
|
|
551
|
+
args: delegation.args,
|
|
552
|
+
exitCode: delegation.exitCode,
|
|
553
|
+
...(delegation.reportedUsage ? { reportedUsage: delegation.reportedUsage } : {}),
|
|
554
|
+
...(delegation.usageSignature ? { usageSignature: delegation.usageSignature } : {}),
|
|
555
|
+
...(telemetry ? { usageAttestation: telemetry.status, usageAttestationReason: telemetry.reason } : {}),
|
|
556
|
+
}
|
|
557
|
+
: undefined;
|
|
558
|
+
// Step 3: accept — the irreversible mutation.
|
|
559
|
+
const pathAudit = (0, trust_audit_1.recordSandboxPathDecision)(run, { workerId, taskId: task.id, sandboxProfileId: scope.sandboxProfileId, policySnapshot: scope.sandboxPolicy, target: absoluteResultPath, decision: "allowed", metadata: { operation: "worker-output-acceptance" } });
|
|
560
|
+
const destination = path.join(run.paths.resultsDir, `${(0, fs_atomic_1.safeFileName)(task.id)}.md`);
|
|
561
|
+
fs.mkdirSync(run.paths.resultsDir, { recursive: true });
|
|
562
|
+
fs.copyFileSync(absoluteResultPath, destination);
|
|
563
|
+
task.status = "completed";
|
|
564
|
+
task.completedAt = new Date().toISOString();
|
|
565
|
+
task.resultPath = destination;
|
|
566
|
+
task.loopStage = "observe";
|
|
567
|
+
task.result = parsedResult;
|
|
568
|
+
const evidence = (0, trust_audit_1.normalizeEvidence)(run, parsedResult.evidence.map((entry, index) => ({ id: `result:${index + 1}`, source: "cw:result", locator: entry, summary: entry })), { source: "cw-validated", workerId, taskId: task.id, auditEventIds: [pathAudit.id] });
|
|
569
|
+
let resultNode = (0, state_node_1.createStateNode)({
|
|
570
|
+
id: `${run.id}:result:${task.id}`,
|
|
571
|
+
kind: "result",
|
|
572
|
+
status: "completed",
|
|
573
|
+
loopStage: "observe",
|
|
574
|
+
inputs: { taskId: task.id, dispatchId: task.dispatchId, workerId },
|
|
575
|
+
outputs: parsedResult,
|
|
576
|
+
artifacts: [
|
|
577
|
+
{ id: "result", kind: "markdown", path: destination },
|
|
578
|
+
{ id: "worker-result", kind: "markdown", path: absoluteResultPath },
|
|
579
|
+
],
|
|
580
|
+
evidence,
|
|
581
|
+
parents: task.dispatchId ? [`${run.id}:dispatch:${task.dispatchId}`] : [String(task.stateNodeId || `${run.id}:task:${task.id}`)],
|
|
582
|
+
contractId: contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
|
|
583
|
+
metadata: {
|
|
584
|
+
taskId: task.id,
|
|
585
|
+
workerId,
|
|
586
|
+
workerDir: scope.workerDir,
|
|
587
|
+
sandboxProfileId: scope.sandboxProfileId,
|
|
588
|
+
auditEventIds: [pathAudit.id],
|
|
589
|
+
...((0, result_normalize_1.isEmptyCapture)(parsedResult) ? { captureWarning: "no findings or evidence captured from result.md" } : {}),
|
|
590
|
+
...(agentDelegationMeta ? { agentDelegation: agentDelegationMeta } : {}),
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
const acceptedAudit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
594
|
+
kind: "worker.output",
|
|
595
|
+
decision: "accepted",
|
|
596
|
+
source: "cw-validated",
|
|
597
|
+
workerId,
|
|
598
|
+
taskId: task.id,
|
|
599
|
+
nodeId: resultNode.id,
|
|
600
|
+
sandboxProfileId: scope.sandboxProfileId,
|
|
601
|
+
policySnapshot: scope.sandboxPolicy,
|
|
602
|
+
normalizedPath: absoluteResultPath,
|
|
603
|
+
evidence,
|
|
604
|
+
parentEventIds: [pathAudit.id],
|
|
605
|
+
metadata: { destination },
|
|
606
|
+
});
|
|
607
|
+
resultNode.evidence = (0, trust_audit_1.normalizeEvidence)(run, resultNode.evidence, { source: "cw-validated", workerId, taskId: task.id, resultNodeId: resultNode.id, auditEventIds: [pathAudit.id, acceptedAudit.id] });
|
|
608
|
+
resultNode = (0, node_store_1.appendRunNode)(run, resultNode);
|
|
609
|
+
task.resultNodeId = resultNode.id;
|
|
610
|
+
// Multi-agent: if this task belongs to an AgentMembership, sync the membership
|
|
611
|
+
// to "reported" with this result's evidence so a fanin can see it as complete
|
|
612
|
+
// (isMembershipReported). No-op for a non-multi-agent task (no membership
|
|
613
|
+
// matches workerId/taskId). Byte-behavior port of the old accept path.
|
|
614
|
+
if (task.multiAgent) {
|
|
615
|
+
(0, multi_agent_io_1.recordMultiAgentWorkerOutput)(run, { workerId, taskId: task.id, resultNodeId: resultNode.id, evidence: resultNode.evidence });
|
|
616
|
+
}
|
|
617
|
+
if ((0, result_normalize_1.isEmptyCapture)(parsedResult)) {
|
|
618
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, { kind: "worker.capture-warning", decision: "recorded", source: "cw-validated", workerId, taskId: task.id, nodeId: resultNode.id, parentEventIds: [acceptedAudit.id], metadata: { reason: "no findings or evidence captured from result.md", resultPath: destination } });
|
|
619
|
+
}
|
|
620
|
+
if (delegation && agentDelegationMeta) {
|
|
621
|
+
// Track 1 (tamper-evidence): bind this verdict into the append-only,
|
|
622
|
+
// hash-chained telemetry ledger BEFORE the audit event, so the event
|
|
623
|
+
// can cross-link the record hash. Editing the recorded verdict/usage
|
|
624
|
+
// later breaks the chain (verifyTelemetryLedger). Only when a
|
|
625
|
+
// verdict was computed (every agent hop gets one, even "absent").
|
|
626
|
+
const ledgerRecord = agentDelegationMeta.usageAttestation
|
|
627
|
+
? (0, telemetry_ledger_io_1.appendTelemetryAttestation)(run, {
|
|
628
|
+
workerId,
|
|
629
|
+
taskId: task.id,
|
|
630
|
+
promptDigest: agentDelegationMeta.promptDigest,
|
|
631
|
+
reportedUsage: agentDelegationMeta.reportedUsage,
|
|
632
|
+
usageSignature: agentDelegationMeta.usageSignature,
|
|
633
|
+
// Store the signed result digest ONLY when the signature
|
|
634
|
+
// actually covered it, so the offline re-verifier can
|
|
635
|
+
// reconstruct the 5-field payload.
|
|
636
|
+
resultDigest: telemetry?.coversResult ? agentDelegationMeta.resultDigest : undefined,
|
|
637
|
+
attestation: agentDelegationMeta.usageAttestation,
|
|
638
|
+
attestationReason: agentDelegationMeta.usageAttestationReason,
|
|
639
|
+
})
|
|
640
|
+
: undefined;
|
|
641
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
642
|
+
kind: "worker.agent-delegation",
|
|
643
|
+
decision: "recorded",
|
|
644
|
+
source: "host-attested",
|
|
645
|
+
workerId,
|
|
646
|
+
taskId: task.id,
|
|
647
|
+
nodeId: resultNode.id,
|
|
648
|
+
sandboxProfileId: scope.sandboxProfileId,
|
|
649
|
+
policySnapshot: scope.sandboxPolicy,
|
|
650
|
+
parentEventIds: [acceptedAudit.id],
|
|
651
|
+
metadata: {
|
|
652
|
+
backendId: "agent",
|
|
653
|
+
handleKind: delegation.handle.kind,
|
|
654
|
+
handleRef: delegation.handle.ref,
|
|
655
|
+
model: delegation.model,
|
|
656
|
+
promptDigest: delegation.promptDigest,
|
|
657
|
+
resultDigest: agentDelegationMeta.resultDigest,
|
|
658
|
+
command: delegation.command,
|
|
659
|
+
args: delegation.args,
|
|
660
|
+
exitCode: delegation.exitCode,
|
|
661
|
+
...(agentDelegationMeta.usageAttestation
|
|
662
|
+
? {
|
|
663
|
+
telemetryAttestation: agentDelegationMeta.usageAttestation,
|
|
664
|
+
...(agentDelegationMeta.usageAttestationReason ? { telemetryAttestationReason: agentDelegationMeta.usageAttestationReason } : {}),
|
|
665
|
+
...(agentDelegationMeta.reportedUsage ? { reportedUsage: agentDelegationMeta.reportedUsage } : {}),
|
|
666
|
+
...(ledgerRecord ? { telemetryRecordId: ledgerRecord.recordId, telemetryRecordHash: ledgerRecord.recordHash, telemetryPrevHash: ledgerRecord.prevHash } : {}),
|
|
667
|
+
}
|
|
668
|
+
: {}),
|
|
669
|
+
},
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
// Step 4: verify — drive the pipeline's "verify" stage off the accepted result.
|
|
673
|
+
const verifierResult = (0, runner_1.runPipelineStage)(run, "verify", resultNode.id, {
|
|
674
|
+
outputNodeId: `${run.id}:verifier:${task.id}`,
|
|
675
|
+
outputStatus: "verified",
|
|
676
|
+
loopStage: "adjust",
|
|
677
|
+
outputs: { accepted: true, workerId },
|
|
678
|
+
artifacts: [{ id: "result", kind: "markdown", path: destination }],
|
|
679
|
+
evidence: resultNode.evidence.length ? resultNode.evidence : [{ id: "result:summary", source: "summary", summary: parsedResult.summary }],
|
|
680
|
+
metadata: { taskId: task.id, workerId, resultNodeId: resultNode.id, sandboxProfileId: scope.sandboxProfileId },
|
|
681
|
+
}, { persist: false, persistNode: node_store_1.appendRunNode });
|
|
682
|
+
task.verifierNodeId = verifierResult.outputNodeId;
|
|
683
|
+
// Step 5: completion — persist the worker scope with the verify-derived status.
|
|
684
|
+
const output = { workerId, taskId: task.id, resultPath: absoluteResultPath, recordedAt: new Date().toISOString(), stateNodeId: resultNode.id, verifierNodeId: task.verifierNodeId, auditEventIds: [pathAudit.id, acceptedAudit.id] };
|
|
685
|
+
const reportedModel = agentDelegationMeta && agentDelegationMeta.model && agentDelegationMeta.model !== "unreported" ? agentDelegationMeta.model : undefined;
|
|
686
|
+
// Host-attested usage rides on the worker record. Recorded when the agent
|
|
687
|
+
// REPORTED a model OR token usage — `unreported`/absent stays ABSENT (never
|
|
688
|
+
// backfilled from the operator-chosen CW_AGENT_MODEL, never made up).
|
|
689
|
+
// Track 1: the attestation verdict (`attested`/`unattested`/`absent`) and its
|
|
690
|
+
// reason ride along, and the token buckets come from normalizeReportedUsage
|
|
691
|
+
// (tolerates snake_case/camelCase) — CW still never measures usage, it only
|
|
692
|
+
// records + labels what the agent self-reported. Byte-exact to the old
|
|
693
|
+
// build's src/worker-accept/verifier-completion.ts:58-68.
|
|
694
|
+
const usageRecord = agentDelegationMeta && (reportedModel || agentDelegationMeta.reportedUsage)
|
|
695
|
+
? {
|
|
696
|
+
schemaVersion: 1,
|
|
697
|
+
source: "host-attested",
|
|
698
|
+
...(reportedModel ? { model: reportedModel } : {}),
|
|
699
|
+
...(0, telemetry_attestation_1.normalizeReportedUsage)(agentDelegationMeta.reportedUsage),
|
|
700
|
+
attestedAt: new Date().toISOString(),
|
|
701
|
+
...(telemetry ? { attestation: telemetry.status, ...(telemetry.reason ? { attestationReason: telemetry.reason } : {}) } : {}),
|
|
702
|
+
note: "agent-delegation host-attested usage",
|
|
703
|
+
}
|
|
704
|
+
: undefined;
|
|
705
|
+
const updatedScope = {
|
|
706
|
+
...scope,
|
|
707
|
+
updatedAt: new Date().toISOString(),
|
|
708
|
+
status: verifierResult.status === "advanced" ? "verified" : "completed",
|
|
709
|
+
resultNodeId: resultNode.id,
|
|
710
|
+
output,
|
|
711
|
+
outputDigest: sha256Local(rawResult),
|
|
712
|
+
outputSizeBytes: Buffer.byteLength(rawResult, "utf8"),
|
|
713
|
+
...(usageRecord ? { usage: usageRecord } : {}),
|
|
714
|
+
};
|
|
715
|
+
upsertWorkerScope(run, updatedScope);
|
|
716
|
+
writeWorkerManifest(run, updatedScope);
|
|
717
|
+
writeWorkerIndex(run);
|
|
718
|
+
return output;
|
|
719
|
+
}
|
|
720
|
+
function sha256Local(value) {
|
|
721
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
722
|
+
const { createHash } = require("node:crypto");
|
|
723
|
+
return `sha256:${createHash("sha256").update(value, "utf8").digest("hex")}`;
|
|
724
|
+
}
|
|
725
|
+
function recordWorkerFailure(run, workerId, error, options = {}) {
|
|
726
|
+
const scope = requireWorkerScope(run, workerId);
|
|
727
|
+
const task = requireWorkerTask(run, scope);
|
|
728
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
729
|
+
const structured = { code: options.code || "worker-runtime-error", message, at: new Date().toISOString(), path: options.path, retryable: options.retryable ?? false };
|
|
730
|
+
const failureNodeId = `${run.id}:worker:${(0, fs_atomic_1.safeFileName)(workerId)}:failure:${scope.errors.length + 1}`;
|
|
731
|
+
let failureNode = (0, state_node_1.recordNodeError)((0, state_node_1.createStateNode)({ id: failureNodeId, kind: "error", status: "pending", loopStage: "adjust", inputs: { workerId, taskId: task.id, dispatchId: scope.dispatchId }, parents: task.stateNodeId ? [String(task.stateNodeId)] : [], contractId: contract_1.DEFAULT_PIPELINE_CONTRACT_ID, metadata: { workerId, taskId: task.id, dispatchId: scope.dispatchId, workerDir: scope.workerDir, sandboxProfileId: scope.sandboxProfileId } }), structured);
|
|
732
|
+
if (task.stateNodeId) {
|
|
733
|
+
const parent = (run.nodes || []).find((n) => n.id === task.stateNodeId);
|
|
734
|
+
if (parent) {
|
|
735
|
+
const [linkedParent, linkedChild] = (0, state_node_1.linkStateNodes)(parent, failureNode);
|
|
736
|
+
(0, node_store_1.appendRunNode)(run, linkedParent);
|
|
737
|
+
failureNode = linkedChild;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
failureNode = (0, node_store_1.appendRunNode)(run, failureNode);
|
|
741
|
+
task.status = "failed";
|
|
742
|
+
task.loopStage = "adjust";
|
|
743
|
+
// Record the failure as append-only operator feedback so worker.feedbackIds
|
|
744
|
+
// and run.feedback carry it (its absence cascaded: failed workers left no
|
|
745
|
+
// feedback trail). Byte-exact to the old build.
|
|
746
|
+
const feedback = (0, error_feedback_io_1.recordFeedback)(run, {
|
|
747
|
+
source: "pipeline-runner",
|
|
748
|
+
error: structured,
|
|
749
|
+
nodeId: failureNode.id,
|
|
750
|
+
taskId: task.id,
|
|
751
|
+
path: structured.path,
|
|
752
|
+
retryable: structured.retryable,
|
|
753
|
+
artifacts: failureNode.artifacts,
|
|
754
|
+
metadata: { workerId, dispatchId: scope.dispatchId, workerDir: scope.workerDir, sandboxProfileId: scope.sandboxProfileId, sandboxPolicy: scope.sandboxPolicy, allowedPaths: scope.allowedPaths, details: structured.details },
|
|
755
|
+
}, { persist: false });
|
|
756
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, { kind: "worker.failure", decision: structured.code === "worker-boundary-violation" || structured.code.startsWith("sandbox-") ? "denied" : "failed", source: structured.code.startsWith("sandbox-") || structured.code === "worker-boundary-violation" ? "cw-validated" : "runtime-derived", workerId, taskId: task.id, nodeId: failureNode.id, feedbackIds: [feedback.id], sandboxProfileId: scope.sandboxProfileId, policySnapshot: scope.sandboxPolicy, normalizedPath: structured.path, metadata: { code: structured.code, dispatchId: scope.dispatchId } });
|
|
757
|
+
const updated = upsertWorkerScope(run, {
|
|
758
|
+
...scope,
|
|
759
|
+
updatedAt: new Date().toISOString(),
|
|
760
|
+
status: structured.code === "worker-boundary-violation" || structured.code.startsWith("sandbox-") ? "rejected" : "failed",
|
|
761
|
+
retryCount: typeof options.retryCount === "number" ? options.retryCount : scope.retryCount,
|
|
762
|
+
feedbackIds: [...new Set([...(scope.feedbackIds || []), feedback.id])],
|
|
763
|
+
errors: [...(scope.errors || []), structured],
|
|
764
|
+
});
|
|
765
|
+
// Byte-exact to the old build's updateWorkerScope: worker.json (scope)
|
|
766
|
+
// AND manifest.json must both reflect the terminal park state — a bare
|
|
767
|
+
// upsertWorkerScope only rewrites worker.json, leaving manifest.json
|
|
768
|
+
// (what `cw worker manifest`/`cw worker show` and operators read)
|
|
769
|
+
// stale at whatever retryCount/status it had at dispatch time.
|
|
770
|
+
writeWorkerManifest(run, updated);
|
|
771
|
+
writeWorkerIndex(run);
|
|
772
|
+
if (options.persist !== false)
|
|
773
|
+
(0, run_store_1.saveCheckpoint)(run);
|
|
774
|
+
return updated;
|
|
775
|
+
}
|
|
776
|
+
function recordWorkerRetryAttempt(run, workerId, attempts, reason) {
|
|
777
|
+
const scope = requireWorkerScope(run, workerId);
|
|
778
|
+
const updated = upsertWorkerScope(run, {
|
|
779
|
+
...scope,
|
|
780
|
+
updatedAt: new Date().toISOString(),
|
|
781
|
+
retryCount: attempts,
|
|
782
|
+
metadata: { ...(scope.metadata || {}), agentDelegationAttempts: attempts, agentDelegationLastFailure: reason },
|
|
783
|
+
});
|
|
784
|
+
writeWorkerManifest(run, updated);
|
|
785
|
+
return updated;
|
|
786
|
+
}
|
|
787
|
+
function showWorkerManifest(run, workerId) {
|
|
788
|
+
const scope = requireWorkerScope(run, workerId);
|
|
789
|
+
const task = run.tasks.find((t) => t.id === scope.taskId);
|
|
790
|
+
return { resultPath: scope.resultPath, inputPath: scope.inputPath, manifestPath: manifestPath(scope), workerDir: scope.workerDir, prompt: task?.prompt, sandboxPolicy: scope.sandboxPolicy };
|
|
791
|
+
}
|
|
792
|
+
/** MILESTONE 11 (reporting/observability) — `cw worker list [--status]`. */
|
|
793
|
+
function listWorkerScopes(run, options = {}) {
|
|
794
|
+
ensureWorkerState(run);
|
|
795
|
+
// Reload from disk and merge so a listing reflects the durable truth (and a
|
|
796
|
+
// single corrupt worker.json is skipped, not fatal) — an in-memory-only slice
|
|
797
|
+
// silently drops workers whenever run.workers was reset.
|
|
798
|
+
const merged = mergeScopes(run.workers || [], loadWorkerScopesFromDisk(run));
|
|
799
|
+
run.workers = merged;
|
|
800
|
+
const workers = merged.slice().sort((a, b) => a.id.localeCompare(b.id));
|
|
801
|
+
return options.status ? workers.filter((w) => w.status === options.status) : workers;
|
|
802
|
+
}
|
|
803
|
+
function countByStatus(workers) {
|
|
804
|
+
const counts = {};
|
|
805
|
+
for (const w of workers)
|
|
806
|
+
counts[w.status] = (counts[w.status] || 0) + 1;
|
|
807
|
+
return counts;
|
|
808
|
+
}
|
|
809
|
+
/** `cw worker summary <run-id>` — the workbench `worker.summary` panel and
|
|
810
|
+
* report.ts's own worker rollup share this one function. */
|
|
811
|
+
function summarizeWorkers(run) {
|
|
812
|
+
const workers = listWorkerScopes(run);
|
|
813
|
+
return {
|
|
814
|
+
total: workers.length,
|
|
815
|
+
byStatus: countByStatus(workers),
|
|
816
|
+
manifestPaths: workers.map((w) => manifestPath(w)),
|
|
817
|
+
failed: workers.filter((w) => w.status === "failed" || w.status === "rejected").map((w) => ({ id: w.id, status: w.status, feedbackIds: w.feedbackIds || [] })),
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function countBucket(values) {
|
|
821
|
+
const counts = {};
|
|
822
|
+
for (const value of values)
|
|
823
|
+
counts[value] = (counts[value] || 0) + 1;
|
|
824
|
+
return counts;
|
|
825
|
+
}
|
|
826
|
+
function formatCountBucket(counts) {
|
|
827
|
+
const entries = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
|
|
828
|
+
if (!entries.length)
|
|
829
|
+
return "none";
|
|
830
|
+
return entries.map(([k, v]) => `${k}=${v}`).join(", ");
|
|
831
|
+
}
|
|
832
|
+
/** `cw worker summary <run-id>` human text — port of the old build's
|
|
833
|
+
* formatWorkerPanel (operator-ux/format.ts): a `Workers` rollup with
|
|
834
|
+
* status/sandbox/backend counts and one line per worker naming its
|
|
835
|
+
* sandbox profile and manifest path. */
|
|
836
|
+
function formatWorkerSummaryText(run) {
|
|
837
|
+
const workers = listWorkerScopes(run);
|
|
838
|
+
const lines = [
|
|
839
|
+
"Workers",
|
|
840
|
+
` total=${workers.length}; status=${formatCountBucket(countBucket(workers.map((w) => w.status)))}; sandbox=${formatCountBucket(countBucket(workers.map((w) => w.sandboxProfileId || "none")))}; backend=${formatCountBucket(countBucket(workers.map((w) => w.backendId || "none")))}`,
|
|
841
|
+
];
|
|
842
|
+
for (const worker of workers.slice(0, 8)) {
|
|
843
|
+
lines.push(` ${worker.id}: ${worker.status}, task=${worker.taskId}, sandbox=${worker.sandboxProfileId || "none"}, backend=${worker.backendId || "none"}`);
|
|
844
|
+
lines.push(` manifest=${manifestPath(worker)}`);
|
|
845
|
+
lines.push(` result=${worker.resultPath}`);
|
|
846
|
+
if ((worker.feedbackIds || []).length)
|
|
847
|
+
lines.push(` feedback=${worker.feedbackIds.join(", ")}`);
|
|
848
|
+
}
|
|
849
|
+
if (workers.length > 8)
|
|
850
|
+
lines.push(` ... ${workers.length - 8} more worker(s)`);
|
|
851
|
+
return lines.join("\n");
|
|
852
|
+
}
|