@chllming/wave-orchestration 0.8.1 → 0.8.3
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 +8 -8
- package/docs/plans/current-state.md +3 -1
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +4 -3
- package/docs/reference/cli-reference.md +5 -1
- package/docs/reference/coordination-and-closure.md +11 -0
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/sample-waves.md +4 -4
- package/package.json +1 -1
- package/releases/manifest.json +39 -0
- package/scripts/wave-orchestrator/control-cli.mjs +57 -5
- package/scripts/wave-orchestrator/coord-cli.mjs +8 -0
- package/scripts/wave-orchestrator/feedback.mjs +11 -1
- package/scripts/wave-orchestrator/human-input-resolution.mjs +344 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.8.3 - 2026-03-24
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Updated the shipped package metadata, release manifest, README, migration guide, sample-wave docs, current-state notes, and npm publishing runbook to advertise `0.8.3` as the current release surface.
|
|
10
|
+
- Documented that `wave feedback respond` is a canonical-state repair path, not a feedback-JSON-only update, and that ad-hoc reconciliation must keep the `--run <id>` context.
|
|
11
|
+
|
|
12
|
+
### Fixed And Hardened
|
|
13
|
+
|
|
14
|
+
- Answered human-feedback requests now reconcile linked clarification, escalation, and helper-assignment state back into the canonical coordination log so reducer state, control surfaces, and launcher gates stop reading the wave as still `clarifying`.
|
|
15
|
+
- `wave feedback respond --run <id>` now carries the ad-hoc run id through the reconciliation helper, so answered human input repairs the isolated ad-hoc lane state instead of the roadmap state root.
|
|
16
|
+
- When a wave is stranded after a human answer arrives and no active attempt is still running, the human-input reconciliation path now writes a safe one-shot continuation request instead of leaving the wave waiting for manual relaunch bookkeeping.
|
|
17
|
+
|
|
18
|
+
### Testing And Validation
|
|
19
|
+
|
|
20
|
+
- Added regression coverage for direct `wave feedback respond` reconciliation and for the ad-hoc `--run <id>` answer path.
|
|
21
|
+
|
|
22
|
+
## 0.8.2 - 2026-03-24
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Updated the shipped package metadata, release manifest, README, migration guide, sample-wave docs, and npm publishing runbook to advertise `0.8.2` as the current release surface.
|
|
27
|
+
|
|
28
|
+
### Fixed And Hardened
|
|
29
|
+
|
|
30
|
+
- `wave control status` now treats `phase=completed` as terminal in the control-status projection layer instead of replaying stale blocking edges from historical open coordination records.
|
|
31
|
+
- Completed waves now return `blockingEdge: null` and `nextTimer: null`, so stale overdue timers or request blockers stop leaking into an already-closed wave view.
|
|
32
|
+
- Successful logical-agent state is now preserved for completed waves, so agents that already finished cleanly stay `closed` or `satisfied` even when old request records remain visible in coordination history.
|
|
33
|
+
|
|
34
|
+
### Testing And Validation
|
|
35
|
+
|
|
36
|
+
- Added regression coverage for completed-wave control-status projections so historical request records stay visible without reopening blocking state after closure.
|
|
37
|
+
- Revalidated the shipped release surface with the full Vitest suite, `wave doctor --json`, and `wave launch --lane main --dry-run --no-dashboard`.
|
|
38
|
+
|
|
5
39
|
## 0.8.1 - 2026-03-24
|
|
6
40
|
|
|
7
41
|
### Changed
|
package/README.md
CHANGED
|
@@ -79,18 +79,18 @@ Wave is built to mitigate those failures with a canonical authority set, generat
|
|
|
79
79
|
|
|
80
80
|
Current release:
|
|
81
81
|
|
|
82
|
-
- `@chllming/wave-orchestration@0.8.
|
|
83
|
-
- Release tag: [`v0.8.
|
|
82
|
+
- `@chllming/wave-orchestration@0.8.3`
|
|
83
|
+
- Release tag: [`v0.8.3`](https://github.com/chllming/agent-wave-orchestrator/releases/tag/v0.8.3)
|
|
84
84
|
- Public install path: npmjs
|
|
85
85
|
- Authenticated fallback: GitHub Packages
|
|
86
86
|
|
|
87
|
-
Highlights in `0.8.
|
|
87
|
+
Highlights in `0.8.3`:
|
|
88
88
|
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
- The
|
|
93
|
-
- Upgrade and operator docs now cover the full `0.8.
|
|
89
|
+
- Answering a human-feedback request now reconciles linked clarification, escalation, and helper-assignment state back into the canonical coordination log instead of only updating the feedback JSON.
|
|
90
|
+
- `wave feedback respond --run <id>` now applies that same reconciliation and safe continuation flow to ad-hoc runs instead of writing into the roadmap lane state root.
|
|
91
|
+
- When a stranded wave can safely continue after the answer arrives and no attempt is still running, Wave writes a one-shot continuation request automatically.
|
|
92
|
+
- The completed-wave control-status hardening from `0.8.2` remains intact.
|
|
93
|
+
- Upgrade and operator docs now cover the full `0.8.3` package surface end to end.
|
|
94
94
|
|
|
95
95
|
Requirements:
|
|
96
96
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Current State
|
|
2
2
|
|
|
3
|
-
- The starter workspace in this source repo reflects the `0.8.
|
|
3
|
+
- The starter workspace in this source repo reflects the `0.8.3` package release surface.
|
|
4
4
|
- The staged architecture cutover from launcher-centric decisions to reducer and phase-engine ownership is tracked in `docs/plans/architecture-hardening-migration.md`.
|
|
5
5
|
- The repository contains the published `@chllming/wave-orchestration` package plus the starter scaffold used by `wave init`.
|
|
6
6
|
- The runtime is package-first and non-destructive for adopting repos: `wave init --adopt-existing` records existing repo-owned plans, waves, prompts, and config without overwriting them, and `wave upgrade` writes only `.wave/install-state.json` plus `.wave/upgrade-history/`.
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- `wave adhoc plan|run|list|show|promote` manage transient operator-driven work
|
|
16
16
|
- requests, generated specs, rendered markdown, and final results live under `.wave/adhoc/runs/<run-id>/`
|
|
17
17
|
- runtime state stays isolated under `.tmp/<lane>-wave-launcher/adhoc/<run-id>/`
|
|
18
|
+
- `wave feedback respond --run <run-id>` now reconciles answered human-input state inside that isolated ad-hoc state root and can queue a safe one-shot continuation request without touching roadmap state
|
|
18
19
|
- ad-hoc runs always keep integration, documentation, and cont-QA closure, while `cont-EVAL` and security review are synthesized only when the request needs them
|
|
19
20
|
- documentation closure still queues canonical shared-plan docs when a run reports a shared-plan delta, alongside the ad-hoc closure report
|
|
20
21
|
- `wave adhoc promote` copies the stored ad-hoc spec into numbered roadmap artifacts instead of re-deriving it from the current project profile
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
- hermetic `traceVersion: 2` per-attempt trace bundles with copied launched-agent summaries, copied component matrices for promoted waves, a hashed `outcome.json` replay baseline, run metadata, and cumulative quality metrics
|
|
40
41
|
- an internal, read-only replay validator for trace bundles, with legacy `traceVersion: 1` bundles kept in best-effort warning mode
|
|
41
42
|
- orchestrator-first clarification triage plus human escalation artifacts
|
|
43
|
+
- answered human-feedback responses that reconcile canonical coordination state, helper assignments, and safe continuation intent even when the launcher is no longer active
|
|
42
44
|
- optional `--resident-orchestrator` support for a long-running, non-owning orchestrator session during live waves
|
|
43
45
|
- persisted relaunch plans under `.tmp/<lane>-wave-launcher/status/` so targeted retry intent can survive a launcher restart
|
|
44
46
|
- a canonical control-plane event log under `.tmp/<lane>-wave-launcher/control-plane/` that records operator tasks, rerun requests, proof bundles, attempt lifecycle, and human-input events as append-only JSONL; `wave control` materializes state from this log
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This is a showcase-first sample wave.
|
|
4
4
|
|
|
5
|
-
Use it as the single reference example for the current `0.8.
|
|
5
|
+
Use it as the single reference example for the current `0.8.3` Wave surface.
|
|
6
6
|
|
|
7
7
|
It intentionally combines more sections than a normal production wave so one file can demonstrate:
|
|
8
8
|
|
package/docs/plans/migration.md
CHANGED
|
@@ -24,13 +24,13 @@ GitHub Packages remains available as an authenticated fallback path, and maintai
|
|
|
24
24
|
- Fresh `wave init` seeds the starter `skills/` library. `wave init --adopt-existing` records existing repo-owned skill bundles when they are already present, but does not replace or rewrite them.
|
|
25
25
|
- The current runtime expects the post-roadmap model: typed coordination, compiled inboxes, `A8` integration, staged closure, orchestrator-first clarification, and operational runtime policy.
|
|
26
26
|
|
|
27
|
-
## Upgrading From 0.6.x To 0.8.
|
|
27
|
+
## Upgrading From 0.6.x To 0.8.3
|
|
28
28
|
|
|
29
29
|
Read `CHANGELOG.md` first, then treat this section as the repo-owned migration checklist for adopted `0.6.x` workspaces.
|
|
30
30
|
|
|
31
31
|
`wave upgrade` updates the installed runtime only. It does not copy planner starter files into a repo that already owns its docs, skills, and Context7 bundles.
|
|
32
32
|
|
|
33
|
-
`0.8.
|
|
33
|
+
`0.8.3` carries forward the `0.8.2` completed-wave control-status hardening and fixes the human-answer reconciliation path: answered feedback now closes the linked clarification or escalation chain in canonical coordination, re-syncs helper-assignment projections, and preserves ad-hoc `--run <id>` context when writing safe continuation intent.
|
|
34
34
|
|
|
35
35
|
### Required Repo Changes
|
|
36
36
|
|
|
@@ -42,7 +42,7 @@ If the repo adopted Wave before the planner corpus became a tracked required sur
|
|
|
42
42
|
- `docs/reference/wave-planning-lessons.md`
|
|
43
43
|
- the `planner-agentic` bundle entry in `docs/context7/bundles.json`
|
|
44
44
|
|
|
45
|
-
If the repo copied the shipped starter architecture docs or skills and wants the `0.8.
|
|
45
|
+
If the repo copied the shipped starter architecture docs or skills and wants the `0.8.3` authority-model language, also sync:
|
|
46
46
|
|
|
47
47
|
- `docs/agents/wave-launcher-role.md`
|
|
48
48
|
- `docs/agents/wave-orchestrator-role.md`
|
|
@@ -57,6 +57,7 @@ After syncing those repo-owned files:
|
|
|
57
57
|
1. Run `pnpm exec wave doctor`.
|
|
58
58
|
2. Run `pnpm exec wave launch --lane main --dry-run --no-dashboard`.
|
|
59
59
|
3. Use `pnpm exec wave dashboard --lane <lane> --attach current` or `--attach global` when you need to reattach to a live tmux-backed dashboard without reverse-engineering the socket or session name.
|
|
60
|
+
4. If your operators answer human-input tickets through `wave feedback respond`, update any repo-local runbooks so ad-hoc runs always pass `--run <id>` when responding outside the main roadmap lane.
|
|
60
61
|
|
|
61
62
|
## Upgrading From 0.5.4 To 0.6.1
|
|
62
63
|
|
|
@@ -297,12 +297,14 @@ wave feedback ask \
|
|
|
297
297
|
|
|
298
298
|
```
|
|
299
299
|
wave feedback respond \
|
|
300
|
-
--id <request-id> --response "<text>" \
|
|
300
|
+
[--run <id>] --id <request-id> --response "<text>" \
|
|
301
301
|
[--operator <name>] [--force]
|
|
302
302
|
```
|
|
303
303
|
|
|
304
304
|
`--force` overrides a previously answered request.
|
|
305
305
|
|
|
306
|
+
When the answered request belongs to a live wave or ad-hoc run, `wave feedback respond` also reconciles the linked clarification, escalation, and helper-assignment state in canonical coordination. If no attempt is still running and the reducer can safely continue, it writes a one-shot continuation request instead of relaunching directly. Use `--run <id>` when answering an ad-hoc request so reconciliation targets the isolated ad-hoc state root.
|
|
307
|
+
|
|
306
308
|
**List feedback requests:**
|
|
307
309
|
|
|
308
310
|
```
|
|
@@ -321,6 +323,8 @@ wave feedback watch [--lane <lane>] [--wave <n>] [--agent <id>] [--pending] [--r
|
|
|
321
323
|
wave feedback show --id <request-id>
|
|
322
324
|
```
|
|
323
325
|
|
|
326
|
+
All `wave feedback` subcommands accept `--run <id>` for ad-hoc runs.
|
|
327
|
+
|
|
324
328
|
## wave dep
|
|
325
329
|
|
|
326
330
|
Cross-lane dependency management.
|
|
@@ -250,6 +250,17 @@ pnpm exec wave control task act resolve --lane main --wave 10 --id escalation-cl
|
|
|
250
250
|
|
|
251
251
|
That keeps clarification routing, dismissal, escalation, and human-answer handling inside the canonical coordination state instead of forcing ad hoc file edits.
|
|
252
252
|
|
|
253
|
+
When the operator answers through the feedback queue directly, the answer path now repairs the same canonical state:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
pnpm exec wave feedback respond \
|
|
257
|
+
--id 202603240000-main-w6-A3-abc123 \
|
|
258
|
+
--response "Use the 90-day compatibility window documented in docs/plans/migration.md." \
|
|
259
|
+
--operator ops-lead
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
For ad-hoc runs, include `--run <id>` on that command. The response path will reconcile the linked clarification or escalation chain, re-sync helper-assignment projections, and write a safe one-shot continuation request when the reducer can resume but no active attempt is still running.
|
|
263
|
+
|
|
253
264
|
## End-To-End Example: Required Dependency
|
|
254
265
|
|
|
255
266
|
Assume the wave needs another lane to land a required API first.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This repo now includes a dedicated npmjs publish workflow at [publish-npm.yml](../../.github/workflows/publish-npm.yml).
|
|
4
4
|
|
|
5
|
-
The current `0.8.
|
|
5
|
+
The current `0.8.3` release procedure publishes through a repository Actions secret named `NPM_TOKEN`.
|
|
6
6
|
|
|
7
7
|
## What This Repo Already Does
|
|
8
8
|
|
|
@@ -47,6 +47,6 @@ If this repo later needs private npm dependencies during CI, consider a separate
|
|
|
47
47
|
1. Confirm [publish-npm.yml](../../.github/workflows/publish-npm.yml) is on the default branch.
|
|
48
48
|
2. Confirm `NPM_TOKEN` exists in the GitHub repo secrets.
|
|
49
49
|
3. Confirm the package version has been bumped and committed.
|
|
50
|
-
4. Push the release commit and release tag, for example `v0.8.
|
|
50
|
+
4. Push the release commit and release tag, for example `v0.8.3`.
|
|
51
51
|
5. Verify both `publish-npm.yml` and `publish-package.yml` start from the tag push.
|
|
52
52
|
6. Verify the npmjs publish completes successfully for the tagged source.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Sample Waves"
|
|
3
|
-
summary: "Showcase-first sample waves that demonstrate the current 0.8.
|
|
3
|
+
summary: "Showcase-first sample waves that demonstrate the current 0.8.3 Wave surface."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Sample Waves
|
|
7
7
|
|
|
8
|
-
This guide points to showcase-first sample waves that demonstrate the current `0.8.
|
|
8
|
+
This guide points to showcase-first sample waves that demonstrate the current `0.8.3` authored Wave surface.
|
|
9
9
|
|
|
10
10
|
The examples are intentionally denser than typical production waves. Their job is to teach the current authoring and runtime surface quickly, not to be the smallest possible launch-ready files.
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ The examples are intentionally denser than typical production waves. Their job i
|
|
|
15
15
|
Shows what a good `repo-landed` outcome looks like when one promoted component only closes honestly if desired-state records, reconcile-loop substrate, and cluster-view surfaces land together. It emphasizes maturity discipline, explicit deliverables, and shared-plan closure without drifting into `pilot-live` claims.
|
|
16
16
|
|
|
17
17
|
- [Full modern sample wave](../plans/examples/wave-example-live-proof.md)
|
|
18
|
-
Shows the combined `0.8.
|
|
18
|
+
Shows the combined `0.8.3` authored surface in one file: closure roles, `E0`, optional security review, delegated and pinned benchmark targets, richer executor config, `### Skills`, `### Capabilities`, `### Deliverables`, `### Exit contract`, `### Proof artifacts`, sticky retry, deploy environments, and proof-first live-wave structure.
|
|
19
19
|
|
|
20
20
|
## What These Examples Teach
|
|
21
21
|
|
|
@@ -38,7 +38,7 @@ The examples are intentionally denser than typical production waves. Their job i
|
|
|
38
38
|
|
|
39
39
|
## Feature Coverage Map
|
|
40
40
|
|
|
41
|
-
Together these samples cover the main surfaces added or hardened for `0.8.
|
|
41
|
+
Together these samples cover the main surfaces added or hardened for `0.8.3`:
|
|
42
42
|
|
|
43
43
|
- repo-landed maturity discipline and anti-overclaim framing
|
|
44
44
|
- explicit shared-plan closure for future-wave safety
|
package/package.json
CHANGED
package/releases/manifest.json
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "@chllming/wave-orchestration",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.8.3",
|
|
7
|
+
"date": "2026-03-24",
|
|
8
|
+
"summary": "Human-input reconciliation repair, ad-hoc feedback-run context hardening, and 0.8.3 release-surface alignment.",
|
|
9
|
+
"features": [
|
|
10
|
+
"Answered human-feedback requests now reconcile linked clarification, escalation, and helper-assignment state back into the canonical coordination log instead of only updating the feedback request JSON.",
|
|
11
|
+
"`wave feedback respond --run <id>` now preserves ad-hoc run context while writing reconciliation and safe continuation state, so ad-hoc answers stop mutating the roadmap lane state root.",
|
|
12
|
+
"When no attempt is still running and the reducer can safely continue, human-input reconciliation writes a one-shot continuation request instead of leaving the wave stranded after the operator answer arrives.",
|
|
13
|
+
"Regression coverage now exercises both direct feedback-response reconciliation and the ad-hoc `--run <id>` path.",
|
|
14
|
+
"Shipped package metadata, README, migration guidance, current-state notes, sample-wave docs, and npm publishing instructions now point at the `0.8.3` release surface."
|
|
15
|
+
],
|
|
16
|
+
"manualSteps": [
|
|
17
|
+
"If your operators answer tickets through `wave feedback respond`, update any repo-local runbooks so ad-hoc runs always pass `--run <id>` when answering outside the main roadmap lane.",
|
|
18
|
+
"Run `pnpm exec wave doctor` and `pnpm exec wave launch --lane main --dry-run --no-dashboard` after upgrading so the repo validates against the `0.8.3` runtime and human-input reconciliation contract.",
|
|
19
|
+
"If an adopted repo fails `wave doctor` after the upgrade, sync the repo-owned planner starter surface (`docs/agents/wave-planner-role.md`, `skills/role-planner/`, `docs/context7/planner-agent/`, `docs/reference/wave-planning-lessons.md`, and the `planner-agentic` bundle entry) before relying on planner-aware validation.",
|
|
20
|
+
"If a repo copied the shipped starter architecture docs or skills and wants the `0.8.3` operator guidance, sync the updated launcher and orchestrator role prompts plus the relevant runtime and closure-role skill bundles."
|
|
21
|
+
],
|
|
22
|
+
"breaking": false
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"version": "0.8.2",
|
|
26
|
+
"date": "2026-03-24",
|
|
27
|
+
"summary": "Completed-wave control-status projection hardening and 0.8.2 release-surface alignment.",
|
|
28
|
+
"features": [
|
|
29
|
+
"`wave control status` now clears stale blocking edges after a wave has already reached `phase=completed`, instead of replaying historical open request records as live blockers.",
|
|
30
|
+
"Completed waves now suppress stale `nextTimer` deadlines in the control-status projection.",
|
|
31
|
+
"Successful logical-agent states are preserved for completed waves instead of being re-blocked by stale coordination history in the control-status surface.",
|
|
32
|
+
"Regression coverage now exercises the completed-wave stale-blocking projection path directly.",
|
|
33
|
+
"Shipped package metadata, README, migration guidance, sample-wave docs, and npm publishing instructions now point at the `0.8.2` release surface."
|
|
34
|
+
],
|
|
35
|
+
"manualSteps": [
|
|
36
|
+
"Run `pnpm exec wave doctor` and `pnpm exec wave launch --lane main --dry-run --no-dashboard` after upgrading so the repo validates against the `0.8.2` runtime and completed-wave control-status contract.",
|
|
37
|
+
"If an adopted repo fails `wave doctor` after the upgrade, sync the repo-owned planner starter surface (`docs/agents/wave-planner-role.md`, `skills/role-planner/`, `docs/context7/planner-agent/`, `docs/reference/wave-planning-lessons.md`, and the `planner-agentic` bundle entry) before relying on planner-aware validation.",
|
|
38
|
+
"If a repo carries custom operator docs for `wave control status`, update that guidance so completed waves are described as terminal projections where historical coordination records may remain visible without reopening blockers.",
|
|
39
|
+
"If an adopted repo copied the starter architecture docs or skill bundles, sync the updated role prompts, `skills/wave-core/`, runtime skills, and closure-role skills so local guidance matches the `0.8.2` authority model and completed-wave control behavior.",
|
|
40
|
+
"Review `docs/plans/architecture-hardening-migration.md` before continuing reducer-authoritative cutover work in repos that extend the shipped starter surface."
|
|
41
|
+
],
|
|
42
|
+
"breaking": false
|
|
43
|
+
},
|
|
5
44
|
{
|
|
6
45
|
"version": "0.8.1",
|
|
7
46
|
"date": "2026-03-24",
|
|
@@ -6,6 +6,7 @@ import { readWaveHumanFeedbackRequests } from "./coordination.mjs";
|
|
|
6
6
|
import { readWaveLedger } from "./ledger.mjs";
|
|
7
7
|
import { buildDependencySnapshot, buildRequestAssignments } from "./routing-state.mjs";
|
|
8
8
|
import { parseWaveFiles } from "./wave-files.mjs";
|
|
9
|
+
import { answerHumanInputAndReconcile } from "./human-input-resolution.mjs";
|
|
9
10
|
import {
|
|
10
11
|
buildLanePaths,
|
|
11
12
|
DEFAULT_COORDINATION_ACK_TIMEOUT_MS,
|
|
@@ -270,6 +271,10 @@ function assignmentRelevantToAgent(assignment, agentId = "") {
|
|
|
270
271
|
);
|
|
271
272
|
}
|
|
272
273
|
|
|
274
|
+
function isCompletedPhase(phase) {
|
|
275
|
+
return String(phase || "").trim().toLowerCase() === "completed";
|
|
276
|
+
}
|
|
277
|
+
|
|
273
278
|
function buildEffectiveSelection(lanePaths, wave, { activeAttempt = null, rerunRequest = null, relaunchPlan = null } = {}) {
|
|
274
279
|
const activeAttemptSelected = Array.isArray(activeAttempt?.selectedAgentIds)
|
|
275
280
|
? Array.from(new Set(activeAttempt.selectedAgentIds.filter(Boolean)))
|
|
@@ -308,10 +313,20 @@ function buildEffectiveSelection(lanePaths, wave, { activeAttempt = null, rerunR
|
|
|
308
313
|
};
|
|
309
314
|
}
|
|
310
315
|
|
|
311
|
-
function buildLogicalAgents({
|
|
316
|
+
function buildLogicalAgents({
|
|
317
|
+
lanePaths,
|
|
318
|
+
wave,
|
|
319
|
+
tasks,
|
|
320
|
+
dependencySnapshot,
|
|
321
|
+
capabilityAssignments,
|
|
322
|
+
selection,
|
|
323
|
+
proofRegistry,
|
|
324
|
+
phase,
|
|
325
|
+
}) {
|
|
312
326
|
const selectedAgentIds = new Set(selection?.selectedAgentIds || []);
|
|
313
327
|
const helperAssignments = Array.isArray(capabilityAssignments) ? capabilityAssignments : [];
|
|
314
328
|
const openInbound = dependencySnapshot?.openInbound || [];
|
|
329
|
+
const completedPhase = isCompletedPhase(phase);
|
|
315
330
|
return wave.agents.map((agent) => {
|
|
316
331
|
const statusPath = statusPathForAgent(lanePaths, wave, agent);
|
|
317
332
|
const statusRecord = readStatusRecordIfPresent(statusPath);
|
|
@@ -343,6 +358,11 @@ function buildLogicalAgents({ lanePaths, wave, tasks, dependencySnapshot, capabi
|
|
|
343
358
|
(assignment) => assignment.blocking && assignment.assignedAgentId === agent.agentId,
|
|
344
359
|
);
|
|
345
360
|
const dependency = openInbound.find((record) => record.assignedAgentId === agent.agentId);
|
|
361
|
+
const satisfiedByStatus =
|
|
362
|
+
statusRecord?.code === 0 &&
|
|
363
|
+
(proofValidation.ok ||
|
|
364
|
+
isSecurityReviewAgent(agent) ||
|
|
365
|
+
isContEvalReportOnlyAgent(agent, { contEvalAgentId: lanePaths.contEvalAgentId }));
|
|
346
366
|
let state = "planned";
|
|
347
367
|
let reason = "";
|
|
348
368
|
if (selection?.source === "active-attempt" && selectedAgentIds.has(agent.agentId)) {
|
|
@@ -354,6 +374,16 @@ function buildLogicalAgents({ lanePaths, wave, tasks, dependencySnapshot, capabi
|
|
|
354
374
|
selection?.source === "relaunch-plan"
|
|
355
375
|
? "Selected by the persisted relaunch plan."
|
|
356
376
|
: "Selected by active rerun request.";
|
|
377
|
+
} else if (completedPhase && satisfiedByStatus) {
|
|
378
|
+
state = [
|
|
379
|
+
lanePaths.contEvalAgentId || "E0",
|
|
380
|
+
lanePaths.integrationAgentId || "A8",
|
|
381
|
+
lanePaths.documentationAgentId || "A9",
|
|
382
|
+
lanePaths.contQaAgentId || "A0",
|
|
383
|
+
].includes(agent.agentId) || isSecurityReviewAgent(agent)
|
|
384
|
+
? "closed"
|
|
385
|
+
: "satisfied";
|
|
386
|
+
reason = "Completed wave preserves the latest satisfied agent state.";
|
|
357
387
|
} else if (targetedBlockingTasks.some((task) => task.state === "working")) {
|
|
358
388
|
state = "working";
|
|
359
389
|
reason = targetedBlockingTasks.find((task) => task.state === "working")?.title || "";
|
|
@@ -365,7 +395,7 @@ function buildLogicalAgents({ lanePaths, wave, tasks, dependencySnapshot, capabi
|
|
|
365
395
|
helperAssignment?.summary ||
|
|
366
396
|
dependency?.summary ||
|
|
367
397
|
"";
|
|
368
|
-
} else if (
|
|
398
|
+
} else if (satisfiedByStatus) {
|
|
369
399
|
state = [
|
|
370
400
|
lanePaths.contEvalAgentId || "E0",
|
|
371
401
|
lanePaths.integrationAgentId || "A8",
|
|
@@ -401,7 +431,19 @@ function selectionTargetsAgent(agentId, selectionSet) {
|
|
|
401
431
|
return Boolean(agentId) && selectionSet.has(agentId);
|
|
402
432
|
}
|
|
403
433
|
|
|
404
|
-
function buildBlockingEdge({
|
|
434
|
+
function buildBlockingEdge({
|
|
435
|
+
tasks,
|
|
436
|
+
capabilityAssignments,
|
|
437
|
+
dependencySnapshot,
|
|
438
|
+
activeAttempt,
|
|
439
|
+
rerunRequest,
|
|
440
|
+
relaunchPlan,
|
|
441
|
+
agentId = "",
|
|
442
|
+
phase,
|
|
443
|
+
}) {
|
|
444
|
+
if (isCompletedPhase(phase)) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
405
447
|
const attemptSelection = new Set(activeAttempt?.selectedAgentIds || []);
|
|
406
448
|
const scopeToActiveAttempt = !agentId && attemptSelection.size > 0;
|
|
407
449
|
const scopedTasks = (agentId
|
|
@@ -546,6 +588,7 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
|
|
|
546
588
|
const logPath = coordinationLogPath(lanePaths, wave.wave);
|
|
547
589
|
const coordinationState = readMaterializedCoordinationState(logPath);
|
|
548
590
|
const ledger = readWaveLedger(ledgerPath(lanePaths, wave.wave)) || { phase: "planned" };
|
|
591
|
+
const phase = ledger.phase || "unknown";
|
|
549
592
|
const capabilityAssignments = buildRequestAssignments({
|
|
550
593
|
coordinationState,
|
|
551
594
|
agents: wave.agents,
|
|
@@ -596,7 +639,7 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
|
|
|
596
639
|
return {
|
|
597
640
|
lane: lanePaths.lane,
|
|
598
641
|
wave: wave.wave,
|
|
599
|
-
phase
|
|
642
|
+
phase,
|
|
600
643
|
agentId: agentId || null,
|
|
601
644
|
blockingEdge: buildBlockingEdge({
|
|
602
645
|
tasks,
|
|
@@ -606,6 +649,7 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
|
|
|
606
649
|
rerunRequest,
|
|
607
650
|
relaunchPlan,
|
|
608
651
|
agentId,
|
|
652
|
+
phase,
|
|
609
653
|
}),
|
|
610
654
|
logicalAgents: buildLogicalAgents({
|
|
611
655
|
lanePaths,
|
|
@@ -615,6 +659,7 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
|
|
|
615
659
|
capabilityAssignments,
|
|
616
660
|
selection,
|
|
617
661
|
proofRegistry,
|
|
662
|
+
phase,
|
|
618
663
|
}).filter((agent) => !agentId || agent.agentId === agentId),
|
|
619
664
|
tasks,
|
|
620
665
|
helperAssignments: (capabilityAssignments || []).filter(
|
|
@@ -634,7 +679,7 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
|
|
|
634
679
|
selectionSource: selection.source,
|
|
635
680
|
rerunRequest,
|
|
636
681
|
relaunchPlan,
|
|
637
|
-
nextTimer: nextTaskDeadline(tasks),
|
|
682
|
+
nextTimer: isCompletedPhase(phase) ? null : nextTaskDeadline(tasks),
|
|
638
683
|
activeAttempt: controlState.activeAttempt,
|
|
639
684
|
};
|
|
640
685
|
}
|
|
@@ -973,6 +1018,13 @@ export async function runControlCli(argv) {
|
|
|
973
1018
|
operator: options.operator,
|
|
974
1019
|
force: true,
|
|
975
1020
|
});
|
|
1021
|
+
answerHumanInputAndReconcile({
|
|
1022
|
+
lanePaths,
|
|
1023
|
+
wave,
|
|
1024
|
+
requestId: options.id,
|
|
1025
|
+
answeredPayload: answered,
|
|
1026
|
+
operator: options.operator,
|
|
1027
|
+
});
|
|
976
1028
|
appendWaveControlEvent(lanePaths, wave.wave, {
|
|
977
1029
|
entityType: "human_input",
|
|
978
1030
|
entityId: options.id,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
writeJsonArtifact,
|
|
24
24
|
} from "./coordination-store.mjs";
|
|
25
25
|
import { answerFeedbackRequest } from "./feedback.mjs";
|
|
26
|
+
import { answerHumanInputAndReconcile } from "./human-input-resolution.mjs";
|
|
26
27
|
import { readWaveHumanFeedbackRequests } from "./coordination.mjs";
|
|
27
28
|
import { readWaveProofRegistry } from "./proof-registry.mjs";
|
|
28
29
|
import {
|
|
@@ -463,6 +464,13 @@ export async function runCoordinationCli(argv) {
|
|
|
463
464
|
force: true,
|
|
464
465
|
recordTelemetry: true,
|
|
465
466
|
});
|
|
467
|
+
answerHumanInputAndReconcile({
|
|
468
|
+
lanePaths,
|
|
469
|
+
wave,
|
|
470
|
+
requestId: options.id,
|
|
471
|
+
answeredPayload: answered,
|
|
472
|
+
operator: options.operator,
|
|
473
|
+
});
|
|
466
474
|
console.log(JSON.stringify(answered, null, 2));
|
|
467
475
|
return;
|
|
468
476
|
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
truncate,
|
|
22
22
|
writeJsonAtomic,
|
|
23
23
|
} from "./shared.mjs";
|
|
24
|
+
import { answerHumanInputByRequest } from "./human-input-resolution.mjs";
|
|
24
25
|
import { safeQueueWaveControlEvent } from "./wave-control-client.mjs";
|
|
25
26
|
|
|
26
27
|
function sanitizeToken(value) {
|
|
@@ -363,7 +364,7 @@ export async function runFeedbackCli(argv) {
|
|
|
363
364
|
if (!options.id || !options.response) {
|
|
364
365
|
throw new Error("respond requires --id and --response");
|
|
365
366
|
}
|
|
366
|
-
answerFeedbackRequest({
|
|
367
|
+
const answered = answerFeedbackRequest({
|
|
367
368
|
feedbackStateDir: stateDir,
|
|
368
369
|
feedbackRequestsDir: requestsDir,
|
|
369
370
|
requestId: options.id,
|
|
@@ -372,6 +373,15 @@ export async function runFeedbackCli(argv) {
|
|
|
372
373
|
force: options.force,
|
|
373
374
|
recordTelemetry: true,
|
|
374
375
|
});
|
|
376
|
+
if (answered?.lane && Number.isFinite(Number(answered.wave))) {
|
|
377
|
+
answerHumanInputByRequest({
|
|
378
|
+
lane: answered.lane,
|
|
379
|
+
waveNumber: Number(answered.wave),
|
|
380
|
+
requestId: options.id,
|
|
381
|
+
operator: options.operator,
|
|
382
|
+
runId: options.runId || null,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
375
385
|
console.log(`[wave-human-feedback] answered ${options.id}`);
|
|
376
386
|
return;
|
|
377
387
|
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
appendCoordinationRecord,
|
|
5
|
+
clarificationClosureCondition,
|
|
6
|
+
clarificationLinkedRequests,
|
|
7
|
+
isOpenCoordinationStatus,
|
|
8
|
+
readMaterializedCoordinationState,
|
|
9
|
+
} from "./coordination-store.mjs";
|
|
10
|
+
import { readWaveHumanFeedbackRequests } from "./coordination.mjs";
|
|
11
|
+
import {
|
|
12
|
+
readControlPlaneEvents,
|
|
13
|
+
readWaveControlPlaneState,
|
|
14
|
+
syncWaveControlPlaneProjections,
|
|
15
|
+
waveControlPlaneLogPath,
|
|
16
|
+
} from "./control-plane.mjs";
|
|
17
|
+
import { readWaveLedger } from "./ledger.mjs";
|
|
18
|
+
import { readRunExecutionSummary } from "./launcher-gates.mjs";
|
|
19
|
+
import { buildResumePlan, clearWaveRelaunchPlan } from "./launcher-retry.mjs";
|
|
20
|
+
import { readWaveProofRegistry } from "./proof-registry.mjs";
|
|
21
|
+
import { buildDependencySnapshot, buildRequestAssignments, syncAssignmentRecords } from "./routing-state.mjs";
|
|
22
|
+
import { buildLanePaths } from "./shared.mjs";
|
|
23
|
+
import { reduceWaveState } from "./wave-state-reducer.mjs";
|
|
24
|
+
import { parseWaveFiles } from "./wave-files.mjs";
|
|
25
|
+
import { writeWaveRetryOverride } from "./retry-control.mjs";
|
|
26
|
+
|
|
27
|
+
function coordinationLogPath(lanePaths, waveNumber) {
|
|
28
|
+
return path.join(lanePaths.coordinationDir, `wave-${waveNumber}.jsonl`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ledgerPath(lanePaths, waveNumber) {
|
|
32
|
+
return path.join(lanePaths.ledgerDir, `wave-${waveNumber}.json`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function coordinationTriagePath(lanePaths, waveNumber) {
|
|
36
|
+
return path.join(lanePaths.feedbackTriageDir, `wave-${waveNumber}.jsonl`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function appendCoordinationStatusUpdate(logPath, record, status, options = {}) {
|
|
40
|
+
return appendCoordinationRecord(logPath, {
|
|
41
|
+
...record,
|
|
42
|
+
status,
|
|
43
|
+
summary: options.summary || record.summary,
|
|
44
|
+
detail: options.detail || record.detail,
|
|
45
|
+
source: options.source || "operator",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function appendTriageEscalationUpdateIfPresent(lanePaths, waveNumber, record) {
|
|
50
|
+
const triagePath = coordinationTriagePath(lanePaths, waveNumber);
|
|
51
|
+
if (!fs.existsSync(triagePath) || record?.kind !== "human-escalation") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
appendCoordinationRecord(triagePath, record);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function loadWave(lanePaths, waveNumber) {
|
|
58
|
+
const waves = parseWaveFiles(lanePaths.wavesDir, { laneProfile: lanePaths.laneProfile });
|
|
59
|
+
const wave = waves.find((entry) => entry.wave === waveNumber);
|
|
60
|
+
if (!wave) {
|
|
61
|
+
throw new Error(`Wave ${waveNumber} not found in ${lanePaths.wavesDir}`);
|
|
62
|
+
}
|
|
63
|
+
return wave;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function taskRunInfoForAgent(lanePaths, wave, agent, proofRegistry) {
|
|
67
|
+
const safeName = `wave-${wave.wave}-${agent.slug}`;
|
|
68
|
+
return {
|
|
69
|
+
agent,
|
|
70
|
+
logPath: path.join(lanePaths.logsDir, `${safeName}.log`),
|
|
71
|
+
statusPath: path.join(lanePaths.statusDir, `${safeName}.status`),
|
|
72
|
+
proofRegistry,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function feedbackLinkMatchesRecord(record, requestId) {
|
|
77
|
+
const normalizedRequestId = String(requestId || "").trim();
|
|
78
|
+
if (!normalizedRequestId) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (String(record?.id || "").trim() === normalizedRequestId) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
Array.isArray(record?.artifactRefs) &&
|
|
86
|
+
record.artifactRefs.some((ref) => String(ref || "").trim() === normalizedRequestId)
|
|
87
|
+
) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (
|
|
91
|
+
Array.isArray(record?.dependsOn) &&
|
|
92
|
+
record.dependsOn.some((value) => String(value || "").trim() === normalizedRequestId)
|
|
93
|
+
) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function linkedClarificationIdsForRecords(records) {
|
|
100
|
+
const ids = new Set();
|
|
101
|
+
for (const record of records || []) {
|
|
102
|
+
if (record?.kind === "clarification-request" && record?.id) {
|
|
103
|
+
ids.add(record.id);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const closureCondition = String(record?.closureCondition || "").trim();
|
|
107
|
+
if (closureCondition.startsWith("clarification:")) {
|
|
108
|
+
ids.add(closureCondition.slice("clarification:".length));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [...ids].filter(Boolean);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function resolveFeedbackLinkedCoordination({
|
|
115
|
+
lanePaths,
|
|
116
|
+
wave,
|
|
117
|
+
requestId,
|
|
118
|
+
operator = "human-operator",
|
|
119
|
+
detail = "",
|
|
120
|
+
}) {
|
|
121
|
+
const logPath = coordinationLogPath(lanePaths, wave.wave);
|
|
122
|
+
const state = readMaterializedCoordinationState(logPath);
|
|
123
|
+
const resolvedRecords = [];
|
|
124
|
+
|
|
125
|
+
const directlyLinked = state.latestRecords.filter((record) =>
|
|
126
|
+
isOpenCoordinationStatus(record.status) && feedbackLinkMatchesRecord(record, requestId),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
for (const record of directlyLinked) {
|
|
130
|
+
const updated = appendCoordinationStatusUpdate(logPath, record, "resolved", {
|
|
131
|
+
detail: detail || `Resolved after answered human input ${requestId}.`,
|
|
132
|
+
summary: record.summary,
|
|
133
|
+
source: operator,
|
|
134
|
+
});
|
|
135
|
+
resolvedRecords.push(updated);
|
|
136
|
+
appendTriageEscalationUpdateIfPresent(lanePaths, wave.wave, updated);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const nextState = readMaterializedCoordinationState(logPath);
|
|
140
|
+
const clarificationIds = linkedClarificationIdsForRecords([
|
|
141
|
+
...directlyLinked,
|
|
142
|
+
...resolvedRecords,
|
|
143
|
+
]);
|
|
144
|
+
for (const clarificationId of clarificationIds) {
|
|
145
|
+
const clarification = nextState.byId.get(clarificationId);
|
|
146
|
+
if (!clarification || !isOpenCoordinationStatus(clarification.status)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const updatedClarification = appendCoordinationStatusUpdate(logPath, clarification, "resolved", {
|
|
150
|
+
detail: detail || `Resolved after answered human input ${requestId}.`,
|
|
151
|
+
summary: clarification.summary,
|
|
152
|
+
source: operator,
|
|
153
|
+
});
|
|
154
|
+
resolvedRecords.push(updatedClarification);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const resolvedState = readMaterializedCoordinationState(logPath);
|
|
158
|
+
for (const clarificationId of clarificationIds) {
|
|
159
|
+
const linkedRequests = clarificationLinkedRequests(resolvedState, clarificationId).filter((entry) =>
|
|
160
|
+
isOpenCoordinationStatus(entry.status),
|
|
161
|
+
);
|
|
162
|
+
for (const linked of linkedRequests) {
|
|
163
|
+
const updatedLinked = appendCoordinationStatusUpdate(logPath, linked, "resolved", {
|
|
164
|
+
detail: `Resolved via clarification ${clarificationId}.`,
|
|
165
|
+
summary: linked.summary,
|
|
166
|
+
source: operator,
|
|
167
|
+
});
|
|
168
|
+
resolvedRecords.push(updatedLinked);
|
|
169
|
+
}
|
|
170
|
+
for (const escalation of (resolvedState.humanEscalations || []).filter(
|
|
171
|
+
(entry) =>
|
|
172
|
+
isOpenCoordinationStatus(entry.status) &&
|
|
173
|
+
entry.closureCondition === clarificationClosureCondition(clarificationId),
|
|
174
|
+
)) {
|
|
175
|
+
const updatedEscalation = appendCoordinationStatusUpdate(logPath, escalation, "resolved", {
|
|
176
|
+
detail: detail || `Resolved via clarification ${clarificationId}.`,
|
|
177
|
+
summary: escalation.summary,
|
|
178
|
+
source: operator,
|
|
179
|
+
});
|
|
180
|
+
resolvedRecords.push(updatedEscalation);
|
|
181
|
+
appendTriageEscalationUpdateIfPresent(lanePaths, wave.wave, updatedEscalation);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const assignmentState = readMaterializedCoordinationState(logPath);
|
|
186
|
+
const ledger = readWaveLedger(ledgerPath(lanePaths, wave.wave)) || { phase: "planned" };
|
|
187
|
+
const assignments = buildRequestAssignments({
|
|
188
|
+
coordinationState: assignmentState,
|
|
189
|
+
agents: wave.agents,
|
|
190
|
+
ledger,
|
|
191
|
+
capabilityRouting: lanePaths.capabilityRouting,
|
|
192
|
+
});
|
|
193
|
+
syncAssignmentRecords(logPath, {
|
|
194
|
+
lane: lanePaths.lane,
|
|
195
|
+
wave: wave.wave,
|
|
196
|
+
assignments,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
resolvedRecords,
|
|
201
|
+
clarificationIds,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function buildResumePlanFromDisk({ lanePaths, wave }) {
|
|
206
|
+
const proofRegistry = readWaveProofRegistry(lanePaths, wave.wave);
|
|
207
|
+
const agentRuns = wave.agents.map((agent) =>
|
|
208
|
+
taskRunInfoForAgent(lanePaths, wave, agent, proofRegistry),
|
|
209
|
+
);
|
|
210
|
+
const agentResults = Object.fromEntries(
|
|
211
|
+
agentRuns
|
|
212
|
+
.map((runInfo) => [runInfo.agent.agentId, readRunExecutionSummary(runInfo, wave)])
|
|
213
|
+
.filter(([, summary]) => Boolean(summary)),
|
|
214
|
+
);
|
|
215
|
+
const coordinationState = readMaterializedCoordinationState(coordinationLogPath(lanePaths, wave.wave));
|
|
216
|
+
const feedbackRequests = readWaveHumanFeedbackRequests({
|
|
217
|
+
feedbackRequestsDir: lanePaths.feedbackRequestsDir,
|
|
218
|
+
lane: lanePaths.lane,
|
|
219
|
+
waveNumber: wave.wave,
|
|
220
|
+
agentIds: wave.agents.map((agent) => agent.agentId),
|
|
221
|
+
orchestratorId: "",
|
|
222
|
+
});
|
|
223
|
+
const ledger = readWaveLedger(ledgerPath(lanePaths, wave.wave)) || { phase: "planned" };
|
|
224
|
+
const dependencySnapshot = buildDependencySnapshot({
|
|
225
|
+
dirPath: lanePaths.crossLaneDependenciesDir,
|
|
226
|
+
lane: lanePaths.lane,
|
|
227
|
+
waveNumber: wave.wave,
|
|
228
|
+
agents: wave.agents,
|
|
229
|
+
ledger,
|
|
230
|
+
capabilityRouting: lanePaths.capabilityRouting,
|
|
231
|
+
});
|
|
232
|
+
const reducerState = reduceWaveState({
|
|
233
|
+
controlPlaneEvents: readControlPlaneEvents(waveControlPlaneLogPath(lanePaths, wave.wave)),
|
|
234
|
+
coordinationRecords: coordinationState.latestRecords || [],
|
|
235
|
+
agentResults,
|
|
236
|
+
waveDefinition: wave,
|
|
237
|
+
dependencyTickets: dependencySnapshot,
|
|
238
|
+
feedbackRequests,
|
|
239
|
+
laneConfig: {
|
|
240
|
+
lane: lanePaths.lane,
|
|
241
|
+
contQaAgentId: lanePaths.contQaAgentId || "A0",
|
|
242
|
+
contEvalAgentId: lanePaths.contEvalAgentId || "E0",
|
|
243
|
+
integrationAgentId: lanePaths.integrationAgentId || "A8",
|
|
244
|
+
documentationAgentId: lanePaths.documentationAgentId || "A9",
|
|
245
|
+
validationMode: "live",
|
|
246
|
+
evalTargets: wave.evalTargets,
|
|
247
|
+
benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
|
|
248
|
+
laneProfile: lanePaths.laneProfile,
|
|
249
|
+
requireIntegrationStewardFromWave: lanePaths.requireIntegrationStewardFromWave,
|
|
250
|
+
capabilityRouting: lanePaths.capabilityRouting,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
return buildResumePlan(reducerState, {
|
|
254
|
+
waveDefinition: wave,
|
|
255
|
+
lanePaths,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function maybeWriteAutoResumeRequest({
|
|
260
|
+
lanePaths,
|
|
261
|
+
wave,
|
|
262
|
+
requestedBy = "human-operator",
|
|
263
|
+
reason = "",
|
|
264
|
+
}) {
|
|
265
|
+
const controlState = readWaveControlPlaneState(lanePaths, wave.wave);
|
|
266
|
+
if (controlState.activeAttempt || controlState.activeRerunRequest) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const resumePlan = buildResumePlanFromDisk({ lanePaths, wave });
|
|
270
|
+
if (!resumePlan.canResume || resumePlan.reason === "human-request") {
|
|
271
|
+
return { resumePlan, request: null };
|
|
272
|
+
}
|
|
273
|
+
if (resumePlan.resumeFromPhase === "completed") {
|
|
274
|
+
return { resumePlan, request: null };
|
|
275
|
+
}
|
|
276
|
+
clearWaveRelaunchPlan(lanePaths, wave.wave);
|
|
277
|
+
const payload = {
|
|
278
|
+
requestedBy,
|
|
279
|
+
reason:
|
|
280
|
+
reason ||
|
|
281
|
+
`Auto continuation after answered human input; resume from ${resumePlan.resumeFromPhase}.`,
|
|
282
|
+
preserveReusableAgentIds: resumePlan.reusableAgentIds,
|
|
283
|
+
reuseProofBundleIds: resumePlan.reusableProofBundleIds,
|
|
284
|
+
applyOnce: true,
|
|
285
|
+
};
|
|
286
|
+
if (resumePlan.resumeFromPhase === "implementation") {
|
|
287
|
+
payload.selectedAgentIds = resumePlan.invalidatedAgentIds;
|
|
288
|
+
} else {
|
|
289
|
+
payload.resumePhase = resumePlan.resumeFromPhase;
|
|
290
|
+
}
|
|
291
|
+
const request = writeWaveRetryOverride(lanePaths, wave.wave, payload);
|
|
292
|
+
return { resumePlan, request };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function answerHumanInputAndReconcile({
|
|
296
|
+
lanePaths,
|
|
297
|
+
wave,
|
|
298
|
+
requestId,
|
|
299
|
+
answeredPayload,
|
|
300
|
+
operator = "human-operator",
|
|
301
|
+
}) {
|
|
302
|
+
const detail =
|
|
303
|
+
`Resolved after human input ${requestId} was answered by ${operator}` +
|
|
304
|
+
(answeredPayload?.response?.text ? `: ${answeredPayload.response.text}` : ".");
|
|
305
|
+
const resolution = resolveFeedbackLinkedCoordination({
|
|
306
|
+
lanePaths,
|
|
307
|
+
wave,
|
|
308
|
+
requestId,
|
|
309
|
+
operator,
|
|
310
|
+
detail,
|
|
311
|
+
});
|
|
312
|
+
syncWaveControlPlaneProjections(
|
|
313
|
+
lanePaths,
|
|
314
|
+
wave.wave,
|
|
315
|
+
readWaveControlPlaneState(lanePaths, wave.wave),
|
|
316
|
+
);
|
|
317
|
+
const autoResume = maybeWriteAutoResumeRequest({
|
|
318
|
+
lanePaths,
|
|
319
|
+
wave,
|
|
320
|
+
requestedBy: operator,
|
|
321
|
+
reason: `Auto continuation after answered human input ${requestId}.`,
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
resolution,
|
|
325
|
+
autoResume,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function answerHumanInputByRequest({
|
|
330
|
+
lane,
|
|
331
|
+
waveNumber,
|
|
332
|
+
requestId,
|
|
333
|
+
operator = "human-operator",
|
|
334
|
+
runId = null,
|
|
335
|
+
}) {
|
|
336
|
+
const lanePaths = buildLanePaths(lane, { adhocRunId: runId || null });
|
|
337
|
+
const wave = loadWave(lanePaths, waveNumber);
|
|
338
|
+
return answerHumanInputAndReconcile({
|
|
339
|
+
lanePaths,
|
|
340
|
+
wave,
|
|
341
|
+
requestId,
|
|
342
|
+
operator,
|
|
343
|
+
});
|
|
344
|
+
}
|