@chllming/wave-orchestration 0.7.3 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +14 -13
- package/docs/README.md +3 -1
- package/docs/agents/wave-cont-qa-role.md +1 -0
- package/docs/agents/wave-integration-role.md +1 -0
- package/docs/agents/wave-launcher-role.md +4 -1
- package/docs/agents/wave-orchestrator-role.md +5 -3
- package/docs/concepts/operating-modes.md +1 -1
- package/docs/concepts/runtime-agnostic-orchestration.md +5 -4
- package/docs/concepts/what-is-a-wave.md +12 -10
- package/docs/guides/author-and-run-waves.md +3 -3
- package/docs/plans/architecture-hardening-migration.md +206 -0
- package/docs/plans/current-state.md +5 -3
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/master-plan.md +2 -2
- package/docs/plans/migration.md +12 -2
- package/docs/plans/wave-orchestrator.md +10 -8
- package/docs/reference/coordination-and-closure.md +8 -4
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/sample-waves.md +4 -4
- package/docs/reference/skills.md +3 -0
- package/docs/reference/wave-control.md +4 -2
- package/docs/research/coordination-failure-review.md +4 -4
- package/docs/roadmap.md +1 -1
- package/package.json +1 -1
- package/releases/manifest.json +39 -0
- package/scripts/wave-orchestrator/agent-state.mjs +405 -89
- package/scripts/wave-orchestrator/contradiction-entity.mjs +487 -0
- package/scripts/wave-orchestrator/coord-cli.mjs +6 -2
- package/scripts/wave-orchestrator/coordination-store.mjs +3 -1
- package/scripts/wave-orchestrator/launcher-gates.mjs +79 -11
- package/scripts/wave-orchestrator/launcher-retry.mjs +36 -6
- package/scripts/wave-orchestrator/launcher.mjs +163 -2
- package/scripts/wave-orchestrator/routing-state.mjs +91 -4
- package/scripts/wave-orchestrator/task-entity.mjs +425 -51
- package/scripts/wave-orchestrator/wave-control-schema.mjs +2 -0
- package/scripts/wave-orchestrator/wave-state-reducer.mjs +260 -111
- package/skills/README.md +3 -0
- package/skills/repo-coding-rules/SKILL.md +1 -1
- package/skills/role-cont-qa/SKILL.md +2 -2
- package/skills/role-deploy/SKILL.md +1 -1
- package/skills/role-documentation/SKILL.md +1 -1
- package/skills/role-implementation/SKILL.md +1 -1
- package/skills/role-infra/SKILL.md +1 -1
- package/skills/role-integration/SKILL.md +2 -2
- package/skills/role-security/SKILL.md +1 -1
- package/skills/runtime-claude/SKILL.md +1 -1
- package/skills/runtime-codex/SKILL.md +1 -1
- package/skills/runtime-opencode/SKILL.md +1 -1
- package/skills/wave-core/SKILL.md +14 -6
- package/skills/wave-core/references/marker-syntax.md +1 -1
|
@@ -226,6 +226,33 @@ export function relaunchReasonBuckets(runs, failures, derivedState) {
|
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
const HUMAN_INPUT_BLOCKER_KINDS = new Set([
|
|
230
|
+
"human-input",
|
|
231
|
+
"human-feedback",
|
|
232
|
+
"human-escalation",
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
function isHumanInputBlocker(blocker) {
|
|
236
|
+
return HUMAN_INPUT_BLOCKER_KINDS.has(String(blocker?.kind || "").trim().toLowerCase());
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function normalizeRetryTargets(retryTargetSet) {
|
|
240
|
+
if (Array.isArray(retryTargetSet)) {
|
|
241
|
+
return retryTargetSet;
|
|
242
|
+
}
|
|
243
|
+
if (Array.isArray(retryTargetSet?.targets)) {
|
|
244
|
+
return retryTargetSet.targets;
|
|
245
|
+
}
|
|
246
|
+
if (Array.isArray(retryTargetSet?.agentIds)) {
|
|
247
|
+
return retryTargetSet.agentIds.map((agentId) => ({
|
|
248
|
+
agentId,
|
|
249
|
+
reason: retryTargetSet.reason || null,
|
|
250
|
+
retryOverride: retryTargetSet.retryOverride || null,
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
|
|
229
256
|
export function applySharedComponentWaitStateToDashboard(componentGate, dashboardState) {
|
|
230
257
|
const waitingSummary = (componentGate?.waitingOnAgentIds || []).join("/");
|
|
231
258
|
if (!waitingSummary) {
|
|
@@ -769,7 +796,7 @@ function classifyResumeReason(waveState) {
|
|
|
769
796
|
return "all-gates-pass";
|
|
770
797
|
}
|
|
771
798
|
const humanBlockers = (waveState.openBlockers || []).filter(
|
|
772
|
-
(blocker) => blocker
|
|
799
|
+
(blocker) => isHumanInputBlocker(blocker),
|
|
773
800
|
);
|
|
774
801
|
if (humanBlockers.length > 0) {
|
|
775
802
|
return "human-request";
|
|
@@ -792,11 +819,14 @@ function classifyResumeReason(waveState) {
|
|
|
792
819
|
|
|
793
820
|
function collectResumeHumanInputBlockers(waveState) {
|
|
794
821
|
return (waveState.openBlockers || [])
|
|
795
|
-
.filter((b) => b
|
|
822
|
+
.filter((b) => isHumanInputBlocker(b))
|
|
796
823
|
.map((b) => ({
|
|
797
824
|
taskId: b.taskId || b.id || null,
|
|
798
825
|
title: b.title || b.detail || null,
|
|
799
|
-
assigneeAgentId:
|
|
826
|
+
assigneeAgentId:
|
|
827
|
+
b.agentId ||
|
|
828
|
+
b.assigneeAgentId ||
|
|
829
|
+
(Array.isArray(b.blockedAgentIds) ? b.blockedAgentIds[0] || null : null),
|
|
800
830
|
}));
|
|
801
831
|
}
|
|
802
832
|
|
|
@@ -813,13 +843,13 @@ function collectResumeGateBlockers(gateSnapshot) {
|
|
|
813
843
|
}
|
|
814
844
|
|
|
815
845
|
function collectResumeExecutorChanges(waveState) {
|
|
816
|
-
const retryTargets = waveState.retryTargetSet
|
|
817
|
-
return
|
|
846
|
+
const retryTargets = normalizeRetryTargets(waveState.retryTargetSet);
|
|
847
|
+
return retryTargets
|
|
818
848
|
.filter((t) => t.reason === "rate-limit-exhausted" || t.reason === "rate-limit" || t.retriesExhausted === true)
|
|
819
849
|
.map((t) => ({
|
|
820
850
|
agentId: t.agentId,
|
|
821
851
|
currentExecutor: t.currentExecutor || t.executor || null,
|
|
822
|
-
suggestedFallback: "claude",
|
|
852
|
+
suggestedFallback: t.retryOverride?.executorId || "claude",
|
|
823
853
|
reason: t.reason || "rate-limit-exhausted",
|
|
824
854
|
}));
|
|
825
855
|
}
|
|
@@ -145,9 +145,11 @@ import {
|
|
|
145
145
|
resolveRetryOverrideRuns,
|
|
146
146
|
waveRelaunchPlanPath,
|
|
147
147
|
} from "./retry-control.mjs";
|
|
148
|
-
import { appendWaveControlEvent } from "./control-plane.mjs";
|
|
148
|
+
import { appendWaveControlEvent, readControlPlaneEvents } from "./control-plane.mjs";
|
|
149
|
+
import { materializeContradictionsFromControlPlaneEvents } from "./contradiction-entity.mjs";
|
|
149
150
|
import { buildQualityMetrics, writeTraceBundle } from "./traces.mjs";
|
|
150
151
|
import { flushWaveControlQueue } from "./wave-control-client.mjs";
|
|
152
|
+
import { reduceWaveState } from "./wave-state-reducer.mjs";
|
|
151
153
|
import { triageClarificationRequests } from "./clarification-triage.mjs";
|
|
152
154
|
import { readProjectProfile, resolveDefaultTerminalSurface } from "./project-profile.mjs";
|
|
153
155
|
import {
|
|
@@ -167,9 +169,11 @@ import {
|
|
|
167
169
|
writeDependencySnapshotMarkdown,
|
|
168
170
|
} from "./routing-state.mjs";
|
|
169
171
|
import {
|
|
172
|
+
readWaveStateSnapshot,
|
|
170
173
|
writeAssignmentSnapshot,
|
|
171
174
|
writeDependencySnapshot,
|
|
172
175
|
writeRelaunchPlan,
|
|
176
|
+
writeWaveStateSnapshot,
|
|
173
177
|
} from "./artifact-schemas.mjs";
|
|
174
178
|
import {
|
|
175
179
|
collectUnexpectedSessionFailures as collectUnexpectedSessionFailuresImpl,
|
|
@@ -238,6 +242,7 @@ export {
|
|
|
238
242
|
|
|
239
243
|
// --- Re-exports from launcher-retry.mjs ---
|
|
240
244
|
import {
|
|
245
|
+
buildResumePlan,
|
|
241
246
|
readWaveRelaunchPlan,
|
|
242
247
|
writeWaveRelaunchPlan,
|
|
243
248
|
clearWaveRelaunchPlan,
|
|
@@ -1070,6 +1075,18 @@ export async function runLauncherCli(argv) {
|
|
|
1070
1075
|
attempt: attemptNumber,
|
|
1071
1076
|
orchestratorId: options.orchestratorId,
|
|
1072
1077
|
});
|
|
1078
|
+
const controlPlaneLogPath = path.join(
|
|
1079
|
+
lanePaths.controlPlaneDir,
|
|
1080
|
+
`wave-${wave.wave}.jsonl`,
|
|
1081
|
+
);
|
|
1082
|
+
const controlPlaneEvents = fs.existsSync(controlPlaneLogPath)
|
|
1083
|
+
? readControlPlaneEvents(controlPlaneLogPath)
|
|
1084
|
+
: [];
|
|
1085
|
+
derivedState = {
|
|
1086
|
+
...derivedState,
|
|
1087
|
+
controlPlaneEvents,
|
|
1088
|
+
contradictions: materializeContradictionsFromControlPlaneEvents(controlPlaneEvents),
|
|
1089
|
+
};
|
|
1073
1090
|
for (const run of agentRuns) {
|
|
1074
1091
|
run.messageBoardSnapshot = derivedState.messageBoardText;
|
|
1075
1092
|
run.sharedSummaryPath = derivedState.sharedSummaryPath;
|
|
@@ -1988,6 +2005,22 @@ export async function runLauncherCli(argv) {
|
|
|
1988
2005
|
validationMode: "live",
|
|
1989
2006
|
});
|
|
1990
2007
|
completionGateSnapshot = gateSnapshot;
|
|
2008
|
+
try {
|
|
2009
|
+
computeReducerSnapshot({
|
|
2010
|
+
lanePaths,
|
|
2011
|
+
wave,
|
|
2012
|
+
agentRuns,
|
|
2013
|
+
derivedState,
|
|
2014
|
+
attempt,
|
|
2015
|
+
options,
|
|
2016
|
+
});
|
|
2017
|
+
} catch (error) {
|
|
2018
|
+
recordCombinedEvent({
|
|
2019
|
+
level: "warn",
|
|
2020
|
+
agentId: lanePaths.integrationAgentId,
|
|
2021
|
+
message: `Reducer shadow snapshot failed for wave ${wave.wave}: ${error instanceof Error ? error.message : String(error)}`,
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
1991
2024
|
const traceDir = writeTraceBundle({
|
|
1992
2025
|
tracesDir: lanePaths.tracesDir,
|
|
1993
2026
|
lanePaths,
|
|
@@ -2039,7 +2072,11 @@ export async function runLauncherCli(argv) {
|
|
|
2039
2072
|
traceDir: path.relative(REPO_ROOT, traceDir),
|
|
2040
2073
|
gateSnapshot,
|
|
2041
2074
|
qualitySummary: {
|
|
2042
|
-
contradictionCount:
|
|
2075
|
+
contradictionCount: Array.isArray(derivedState?.contradictions)
|
|
2076
|
+
? derivedState.contradictions.length
|
|
2077
|
+
: derivedState?.contradictions instanceof Map
|
|
2078
|
+
? derivedState.contradictions.size
|
|
2079
|
+
: 0,
|
|
2043
2080
|
finalRecommendation: derivedState.integrationSummary?.recommendation || "unknown",
|
|
2044
2081
|
},
|
|
2045
2082
|
},
|
|
@@ -2409,3 +2446,127 @@ export async function runLauncherCli(argv) {
|
|
|
2409
2446
|
}
|
|
2410
2447
|
}
|
|
2411
2448
|
}
|
|
2449
|
+
|
|
2450
|
+
/**
|
|
2451
|
+
* Compute and persist a reducer snapshot alongside the traditional gate evaluation.
|
|
2452
|
+
* Shadow mode: the reducer runs and its output is written to disk, but decisions
|
|
2453
|
+
* still come from the traditional gate readers. This enables comparison and validation.
|
|
2454
|
+
*
|
|
2455
|
+
* @param {object} params
|
|
2456
|
+
* @param {object} params.lanePaths
|
|
2457
|
+
* @param {object} params.wave - Wave definition
|
|
2458
|
+
* @param {object} params.agentRuns - Array of run info objects
|
|
2459
|
+
* @param {object} params.derivedState - Current derived state
|
|
2460
|
+
* @param {number} params.attempt - Current attempt number
|
|
2461
|
+
* @param {object} params.options - Launcher options
|
|
2462
|
+
* @returns {object} { reducerState, resumePlan, snapshotPath }
|
|
2463
|
+
*/
|
|
2464
|
+
export function computeReducerSnapshot({
|
|
2465
|
+
lanePaths,
|
|
2466
|
+
wave,
|
|
2467
|
+
agentRuns,
|
|
2468
|
+
derivedState,
|
|
2469
|
+
attempt,
|
|
2470
|
+
options = {},
|
|
2471
|
+
}) {
|
|
2472
|
+
// Build agentResults from agentRuns
|
|
2473
|
+
const agentResults = {};
|
|
2474
|
+
for (const run of agentRuns) {
|
|
2475
|
+
const summary = readRunExecutionSummary(run, wave);
|
|
2476
|
+
if (summary) {
|
|
2477
|
+
agentResults[run.agent.agentId] = summary;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
// Load canonical event sources
|
|
2482
|
+
const controlPlaneLogPath = path.join(
|
|
2483
|
+
lanePaths.controlPlaneDir,
|
|
2484
|
+
`wave-${wave.wave}.jsonl`,
|
|
2485
|
+
);
|
|
2486
|
+
const controlPlaneEvents = fs.existsSync(controlPlaneLogPath)
|
|
2487
|
+
? readControlPlaneEvents(controlPlaneLogPath)
|
|
2488
|
+
: [];
|
|
2489
|
+
|
|
2490
|
+
const coordinationLogPath = path.join(
|
|
2491
|
+
lanePaths.coordinationDir,
|
|
2492
|
+
`wave-${wave.wave}.jsonl`,
|
|
2493
|
+
);
|
|
2494
|
+
const coordinationRecords = fs.existsSync(coordinationLogPath)
|
|
2495
|
+
? readMaterializedCoordinationState(coordinationLogPath)
|
|
2496
|
+
: null;
|
|
2497
|
+
|
|
2498
|
+
const feedbackRequests = readWaveHumanFeedbackRequests({
|
|
2499
|
+
feedbackRequestsDir: lanePaths.feedbackRequestsDir,
|
|
2500
|
+
lane: lanePaths.lane,
|
|
2501
|
+
waveNumber: wave.wave,
|
|
2502
|
+
agentIds: (agentRuns || []).map((run) => run.agent.agentId),
|
|
2503
|
+
orchestratorId: options.orchestratorId,
|
|
2504
|
+
});
|
|
2505
|
+
|
|
2506
|
+
// Build dependency tickets from derivedState
|
|
2507
|
+
const dependencyTickets = derivedState?.dependencySnapshot || null;
|
|
2508
|
+
|
|
2509
|
+
// Run the reducer
|
|
2510
|
+
const reducerState = reduceWaveState({
|
|
2511
|
+
controlPlaneEvents,
|
|
2512
|
+
coordinationRecords: coordinationRecords?.latestRecords || [],
|
|
2513
|
+
agentResults,
|
|
2514
|
+
waveDefinition: wave,
|
|
2515
|
+
dependencyTickets,
|
|
2516
|
+
feedbackRequests: feedbackRequests || [],
|
|
2517
|
+
laneConfig: {
|
|
2518
|
+
lane: lanePaths.lane,
|
|
2519
|
+
contQaAgentId: lanePaths.contQaAgentId || "A0",
|
|
2520
|
+
contEvalAgentId: lanePaths.contEvalAgentId || "E0",
|
|
2521
|
+
integrationAgentId: lanePaths.integrationAgentId || "A8",
|
|
2522
|
+
documentationAgentId: lanePaths.documentationAgentId || "A9",
|
|
2523
|
+
validationMode: "live",
|
|
2524
|
+
evalTargets: wave.evalTargets,
|
|
2525
|
+
benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
|
|
2526
|
+
laneProfile: lanePaths.laneProfile,
|
|
2527
|
+
requireIntegrationStewardFromWave: lanePaths.requireIntegrationStewardFromWave,
|
|
2528
|
+
capabilityRouting: lanePaths.capabilityRouting,
|
|
2529
|
+
},
|
|
2530
|
+
});
|
|
2531
|
+
|
|
2532
|
+
// Build resume plan
|
|
2533
|
+
const resumePlan = buildResumePlan(reducerState, {
|
|
2534
|
+
waveDefinition: wave,
|
|
2535
|
+
lanePaths,
|
|
2536
|
+
});
|
|
2537
|
+
|
|
2538
|
+
// Persist snapshot
|
|
2539
|
+
const stateDir = path.join(lanePaths.stateDir, "reducer");
|
|
2540
|
+
ensureDirectory(stateDir);
|
|
2541
|
+
const snapshotPath = path.join(stateDir, `wave-${wave.wave}.json`);
|
|
2542
|
+
writeWaveStateSnapshot(snapshotPath, {
|
|
2543
|
+
...reducerState,
|
|
2544
|
+
attempt,
|
|
2545
|
+
resumePlan,
|
|
2546
|
+
}, {
|
|
2547
|
+
lane: lanePaths.lane,
|
|
2548
|
+
wave: wave.wave,
|
|
2549
|
+
});
|
|
2550
|
+
|
|
2551
|
+
return {
|
|
2552
|
+
reducerState,
|
|
2553
|
+
resumePlan,
|
|
2554
|
+
snapshotPath,
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
/**
|
|
2559
|
+
* Read a previously persisted reducer snapshot from disk.
|
|
2560
|
+
*
|
|
2561
|
+
* @param {object} lanePaths
|
|
2562
|
+
* @param {number} waveNumber
|
|
2563
|
+
* @returns {object|null} The persisted snapshot, or null if not found
|
|
2564
|
+
*/
|
|
2565
|
+
export function readPersistedReducerSnapshot(lanePaths, waveNumber) {
|
|
2566
|
+
const stateDir = path.join(lanePaths.stateDir, "reducer");
|
|
2567
|
+
const snapshotPath = path.join(stateDir, `wave-${waveNumber}.json`);
|
|
2568
|
+
return readWaveStateSnapshot(snapshotPath, {
|
|
2569
|
+
lane: lanePaths.lane,
|
|
2570
|
+
wave: waveNumber,
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
@@ -155,6 +155,82 @@ function assignmentStateForRecord(record) {
|
|
|
155
155
|
return "open";
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
function recordText(record) {
|
|
159
|
+
return `${String(record?.summary || "")}\n${String(record?.detail || "")}`.trim().toLowerCase();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function targetMatchesAgent(target, agentId) {
|
|
163
|
+
const normalizedTarget = String(target || "").trim();
|
|
164
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
165
|
+
if (!normalizedTarget || !normalizedAgentId) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
return normalizedTarget === normalizedAgentId || normalizedTarget === `agent:${normalizedAgentId}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function recordTargetsAgent(record, agentId) {
|
|
172
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
173
|
+
if (!normalizedAgentId) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (String(record?.agentId || "").trim() === normalizedAgentId) {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
return Array.isArray(record?.targets)
|
|
180
|
+
? record.targets.some((target) => targetMatchesAgent(target, normalizedAgentId))
|
|
181
|
+
: false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function requestResolutionForAssignment({
|
|
185
|
+
coordinationState,
|
|
186
|
+
requestRecord,
|
|
187
|
+
assignmentId,
|
|
188
|
+
assignedAgentId,
|
|
189
|
+
target,
|
|
190
|
+
}) {
|
|
191
|
+
const requestId = String(requestRecord?.id || "").trim();
|
|
192
|
+
if (!requestId) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const requestIdLower = requestId.toLowerCase();
|
|
196
|
+
const assignmentIdLower = String(assignmentId || "").trim().toLowerCase();
|
|
197
|
+
const requestTargets = Array.isArray(requestRecord?.targets)
|
|
198
|
+
? requestRecord.targets.filter((entry) => String(entry || "").trim())
|
|
199
|
+
: [];
|
|
200
|
+
const requiresAssignmentSpecificMatch = requestTargets.length > 1;
|
|
201
|
+
const resolvedRecords = [...(coordinationState?.resolvedByPolicy || [])].reverse();
|
|
202
|
+
for (const record of resolvedRecords) {
|
|
203
|
+
const dependsOn = Array.isArray(record?.dependsOn)
|
|
204
|
+
? record.dependsOn.map((value) => String(value || "").trim().toLowerCase())
|
|
205
|
+
: [];
|
|
206
|
+
const assignmentDependsOnMatch = assignmentIdLower && dependsOn.includes(assignmentIdLower);
|
|
207
|
+
const requestDependsOnMatch = dependsOn.includes(requestIdLower);
|
|
208
|
+
if (assignmentDependsOnMatch || (!requiresAssignmentSpecificMatch && requestDependsOnMatch)) {
|
|
209
|
+
return record;
|
|
210
|
+
}
|
|
211
|
+
const closureCondition = String(record?.closureCondition || "").trim().toLowerCase();
|
|
212
|
+
const assignmentClosureMatch =
|
|
213
|
+
assignmentIdLower && closureCondition.includes(assignmentIdLower);
|
|
214
|
+
const requestClosureMatch = closureCondition.includes(requestIdLower);
|
|
215
|
+
if (
|
|
216
|
+
assignmentClosureMatch ||
|
|
217
|
+
(!requiresAssignmentSpecificMatch && requestClosureMatch)
|
|
218
|
+
) {
|
|
219
|
+
return record;
|
|
220
|
+
}
|
|
221
|
+
if (!recordTargetsAgent(record, assignedAgentId) && !targetMatchesAgent(target, record?.agentId)) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const text = recordText(record);
|
|
225
|
+
const assignmentTextMatch = assignmentIdLower && text.includes(assignmentIdLower);
|
|
226
|
+
const requestTextMatch = text.includes(requestIdLower);
|
|
227
|
+
if (assignmentTextMatch || (!requiresAssignmentSpecificMatch && requestTextMatch)) {
|
|
228
|
+
return record;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
158
234
|
export function buildRequestAssignments({
|
|
159
235
|
coordinationState,
|
|
160
236
|
agents,
|
|
@@ -179,8 +255,18 @@ export function buildRequestAssignments({
|
|
|
179
255
|
}
|
|
180
256
|
for (const target of targets) {
|
|
181
257
|
const resolution = resolveTargetAssignment(target, agents, ledger, capabilityRouting);
|
|
258
|
+
const assignmentId = `assignment:${record.id}:${targetSlug(target) || "target"}`;
|
|
259
|
+
const resolvedByPolicyRecord = requestResolutionForAssignment({
|
|
260
|
+
coordinationState,
|
|
261
|
+
requestRecord: record,
|
|
262
|
+
assignmentId,
|
|
263
|
+
assignedAgentId: resolution.assignedAgentId,
|
|
264
|
+
target,
|
|
265
|
+
});
|
|
266
|
+
const resolvedByPolicy = Boolean(resolvedByPolicyRecord);
|
|
267
|
+
const effectiveStatus = resolvedByPolicy ? "resolved" : record.status;
|
|
182
268
|
assignments.push({
|
|
183
|
-
id:
|
|
269
|
+
id: assignmentId,
|
|
184
270
|
requestId: record.id,
|
|
185
271
|
recordId: record.id,
|
|
186
272
|
sourceKind: record.kind,
|
|
@@ -188,20 +274,21 @@ export function buildRequestAssignments({
|
|
|
188
274
|
summary: record.summary || "",
|
|
189
275
|
detail: record.detail || "",
|
|
190
276
|
priority: record.priority || "normal",
|
|
191
|
-
requestStatus:
|
|
192
|
-
state: assignmentStateForRecord(record),
|
|
277
|
+
requestStatus: effectiveStatus,
|
|
278
|
+
state: assignmentStateForRecord({ ...record, status: effectiveStatus }),
|
|
193
279
|
target: resolution.target,
|
|
194
280
|
targetType: resolution.targetType,
|
|
195
281
|
capability: resolution.capability,
|
|
196
282
|
assignedAgentId: resolution.assignedAgentId,
|
|
197
283
|
assignmentReason: resolution.assignmentReason,
|
|
198
284
|
assignmentDetail: resolution.detail,
|
|
199
|
-
blocking: isOpenCoordinationStatus(record.status),
|
|
285
|
+
blocking: !resolvedByPolicy && isOpenCoordinationStatus(record.status),
|
|
200
286
|
artifactRefs: Array.isArray(record.artifactRefs) ? record.artifactRefs : [],
|
|
201
287
|
dependsOn: Array.isArray(record.dependsOn) ? record.dependsOn : [],
|
|
202
288
|
closureCondition: String(record.closureCondition || ""),
|
|
203
289
|
createdAt: record.createdAt,
|
|
204
290
|
updatedAt: record.updatedAt,
|
|
291
|
+
resolvedByRecordId: resolvedByPolicyRecord?.id || null,
|
|
205
292
|
});
|
|
206
293
|
}
|
|
207
294
|
}
|