@chllming/wave-orchestration 0.6.3 → 0.7.1

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.
Files changed (118) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/README.md +40 -7
  3. package/docs/agents/wave-orchestrator-role.md +50 -0
  4. package/docs/agents/wave-planner-role.md +39 -0
  5. package/docs/context7/bundles.json +9 -0
  6. package/docs/context7/planner-agent/README.md +25 -0
  7. package/docs/context7/planner-agent/manifest.json +83 -0
  8. package/docs/context7/planner-agent/papers/cooperbench-why-coding-agents-cannot-be-your-teammates-yet.md +3283 -0
  9. package/docs/context7/planner-agent/papers/dova-deliberation-first-multi-agent-orchestration-for-autonomous-research-automation.md +1699 -0
  10. package/docs/context7/planner-agent/papers/dpbench-large-language-models-struggle-with-simultaneous-coordination.md +2251 -0
  11. package/docs/context7/planner-agent/papers/incremental-planning-to-control-a-blackboard-based-problem-solver.md +1729 -0
  12. package/docs/context7/planner-agent/papers/silo-bench-a-scalable-environment-for-evaluating-distributed-coordination-in-multi-agent-llm-systems.md +3747 -0
  13. package/docs/context7/planner-agent/papers/todoevolve-learning-to-architect-agent-planning-systems.md +1675 -0
  14. package/docs/context7/planner-agent/papers/verified-multi-agent-orchestration-a-plan-execute-verify-replan-framework-for-complex-query-resolution.md +1173 -0
  15. package/docs/context7/planner-agent/papers/why-do-multi-agent-llm-systems-fail.md +5211 -0
  16. package/docs/context7/planner-agent/topics/planning-and-orchestration.md +24 -0
  17. package/docs/evals/README.md +96 -1
  18. package/docs/evals/arm-templates/README.md +13 -0
  19. package/docs/evals/arm-templates/full-wave.json +15 -0
  20. package/docs/evals/arm-templates/single-agent.json +15 -0
  21. package/docs/evals/benchmark-catalog.json +7 -0
  22. package/docs/evals/cases/README.md +47 -0
  23. package/docs/evals/cases/wave-blackboard-inbox-targeting.json +73 -0
  24. package/docs/evals/cases/wave-contradiction-conflict.json +104 -0
  25. package/docs/evals/cases/wave-expert-routing-preservation.json +69 -0
  26. package/docs/evals/cases/wave-hidden-profile-private-evidence.json +81 -0
  27. package/docs/evals/cases/wave-premature-closure-guard.json +71 -0
  28. package/docs/evals/cases/wave-silo-cross-agent-state.json +77 -0
  29. package/docs/evals/cases/wave-simultaneous-lockstep.json +92 -0
  30. package/docs/evals/cooperbench/real-world-mitigation.md +341 -0
  31. package/docs/evals/external-benchmarks.json +85 -0
  32. package/docs/evals/external-command-config.sample.json +9 -0
  33. package/docs/evals/external-command-config.swe-bench-pro.json +8 -0
  34. package/docs/evals/pilots/README.md +47 -0
  35. package/docs/evals/pilots/swe-bench-pro-public-full-wave-review-10.json +64 -0
  36. package/docs/evals/pilots/swe-bench-pro-public-pilot.json +111 -0
  37. package/docs/evals/wave-benchmark-program.md +302 -0
  38. package/docs/guides/planner.md +67 -11
  39. package/docs/guides/terminal-surfaces.md +12 -0
  40. package/docs/plans/context7-wave-orchestrator.md +20 -0
  41. package/docs/plans/current-state.md +8 -1
  42. package/docs/plans/examples/wave-benchmark-improvement.md +108 -0
  43. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  44. package/docs/plans/examples/wave-example-rollout-fidelity.md +340 -0
  45. package/docs/plans/migration.md +26 -0
  46. package/docs/plans/wave-orchestrator.md +60 -12
  47. package/docs/plans/waves/reviews/wave-1-benchmark-operator.md +118 -0
  48. package/docs/reference/cli-reference.md +547 -0
  49. package/docs/reference/coordination-and-closure.md +436 -0
  50. package/docs/reference/live-proof-waves.md +25 -3
  51. package/docs/reference/npmjs-trusted-publishing.md +3 -3
  52. package/docs/reference/proof-metrics.md +90 -0
  53. package/docs/reference/runtime-config/README.md +63 -2
  54. package/docs/reference/runtime-config/codex.md +2 -1
  55. package/docs/reference/sample-waves.md +29 -18
  56. package/docs/reference/wave-control.md +164 -0
  57. package/docs/reference/wave-planning-lessons.md +131 -0
  58. package/package.json +5 -4
  59. package/releases/manifest.json +40 -0
  60. package/scripts/research/agent-context-archive.mjs +18 -0
  61. package/scripts/research/manifests/agent-context-expanded-2026-03-22.mjs +17 -0
  62. package/scripts/research/sync-planner-context7-bundle.mjs +133 -0
  63. package/scripts/wave-orchestrator/agent-state.mjs +11 -2
  64. package/scripts/wave-orchestrator/artifact-schemas.mjs +232 -0
  65. package/scripts/wave-orchestrator/autonomous.mjs +7 -0
  66. package/scripts/wave-orchestrator/benchmark-cases.mjs +374 -0
  67. package/scripts/wave-orchestrator/benchmark-external.mjs +1384 -0
  68. package/scripts/wave-orchestrator/benchmark.mjs +972 -0
  69. package/scripts/wave-orchestrator/clarification-triage.mjs +78 -12
  70. package/scripts/wave-orchestrator/config.mjs +175 -0
  71. package/scripts/wave-orchestrator/control-cli.mjs +1216 -0
  72. package/scripts/wave-orchestrator/control-plane.mjs +697 -0
  73. package/scripts/wave-orchestrator/coord-cli.mjs +360 -2
  74. package/scripts/wave-orchestrator/coordination-store.mjs +211 -9
  75. package/scripts/wave-orchestrator/coordination.mjs +84 -0
  76. package/scripts/wave-orchestrator/dashboard-renderer.mjs +120 -5
  77. package/scripts/wave-orchestrator/dashboard-state.mjs +22 -0
  78. package/scripts/wave-orchestrator/evals.mjs +23 -0
  79. package/scripts/wave-orchestrator/executors.mjs +3 -2
  80. package/scripts/wave-orchestrator/feedback.mjs +55 -0
  81. package/scripts/wave-orchestrator/install.mjs +151 -2
  82. package/scripts/wave-orchestrator/launcher-closure.mjs +4 -1
  83. package/scripts/wave-orchestrator/launcher-runtime.mjs +33 -30
  84. package/scripts/wave-orchestrator/launcher.mjs +884 -36
  85. package/scripts/wave-orchestrator/planner-context.mjs +75 -0
  86. package/scripts/wave-orchestrator/planner.mjs +2270 -136
  87. package/scripts/wave-orchestrator/proof-cli.mjs +195 -0
  88. package/scripts/wave-orchestrator/proof-registry.mjs +317 -0
  89. package/scripts/wave-orchestrator/replay.mjs +10 -4
  90. package/scripts/wave-orchestrator/retry-cli.mjs +184 -0
  91. package/scripts/wave-orchestrator/retry-control.mjs +225 -0
  92. package/scripts/wave-orchestrator/shared.mjs +26 -0
  93. package/scripts/wave-orchestrator/swe-bench-pro-task.mjs +1004 -0
  94. package/scripts/wave-orchestrator/terminals.mjs +1 -1
  95. package/scripts/wave-orchestrator/traces.mjs +157 -2
  96. package/scripts/wave-orchestrator/wave-control-client.mjs +532 -0
  97. package/scripts/wave-orchestrator/wave-control-schema.mjs +309 -0
  98. package/scripts/wave-orchestrator/wave-files.mjs +144 -23
  99. package/scripts/wave.mjs +27 -0
  100. package/skills/repo-coding-rules/SKILL.md +1 -0
  101. package/skills/role-cont-eval/SKILL.md +1 -0
  102. package/skills/role-cont-qa/SKILL.md +13 -6
  103. package/skills/role-deploy/SKILL.md +1 -0
  104. package/skills/role-documentation/SKILL.md +4 -0
  105. package/skills/role-implementation/SKILL.md +4 -0
  106. package/skills/role-infra/SKILL.md +2 -1
  107. package/skills/role-integration/SKILL.md +15 -8
  108. package/skills/role-planner/SKILL.md +39 -0
  109. package/skills/role-planner/skill.json +21 -0
  110. package/skills/role-research/SKILL.md +1 -0
  111. package/skills/role-security/SKILL.md +2 -2
  112. package/skills/runtime-claude/SKILL.md +2 -1
  113. package/skills/runtime-codex/SKILL.md +1 -0
  114. package/skills/runtime-local/SKILL.md +2 -0
  115. package/skills/runtime-opencode/SKILL.md +1 -0
  116. package/skills/wave-core/SKILL.md +25 -6
  117. package/skills/wave-core/references/marker-syntax.md +16 -8
  118. package/wave.config.json +45 -0
@@ -0,0 +1,697 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import {
5
+ normalizeProofRegistry,
6
+ readProofRegistry,
7
+ normalizeRetryOverride,
8
+ writeProofRegistry,
9
+ writeRetryOverride,
10
+ } from "./artifact-schemas.mjs";
11
+ import {
12
+ CLARIFICATION_CLOSURE_PREFIX,
13
+ buildCoordinationResponseMetrics,
14
+ } from "./coordination-store.mjs";
15
+ import {
16
+ DEFAULT_COORDINATION_ACK_TIMEOUT_MS,
17
+ DEFAULT_COORDINATION_RESOLUTION_STALE_MS,
18
+ ensureDirectory,
19
+ parseNonNegativeInt,
20
+ toIsoTimestamp,
21
+ } from "./shared.mjs";
22
+ import {
23
+ WAVE_CONTROL_ENTITY_TYPES,
24
+ normalizeWaveControlRunKind,
25
+ } from "./wave-control-schema.mjs";
26
+ import { safeQueueWaveControlEvent } from "./wave-control-client.mjs";
27
+
28
+ const TASKABLE_COORDINATION_KINDS = new Set([
29
+ "request",
30
+ "blocker",
31
+ "handoff",
32
+ "evidence",
33
+ "claim",
34
+ "decision",
35
+ "clarification-request",
36
+ "human-feedback",
37
+ "human-escalation",
38
+ ]);
39
+
40
+ const PROOF_BUNDLE_STATES = new Set(["active", "superseded", "revoked"]);
41
+ const RERUN_REQUEST_STATES = new Set(["active", "applied", "cleared"]);
42
+ const ATTEMPT_STATES = new Set(["running", "completed", "failed", "cancelled"]);
43
+
44
+ function cloneJson(value) {
45
+ return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
46
+ }
47
+
48
+ function normalizeText(value, fallback = "") {
49
+ const normalized = String(value ?? "").trim();
50
+ return normalized || fallback;
51
+ }
52
+
53
+ function normalizeStringArray(values) {
54
+ if (!Array.isArray(values)) {
55
+ return [];
56
+ }
57
+ return Array.from(
58
+ new Set(
59
+ values
60
+ .map((value) => normalizeText(value))
61
+ .filter(Boolean),
62
+ ),
63
+ );
64
+ }
65
+
66
+ function normalizePlainObject(value) {
67
+ return value && typeof value === "object" && !Array.isArray(value) ? cloneJson(value) : null;
68
+ }
69
+
70
+ function stableId(prefix) {
71
+ return `${prefix}-${crypto.randomBytes(4).toString("hex")}`;
72
+ }
73
+
74
+ function compareRecordedEvents(left, right) {
75
+ const leftTs = Date.parse(left?.recordedAt || "");
76
+ const rightTs = Date.parse(right?.recordedAt || "");
77
+ if (Number.isFinite(leftTs) && Number.isFinite(rightTs) && leftTs !== rightTs) {
78
+ return leftTs - rightTs;
79
+ }
80
+ return String(left?.id || "").localeCompare(String(right?.id || ""));
81
+ }
82
+
83
+ function assertEnum(value, allowed, label) {
84
+ if (!allowed.has(value)) {
85
+ throw new Error(`${label} must be one of ${Array.from(allowed).join(", ")} (got: ${value || "empty"})`);
86
+ }
87
+ }
88
+
89
+ function controlProjectionPaths(lanePaths, waveNumber) {
90
+ const normalizedWave = parseNonNegativeInt(waveNumber, "wave");
91
+ return {
92
+ retryOverridePath: path.join(
93
+ lanePaths.controlDir,
94
+ `retry-override-wave-${normalizedWave}.json`,
95
+ ),
96
+ proofRegistryPath: path.join(lanePaths.proofDir, `wave-${normalizedWave}.json`),
97
+ };
98
+ }
99
+
100
+ export function waveControlPlaneLogPath(lanePaths, waveNumber) {
101
+ return path.join(
102
+ lanePaths.controlPlaneDir,
103
+ `wave-${parseNonNegativeInt(waveNumber, "wave")}.jsonl`,
104
+ );
105
+ }
106
+
107
+ export function normalizeControlPlaneEvent(rawEvent, defaults = {}) {
108
+ if (!rawEvent || typeof rawEvent !== "object" || Array.isArray(rawEvent)) {
109
+ throw new Error("Control-plane event must be an object");
110
+ }
111
+ const entityType = normalizeText(rawEvent.entityType || defaults.entityType).toLowerCase();
112
+ assertEnum(entityType, WAVE_CONTROL_ENTITY_TYPES, "Control-plane entity type");
113
+ const lane = normalizeText(rawEvent.lane || defaults.lane);
114
+ const wave = Number.parseInt(String(rawEvent.wave ?? defaults.wave ?? ""), 10);
115
+ const entityId = normalizeText(rawEvent.entityId || defaults.entityId);
116
+ const action = normalizeText(rawEvent.action || defaults.action).toLowerCase();
117
+ const runId = normalizeText(rawEvent.runId || defaults.runId, null);
118
+ if (!lane) {
119
+ throw new Error("Control-plane lane is required");
120
+ }
121
+ if (!Number.isFinite(wave) || wave < 0) {
122
+ throw new Error(`Control-plane wave must be a non-negative integer (got: ${rawEvent.wave})`);
123
+ }
124
+ if (!entityId) {
125
+ throw new Error("Control-plane entityId is required");
126
+ }
127
+ if (!action) {
128
+ throw new Error("Control-plane action is required");
129
+ }
130
+ return {
131
+ recordVersion: 1,
132
+ id: normalizeText(rawEvent.id || defaults.id) || stableId(`ctrl-${entityType}`),
133
+ lane,
134
+ wave,
135
+ runKind: normalizeWaveControlRunKind(rawEvent.runKind || defaults.runKind || "roadmap"),
136
+ runId,
137
+ entityType,
138
+ entityId,
139
+ action,
140
+ source: normalizeText(rawEvent.source || defaults.source, "launcher"),
141
+ actor: normalizeText(rawEvent.actor || defaults.actor, ""),
142
+ recordedAt: normalizeText(rawEvent.recordedAt || defaults.recordedAt, toIsoTimestamp()),
143
+ attempt:
144
+ rawEvent.attempt === null || rawEvent.attempt === undefined || rawEvent.attempt === ""
145
+ ? defaults.attempt ?? null
146
+ : parseNonNegativeInt(rawEvent.attempt, "control-plane attempt"),
147
+ data: normalizePlainObject(rawEvent.data || defaults.data) || {},
148
+ };
149
+ }
150
+
151
+ export function appendControlPlaneEvent(filePath, rawEvent, defaults = {}) {
152
+ const event = normalizeControlPlaneEvent(rawEvent, defaults);
153
+ ensureDirectory(path.dirname(filePath));
154
+ fs.appendFileSync(filePath, `${JSON.stringify(event)}\n`, "utf8");
155
+ return event;
156
+ }
157
+
158
+ export function readControlPlaneEvents(filePath) {
159
+ if (!fs.existsSync(filePath)) {
160
+ return [];
161
+ }
162
+ return fs
163
+ .readFileSync(filePath, "utf8")
164
+ .split(/\r?\n/)
165
+ .map((line) => line.trim())
166
+ .filter(Boolean)
167
+ .map((line) => normalizeControlPlaneEvent(JSON.parse(line)))
168
+ .sort(compareRecordedEvents);
169
+ }
170
+
171
+ function normalizeProofArtifactEntry(entry) {
172
+ const source = normalizePlainObject(entry) || {};
173
+ return {
174
+ path: normalizeText(source.path),
175
+ kind: normalizeText(source.kind, null),
176
+ requiredFor: normalizeStringArray(source.requiredFor),
177
+ exists: source.exists === true,
178
+ sha256: normalizeText(source.sha256, null),
179
+ };
180
+ }
181
+
182
+ function normalizeProofComponentEntry(entry) {
183
+ const source = normalizePlainObject(entry) || {};
184
+ return {
185
+ componentId: normalizeText(source.componentId),
186
+ level: normalizeText(source.level, null),
187
+ state: normalizeText(source.state, null),
188
+ detail: normalizeText(source.detail, null),
189
+ };
190
+ }
191
+
192
+ function normalizeProofBundleSnapshot(rawBundle, defaults = {}) {
193
+ const source = normalizePlainObject(rawBundle) || {};
194
+ const state = normalizeText(source.state, normalizeText(defaults.state, "active")).toLowerCase();
195
+ assertEnum(state, PROOF_BUNDLE_STATES, "Proof bundle state");
196
+ const recordedAt = normalizeText(source.recordedAt, normalizeText(defaults.recordedAt, toIsoTimestamp()));
197
+ return {
198
+ proofBundleId: normalizeText(source.proofBundleId, normalizeText(defaults.proofBundleId)),
199
+ agentId: normalizeText(source.agentId, normalizeText(defaults.agentId)),
200
+ state,
201
+ authoritative: source.authoritative === true,
202
+ recordedAt,
203
+ updatedAt: normalizeText(source.updatedAt, normalizeText(defaults.updatedAt, recordedAt)),
204
+ recordedBy: normalizeText(source.recordedBy, normalizeText(defaults.recordedBy, null)),
205
+ detail: normalizeText(source.detail, normalizeText(defaults.detail, null)),
206
+ summary: normalizeText(source.summary, normalizeText(defaults.summary, null)),
207
+ satisfyOwnedComponents: source.satisfyOwnedComponents === true,
208
+ proof: normalizePlainObject(source.proof),
209
+ docDelta: normalizePlainObject(source.docDelta),
210
+ components: (Array.isArray(source.components) ? source.components : [])
211
+ .map((entry) => normalizeProofComponentEntry(entry))
212
+ .filter((entry) => entry.componentId),
213
+ artifacts: (Array.isArray(source.artifacts) ? source.artifacts : [])
214
+ .map((entry) => normalizeProofArtifactEntry(entry))
215
+ .filter((entry) => entry.path),
216
+ scope: normalizeText(source.scope, normalizeText(defaults.scope, "wave")),
217
+ attestation: normalizePlainObject(source.attestation),
218
+ satisfies: normalizeStringArray(source.satisfies),
219
+ supersedes: normalizeText(source.supersedes, normalizeText(defaults.supersedes, null)),
220
+ supersededBy: normalizeText(source.supersededBy, normalizeText(defaults.supersededBy, null)),
221
+ };
222
+ }
223
+
224
+ function normalizeRerunRequestSnapshot(rawRequest, defaults = {}) {
225
+ const source = normalizePlainObject(rawRequest) || {};
226
+ const createdAt = normalizeText(source.createdAt, normalizeText(defaults.createdAt, toIsoTimestamp()));
227
+ const state = normalizeText(source.state, normalizeText(defaults.state, "active")).toLowerCase();
228
+ assertEnum(state, RERUN_REQUEST_STATES, "Rerun request state");
229
+ return {
230
+ requestId: normalizeText(source.requestId, normalizeText(defaults.requestId)),
231
+ state,
232
+ selectedAgentIds: normalizeStringArray(source.selectedAgentIds),
233
+ resumeCursor: normalizeText(
234
+ source.resumeCursor,
235
+ normalizeText(source.resumePhase, normalizeText(defaults.resumeCursor, null)),
236
+ ),
237
+ reuseAttemptIds: normalizeStringArray(source.reuseAttemptIds),
238
+ reuseProofBundleIds: normalizeStringArray(source.reuseProofBundleIds),
239
+ reuseDerivedSummaries: source.reuseDerivedSummaries !== false,
240
+ invalidateComponentIds: normalizeStringArray(source.invalidateComponentIds),
241
+ clearReusableAgentIds: normalizeStringArray(source.clearReusableAgentIds),
242
+ preserveReusableAgentIds: normalizeStringArray(source.preserveReusableAgentIds),
243
+ requestedBy: normalizeText(source.requestedBy, normalizeText(defaults.requestedBy, "human-operator")),
244
+ reason: normalizeText(source.reason, normalizeText(defaults.reason, null)),
245
+ applyOnce: source.applyOnce !== false,
246
+ createdAt,
247
+ updatedAt: normalizeText(source.updatedAt, normalizeText(defaults.updatedAt, createdAt)),
248
+ };
249
+ }
250
+
251
+ function normalizeAttemptSnapshot(rawAttempt, defaults = {}) {
252
+ const source = normalizePlainObject(rawAttempt) || {};
253
+ const createdAt = normalizeText(source.createdAt, normalizeText(defaults.createdAt, toIsoTimestamp()));
254
+ const state = normalizeText(source.state, normalizeText(defaults.state, "running")).toLowerCase();
255
+ assertEnum(state, ATTEMPT_STATES, "Attempt state");
256
+ return {
257
+ attemptId: normalizeText(source.attemptId, normalizeText(defaults.attemptId)),
258
+ attemptNumber: Number.parseInt(String(source.attemptNumber ?? defaults.attemptNumber ?? ""), 10) || 0,
259
+ state,
260
+ selectedAgentIds: normalizeStringArray(source.selectedAgentIds),
261
+ detail: normalizeText(source.detail, normalizeText(defaults.detail, null)),
262
+ createdAt,
263
+ updatedAt: normalizeText(source.updatedAt, normalizeText(defaults.updatedAt, createdAt)),
264
+ };
265
+ }
266
+
267
+ function normalizeHumanInputSnapshot(rawInput, defaults = {}) {
268
+ const source = normalizePlainObject(rawInput) || {};
269
+ const createdAt = normalizeText(source.createdAt, normalizeText(defaults.createdAt, toIsoTimestamp()));
270
+ return {
271
+ humanInputId: normalizeText(source.humanInputId, normalizeText(defaults.humanInputId)),
272
+ requestId: normalizeText(source.requestId, normalizeText(defaults.requestId, null)),
273
+ state: normalizeText(source.state, normalizeText(defaults.state, "pending")).toLowerCase(),
274
+ operator: normalizeText(source.operator, normalizeText(defaults.operator, null)),
275
+ response: normalizeText(source.response, normalizeText(defaults.response, null)),
276
+ createdAt,
277
+ updatedAt: normalizeText(source.updatedAt, normalizeText(defaults.updatedAt, createdAt)),
278
+ };
279
+ }
280
+
281
+ function materializeByEntity(events, entityType, normalizer) {
282
+ const snapshotsById = new Map();
283
+ const ordered = events.filter((event) => event.entityType === entityType);
284
+ for (const event of ordered) {
285
+ const existing = snapshotsById.get(event.entityId) || {};
286
+ const snapshot = normalizer({
287
+ ...existing,
288
+ ...(normalizePlainObject(event.data) || {}),
289
+ }, existing);
290
+ snapshotsById.set(event.entityId, snapshot);
291
+ }
292
+ return {
293
+ byId: snapshotsById,
294
+ all: Array.from(snapshotsById.values()).sort((left, right) =>
295
+ String(left.updatedAt || left.recordedAt || "").localeCompare(String(right.updatedAt || right.recordedAt || "")),
296
+ ),
297
+ };
298
+ }
299
+
300
+ function bundleToRegistryEntry(bundle) {
301
+ return {
302
+ id: bundle.proofBundleId,
303
+ agentId: bundle.agentId,
304
+ state: bundle.state,
305
+ authoritative: bundle.authoritative,
306
+ recordedAt: bundle.recordedAt,
307
+ recordedBy: bundle.recordedBy,
308
+ detail: bundle.detail,
309
+ summary: bundle.summary,
310
+ satisfyOwnedComponents: bundle.satisfyOwnedComponents,
311
+ proof: cloneJson(bundle.proof),
312
+ docDelta: cloneJson(bundle.docDelta),
313
+ components: cloneJson(bundle.components),
314
+ artifacts: cloneJson(bundle.artifacts),
315
+ scope: bundle.scope,
316
+ attestation: cloneJson(bundle.attestation),
317
+ satisfies: cloneJson(bundle.satisfies),
318
+ supersedes: bundle.supersedes,
319
+ supersededBy: bundle.supersededBy,
320
+ };
321
+ }
322
+
323
+ export function materializeControlPlaneState(events) {
324
+ const orderedEvents = [...(Array.isArray(events) ? events : [])].sort(compareRecordedEvents);
325
+ const proofBundles = materializeByEntity(
326
+ orderedEvents,
327
+ "proof_bundle",
328
+ normalizeProofBundleSnapshot,
329
+ );
330
+ const rerunRequests = materializeByEntity(
331
+ orderedEvents,
332
+ "rerun_request",
333
+ normalizeRerunRequestSnapshot,
334
+ );
335
+ const attempts = materializeByEntity(
336
+ orderedEvents,
337
+ "attempt",
338
+ normalizeAttemptSnapshot,
339
+ );
340
+ const humanInputs = materializeByEntity(
341
+ orderedEvents,
342
+ "human_input",
343
+ normalizeHumanInputSnapshot,
344
+ );
345
+ const activeProofBundles = proofBundles.all.filter((bundle) => bundle.state === "active");
346
+ const activeRerunRequest =
347
+ [...rerunRequests.all]
348
+ .sort((left, right) =>
349
+ String(left.updatedAt || "").localeCompare(String(right.updatedAt || "")),
350
+ )
351
+ .filter((request) => request.state === "active")
352
+ .at(-1) || null;
353
+ const activeAttempt =
354
+ [...attempts.all]
355
+ .sort((left, right) =>
356
+ String(left.updatedAt || "").localeCompare(String(right.updatedAt || "")),
357
+ )
358
+ .filter((attempt) => attempt.state === "running")
359
+ .at(-1) || null;
360
+ return {
361
+ events: orderedEvents,
362
+ proofBundlesById: proofBundles.byId,
363
+ proofBundles: proofBundles.all,
364
+ activeProofBundles,
365
+ rerunRequestsById: rerunRequests.byId,
366
+ rerunRequests: rerunRequests.all,
367
+ activeRerunRequest,
368
+ attemptsById: attempts.byId,
369
+ attempts: attempts.all,
370
+ activeAttempt,
371
+ humanInputsById: humanInputs.byId,
372
+ humanInputs: humanInputs.all,
373
+ };
374
+ }
375
+
376
+ export function readWaveControlPlaneState(lanePaths, waveNumber) {
377
+ return materializeControlPlaneState(readControlPlaneEvents(waveControlPlaneLogPath(lanePaths, waveNumber)));
378
+ }
379
+
380
+ export function appendWaveControlEvent(lanePaths, waveNumber, rawEvent, defaults = {}) {
381
+ const filePath = waveControlPlaneLogPath(lanePaths, waveNumber);
382
+ const event = appendControlPlaneEvent(filePath, rawEvent, {
383
+ lane: lanePaths?.lane || defaults.lane || null,
384
+ wave: waveNumber,
385
+ runKind: lanePaths?.runKind || defaults.runKind || "roadmap",
386
+ runId: lanePaths?.runId || defaults.runId || null,
387
+ ...defaults,
388
+ });
389
+ if (lanePaths?.waveControl?.captureControlPlaneEvents !== false) {
390
+ safeQueueWaveControlEvent(lanePaths, {
391
+ category: "control-plane",
392
+ entityType: event.entityType,
393
+ entityId: event.entityId,
394
+ action: event.action,
395
+ source: event.source,
396
+ actor: event.actor,
397
+ recordedAt: event.recordedAt,
398
+ identity: {
399
+ lane: event.lane,
400
+ wave: event.wave,
401
+ attempt: event.attempt,
402
+ runKind: event.runKind,
403
+ runId: event.runId,
404
+ },
405
+ tags: ["control-plane", event.entityType],
406
+ data: event.data,
407
+ });
408
+ }
409
+ return event;
410
+ }
411
+
412
+ function materializeProofRegistryProjection(lanePaths, waveNumber, controlState) {
413
+ return normalizeProofRegistry(
414
+ {
415
+ lane: lanePaths?.lane || null,
416
+ wave: waveNumber,
417
+ updatedAt:
418
+ controlState?.activeProofBundles?.at(-1)?.updatedAt ||
419
+ controlState?.proofBundles?.at(-1)?.updatedAt ||
420
+ toIsoTimestamp(),
421
+ entries: (controlState?.proofBundles || []).map((bundle) => bundleToRegistryEntry(bundle)),
422
+ },
423
+ {
424
+ lane: lanePaths?.lane || null,
425
+ wave: waveNumber,
426
+ },
427
+ );
428
+ }
429
+
430
+ function materializeRetryOverrideProjection(lanePaths, waveNumber, controlState) {
431
+ const activeRequest = controlState?.activeRerunRequest;
432
+ if (!activeRequest) {
433
+ return null;
434
+ }
435
+ return normalizeRetryOverride(
436
+ {
437
+ lane: lanePaths?.lane || null,
438
+ wave: waveNumber,
439
+ selectedAgentIds: activeRequest.selectedAgentIds,
440
+ reuseAttemptIds: activeRequest.reuseAttemptIds,
441
+ reuseProofBundleIds: activeRequest.reuseProofBundleIds,
442
+ reuseDerivedSummaries: activeRequest.reuseDerivedSummaries,
443
+ invalidateComponentIds: activeRequest.invalidateComponentIds,
444
+ clearReusableAgentIds: activeRequest.clearReusableAgentIds,
445
+ preserveReusableAgentIds: activeRequest.preserveReusableAgentIds,
446
+ resumePhase: activeRequest.resumeCursor,
447
+ requestedBy: activeRequest.requestedBy,
448
+ reason: activeRequest.reason,
449
+ applyOnce: activeRequest.applyOnce,
450
+ createdAt: activeRequest.createdAt,
451
+ },
452
+ {
453
+ lane: lanePaths?.lane || null,
454
+ wave: waveNumber,
455
+ },
456
+ );
457
+ }
458
+
459
+ export function syncWaveControlPlaneProjections(lanePaths, waveNumber, controlState = null) {
460
+ const state = controlState || readWaveControlPlaneState(lanePaths, waveNumber);
461
+ const filePaths = controlProjectionPaths(lanePaths, waveNumber);
462
+ ensureDirectory(lanePaths.controlDir);
463
+ ensureDirectory(lanePaths.proofDir);
464
+ const hasProofEvents = Array.isArray(state?.proofBundles) && state.proofBundles.length > 0;
465
+ const proofRegistry = hasProofEvents
466
+ ? materializeProofRegistryProjection(lanePaths, waveNumber, state)
467
+ : null;
468
+ if (proofRegistry) {
469
+ writeProofRegistry(filePaths.proofRegistryPath, proofRegistry, {
470
+ lane: lanePaths?.lane || null,
471
+ wave: waveNumber,
472
+ });
473
+ }
474
+ const retryOverride = materializeRetryOverrideProjection(lanePaths, waveNumber, state);
475
+ if (retryOverride) {
476
+ writeRetryOverride(filePaths.retryOverridePath, retryOverride, {
477
+ lane: lanePaths?.lane || null,
478
+ wave: waveNumber,
479
+ });
480
+ } else if (Array.isArray(state?.rerunRequests) && state.rerunRequests.length > 0) {
481
+ fs.rmSync(filePaths.retryOverridePath, { force: true });
482
+ }
483
+ return {
484
+ proofRegistry:
485
+ proofRegistry ||
486
+ (fs.existsSync(filePaths.proofRegistryPath)
487
+ ? readProofRegistry(filePaths.proofRegistryPath, {
488
+ lane: lanePaths?.lane || null,
489
+ wave: waveNumber,
490
+ })
491
+ : null),
492
+ retryOverride,
493
+ };
494
+ }
495
+
496
+ function firstTargetAgentId(record) {
497
+ for (const target of Array.isArray(record?.targets) ? record.targets : []) {
498
+ const normalized = normalizeText(target);
499
+ if (normalized.startsWith("agent:")) {
500
+ return normalized.slice("agent:".length);
501
+ }
502
+ }
503
+ return null;
504
+ }
505
+
506
+ function isLauncherSeedTaskRecord(record) {
507
+ return (
508
+ record?.source === "launcher" &&
509
+ /^wave-\d+-agent-[^-]+-request$/.test(String(record.id || "")) &&
510
+ !String(record.closureCondition || "").trim() &&
511
+ (!Array.isArray(record.dependsOn) || record.dependsOn.length === 0)
512
+ );
513
+ }
514
+
515
+ function taskTypeForCoordinationKind(kind) {
516
+ if (kind === "clarification-request") {
517
+ return "clarification";
518
+ }
519
+ if (kind === "human-feedback") {
520
+ return "human-input";
521
+ }
522
+ if (kind === "human-escalation") {
523
+ return "escalation";
524
+ }
525
+ return kind;
526
+ }
527
+
528
+ function taskStateForCoordinationRecord(record, feedbackRequest = null) {
529
+ if (feedbackRequest?.status === "answered") {
530
+ return "resolved";
531
+ }
532
+ const status = normalizeText(record?.status).toLowerCase();
533
+ if (status === "open") {
534
+ return record?.kind === "human-feedback" ? "input-required" : "open";
535
+ }
536
+ if (["acknowledged", "in_progress"].includes(status)) {
537
+ return "working";
538
+ }
539
+ if (["resolved", "closed"].includes(status)) {
540
+ return "resolved";
541
+ }
542
+ if (status === "cancelled") {
543
+ return "cancelled";
544
+ }
545
+ if (status === "superseded") {
546
+ return "superseded";
547
+ }
548
+ return "dismissed";
549
+ }
550
+
551
+ function deadlineAt(baseTimestamp, offsetMs) {
552
+ const baseMs = Date.parse(baseTimestamp || "");
553
+ if (!Number.isFinite(baseMs)) {
554
+ return null;
555
+ }
556
+ return new Date(baseMs + offsetMs).toISOString();
557
+ }
558
+
559
+ function clarificationIdFromCondition(value) {
560
+ const normalized = normalizeText(value);
561
+ if (!normalized.startsWith(CLARIFICATION_CLOSURE_PREFIX)) {
562
+ return null;
563
+ }
564
+ return normalized.slice(CLARIFICATION_CLOSURE_PREFIX.length) || null;
565
+ }
566
+
567
+ export function buildTaskSnapshots({
568
+ coordinationState,
569
+ feedbackRequests = [],
570
+ ackTimeoutMs = DEFAULT_COORDINATION_ACK_TIMEOUT_MS,
571
+ resolutionStaleMs = DEFAULT_COORDINATION_RESOLUTION_STALE_MS,
572
+ }) {
573
+ const feedbackById = new Map((feedbackRequests || []).map((request) => [request.id, request]));
574
+ const responseMetrics = buildCoordinationResponseMetrics(coordinationState, {
575
+ ackTimeoutMs,
576
+ resolutionStaleMs,
577
+ });
578
+ const tasks = [];
579
+ for (const record of coordinationState?.latestRecords || []) {
580
+ if (!TASKABLE_COORDINATION_KINDS.has(record.kind)) {
581
+ continue;
582
+ }
583
+ if (isLauncherSeedTaskRecord(record)) {
584
+ continue;
585
+ }
586
+ const metrics = responseMetrics.recordMetricsById.get(record.id) || {};
587
+ const feedbackRequest = feedbackById.get(record.id) || null;
588
+ const taskState = taskStateForCoordinationRecord(record, feedbackRequest);
589
+ tasks.push({
590
+ taskId: record.id,
591
+ sourceRecordId: record.id,
592
+ taskType: taskTypeForCoordinationKind(record.kind),
593
+ state: taskState,
594
+ title: record.summary || record.detail || "Untitled task",
595
+ detail: record.detail || record.summary || "",
596
+ priority: record.priority || "normal",
597
+ ownerAgentId: record.agentId || null,
598
+ assigneeAgentId: firstTargetAgentId(record),
599
+ leaseOwnerAgentId:
600
+ ["acknowledged", "in_progress"].includes(record.status) ? firstTargetAgentId(record) : null,
601
+ needsHuman:
602
+ record.kind === "human-feedback" ||
603
+ feedbackRequest?.status === "pending" ||
604
+ taskState === "input-required",
605
+ dependsOn: Array.isArray(record.dependsOn) ? record.dependsOn : [],
606
+ evidenceRefs: Array.isArray(record.artifactRefs) ? record.artifactRefs : [],
607
+ blockerReason: record.kind === "blocker" ? record.detail || record.summary || "" : null,
608
+ retryReason: record.kind === "request" ? record.detail || record.summary || "" : null,
609
+ supersedes: record.status === "superseded" ? record.id : null,
610
+ clarificationId:
611
+ record.kind === "clarification-request"
612
+ ? record.id
613
+ : clarificationIdFromCondition(record.closureCondition),
614
+ createdAt: record.createdAt,
615
+ updatedAt: feedbackRequest?.updatedAt || record.updatedAt || record.createdAt,
616
+ ackDeadlineAt:
617
+ metrics.ackTracked && taskState === "open"
618
+ ? deadlineAt(record.createdAt || record.updatedAt, ackTimeoutMs)
619
+ : null,
620
+ resolveDeadlineAt:
621
+ (record.kind === "clarification-request" || metrics.clarificationLinked) &&
622
+ ["open", "working", "input-required"].includes(taskState)
623
+ ? deadlineAt(record.createdAt || record.updatedAt, resolutionStaleMs)
624
+ : null,
625
+ lastHeartbeatAt:
626
+ taskState === "working"
627
+ ? feedbackRequest?.updatedAt || record.updatedAt || record.createdAt
628
+ : null,
629
+ overdueAck: metrics.overdueAck === true,
630
+ stale: metrics.staleClarification === true,
631
+ feedbackRequestId: feedbackRequest?.id || null,
632
+ humanResponse: feedbackRequest?.responseText || null,
633
+ humanOperator: feedbackRequest?.responseOperator || null,
634
+ });
635
+ }
636
+ for (const request of feedbackRequests || []) {
637
+ if (tasks.some((task) => task.taskId === request.id)) {
638
+ continue;
639
+ }
640
+ tasks.push({
641
+ taskId: request.id,
642
+ sourceRecordId: null,
643
+ taskType: "human-input",
644
+ state: request.status === "answered" ? "resolved" : "input-required",
645
+ title: request.question || "Human feedback requested",
646
+ detail: request.context || "",
647
+ priority: "high",
648
+ ownerAgentId: request.agentId || null,
649
+ assigneeAgentId: request.agentId || null,
650
+ leaseOwnerAgentId: null,
651
+ needsHuman: request.status !== "answered",
652
+ dependsOn: [],
653
+ evidenceRefs: [],
654
+ blockerReason: request.context || "",
655
+ retryReason: null,
656
+ supersedes: null,
657
+ clarificationId: null,
658
+ createdAt: request.createdAt,
659
+ updatedAt: request.updatedAt || request.createdAt,
660
+ ackDeadlineAt: null,
661
+ resolveDeadlineAt:
662
+ request.status === "answered" ? null : deadlineAt(request.createdAt, resolutionStaleMs),
663
+ lastHeartbeatAt: null,
664
+ overdueAck: false,
665
+ stale: false,
666
+ feedbackRequestId: request.id,
667
+ humanResponse: request.responseText || null,
668
+ humanOperator: request.responseOperator || null,
669
+ });
670
+ }
671
+ return tasks.sort((left, right) =>
672
+ String(left.createdAt || left.updatedAt || "").localeCompare(String(right.createdAt || right.updatedAt || "")),
673
+ );
674
+ }
675
+
676
+ export function nextTaskDeadline(tasks) {
677
+ const candidates = [];
678
+ for (const task of tasks || []) {
679
+ for (const [kind, value] of [
680
+ ["ack", task.ackDeadlineAt],
681
+ ["resolve", task.resolveDeadlineAt],
682
+ ]) {
683
+ const ts = Date.parse(value || "");
684
+ if (!Number.isFinite(ts)) {
685
+ continue;
686
+ }
687
+ candidates.push({
688
+ kind,
689
+ taskId: task.taskId,
690
+ at: value,
691
+ title: task.title,
692
+ assigneeAgentId: task.assigneeAgentId || null,
693
+ });
694
+ }
695
+ }
696
+ return candidates.sort((left, right) => Date.parse(left.at) - Date.parse(right.at))[0] || null;
697
+ }