@chllming/wave-orchestration 0.9.13 → 0.9.15
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 +27 -0
- package/README.md +7 -7
- package/docs/README.md +3 -3
- package/docs/concepts/operating-modes.md +1 -1
- package/docs/guides/author-and-run-waves.md +1 -1
- package/docs/guides/planner.md +2 -2
- package/docs/guides/recommendations-0.9.15.md +83 -0
- package/docs/guides/sandboxed-environments.md +2 -2
- package/docs/guides/signal-wrappers.md +10 -0
- package/docs/plans/agent-first-closure-hardening.md +612 -0
- package/docs/plans/current-state.md +3 -3
- package/docs/plans/end-state-architecture.md +1 -1
- package/docs/plans/examples/wave-example-design-handoff.md +1 -1
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +75 -20
- package/docs/reference/cli-reference.md +34 -1
- package/docs/reference/coordination-and-closure.md +16 -1
- package/docs/reference/npmjs-token-publishing.md +3 -3
- package/docs/reference/package-publishing-flow.md +13 -11
- package/docs/reference/runtime-config/README.md +2 -2
- package/docs/reference/sample-waves.md +5 -5
- package/docs/reference/skills.md +1 -1
- package/docs/reference/wave-control.md +1 -1
- package/docs/roadmap.md +5 -3
- package/package.json +1 -1
- package/releases/manifest.json +35 -0
- package/scripts/wave-orchestrator/agent-state.mjs +221 -313
- package/scripts/wave-orchestrator/artifact-schemas.mjs +37 -2
- package/scripts/wave-orchestrator/closure-adjudicator.mjs +311 -0
- package/scripts/wave-orchestrator/control-cli.mjs +212 -18
- package/scripts/wave-orchestrator/dashboard-state.mjs +40 -0
- package/scripts/wave-orchestrator/derived-state-engine.mjs +3 -0
- package/scripts/wave-orchestrator/gate-engine.mjs +140 -3
- package/scripts/wave-orchestrator/install.mjs +1 -1
- package/scripts/wave-orchestrator/launcher.mjs +49 -10
- package/scripts/wave-orchestrator/signal-cli.mjs +271 -0
- package/scripts/wave-orchestrator/structured-signal-parser.mjs +499 -0
- package/scripts/wave-orchestrator/task-entity.mjs +13 -4
- package/scripts/wave.mjs +9 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeClosureAdjudication } from "./artifact-schemas.mjs";
|
|
4
|
+
import { REPO_ROOT, ensureDirectory } from "./shared.mjs";
|
|
5
|
+
import { parseStructuredSignalCandidate } from "./structured-signal-parser.mjs";
|
|
6
|
+
import { validateImplementationSummary } from "./agent-state.mjs";
|
|
7
|
+
|
|
8
|
+
function isOpenCoordinationStatus(status) {
|
|
9
|
+
return ["open", "acknowledged", "in_progress"].includes(String(status || "").trim().toLowerCase());
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function blockingCoordinationForAgent(derivedState, agentId) {
|
|
13
|
+
const latestRecords = Array.isArray(derivedState?.coordinationState?.latestRecords)
|
|
14
|
+
? derivedState.coordinationState.latestRecords
|
|
15
|
+
: [];
|
|
16
|
+
return latestRecords.some((record) => {
|
|
17
|
+
if (!isOpenCoordinationStatus(record?.status) || record?.blocking === false) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (record?.agentId === agentId) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return (record?.targets || []).some((target) => String(target || "").trim() === `agent:${agentId}`);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildEvidence(summary, envelope, derivedState) {
|
|
28
|
+
return [
|
|
29
|
+
{ kind: "exit-code", value: summary?.exitCode ?? null },
|
|
30
|
+
{
|
|
31
|
+
kind: "deliverables",
|
|
32
|
+
value: (summary?.deliverables || []).map((deliverable) => ({
|
|
33
|
+
path: deliverable.path,
|
|
34
|
+
exists: deliverable.exists === true,
|
|
35
|
+
})),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
kind: "proof-artifacts",
|
|
39
|
+
value: (summary?.proofArtifacts || []).map((artifact) => ({
|
|
40
|
+
path: artifact.path,
|
|
41
|
+
exists: artifact.exists === true,
|
|
42
|
+
})),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
kind: "envelope-role",
|
|
46
|
+
value: envelope?.role || null,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
kind: "integration-summary",
|
|
50
|
+
value: derivedState?.integrationSummary?.recommendation || null,
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function synthesizedSignals(summary) {
|
|
56
|
+
const signals = [];
|
|
57
|
+
if (summary?.proof) {
|
|
58
|
+
signals.push(
|
|
59
|
+
`[wave-proof] completion=${summary.proof.completion} durability=${summary.proof.durability} proof=${summary.proof.proof} state=${summary.proof.state}${summary.proof.detail ? ` detail=${summary.proof.detail}` : ""}`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (summary?.docDelta) {
|
|
63
|
+
signals.push(
|
|
64
|
+
`[wave-doc-delta] state=${summary.docDelta.state}${(summary.docDelta.paths || []).length > 0 ? ` paths=${summary.docDelta.paths.join(",")}` : ""}${summary.docDelta.detail ? ` detail=${summary.docDelta.detail}` : ""}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
for (const component of summary?.components || []) {
|
|
68
|
+
signals.push(
|
|
69
|
+
`[wave-component] component=${component.componentId} level=${component.level} state=${component.state}${component.detail ? ` detail=${component.detail}` : ""}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return signals;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function cleanText(value) {
|
|
76
|
+
return String(value || "").trim();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function cleanState(value) {
|
|
80
|
+
const normalized = cleanText(value).toLowerCase();
|
|
81
|
+
return normalized === "complete" ? "met" : normalized;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function proofLineFromRawValues(rawValues) {
|
|
85
|
+
const completion = cleanText(rawValues?.completion).toLowerCase();
|
|
86
|
+
const durability = cleanText(rawValues?.durability).toLowerCase();
|
|
87
|
+
const proof = cleanText(rawValues?.proof).toLowerCase();
|
|
88
|
+
const state = cleanState(rawValues?.state);
|
|
89
|
+
const line = `[wave-proof] completion=${completion} durability=${durability} proof=${proof} state=${state}`;
|
|
90
|
+
const candidate = parseStructuredSignalCandidate(line);
|
|
91
|
+
return candidate?.accepted ? candidate.normalizedLine : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function docDeltaLineFromRawValues(rawValues) {
|
|
95
|
+
const state = cleanText(rawValues?.state).toLowerCase();
|
|
96
|
+
const line = `[wave-doc-delta] state=${state}`;
|
|
97
|
+
const candidate = parseStructuredSignalCandidate(line);
|
|
98
|
+
return candidate?.accepted ? candidate.normalizedLine : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function componentLineFromRawValues(rawValues) {
|
|
102
|
+
const componentId = cleanText(rawValues?.component);
|
|
103
|
+
const level = cleanText(rawValues?.level).toLowerCase();
|
|
104
|
+
const state = cleanState(rawValues?.state);
|
|
105
|
+
const line = `[wave-component] component=${componentId} level=${level} state=${state}`;
|
|
106
|
+
const candidate = parseStructuredSignalCandidate(line);
|
|
107
|
+
return candidate?.accepted ? candidate.normalizedLine : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function rejectedStructuredSignalSamples(summary, key) {
|
|
111
|
+
const bucket = summary?.structuredSignalDiagnostics?.[key];
|
|
112
|
+
return Array.isArray(bucket?.rejectedSamples) ? bucket.rejectedSamples : [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function recoverStructuredSignalLine(gate, summary, agentRun) {
|
|
116
|
+
if (gate?.statusCode === "invalid-wave-proof-format") {
|
|
117
|
+
for (const sample of rejectedStructuredSignalSamples(summary, "proof")) {
|
|
118
|
+
const line = proofLineFromRawValues(sample?.rawValues);
|
|
119
|
+
if (line) {
|
|
120
|
+
return line;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
if (gate?.statusCode === "invalid-doc-delta-format") {
|
|
126
|
+
for (const sample of rejectedStructuredSignalSamples(summary, "docDelta")) {
|
|
127
|
+
const line = docDeltaLineFromRawValues(sample?.rawValues);
|
|
128
|
+
if (line) {
|
|
129
|
+
return line;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
if (gate?.statusCode === "invalid-wave-component-format") {
|
|
135
|
+
const ownedComponents = new Set(Array.isArray(agentRun?.agent?.components) ? agentRun.agent.components : []);
|
|
136
|
+
for (const sample of rejectedStructuredSignalSamples(summary, "component")) {
|
|
137
|
+
if (!ownedComponents.has(cleanText(sample?.componentId))) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const line = componentLineFromRawValues(sample?.rawValues);
|
|
141
|
+
if (line) {
|
|
142
|
+
return line;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function recoverSummaryFromRejectedSignals(gate, summary, agentRun) {
|
|
151
|
+
const recoveredLine = recoverStructuredSignalLine(gate, summary, agentRun);
|
|
152
|
+
if (!recoveredLine) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const candidate = parseStructuredSignalCandidate(recoveredLine);
|
|
156
|
+
if (!candidate?.accepted) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const recoveredSummary = {
|
|
160
|
+
...summary,
|
|
161
|
+
};
|
|
162
|
+
if (candidate.kind === "proof") {
|
|
163
|
+
recoveredSummary.proof = {
|
|
164
|
+
completion: candidate.rawValues.completion.toLowerCase(),
|
|
165
|
+
durability: candidate.rawValues.durability.toLowerCase(),
|
|
166
|
+
proof: candidate.rawValues.proof.toLowerCase(),
|
|
167
|
+
state: cleanState(candidate.rawValues.state),
|
|
168
|
+
detail: "",
|
|
169
|
+
};
|
|
170
|
+
} else if (candidate.kind === "docDelta") {
|
|
171
|
+
recoveredSummary.docDelta = {
|
|
172
|
+
state: candidate.rawValues.state.toLowerCase(),
|
|
173
|
+
paths: [],
|
|
174
|
+
detail: "",
|
|
175
|
+
};
|
|
176
|
+
} else if (candidate.kind === "component") {
|
|
177
|
+
const recoveredComponent = {
|
|
178
|
+
componentId: cleanText(candidate.rawValues.component),
|
|
179
|
+
level: cleanText(candidate.rawValues.level).toLowerCase(),
|
|
180
|
+
state: cleanState(candidate.rawValues.state),
|
|
181
|
+
detail: "",
|
|
182
|
+
};
|
|
183
|
+
const existingComponents = Array.isArray(summary?.components) ? summary.components : [];
|
|
184
|
+
recoveredSummary.components = [
|
|
185
|
+
...existingComponents.filter((component) => component.componentId !== recoveredComponent.componentId),
|
|
186
|
+
recoveredComponent,
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
recoveredLine,
|
|
191
|
+
recoveredSummary,
|
|
192
|
+
recoveredValidation: validateImplementationSummary(agentRun?.agent || null, recoveredSummary),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function closureAdjudicationPath(lanePaths, waveNumber, attempt, agentId) {
|
|
197
|
+
return path.join(
|
|
198
|
+
lanePaths.statusDir,
|
|
199
|
+
"..",
|
|
200
|
+
"closure",
|
|
201
|
+
`wave-${waveNumber}`,
|
|
202
|
+
`attempt-${attempt || 1}`,
|
|
203
|
+
`${agentId}.json`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function evaluateClosureAdjudication({
|
|
208
|
+
wave,
|
|
209
|
+
lanePaths,
|
|
210
|
+
gate,
|
|
211
|
+
summary,
|
|
212
|
+
derivedState,
|
|
213
|
+
agentRun,
|
|
214
|
+
envelope,
|
|
215
|
+
}) {
|
|
216
|
+
if (gate?.failureClass !== "transport-failure" || gate?.eligibleForAdjudication !== true) {
|
|
217
|
+
return {
|
|
218
|
+
status: "ambiguous",
|
|
219
|
+
reason: "not-eligible",
|
|
220
|
+
detail: "Closure failure is not eligible for deterministic adjudication.",
|
|
221
|
+
evidence: [],
|
|
222
|
+
synthesizedSignals: [],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (summary?.proof?.state === "gap" || (summary?.gaps || []).length > 0) {
|
|
226
|
+
return {
|
|
227
|
+
status: "rework-required",
|
|
228
|
+
reason: "semantic-negative-signal",
|
|
229
|
+
detail: "Explicit negative semantic proof signals remain.",
|
|
230
|
+
evidence: buildEvidence(summary, envelope, derivedState),
|
|
231
|
+
synthesizedSignals: synthesizedSignals(summary),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (blockingCoordinationForAgent(derivedState, agentRun?.agent?.agentId)) {
|
|
235
|
+
return {
|
|
236
|
+
status: "ambiguous",
|
|
237
|
+
reason: "blocking-coordination",
|
|
238
|
+
detail: "Blocking coordination owned by the same agent slice remains open.",
|
|
239
|
+
evidence: buildEvidence(summary, envelope, derivedState),
|
|
240
|
+
synthesizedSignals: synthesizedSignals(summary),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const recovered = recoverSummaryFromRejectedSignals(gate, summary, agentRun);
|
|
244
|
+
if (!recovered) {
|
|
245
|
+
return {
|
|
246
|
+
status: "rework-required",
|
|
247
|
+
reason: "reconstruction-failed",
|
|
248
|
+
detail: "Rejected marker text did not preserve enough safe contract data to reconstruct a canonical closure signal.",
|
|
249
|
+
evidence: buildEvidence(summary, envelope, derivedState),
|
|
250
|
+
synthesizedSignals: synthesizedSignals(summary),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (!recovered.recoveredValidation?.ok) {
|
|
254
|
+
return {
|
|
255
|
+
status: "rework-required",
|
|
256
|
+
reason: "recovered-signal-failed-validation",
|
|
257
|
+
detail: recovered.recoveredValidation?.detail || "Recovered closure signal still does not satisfy the implementation exit contract.",
|
|
258
|
+
evidence: buildEvidence(summary, envelope, derivedState),
|
|
259
|
+
synthesizedSignals: [...synthesizedSignals(summary), recovered.recoveredLine],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
status: "pass",
|
|
264
|
+
reason: "recovered-canonical-signal",
|
|
265
|
+
detail: "Rejected marker text preserved enough contract data to recover a canonical closure signal that satisfies the implementation exit contract.",
|
|
266
|
+
evidence: buildEvidence(summary, envelope, derivedState),
|
|
267
|
+
synthesizedSignals: [...synthesizedSignals(summary), recovered.recoveredLine],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function persistClosureAdjudication({
|
|
272
|
+
lanePaths,
|
|
273
|
+
waveNumber,
|
|
274
|
+
attempt,
|
|
275
|
+
agentId,
|
|
276
|
+
payload,
|
|
277
|
+
}) {
|
|
278
|
+
const filePath = closureAdjudicationPath(lanePaths, waveNumber, attempt, agentId);
|
|
279
|
+
ensureDirectory(path.dirname(filePath));
|
|
280
|
+
return {
|
|
281
|
+
filePath,
|
|
282
|
+
adjudication: writeClosureAdjudication(
|
|
283
|
+
filePath,
|
|
284
|
+
{
|
|
285
|
+
lane: lanePaths?.lane || null,
|
|
286
|
+
wave: waveNumber,
|
|
287
|
+
attempt,
|
|
288
|
+
agentId,
|
|
289
|
+
...payload,
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
lane: lanePaths?.lane || null,
|
|
293
|
+
wave: waveNumber,
|
|
294
|
+
attempt,
|
|
295
|
+
agentId,
|
|
296
|
+
},
|
|
297
|
+
),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function readPersistedClosureAdjudication(filePath, defaults = {}) {
|
|
302
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
const payload = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
306
|
+
return {
|
|
307
|
+
...payload,
|
|
308
|
+
filePath: path.isAbsolute(filePath) ? path.relative(REPO_ROOT, filePath) : filePath,
|
|
309
|
+
...defaults,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
@@ -39,9 +39,11 @@ import {
|
|
|
39
39
|
registerWaveProofBundle,
|
|
40
40
|
waveProofRegistryPath,
|
|
41
41
|
} from "./proof-registry.mjs";
|
|
42
|
+
import { closureAdjudicationPath, evaluateClosureAdjudication } from "./closure-adjudicator.mjs";
|
|
42
43
|
import { readWaveRelaunchPlanSnapshot, readWaveRetryOverride, resolveRetryOverrideAgentIds, writeWaveRetryOverride, clearWaveRetryOverride } from "./retry-control.mjs";
|
|
43
44
|
import { flushWaveControlQueue, readWaveControlQueueState } from "./wave-control-client.mjs";
|
|
44
45
|
import { readAgentExecutionSummary, validateImplementationSummary } from "./agent-state.mjs";
|
|
46
|
+
import { readClosureAdjudication } from "./artifact-schemas.mjs";
|
|
45
47
|
import { isContEvalReportOnlyAgent, isSecurityReviewAgentForLane } from "./role-helpers.mjs";
|
|
46
48
|
import {
|
|
47
49
|
buildSignalStatusLine,
|
|
@@ -68,6 +70,8 @@ function printUsage() {
|
|
|
68
70
|
wave control proof get --project <id> --lane <lane> --wave <n> [--agent <id>] [--id <bundle-id>] [--json]
|
|
69
71
|
wave control proof supersede --project <id> --lane <lane> --wave <n> --id <bundle-id> --agent <id> --artifact <path> [--artifact <path> ...] [options]
|
|
70
72
|
wave control proof revoke --project <id> --lane <lane> --wave <n> --id <bundle-id> [--operator <name>] [--detail <text>] [--json]
|
|
73
|
+
|
|
74
|
+
wave control adjudication get --project <id> --lane <lane> --wave <n> [--agent <id>] [--json]
|
|
71
75
|
`);
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -135,7 +139,7 @@ function parseArgs(argv) {
|
|
|
135
139
|
? 1
|
|
136
140
|
: surface === "telemetry"
|
|
137
141
|
? 2
|
|
138
|
-
: surface === "task" || surface === "rerun" || surface === "proof"
|
|
142
|
+
: surface === "task" || surface === "rerun" || surface === "proof" || surface === "adjudication"
|
|
139
143
|
? operation === "act"
|
|
140
144
|
? 3
|
|
141
145
|
: 2
|
|
@@ -256,6 +260,42 @@ function ledgerPath(lanePaths, waveNumber) {
|
|
|
256
260
|
return path.join(lanePaths.ledgerDir, `wave-${waveNumber}.json`);
|
|
257
261
|
}
|
|
258
262
|
|
|
263
|
+
function readWaveClosureAdjudications(lanePaths, waveNumber, agentId = "") {
|
|
264
|
+
const samplePath = closureAdjudicationPath(lanePaths, waveNumber, 1, agentId || "sample");
|
|
265
|
+
const attemptsRoot = path.dirname(path.dirname(samplePath));
|
|
266
|
+
if (!fs.existsSync(attemptsRoot)) {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
const adjudications = [];
|
|
270
|
+
for (const attemptEntry of fs.readdirSync(attemptsRoot, { withFileTypes: true })) {
|
|
271
|
+
if (!attemptEntry.isDirectory()) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const attemptDir = path.join(attemptsRoot, attemptEntry.name);
|
|
275
|
+
for (const fileEntry of fs.readdirSync(attemptDir, { withFileTypes: true })) {
|
|
276
|
+
if (!fileEntry.isFile() || !fileEntry.name.endsWith(".json")) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const filePath = path.join(attemptDir, fileEntry.name);
|
|
280
|
+
const payload = readClosureAdjudication(filePath, {
|
|
281
|
+
lane: lanePaths.lane,
|
|
282
|
+
wave: waveNumber,
|
|
283
|
+
});
|
|
284
|
+
if (!payload) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (agentId && payload.agentId !== agentId) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
adjudications.push({
|
|
291
|
+
...payload,
|
|
292
|
+
filePath: path.relative(process.cwd(), filePath),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return adjudications.sort((left, right) => String(left.createdAt || "").localeCompare(String(right.createdAt || "")));
|
|
297
|
+
}
|
|
298
|
+
|
|
259
299
|
function targetAgentId(target) {
|
|
260
300
|
const value = String(target || "").trim();
|
|
261
301
|
return value.startsWith("agent:") ? value.slice("agent:".length) : value;
|
|
@@ -342,6 +382,7 @@ function buildLogicalAgents({
|
|
|
342
382
|
lanePaths,
|
|
343
383
|
wave,
|
|
344
384
|
tasks,
|
|
385
|
+
coordinationState,
|
|
345
386
|
dependencySnapshot,
|
|
346
387
|
capabilityAssignments,
|
|
347
388
|
selection,
|
|
@@ -371,6 +412,36 @@ function buildLogicalAgents({
|
|
|
371
412
|
!isContEvalReportOnlyAgent(agent, { contEvalAgentId: lanePaths.contEvalAgentId })
|
|
372
413
|
? validateImplementationSummary(agent, summary ? summary : null)
|
|
373
414
|
: { ok: statusRecord?.code === 0, statusCode: statusRecord?.code === 0 ? "pass" : "pending" };
|
|
415
|
+
const adjudicationPath = closureAdjudicationPath(
|
|
416
|
+
lanePaths,
|
|
417
|
+
wave.wave,
|
|
418
|
+
statusRecord?.attempt || 1,
|
|
419
|
+
agent.agentId,
|
|
420
|
+
);
|
|
421
|
+
const persistedAdjudication = readClosureAdjudication(adjudicationPath, {
|
|
422
|
+
lane: lanePaths.lane,
|
|
423
|
+
wave: wave.wave,
|
|
424
|
+
attempt: statusRecord?.attempt || 1,
|
|
425
|
+
agentId: agent.agentId,
|
|
426
|
+
});
|
|
427
|
+
const adjudication =
|
|
428
|
+
proofValidation.eligibleForAdjudication && !proofValidation.ok
|
|
429
|
+
? persistedAdjudication ||
|
|
430
|
+
evaluateClosureAdjudication({
|
|
431
|
+
wave,
|
|
432
|
+
lanePaths,
|
|
433
|
+
gate: proofValidation,
|
|
434
|
+
summary,
|
|
435
|
+
derivedState: {
|
|
436
|
+
coordinationState,
|
|
437
|
+
},
|
|
438
|
+
agentRun: {
|
|
439
|
+
agent,
|
|
440
|
+
logPath,
|
|
441
|
+
},
|
|
442
|
+
envelope: null,
|
|
443
|
+
})
|
|
444
|
+
: null;
|
|
374
445
|
const targetedTasks = tasks.filter(
|
|
375
446
|
(task) =>
|
|
376
447
|
task.ownerAgentId === agent.agentId ||
|
|
@@ -387,19 +458,29 @@ function buildLogicalAgents({
|
|
|
387
458
|
const satisfiedByStatus =
|
|
388
459
|
statusRecord?.code === 0 &&
|
|
389
460
|
(proofValidation.ok ||
|
|
461
|
+
adjudication?.status === "pass" ||
|
|
390
462
|
isSecurityReviewAgentForLane(agent, lanePaths) ||
|
|
391
463
|
isContEvalReportOnlyAgent(agent, { contEvalAgentId: lanePaths.contEvalAgentId }));
|
|
464
|
+
const executionState =
|
|
465
|
+
selection?.source === "active-attempt" && selectedAgentIds.has(agent.agentId)
|
|
466
|
+
? "active"
|
|
467
|
+
: Number.isInteger(statusRecord?.code)
|
|
468
|
+
? "settled"
|
|
469
|
+
: "pending";
|
|
392
470
|
let state = "planned";
|
|
393
471
|
let reason = "";
|
|
472
|
+
let closureState = "pending";
|
|
394
473
|
if (selection?.source === "active-attempt" && selectedAgentIds.has(agent.agentId)) {
|
|
395
474
|
state = "working";
|
|
396
475
|
reason = selection?.detail || "Selected by the active launcher attempt.";
|
|
476
|
+
closureState = "evaluating";
|
|
397
477
|
} else if (selectedAgentIds.has(agent.agentId)) {
|
|
398
478
|
state = "needs-rerun";
|
|
399
479
|
reason =
|
|
400
480
|
selection?.source === "relaunch-plan"
|
|
401
481
|
? "Selected by the persisted relaunch plan."
|
|
402
482
|
: "Selected by active rerun request.";
|
|
483
|
+
closureState = "failed";
|
|
403
484
|
} else if (completedPhase && satisfiedByStatus) {
|
|
404
485
|
state = [
|
|
405
486
|
lanePaths.contEvalAgentId || "E0",
|
|
@@ -410,9 +491,11 @@ function buildLogicalAgents({
|
|
|
410
491
|
? "closed"
|
|
411
492
|
: "satisfied";
|
|
412
493
|
reason = "Completed wave preserves the latest satisfied agent state.";
|
|
494
|
+
closureState = "passed";
|
|
413
495
|
} else if (targetedBlockingTasks.some((task) => task.state === "working")) {
|
|
414
496
|
state = "working";
|
|
415
497
|
reason = targetedBlockingTasks.find((task) => task.state === "working")?.title || "";
|
|
498
|
+
closureState = "evaluating";
|
|
416
499
|
} else if (targetedBlockingTasks.length > 0 || helperAssignment || dependency) {
|
|
417
500
|
state = "blocked";
|
|
418
501
|
reason =
|
|
@@ -421,6 +504,7 @@ function buildLogicalAgents({
|
|
|
421
504
|
helperAssignment?.summary ||
|
|
422
505
|
dependency?.summary ||
|
|
423
506
|
"";
|
|
507
|
+
closureState = "blocked";
|
|
424
508
|
} else if (satisfiedByStatus) {
|
|
425
509
|
state = [
|
|
426
510
|
lanePaths.contEvalAgentId || "E0",
|
|
@@ -431,14 +515,26 @@ function buildLogicalAgents({
|
|
|
431
515
|
? "closed"
|
|
432
516
|
: "satisfied";
|
|
433
517
|
reason = "Latest attempt satisfied current control-plane state.";
|
|
518
|
+
closureState = "passed";
|
|
519
|
+
} else if (adjudication?.status === "ambiguous") {
|
|
520
|
+
state = "awaiting-adjudication";
|
|
521
|
+
reason = adjudication.detail || proofValidation.detail || "Closure transport is awaiting deterministic adjudication.";
|
|
522
|
+
closureState = "awaiting-adjudication";
|
|
523
|
+
} else if (adjudication?.status === "rework-required") {
|
|
524
|
+
state = "needs-rerun";
|
|
525
|
+
reason = adjudication.detail || proofValidation.detail || "Closure adjudication requires more work.";
|
|
526
|
+
closureState = "failed";
|
|
434
527
|
} else if (Number.isInteger(statusRecord?.code) && statusRecord.code !== 0) {
|
|
435
528
|
state = "needs-rerun";
|
|
436
529
|
reason = `Latest attempt exited with code ${statusRecord.code}.`;
|
|
530
|
+
closureState = "failed";
|
|
437
531
|
}
|
|
438
532
|
return {
|
|
439
533
|
agentId: agent.agentId,
|
|
440
534
|
state,
|
|
441
535
|
reason: reason || null,
|
|
536
|
+
executionState,
|
|
537
|
+
closureState,
|
|
442
538
|
taskIds: targetedTasks.map((task) => task.taskId),
|
|
443
539
|
selectedForRerun: selectedAgentIds.has(agent.agentId) && selection?.source !== "active-attempt",
|
|
444
540
|
selectedForActiveAttempt: selection?.source === "active-attempt" && selectedAgentIds.has(agent.agentId),
|
|
@@ -453,6 +549,53 @@ function buildLogicalAgents({
|
|
|
453
549
|
});
|
|
454
550
|
}
|
|
455
551
|
|
|
552
|
+
function hasLiveSupervisorRuntime(supervisor) {
|
|
553
|
+
const runtimeSummary = Array.isArray(supervisor?.agentRuntimeSummary) ? supervisor.agentRuntimeSummary : [];
|
|
554
|
+
return runtimeSummary.some((runtime) => !["completed", "failed", "terminated"].includes(String(runtime?.terminalDisposition || "").trim().toLowerCase()));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function deriveExecutionState({ phase, activeAttempt, supervisor, logicalAgents }) {
|
|
558
|
+
if (activeAttempt || hasLiveSupervisorRuntime(supervisor)) {
|
|
559
|
+
return "active";
|
|
560
|
+
}
|
|
561
|
+
if (
|
|
562
|
+
isCompletedPhase(phase) ||
|
|
563
|
+
(Array.isArray(logicalAgents) && logicalAgents.some((agent) => agent.executionState === "settled" || agent.closureState === "passed"))
|
|
564
|
+
) {
|
|
565
|
+
return "settled";
|
|
566
|
+
}
|
|
567
|
+
return "pending";
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function deriveControllerState({ executionState, relaunchPlan, supervisor }) {
|
|
571
|
+
if (executionState === "active") {
|
|
572
|
+
return "active";
|
|
573
|
+
}
|
|
574
|
+
if (relaunchPlan?.selectedAgentIds?.length > 0) {
|
|
575
|
+
return "relaunch-planned";
|
|
576
|
+
}
|
|
577
|
+
if (["degraded", "failed"].includes(String(supervisor?.recoveryState || "").trim().toLowerCase())) {
|
|
578
|
+
return "stale";
|
|
579
|
+
}
|
|
580
|
+
return "idle";
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function deriveClosureState({ executionState, blockingEdge, logicalAgents, relaunchPlan, phase }) {
|
|
584
|
+
if ((logicalAgents || []).some((agent) => agent.closureState === "awaiting-adjudication")) {
|
|
585
|
+
return "awaiting-adjudication";
|
|
586
|
+
}
|
|
587
|
+
if ((logicalAgents || []).some((agent) => agent.closureState === "failed") || blockingEdge || relaunchPlan?.selectedAgentIds?.length > 0) {
|
|
588
|
+
return "blocked";
|
|
589
|
+
}
|
|
590
|
+
if (isCompletedPhase(phase) || (logicalAgents || []).every((agent) => agent.closureState === "passed")) {
|
|
591
|
+
return "passed";
|
|
592
|
+
}
|
|
593
|
+
if (executionState === "active" || (logicalAgents || []).some((agent) => agent.closureState === "evaluating")) {
|
|
594
|
+
return "evaluating";
|
|
595
|
+
}
|
|
596
|
+
return "pending";
|
|
597
|
+
}
|
|
598
|
+
|
|
456
599
|
function selectionTargetsAgent(agentId, selectionSet) {
|
|
457
600
|
return Boolean(agentId) && selectionSet.has(agentId);
|
|
458
601
|
}
|
|
@@ -682,11 +825,30 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
|
|
|
682
825
|
rerunRequest,
|
|
683
826
|
relaunchPlan,
|
|
684
827
|
});
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
wave
|
|
828
|
+
const logicalAgents = buildLogicalAgents({
|
|
829
|
+
lanePaths,
|
|
830
|
+
wave,
|
|
831
|
+
tasks,
|
|
832
|
+
dependencySnapshot,
|
|
833
|
+
capabilityAssignments,
|
|
834
|
+
selection,
|
|
835
|
+
proofRegistry,
|
|
688
836
|
phase,
|
|
689
|
-
|
|
837
|
+
coordinationState,
|
|
838
|
+
}).filter((agent) => !agentId || agent.agentId === agentId);
|
|
839
|
+
const executionState = deriveExecutionState({
|
|
840
|
+
phase,
|
|
841
|
+
activeAttempt: controlState.activeAttempt,
|
|
842
|
+
supervisor,
|
|
843
|
+
logicalAgents,
|
|
844
|
+
});
|
|
845
|
+
const controllerState = deriveControllerState({
|
|
846
|
+
executionState,
|
|
847
|
+
relaunchPlan,
|
|
848
|
+
supervisor,
|
|
849
|
+
});
|
|
850
|
+
const closureState = deriveClosureState({
|
|
851
|
+
executionState,
|
|
690
852
|
blockingEdge: buildBlockingEdge({
|
|
691
853
|
tasks,
|
|
692
854
|
capabilityAssignments,
|
|
@@ -697,16 +859,30 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
|
|
|
697
859
|
agentId,
|
|
698
860
|
phase,
|
|
699
861
|
}),
|
|
700
|
-
logicalAgents
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
862
|
+
logicalAgents,
|
|
863
|
+
relaunchPlan,
|
|
864
|
+
phase,
|
|
865
|
+
});
|
|
866
|
+
const blockingEdge = buildBlockingEdge({
|
|
867
|
+
tasks,
|
|
868
|
+
capabilityAssignments,
|
|
869
|
+
dependencySnapshot,
|
|
870
|
+
activeAttempt: controlState.activeAttempt,
|
|
871
|
+
rerunRequest,
|
|
872
|
+
relaunchPlan,
|
|
873
|
+
agentId,
|
|
874
|
+
phase,
|
|
875
|
+
});
|
|
876
|
+
return {
|
|
877
|
+
lane: lanePaths.lane,
|
|
878
|
+
wave: wave.wave,
|
|
879
|
+
phase,
|
|
880
|
+
agentId: agentId || null,
|
|
881
|
+
executionState,
|
|
882
|
+
closureState,
|
|
883
|
+
controllerState,
|
|
884
|
+
blockingEdge,
|
|
885
|
+
logicalAgents,
|
|
710
886
|
tasks,
|
|
711
887
|
helperAssignments: (capabilityAssignments || []).filter(
|
|
712
888
|
(assignment) => assignment.blocking && assignmentRelevantToAgent(assignment, agentId),
|
|
@@ -764,6 +940,9 @@ function printStatus(payload) {
|
|
|
764
940
|
? `${payload.blockingEdge.kind} ${payload.blockingEdge.id}: ${payload.blockingEdge.detail}`
|
|
765
941
|
: "none";
|
|
766
942
|
console.log(`lane=${payload.lane} wave=${payload.wave} phase=${payload.phase}`);
|
|
943
|
+
console.log(
|
|
944
|
+
`execution=${payload.executionState || "unknown"} closure=${payload.closureState || "unknown"} controller=${payload.controllerState || "unknown"}`,
|
|
945
|
+
);
|
|
767
946
|
if (payload.signals?.wave) {
|
|
768
947
|
console.log(buildSignalStatusLine(payload.signals.wave, payload));
|
|
769
948
|
}
|
|
@@ -801,7 +980,9 @@ function printStatus(payload) {
|
|
|
801
980
|
if (payload.logicalAgents.length > 0) {
|
|
802
981
|
console.log("logical-agents:");
|
|
803
982
|
for (const agent of payload.logicalAgents) {
|
|
804
|
-
console.log(
|
|
983
|
+
console.log(
|
|
984
|
+
`- ${agent.agentId} ${agent.state} execution=${agent.executionState || "unknown"} closure=${agent.closureState || "unknown"}${agent.reason ? `: ${agent.reason}` : ""}`,
|
|
985
|
+
);
|
|
805
986
|
}
|
|
806
987
|
}
|
|
807
988
|
}
|
|
@@ -991,8 +1172,8 @@ export async function runControlCli(argv) {
|
|
|
991
1172
|
printUsage();
|
|
992
1173
|
return;
|
|
993
1174
|
}
|
|
994
|
-
if (surface !== "status" && !["telemetry", "task", "rerun", "proof"].includes(surface)) {
|
|
995
|
-
throw new Error("Expected control surface: status | telemetry | task | rerun | proof");
|
|
1175
|
+
if (surface !== "status" && !["telemetry", "task", "rerun", "adjudication", "proof"].includes(surface)) {
|
|
1176
|
+
throw new Error("Expected control surface: status | telemetry | task | rerun | adjudication | proof");
|
|
996
1177
|
}
|
|
997
1178
|
if (options.runId) {
|
|
998
1179
|
const context = resolveRunContext(options.runId, options.project, options.lane);
|
|
@@ -1291,6 +1472,19 @@ export async function runControlCli(argv) {
|
|
|
1291
1472
|
throw new Error("Expected rerun operation: request | get | clear");
|
|
1292
1473
|
}
|
|
1293
1474
|
|
|
1475
|
+
if (surface === "adjudication") {
|
|
1476
|
+
if (operation !== "get") {
|
|
1477
|
+
throw new Error("Expected adjudication operation: get");
|
|
1478
|
+
}
|
|
1479
|
+
const adjudications = readWaveClosureAdjudications(lanePaths, wave.wave, options.agent || "");
|
|
1480
|
+
console.log(JSON.stringify({
|
|
1481
|
+
lane: lanePaths.lane,
|
|
1482
|
+
wave: wave.wave,
|
|
1483
|
+
adjudications,
|
|
1484
|
+
}, null, 2));
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1294
1488
|
if (surface === "proof") {
|
|
1295
1489
|
if (operation === "get") {
|
|
1296
1490
|
const registry = readWaveProofRegistry(lanePaths, wave.wave);
|