cool-workflow 0.1.94 → 0.1.96
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 +4 -0
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review/workflow.js +3 -3
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pdca-blackboard-loop/app.json +45 -0
- package/apps/pdca-blackboard-loop/workflow.js +59 -0
- 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/agent-config.js +2 -1
- package/dist/cli/command-surface.js +3 -1
- package/dist/dispatch.js +12 -6
- package/dist/evidence-grounding.js +18 -13
- package/dist/execution-backend/probes.js +22 -6
- package/dist/execution-backend.js +37 -4
- package/dist/node-snapshot.js +3 -3
- package/dist/orchestrator/lifecycle-operations.js +13 -5
- package/dist/orchestrator.js +28 -2
- package/dist/reclamation.js +8 -2
- package/dist/run-registry/derive.js +4 -1
- package/dist/scheduler.js +14 -14
- package/dist/schema-validate.js +8 -2
- package/dist/state-explosion/helpers.js +4 -21
- package/dist/state-explosion/size.js +63 -0
- package/dist/state-explosion.js +18 -71
- package/dist/state.js +47 -9
- package/dist/trust-audit.js +27 -2
- package/dist/util/fingerprint.js +19 -0
- package/dist/util/fingerprint.test.js +27 -0
- package/dist/version.js +1 -1
- package/dist/workbench-host.js +11 -0
- package/dist/workbench.js +19 -17
- package/dist/worker-isolation.js +25 -1
- package/docs/agent-delegation-drive.7.md +66 -1
- package/docs/cli-mcp-parity.7.md +4 -0
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -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/multi-agent-cli-mcp-surface.7.md +12 -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 +11 -4
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +20 -0
- package/docs/routines.md +30 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/sandbox-profiles.7.md +15 -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/package.json +8 -5
- package/scripts/agents/agent-adapter-core.js +22 -1
- package/scripts/agents/claude-p-agent.js +10 -2
- package/scripts/agents/codex-agent.js +22 -2
- package/scripts/agents/gemini-agent.js +10 -2
- package/scripts/agents/opencode-agent.js +10 -2
- package/scripts/bump-version.js +10 -0
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/release-check.js +7 -1
- package/scripts/release-flow.js +44 -3
- package/scripts/release-gate.sh +1 -1
- package/scripts/version-sync-check.js +2 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cool-workflow",
|
|
3
3
|
"description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.96",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "COOLWHITE LLC"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cool-workflow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.96",
|
|
4
4
|
"description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "COOLWHITE LLC"
|
package/README.md
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
### Get a saved, cited report from your AI agent — not a chat message you lose.
|
|
16
16
|
|
|
17
|
+
<br>
|
|
18
|
+
|
|
19
|
+
<img src="https://raw.githubusercontent.com/coo1white/cool-workflow/main/docs/assets/demo.gif" alt="cw demo tamper — builds a signed ledger, forges it three ways, catches all three" width="100%">
|
|
20
|
+
|
|
17
21
|
</div>
|
|
18
22
|
|
|
19
23
|
Point an AI coding agent at a repo (or any folder of docs) and Cool Workflow turns the run into a
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "architecture-review",
|
|
4
4
|
"title": "Architecture Review",
|
|
5
5
|
"summary": "Map a repository architecture, assess risks, verify important findings, and synthesize an evidence-backed verdict.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.96",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module.exports = ({ workflow, phase, agent, artifact, input }) => {
|
|
1
|
+
module.exports = ({ workflow, phase, parallel, agent, artifact, input }) => {
|
|
2
2
|
const inputs = [
|
|
3
3
|
input("repo", {
|
|
4
4
|
type: "path",
|
|
@@ -33,7 +33,7 @@ module.exports = ({ workflow, phase, agent, artifact, input }) => {
|
|
|
33
33
|
inputs,
|
|
34
34
|
sandboxProfiles: ["readonly"],
|
|
35
35
|
phases: [
|
|
36
|
-
|
|
36
|
+
parallel("Map", [
|
|
37
37
|
agent(
|
|
38
38
|
"map:server-api",
|
|
39
39
|
"Map server/API entrypoints, request flows, service boundaries, auth surfaces, and owned state in {{repo}} for {{question}}. Focus: {{focus}}. Invariants: {{invariant}}. Return inspected files, dependencies, invariants, and candidate risks.",
|
|
@@ -65,7 +65,7 @@ module.exports = ({ workflow, phase, agent, artifact, input }) => {
|
|
|
65
65
|
{ sandboxProfileId: "readonly" }
|
|
66
66
|
)
|
|
67
67
|
]),
|
|
68
|
-
|
|
68
|
+
parallel("Assess", [
|
|
69
69
|
agent(
|
|
70
70
|
"assess:security",
|
|
71
71
|
"Assess mapper findings through a security lens. Separate real, conditional, non-issue, and unknown risks with evidence, falsifiers, and exact files or config keys.",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "architecture-review-fast",
|
|
4
4
|
"title": "Architecture Review Fast",
|
|
5
5
|
"summary": "Run a shorter architecture review with parallel map and assess phases for faster first results.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.96",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "pdca-blackboard-loop",
|
|
4
|
+
"title": "PDCA Blackboard Loop",
|
|
5
|
+
"summary": "Three agents use one blackboard to plan, build, check, and choose the next step.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"author": "COOLWHITE LLC",
|
|
8
|
+
"inputs": [
|
|
9
|
+
{
|
|
10
|
+
"name": "goal",
|
|
11
|
+
"type": "string",
|
|
12
|
+
"required": true,
|
|
13
|
+
"description": "Work goal for the PDCA loop."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "repo",
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Optional repo or folder path."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "acceptance",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Optional checks that say the work is done."
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"sandboxProfiles": [
|
|
27
|
+
"readonly",
|
|
28
|
+
"workspace-write"
|
|
29
|
+
],
|
|
30
|
+
"compatibility": {
|
|
31
|
+
"minVersion": "0.1.30"
|
|
32
|
+
},
|
|
33
|
+
"metadata": {
|
|
34
|
+
"example": true,
|
|
35
|
+
"domain": "coordination",
|
|
36
|
+
"uses": [
|
|
37
|
+
"blackboard",
|
|
38
|
+
"mcp",
|
|
39
|
+
"pdca"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"workflow": {
|
|
43
|
+
"entrypoint": "workflow.js"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module.exports = ({ workflow, phase, agent, input }) => {
|
|
2
|
+
const inputs = [
|
|
3
|
+
input("goal", {
|
|
4
|
+
type: "string",
|
|
5
|
+
required: true,
|
|
6
|
+
description: "Work goal for the PDCA loop."
|
|
7
|
+
}),
|
|
8
|
+
input("repo", {
|
|
9
|
+
type: "string",
|
|
10
|
+
description: "Optional repo or folder path."
|
|
11
|
+
}),
|
|
12
|
+
input("acceptance", {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "Optional checks that say the work is done."
|
|
15
|
+
})
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
return workflow({
|
|
19
|
+
id: "pdca-blackboard-loop",
|
|
20
|
+
title: "PDCA Blackboard Loop",
|
|
21
|
+
summary: "Three agents use one blackboard to plan, build, check, and choose the next step.",
|
|
22
|
+
limits: {
|
|
23
|
+
maxAgents: 4,
|
|
24
|
+
maxConcurrentAgents: 1
|
|
25
|
+
},
|
|
26
|
+
inputs,
|
|
27
|
+
sandboxProfiles: ["readonly", "workspace-write"],
|
|
28
|
+
phases: [
|
|
29
|
+
phase("Plan", [
|
|
30
|
+
agent(
|
|
31
|
+
"planner:plan",
|
|
32
|
+
"Plan the smallest work loop for {{goal}} in {{repo}}. Include acceptance notes: {{acceptance}}. Write the plan so the builder can use it.",
|
|
33
|
+
{ sandboxProfileId: "readonly" }
|
|
34
|
+
)
|
|
35
|
+
]),
|
|
36
|
+
phase("Do", [
|
|
37
|
+
agent(
|
|
38
|
+
"builder:build",
|
|
39
|
+
"Use the planner output and do the smallest useful work for {{goal}} in {{repo}}. If the plan is missing or unsafe, report blocked instead of success.",
|
|
40
|
+
{ requiresEvidence: true, sandboxProfileId: "workspace-write" }
|
|
41
|
+
)
|
|
42
|
+
]),
|
|
43
|
+
phase("Check", [
|
|
44
|
+
agent(
|
|
45
|
+
"auditor:audit",
|
|
46
|
+
"Check the builder output against {{acceptance}}. Cite the builder evidence. If evidence is missing, report blocked instead of success.",
|
|
47
|
+
{ requiresEvidence: true, sandboxProfileId: "readonly" }
|
|
48
|
+
)
|
|
49
|
+
]),
|
|
50
|
+
phase("Act", [
|
|
51
|
+
agent(
|
|
52
|
+
"planner:next",
|
|
53
|
+
"Read the auditor verdict and record one next action for {{goal}}: accepted, revise, or blocked. Cite the audit evidence.",
|
|
54
|
+
{ requiresEvidence: true, sandboxProfileId: "readonly" }
|
|
55
|
+
)
|
|
56
|
+
])
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "pr-review-fix-ci",
|
|
4
4
|
"title": "PR Review Fix CI",
|
|
5
5
|
"summary": "Review a pull request or branch, inspect CI failures, diagnose actionable issues, optionally patch, verify, and summarize with evidence.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.96",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "release-cut",
|
|
4
4
|
"title": "Release Cut",
|
|
5
5
|
"summary": "Prepare a release with checklist discipline: version checks, changelog, tests, packaging, release notes, and final verification.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.96",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "research-synthesis",
|
|
4
4
|
"title": "Research Synthesis",
|
|
5
5
|
"summary": "Split a research question into claims, investigate sources, cross-check evidence, verify claims, and synthesize a concise answer.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.96",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
package/dist/agent-config.js
CHANGED
|
@@ -31,6 +31,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
31
31
|
const node_path_1 = __importDefault(require("node:path"));
|
|
32
32
|
const run_registry_1 = require("./run-registry");
|
|
33
33
|
const execution_backend_1 = require("./execution-backend");
|
|
34
|
+
const state_1 = require("./state");
|
|
34
35
|
exports.AGENT_CONFIG_SCHEMA_VERSION = 1;
|
|
35
36
|
function agentConfigPath(env = process.env) {
|
|
36
37
|
return node_path_1.default.join((0, run_registry_1.resolveCwHome)(env), "agent-config.json");
|
|
@@ -260,7 +261,7 @@ function setAgentConfigFile(patch, env = process.env) {
|
|
|
260
261
|
const stored = redacted(merged);
|
|
261
262
|
const file = agentConfigPath(env);
|
|
262
263
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
|
|
263
|
-
|
|
264
|
+
(0, state_1.writeJson)(file, stored);
|
|
264
265
|
return stored;
|
|
265
266
|
}
|
|
266
267
|
/** Read-only, deterministic projection of the effective config (secret-stripped).
|
|
@@ -80,11 +80,13 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
// Map vendor shorthand flags (-claude, -codex, -deepseek) to --agent-command.
|
|
83
|
+
// Map vendor shorthand flags (-claude, -codex, -gemini, -deepseek) to --agent-command.
|
|
84
84
|
if (args.options.claude)
|
|
85
85
|
args.options["agent-command"] = "builtin:claude";
|
|
86
86
|
if (args.options.codex)
|
|
87
87
|
args.options["agent-command"] = "builtin:codex";
|
|
88
|
+
if (args.options.gemini)
|
|
89
|
+
args.options["agent-command"] = "builtin:gemini";
|
|
88
90
|
if (args.options.deepseek)
|
|
89
91
|
args.options["agent-command"] = "builtin:deepseek";
|
|
90
92
|
// -dir / --dir / -d : an intuitive alias for --repo — the project folder to review,
|
package/dist/dispatch.js
CHANGED
|
@@ -202,15 +202,16 @@ function formatDispatchTask(task) {
|
|
|
202
202
|
};
|
|
203
203
|
}
|
|
204
204
|
// Deterministic dispatch id (replay-determinism self-audit): the wall-clock stamp
|
|
205
|
-
// is an edge timestamp
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
// run still gets a distinct, monotonically increasing id. Mirrors the de-clock done
|
|
210
|
-
// for worker ids in src/worker-isolation/paths.ts.
|
|
205
|
+
// is an edge timestamp for human readability. Set CW_DETERMINISTIC_RUN_IDS=1 to
|
|
206
|
+
// use a content-hash-based id without wall-clock, so re-running the same workflow
|
|
207
|
+
// yields byte-identical dispatch ids. The suffix is a per-run sequence, so each
|
|
208
|
+
// dispatch still gets a distinct, monotonically increasing id.
|
|
211
209
|
function createDispatchId(run) {
|
|
212
210
|
const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
|
|
213
211
|
const seq = (run.dispatches?.length || 0) + 1;
|
|
212
|
+
if (/^(1|true|yes|on)$/i.test(process.env.CW_DETERMINISTIC_RUN_IDS || "")) {
|
|
213
|
+
return `dispatch-${String(seq).padStart(4, "0")}`;
|
|
214
|
+
}
|
|
214
215
|
return `dispatch-${stamp}-${String(seq).padStart(4, "0")}`;
|
|
215
216
|
}
|
|
216
217
|
// H7: persist a CUSTOM sandbox profile DEFINITION (loaded from a FILE at dispatch)
|
|
@@ -240,5 +241,10 @@ function persistCustomSandboxProfile(run, requested) {
|
|
|
240
241
|
if (!definition || typeof definition !== "object" || typeof definition.id !== "string" || !definition.id)
|
|
241
242
|
return;
|
|
242
243
|
run.customSandboxProfiles = run.customSandboxProfiles || {};
|
|
244
|
+
const previous = run.customSandboxProfiles[definition.id];
|
|
245
|
+
if (previous && JSON.stringify(previous) !== JSON.stringify(definition)) {
|
|
246
|
+
throw new Error(`Sandbox profile id collision: "${definition.id}" is already defined by a different custom profile ` +
|
|
247
|
+
`(source: ${absolute}). Use a unique id in each custom profile file.`);
|
|
248
|
+
}
|
|
243
249
|
run.customSandboxProfiles[definition.id] = definition;
|
|
244
250
|
}
|
|
@@ -27,15 +27,14 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
27
27
|
// are all legitimate — so we cannot require "must be a file that exists" by
|
|
28
28
|
// default without breaking cross-repo, URL, and runtime-token evidence. Instead:
|
|
29
29
|
//
|
|
30
|
-
// 1. DEFAULT
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
30
|
+
// 1. DEFAULT: require evidence to be GROUNDED — a URL, a path-like locator, or
|
|
31
|
+
// a `namespace:value` token. This rejects bare prose ("x", "anything",
|
|
32
|
+
// "HIGH severity") while accepting every shape CW itself emits. Being a pure
|
|
33
|
+
// function of the string, it is replay-safe.
|
|
34
34
|
//
|
|
35
|
-
// 2.
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// dependent; on for self-audit / compliance where evidence MUST be checkable.
|
|
35
|
+
// 2. DEFAULT (v0.1.95): file-style evidence locators MUST also exist on disk.
|
|
36
|
+
// CW_REQUIRE_RESOLVABLE_EVIDENCE=0 restores the prior shape-only behavior.
|
|
37
|
+
// CW_REQUIRE_RESOLVABLE_EVIDENCE=url additionally requires URL reachability.
|
|
39
38
|
// ---------------------------------------------------------------------------
|
|
40
39
|
const URL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
41
40
|
const PATH_SEP_RE = /[\\/]/;
|
|
@@ -63,14 +62,20 @@ function hasGroundedEvidence(evidence) {
|
|
|
63
62
|
return Array.isArray(evidence) && evidence.some((entry) => isGroundedEvidence(entry));
|
|
64
63
|
}
|
|
65
64
|
/** Whether opt-in strict resolution is requested via the environment.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* Enabled by DEFAULT (v0.1.95): file-style evidence locators MUST exist on disk.
|
|
66
|
+
* Set CW_REQUIRE_RESOLVABLE_EVIDENCE=0 to restore the prior shape-only check.
|
|
67
|
+
* Set CW_REQUIRE_RESOLVABLE_EVIDENCE=url for URL reachability checks too. */
|
|
68
68
|
function requireResolvableEvidence() {
|
|
69
|
-
|
|
69
|
+
const raw = process.env.CW_REQUIRE_RESOLVABLE_EVIDENCE;
|
|
70
|
+
if (raw === undefined || raw === null || raw === "")
|
|
71
|
+
return true;
|
|
72
|
+
if (/^(0|false|no|off)$/i.test(raw))
|
|
73
|
+
return false;
|
|
74
|
+
return true;
|
|
70
75
|
}
|
|
71
|
-
/** Whether URL reachability checks are enabled
|
|
76
|
+
/** Whether URL reachability checks are enabled (v0.1.63). Always opt-in. */
|
|
72
77
|
function requireUrlReachability() {
|
|
73
|
-
return
|
|
78
|
+
return /url/i.test(process.env.CW_REQUIRE_RESOLVABLE_EVIDENCE || "");
|
|
74
79
|
}
|
|
75
80
|
function classify(raw) {
|
|
76
81
|
const value = raw.trim();
|
|
@@ -22,6 +22,7 @@ exports.probeAgentBackend = probeAgentBackend;
|
|
|
22
22
|
//
|
|
23
23
|
// Readiness probe. Deterministic given the host (PATH + configured env).
|
|
24
24
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
25
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
25
26
|
const util_1 = require("./util");
|
|
26
27
|
function probeNodeBackend() {
|
|
27
28
|
const ok = (0, util_1.hasExecutable)("node");
|
|
@@ -63,20 +64,35 @@ function probeContainerBackend() {
|
|
|
63
64
|
reason: docker || podman ? undefined : "no container runtime (docker/podman) found; supply --image to delegate explicitly"
|
|
64
65
|
};
|
|
65
66
|
}
|
|
67
|
+
function delegateChildExists() {
|
|
68
|
+
return node_fs_1.default.existsSync(node_path_1.default.resolve(__dirname, "..", "..", "scripts", "children", "http-delegate-child.js"));
|
|
69
|
+
}
|
|
66
70
|
function probeRemoteBackend() {
|
|
67
71
|
const endpoint = (process.env.CW_REMOTE_ENDPOINT || "").trim();
|
|
72
|
+
const scriptOk = delegateChildExists();
|
|
73
|
+
const checks = [
|
|
74
|
+
{ name: "endpoint", ok: Boolean(endpoint), detail: endpoint ? "CW_REMOTE_ENDPOINT configured" : "CW_REMOTE_ENDPOINT not set" },
|
|
75
|
+
{ name: "delegate-child-script", ok: scriptOk, detail: scriptOk ? "http-delegate-child.js found" : "http-delegate-child.js missing — reinstall cool-workflow" }
|
|
76
|
+
];
|
|
77
|
+
const readiness = !endpoint ? "unverified" : !scriptOk ? "unavailable" : "ready";
|
|
68
78
|
return {
|
|
69
|
-
checks
|
|
70
|
-
readiness
|
|
71
|
-
reason:
|
|
79
|
+
checks,
|
|
80
|
+
readiness,
|
|
81
|
+
reason: readiness === "ready" ? undefined : !endpoint ? "no remote endpoint configured (set CW_REMOTE_ENDPOINT or pass --endpoint)" : "delegate child script is missing"
|
|
72
82
|
};
|
|
73
83
|
}
|
|
74
84
|
function probeCiBackend() {
|
|
75
85
|
const endpoint = (process.env.CW_CI_ENDPOINT || "").trim();
|
|
86
|
+
const scriptOk = delegateChildExists();
|
|
87
|
+
const checks = [
|
|
88
|
+
{ name: "ci-endpoint", ok: Boolean(endpoint), detail: endpoint ? "CW_CI_ENDPOINT configured" : "CW_CI_ENDPOINT not set" },
|
|
89
|
+
{ name: "delegate-child-script", ok: scriptOk, detail: scriptOk ? "http-delegate-child.js found" : "http-delegate-child.js missing — reinstall cool-workflow" }
|
|
90
|
+
];
|
|
91
|
+
const readiness = !endpoint ? "unverified" : !scriptOk ? "unavailable" : "ready";
|
|
76
92
|
return {
|
|
77
|
-
checks
|
|
78
|
-
readiness
|
|
79
|
-
reason:
|
|
93
|
+
checks,
|
|
94
|
+
readiness,
|
|
95
|
+
reason: readiness === "ready" ? undefined : !endpoint ? "no CI job target configured (set CW_CI_ENDPOINT or pass --job)" : "delegate child script is missing"
|
|
80
96
|
};
|
|
81
97
|
}
|
|
82
98
|
function probeAgentBackend() {
|
|
@@ -38,6 +38,7 @@ exports.requiredSandboxDimensions = requiredSandboxDimensions;
|
|
|
38
38
|
exports.attestSandbox = attestSandbox;
|
|
39
39
|
exports.probeBackend = probeBackend;
|
|
40
40
|
exports.runBackend = runBackend;
|
|
41
|
+
exports.delegateChildScript = delegateChildScript;
|
|
41
42
|
exports.createExecutionBackend = createExecutionBackend;
|
|
42
43
|
exports.backendListPayload = backendListPayload;
|
|
43
44
|
exports.backendShowPayload = backendShowPayload;
|
|
@@ -404,12 +405,22 @@ function executeLocal(descriptor, policy, request, label, attestation) {
|
|
|
404
405
|
// shell backend runs via /bin/sh -c; node/bun run the command directly
|
|
405
406
|
// (bun is Node-compatible by default so evidence stays byte-stable with node).
|
|
406
407
|
// spawnStyle comes from the registered driver, not a hardcoded id check.
|
|
408
|
+
const spawnStyle = getBackendDriver(descriptor.id)?.spawnStyle;
|
|
409
|
+
if (spawnStyle === "shell") {
|
|
410
|
+
// Shell injection guard: reject args that contain shell control characters
|
|
411
|
+
// (beyond template placeholders). The command itself is operator-configured.
|
|
412
|
+
const shellArg = [command, ...args].join(" ").replace(/\{\{[a-zA-Z0-9_.-]+\}\}/g, "");
|
|
413
|
+
if (/[;&|`$(){}<>!\n\r]/.test(shellArg)) {
|
|
414
|
+
throw new Error(`Shell backend refused: args contain shell control characters. ` +
|
|
415
|
+
`Use the node, bun, or agent backend instead for untrusted inputs.`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
407
418
|
const isTTY = process.stderr.isTTY;
|
|
408
419
|
const shortLabel = command.split("/").pop() || command;
|
|
409
420
|
if (isTTY)
|
|
410
421
|
process.stderr.write(`● Running ${shortLabel}...\n`);
|
|
411
422
|
const startedAt = process.hrtime.bigint();
|
|
412
|
-
const result =
|
|
423
|
+
const result = spawnStyle === "shell"
|
|
413
424
|
? (0, node_child_process_1.spawnSync)([command, ...args].join(" "), { ...options, shell: true })
|
|
414
425
|
: (0, node_child_process_1.spawnSync)(command, args, { ...options, shell: false });
|
|
415
426
|
const elapsedMs = Number((process.hrtime.bigint() - startedAt) / 1000000n);
|
|
@@ -587,7 +598,17 @@ function runContainer(descriptor, policy, request, label, handle, attestation) {
|
|
|
587
598
|
// portable and synchronous from CW's view. We spawn it BY PATH (shell:false). The
|
|
588
599
|
// path is resolved from this compiled module (dist/execution-backend.js) up to the
|
|
589
600
|
// package's `scripts/children/` dir, which package.json ships in "files".
|
|
590
|
-
|
|
601
|
+
function delegateChildScript() {
|
|
602
|
+
const resolved = node_path_1.default.resolve(__dirname, "..", "scripts", "children", "http-delegate-child.js");
|
|
603
|
+
if (!node_fs_1.default.existsSync(resolved)) {
|
|
604
|
+
throw new Error(`Delegate child script not found at ${resolved}. ` +
|
|
605
|
+
`This indicates a broken installation — reinstall cool-workflow or ensure ` +
|
|
606
|
+
`"scripts/children/http-delegate-child.js" is shipped in the package.`);
|
|
607
|
+
}
|
|
608
|
+
return resolved;
|
|
609
|
+
}
|
|
610
|
+
/** The shared HTTP delegation child script path (resolved at import time). */
|
|
611
|
+
const HTTP_DELEGATE_CHILD_SCRIPT = delegateChildScript();
|
|
591
612
|
/** remote / ci — real HTTP delegation. POSTs the job to the configured endpoint
|
|
592
613
|
* (and polls a returned jobId) via a Node child, then records the runner's exit +
|
|
593
614
|
* stdout digest as canonical evidence. Fails closed when the endpoint is missing,
|
|
@@ -675,9 +696,17 @@ function runAgentProcess(descriptor, policy, request, label, handle, attestation
|
|
|
675
696
|
// captured as data. CI/pipes stay silent. CW_AGENT_STREAM=0 or
|
|
676
697
|
// CW_NO_STREAM=1 forces off; CW_AGENT_STREAM=1 forces on.
|
|
677
698
|
const streamStderr = process.env.CW_AGENT_STREAM !== "0" && Boolean(process.stderr.isTTY) && process.env.CW_NO_STREAM !== "1";
|
|
699
|
+
// Build child env from sandbox policy as baseline (respects env.inherit/expose/deny),
|
|
700
|
+
// then re-allow CW_* + well-known API key env vars the agent needs.
|
|
701
|
+
const childEnv = buildChildEnv(policy);
|
|
702
|
+
for (const key of Object.keys(process.env)) {
|
|
703
|
+
if (/^(CW_|ANTHROPIC_|OPENAI_|GEMINI_|DEEPSEEK_|CODEX_|GOOGLE_|COHERE_|MISTRAL_|OLLAMA_|AZURE_|AWS_)/i.test(key)) {
|
|
704
|
+
childEnv[key] = process.env[key];
|
|
705
|
+
}
|
|
706
|
+
}
|
|
678
707
|
const child = (0, node_child_process_1.spawnSync)(resolved.binary, realArgs, {
|
|
679
708
|
cwd: request.cwd,
|
|
680
|
-
env:
|
|
709
|
+
env: childEnv,
|
|
681
710
|
encoding: "utf8",
|
|
682
711
|
timeout: resolved.timeoutMs || 600000,
|
|
683
712
|
maxBuffer: 32 * 1024 * 1024,
|
|
@@ -896,7 +925,11 @@ function runtimeNote(descriptor) {
|
|
|
896
925
|
}
|
|
897
926
|
// ---- Probe cache (v0.1.60) — mechanism, not policy -----------------------
|
|
898
927
|
const _probeCache = new Map();
|
|
899
|
-
const PROBE_CACHE_TTL_MS =
|
|
928
|
+
const PROBE_CACHE_TTL_MS = (() => {
|
|
929
|
+
const raw = process.env.CW_PROBE_CACHE_TTL_MS;
|
|
930
|
+
const n = raw ? parseInt(raw, 10) : 60_000;
|
|
931
|
+
return Number.isFinite(n) && n >= 0 ? n : 60_000;
|
|
932
|
+
})(); // default 60s; set CW_PROBE_CACHE_TTL_MS=0 to disable
|
|
900
933
|
function cachedProbeBackend(id, context) {
|
|
901
934
|
const key = `${id}:${context.cwd || ''}`;
|
|
902
935
|
const cached = _probeCache.get(key);
|
package/dist/node-snapshot.js
CHANGED
|
@@ -38,7 +38,7 @@ const pipeline_runner_1 = require("./pipeline-runner");
|
|
|
38
38
|
const state_1 = require("./state");
|
|
39
39
|
const multi_agent_eval_1 = require("./multi-agent-eval");
|
|
40
40
|
const node_projection_1 = require("./node-projection");
|
|
41
|
-
const
|
|
41
|
+
const fingerprint_1 = require("./util/fingerprint");
|
|
42
42
|
const validation_1 = require("./validation");
|
|
43
43
|
exports.NODE_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
44
44
|
/** Structured fail-closed error (mirrors the PipelineContractError shape). */
|
|
@@ -75,7 +75,7 @@ function snapshotBody(node) {
|
|
|
75
75
|
/** RAW fingerprint (NOT normalized): any transition (updatedAt/status) or
|
|
76
76
|
* artifact/evidence change flips it, which is how drift is detected. */
|
|
77
77
|
function sourceFingerprint(node) {
|
|
78
|
-
return (0,
|
|
78
|
+
return (0, fingerprint_1.fingerprintStrings)([
|
|
79
79
|
`node:${node.id}:${node.status}:${node.updatedAt}`,
|
|
80
80
|
...node.artifacts.map((artifact) => `artifact:${artifact.id}:${artifact.path}`),
|
|
81
81
|
...node.evidence.map((evidence) => `evidence:${evidence.id}:${evidence.path || ""}`)
|
|
@@ -203,7 +203,7 @@ function replayNodeSnapshot(run, snapshot, options = {}) {
|
|
|
203
203
|
throw new NodeSnapshotError(freshness === "stale" ? "snapshot-stale" : "snapshot-absent", reason || `cannot replay a ${freshness} snapshot of node ${snapshot.nodeId}`, { freshness, details: { runId: run.id, nodeId: snapshot.nodeId } });
|
|
204
204
|
}
|
|
205
205
|
const body = (0, multi_agent_eval_1.normalizeValue)(snapshot.body);
|
|
206
|
-
const outputFingerprint = (0,
|
|
206
|
+
const outputFingerprint = (0, fingerprint_1.fingerprintStrings)([(0, multi_agent_eval_1.replayStableStringify)(body)]);
|
|
207
207
|
const replay = {
|
|
208
208
|
schemaVersion: 1,
|
|
209
209
|
replayId: `replay-${snapshot.snapshotId}-${outputFingerprint.replace("sha256:", "").slice(0, 8)}`,
|
|
@@ -599,12 +599,20 @@ function renderPrompt(prompt, inputs) {
|
|
|
599
599
|
// worker ids in src/worker-isolation/paths.ts.
|
|
600
600
|
let runIdSequence = 0;
|
|
601
601
|
function createRunId(workflowId) {
|
|
602
|
+
// Use process.pid + monotonic counter for uniqueness (no wall-clock),
|
|
603
|
+
// but keep a second-resolution stamp for human readability in the id.
|
|
602
604
|
const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
|
|
603
|
-
//
|
|
604
|
-
//
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
605
|
+
// Set CW_DETERMINISTIC_RUN_IDS=1 to use a content hash instead of wall-clock,
|
|
606
|
+
// so two plan() calls with the same inputs produce the same id (replay-safe).
|
|
607
|
+
if (/^(1|true|yes|on)$/i.test(process.env.CW_DETERMINISTIC_RUN_IDS || "")) {
|
|
608
|
+
runIdSequence += 1;
|
|
609
|
+
const suffix = node_crypto_1.default
|
|
610
|
+
.createHash("sha256")
|
|
611
|
+
.update(`${workflowId}:${process.pid}:${runIdSequence}`)
|
|
612
|
+
.digest("hex")
|
|
613
|
+
.slice(0, 6);
|
|
614
|
+
return `${workflowId}-${suffix}`;
|
|
615
|
+
}
|
|
608
616
|
runIdSequence += 1;
|
|
609
617
|
const suffix = node_crypto_1.default
|
|
610
618
|
.createHash("sha256")
|
package/dist/orchestrator.js
CHANGED
|
@@ -56,6 +56,7 @@ const worker_isolation_1 = require("./worker-isolation");
|
|
|
56
56
|
const sandbox_profile_1 = require("./sandbox-profile");
|
|
57
57
|
const execution_backend_1 = require("./execution-backend");
|
|
58
58
|
const operator_ux_1 = require("./operator-ux");
|
|
59
|
+
const trust_audit_1 = require("./trust-audit");
|
|
59
60
|
const multi_agent_1 = require("./multi-agent");
|
|
60
61
|
const multi_agent_operator_ux_1 = require("./multi-agent-operator-ux");
|
|
61
62
|
const multi_agent_eval_1 = require("./multi-agent-eval");
|
|
@@ -102,12 +103,29 @@ class CoolWorkflowRunner {
|
|
|
102
103
|
// reads runs from disk per call (no in-memory run state), so withBaseDir hands back
|
|
103
104
|
// a cheap scoped clone instead of mutating the global process cwd.
|
|
104
105
|
baseDir;
|
|
106
|
+
// Per-request cache (v0.1.95): when set, loadRun memoizes by runId so the 17
|
|
107
|
+
// workbench sub-panels that each call loadRun independently share one read.
|
|
108
|
+
_requestCache;
|
|
105
109
|
constructor({ pluginRoot, baseDir }) {
|
|
106
110
|
this.pluginRoot = resolvePluginRoot(pluginRoot);
|
|
107
111
|
this.workflowsDir = node_path_1.default.join(this.pluginRoot, "workflows");
|
|
108
112
|
this.appsDir = node_path_1.default.join(this.pluginRoot, "apps");
|
|
109
113
|
this.baseDir = baseDir ? node_path_1.default.resolve(baseDir) : undefined;
|
|
110
114
|
}
|
|
115
|
+
/** Run fn with a per-request loadRun cache. All loadRun calls inside fn share
|
|
116
|
+
* the same cached run state, collapsing 18 reads into 1. Returns fn's result
|
|
117
|
+
* and clears the cache afterwards (never leaks between requests). */
|
|
118
|
+
loadWithCache(fn) {
|
|
119
|
+
this._requestCache = new Map();
|
|
120
|
+
(0, trust_audit_1.setAuditEventCache)(new Map());
|
|
121
|
+
try {
|
|
122
|
+
return fn(this);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
this._requestCache = undefined;
|
|
126
|
+
(0, trust_audit_1.clearAuditEventCache)();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
111
129
|
/** Return a runner that resolves runs against `dir` instead of process.cwd(),
|
|
112
130
|
* WITHOUT chdir-ing the process (F7). Same instance when the dir is unchanged. */
|
|
113
131
|
withBaseDir(dir) {
|
|
@@ -632,7 +650,14 @@ class CoolWorkflowRunner {
|
|
|
632
650
|
return feedbackOps.resolveFeedback(this.loadRun(runId), feedbackId, options);
|
|
633
651
|
}
|
|
634
652
|
loadRun(runId) {
|
|
635
|
-
|
|
653
|
+
if (this._requestCache) {
|
|
654
|
+
const cached = this._requestCache.get(runId);
|
|
655
|
+
if (cached)
|
|
656
|
+
return cached;
|
|
657
|
+
}
|
|
658
|
+
const run = (0, state_1.loadRunFromCwd)(runId, this.baseDir);
|
|
659
|
+
this._requestCache?.set(runId, run);
|
|
660
|
+
return run;
|
|
636
661
|
}
|
|
637
662
|
invocationCwd() {
|
|
638
663
|
return this.baseDir || process.cwd();
|
|
@@ -812,7 +837,7 @@ function formatHelp() {
|
|
|
812
837
|
return [
|
|
813
838
|
(0, term_1.bold)("Cool Workflow", out),
|
|
814
839
|
"",
|
|
815
|
-
" -q \"question\" [-claude|-codex|-deepseek] Ask a question, get a report",
|
|
840
|
+
" -q \"question\" [-claude|-codex|-gemini|-deepseek] Ask a question, get a report",
|
|
816
841
|
" -q \"question\" --link <url> Review a remote repo by URL",
|
|
817
842
|
" version Show version",
|
|
818
843
|
" update Update to latest release",
|
|
@@ -825,6 +850,7 @@ function formatHelp() {
|
|
|
825
850
|
" -d, --dir PATH Project folder to review (alias for --repo)",
|
|
826
851
|
" -claude Use Claude agent",
|
|
827
852
|
" -codex Use Codex agent",
|
|
853
|
+
" -gemini Use Gemini (via opencode)",
|
|
828
854
|
" -deepseek Use DeepSeek (via opencode)",
|
|
829
855
|
" --verbose Show full agent narration live (default: compact)",
|
|
830
856
|
" --full Verbose, plus the report printed inline at the end",
|
package/dist/reclamation.js
CHANGED
|
@@ -121,7 +121,10 @@ function loadReclamationLog(run) {
|
|
|
121
121
|
return { schemaVersion: 1, runId: run.id, tombstones: [] };
|
|
122
122
|
try {
|
|
123
123
|
const parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
124
|
-
|
|
124
|
+
if (!parsed || typeof parsed !== "object" || parsed.schemaVersion !== 1 || !Array.isArray(parsed.tombstones)) {
|
|
125
|
+
return { schemaVersion: 1, runId: run.id, tombstones: [] };
|
|
126
|
+
}
|
|
127
|
+
return { schemaVersion: 1, runId: run.id, tombstones: parsed.tombstones };
|
|
125
128
|
}
|
|
126
129
|
catch {
|
|
127
130
|
// A malformed overlay must NOT brick the run — fail closed to an empty chain.
|
|
@@ -420,7 +423,10 @@ function planReclamation(run, policy = {}) {
|
|
|
420
423
|
catch {
|
|
421
424
|
continue; // unreadable snapshot → retain (fail closed)
|
|
422
425
|
}
|
|
423
|
-
|
|
426
|
+
if (!snap || typeof snap !== "object" || typeof snap.nodeId !== "string")
|
|
427
|
+
continue;
|
|
428
|
+
const nodeId = snap.nodeId;
|
|
429
|
+
const node = (run.nodes || []).find((n) => n.id === nodeId);
|
|
424
430
|
if (!node)
|
|
425
431
|
continue; // source node gone → cannot reconstruct → retain
|
|
426
432
|
if (repointNodeIds.has(node.id))
|