cool-workflow 0.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +20 -0
- package/.codex-plugin/mcp.json +10 -0
- package/.codex-plugin/plugin.json +38 -0
- package/.mcp.json +10 -0
- package/LICENSE +24 -0
- package/README.md +638 -0
- package/apps/architecture-review/app.json +51 -0
- package/apps/architecture-review/workflow.js +116 -0
- package/apps/end-to-end-golden-path/app.json +30 -0
- package/apps/end-to-end-golden-path/workflow.js +33 -0
- package/apps/pr-review-fix-ci/app.json +59 -0
- package/apps/pr-review-fix-ci/workflow.js +90 -0
- package/apps/release-cut/app.json +54 -0
- package/apps/release-cut/workflow.js +82 -0
- package/apps/research-synthesis/app.json +50 -0
- package/apps/research-synthesis/workflow.js +76 -0
- package/apps/workflow-app-framework-demo/app.json +29 -0
- package/apps/workflow-app-framework-demo/workflow.js +44 -0
- package/dist/agent-config.js +223 -0
- package/dist/candidate-scoring.js +715 -0
- package/dist/capability-core.js +630 -0
- package/dist/capability-dispatcher.js +86 -0
- package/dist/capability-registry.js +523 -0
- package/dist/cli.js +1276 -0
- package/dist/collaboration.js +727 -0
- package/dist/commit.js +570 -0
- package/dist/contract-migration.js +234 -0
- package/dist/coordinator.js +1163 -0
- package/dist/daemon.js +44 -0
- package/dist/dispatch.js +201 -0
- package/dist/drive.js +503 -0
- package/dist/error-feedback.js +415 -0
- package/dist/evidence-grounding.js +179 -0
- package/dist/evidence-reasoning.js +733 -0
- package/dist/execution-backend.js +1279 -0
- package/dist/harness.js +61 -0
- package/dist/mcp-server.js +1615 -0
- package/dist/multi-agent-eval.js +857 -0
- package/dist/multi-agent-host.js +764 -0
- package/dist/multi-agent-operator-ux.js +537 -0
- package/dist/multi-agent-trust.js +366 -0
- package/dist/multi-agent.js +1173 -0
- package/dist/node-snapshot.js +270 -0
- package/dist/observability.js +922 -0
- package/dist/operator-ux.js +971 -0
- package/dist/orchestrator/audit-operations.js +182 -0
- package/dist/orchestrator/candidate-operations.js +117 -0
- package/dist/orchestrator/cli-options.js +288 -0
- package/dist/orchestrator/collaboration-operations.js +86 -0
- package/dist/orchestrator/feedback-operations.js +81 -0
- package/dist/orchestrator/host-operations.js +78 -0
- package/dist/orchestrator/lifecycle-operations.js +462 -0
- package/dist/orchestrator/migration-operations.js +44 -0
- package/dist/orchestrator/multi-agent-operations.js +362 -0
- package/dist/orchestrator/report.js +369 -0
- package/dist/orchestrator/topology-operations.js +84 -0
- package/dist/orchestrator.js +874 -0
- package/dist/pipeline-contract.js +92 -0
- package/dist/pipeline-runner.js +285 -0
- package/dist/reclamation.js +882 -0
- package/dist/result-normalize.js +194 -0
- package/dist/run-export.js +64 -0
- package/dist/run-registry.js +1347 -0
- package/dist/run-state-schema.js +67 -0
- package/dist/sandbox-profile.js +471 -0
- package/dist/scheduler.js +266 -0
- package/dist/scheduling.js +184 -0
- package/dist/schema-validate.js +98 -0
- package/dist/state-explosion.js +1213 -0
- package/dist/state-migrations.js +463 -0
- package/dist/state-node.js +301 -0
- package/dist/state.js +308 -0
- package/dist/telemetry-attestation.js +156 -0
- package/dist/telemetry-ledger.js +145 -0
- package/dist/topology.js +527 -0
- package/dist/triggers.js +159 -0
- package/dist/trust-audit.js +475 -0
- package/dist/types/blackboard.js +2 -0
- package/dist/types/boundary.js +29 -0
- package/dist/types/candidate.js +2 -0
- package/dist/types/collaboration.js +2 -0
- package/dist/types/core.js +2 -0
- package/dist/types/drive.js +10 -0
- package/dist/types/error-feedback.js +2 -0
- package/dist/types/evidence-reasoning.js +2 -0
- package/dist/types/execution-backend.js +2 -0
- package/dist/types/multi-agent.js +2 -0
- package/dist/types/observability.js +2 -0
- package/dist/types/pipeline.js +2 -0
- package/dist/types/reclamation.js +8 -0
- package/dist/types/result.js +2 -0
- package/dist/types/run-registry.js +2 -0
- package/dist/types/run.js +2 -0
- package/dist/types/sandbox.js +2 -0
- package/dist/types/schedule.js +2 -0
- package/dist/types/state-node.js +2 -0
- package/dist/types/topology.js +2 -0
- package/dist/types/trust.js +2 -0
- package/dist/types/workbench.js +2 -0
- package/dist/types/worker.js +2 -0
- package/dist/types/workflow-app.js +2 -0
- package/dist/types.js +43 -0
- package/dist/verifier-registry.js +46 -0
- package/dist/verifier.js +78 -0
- package/dist/version.js +8 -0
- package/dist/workbench-host.js +172 -0
- package/dist/workbench.js +190 -0
- package/dist/worker-isolation.js +1028 -0
- package/dist/workflow-api.js +98 -0
- package/dist/workflow-app-framework.js +626 -0
- package/docs/agent-delegation-drive.7.md +190 -0
- package/docs/agent-framework.md +176 -0
- package/docs/candidate-scoring.7.md +106 -0
- package/docs/canonical-workflow-apps.7.md +137 -0
- package/docs/capability-topology-registry.7.md +168 -0
- package/docs/cli-mcp-parity.7.md +373 -0
- package/docs/contract-migration-tooling.7.md +123 -0
- package/docs/control-plane-scheduling.7.md +110 -0
- package/docs/coordinator-blackboard.7.md +183 -0
- package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
- package/docs/dogfood-one-real-repo.7.md +168 -0
- package/docs/durable-state-and-locking.7.md +107 -0
- package/docs/end-to-end-golden-path.7.md +117 -0
- package/docs/error-feedback.7.md +153 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
- package/docs/execution-backends.7.md +300 -0
- package/docs/getting-started.md +99 -0
- package/docs/index.md +41 -0
- package/docs/mcp-app-surface.7.md +235 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
- package/docs/multi-agent-eval-replay-harness.7.md +302 -0
- package/docs/multi-agent-operator-ux.7.md +314 -0
- package/docs/multi-agent-runtime-core.7.md +231 -0
- package/docs/multi-agent-topologies.7.md +103 -0
- package/docs/multi-agent-trust-policy-audit.7.md +154 -0
- package/docs/node-snapshot-diff-replay.7.md +135 -0
- package/docs/observability-cost-accounting.7.md +194 -0
- package/docs/operator-ux.7.md +180 -0
- package/docs/pipeline-runner.7.md +136 -0
- package/docs/project-index.md +261 -0
- package/docs/real-execution-backends.7.md +142 -0
- package/docs/release-and-migration.7.md +280 -0
- package/docs/release-tooling.7.md +159 -0
- package/docs/routines.md +48 -0
- package/docs/run-registry-control-plane.7.md +312 -0
- package/docs/run-retention-reclamation.7.md +191 -0
- package/docs/sandbox-profiles.7.md +137 -0
- package/docs/scheduled-tasks.md +80 -0
- package/docs/security-trust-hardening.7.md +117 -0
- package/docs/state-explosion-management.7.md +264 -0
- package/docs/state-node.7.md +96 -0
- package/docs/team-collaboration.7.md +207 -0
- package/docs/unix-principles.md +192 -0
- package/docs/verifier-gated-commit.7.md +140 -0
- package/docs/web-desktop-workbench.7.md +215 -0
- package/docs/worker-isolation.7.md +167 -0
- package/docs/workflow-app-framework.7.md +274 -0
- package/manifest/README.md +43 -0
- package/manifest/plugin.manifest.json +316 -0
- package/manifest/pricing.policy.json +14 -0
- package/package.json +79 -0
- package/scripts/agents/claude-p-agent.js +104 -0
- package/scripts/agents/claude-p-agent.sh +9 -0
- package/scripts/agents/cw-attest-keygen.js +55 -0
- package/scripts/agents/cw-attest-wrap.js +143 -0
- package/scripts/block-unapproved-tag.sh +39 -0
- package/scripts/bump-version.js +249 -0
- package/scripts/canonical-apps.js +171 -0
- package/scripts/cw.js +4 -0
- package/scripts/dist-drift-check.js +79 -0
- package/scripts/dogfood-architecture-review.js +237 -0
- package/scripts/dogfood-release.js +624 -0
- package/scripts/forward-ref-docs.js +73 -0
- package/scripts/gen-manifests.js +232 -0
- package/scripts/golden-path.js +300 -0
- package/scripts/mcp-server.js +4 -0
- package/scripts/new-feature.js +121 -0
- package/scripts/parity-check.js +213 -0
- package/scripts/release-check.js +118 -0
- package/scripts/release-flow.js +272 -0
- package/scripts/release-gate.sh +85 -0
- package/scripts/sync-project-index.js +387 -0
- package/scripts/validate-run-state-schema.js +126 -0
- package/scripts/verify-container-selfref.js +64 -0
- package/scripts/version-sync-check.js +237 -0
- package/skills/cool-workflow/SKILL.md +162 -0
- package/skills/cool-workflow/references/commands.md +282 -0
- package/tsconfig.json +16 -0
- package/ui/workbench/app.css +76 -0
- package/ui/workbench/app.js +159 -0
- package/ui/workbench/index.html +32 -0
- package/workflows/architecture-review.workflow.js +84 -0
- package/workflows/research-synthesis.workflow.js +47 -0
package/dist/topology.js
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OFFICIAL_TOPOLOGIES = exports.TOPOLOGY_SCHEMA_VERSION = void 0;
|
|
7
|
+
exports.ensureTopologyState = ensureTopologyState;
|
|
8
|
+
exports.persistTopologyState = persistTopologyState;
|
|
9
|
+
exports.registerTopology = registerTopology;
|
|
10
|
+
exports.listTopologyDefinitions = listTopologyDefinitions;
|
|
11
|
+
exports.getTopologyDefinition = getTopologyDefinition;
|
|
12
|
+
exports.validateTopologyDefinition = validateTopologyDefinition;
|
|
13
|
+
exports.applyTopology = applyTopology;
|
|
14
|
+
exports.summarizeTopologies = summarizeTopologies;
|
|
15
|
+
exports.buildTopologyGraph = buildTopologyGraph;
|
|
16
|
+
exports.showTopologyRun = showTopologyRun;
|
|
17
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
18
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
19
|
+
const state_1 = require("./state");
|
|
20
|
+
const pipeline_contract_1 = require("./pipeline-contract");
|
|
21
|
+
const state_node_1 = require("./state-node");
|
|
22
|
+
const trust_audit_1 = require("./trust-audit");
|
|
23
|
+
const multi_agent_1 = require("./multi-agent");
|
|
24
|
+
const coordinator_1 = require("./coordinator");
|
|
25
|
+
exports.TOPOLOGY_SCHEMA_VERSION = 1;
|
|
26
|
+
exports.OFFICIAL_TOPOLOGIES = [
|
|
27
|
+
{
|
|
28
|
+
schemaVersion: 1,
|
|
29
|
+
id: "map-reduce",
|
|
30
|
+
title: "Map-Reduce",
|
|
31
|
+
summary: "Fan out mapper roles, index mapper evidence on the blackboard, then reduce only after required evidence is present.",
|
|
32
|
+
roles: [
|
|
33
|
+
roleSpec("mapper", "Mapper", ["Produce an independent shard result and cite evidence."], ["mapper output artifact"], ["indexed mapper artifact"]),
|
|
34
|
+
roleSpec("reducer", "Reducer", ["Synthesize mapper outputs only after fanin is verifier-ready."], ["reducer synthesis"], ["all mapper evidence"])
|
|
35
|
+
],
|
|
36
|
+
groups: [{ id: "map-reduce", title: "Map-Reduce Group", roleIds: ["mapper", "reducer"] }],
|
|
37
|
+
blackboardTopics: [
|
|
38
|
+
topicSpec("mapper-outputs", "Mapper Outputs", "Indexed mapper result artifacts and evidence."),
|
|
39
|
+
topicSpec("reducer-synthesis", "Reducer Synthesis", "Reducer fanin readiness and synthesis provenance.")
|
|
40
|
+
],
|
|
41
|
+
phases: [
|
|
42
|
+
phaseSpec("map", "Map", ["mapper"], true, false, ["mapper output artifact"], ["artifact-index"]),
|
|
43
|
+
phaseSpec("reduce", "Reduce", ["reducer"], false, true, ["all mapper evidence"], ["fanin-readiness", "candidate-synthesis"])
|
|
44
|
+
],
|
|
45
|
+
fanoutStrategy: "one membership per mapper role over selected run tasks",
|
|
46
|
+
faninStrategy: "required mapper roles must report result evidence and indexed blackboard artifacts",
|
|
47
|
+
requiredEvidence: ["mapper output artifact", "blackboard artifact ref", "reducer synthesis"],
|
|
48
|
+
coordinatorDecisions: ["artifact-index", "fanin-readiness", "candidate-synthesis"],
|
|
49
|
+
candidateExpectations: ["Reducer result becomes a candidate only with mapper provenance."],
|
|
50
|
+
verifierGates: ["Reducer fanin must be ready before completion or commit."]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
schemaVersion: 1,
|
|
54
|
+
id: "debate",
|
|
55
|
+
title: "Debate",
|
|
56
|
+
summary: "Record opposing claims, rebuttal rounds, conflict context, coordinator decisions, and final synthesis on shared topics.",
|
|
57
|
+
roles: [
|
|
58
|
+
roleSpec("position-a", "Position A", ["Argue one supported position with evidence."], ["claim message"], ["round messages"]),
|
|
59
|
+
roleSpec("position-b", "Position B", ["Argue a contrasting position with evidence."], ["counterclaim message"], ["round messages"]),
|
|
60
|
+
roleSpec("synthesizer", "Synthesis", ["Resolve or preserve conflicts with citations."], ["debate synthesis"], ["coordinator decisions"])
|
|
61
|
+
],
|
|
62
|
+
groups: [{ id: "debate", title: "Debate Group", roleIds: ["position-a", "position-b", "synthesizer"] }],
|
|
63
|
+
blackboardTopics: [
|
|
64
|
+
topicSpec("debate-rounds", "Debate Rounds", "Claim and rebuttal messages by round."),
|
|
65
|
+
topicSpec("debate-conflicts", "Conflict Context", "Conflicting or unresolved claims."),
|
|
66
|
+
topicSpec("debate-synthesis", "Final Synthesis", "Accepted, rejected, conflicting, and unresolved claims.")
|
|
67
|
+
],
|
|
68
|
+
phases: [
|
|
69
|
+
phaseSpec("opening", "Opening Claims", ["position-a", "position-b"], true, false, ["claim evidence"], ["message-moderation"]),
|
|
70
|
+
phaseSpec("rebuttal", "Rebuttal Rounds", ["position-a", "position-b"], true, false, ["response evidence"], ["conflict-resolution"]),
|
|
71
|
+
phaseSpec("synthesis", "Synthesis", ["synthesizer"], false, true, ["debate messages", "coordinator decisions"], ["candidate-synthesis"])
|
|
72
|
+
],
|
|
73
|
+
fanoutStrategy: "opposing roles write blackboard messages for each round",
|
|
74
|
+
faninStrategy: "synthesis requires debate messages and coordinator claim decisions",
|
|
75
|
+
requiredEvidence: ["debate message", "conflict context", "coordinator decision", "final synthesis"],
|
|
76
|
+
coordinatorDecisions: ["message-moderation", "conflict-resolution", "candidate-synthesis"],
|
|
77
|
+
candidateExpectations: ["Final synthesis cites debate messages and decisions."],
|
|
78
|
+
verifierGates: ["Required debate rounds and synthesis evidence must be present."]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
schemaVersion: 1,
|
|
82
|
+
id: "judge-panel",
|
|
83
|
+
title: "Judge Panel",
|
|
84
|
+
summary: "Collect independent judge outputs, aggregate scores, and select a panel decision with linked evidence.",
|
|
85
|
+
roles: [
|
|
86
|
+
roleSpec("judge", "Judge", ["Score candidates independently and cite evidence."], ["judge score artifact"], ["judge verdict"]),
|
|
87
|
+
roleSpec("panel-chair", "Panel Chair", ["Aggregate scores and write a panel decision."], ["panel decision"], ["judge evidence"])
|
|
88
|
+
],
|
|
89
|
+
groups: [{ id: "judge-panel", title: "Judge Panel Group", roleIds: ["judge", "panel-chair"] }],
|
|
90
|
+
blackboardTopics: [
|
|
91
|
+
topicSpec("judge-verdicts", "Judge Verdicts", "Independent judge outputs and score evidence."),
|
|
92
|
+
topicSpec("panel-decision", "Panel Decision", "Aggregated verdict and candidate selection rationale.")
|
|
93
|
+
],
|
|
94
|
+
phases: [
|
|
95
|
+
phaseSpec("judge", "Judge", ["judge"], true, false, ["judge score artifact"], ["artifact-index"]),
|
|
96
|
+
phaseSpec("panel", "Panel", ["panel-chair"], false, true, ["judge evidence", "score records"], ["candidate-synthesis"])
|
|
97
|
+
],
|
|
98
|
+
fanoutStrategy: "one membership per independent judge role",
|
|
99
|
+
faninStrategy: "panel decision requires fanin over judge evidence and score records",
|
|
100
|
+
requiredEvidence: ["judge output", "score record", "panel decision", "candidate selection rationale"],
|
|
101
|
+
coordinatorDecisions: ["artifact-index", "candidate-synthesis"],
|
|
102
|
+
candidateExpectations: ["No single judge is authoritative without aggregated fanin and score evidence."],
|
|
103
|
+
verifierGates: ["Panel decision requires multiple judge outputs unless explicitly configured otherwise."]
|
|
104
|
+
}
|
|
105
|
+
];
|
|
106
|
+
function ensureTopologyState(run) {
|
|
107
|
+
run.paths.topologiesDir = topologyRoot(run);
|
|
108
|
+
node_fs_1.default.mkdirSync(run.paths.topologiesDir, { recursive: true });
|
|
109
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(run.paths.topologiesDir, "runs"), { recursive: true });
|
|
110
|
+
if (!run.topologies)
|
|
111
|
+
run.topologies = { schemaVersion: exports.TOPOLOGY_SCHEMA_VERSION, runs: [] };
|
|
112
|
+
run.topologies.schemaVersion = exports.TOPOLOGY_SCHEMA_VERSION;
|
|
113
|
+
run.topologies.runs = run.topologies.runs || [];
|
|
114
|
+
return run.topologies;
|
|
115
|
+
}
|
|
116
|
+
function persistTopologyState(run) {
|
|
117
|
+
const state = ensureTopologyState(run);
|
|
118
|
+
(0, state_1.writeJson)(node_path_1.default.join(topologyRoot(run), "index.json"), {
|
|
119
|
+
schemaVersion: exports.TOPOLOGY_SCHEMA_VERSION,
|
|
120
|
+
runId: run.id,
|
|
121
|
+
counts: { runs: state.runs.length },
|
|
122
|
+
runs: state.runs.map((record) => ({
|
|
123
|
+
id: record.id,
|
|
124
|
+
topologyId: record.topologyId,
|
|
125
|
+
status: record.status,
|
|
126
|
+
updatedAt: record.updatedAt
|
|
127
|
+
}))
|
|
128
|
+
});
|
|
129
|
+
for (const record of state.runs)
|
|
130
|
+
(0, state_1.writeJson)(topologyRunPath(run, record.id), record);
|
|
131
|
+
}
|
|
132
|
+
// ---- Topology registry (v0.1.53) — MECHANISM, not policy ------------------
|
|
133
|
+
// SEPARATE MECHANISM FROM POLICY. The Map is mechanism; OFFICIAL_TOPOLOGIES and
|
|
134
|
+
// any registerTopology() calls are policy. listTopologyDefinitions() composes
|
|
135
|
+
// them — consumers see one merged set, never two.
|
|
136
|
+
const _topologyRegistry = new Map();
|
|
137
|
+
/** Register a topology definition. Later registrations with the same id
|
|
138
|
+
* overwrite earlier ones (last-write-wins dedup). */
|
|
139
|
+
function registerTopology(definition) {
|
|
140
|
+
_topologyRegistry.set(definition.id, clone(definition));
|
|
141
|
+
}
|
|
142
|
+
function listTopologyDefinitions() {
|
|
143
|
+
const merged = exports.OFFICIAL_TOPOLOGIES.map((definition) => clone(definition));
|
|
144
|
+
for (const registered of _topologyRegistry.values()) {
|
|
145
|
+
const idx = merged.findIndex((d) => d.id === registered.id);
|
|
146
|
+
if (idx >= 0)
|
|
147
|
+
merged[idx] = clone(registered);
|
|
148
|
+
else
|
|
149
|
+
merged.push(clone(registered));
|
|
150
|
+
}
|
|
151
|
+
return merged;
|
|
152
|
+
}
|
|
153
|
+
function getTopologyDefinition(topologyId) {
|
|
154
|
+
const registered = _topologyRegistry.get(topologyId);
|
|
155
|
+
if (registered)
|
|
156
|
+
return clone(registered);
|
|
157
|
+
return exports.OFFICIAL_TOPOLOGIES.find((definition) => definition.id === topologyId);
|
|
158
|
+
}
|
|
159
|
+
function validateTopologyDefinition(topologyId) {
|
|
160
|
+
const definition = getTopologyDefinition(topologyId);
|
|
161
|
+
if (!definition)
|
|
162
|
+
return { valid: false, topologyId, issues: [{ code: "unknown-topology", message: `Unknown topology id: ${topologyId}` }] };
|
|
163
|
+
const issues = [];
|
|
164
|
+
if (!definition.roles.length)
|
|
165
|
+
issues.push(issue("missing-roles", "Topology must declare at least one role.", "roles"));
|
|
166
|
+
if (!definition.groups.length)
|
|
167
|
+
issues.push(issue("missing-groups", "Topology must declare at least one group.", "groups"));
|
|
168
|
+
if (!definition.blackboardTopics.length)
|
|
169
|
+
issues.push(issue("missing-topics", "Topology must declare blackboard topics.", "blackboardTopics"));
|
|
170
|
+
if (!definition.requiredEvidence.length)
|
|
171
|
+
issues.push(issue("missing-evidence", "Topology must declare required evidence.", "requiredEvidence"));
|
|
172
|
+
const roleIds = new Set(definition.roles.map((role) => role.id));
|
|
173
|
+
for (const phase of definition.phases) {
|
|
174
|
+
for (const roleId of phase.roleIds) {
|
|
175
|
+
if (!roleIds.has(roleId))
|
|
176
|
+
issues.push(issue("unknown-phase-role", `Phase ${phase.id} references unknown role ${roleId}.`, `phases.${phase.id}`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return { valid: issues.length === 0, topologyId, issues, definition };
|
|
180
|
+
}
|
|
181
|
+
function applyTopology(run, topologyId, input = {}) {
|
|
182
|
+
const validation = validateTopologyDefinition(topologyId);
|
|
183
|
+
if (!validation.valid || !validation.definition) {
|
|
184
|
+
throw new Error(`Invalid topology ${topologyId}: ${validation.issues.map((entry) => entry.message).join("; ")}`);
|
|
185
|
+
}
|
|
186
|
+
const definition = validation.definition;
|
|
187
|
+
const state = ensureTopologyState(run);
|
|
188
|
+
(0, multi_agent_1.ensureMultiAgentState)(run);
|
|
189
|
+
const id = input.id || `${definition.id}-${timestampId()}`;
|
|
190
|
+
if (state.runs.some((record) => record.id === id))
|
|
191
|
+
throw new Error(`Duplicate MultiAgentTopologyRun id: ${id}`);
|
|
192
|
+
const taskIds = selectedTaskIds(run, input.taskIds);
|
|
193
|
+
const board = (0, coordinator_1.resolveBlackboard)(run, {
|
|
194
|
+
id: input.blackboardId || `${id}-blackboard`,
|
|
195
|
+
title: `${definition.title} Blackboard`,
|
|
196
|
+
tags: ["topology", definition.id]
|
|
197
|
+
});
|
|
198
|
+
const topics = definition.blackboardTopics.map((topic) => (0, coordinator_1.createBlackboardTopic)(run, {
|
|
199
|
+
id: `${id}-${topic.id}`,
|
|
200
|
+
title: topic.title,
|
|
201
|
+
description: topic.description,
|
|
202
|
+
blackboardId: board.id,
|
|
203
|
+
tags: ["topology", definition.id]
|
|
204
|
+
}));
|
|
205
|
+
const multiAgentRun = (0, multi_agent_1.createMultiAgentRun)(run, {
|
|
206
|
+
id: input.multiAgentRunId || `${id}-ma`,
|
|
207
|
+
title: input.title || definition.title,
|
|
208
|
+
objective: definition.summary,
|
|
209
|
+
blackboardId: board.id,
|
|
210
|
+
topicIds: topics.map((topic) => topic.id),
|
|
211
|
+
metadata: { topologyId: definition.id, topologyRunId: id }
|
|
212
|
+
});
|
|
213
|
+
const roleIds = [];
|
|
214
|
+
for (const role of materializedRoles(definition, input)) {
|
|
215
|
+
const record = (0, multi_agent_1.createAgentRole)(run, {
|
|
216
|
+
id: `${id}-${role.id}`,
|
|
217
|
+
multiAgentRunId: multiAgentRun.id,
|
|
218
|
+
title: role.title,
|
|
219
|
+
responsibilities: role.responsibilities,
|
|
220
|
+
requiredEvidence: role.requiredEvidence,
|
|
221
|
+
expectedArtifacts: role.expectedArtifacts,
|
|
222
|
+
faninObligations: role.faninObligations,
|
|
223
|
+
blackboardId: board.id,
|
|
224
|
+
topicIds: topics.map((topic) => topic.id),
|
|
225
|
+
metadata: { topologyId: definition.id, topologyRunId: id, topologyRoleId: role.id }
|
|
226
|
+
});
|
|
227
|
+
roleIds.push(record.id);
|
|
228
|
+
}
|
|
229
|
+
const group = (0, multi_agent_1.createAgentGroup)(run, {
|
|
230
|
+
id: `${id}-group`,
|
|
231
|
+
multiAgentRunId: multiAgentRun.id,
|
|
232
|
+
title: `${definition.title} Group`,
|
|
233
|
+
phase: definition.title,
|
|
234
|
+
taskIds,
|
|
235
|
+
blackboardId: board.id,
|
|
236
|
+
topicIds: topics.map((topic) => topic.id),
|
|
237
|
+
metadata: { topologyId: definition.id, topologyRunId: id }
|
|
238
|
+
});
|
|
239
|
+
const fanoutRoleIds = roleIds.filter((roleId) => !roleId.endsWith("-reducer") && !roleId.endsWith("-synthesizer") && !roleId.endsWith("-panel-chair"));
|
|
240
|
+
const fanout = (0, multi_agent_1.createAgentFanout)(run, {
|
|
241
|
+
id: `${id}-fanout`,
|
|
242
|
+
multiAgentRunId: multiAgentRun.id,
|
|
243
|
+
groupId: group.id,
|
|
244
|
+
reason: `${definition.id} topology fanout`,
|
|
245
|
+
roleIds: fanoutRoleIds.length ? fanoutRoleIds : roleIds,
|
|
246
|
+
taskIds,
|
|
247
|
+
concurrencyLimit: fanoutRoleIds.length || roleIds.length,
|
|
248
|
+
expectedReturnShape: `${definition.title} worker output must include cw:result evidence and blackboard-indexable artifacts/messages.`,
|
|
249
|
+
blackboardId: board.id,
|
|
250
|
+
topicIds: topics.map((topic) => topic.id),
|
|
251
|
+
metadata: { topologyId: definition.id, topologyRunId: id, fanoutStrategy: definition.fanoutStrategy }
|
|
252
|
+
});
|
|
253
|
+
const message = (0, coordinator_1.postBlackboardMessage)(run, {
|
|
254
|
+
topicId: topics[0].id,
|
|
255
|
+
blackboardId: board.id,
|
|
256
|
+
body: `${definition.title} topology applied. Roles=${roleIds.join(", ")} fanout=${fanout.id}.`,
|
|
257
|
+
tags: ["topology", definition.id],
|
|
258
|
+
metadata: { topologyRunId: id }
|
|
259
|
+
});
|
|
260
|
+
const decision = (0, coordinator_1.recordCoordinatorDecision)(run, {
|
|
261
|
+
blackboardId: board.id,
|
|
262
|
+
topicId: topics[0].id,
|
|
263
|
+
kind: "context-update",
|
|
264
|
+
outcome: "accepted",
|
|
265
|
+
reason: `${definition.title} topology materialized on multi-agent runtime and blackboard.`,
|
|
266
|
+
subjectIds: [multiAgentRun.id, group.id, fanout.id],
|
|
267
|
+
messageIds: [message.id],
|
|
268
|
+
tags: ["topology", definition.id],
|
|
269
|
+
metadata: { topologyRunId: id }
|
|
270
|
+
});
|
|
271
|
+
const fanin = input.collectInitialFanin ? (0, multi_agent_1.collectAgentFanin)(run, {
|
|
272
|
+
id: `${id}-fanin-initial`,
|
|
273
|
+
multiAgentRunId: multiAgentRun.id,
|
|
274
|
+
groupId: group.id,
|
|
275
|
+
fanoutId: fanout.id,
|
|
276
|
+
requiredRoleIds: fanout.roleIds,
|
|
277
|
+
strategy: definition.faninStrategy,
|
|
278
|
+
blackboardId: board.id,
|
|
279
|
+
topicIds: topics.map((topic) => topic.id),
|
|
280
|
+
metadata: { topologyId: definition.id, topologyRunId: id }
|
|
281
|
+
}) : undefined;
|
|
282
|
+
const audit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
283
|
+
kind: "topology.create",
|
|
284
|
+
decision: "recorded",
|
|
285
|
+
source: "runtime-derived",
|
|
286
|
+
topologyId: definition.id,
|
|
287
|
+
topologyRunId: id,
|
|
288
|
+
multiAgentRunId: multiAgentRun.id,
|
|
289
|
+
agentGroupId: group.id,
|
|
290
|
+
agentFanoutId: fanout.id,
|
|
291
|
+
blackboardId: board.id,
|
|
292
|
+
blackboardMessageId: message.id,
|
|
293
|
+
coordinatorDecisionId: decision.id,
|
|
294
|
+
metadata: { fanoutStrategy: definition.fanoutStrategy, faninStrategy: definition.faninStrategy }
|
|
295
|
+
});
|
|
296
|
+
const now = new Date().toISOString();
|
|
297
|
+
const record = {
|
|
298
|
+
schemaVersion: exports.TOPOLOGY_SCHEMA_VERSION,
|
|
299
|
+
id,
|
|
300
|
+
runId: run.id,
|
|
301
|
+
topologyId: definition.id,
|
|
302
|
+
createdAt: now,
|
|
303
|
+
updatedAt: now,
|
|
304
|
+
status: fanin?.status === "blocked" ? "blocked" : "planned",
|
|
305
|
+
title: input.title || definition.title,
|
|
306
|
+
multiAgentRunId: multiAgentRun.id,
|
|
307
|
+
blackboardId: board.id,
|
|
308
|
+
topicIds: topics.map((topic) => topic.id),
|
|
309
|
+
roleIds,
|
|
310
|
+
groupIds: [group.id],
|
|
311
|
+
fanoutIds: [fanout.id],
|
|
312
|
+
faninIds: fanin ? [fanin.id] : [],
|
|
313
|
+
messageIds: [message.id],
|
|
314
|
+
artifactRefIds: [],
|
|
315
|
+
coordinatorDecisionIds: [decision.id],
|
|
316
|
+
candidateIds: [],
|
|
317
|
+
selectionIds: [],
|
|
318
|
+
commitIds: [],
|
|
319
|
+
missingEvidence: fanin?.blockedReasons || definition.requiredEvidence,
|
|
320
|
+
conflicts: [],
|
|
321
|
+
nextActions: nextActionsFor(definition.id, run.id, id, fanout.id),
|
|
322
|
+
links: {
|
|
323
|
+
workflowRunId: run.id,
|
|
324
|
+
multiAgentRunId: multiAgentRun.id,
|
|
325
|
+
blackboardId: board.id,
|
|
326
|
+
blackboardTopicIds: topics.map((topic) => topic.id),
|
|
327
|
+
agentRoleIds: roleIds,
|
|
328
|
+
agentGroupIds: [group.id],
|
|
329
|
+
agentFanoutIds: [fanout.id],
|
|
330
|
+
agentFaninIds: fanin ? [fanin.id] : [],
|
|
331
|
+
coordinatorDecisionIds: [decision.id],
|
|
332
|
+
candidateIds: [],
|
|
333
|
+
selectionIds: [],
|
|
334
|
+
commitIds: [],
|
|
335
|
+
auditEventIds: [audit.id]
|
|
336
|
+
},
|
|
337
|
+
metadata: compact({ ...input.metadata, topology: definition })
|
|
338
|
+
};
|
|
339
|
+
state.runs.push(record);
|
|
340
|
+
appendTopologyNode(run, record, statusToNodeStatus(record.status));
|
|
341
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
342
|
+
kind: "topology.verdict",
|
|
343
|
+
decision: record.status === "blocked" ? "failed" : "recorded",
|
|
344
|
+
source: "cw-validated",
|
|
345
|
+
topologyId: definition.id,
|
|
346
|
+
topologyRunId: id,
|
|
347
|
+
multiAgentRunId: multiAgentRun.id,
|
|
348
|
+
agentFanoutId: fanout.id,
|
|
349
|
+
agentFaninId: fanin?.id,
|
|
350
|
+
blackboardId: board.id,
|
|
351
|
+
coordinatorDecisionId: decision.id,
|
|
352
|
+
metadata: { status: record.status, missingEvidence: record.missingEvidence }
|
|
353
|
+
});
|
|
354
|
+
persistTopologyState(run);
|
|
355
|
+
return record;
|
|
356
|
+
}
|
|
357
|
+
function summarizeTopologies(run) {
|
|
358
|
+
const state = ensureTopologyState(run);
|
|
359
|
+
const multi = (0, multi_agent_1.ensureMultiAgentState)(run);
|
|
360
|
+
const active = state.runs.map((record) => {
|
|
361
|
+
const inferredFanins = multi.fanins.filter((fanin) => record.groupIds.includes(fanin.groupId) || record.fanoutIds.includes(fanin.fanoutId || ""));
|
|
362
|
+
const allFaninIds = unique([...record.faninIds, ...inferredFanins.map((fanin) => fanin.id)]);
|
|
363
|
+
const blocked = inferredFanins.filter((fanin) => fanin.status === "blocked" || !fanin.verifierReady);
|
|
364
|
+
const ready = inferredFanins.some((fanin) => fanin.verifierReady);
|
|
365
|
+
const missingEvidence = unique([
|
|
366
|
+
...record.missingEvidence,
|
|
367
|
+
...blocked.flatMap((fanin) => fanin.blockedReasons)
|
|
368
|
+
]);
|
|
369
|
+
return {
|
|
370
|
+
id: record.id,
|
|
371
|
+
topologyId: record.topologyId,
|
|
372
|
+
status: ready ? "ready" : blocked.length ? "blocked" : record.status,
|
|
373
|
+
multiAgentRunId: record.multiAgentRunId,
|
|
374
|
+
blackboardId: record.blackboardId,
|
|
375
|
+
roles: record.roleIds,
|
|
376
|
+
groups: record.groupIds,
|
|
377
|
+
topics: record.topicIds,
|
|
378
|
+
fanouts: record.fanoutIds,
|
|
379
|
+
fanins: allFaninIds,
|
|
380
|
+
missingEvidence,
|
|
381
|
+
conflicts: record.conflicts,
|
|
382
|
+
readiness: ready ? "fanin ready" : missingEvidence.length ? "missing evidence" : "awaiting worker output",
|
|
383
|
+
nextActions: ready ? [`node scripts/cw.js candidate register ${run.id} --result-node <reducer-or-panel-result>`] : record.nextActions
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
return {
|
|
387
|
+
runId: run.id,
|
|
388
|
+
totalRuns: state.runs.length,
|
|
389
|
+
runsByStatus: countBy(active, (record) => record.status),
|
|
390
|
+
officialTopologies: exports.OFFICIAL_TOPOLOGIES.map((definition) => definition.id),
|
|
391
|
+
active,
|
|
392
|
+
nextAction: active.find((record) => record.nextActions.length)?.nextActions[0] || `node scripts/cw.js topology apply ${run.id} map-reduce --task <task-id>`
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function buildTopologyGraph(run) {
|
|
396
|
+
const state = ensureTopologyState(run);
|
|
397
|
+
const nodes = [];
|
|
398
|
+
const edges = [];
|
|
399
|
+
for (const record of state.runs) {
|
|
400
|
+
nodes.push({ id: `${run.id}:topology:${record.id}`, kind: "topology-run", status: record.status, label: `${record.topologyId}:${record.id}`, path: topologyRunPath(run, record.id) });
|
|
401
|
+
edges.push({ from: `${run.id}:run`, to: `${run.id}:topology:${record.id}` });
|
|
402
|
+
edges.push({ from: `${run.id}:topology:${record.id}`, to: `${run.id}:multi-agent:${record.multiAgentRunId}`, label: "multi-agent" });
|
|
403
|
+
edges.push({ from: `${run.id}:topology:${record.id}`, to: `${run.id}:blackboard:${record.blackboardId}`, label: "blackboard" });
|
|
404
|
+
for (const topicId of record.topicIds)
|
|
405
|
+
edges.push({ from: `${run.id}:topology:${record.id}`, to: `${run.id}:blackboard:topic:${topicId}`, label: "topic" });
|
|
406
|
+
for (const roleId of record.roleIds)
|
|
407
|
+
edges.push({ from: `${run.id}:topology:${record.id}`, to: `${run.id}:multi-agent:role:${roleId}`, label: "role" });
|
|
408
|
+
for (const groupId of record.groupIds)
|
|
409
|
+
edges.push({ from: `${run.id}:topology:${record.id}`, to: `${run.id}:multi-agent:group:${groupId}`, label: "group" });
|
|
410
|
+
for (const fanoutId of record.fanoutIds)
|
|
411
|
+
edges.push({ from: `${run.id}:topology:${record.id}`, to: `${run.id}:multi-agent:fanout:${fanoutId}`, label: "fanout" });
|
|
412
|
+
for (const faninId of record.faninIds)
|
|
413
|
+
edges.push({ from: `${run.id}:topology:${record.id}`, to: `${run.id}:multi-agent:fanin:${faninId}`, label: "fanin" });
|
|
414
|
+
for (const decisionId of record.coordinatorDecisionIds)
|
|
415
|
+
edges.push({ from: `${run.id}:topology:${record.id}`, to: `${run.id}:blackboard:decision:${decisionId}`, label: "decision" });
|
|
416
|
+
}
|
|
417
|
+
return { nodes, edges: uniqueEdges(edges) };
|
|
418
|
+
}
|
|
419
|
+
function showTopologyRun(run, topologyRunId) {
|
|
420
|
+
const record = ensureTopologyState(run).runs.find((entry) => entry.id === topologyRunId);
|
|
421
|
+
if (!record)
|
|
422
|
+
throw new Error(`Unknown topology run id: ${topologyRunId}`);
|
|
423
|
+
return record;
|
|
424
|
+
}
|
|
425
|
+
function materializedRoles(definition, input) {
|
|
426
|
+
const count = definition.id === "map-reduce" ? Math.max(1, input.mapperCount || 2) : definition.id === "judge-panel" ? Math.max(2, input.judgeCount || 3) : 1;
|
|
427
|
+
const roles = [];
|
|
428
|
+
for (const role of definition.roles) {
|
|
429
|
+
const roleCount = role.count ?? (role.id === "mapper" || role.id === "judge" ? count : 1);
|
|
430
|
+
if (roleCount > 1) {
|
|
431
|
+
for (let index = 1; index <= roleCount; index += 1)
|
|
432
|
+
roles.push(expandRole(role, `${role.id}-${index}`, `${role.title} ${index}`));
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
roles.push(expandRole(role, role.id, role.title));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return roles;
|
|
439
|
+
}
|
|
440
|
+
function selectedTaskIds(run, taskIds) {
|
|
441
|
+
const ids = taskIds?.length ? taskIds : [run.tasks.find((task) => task.status === "pending")?.id || run.tasks[0]?.id].filter(Boolean);
|
|
442
|
+
for (const id of ids) {
|
|
443
|
+
if (!run.tasks.some((task) => task.id === id))
|
|
444
|
+
throw new Error(`Unknown task id for topology: ${id}`);
|
|
445
|
+
}
|
|
446
|
+
return ids;
|
|
447
|
+
}
|
|
448
|
+
function appendTopologyNode(run, record, status) {
|
|
449
|
+
(0, state_node_1.appendRunNode)(run, (0, state_node_1.createStateNode)({
|
|
450
|
+
id: `${run.id}:topology:${record.id}`,
|
|
451
|
+
kind: "topology-run",
|
|
452
|
+
status,
|
|
453
|
+
loopStage: run.loopStage,
|
|
454
|
+
outputs: { topologyId: record.topologyId, status: record.status },
|
|
455
|
+
artifacts: [{ id: "topology-run", kind: "json", path: topologyRunPath(run, record.id) }],
|
|
456
|
+
parents: [`${run.id}:multi-agent:${record.multiAgentRunId}`, `${run.id}:blackboard:${record.blackboardId}`],
|
|
457
|
+
contractId: pipeline_contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
|
|
458
|
+
metadata: { topologyId: record.topologyId, topologyRunId: record.id }
|
|
459
|
+
}));
|
|
460
|
+
}
|
|
461
|
+
function roleSpec(id, title, responsibilities, expectedArtifacts, faninObligations) {
|
|
462
|
+
return { id, title, responsibilities, requiredEvidence: expectedArtifacts, expectedArtifacts, faninObligations };
|
|
463
|
+
}
|
|
464
|
+
function topicSpec(id, title, description) {
|
|
465
|
+
return { id, title, description };
|
|
466
|
+
}
|
|
467
|
+
function phaseSpec(id, title, roleIds, fanout, fanin, requiredEvidence, coordinatorDecisionKinds) {
|
|
468
|
+
return { id, title, roleIds, fanout, fanin, requiredEvidence, coordinatorDecisionKinds };
|
|
469
|
+
}
|
|
470
|
+
function expandRole(role, id, title) {
|
|
471
|
+
return { ...role, id, title };
|
|
472
|
+
}
|
|
473
|
+
function topologyRoot(run) {
|
|
474
|
+
return run.paths.topologiesDir || node_path_1.default.join(run.paths.runDir, "topologies");
|
|
475
|
+
}
|
|
476
|
+
function topologyRunPath(run, id) {
|
|
477
|
+
return node_path_1.default.join(topologyRoot(run), "runs", `${(0, state_1.safeFileName)(id)}.json`);
|
|
478
|
+
}
|
|
479
|
+
function nextActionsFor(topologyId, runId, topologyRunId, fanoutId) {
|
|
480
|
+
return [
|
|
481
|
+
`node scripts/cw.js dispatch ${runId} --multi-agent-fanout ${fanoutId}`,
|
|
482
|
+
`node scripts/cw.js multi-agent fanin ${runId} ${topologyRunId}-fanin --fanout ${fanoutId}`,
|
|
483
|
+
`node scripts/cw.js topology summary ${runId}`
|
|
484
|
+
];
|
|
485
|
+
}
|
|
486
|
+
function statusToNodeStatus(status) {
|
|
487
|
+
if (status === "completed" || status === "ready")
|
|
488
|
+
return "completed";
|
|
489
|
+
if (status === "blocked")
|
|
490
|
+
return "blocked";
|
|
491
|
+
if (status === "failed")
|
|
492
|
+
return "failed";
|
|
493
|
+
if (status === "running")
|
|
494
|
+
return "running";
|
|
495
|
+
return "pending";
|
|
496
|
+
}
|
|
497
|
+
function issue(code, message, path) {
|
|
498
|
+
return { code, message, path };
|
|
499
|
+
}
|
|
500
|
+
function timestampId() {
|
|
501
|
+
return new Date().toISOString().replace(/[-:.]/g, "").slice(0, 15).toLowerCase();
|
|
502
|
+
}
|
|
503
|
+
function countBy(items, pick) {
|
|
504
|
+
const counts = {};
|
|
505
|
+
for (const item of items)
|
|
506
|
+
counts[pick(item)] = (counts[pick(item)] || 0) + 1;
|
|
507
|
+
return counts;
|
|
508
|
+
}
|
|
509
|
+
function unique(items) {
|
|
510
|
+
return [...new Set(items.filter((item) => item !== undefined && item !== null))];
|
|
511
|
+
}
|
|
512
|
+
function uniqueEdges(edges) {
|
|
513
|
+
const seen = new Set();
|
|
514
|
+
return edges.filter((edge) => {
|
|
515
|
+
const key = `${edge.from}->${edge.to}:${edge.label || ""}`;
|
|
516
|
+
if (seen.has(key))
|
|
517
|
+
return false;
|
|
518
|
+
seen.add(key);
|
|
519
|
+
return true;
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
function compact(value) {
|
|
523
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
524
|
+
}
|
|
525
|
+
function clone(value) {
|
|
526
|
+
return JSON.parse(JSON.stringify(value));
|
|
527
|
+
}
|
package/dist/triggers.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RoutineTriggerBridge = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const state_1 = require("./state");
|
|
10
|
+
class RoutineTriggerBridge {
|
|
11
|
+
cwd;
|
|
12
|
+
storePath;
|
|
13
|
+
payloadsDir;
|
|
14
|
+
constructor(cwd = process.cwd()) {
|
|
15
|
+
this.cwd = node_path_1.default.resolve(cwd);
|
|
16
|
+
this.storePath = node_path_1.default.join(this.cwd, ".cw", "routines", "triggers.json");
|
|
17
|
+
this.payloadsDir = node_path_1.default.join(this.cwd, ".cw", "routines", "payloads");
|
|
18
|
+
}
|
|
19
|
+
create(options) {
|
|
20
|
+
const now = new Date().toISOString();
|
|
21
|
+
const trigger = {
|
|
22
|
+
id: createTriggerId(normalizeKind(options.kind)),
|
|
23
|
+
kind: normalizeKind(options.kind),
|
|
24
|
+
createdAt: now,
|
|
25
|
+
updatedAt: now,
|
|
26
|
+
source: String(options.source || options.kind || "api"),
|
|
27
|
+
prompt: requiredString(options.prompt, "prompt"),
|
|
28
|
+
workflowId: stringOption(options.workflowId),
|
|
29
|
+
runId: stringOption(options.runId),
|
|
30
|
+
match: parseJsonObject(options.match),
|
|
31
|
+
metadata: parseJsonObject(options.metadata)
|
|
32
|
+
};
|
|
33
|
+
const store = this.load();
|
|
34
|
+
store.triggers.push(trigger);
|
|
35
|
+
this.save(store);
|
|
36
|
+
return trigger;
|
|
37
|
+
}
|
|
38
|
+
list(kind) {
|
|
39
|
+
const store = this.load();
|
|
40
|
+
return kind ? store.triggers.filter((trigger) => trigger.kind === kind) : store.triggers;
|
|
41
|
+
}
|
|
42
|
+
delete(id) {
|
|
43
|
+
const store = this.load();
|
|
44
|
+
const before = store.triggers.length;
|
|
45
|
+
store.triggers = store.triggers.filter((trigger) => trigger.id !== id);
|
|
46
|
+
this.save(store);
|
|
47
|
+
return { deleted: store.triggers.length !== before, id };
|
|
48
|
+
}
|
|
49
|
+
fire(kind, payload) {
|
|
50
|
+
const normalizedKind = normalizeKind(kind);
|
|
51
|
+
const store = this.load();
|
|
52
|
+
const now = new Date().toISOString();
|
|
53
|
+
const events = store.triggers
|
|
54
|
+
.filter((trigger) => trigger.kind === normalizedKind)
|
|
55
|
+
.map((trigger) => this.createEvent(trigger, payload, now));
|
|
56
|
+
store.events.push(...events);
|
|
57
|
+
this.save(store);
|
|
58
|
+
return events;
|
|
59
|
+
}
|
|
60
|
+
events(triggerId) {
|
|
61
|
+
const store = this.load();
|
|
62
|
+
return triggerId ? store.events.filter((event) => event.triggerId === triggerId) : store.events;
|
|
63
|
+
}
|
|
64
|
+
createEvent(trigger, payload, receivedAt) {
|
|
65
|
+
const matched = matches(trigger.match, payload);
|
|
66
|
+
const eventId = createEventId(trigger.kind);
|
|
67
|
+
const payloadPath = node_path_1.default.join(this.payloadsDir, `${(0, state_1.safeFileName)(eventId)}.json`);
|
|
68
|
+
(0, state_1.writeJson)(payloadPath, {
|
|
69
|
+
schemaVersion: 1,
|
|
70
|
+
trigger,
|
|
71
|
+
receivedAt,
|
|
72
|
+
matched,
|
|
73
|
+
payload
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
id: eventId,
|
|
77
|
+
triggerId: trigger.id,
|
|
78
|
+
kind: trigger.kind,
|
|
79
|
+
receivedAt,
|
|
80
|
+
matched,
|
|
81
|
+
prompt: matched ? renderPrompt(trigger, payload) : undefined,
|
|
82
|
+
payloadPath
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
load() {
|
|
86
|
+
if (!node_fs_1.default.existsSync(this.storePath))
|
|
87
|
+
return { schemaVersion: 1, triggers: [], events: [] };
|
|
88
|
+
const value = (0, state_1.readJson)(this.storePath);
|
|
89
|
+
return {
|
|
90
|
+
schemaVersion: 1,
|
|
91
|
+
triggers: Array.isArray(value.triggers) ? value.triggers : [],
|
|
92
|
+
events: Array.isArray(value.events) ? value.events : []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
save(store) {
|
|
96
|
+
(0, state_1.writeJson)(this.storePath, store);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.RoutineTriggerBridge = RoutineTriggerBridge;
|
|
100
|
+
function normalizeKind(value) {
|
|
101
|
+
const kind = String(value || "api");
|
|
102
|
+
if (kind === "api" || kind === "github")
|
|
103
|
+
return kind;
|
|
104
|
+
throw new Error(`Unsupported routine trigger kind: ${kind}`);
|
|
105
|
+
}
|
|
106
|
+
function matches(match, payload) {
|
|
107
|
+
if (!match || !Object.keys(match).length)
|
|
108
|
+
return true;
|
|
109
|
+
if (!payload || typeof payload !== "object")
|
|
110
|
+
return false;
|
|
111
|
+
return Object.entries(match).every(([key, expected]) => deepValue(payload, key) === expected);
|
|
112
|
+
}
|
|
113
|
+
function deepValue(value, key) {
|
|
114
|
+
return key.split(".").reduce((current, part) => {
|
|
115
|
+
if (!current || typeof current !== "object")
|
|
116
|
+
return undefined;
|
|
117
|
+
return current[part];
|
|
118
|
+
}, value);
|
|
119
|
+
}
|
|
120
|
+
function renderPrompt(trigger, payload) {
|
|
121
|
+
return `${trigger.prompt}\n\ncw:routine\n${JSON.stringify({
|
|
122
|
+
triggerId: trigger.id,
|
|
123
|
+
kind: trigger.kind,
|
|
124
|
+
source: trigger.source,
|
|
125
|
+
workflowId: trigger.workflowId,
|
|
126
|
+
runId: trigger.runId,
|
|
127
|
+
payload
|
|
128
|
+
}, null, 2)}`;
|
|
129
|
+
}
|
|
130
|
+
function parseJsonObject(value) {
|
|
131
|
+
if (!value || value === true)
|
|
132
|
+
return undefined;
|
|
133
|
+
if (typeof value === "object")
|
|
134
|
+
return value;
|
|
135
|
+
const parsed = JSON.parse(String(value));
|
|
136
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
137
|
+
throw new Error("Expected JSON object");
|
|
138
|
+
}
|
|
139
|
+
return parsed;
|
|
140
|
+
}
|
|
141
|
+
function requiredString(value, name) {
|
|
142
|
+
const text = stringOption(value);
|
|
143
|
+
if (!text)
|
|
144
|
+
throw new Error(`Missing required ${name}`);
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
147
|
+
function stringOption(value) {
|
|
148
|
+
if (value === undefined || value === null || value === true)
|
|
149
|
+
return undefined;
|
|
150
|
+
return String(value);
|
|
151
|
+
}
|
|
152
|
+
function createTriggerId(kind) {
|
|
153
|
+
const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
|
|
154
|
+
return `${kind}-${stamp}-${Math.random().toString(36).slice(2, 8)}`;
|
|
155
|
+
}
|
|
156
|
+
function createEventId(kind) {
|
|
157
|
+
const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
|
|
158
|
+
return `event-${kind}-${stamp}-${Math.random().toString(36).slice(2, 8)}`;
|
|
159
|
+
}
|