@chllming/wave-orchestration 0.8.5 → 0.8.7
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 +46 -0
- package/README.md +14 -9
- package/docs/README.md +3 -1
- package/docs/context7/bundles.json +19 -20
- package/docs/context7/planner-agent/README.md +4 -1
- package/docs/guides/author-and-run-waves.md +4 -1
- package/docs/guides/planner.md +3 -1
- package/docs/guides/signal-wrappers.md +165 -0
- package/docs/guides/terminal-surfaces.md +15 -0
- package/docs/plans/context7-wave-orchestrator.md +24 -7
- package/docs/plans/current-state.md +7 -3
- package/docs/plans/end-state-architecture.md +16 -4
- 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 +179 -72
- package/docs/plans/wave-orchestrator.md +11 -5
- package/docs/reference/cli-reference.md +21 -4
- package/docs/reference/coordination-and-closure.md +26 -5
- package/docs/reference/live-proof-waves.md +9 -0
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/runtime-config/README.md +9 -3
- package/docs/reference/sample-waves.md +5 -5
- package/docs/reference/skills.md +9 -1
- package/docs/reference/wave-control.md +18 -0
- package/docs/reference/wave-planning-lessons.md +7 -1
- package/docs/research/coordination-failure-review.md +6 -6
- package/package.json +1 -1
- package/releases/manifest.json +38 -0
- package/scripts/context7-api-check.sh +57 -13
- package/scripts/wave-orchestrator/agent-state.mjs +42 -0
- package/scripts/wave-orchestrator/autonomous.mjs +42 -6
- package/scripts/wave-orchestrator/clarification-triage.mjs +4 -3
- package/scripts/wave-orchestrator/control-cli.mjs +145 -11
- package/scripts/wave-orchestrator/control-plane.mjs +12 -1
- package/scripts/wave-orchestrator/coordination-store.mjs +124 -4
- package/scripts/wave-orchestrator/coordination.mjs +35 -0
- package/scripts/wave-orchestrator/executors.mjs +11 -6
- package/scripts/wave-orchestrator/gate-engine.mjs +5 -5
- package/scripts/wave-orchestrator/install.mjs +2 -0
- package/scripts/wave-orchestrator/launcher-runtime.mjs +12 -1
- package/scripts/wave-orchestrator/launcher.mjs +236 -0
- package/scripts/wave-orchestrator/ledger.mjs +14 -12
- package/scripts/wave-orchestrator/reducer-snapshot.mjs +8 -6
- package/scripts/wave-orchestrator/retry-engine.mjs +19 -11
- package/scripts/wave-orchestrator/routing-state.mjs +50 -3
- package/scripts/wave-orchestrator/session-supervisor.mjs +119 -10
- package/scripts/wave-orchestrator/shared.mjs +1 -0
- package/scripts/wave-orchestrator/signals.mjs +681 -0
- package/scripts/wave-orchestrator/task-entity.mjs +4 -4
- package/scripts/wave-orchestrator/terminals.mjs +14 -14
- package/scripts/wave-orchestrator/wave-control-schema.mjs +2 -0
- package/scripts/wave-orchestrator/wave-files.mjs +15 -21
- package/scripts/wave-orchestrator/wave-state-reducer.mjs +72 -5
- package/scripts/wave-status.sh +200 -0
- package/scripts/wave-watch.sh +200 -0
- package/skills/README.md +3 -0
- package/skills/signal-hygiene/SKILL.md +51 -0
- package/skills/signal-hygiene/skill.json +20 -0
|
@@ -27,8 +27,13 @@ import {
|
|
|
27
27
|
maybeAnnouncePackageUpdate,
|
|
28
28
|
WAVE_SUPPRESS_UPDATE_NOTICE_ENV,
|
|
29
29
|
} from "./package-update-notice.mjs";
|
|
30
|
+
import { buildTaskSnapshots } from "./control-plane.mjs";
|
|
31
|
+
import { readWaveHumanFeedbackRequests } from "./coordination.mjs";
|
|
30
32
|
import { readRunState } from "./wave-files.mjs";
|
|
31
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
readDependencyTickets,
|
|
35
|
+
readMaterializedCoordinationState,
|
|
36
|
+
} from "./coordination-store.mjs";
|
|
32
37
|
import { readWaveLedger } from "./ledger.mjs";
|
|
33
38
|
|
|
34
39
|
const AUTONOMOUS_EXECUTOR_MODES = SUPPORTED_EXECUTOR_MODES.filter((mode) => mode !== "local");
|
|
@@ -249,7 +254,38 @@ function requiredInboundDependenciesOpen(lanePaths, lane) {
|
|
|
249
254
|
});
|
|
250
255
|
}
|
|
251
256
|
|
|
252
|
-
function
|
|
257
|
+
function liveBlockingHumanItemsForWave(lanePaths, lane, wave) {
|
|
258
|
+
if (!lanePaths?.coordinationDir || !lanePaths?.feedbackRequestsDir) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const coordinationState = readMaterializedCoordinationState(
|
|
262
|
+
path.join(lanePaths.coordinationDir, `wave-${wave}.jsonl`),
|
|
263
|
+
);
|
|
264
|
+
const feedbackRequests = readWaveHumanFeedbackRequests({
|
|
265
|
+
feedbackRequestsDir: lanePaths.feedbackRequestsDir,
|
|
266
|
+
lane,
|
|
267
|
+
waveNumber: wave,
|
|
268
|
+
agentIds: [],
|
|
269
|
+
orchestratorId: "",
|
|
270
|
+
});
|
|
271
|
+
return buildTaskSnapshots({
|
|
272
|
+
coordinationState,
|
|
273
|
+
feedbackRequests,
|
|
274
|
+
})
|
|
275
|
+
.filter(
|
|
276
|
+
(task) =>
|
|
277
|
+
["human-input", "escalation"].includes(task.taskType) &&
|
|
278
|
+
task.blocking !== false &&
|
|
279
|
+
["open", "working", "input-required"].includes(task.state),
|
|
280
|
+
)
|
|
281
|
+
.map((task) => task.taskId);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function pendingHumanItemsForWave(lanePaths, lane, wave) {
|
|
285
|
+
const liveItems = liveBlockingHumanItemsForWave(lanePaths, lane, wave);
|
|
286
|
+
if (Array.isArray(liveItems)) {
|
|
287
|
+
return liveItems;
|
|
288
|
+
}
|
|
253
289
|
const existingLedger = readWaveLedger(path.join(lanePaths.ledgerDir, `wave-${wave}.json`));
|
|
254
290
|
return [
|
|
255
291
|
...(existingLedger?.humanFeedback || []),
|
|
@@ -257,7 +293,7 @@ function pendingHumanItemsForWave(lanePaths, wave) {
|
|
|
257
293
|
];
|
|
258
294
|
}
|
|
259
295
|
|
|
260
|
-
function pendingHumanItemsForLane(lanePaths) {
|
|
296
|
+
function pendingHumanItemsForLane(lanePaths, lane) {
|
|
261
297
|
if (!fs.existsSync(lanePaths.ledgerDir)) {
|
|
262
298
|
return [];
|
|
263
299
|
}
|
|
@@ -271,7 +307,7 @@ function pendingHumanItemsForLane(lanePaths) {
|
|
|
271
307
|
.filter((item) => Number.isFinite(item.wave))
|
|
272
308
|
.sort((left, right) => left.wave - right.wave)
|
|
273
309
|
.flatMap((item) =>
|
|
274
|
-
pendingHumanItemsForWave(lanePaths, item.wave).map((id) => ({
|
|
310
|
+
pendingHumanItemsForWave(lanePaths, lane, item.wave).map((id) => ({
|
|
275
311
|
wave: item.wave,
|
|
276
312
|
id,
|
|
277
313
|
})),
|
|
@@ -292,7 +328,7 @@ export function readAutonomousBarrier(lanePaths, lane, wave = null) {
|
|
|
292
328
|
};
|
|
293
329
|
}
|
|
294
330
|
if (wave === null) {
|
|
295
|
-
const pendingHumanEntries = pendingHumanItemsForLane(lanePaths);
|
|
331
|
+
const pendingHumanEntries = pendingHumanItemsForLane(lanePaths, lane);
|
|
296
332
|
if (pendingHumanEntries.length > 0) {
|
|
297
333
|
return {
|
|
298
334
|
kind: "human-input",
|
|
@@ -303,7 +339,7 @@ export function readAutonomousBarrier(lanePaths, lane, wave = null) {
|
|
|
303
339
|
}
|
|
304
340
|
return null;
|
|
305
341
|
}
|
|
306
|
-
const pendingHumanItems = pendingHumanItemsForWave(lanePaths, wave);
|
|
342
|
+
const pendingHumanItems = pendingHumanItemsForWave(lanePaths, lane, wave);
|
|
307
343
|
if (pendingHumanItems.length > 0) {
|
|
308
344
|
return {
|
|
309
345
|
kind: "human-input",
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
appendCoordinationRecord,
|
|
5
5
|
clarificationClosureCondition,
|
|
6
6
|
clarificationLinkedRequests,
|
|
7
|
+
coordinationRecordBlocksWave,
|
|
7
8
|
isOpenCoordinationStatus,
|
|
8
9
|
readMaterializedCoordinationState,
|
|
9
10
|
} from "./coordination-store.mjs";
|
|
@@ -467,14 +468,14 @@ export function triageClarificationRequests({
|
|
|
467
468
|
ensureDirectory(lanePaths.feedbackTriageDir);
|
|
468
469
|
const triagePath = triageLogPath(lanePaths, wave.wave);
|
|
469
470
|
const openClarifications = (coordinationState?.clarifications || []).filter((record) =>
|
|
470
|
-
|
|
471
|
+
coordinationRecordBlocksWave(record),
|
|
471
472
|
);
|
|
472
473
|
let changed = false;
|
|
473
474
|
|
|
474
475
|
for (const record of openClarifications) {
|
|
475
476
|
const linkedRequests = clarificationLinkedRequests(coordinationState, record.id);
|
|
476
477
|
const openLinkedRequests = linkedRequests.filter((entry) =>
|
|
477
|
-
|
|
478
|
+
coordinationRecordBlocksWave(entry),
|
|
478
479
|
);
|
|
479
480
|
const openAckPendingLinkedRequests = openLinkedRequests.filter(
|
|
480
481
|
(entry) => entry.status === "open",
|
|
@@ -487,7 +488,7 @@ export function triageClarificationRequests({
|
|
|
487
488
|
const openEscalations = (coordinationState?.humanEscalations || []).filter(
|
|
488
489
|
(entry) =>
|
|
489
490
|
entry.closureCondition === clarificationClosureCondition(record.id) &&
|
|
490
|
-
|
|
491
|
+
coordinationRecordBlocksWave(entry),
|
|
491
492
|
);
|
|
492
493
|
if (resolvedLinkedRequest || resolvedEscalation) {
|
|
493
494
|
if (openEscalations.length > 0) {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
appendCoordinationRecord,
|
|
5
|
+
clarificationClosureCondition,
|
|
6
|
+
clarificationLinkedRequests,
|
|
7
|
+
isOpenCoordinationStatus,
|
|
8
|
+
readMaterializedCoordinationState,
|
|
9
|
+
updateSeedRecords,
|
|
10
|
+
} from "./coordination-store.mjs";
|
|
4
11
|
import { answerFeedbackRequest, createFeedbackRequest } from "./feedback.mjs";
|
|
5
12
|
import { readWaveHumanFeedbackRequests } from "./coordination.mjs";
|
|
6
13
|
import { readWaveLedger } from "./ledger.mjs";
|
|
@@ -18,6 +25,7 @@ import {
|
|
|
18
25
|
REPO_ROOT,
|
|
19
26
|
sanitizeAdhocRunId,
|
|
20
27
|
sanitizeLaneName,
|
|
28
|
+
toIsoTimestamp,
|
|
21
29
|
} from "./shared.mjs";
|
|
22
30
|
import {
|
|
23
31
|
appendWaveControlEvent,
|
|
@@ -36,6 +44,10 @@ import { readWaveRelaunchPlanSnapshot, readWaveRetryOverride, resolveRetryOverri
|
|
|
36
44
|
import { flushWaveControlQueue, readWaveControlQueueState } from "./wave-control-client.mjs";
|
|
37
45
|
import { readAgentExecutionSummary, validateImplementationSummary } from "./agent-state.mjs";
|
|
38
46
|
import { isContEvalReportOnlyAgent, isSecurityReviewAgent } from "./role-helpers.mjs";
|
|
47
|
+
import {
|
|
48
|
+
buildSignalStatusLine,
|
|
49
|
+
syncWaveSignalProjections,
|
|
50
|
+
} from "./signals.mjs";
|
|
39
51
|
|
|
40
52
|
function printUsage() {
|
|
41
53
|
console.log(`Usage:
|
|
@@ -46,7 +58,7 @@ function printUsage() {
|
|
|
46
58
|
wave control task create --lane <lane> --wave <n> --agent <id> --kind <request|blocker|clarification|handoff|evidence|claim|decision|human-input> --summary <text> [options]
|
|
47
59
|
wave control task list --lane <lane> --wave <n> [--agent <id>] [--json]
|
|
48
60
|
wave control task get --lane <lane> --wave <n> --id <task-id> [--json]
|
|
49
|
-
wave control task act <start|resolve|dismiss|cancel|reassign|answer|escalate> --lane <lane> --wave <n> --id <task-id> [options]
|
|
61
|
+
wave control task act <start|resolve|dismiss|cancel|reassign|answer|escalate|defer|mark-advisory|mark-stale|resolve-policy> --lane <lane> --wave <n> --id <task-id> [options]
|
|
50
62
|
|
|
51
63
|
wave control rerun request --lane <lane> --wave <n> [--agent <id> ...] [--resume-cursor <cursor>] [--reuse-attempt <id> ...] [--reuse-proof <id> ...] [--reuse-derived-summaries <true|false>] [--invalidate-component <id> ...] [--clear-reuse <id> ...] [--preserve-reuse <id> ...] [--requested-by <name>] [--reason <text>] [--json]
|
|
52
64
|
wave control rerun get --lane <lane> --wave <n> [--json]
|
|
@@ -90,6 +102,8 @@ function parseArgs(argv) {
|
|
|
90
102
|
detail: "",
|
|
91
103
|
targets: [],
|
|
92
104
|
priority: "normal",
|
|
105
|
+
blocking: null,
|
|
106
|
+
blockerSeverity: "",
|
|
93
107
|
dependsOn: [],
|
|
94
108
|
artifactRefs: [],
|
|
95
109
|
status: "open",
|
|
@@ -151,6 +165,10 @@ function parseArgs(argv) {
|
|
|
151
165
|
options.targets.push(String(args[++i] || "").trim());
|
|
152
166
|
} else if (arg === "--priority") {
|
|
153
167
|
options.priority = String(args[++i] || "").trim();
|
|
168
|
+
} else if (arg === "--blocking") {
|
|
169
|
+
options.blocking = normalizeBooleanish(args[++i], true);
|
|
170
|
+
} else if (arg === "--severity") {
|
|
171
|
+
options.blockerSeverity = String(args[++i] || "").trim();
|
|
154
172
|
} else if (arg === "--depends-on") {
|
|
155
173
|
options.dependsOn.push(String(args[++i] || "").trim());
|
|
156
174
|
} else if (arg === "--artifact") {
|
|
@@ -260,7 +278,10 @@ const BLOCKING_TASK_TYPES = new Set([
|
|
|
260
278
|
]);
|
|
261
279
|
|
|
262
280
|
function taskBlocksAgent(task) {
|
|
263
|
-
return
|
|
281
|
+
return (
|
|
282
|
+
BLOCKING_TASK_TYPES.has(String(task?.taskType || "").trim().toLowerCase()) &&
|
|
283
|
+
task?.blocking !== false
|
|
284
|
+
);
|
|
264
285
|
}
|
|
265
286
|
|
|
266
287
|
function assignmentRelevantToAgent(assignment, agentId = "") {
|
|
@@ -458,7 +479,9 @@ function buildBlockingEdge({
|
|
|
458
479
|
selectionTargetsAgent(task.assigneeAgentId, attemptSelection)
|
|
459
480
|
);
|
|
460
481
|
});
|
|
461
|
-
const pendingHuman = scopedTasks.find(
|
|
482
|
+
const pendingHuman = scopedTasks.find(
|
|
483
|
+
(task) => task.state === "input-required" && task.blocking !== false,
|
|
484
|
+
);
|
|
462
485
|
if (pendingHuman) {
|
|
463
486
|
return {
|
|
464
487
|
kind: "human-input",
|
|
@@ -468,7 +491,10 @@ function buildBlockingEdge({
|
|
|
468
491
|
};
|
|
469
492
|
}
|
|
470
493
|
const escalation = scopedTasks.find(
|
|
471
|
-
(task) =>
|
|
494
|
+
(task) =>
|
|
495
|
+
task.taskType === "escalation" &&
|
|
496
|
+
task.blocking !== false &&
|
|
497
|
+
["open", "working"].includes(task.state),
|
|
472
498
|
);
|
|
473
499
|
if (escalation) {
|
|
474
500
|
return {
|
|
@@ -479,7 +505,10 @@ function buildBlockingEdge({
|
|
|
479
505
|
};
|
|
480
506
|
}
|
|
481
507
|
const clarification = scopedTasks.find(
|
|
482
|
-
(task) =>
|
|
508
|
+
(task) =>
|
|
509
|
+
task.taskType === "clarification" &&
|
|
510
|
+
task.blocking !== false &&
|
|
511
|
+
["open", "working"].includes(task.state),
|
|
483
512
|
);
|
|
484
513
|
if (clarification) {
|
|
485
514
|
return {
|
|
@@ -560,7 +589,10 @@ function buildBlockingEdge({
|
|
|
560
589
|
};
|
|
561
590
|
}
|
|
562
591
|
const blocker = scopedTasks.find(
|
|
563
|
-
(task) =>
|
|
592
|
+
(task) =>
|
|
593
|
+
task.taskType === "blocker" &&
|
|
594
|
+
task.blocking !== false &&
|
|
595
|
+
["open", "working"].includes(task.state),
|
|
564
596
|
);
|
|
565
597
|
if (blocker) {
|
|
566
598
|
return {
|
|
@@ -571,7 +603,10 @@ function buildBlockingEdge({
|
|
|
571
603
|
};
|
|
572
604
|
}
|
|
573
605
|
const request = scopedTasks.find(
|
|
574
|
-
(task) =>
|
|
606
|
+
(task) =>
|
|
607
|
+
task.taskType === "request" &&
|
|
608
|
+
task.blocking !== false &&
|
|
609
|
+
["open", "working"].includes(task.state),
|
|
575
610
|
);
|
|
576
611
|
if (request) {
|
|
577
612
|
return {
|
|
@@ -676,6 +711,7 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
|
|
|
676
711
|
proofBundles: (proofRegistry?.entries || []).filter(
|
|
677
712
|
(entry) => !agentId || entry.agentId === agentId,
|
|
678
713
|
),
|
|
714
|
+
feedbackRequests,
|
|
679
715
|
selectionSource: selection.source,
|
|
680
716
|
rerunRequest,
|
|
681
717
|
relaunchPlan,
|
|
@@ -690,6 +726,7 @@ function ensureWaveStateDirs(lanePaths) {
|
|
|
690
726
|
ensureDirectory(lanePaths.controlPlaneDir);
|
|
691
727
|
ensureDirectory(lanePaths.assignmentsDir);
|
|
692
728
|
ensureDirectory(lanePaths.inboxesDir);
|
|
729
|
+
ensureDirectory(lanePaths.signalsDir);
|
|
693
730
|
ensureDirectory(lanePaths.messageboardsDir);
|
|
694
731
|
ensureDirectory(lanePaths.docsQueueDir);
|
|
695
732
|
ensureDirectory(lanePaths.ledgerDir);
|
|
@@ -714,6 +751,9 @@ function printStatus(payload) {
|
|
|
714
751
|
? `${payload.blockingEdge.kind} ${payload.blockingEdge.id}: ${payload.blockingEdge.detail}`
|
|
715
752
|
: "none";
|
|
716
753
|
console.log(`lane=${payload.lane} wave=${payload.wave} phase=${payload.phase}`);
|
|
754
|
+
if (payload.signals?.wave) {
|
|
755
|
+
console.log(buildSignalStatusLine(payload.signals.wave, payload));
|
|
756
|
+
}
|
|
717
757
|
console.log(`blocking=${blocking}`);
|
|
718
758
|
if (payload.nextTimer) {
|
|
719
759
|
console.log(`next-timer=${payload.nextTimer.kind} ${payload.nextTimer.taskId} at ${payload.nextTimer.at}`);
|
|
@@ -729,7 +769,9 @@ function printStatus(payload) {
|
|
|
729
769
|
function appendCoordinationStatusUpdate(logPath, record, status, options = {}) {
|
|
730
770
|
return appendCoordinationRecord(logPath, {
|
|
731
771
|
...record,
|
|
772
|
+
...(options.patch || {}),
|
|
732
773
|
status,
|
|
774
|
+
updatedAt: options.updatedAt || toIsoTimestamp(),
|
|
733
775
|
summary: options.summary || record.summary,
|
|
734
776
|
detail: options.detail || record.detail,
|
|
735
777
|
source: options.source || "operator",
|
|
@@ -814,6 +856,73 @@ function appendTaskCoordinationEvent(logPath, lanePaths, wave, record, action, o
|
|
|
814
856
|
source: "operator",
|
|
815
857
|
});
|
|
816
858
|
}
|
|
859
|
+
if (action === "defer") {
|
|
860
|
+
return appendCoordinationStatusUpdate(logPath, record, record.status, {
|
|
861
|
+
detail:
|
|
862
|
+
options.detail ||
|
|
863
|
+
`${record.summary || record.id} deferred by operator; keep visible but do not block wave progression.`,
|
|
864
|
+
patch: {
|
|
865
|
+
blocking: false,
|
|
866
|
+
blockerSeverity: "soft",
|
|
867
|
+
},
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
if (action === "mark-advisory") {
|
|
871
|
+
return appendCoordinationStatusUpdate(logPath, record, record.status, {
|
|
872
|
+
detail:
|
|
873
|
+
options.detail ||
|
|
874
|
+
`${record.summary || record.id} marked advisory by operator; keep visible without blocking closure.`,
|
|
875
|
+
patch: {
|
|
876
|
+
blocking: false,
|
|
877
|
+
blockerSeverity: "advisory",
|
|
878
|
+
},
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
if (action === "mark-stale") {
|
|
882
|
+
return appendCoordinationStatusUpdate(logPath, record, record.status, {
|
|
883
|
+
detail:
|
|
884
|
+
options.detail ||
|
|
885
|
+
`${record.summary || record.id} marked stale by operator; historical context preserved without blocking.`,
|
|
886
|
+
patch: {
|
|
887
|
+
blocking: false,
|
|
888
|
+
blockerSeverity: "stale",
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
if (action === "resolve-policy") {
|
|
893
|
+
const resolvedRecord = appendCoordinationStatusUpdate(logPath, record, "resolved", {
|
|
894
|
+
detail: options.detail || `Resolved by operator policy: ${record.summary || record.id}.`,
|
|
895
|
+
patch: {
|
|
896
|
+
blocking: false,
|
|
897
|
+
blockerSeverity: "advisory",
|
|
898
|
+
},
|
|
899
|
+
});
|
|
900
|
+
const policyRecord = appendCoordinationRecord(logPath, {
|
|
901
|
+
id: `policy-${record.id}`,
|
|
902
|
+
lane: lanePaths.lane,
|
|
903
|
+
wave: wave.wave,
|
|
904
|
+
agentId: options.agent || "operator",
|
|
905
|
+
kind: "resolved-by-policy",
|
|
906
|
+
targets: record.targets,
|
|
907
|
+
priority: record.priority,
|
|
908
|
+
artifactRefs: record.artifactRefs,
|
|
909
|
+
dependsOn: Array.from(new Set([record.id, ...(record.dependsOn || [])])),
|
|
910
|
+
closureCondition:
|
|
911
|
+
record.kind === "clarification-request"
|
|
912
|
+
? clarificationClosureCondition(record.id)
|
|
913
|
+
: record.closureCondition || "",
|
|
914
|
+
summary: record.summary,
|
|
915
|
+
detail: options.detail || `Operator resolved ${record.id} by policy.`,
|
|
916
|
+
status: "resolved",
|
|
917
|
+
source: "operator",
|
|
918
|
+
blocking: false,
|
|
919
|
+
blockerSeverity: "advisory",
|
|
920
|
+
});
|
|
921
|
+
return {
|
|
922
|
+
resolvedRecord,
|
|
923
|
+
policyRecord,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
817
926
|
throw new Error(`Unsupported task action: ${action}`);
|
|
818
927
|
}
|
|
819
928
|
|
|
@@ -901,6 +1010,16 @@ export async function runControlCli(argv) {
|
|
|
901
1010
|
wave,
|
|
902
1011
|
agentId: options.agent || "",
|
|
903
1012
|
});
|
|
1013
|
+
const signalSync = syncWaveSignalProjections({
|
|
1014
|
+
lanePaths,
|
|
1015
|
+
wave,
|
|
1016
|
+
statusPayload: payload,
|
|
1017
|
+
includeResident: Boolean(options.orchestratorId),
|
|
1018
|
+
});
|
|
1019
|
+
payload.signals = {
|
|
1020
|
+
wave: signalSync.wave?.snapshot || null,
|
|
1021
|
+
agents: (signalSync.agents || []).map((entry) => entry.snapshot),
|
|
1022
|
+
};
|
|
904
1023
|
if (options.json) {
|
|
905
1024
|
console.log(JSON.stringify(payload, null, 2));
|
|
906
1025
|
} else {
|
|
@@ -956,6 +1075,8 @@ export async function runControlCli(argv) {
|
|
|
956
1075
|
artifactRefs: options.artifactRefs,
|
|
957
1076
|
status: options.status,
|
|
958
1077
|
source: "operator",
|
|
1078
|
+
...(options.blocking !== null ? { blocking: options.blocking } : {}),
|
|
1079
|
+
...(options.blockerSeverity ? { blockerSeverity: options.blockerSeverity } : {}),
|
|
959
1080
|
});
|
|
960
1081
|
console.log(JSON.stringify(record, null, 2));
|
|
961
1082
|
return;
|
|
@@ -1048,14 +1169,27 @@ export async function runControlCli(argv) {
|
|
|
1048
1169
|
throw new Error(`Task not found: ${options.id}`);
|
|
1049
1170
|
}
|
|
1050
1171
|
const updated = appendTaskCoordinationEvent(logPath, lanePaths, wave, record, action, options);
|
|
1051
|
-
if (record.kind === "clarification-request" && ["resolve", "dismiss"].includes(action)) {
|
|
1172
|
+
if (record.kind === "clarification-request" && ["resolve", "dismiss", "resolve-policy"].includes(action)) {
|
|
1052
1173
|
const nextStatus = action === "resolve" ? "resolved" : "cancelled";
|
|
1174
|
+
const linkedStatus = action === "resolve-policy" ? "resolved" : nextStatus;
|
|
1053
1175
|
for (const linked of clarificationLinkedRequests(coordinationState, record.id).filter((entry) =>
|
|
1054
1176
|
isOpenCoordinationStatus(entry.status),
|
|
1055
1177
|
)) {
|
|
1056
|
-
appendCoordinationStatusUpdate(logPath, linked,
|
|
1057
|
-
detail:
|
|
1178
|
+
appendCoordinationStatusUpdate(logPath, linked, linkedStatus, {
|
|
1179
|
+
detail:
|
|
1180
|
+
action === "resolve"
|
|
1181
|
+
? `Resolved via clarification ${record.id}.`
|
|
1182
|
+
: action === "resolve-policy"
|
|
1183
|
+
? `Resolved by policy via clarification ${record.id}.`
|
|
1184
|
+
: `Cancelled via clarification ${record.id}.`,
|
|
1058
1185
|
summary: linked.summary,
|
|
1186
|
+
patch:
|
|
1187
|
+
action === "resolve-policy"
|
|
1188
|
+
? {
|
|
1189
|
+
blocking: false,
|
|
1190
|
+
blockerSeverity: "advisory",
|
|
1191
|
+
}
|
|
1192
|
+
: undefined,
|
|
1059
1193
|
});
|
|
1060
1194
|
}
|
|
1061
1195
|
}
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
CLARIFICATION_CLOSURE_PREFIX,
|
|
13
13
|
buildCoordinationResponseMetrics,
|
|
14
|
+
coordinationBlockerSeverity,
|
|
15
|
+
coordinationRecordBlocksWave,
|
|
14
16
|
} from "./coordination-store.mjs";
|
|
15
17
|
import {
|
|
16
18
|
DEFAULT_COORDINATION_ACK_TIMEOUT_MS,
|
|
@@ -586,6 +588,8 @@ export function buildTaskSnapshots({
|
|
|
586
588
|
const metrics = responseMetrics.recordMetricsById.get(record.id) || {};
|
|
587
589
|
const feedbackRequest = feedbackById.get(record.id) || null;
|
|
588
590
|
const taskState = taskStateForCoordinationRecord(record, feedbackRequest);
|
|
591
|
+
const blocking = coordinationRecordBlocksWave(record);
|
|
592
|
+
const blockerSeverity = coordinationBlockerSeverity(record);
|
|
589
593
|
tasks.push({
|
|
590
594
|
taskId: record.id,
|
|
591
595
|
sourceRecordId: record.id,
|
|
@@ -598,6 +602,8 @@ export function buildTaskSnapshots({
|
|
|
598
602
|
assigneeAgentId: firstTargetAgentId(record),
|
|
599
603
|
leaseOwnerAgentId:
|
|
600
604
|
["acknowledged", "in_progress"].includes(record.status) ? firstTargetAgentId(record) : null,
|
|
605
|
+
blocking,
|
|
606
|
+
blockerSeverity,
|
|
601
607
|
needsHuman:
|
|
602
608
|
record.kind === "human-feedback" ||
|
|
603
609
|
feedbackRequest?.status === "pending" ||
|
|
@@ -627,7 +633,7 @@ export function buildTaskSnapshots({
|
|
|
627
633
|
? feedbackRequest?.updatedAt || record.updatedAt || record.createdAt
|
|
628
634
|
: null,
|
|
629
635
|
overdueAck: metrics.overdueAck === true,
|
|
630
|
-
stale: metrics.staleClarification === true,
|
|
636
|
+
stale: metrics.staleClarification === true || blockerSeverity === "stale",
|
|
631
637
|
feedbackRequestId: feedbackRequest?.id || null,
|
|
632
638
|
humanResponse: feedbackRequest?.responseText || null,
|
|
633
639
|
humanOperator: feedbackRequest?.responseOperator || null,
|
|
@@ -648,6 +654,8 @@ export function buildTaskSnapshots({
|
|
|
648
654
|
ownerAgentId: request.agentId || null,
|
|
649
655
|
assigneeAgentId: request.agentId || null,
|
|
650
656
|
leaseOwnerAgentId: null,
|
|
657
|
+
blocking: true,
|
|
658
|
+
blockerSeverity: "hard",
|
|
651
659
|
needsHuman: request.status !== "answered",
|
|
652
660
|
dependsOn: [],
|
|
653
661
|
evidenceRefs: [],
|
|
@@ -676,6 +684,9 @@ export function buildTaskSnapshots({
|
|
|
676
684
|
export function nextTaskDeadline(tasks) {
|
|
677
685
|
const candidates = [];
|
|
678
686
|
for (const task of tasks || []) {
|
|
687
|
+
if (task?.blocking === false) {
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
679
690
|
for (const [kind, value] of [
|
|
680
691
|
["ack", task.ackDeadlineAt],
|
|
681
692
|
["resolve", task.resolveDeadlineAt],
|
|
@@ -43,7 +43,17 @@ export const COORDINATION_STATUS_VALUES = [
|
|
|
43
43
|
|
|
44
44
|
export const COORDINATION_PRIORITY_VALUES = ["low", "normal", "high", "urgent"];
|
|
45
45
|
export const COORDINATION_CONFIDENCE_VALUES = ["low", "medium", "high"];
|
|
46
|
+
export const COORDINATION_BLOCKER_SEVERITY_VALUES = [
|
|
47
|
+
"hard",
|
|
48
|
+
"soft",
|
|
49
|
+
"stale",
|
|
50
|
+
"advisory",
|
|
51
|
+
"proof-critical",
|
|
52
|
+
"closure-critical",
|
|
53
|
+
];
|
|
46
54
|
const OPEN_COORDINATION_STATUSES = new Set(["open", "acknowledged", "in_progress"]);
|
|
55
|
+
const NON_BLOCKING_BLOCKER_SEVERITIES = new Set(["stale", "advisory"]);
|
|
56
|
+
const HARD_BLOCKER_SEVERITIES = new Set(["hard", "proof-critical", "closure-critical"]);
|
|
47
57
|
export const CLARIFICATION_CLOSURE_PREFIX = "clarification:";
|
|
48
58
|
|
|
49
59
|
function normalizeString(value, fallback = "") {
|
|
@@ -65,6 +75,23 @@ function normalizeStringArray(values) {
|
|
|
65
75
|
);
|
|
66
76
|
}
|
|
67
77
|
|
|
78
|
+
function normalizeOptionalBoolean(value, fallback = null) {
|
|
79
|
+
if (value === undefined || value === null || value === "") {
|
|
80
|
+
return fallback;
|
|
81
|
+
}
|
|
82
|
+
if (typeof value === "boolean") {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
const normalized = String(value).trim().toLowerCase();
|
|
86
|
+
if (["1", "true", "yes", "y", "on"].includes(normalized)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (["0", "false", "no", "n", "off"].includes(normalized)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return Boolean(value);
|
|
93
|
+
}
|
|
94
|
+
|
|
68
95
|
function validateEnum(value, allowed, label) {
|
|
69
96
|
if (!allowed.includes(value)) {
|
|
70
97
|
throw new Error(`${label} must be one of ${allowed.join(", ")} (got: ${value || "empty"})`);
|
|
@@ -75,6 +102,74 @@ function stableId(prefix) {
|
|
|
75
102
|
return `${prefix}-${crypto.randomBytes(4).toString("hex")}`;
|
|
76
103
|
}
|
|
77
104
|
|
|
105
|
+
function defaultBlockingForKind(kind) {
|
|
106
|
+
return ["request", "blocker", "clarification-request", "human-escalation", "human-feedback"].includes(
|
|
107
|
+
kind,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function defaultSeverityForRecord(kind, priority, blocking) {
|
|
112
|
+
if (blocking === false) {
|
|
113
|
+
return "advisory";
|
|
114
|
+
}
|
|
115
|
+
if (kind === "human-escalation" || kind === "human-feedback") {
|
|
116
|
+
return "hard";
|
|
117
|
+
}
|
|
118
|
+
if (kind === "request" || kind === "clarification-request") {
|
|
119
|
+
return "closure-critical";
|
|
120
|
+
}
|
|
121
|
+
if (kind === "blocker") {
|
|
122
|
+
return ["high", "urgent"].includes(priority) ? "hard" : "soft";
|
|
123
|
+
}
|
|
124
|
+
return "advisory";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeBlockerSeverity(value, defaults = {}) {
|
|
128
|
+
const normalized = normalizeString(value ?? defaults.blockerSeverity, "").toLowerCase();
|
|
129
|
+
return normalized || null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function coordinationBlockerSeverity(record) {
|
|
133
|
+
if (!record || typeof record !== "object") {
|
|
134
|
+
return "advisory";
|
|
135
|
+
}
|
|
136
|
+
const blocking =
|
|
137
|
+
record.blocking === undefined || record.blocking === null
|
|
138
|
+
? defaultBlockingForKind(String(record.kind || "").trim().toLowerCase())
|
|
139
|
+
: record.blocking !== false;
|
|
140
|
+
const explicitSeverity = normalizeBlockerSeverity(record.blockerSeverity);
|
|
141
|
+
const derivedSeverity =
|
|
142
|
+
explicitSeverity ||
|
|
143
|
+
defaultSeverityForRecord(
|
|
144
|
+
String(record.kind || "").trim().toLowerCase(),
|
|
145
|
+
String(record.priority || "normal").trim().toLowerCase(),
|
|
146
|
+
blocking,
|
|
147
|
+
);
|
|
148
|
+
if (COORDINATION_BLOCKER_SEVERITY_VALUES.includes(derivedSeverity)) {
|
|
149
|
+
return derivedSeverity;
|
|
150
|
+
}
|
|
151
|
+
return defaultSeverityForRecord(
|
|
152
|
+
String(record.kind || "").trim().toLowerCase(),
|
|
153
|
+
String(record.priority || "normal").trim().toLowerCase(),
|
|
154
|
+
blocking,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function coordinationRecordBlocksWave(record) {
|
|
159
|
+
if (!record || !isOpenCoordinationStatus(record.status)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (record.blocking === false) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return !NON_BLOCKING_BLOCKER_SEVERITIES.has(coordinationBlockerSeverity(record));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function coordinationRecordIsHardBlocker(record) {
|
|
169
|
+
return coordinationRecordBlocksWave(record) &&
|
|
170
|
+
HARD_BLOCKER_SEVERITIES.has(coordinationBlockerSeverity(record));
|
|
171
|
+
}
|
|
172
|
+
|
|
78
173
|
export function normalizeCoordinationRecord(rawRecord, defaults = {}) {
|
|
79
174
|
if (!rawRecord || typeof rawRecord !== "object" || Array.isArray(rawRecord)) {
|
|
80
175
|
throw new Error("Coordination record must be an object");
|
|
@@ -93,12 +188,25 @@ export function normalizeCoordinationRecord(rawRecord, defaults = {}) {
|
|
|
93
188
|
).toLowerCase();
|
|
94
189
|
const priority = normalizeString(rawRecord.priority || defaults.priority || "normal").toLowerCase();
|
|
95
190
|
const confidence = normalizeString(rawRecord.confidence || defaults.confidence || "medium").toLowerCase();
|
|
191
|
+
const explicitBlocking = normalizeOptionalBoolean(
|
|
192
|
+
rawRecord.blocking,
|
|
193
|
+
normalizeOptionalBoolean(defaults.blocking, null),
|
|
194
|
+
);
|
|
195
|
+
const blocking = explicitBlocking ?? defaultBlockingForKind(kind);
|
|
196
|
+
const blockerSeverity =
|
|
197
|
+
normalizeBlockerSeverity(rawRecord.blockerSeverity, defaults) ||
|
|
198
|
+
defaultSeverityForRecord(kind, priority, blocking);
|
|
96
199
|
const createdAt = normalizeString(rawRecord.createdAt || defaults.createdAt || now);
|
|
97
200
|
const updatedAt = normalizeString(rawRecord.updatedAt || defaults.updatedAt || createdAt);
|
|
98
201
|
validateEnum(kind, COORDINATION_KIND_VALUES, "Coordination kind");
|
|
99
202
|
validateEnum(status, COORDINATION_STATUS_VALUES, "Coordination status");
|
|
100
203
|
validateEnum(priority, COORDINATION_PRIORITY_VALUES, "Coordination priority");
|
|
101
204
|
validateEnum(confidence, COORDINATION_CONFIDENCE_VALUES, "Coordination confidence");
|
|
205
|
+
validateEnum(
|
|
206
|
+
blockerSeverity,
|
|
207
|
+
COORDINATION_BLOCKER_SEVERITY_VALUES,
|
|
208
|
+
"Coordination blockerSeverity",
|
|
209
|
+
);
|
|
102
210
|
if (!lane) {
|
|
103
211
|
throw new Error("Coordination lane is required");
|
|
104
212
|
}
|
|
@@ -118,6 +226,8 @@ export function normalizeCoordinationRecord(rawRecord, defaults = {}) {
|
|
|
118
226
|
targets: normalizeStringArray(rawRecord.targets ?? defaults.targets),
|
|
119
227
|
status,
|
|
120
228
|
priority,
|
|
229
|
+
blocking,
|
|
230
|
+
blockerSeverity,
|
|
121
231
|
artifactRefs: normalizeStringArray(rawRecord.artifactRefs ?? defaults.artifactRefs),
|
|
122
232
|
dependsOn: normalizeStringArray(rawRecord.dependsOn ?? defaults.dependsOn),
|
|
123
233
|
closureCondition: normalizeString(rawRecord.closureCondition ?? defaults.closureCondition, ""),
|
|
@@ -180,6 +290,8 @@ export function appendCoordinationRecord(filePath, rawRecord, defaults = {}) {
|
|
|
180
290
|
kind: record.kind,
|
|
181
291
|
status: record.status,
|
|
182
292
|
priority: record.priority,
|
|
293
|
+
blocking: record.blocking !== false,
|
|
294
|
+
blockerSeverity: record.blockerSeverity,
|
|
183
295
|
confidence: record.confidence,
|
|
184
296
|
summary: record.summary,
|
|
185
297
|
detail: record.detail,
|
|
@@ -416,15 +528,16 @@ export function buildCoordinationResponseMetrics(state, options = {}) {
|
|
|
416
528
|
for (const record of state?.openRecords || []) {
|
|
417
529
|
const startMs = parseRecordStartMs(record);
|
|
418
530
|
const ageMs = Number.isFinite(startMs) ? Math.max(0, nowMs - startMs) : null;
|
|
419
|
-
const
|
|
531
|
+
const blocking = coordinationRecordBlocksWave(record);
|
|
532
|
+
const ackTracked = blocking && isAckTrackedRecord(record);
|
|
420
533
|
const ackPending = ackTracked && record.status === "open";
|
|
421
534
|
const clarificationLinked =
|
|
422
|
-
record.kind === "clarification-request" || isClarificationLinkedRequest(record);
|
|
535
|
+
blocking && (record.kind === "clarification-request" || isClarificationLinkedRequest(record));
|
|
423
536
|
const overdueAck = ackPending && Number.isFinite(ageMs) && ageMs >= ackTimeoutMs;
|
|
424
537
|
const staleClarification =
|
|
425
538
|
clarificationLinked && Number.isFinite(ageMs) && ageMs >= resolutionStaleMs;
|
|
426
539
|
|
|
427
|
-
if (Number.isFinite(ageMs)) {
|
|
540
|
+
if (blocking && Number.isFinite(ageMs)) {
|
|
428
541
|
oldestOpenCoordinationAgeMs =
|
|
429
542
|
oldestOpenCoordinationAgeMs === null
|
|
430
543
|
? ageMs
|
|
@@ -454,6 +567,7 @@ export function buildCoordinationResponseMetrics(state, options = {}) {
|
|
|
454
567
|
overdueAck,
|
|
455
568
|
clarificationLinked,
|
|
456
569
|
staleClarification,
|
|
570
|
+
blocking,
|
|
457
571
|
});
|
|
458
572
|
}
|
|
459
573
|
|
|
@@ -469,7 +583,7 @@ export function buildCoordinationResponseMetrics(state, options = {}) {
|
|
|
469
583
|
a.localeCompare(b),
|
|
470
584
|
),
|
|
471
585
|
openHumanEscalationCount: (state?.humanEscalations || []).filter((record) =>
|
|
472
|
-
|
|
586
|
+
coordinationRecordBlocksWave(record),
|
|
473
587
|
).length,
|
|
474
588
|
recordMetricsById,
|
|
475
589
|
};
|
|
@@ -490,6 +604,12 @@ function renderOpenRecord(record, responseMetrics = null) {
|
|
|
490
604
|
if (recordMetrics?.staleClarification) {
|
|
491
605
|
tags.push("stale-clarification");
|
|
492
606
|
}
|
|
607
|
+
if (record.blocking === false) {
|
|
608
|
+
tags.push("non-blocking");
|
|
609
|
+
}
|
|
610
|
+
if (record.blockerSeverity) {
|
|
611
|
+
tags.push(`severity=${record.blockerSeverity}`);
|
|
612
|
+
}
|
|
493
613
|
const timing = tags.length > 0 ? ` [${tags.join(", ")}]` : "";
|
|
494
614
|
return `- [${record.priority}] ${record.kind}/${record.status} ${record.agentId}${targets}${timing} id=${record.id}: ${compactSingleLine(record.summary || record.detail || "no summary", 160)}${artifacts}`;
|
|
495
615
|
}
|