@chllming/wave-orchestration 0.7.0 → 0.7.2
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/CHANGELOG.md +40 -0
- package/README.md +9 -8
- package/docs/guides/planner.md +19 -0
- package/docs/guides/terminal-surfaces.md +12 -0
- package/docs/plans/component-cutover-matrix.json +50 -3
- package/docs/plans/current-state.md +1 -1
- package/docs/plans/end-state-architecture.md +927 -0
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +26 -0
- package/docs/plans/wave-orchestrator.md +4 -7
- package/docs/plans/waves/wave-1.md +376 -0
- package/docs/plans/waves/wave-2.md +292 -0
- package/docs/plans/waves/wave-3.md +342 -0
- package/docs/plans/waves/wave-4.md +391 -0
- package/docs/plans/waves/wave-5.md +382 -0
- package/docs/plans/waves/wave-6.md +321 -0
- package/docs/reference/cli-reference.md +547 -0
- package/docs/reference/coordination-and-closure.md +1 -1
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/runtime-config/README.md +2 -2
- package/docs/reference/runtime-config/codex.md +2 -1
- package/docs/reference/sample-waves.md +4 -4
- package/package.json +1 -1
- package/releases/manifest.json +43 -2
- package/scripts/wave-orchestrator/agent-state.mjs +458 -35
- package/scripts/wave-orchestrator/artifact-schemas.mjs +81 -0
- package/scripts/wave-orchestrator/control-cli.mjs +119 -20
- package/scripts/wave-orchestrator/coordination.mjs +11 -10
- package/scripts/wave-orchestrator/dashboard-renderer.mjs +82 -2
- package/scripts/wave-orchestrator/human-input-workflow.mjs +289 -0
- package/scripts/wave-orchestrator/install.mjs +120 -3
- package/scripts/wave-orchestrator/launcher-derived-state.mjs +915 -0
- package/scripts/wave-orchestrator/launcher-gates.mjs +1061 -0
- package/scripts/wave-orchestrator/launcher-retry.mjs +873 -0
- package/scripts/wave-orchestrator/launcher-runtime.mjs +9 -9
- package/scripts/wave-orchestrator/launcher-supervisor.mjs +704 -0
- package/scripts/wave-orchestrator/launcher.mjs +317 -2999
- package/scripts/wave-orchestrator/task-entity.mjs +557 -0
- package/scripts/wave-orchestrator/terminals.mjs +1 -1
- package/scripts/wave-orchestrator/wave-files.mjs +138 -20
- package/scripts/wave-orchestrator/wave-state-reducer.mjs +566 -0
- package/wave.config.json +1 -1
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { toIsoTimestamp } from "./shared.mjs";
|
|
3
|
+
import {
|
|
4
|
+
validateImplementationSummary,
|
|
5
|
+
validateContQaSummary,
|
|
6
|
+
validateContEvalSummary,
|
|
7
|
+
validateDocumentationClosureSummary,
|
|
8
|
+
validateSecuritySummary,
|
|
9
|
+
validateIntegrationSummary,
|
|
10
|
+
} from "./agent-state.mjs";
|
|
11
|
+
import {
|
|
12
|
+
isContEvalImplementationOwningAgent,
|
|
13
|
+
isContEvalReportOnlyAgent,
|
|
14
|
+
isSecurityReviewAgent,
|
|
15
|
+
} from "./role-helpers.mjs";
|
|
16
|
+
import {
|
|
17
|
+
isOpenCoordinationStatus,
|
|
18
|
+
openClarificationLinkedRequests,
|
|
19
|
+
} from "./coordination-store.mjs";
|
|
20
|
+
|
|
21
|
+
export const TASK_TYPES = new Set([
|
|
22
|
+
"implementation",
|
|
23
|
+
"integration",
|
|
24
|
+
"documentation",
|
|
25
|
+
"cont-qa",
|
|
26
|
+
"cont-eval",
|
|
27
|
+
"security",
|
|
28
|
+
"component",
|
|
29
|
+
"helper",
|
|
30
|
+
"dependency",
|
|
31
|
+
"clarification",
|
|
32
|
+
"human-input",
|
|
33
|
+
"escalation",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
export const CLOSURE_STATES = new Set([
|
|
37
|
+
"open",
|
|
38
|
+
"owned_slice_proven",
|
|
39
|
+
"wave_closure_ready",
|
|
40
|
+
"closed",
|
|
41
|
+
"cancelled",
|
|
42
|
+
"superseded",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
export const LEASE_STATES = new Set([
|
|
46
|
+
"unleased",
|
|
47
|
+
"leased",
|
|
48
|
+
"released",
|
|
49
|
+
"expired",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const VALID_PRIORITIES = new Set(["low", "normal", "high", "urgent"]);
|
|
53
|
+
|
|
54
|
+
const CLOSURE_TRANSITIONS = {
|
|
55
|
+
open: new Set(["owned_slice_proven", "cancelled", "superseded"]),
|
|
56
|
+
owned_slice_proven: new Set(["wave_closure_ready", "cancelled", "superseded"]),
|
|
57
|
+
wave_closure_ready: new Set(["closed", "cancelled", "superseded"]),
|
|
58
|
+
closed: new Set(),
|
|
59
|
+
cancelled: new Set(),
|
|
60
|
+
superseded: new Set(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function stableId(prefix) {
|
|
64
|
+
return `${prefix}-${crypto.randomBytes(4).toString("hex")}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeText(value, fallback = "") {
|
|
68
|
+
const normalized = String(value ?? "").trim();
|
|
69
|
+
return normalized || fallback;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeStringArray(values) {
|
|
73
|
+
if (!Array.isArray(values)) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
return Array.from(
|
|
77
|
+
new Set(
|
|
78
|
+
values
|
|
79
|
+
.map((value) => normalizeText(value))
|
|
80
|
+
.filter(Boolean),
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizePlainObject(value) {
|
|
86
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
87
|
+
? JSON.parse(JSON.stringify(value))
|
|
88
|
+
: null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function normalizeTask(rawTask, defaults = {}) {
|
|
92
|
+
if (!rawTask || typeof rawTask !== "object" || Array.isArray(rawTask)) {
|
|
93
|
+
throw new Error("Task must be an object");
|
|
94
|
+
}
|
|
95
|
+
const taskId = normalizeText(rawTask.taskId) || defaults.taskId || stableId("task");
|
|
96
|
+
const taskType = normalizeText(rawTask.taskType) || defaults.taskType || "implementation";
|
|
97
|
+
if (!TASK_TYPES.has(taskType)) {
|
|
98
|
+
throw new Error(`taskType must be one of ${[...TASK_TYPES].join(", ")} (got: ${taskType})`);
|
|
99
|
+
}
|
|
100
|
+
const closureState = normalizeText(rawTask.closureState) || defaults.closureState || "open";
|
|
101
|
+
if (!CLOSURE_STATES.has(closureState)) {
|
|
102
|
+
throw new Error(`closureState must be one of ${[...CLOSURE_STATES].join(", ")} (got: ${closureState})`);
|
|
103
|
+
}
|
|
104
|
+
const leaseState = normalizeText(rawTask.leaseState) || defaults.leaseState || "unleased";
|
|
105
|
+
if (!LEASE_STATES.has(leaseState)) {
|
|
106
|
+
throw new Error(`leaseState must be one of ${[...LEASE_STATES].join(", ")} (got: ${leaseState})`);
|
|
107
|
+
}
|
|
108
|
+
const priority = normalizeText(rawTask.priority) || defaults.priority || "normal";
|
|
109
|
+
if (!VALID_PRIORITIES.has(priority)) {
|
|
110
|
+
throw new Error(`priority must be one of ${[...VALID_PRIORITIES].join(", ")} (got: ${priority})`);
|
|
111
|
+
}
|
|
112
|
+
const now = toIsoTimestamp();
|
|
113
|
+
const artifactContract = normalizePlainObject(rawTask.artifactContract) || {
|
|
114
|
+
requiredPaths: [],
|
|
115
|
+
proofArtifacts: [],
|
|
116
|
+
exitContract: null,
|
|
117
|
+
componentTargets: {},
|
|
118
|
+
};
|
|
119
|
+
if (!Array.isArray(artifactContract.requiredPaths)) {
|
|
120
|
+
artifactContract.requiredPaths = [];
|
|
121
|
+
}
|
|
122
|
+
if (!Array.isArray(artifactContract.proofArtifacts)) {
|
|
123
|
+
artifactContract.proofArtifacts = [];
|
|
124
|
+
}
|
|
125
|
+
if (!artifactContract.exitContract || typeof artifactContract.exitContract !== "object") {
|
|
126
|
+
artifactContract.exitContract = null;
|
|
127
|
+
}
|
|
128
|
+
if (!artifactContract.componentTargets || typeof artifactContract.componentTargets !== "object" || Array.isArray(artifactContract.componentTargets)) {
|
|
129
|
+
artifactContract.componentTargets = {};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
taskId,
|
|
134
|
+
taskType,
|
|
135
|
+
title: normalizeText(rawTask.title, defaults.title || ""),
|
|
136
|
+
detail: normalizeText(rawTask.detail, defaults.detail || ""),
|
|
137
|
+
ownerAgentId: normalizeText(rawTask.ownerAgentId) || defaults.ownerAgentId || null,
|
|
138
|
+
assigneeAgentId: normalizeText(rawTask.assigneeAgentId) || defaults.assigneeAgentId || null,
|
|
139
|
+
leaseState,
|
|
140
|
+
leaseOwnerAgentId: normalizeText(rawTask.leaseOwnerAgentId) || null,
|
|
141
|
+
leaseAcquiredAt: normalizeText(rawTask.leaseAcquiredAt) || null,
|
|
142
|
+
leaseExpiresAt: normalizeText(rawTask.leaseExpiresAt) || null,
|
|
143
|
+
leaseHeartbeatAt: normalizeText(rawTask.leaseHeartbeatAt) || null,
|
|
144
|
+
artifactContract,
|
|
145
|
+
proofRequirements: normalizeStringArray(rawTask.proofRequirements || defaults.proofRequirements || []),
|
|
146
|
+
dependencyEdges: Array.isArray(rawTask.dependencyEdges)
|
|
147
|
+
? rawTask.dependencyEdges.map((edge) => ({
|
|
148
|
+
targetTaskId: normalizeText(edge.targetTaskId),
|
|
149
|
+
kind: normalizeText(edge.kind, "blocks"),
|
|
150
|
+
}))
|
|
151
|
+
: [],
|
|
152
|
+
closureState,
|
|
153
|
+
sourceRecordId: normalizeText(rawTask.sourceRecordId) || defaults.sourceRecordId || null,
|
|
154
|
+
priority,
|
|
155
|
+
createdAt: normalizeText(rawTask.createdAt) || defaults.createdAt || now,
|
|
156
|
+
updatedAt: normalizeText(rawTask.updatedAt) || now,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function transitionClosureState(currentState, targetState) {
|
|
161
|
+
if (!CLOSURE_STATES.has(currentState)) {
|
|
162
|
+
throw new Error(`Invalid closure state: ${currentState}`);
|
|
163
|
+
}
|
|
164
|
+
if (!CLOSURE_STATES.has(targetState)) {
|
|
165
|
+
throw new Error(`Invalid target closure state: ${targetState}`);
|
|
166
|
+
}
|
|
167
|
+
const allowed = CLOSURE_TRANSITIONS[currentState];
|
|
168
|
+
if (!allowed || !allowed.has(targetState)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Invalid closure transition from ${currentState} to ${targetState}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return targetState;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function acquireLease(task, agentId, expiresAt) {
|
|
177
|
+
if (!task || typeof task !== "object") {
|
|
178
|
+
throw new Error("task must be an object");
|
|
179
|
+
}
|
|
180
|
+
if (!agentId) {
|
|
181
|
+
throw new Error("agentId is required to acquire a lease");
|
|
182
|
+
}
|
|
183
|
+
if (task.leaseState === "leased") {
|
|
184
|
+
throw new Error(`Task ${task.taskId} is already leased by ${task.leaseOwnerAgentId}`);
|
|
185
|
+
}
|
|
186
|
+
const now = toIsoTimestamp();
|
|
187
|
+
return {
|
|
188
|
+
...task,
|
|
189
|
+
leaseState: "leased",
|
|
190
|
+
leaseOwnerAgentId: String(agentId).trim(),
|
|
191
|
+
leaseAcquiredAt: now,
|
|
192
|
+
leaseExpiresAt: expiresAt || null,
|
|
193
|
+
leaseHeartbeatAt: now,
|
|
194
|
+
updatedAt: now,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function releaseLease(task) {
|
|
199
|
+
if (!task || typeof task !== "object") {
|
|
200
|
+
throw new Error("task must be an object");
|
|
201
|
+
}
|
|
202
|
+
const now = toIsoTimestamp();
|
|
203
|
+
return {
|
|
204
|
+
...task,
|
|
205
|
+
leaseState: "released",
|
|
206
|
+
leaseOwnerAgentId: null,
|
|
207
|
+
leaseAcquiredAt: null,
|
|
208
|
+
leaseExpiresAt: null,
|
|
209
|
+
leaseHeartbeatAt: null,
|
|
210
|
+
updatedAt: now,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function heartbeatLease(task) {
|
|
215
|
+
if (!task || typeof task !== "object") {
|
|
216
|
+
throw new Error("task must be an object");
|
|
217
|
+
}
|
|
218
|
+
if (task.leaseState !== "leased") {
|
|
219
|
+
throw new Error(`Cannot heartbeat task ${task.taskId} in leaseState ${task.leaseState}`);
|
|
220
|
+
}
|
|
221
|
+
const now = toIsoTimestamp();
|
|
222
|
+
return {
|
|
223
|
+
...task,
|
|
224
|
+
leaseHeartbeatAt: now,
|
|
225
|
+
updatedAt: now,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function isLeaseExpired(task) {
|
|
230
|
+
if (!task || task.leaseState !== "leased") {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
if (!task.leaseExpiresAt) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const expiresMs = Date.parse(task.leaseExpiresAt);
|
|
237
|
+
if (!Number.isFinite(expiresMs)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return Date.now() > expiresMs;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function buildTasksFromWaveDefinition(waveDefinition, laneConfig = {}) {
|
|
244
|
+
if (!waveDefinition || typeof waveDefinition !== "object") {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
const agents = Array.isArray(waveDefinition.agents) ? waveDefinition.agents : [];
|
|
248
|
+
const contQaAgentId = laneConfig.contQaAgentId || "A0";
|
|
249
|
+
const contEvalAgentId = laneConfig.contEvalAgentId || "E0";
|
|
250
|
+
const integrationAgentId = laneConfig.integrationAgentId || "A8";
|
|
251
|
+
const documentationAgentId = laneConfig.documentationAgentId || "A9";
|
|
252
|
+
const now = toIsoTimestamp();
|
|
253
|
+
const tasks = [];
|
|
254
|
+
|
|
255
|
+
for (const agent of agents) {
|
|
256
|
+
const agentId = agent.agentId;
|
|
257
|
+
const taskType =
|
|
258
|
+
agentId === contQaAgentId
|
|
259
|
+
? "cont-qa"
|
|
260
|
+
: agentId === contEvalAgentId
|
|
261
|
+
? "cont-eval"
|
|
262
|
+
: agentId === integrationAgentId
|
|
263
|
+
? "integration"
|
|
264
|
+
: agentId === documentationAgentId
|
|
265
|
+
? "documentation"
|
|
266
|
+
: isSecurityReviewAgent(agent)
|
|
267
|
+
? "security"
|
|
268
|
+
: "implementation";
|
|
269
|
+
const exitContract =
|
|
270
|
+
agent.exitContract && typeof agent.exitContract === "object"
|
|
271
|
+
? { ...agent.exitContract }
|
|
272
|
+
: null;
|
|
273
|
+
const componentTargets =
|
|
274
|
+
agent.componentTargets && typeof agent.componentTargets === "object"
|
|
275
|
+
? { ...agent.componentTargets }
|
|
276
|
+
: {};
|
|
277
|
+
const proofRequirements = [];
|
|
278
|
+
if (taskType === "implementation") {
|
|
279
|
+
proofRequirements.push("implementation-exit-met");
|
|
280
|
+
if (Object.keys(componentTargets).length > 0) {
|
|
281
|
+
proofRequirements.push("component-level-met");
|
|
282
|
+
}
|
|
283
|
+
if (Array.isArray(agent.deliverables) && agent.deliverables.length > 0) {
|
|
284
|
+
proofRequirements.push("proof-artifacts-present");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
tasks.push(
|
|
288
|
+
normalizeTask(
|
|
289
|
+
{
|
|
290
|
+
taskType,
|
|
291
|
+
title: `${agentId}: ${agent.title || ""}`.trim(),
|
|
292
|
+
detail: agent.detail || "",
|
|
293
|
+
ownerAgentId: agentId,
|
|
294
|
+
assigneeAgentId: agentId,
|
|
295
|
+
artifactContract: {
|
|
296
|
+
requiredPaths: normalizeStringArray(agent.ownedPaths || []),
|
|
297
|
+
proofArtifacts: Array.isArray(agent.deliverables)
|
|
298
|
+
? agent.deliverables.map((deliverable) => ({
|
|
299
|
+
path: typeof deliverable === "string" ? deliverable : deliverable?.path || "",
|
|
300
|
+
kind: typeof deliverable === "object" ? deliverable?.kind || "file" : "file",
|
|
301
|
+
requiredFor: typeof deliverable === "object" ? deliverable?.requiredFor || null : null,
|
|
302
|
+
}))
|
|
303
|
+
: [],
|
|
304
|
+
exitContract,
|
|
305
|
+
componentTargets,
|
|
306
|
+
},
|
|
307
|
+
proofRequirements,
|
|
308
|
+
closureState: "open",
|
|
309
|
+
priority:
|
|
310
|
+
taskType === "implementation"
|
|
311
|
+
? "normal"
|
|
312
|
+
: "high",
|
|
313
|
+
},
|
|
314
|
+
{ createdAt: now },
|
|
315
|
+
),
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
for (const promotion of waveDefinition.componentPromotions || []) {
|
|
320
|
+
tasks.push(
|
|
321
|
+
normalizeTask(
|
|
322
|
+
{
|
|
323
|
+
taskType: "component",
|
|
324
|
+
title: `Promote ${promotion.componentId} to ${promotion.targetLevel}`,
|
|
325
|
+
detail: "",
|
|
326
|
+
ownerAgentId: null,
|
|
327
|
+
artifactContract: {
|
|
328
|
+
requiredPaths: [promotion.componentId],
|
|
329
|
+
proofArtifacts: [],
|
|
330
|
+
exitContract: null,
|
|
331
|
+
componentTargets: { [promotion.componentId]: promotion.targetLevel },
|
|
332
|
+
},
|
|
333
|
+
proofRequirements: ["component-level-met"],
|
|
334
|
+
closureState: "open",
|
|
335
|
+
priority: "high",
|
|
336
|
+
},
|
|
337
|
+
{ createdAt: now },
|
|
338
|
+
),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return tasks;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function buildTasksFromCoordinationState(coordinationState, feedbackRequests = []) {
|
|
346
|
+
if (!coordinationState || typeof coordinationState !== "object") {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
const now = toIsoTimestamp();
|
|
350
|
+
const tasks = [];
|
|
351
|
+
|
|
352
|
+
for (const record of coordinationState.clarifications || []) {
|
|
353
|
+
if (!isOpenCoordinationStatus(record.status)) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
tasks.push(
|
|
357
|
+
normalizeTask(
|
|
358
|
+
{
|
|
359
|
+
taskType: "clarification",
|
|
360
|
+
title: `Clarification: ${record.summary || record.id}`,
|
|
361
|
+
detail: record.detail || "",
|
|
362
|
+
ownerAgentId: record.agentId || null,
|
|
363
|
+
sourceRecordId: record.id || null,
|
|
364
|
+
closureState: "open",
|
|
365
|
+
priority: record.priority || "normal",
|
|
366
|
+
},
|
|
367
|
+
{ createdAt: now },
|
|
368
|
+
),
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
for (const record of coordinationState.humanFeedback || []) {
|
|
373
|
+
if (!isOpenCoordinationStatus(record.status)) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
tasks.push(
|
|
377
|
+
normalizeTask(
|
|
378
|
+
{
|
|
379
|
+
taskType: "human-input",
|
|
380
|
+
title: `Human feedback: ${record.summary || record.id}`,
|
|
381
|
+
detail: record.detail || "",
|
|
382
|
+
ownerAgentId: record.agentId || null,
|
|
383
|
+
sourceRecordId: record.id || null,
|
|
384
|
+
closureState: "open",
|
|
385
|
+
priority: record.priority || "high",
|
|
386
|
+
},
|
|
387
|
+
{ createdAt: now },
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
for (const record of coordinationState.humanEscalations || []) {
|
|
393
|
+
if (!isOpenCoordinationStatus(record.status)) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
tasks.push(
|
|
397
|
+
normalizeTask(
|
|
398
|
+
{
|
|
399
|
+
taskType: "escalation",
|
|
400
|
+
title: `Escalation: ${record.summary || record.id}`,
|
|
401
|
+
detail: record.detail || "",
|
|
402
|
+
ownerAgentId: record.agentId || null,
|
|
403
|
+
sourceRecordId: record.id || null,
|
|
404
|
+
closureState: "open",
|
|
405
|
+
priority: "urgent",
|
|
406
|
+
},
|
|
407
|
+
{ createdAt: now },
|
|
408
|
+
),
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
for (const request of feedbackRequests || []) {
|
|
413
|
+
if (!isOpenCoordinationStatus(request.status || "open")) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
tasks.push(
|
|
417
|
+
normalizeTask(
|
|
418
|
+
{
|
|
419
|
+
taskType: "human-input",
|
|
420
|
+
title: `Feedback request: ${request.summary || request.id || ""}`,
|
|
421
|
+
detail: request.detail || "",
|
|
422
|
+
ownerAgentId: request.agentId || null,
|
|
423
|
+
sourceRecordId: request.id || null,
|
|
424
|
+
closureState: "open",
|
|
425
|
+
priority: request.priority || "high",
|
|
426
|
+
},
|
|
427
|
+
{ createdAt: now },
|
|
428
|
+
),
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return tasks;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function mergeTaskSets(seedTasks, coordinationTasks) {
|
|
436
|
+
const merged = [...(Array.isArray(seedTasks) ? seedTasks : [])];
|
|
437
|
+
const existingSourceIds = new Set(
|
|
438
|
+
merged.map((task) => task.sourceRecordId).filter(Boolean),
|
|
439
|
+
);
|
|
440
|
+
for (const task of Array.isArray(coordinationTasks) ? coordinationTasks : []) {
|
|
441
|
+
if (task.sourceRecordId && existingSourceIds.has(task.sourceRecordId)) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
merged.push(task);
|
|
445
|
+
if (task.sourceRecordId) {
|
|
446
|
+
existingSourceIds.add(task.sourceRecordId);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return merged;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export function evaluateOwnedSliceProven(task, agentResult, proofBundles = []) {
|
|
453
|
+
if (!task || !task.taskType) {
|
|
454
|
+
return { proven: false, reason: "Invalid task" };
|
|
455
|
+
}
|
|
456
|
+
if (!agentResult) {
|
|
457
|
+
return { proven: false, reason: "No agent result available" };
|
|
458
|
+
}
|
|
459
|
+
const agent = {
|
|
460
|
+
agentId: task.assigneeAgentId || task.ownerAgentId,
|
|
461
|
+
ownedPaths: task.artifactContract?.requiredPaths || [],
|
|
462
|
+
deliverables: (task.artifactContract?.proofArtifacts || []).map((artifact) => artifact.path),
|
|
463
|
+
exitContract: task.artifactContract?.exitContract || null,
|
|
464
|
+
components: Object.keys(task.artifactContract?.componentTargets || {}),
|
|
465
|
+
componentTargets: task.artifactContract?.componentTargets || {},
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
if (task.taskType === "implementation") {
|
|
469
|
+
const validation = validateImplementationSummary(agent, agentResult);
|
|
470
|
+
if (!validation.ok) {
|
|
471
|
+
return { proven: false, reason: validation.detail || validation.statusCode };
|
|
472
|
+
}
|
|
473
|
+
const componentTargets = task.artifactContract?.componentTargets || {};
|
|
474
|
+
const componentIds = Object.keys(componentTargets);
|
|
475
|
+
if (componentIds.length > 0) {
|
|
476
|
+
const componentMarkers = new Map(
|
|
477
|
+
Array.isArray(agentResult?.components)
|
|
478
|
+
? agentResult.components.map((component) => [component.componentId, component])
|
|
479
|
+
: [],
|
|
480
|
+
);
|
|
481
|
+
for (const componentId of componentIds) {
|
|
482
|
+
const expectedLevel = componentTargets[componentId];
|
|
483
|
+
const marker = componentMarkers.get(componentId);
|
|
484
|
+
if (!marker || marker.state !== "met" || (expectedLevel && marker.level !== expectedLevel)) {
|
|
485
|
+
return { proven: false, reason: `Component ${componentId} not proven at ${expectedLevel || "any level"}` };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return { proven: true, reason: "Exit contract satisfied" };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (task.taskType === "cont-qa") {
|
|
493
|
+
const validation = validateContQaSummary(agent, agentResult, { mode: "live" });
|
|
494
|
+
return validation.ok
|
|
495
|
+
? { proven: true, reason: "Cont-QA satisfied" }
|
|
496
|
+
: { proven: false, reason: validation.detail || validation.statusCode };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (task.taskType === "cont-eval") {
|
|
500
|
+
const evalValidation = validateContEvalSummary(agent, agentResult, { mode: "live" });
|
|
501
|
+
if (!evalValidation.ok) {
|
|
502
|
+
return { proven: false, reason: evalValidation.detail || evalValidation.statusCode };
|
|
503
|
+
}
|
|
504
|
+
if (isContEvalImplementationOwningAgent(agent, { contEvalAgentId: agent.agentId })) {
|
|
505
|
+
const implValidation = validateImplementationSummary(agent, agentResult);
|
|
506
|
+
if (!implValidation.ok) {
|
|
507
|
+
return { proven: false, reason: implValidation.detail || implValidation.statusCode };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return { proven: true, reason: "Cont-EVAL satisfied" };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (task.taskType === "documentation") {
|
|
514
|
+
const validation = validateDocumentationClosureSummary(agent, agentResult);
|
|
515
|
+
return validation.ok
|
|
516
|
+
? { proven: true, reason: "Documentation closure satisfied" }
|
|
517
|
+
: { proven: false, reason: validation.detail || validation.statusCode };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (task.taskType === "security") {
|
|
521
|
+
const validation = validateSecuritySummary(agent, agentResult);
|
|
522
|
+
return validation.ok
|
|
523
|
+
? { proven: true, reason: "Security review satisfied" }
|
|
524
|
+
: { proven: false, reason: validation.detail || validation.statusCode };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (task.taskType === "integration") {
|
|
528
|
+
const validation = validateIntegrationSummary(agent, agentResult);
|
|
529
|
+
return validation.ok
|
|
530
|
+
? { proven: true, reason: "Integration summary satisfied" }
|
|
531
|
+
: { proven: false, reason: validation.detail || validation.statusCode };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return { proven: false, reason: `Unsupported task type: ${task.taskType}` };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function evaluateWaveClosureReady(tasks, gateSnapshot) {
|
|
538
|
+
if (!gateSnapshot || !gateSnapshot.overall) {
|
|
539
|
+
return { ready: false, reason: "No gate snapshot available" };
|
|
540
|
+
}
|
|
541
|
+
if (!gateSnapshot.overall.ok) {
|
|
542
|
+
return {
|
|
543
|
+
ready: false,
|
|
544
|
+
reason: `Gate ${gateSnapshot.overall.gate || "unknown"} failed: ${gateSnapshot.overall.detail || gateSnapshot.overall.statusCode}`,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const openTasks = (tasks || []).filter(
|
|
548
|
+
(task) => task.closureState === "open" || task.closureState === "owned_slice_proven",
|
|
549
|
+
);
|
|
550
|
+
if (openTasks.length > 0) {
|
|
551
|
+
return {
|
|
552
|
+
ready: false,
|
|
553
|
+
reason: `${openTasks.length} task(s) are not yet closure-ready`,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
return { ready: true, reason: "All gates pass and all tasks are closure-ready" };
|
|
557
|
+
}
|
|
@@ -129,7 +129,7 @@ export function createTemporaryTerminalEntries(
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
export function createGlobalDashboardTerminalEntry(lanePaths, runTag) {
|
|
132
|
-
const sessionName = `${lanePaths.tmuxGlobalDashboardSessionPrefix}
|
|
132
|
+
const sessionName = `${lanePaths.tmuxGlobalDashboardSessionPrefix}_current`.replace(
|
|
133
133
|
/[^a-zA-Z0-9:_-]/g,
|
|
134
134
|
"_",
|
|
135
135
|
);
|