@chllming/wave-orchestration 0.8.9 → 0.9.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 +57 -0
- package/README.md +135 -18
- package/docs/README.md +9 -3
- package/docs/architecture/README.md +1498 -0
- package/docs/concepts/context7-vs-skills.md +1 -1
- package/docs/concepts/operating-modes.md +3 -3
- package/docs/concepts/what-is-a-wave.md +1 -1
- package/docs/guides/author-and-run-waves.md +27 -4
- package/docs/guides/monorepo-projects.md +226 -0
- package/docs/guides/planner.md +10 -3
- package/docs/guides/{recommendations-0.8.9.md → recommendations-0.9.1.md} +8 -7
- package/docs/guides/sandboxed-environments.md +158 -0
- package/docs/guides/terminal-surfaces.md +14 -12
- package/docs/plans/current-state.md +11 -7
- package/docs/plans/end-state-architecture.md +3 -1
- package/docs/plans/examples/wave-example-design-handoff.md +3 -1
- package/docs/plans/examples/wave-example-live-proof.md +6 -1
- package/docs/plans/examples/wave-example-rollout-fidelity.md +2 -0
- package/docs/plans/migration.md +48 -18
- package/docs/plans/sandbox-end-state-architecture.md +153 -0
- package/docs/plans/wave-orchestrator.md +4 -4
- package/docs/reference/cli-reference.md +125 -57
- package/docs/reference/coordination-and-closure.md +1 -1
- package/docs/reference/github-packages-setup.md +1 -1
- package/docs/reference/migration-0.2-to-0.5.md +9 -7
- package/docs/reference/npmjs-token-publishing.md +53 -0
- package/docs/reference/npmjs-trusted-publishing.md +4 -50
- package/docs/reference/package-publishing-flow.md +272 -0
- package/docs/reference/runtime-config/README.md +140 -12
- package/docs/reference/sample-waves.md +100 -5
- package/docs/reference/skills.md +1 -1
- package/docs/reference/wave-control.md +23 -5
- package/docs/roadmap.md +43 -201
- package/package.json +1 -1
- package/releases/manifest.json +38 -0
- package/scripts/wave-orchestrator/adhoc.mjs +49 -17
- package/scripts/wave-orchestrator/agent-process-runner.mjs +344 -0
- package/scripts/wave-orchestrator/agent-state.mjs +0 -1
- package/scripts/wave-orchestrator/artifact-schemas.mjs +7 -0
- package/scripts/wave-orchestrator/autonomous.mjs +96 -29
- package/scripts/wave-orchestrator/benchmark-external.mjs +23 -7
- package/scripts/wave-orchestrator/benchmark.mjs +33 -10
- package/scripts/wave-orchestrator/closure-engine.mjs +138 -17
- package/scripts/wave-orchestrator/config.mjs +239 -24
- package/scripts/wave-orchestrator/control-cli.mjs +71 -28
- package/scripts/wave-orchestrator/coord-cli.mjs +22 -14
- package/scripts/wave-orchestrator/coordination-store.mjs +8 -0
- package/scripts/wave-orchestrator/dashboard-renderer.mjs +123 -44
- package/scripts/wave-orchestrator/dep-cli.mjs +47 -21
- package/scripts/wave-orchestrator/derived-state-engine.mjs +6 -3
- package/scripts/wave-orchestrator/feedback.mjs +28 -11
- package/scripts/wave-orchestrator/gate-engine.mjs +106 -38
- package/scripts/wave-orchestrator/human-input-resolution.mjs +5 -1
- package/scripts/wave-orchestrator/install.mjs +13 -0
- package/scripts/wave-orchestrator/launcher-progress.mjs +91 -0
- package/scripts/wave-orchestrator/launcher-runtime.mjs +179 -68
- package/scripts/wave-orchestrator/launcher.mjs +222 -53
- package/scripts/wave-orchestrator/ledger.mjs +7 -2
- package/scripts/wave-orchestrator/planner.mjs +48 -27
- package/scripts/wave-orchestrator/project-profile.mjs +31 -8
- package/scripts/wave-orchestrator/projection-writer.mjs +13 -1
- package/scripts/wave-orchestrator/proof-cli.mjs +18 -12
- package/scripts/wave-orchestrator/reducer-snapshot.mjs +6 -0
- package/scripts/wave-orchestrator/retry-cli.mjs +19 -13
- package/scripts/wave-orchestrator/retry-control.mjs +3 -3
- package/scripts/wave-orchestrator/retry-engine.mjs +93 -6
- package/scripts/wave-orchestrator/role-helpers.mjs +30 -0
- package/scripts/wave-orchestrator/session-supervisor.mjs +94 -85
- package/scripts/wave-orchestrator/shared.mjs +77 -14
- package/scripts/wave-orchestrator/supervisor-cli.mjs +1306 -0
- package/scripts/wave-orchestrator/terminals.mjs +12 -32
- package/scripts/wave-orchestrator/tmux-adapter.mjs +300 -0
- package/scripts/wave-orchestrator/wave-control-client.mjs +84 -16
- package/scripts/wave-orchestrator/wave-files.mjs +43 -6
- package/scripts/wave.mjs +13 -0
|
@@ -35,22 +35,21 @@ import { writeAssignmentSnapshot, writeDependencySnapshot } from "./artifact-sch
|
|
|
35
35
|
import {
|
|
36
36
|
buildLanePaths,
|
|
37
37
|
ensureDirectory,
|
|
38
|
+
findAdhocRunRecord,
|
|
38
39
|
parseNonNegativeInt,
|
|
39
|
-
readJsonOrNull,
|
|
40
|
-
REPO_ROOT,
|
|
41
40
|
sanitizeAdhocRunId,
|
|
42
41
|
} from "./shared.mjs";
|
|
43
42
|
import { parseWaveFiles } from "./wave-files.mjs";
|
|
44
43
|
|
|
45
44
|
function printUsage() {
|
|
46
45
|
console.log(`Usage:
|
|
47
|
-
wave coord post --lane <lane> --wave <n> --agent <id> --kind <kind> --summary <text> [--dry-run] [options]
|
|
48
|
-
wave coord show --lane <lane> --wave <n> [--dry-run] [--json]
|
|
49
|
-
wave coord render --lane <lane> --wave <n> [--dry-run]
|
|
50
|
-
wave coord inbox --lane <lane> --wave <n> --agent <id> [--dry-run]
|
|
51
|
-
wave coord explain --lane <lane> --wave <n> [--agent <id>] [--json]
|
|
52
|
-
wave coord act <resolve|dismiss|reroute|reassign|escalate|answer-human> --lane <lane> --wave <n> [options]
|
|
53
|
-
wave coord <subcommand> --run <id> [--wave 0] ...
|
|
46
|
+
wave coord post --project <id> --lane <lane> --wave <n> --agent <id> --kind <kind> --summary <text> [--dry-run] [options]
|
|
47
|
+
wave coord show --project <id> --lane <lane> --wave <n> [--dry-run] [--json]
|
|
48
|
+
wave coord render --project <id> --lane <lane> --wave <n> [--dry-run]
|
|
49
|
+
wave coord inbox --project <id> --lane <lane> --wave <n> --agent <id> [--dry-run]
|
|
50
|
+
wave coord explain --project <id> --lane <lane> --wave <n> [--agent <id>] [--json]
|
|
51
|
+
wave coord act <resolve|dismiss|reroute|reassign|escalate|answer-human> --project <id> --lane <lane> --wave <n> [options]
|
|
52
|
+
wave coord <subcommand> --run <id> [--project <id>] [--wave 0] ...
|
|
54
53
|
`);
|
|
55
54
|
}
|
|
56
55
|
|
|
@@ -58,6 +57,7 @@ function parseArgs(argv) {
|
|
|
58
57
|
const args = argv[0] === "--" ? argv.slice(1) : argv;
|
|
59
58
|
const subcommand = String(args[0] || "").trim().toLowerCase();
|
|
60
59
|
const options = {
|
|
60
|
+
project: "",
|
|
61
61
|
lane: "main",
|
|
62
62
|
wave: null,
|
|
63
63
|
runId: "",
|
|
@@ -85,7 +85,9 @@ function parseArgs(argv) {
|
|
|
85
85
|
}
|
|
86
86
|
for (let i = startIndex; i < args.length; i += 1) {
|
|
87
87
|
const arg = args[i];
|
|
88
|
-
if (arg === "--
|
|
88
|
+
if (arg === "--project") {
|
|
89
|
+
options.project = String(args[++i] || "").trim();
|
|
90
|
+
} else if (arg === "--lane") {
|
|
89
91
|
options.lane = String(args[++i] || "").trim();
|
|
90
92
|
} else if (arg === "--run") {
|
|
91
93
|
options.runId = sanitizeAdhocRunId(args[++i]);
|
|
@@ -131,9 +133,12 @@ function parseArgs(argv) {
|
|
|
131
133
|
return { subcommand, options };
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
function
|
|
135
|
-
const
|
|
136
|
-
return
|
|
136
|
+
function resolveRunContext(runId, fallbackProject, fallbackLane) {
|
|
137
|
+
const record = findAdhocRunRecord(runId);
|
|
138
|
+
return {
|
|
139
|
+
project: record?.project || fallbackProject,
|
|
140
|
+
lane: record?.result?.lane || fallbackLane,
|
|
141
|
+
};
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
function loadWave(lanePaths, waveNumber) {
|
|
@@ -329,9 +334,12 @@ export async function runCoordinationCli(argv) {
|
|
|
329
334
|
}
|
|
330
335
|
const { subcommand, options } = parseArgs(argv);
|
|
331
336
|
if (options.runId) {
|
|
332
|
-
|
|
337
|
+
const context = resolveRunContext(options.runId, options.project, options.lane);
|
|
338
|
+
options.project = context.project;
|
|
339
|
+
options.lane = context.lane;
|
|
333
340
|
}
|
|
334
341
|
const lanePaths = buildLanePaths(options.lane, {
|
|
342
|
+
project: options.project || undefined,
|
|
335
343
|
runVariant: options.dryRun ? "dry-run" : undefined,
|
|
336
344
|
adhocRunId: options.runId || null,
|
|
337
345
|
});
|
|
@@ -242,8 +242,11 @@ export function normalizeCoordinationRecord(rawRecord, defaults = {}) {
|
|
|
242
242
|
: Number.parseInt(String(rawRecord.attempt), 10),
|
|
243
243
|
source: normalizeString(rawRecord.source ?? defaults.source, "launcher"),
|
|
244
244
|
executorId: normalizeString(rawRecord.executorId ?? defaults.executorId, ""),
|
|
245
|
+
project: normalizeString(rawRecord.project ?? defaults.project, ""),
|
|
245
246
|
requesterLane: normalizeString(rawRecord.requesterLane ?? defaults.requesterLane, ""),
|
|
246
247
|
ownerLane: normalizeString(rawRecord.ownerLane ?? defaults.ownerLane, ""),
|
|
248
|
+
requesterProject: normalizeString(rawRecord.requesterProject ?? defaults.requesterProject, ""),
|
|
249
|
+
ownerProject: normalizeString(rawRecord.ownerProject ?? defaults.ownerProject, ""),
|
|
247
250
|
requesterWave:
|
|
248
251
|
rawRecord.requesterWave === null || rawRecord.requesterWave === undefined || rawRecord.requesterWave === ""
|
|
249
252
|
? defaults.requesterWave ?? null
|
|
@@ -264,8 +267,10 @@ export function appendCoordinationRecord(filePath, rawRecord, defaults = {}) {
|
|
|
264
267
|
ensureDirectory(path.dirname(filePath));
|
|
265
268
|
fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`, "utf8");
|
|
266
269
|
const runIdHint = normalizeString(rawRecord?.runId ?? defaults.runId, "");
|
|
270
|
+
const projectHint = normalizeString(rawRecord?.project ?? defaults.project, "");
|
|
267
271
|
try {
|
|
268
272
|
const lanePaths = buildLanePaths(record.lane, {
|
|
273
|
+
...(projectHint ? { project: projectHint } : {}),
|
|
269
274
|
...(runIdHint ? { adhocRunId: runIdHint } : {}),
|
|
270
275
|
});
|
|
271
276
|
if (lanePaths?.waveControl?.captureCoordinationRecords !== false) {
|
|
@@ -301,8 +306,11 @@ export function appendCoordinationRecord(filePath, rawRecord, defaults = {}) {
|
|
|
301
306
|
closureCondition: record.closureCondition,
|
|
302
307
|
required: record.required,
|
|
303
308
|
executorId: record.executorId || null,
|
|
309
|
+
project: record.project || null,
|
|
304
310
|
requesterLane: record.requesterLane || null,
|
|
305
311
|
ownerLane: record.ownerLane || null,
|
|
312
|
+
requesterProject: record.requesterProject || null,
|
|
313
|
+
ownerProject: record.ownerProject || null,
|
|
306
314
|
},
|
|
307
315
|
});
|
|
308
316
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { loadWaveConfig } from "./config.mjs";
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
formatAgeFromTimestamp,
|
|
15
14
|
formatElapsed,
|
|
16
15
|
pad,
|
|
16
|
+
readJsonOrNull,
|
|
17
17
|
sleep,
|
|
18
18
|
truncate,
|
|
19
19
|
} from "./shared.mjs";
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
createCurrentWaveDashboardTerminalEntry,
|
|
22
22
|
createGlobalDashboardTerminalEntry,
|
|
23
23
|
} from "./terminals.mjs";
|
|
24
|
+
import {
|
|
25
|
+
attachSession as attachTmuxSession,
|
|
26
|
+
hasSession as hasTmuxSession,
|
|
27
|
+
} from "./tmux-adapter.mjs";
|
|
24
28
|
|
|
25
29
|
const DASHBOARD_ATTACH_TARGETS = ["current", "global"];
|
|
26
30
|
|
|
@@ -36,6 +40,7 @@ function normalizeDashboardAttachTarget(value) {
|
|
|
36
40
|
|
|
37
41
|
export function parseDashboardArgs(argv) {
|
|
38
42
|
const options = {
|
|
43
|
+
project: null,
|
|
39
44
|
lane: DEFAULT_WAVE_LANE,
|
|
40
45
|
dashboardFile: null,
|
|
41
46
|
messageBoard: null,
|
|
@@ -50,6 +55,8 @@ export function parseDashboardArgs(argv) {
|
|
|
50
55
|
}
|
|
51
56
|
if (arg === "--watch") {
|
|
52
57
|
options.watch = true;
|
|
58
|
+
} else if (arg === "--project") {
|
|
59
|
+
options.project = String(argv[++i] || "").trim() || null;
|
|
53
60
|
} else if (arg === "--lane") {
|
|
54
61
|
options.lane =
|
|
55
62
|
String(argv[++i] || "")
|
|
@@ -75,55 +82,122 @@ export function parseDashboardArgs(argv) {
|
|
|
75
82
|
return { help: false, options };
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
function
|
|
79
|
-
const result = spawnSync("tmux", ["-L", socketName, "has-session", "-t", sessionName], {
|
|
80
|
-
cwd: REPO_ROOT,
|
|
81
|
-
encoding: "utf8",
|
|
82
|
-
env: { ...process.env, TMUX: "" },
|
|
83
|
-
});
|
|
84
|
-
if (result.error) {
|
|
85
|
-
throw new Error(`tmux session lookup failed: ${result.error.message}`);
|
|
86
|
-
}
|
|
87
|
-
if (result.status === 0) {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
const combined = `${String(result.stderr || "").toLowerCase()}\n${String(result.stdout || "").toLowerCase()}`;
|
|
91
|
-
if (
|
|
92
|
-
combined.includes("can't find session") ||
|
|
93
|
-
combined.includes("no server running") ||
|
|
94
|
-
combined.includes("error connecting")
|
|
95
|
-
) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
throw new Error((result.stderr || result.stdout || "tmux has-session failed").trim());
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function attachDashboardSession(lane, target) {
|
|
85
|
+
async function attachDashboardSession(project, lane, target) {
|
|
102
86
|
const config = loadWaveConfig();
|
|
103
|
-
const lanePaths = buildLanePaths(lane, {
|
|
87
|
+
const lanePaths = buildLanePaths(lane, {
|
|
88
|
+
config,
|
|
89
|
+
project: project || config.defaultProject,
|
|
90
|
+
});
|
|
104
91
|
const entry =
|
|
105
92
|
target === "global"
|
|
106
93
|
? createGlobalDashboardTerminalEntry(lanePaths, "current")
|
|
107
94
|
: createCurrentWaveDashboardTerminalEntry(lanePaths);
|
|
108
|
-
if (!
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
if (!await hasTmuxSession(lanePaths.tmuxSocketName, entry.sessionName, { allowMissingBinary: false })) {
|
|
96
|
+
const fallback = resolveDashboardAttachFallback(lanePaths, target);
|
|
97
|
+
if (fallback) {
|
|
98
|
+
return fallback;
|
|
99
|
+
}
|
|
100
|
+
throw new Error(buildMissingDashboardAttachError(lanePaths, target));
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
await attachTmuxSession(lanePaths.tmuxSocketName, entry.sessionName);
|
|
104
|
+
return null;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error?.tmuxMissingSession) {
|
|
107
|
+
const fallback = resolveDashboardAttachFallback(lanePaths, target);
|
|
108
|
+
if (fallback) {
|
|
109
|
+
return fallback;
|
|
110
|
+
}
|
|
111
|
+
throw new Error(buildMissingDashboardAttachError(lanePaths, target));
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildMissingDashboardAttachError(lanePaths, target) {
|
|
118
|
+
const dashboardsRel = path.relative(REPO_ROOT, path.dirname(lanePaths.globalDashboardPath));
|
|
119
|
+
return `No ${target} dashboard session is live for lane ${lanePaths.lane}. Launch a dashboarded run on that lane, then inspect ${dashboardsRel} if you need the last written dashboard state.`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function waveDashboardPathForNumber(lanePaths, waveNumber) {
|
|
123
|
+
if (!Number.isFinite(Number(waveNumber))) {
|
|
124
|
+
return null;
|
|
113
125
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
const candidate = path.join(lanePaths.dashboardsDir, `wave-${Number(waveNumber)}.json`);
|
|
127
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function selectCurrentWaveFromGlobalDashboard(globalState) {
|
|
131
|
+
const waves = Array.isArray(globalState?.waves) ? globalState.waves : [];
|
|
132
|
+
const candidates = waves
|
|
133
|
+
.map((wave) => ({
|
|
134
|
+
waveNumber: Number.parseInt(String(wave?.wave ?? ""), 10),
|
|
135
|
+
status: String(wave?.status || "").trim().toLowerCase(),
|
|
136
|
+
updatedAt: Date.parse(
|
|
137
|
+
String(wave?.updatedAt || wave?.completedAt || wave?.startedAt || ""),
|
|
138
|
+
),
|
|
139
|
+
}))
|
|
140
|
+
.filter((entry) => Number.isFinite(entry.waveNumber));
|
|
141
|
+
if (candidates.length === 0) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
candidates.sort((left, right) => {
|
|
145
|
+
const leftTerminal = TERMINAL_STATES.has(left.status);
|
|
146
|
+
const rightTerminal = TERMINAL_STATES.has(right.status);
|
|
147
|
+
if (leftTerminal !== rightTerminal) {
|
|
148
|
+
return leftTerminal ? 1 : -1;
|
|
149
|
+
}
|
|
150
|
+
const leftUpdatedAt = Number.isFinite(left.updatedAt) ? left.updatedAt : 0;
|
|
151
|
+
const rightUpdatedAt = Number.isFinite(right.updatedAt) ? right.updatedAt : 0;
|
|
152
|
+
if (leftUpdatedAt !== rightUpdatedAt) {
|
|
153
|
+
return rightUpdatedAt - leftUpdatedAt;
|
|
154
|
+
}
|
|
155
|
+
return right.waveNumber - left.waveNumber;
|
|
118
156
|
});
|
|
119
|
-
|
|
120
|
-
|
|
157
|
+
return candidates[0].waveNumber;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function resolveDashboardAttachFallback(lanePaths, target) {
|
|
161
|
+
if (target === "global") {
|
|
162
|
+
return fs.existsSync(lanePaths.globalDashboardPath)
|
|
163
|
+
? { dashboardFile: lanePaths.globalDashboardPath }
|
|
164
|
+
: null;
|
|
121
165
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
166
|
+
const globalState = readJsonOrNull(lanePaths.globalDashboardPath);
|
|
167
|
+
const preferredWaveNumber = selectCurrentWaveFromGlobalDashboard(globalState);
|
|
168
|
+
const preferredWavePath = waveDashboardPathForNumber(lanePaths, preferredWaveNumber);
|
|
169
|
+
if (preferredWavePath) {
|
|
170
|
+
return { dashboardFile: preferredWavePath };
|
|
126
171
|
}
|
|
172
|
+
if (!fs.existsSync(lanePaths.dashboardsDir)) {
|
|
173
|
+
return fs.existsSync(lanePaths.globalDashboardPath)
|
|
174
|
+
? { dashboardFile: lanePaths.globalDashboardPath }
|
|
175
|
+
: null;
|
|
176
|
+
}
|
|
177
|
+
const candidates = fs.readdirSync(lanePaths.dashboardsDir, { withFileTypes: true })
|
|
178
|
+
.filter((entry) => entry.isFile())
|
|
179
|
+
.map((entry) => ({
|
|
180
|
+
filePath: path.join(lanePaths.dashboardsDir, entry.name),
|
|
181
|
+
match: entry.name.match(/^wave-(\d+)\.json$/),
|
|
182
|
+
}))
|
|
183
|
+
.filter((entry) => entry.match)
|
|
184
|
+
.map((entry) => ({
|
|
185
|
+
dashboardFile: entry.filePath,
|
|
186
|
+
waveNumber: Number.parseInt(entry.match[1], 10),
|
|
187
|
+
mtimeMs: fs.statSync(entry.filePath).mtimeMs,
|
|
188
|
+
}))
|
|
189
|
+
.sort((left, right) => {
|
|
190
|
+
if (left.mtimeMs !== right.mtimeMs) {
|
|
191
|
+
return right.mtimeMs - left.mtimeMs;
|
|
192
|
+
}
|
|
193
|
+
return right.waveNumber - left.waveNumber;
|
|
194
|
+
});
|
|
195
|
+
if (candidates.length > 0) {
|
|
196
|
+
return { dashboardFile: candidates[0].dashboardFile };
|
|
197
|
+
}
|
|
198
|
+
return fs.existsSync(lanePaths.globalDashboardPath)
|
|
199
|
+
? { dashboardFile: lanePaths.globalDashboardPath }
|
|
200
|
+
: null;
|
|
127
201
|
}
|
|
128
202
|
|
|
129
203
|
function readMessageBoardTail(messageBoardPath, maxLines = 24) {
|
|
@@ -449,11 +523,12 @@ export async function runDashboardCli(argv) {
|
|
|
449
523
|
console.log(`Usage: pnpm exec wave dashboard --dashboard-file <path> [options]
|
|
450
524
|
|
|
451
525
|
Options:
|
|
526
|
+
--project <id> Project id (default: config default)
|
|
452
527
|
--lane <name> Wave lane name (default: ${DEFAULT_WAVE_LANE})
|
|
453
528
|
--dashboard-file <path> Path to wave/global dashboard JSON
|
|
454
529
|
--message-board <path> Optional message board path override
|
|
455
530
|
--attach <current|global>
|
|
456
|
-
Attach to the stable
|
|
531
|
+
Attach to the stable dashboard session for the lane, or follow the last written dashboard file when no live session exists
|
|
457
532
|
--watch Refresh continuously
|
|
458
533
|
--refresh-ms <n> Refresh interval in ms (default: ${DEFAULT_REFRESH_MS})
|
|
459
534
|
`);
|
|
@@ -461,8 +536,12 @@ Options:
|
|
|
461
536
|
}
|
|
462
537
|
|
|
463
538
|
if (options.attach) {
|
|
464
|
-
attachDashboardSession(options.lane, options.attach);
|
|
465
|
-
|
|
539
|
+
const fallback = await attachDashboardSession(options.project, options.lane, options.attach);
|
|
540
|
+
if (!fallback) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
options.dashboardFile = fallback.dashboardFile;
|
|
544
|
+
options.watch = true;
|
|
466
545
|
}
|
|
467
546
|
|
|
468
547
|
let terminalStateReachedAt = null;
|
|
@@ -15,10 +15,10 @@ import { parseWaveFiles } from "./wave-files.mjs";
|
|
|
15
15
|
|
|
16
16
|
function printUsage() {
|
|
17
17
|
console.log(`Usage:
|
|
18
|
-
wave dep post --owner-lane <lane> --requester-lane <lane> --owner-wave <n> --requester-wave <n> --agent <id> --summary <text> [options]
|
|
19
|
-
wave dep show --lane <lane> [--wave <n>] [--json]
|
|
20
|
-
wave dep resolve --lane <lane> --id <id> --agent <id> [--detail <text>] [--status resolved|closed]
|
|
21
|
-
wave dep render --lane <lane> [--wave <n>] [--json]
|
|
18
|
+
wave dep post --owner-project <id> --owner-lane <lane> --requester-project <id> --requester-lane <lane> --owner-wave <n> --requester-wave <n> --agent <id> --summary <text> [options]
|
|
19
|
+
wave dep show --project <id> --lane <lane> [--wave <n>] [--json]
|
|
20
|
+
wave dep resolve --project <id> --lane <lane> --id <id> --agent <id> [--detail <text>] [--status resolved|closed]
|
|
21
|
+
wave dep render --project <id> --lane <lane> [--wave <n>] [--json]
|
|
22
22
|
`);
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -26,6 +26,9 @@ function parseArgs(argv) {
|
|
|
26
26
|
const args = argv[0] === "--" ? argv.slice(1) : argv;
|
|
27
27
|
const subcommand = String(args[0] || "").trim().toLowerCase();
|
|
28
28
|
const options = {
|
|
29
|
+
project: "",
|
|
30
|
+
ownerProject: "",
|
|
31
|
+
requesterProject: "",
|
|
29
32
|
lane: "",
|
|
30
33
|
ownerLane: "",
|
|
31
34
|
requesterLane: "",
|
|
@@ -46,7 +49,13 @@ function parseArgs(argv) {
|
|
|
46
49
|
};
|
|
47
50
|
for (let index = 1; index < args.length; index += 1) {
|
|
48
51
|
const arg = args[index];
|
|
49
|
-
if (arg === "--
|
|
52
|
+
if (arg === "--project") {
|
|
53
|
+
options.project = String(args[++index] || "").trim();
|
|
54
|
+
} else if (arg === "--owner-project") {
|
|
55
|
+
options.ownerProject = String(args[++index] || "").trim();
|
|
56
|
+
} else if (arg === "--requester-project") {
|
|
57
|
+
options.requesterProject = String(args[++index] || "").trim();
|
|
58
|
+
} else if (arg === "--lane") {
|
|
50
59
|
options.lane = String(args[++index] || "").trim();
|
|
51
60
|
} else if (arg === "--owner-lane") {
|
|
52
61
|
options.ownerLane = String(args[++index] || "").trim();
|
|
@@ -112,11 +121,15 @@ export async function runDependencyCli(argv) {
|
|
|
112
121
|
return;
|
|
113
122
|
}
|
|
114
123
|
const { subcommand, options } = parseArgs(argv);
|
|
124
|
+
const baseProject =
|
|
125
|
+
options.project || options.ownerProject || options.requesterProject || undefined;
|
|
115
126
|
const baseLane = options.lane || options.ownerLane || options.requesterLane || "main";
|
|
116
|
-
const lanePaths = buildLanePaths(baseLane);
|
|
127
|
+
const lanePaths = buildLanePaths(baseLane, { project: baseProject });
|
|
117
128
|
ensureDirectory(lanePaths.crossLaneDependenciesDir);
|
|
118
129
|
|
|
119
130
|
if (subcommand === "post") {
|
|
131
|
+
const ownerProject = options.ownerProject || options.project || lanePaths.project;
|
|
132
|
+
const requesterProject = options.requesterProject || options.project || lanePaths.project;
|
|
120
133
|
const ownerLane = options.ownerLane || options.lane;
|
|
121
134
|
const requesterLane = options.requesterLane || lanePaths.lane;
|
|
122
135
|
if (!ownerLane || !requesterLane || options.ownerWave === null || options.requesterWave === null) {
|
|
@@ -125,13 +138,17 @@ export async function runDependencyCli(argv) {
|
|
|
125
138
|
if (!options.agent || !options.summary) {
|
|
126
139
|
throw new Error("--agent and --summary are required");
|
|
127
140
|
}
|
|
128
|
-
const
|
|
141
|
+
const ownerLanePaths = buildLanePaths(ownerLane, { project: ownerProject });
|
|
142
|
+
ensureDirectory(ownerLanePaths.crossLaneDependenciesDir);
|
|
143
|
+
const record = appendDependencyTicket(ownerLanePaths.crossLaneDependenciesDir, ownerLane, {
|
|
129
144
|
id: options.id || `dep-${Date.now().toString(36)}`,
|
|
130
145
|
kind: "request",
|
|
131
146
|
lane: ownerLane,
|
|
132
147
|
wave: options.ownerWave,
|
|
148
|
+
ownerProject,
|
|
133
149
|
ownerLane,
|
|
134
150
|
ownerWave: options.ownerWave,
|
|
151
|
+
requesterProject,
|
|
135
152
|
requesterLane,
|
|
136
153
|
requesterWave: options.requesterWave,
|
|
137
154
|
agentId: options.agent,
|
|
@@ -155,14 +172,15 @@ export async function runDependencyCli(argv) {
|
|
|
155
172
|
if (!lane || !options.id || !options.agent) {
|
|
156
173
|
throw new Error("--lane, --id, and --agent are required for resolve");
|
|
157
174
|
}
|
|
158
|
-
const
|
|
159
|
-
const
|
|
175
|
+
const targetProject = options.project || options.ownerProject || lanePaths.project;
|
|
176
|
+
const targetLanePaths = buildLanePaths(lane, { project: targetProject });
|
|
177
|
+
const latest = materializeCoordinationState(readDependencyTickets(targetLanePaths.crossLaneDependenciesDir, lane)).byId.get(
|
|
160
178
|
options.id,
|
|
161
179
|
);
|
|
162
180
|
if (!latest) {
|
|
163
181
|
throw new Error(`Dependency ${options.id} not found for lane ${lane}`);
|
|
164
182
|
}
|
|
165
|
-
const record = appendDependencyTicket(
|
|
183
|
+
const record = appendDependencyTicket(targetLanePaths.crossLaneDependenciesDir, lane, {
|
|
166
184
|
...latest,
|
|
167
185
|
agentId: options.agent,
|
|
168
186
|
status: options.status || "resolved",
|
|
@@ -175,17 +193,23 @@ export async function runDependencyCli(argv) {
|
|
|
175
193
|
|
|
176
194
|
if (subcommand === "show") {
|
|
177
195
|
const lane = options.lane || options.ownerLane || lanePaths.lane;
|
|
196
|
+
const targetProject = options.project || options.ownerProject || lanePaths.project;
|
|
197
|
+
const targetLanePaths = buildLanePaths(lane, { project: targetProject });
|
|
178
198
|
const records =
|
|
179
199
|
options.wave === null
|
|
180
|
-
? readAllDependencyTickets(
|
|
181
|
-
(record) =>
|
|
200
|
+
? readAllDependencyTickets(targetLanePaths.crossLaneDependenciesDir).filter(
|
|
201
|
+
(record) =>
|
|
202
|
+
(record.ownerLane === lane || record.requesterLane === lane || record.lane === lane) &&
|
|
203
|
+
(!options.project ||
|
|
204
|
+
record.ownerProject === targetProject ||
|
|
205
|
+
record.requesterProject === targetProject),
|
|
182
206
|
)
|
|
183
207
|
: buildDependencySnapshot({
|
|
184
|
-
dirPath:
|
|
208
|
+
dirPath: targetLanePaths.crossLaneDependenciesDir,
|
|
185
209
|
lane,
|
|
186
210
|
waveNumber: options.wave,
|
|
187
|
-
agents: loadWaveAgents(
|
|
188
|
-
capabilityRouting:
|
|
211
|
+
agents: loadWaveAgents(targetLanePaths, options.wave),
|
|
212
|
+
capabilityRouting: targetLanePaths.capabilityRouting,
|
|
189
213
|
});
|
|
190
214
|
if (options.json || options.wave !== null) {
|
|
191
215
|
console.log(JSON.stringify(records, null, 2));
|
|
@@ -201,20 +225,22 @@ export async function runDependencyCli(argv) {
|
|
|
201
225
|
|
|
202
226
|
if (subcommand === "render") {
|
|
203
227
|
const lane = options.lane || options.ownerLane || lanePaths.lane;
|
|
228
|
+
const targetProject = options.project || options.ownerProject || lanePaths.project;
|
|
229
|
+
const targetLanePaths = buildLanePaths(lane, { project: targetProject });
|
|
204
230
|
const snapshot = buildDependencySnapshot({
|
|
205
|
-
dirPath:
|
|
231
|
+
dirPath: targetLanePaths.crossLaneDependenciesDir,
|
|
206
232
|
lane,
|
|
207
233
|
waveNumber: options.wave ?? 0,
|
|
208
|
-
agents: loadWaveAgents(
|
|
209
|
-
capabilityRouting:
|
|
234
|
+
agents: loadWaveAgents(targetLanePaths, options.wave ?? 0),
|
|
235
|
+
capabilityRouting: targetLanePaths.capabilityRouting,
|
|
210
236
|
});
|
|
211
|
-
const markdownPath = dependencyMarkdownPath(
|
|
212
|
-
writeDependencySnapshot(path.join(
|
|
237
|
+
const markdownPath = dependencyMarkdownPath(targetLanePaths, lane);
|
|
238
|
+
writeDependencySnapshot(path.join(targetLanePaths.crossLaneDependenciesDir, `${lane}.json`), snapshot, {
|
|
213
239
|
lane,
|
|
214
240
|
wave: options.wave ?? 0,
|
|
215
241
|
});
|
|
216
242
|
writeTextAtomic(markdownPath, `${renderDependencySnapshotMarkdown(snapshot)}\n`);
|
|
217
|
-
console.log(JSON.stringify({ markdownPath, jsonPath: path.join(
|
|
243
|
+
console.log(JSON.stringify({ markdownPath, jsonPath: path.join(targetLanePaths.crossLaneDependenciesDir, `${lane}.json`) }, null, 2));
|
|
218
244
|
return;
|
|
219
245
|
}
|
|
220
246
|
|
|
@@ -26,7 +26,7 @@ import { deriveWaveLedger, readWaveLedger } from "./ledger.mjs";
|
|
|
26
26
|
import { buildDocsQueue, readDocsQueue } from "./docs-queue.mjs";
|
|
27
27
|
import { parseStructuredSignalsFromLog } from "./dashboard-state.mjs";
|
|
28
28
|
import {
|
|
29
|
-
|
|
29
|
+
isSecurityReviewAgentForLane,
|
|
30
30
|
resolveSecurityReviewReportPath,
|
|
31
31
|
isContEvalImplementationOwningAgent,
|
|
32
32
|
resolveWaveRoleBindings,
|
|
@@ -214,7 +214,9 @@ export function buildWaveSecuritySummary({
|
|
|
214
214
|
summariesByAgentId = {},
|
|
215
215
|
}) {
|
|
216
216
|
const createdAt = toIsoTimestamp();
|
|
217
|
-
const securityAgents = (wave.agents || []).filter((agent) =>
|
|
217
|
+
const securityAgents = (wave.agents || []).filter((agent) =>
|
|
218
|
+
isSecurityReviewAgentForLane(agent, lanePaths),
|
|
219
|
+
);
|
|
218
220
|
if (securityAgents.length === 0) {
|
|
219
221
|
return {
|
|
220
222
|
wave: wave.wave,
|
|
@@ -377,7 +379,7 @@ function buildIntegrationEvidence({
|
|
|
377
379
|
isContEvalImplementationOwningAgent(agent, {
|
|
378
380
|
contEvalAgentId: roleBindings.contEvalAgentId,
|
|
379
381
|
});
|
|
380
|
-
if (
|
|
382
|
+
if (isSecurityReviewAgentForLane(agent, lanePaths)) {
|
|
381
383
|
continue;
|
|
382
384
|
}
|
|
383
385
|
if (agent.agentId === roleBindings.contEvalAgentId) {
|
|
@@ -710,6 +712,7 @@ export function buildWaveDerivedState({
|
|
|
710
712
|
benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
|
|
711
713
|
capabilityAssignments,
|
|
712
714
|
dependencySnapshot,
|
|
715
|
+
securityRolePromptPath: lanePaths.securityRolePromptPath,
|
|
713
716
|
});
|
|
714
717
|
const inboxDir = waveInboxDir(lanePaths, wave.wave);
|
|
715
718
|
const sharedSummary = compileSharedSummary({
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
7
7
|
DEFAULT_WATCH_REFRESH_MS,
|
|
8
8
|
DEFAULT_WAVE_LANE,
|
|
9
|
-
REPO_ROOT,
|
|
10
9
|
buildLanePaths,
|
|
11
10
|
compactSingleLine,
|
|
12
11
|
ensureDirectory,
|
|
12
|
+
findAdhocRunRecord,
|
|
13
13
|
formatAgeFromTimestamp,
|
|
14
14
|
parseNonNegativeInt,
|
|
15
15
|
parsePositiveInt,
|
|
@@ -41,9 +41,12 @@ function requestFilePath(feedbackRequestsDir, requestId) {
|
|
|
41
41
|
return path.join(feedbackRequestsDir, `${requestId}.json`);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
return
|
|
44
|
+
function resolveRunContext(runId, fallbackProject, fallbackLane) {
|
|
45
|
+
const record = findAdhocRunRecord(runId);
|
|
46
|
+
return {
|
|
47
|
+
project: record?.project || fallbackProject,
|
|
48
|
+
lane: record?.result?.lane || fallbackLane,
|
|
49
|
+
};
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
function buildRequestId({ lane, wave, agentId }) {
|
|
@@ -55,6 +58,7 @@ function buildRequestId({ lane, wave, agentId }) {
|
|
|
55
58
|
export function createFeedbackRequest({
|
|
56
59
|
feedbackStateDir,
|
|
57
60
|
feedbackRequestsDir,
|
|
61
|
+
project = null,
|
|
58
62
|
lane,
|
|
59
63
|
wave,
|
|
60
64
|
agentId,
|
|
@@ -69,6 +73,7 @@ export function createFeedbackRequest({
|
|
|
69
73
|
const now = toIsoTimestamp();
|
|
70
74
|
const payload = {
|
|
71
75
|
id: requestId,
|
|
76
|
+
project: project || null,
|
|
72
77
|
createdAt: now,
|
|
73
78
|
updatedAt: now,
|
|
74
79
|
lane,
|
|
@@ -88,7 +93,9 @@ export function createFeedbackRequest({
|
|
|
88
93
|
});
|
|
89
94
|
if (recordTelemetry) {
|
|
90
95
|
try {
|
|
91
|
-
const lanePaths = buildLanePaths(lane
|
|
96
|
+
const lanePaths = buildLanePaths(lane, {
|
|
97
|
+
project: project || undefined,
|
|
98
|
+
});
|
|
92
99
|
safeQueueWaveControlEvent(lanePaths, {
|
|
93
100
|
category: "feedback",
|
|
94
101
|
entityType: "human_input",
|
|
@@ -148,7 +155,9 @@ export function answerFeedbackRequest({
|
|
|
148
155
|
});
|
|
149
156
|
if (recordTelemetry) {
|
|
150
157
|
try {
|
|
151
|
-
const lanePaths = buildLanePaths(answeredPayload?.lane || DEFAULT_WAVE_LANE
|
|
158
|
+
const lanePaths = buildLanePaths(answeredPayload?.lane || DEFAULT_WAVE_LANE, {
|
|
159
|
+
project: answeredPayload?.project || undefined,
|
|
160
|
+
});
|
|
152
161
|
safeQueueWaveControlEvent(lanePaths, {
|
|
153
162
|
category: "feedback",
|
|
154
163
|
entityType: "human_input",
|
|
@@ -245,6 +254,7 @@ function parseFeedbackArgs(argv) {
|
|
|
245
254
|
id: "",
|
|
246
255
|
response: "",
|
|
247
256
|
operator: "human-operator",
|
|
257
|
+
project: "",
|
|
248
258
|
force: false,
|
|
249
259
|
pending: false,
|
|
250
260
|
json: false,
|
|
@@ -255,7 +265,9 @@ function parseFeedbackArgs(argv) {
|
|
|
255
265
|
if (arg === "--") {
|
|
256
266
|
continue;
|
|
257
267
|
}
|
|
258
|
-
if (arg === "--
|
|
268
|
+
if (arg === "--project") {
|
|
269
|
+
out.project = String(args[++i] || "").trim();
|
|
270
|
+
} else if (arg === "--lane") {
|
|
259
271
|
out.lane = sanitizeLaneName(args[++i]);
|
|
260
272
|
} else if (arg === "--run") {
|
|
261
273
|
out.runId = sanitizeAdhocRunId(args[++i]);
|
|
@@ -310,10 +322,10 @@ async function waitForAnswer(filePath, timeoutSeconds) {
|
|
|
310
322
|
|
|
311
323
|
function printHelp() {
|
|
312
324
|
console.log(`Usage:
|
|
313
|
-
pnpm exec wave-feedback ask --lane <lane> --wave <n> --agent <id> --question "<text>" [options]
|
|
325
|
+
pnpm exec wave-feedback ask --project <id> --lane <lane> --wave <n> --agent <id> --question "<text>" [options]
|
|
314
326
|
pnpm exec wave-feedback respond --id <request-id> --response "<text>" [options]
|
|
315
|
-
pnpm exec wave-feedback list [--pending] [--lane <lane>] [--wave <n>] [--agent <id>] [--json]
|
|
316
|
-
pnpm exec wave-feedback watch [--pending] [--lane <lane>] [--wave <n>] [--agent <id>] [--refresh-ms <n>]
|
|
327
|
+
pnpm exec wave-feedback list [--pending] [--project <id>] [--lane <lane>] [--wave <n>] [--agent <id>] [--json]
|
|
328
|
+
pnpm exec wave-feedback watch [--pending] [--project <id>] [--lane <lane>] [--wave <n>] [--agent <id>] [--refresh-ms <n>]
|
|
317
329
|
pnpm exec wave-feedback show --id <request-id>
|
|
318
330
|
`);
|
|
319
331
|
}
|
|
@@ -325,9 +337,12 @@ export async function runFeedbackCli(argv) {
|
|
|
325
337
|
return;
|
|
326
338
|
}
|
|
327
339
|
if (options.runId) {
|
|
328
|
-
|
|
340
|
+
const context = resolveRunContext(options.runId, options.project, options.lane);
|
|
341
|
+
options.project = context.project;
|
|
342
|
+
options.lane = context.lane;
|
|
329
343
|
}
|
|
330
344
|
const lanePaths = buildLanePaths(options.lane, {
|
|
345
|
+
project: options.project || undefined,
|
|
331
346
|
adhocRunId: options.runId || null,
|
|
332
347
|
});
|
|
333
348
|
const requestsDir = lanePaths.feedbackRequestsDir;
|
|
@@ -340,6 +355,7 @@ export async function runFeedbackCli(argv) {
|
|
|
340
355
|
const result = createFeedbackRequest({
|
|
341
356
|
feedbackStateDir: stateDir,
|
|
342
357
|
feedbackRequestsDir: requestsDir,
|
|
358
|
+
project: lanePaths.project,
|
|
343
359
|
lane: options.lane,
|
|
344
360
|
wave: options.wave,
|
|
345
361
|
agentId: options.agent,
|
|
@@ -375,6 +391,7 @@ export async function runFeedbackCli(argv) {
|
|
|
375
391
|
});
|
|
376
392
|
if (answered?.lane && Number.isFinite(Number(answered.wave))) {
|
|
377
393
|
answerHumanInputByRequest({
|
|
394
|
+
project: answered.project || options.project || null,
|
|
378
395
|
lane: answered.lane,
|
|
379
396
|
waveNumber: Number(answered.wave),
|
|
380
397
|
requestId: options.id,
|