@chllming/wave-orchestration 0.6.1 → 0.6.2
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 +9 -0
- package/README.md +75 -30
- package/docs/README.md +15 -3
- package/docs/concepts/context7-vs-skills.md +24 -0
- package/docs/concepts/runtime-agnostic-orchestration.md +17 -2
- package/docs/concepts/what-is-a-wave.md +28 -0
- package/docs/evals/README.md +2 -0
- package/docs/guides/terminal-surfaces.md +2 -0
- package/docs/plans/wave-orchestrator.md +11 -3
- package/docs/reference/runtime-config/README.md +4 -4
- package/docs/reference/runtime-config/claude.md +6 -1
- package/docs/reference/runtime-config/codex.md +2 -2
- package/docs/reference/runtime-config/opencode.md +1 -1
- package/docs/research/agent-context-sources.md +2 -0
- package/docs/research/coordination-failure-review.md +37 -13
- package/package.json +1 -1
- package/releases/manifest.json +18 -0
- package/scripts/wave-orchestrator/agent-state.mjs +10 -3
- package/scripts/wave-orchestrator/config.mjs +19 -0
- package/scripts/wave-orchestrator/dashboard-renderer.mjs +150 -20
- package/scripts/wave-orchestrator/dashboard-state.mjs +8 -0
- package/scripts/wave-orchestrator/executors.mjs +67 -4
- package/scripts/wave-orchestrator/launcher-runtime.mjs +1 -0
- package/scripts/wave-orchestrator/launcher.mjs +245 -10
- package/scripts/wave-orchestrator/terminals.mjs +25 -0
- package/scripts/wave-orchestrator/wave-files.mjs +31 -0
|
@@ -183,7 +183,7 @@ function findLatestComponentMatches(text) {
|
|
|
183
183
|
return Array.from(byComponent.values());
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
function detectTermination(logText, statusRecord) {
|
|
186
|
+
function detectTermination(agent, logText, statusRecord) {
|
|
187
187
|
const patterns = [
|
|
188
188
|
{ reason: "max-turns", regex: /(Reached max turns \(\d+\))/i },
|
|
189
189
|
{ reason: "timeout", regex: /(timed out(?: after [^\n.]+)?)/i },
|
|
@@ -192,9 +192,16 @@ function detectTermination(logText, statusRecord) {
|
|
|
192
192
|
for (const pattern of patterns) {
|
|
193
193
|
const match = String(logText || "").match(pattern.regex);
|
|
194
194
|
if (match) {
|
|
195
|
+
const baseHint = cleanText(match[1] || match[0]);
|
|
196
|
+
if (pattern.reason === "max-turns" && agent?.executorResolved?.id === "codex") {
|
|
197
|
+
return {
|
|
198
|
+
reason: pattern.reason,
|
|
199
|
+
hint: `${baseHint}. Wave does not set a Codex turn-limit flag; inspect launch-preview.json limits for any profile or upstream-runtime ceiling notes.`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
195
202
|
return {
|
|
196
203
|
reason: pattern.reason,
|
|
197
|
-
hint:
|
|
204
|
+
hint: baseHint,
|
|
198
205
|
};
|
|
199
206
|
}
|
|
200
207
|
}
|
|
@@ -341,7 +348,7 @@ export function buildAgentExecutionSummary({ agent, statusRecord, logPath, repor
|
|
|
341
348
|
const reportVerdict = parseVerdictFromText(reportText, REPORT_VERDICT_REGEX);
|
|
342
349
|
const logVerdict = parseVerdictFromText(signalText, WAVE_VERDICT_REGEX);
|
|
343
350
|
const verdict = reportVerdict.verdict ? reportVerdict : logVerdict;
|
|
344
|
-
const termination = detectTermination(logText, statusRecord);
|
|
351
|
+
const termination = detectTermination(agent, logText, statusRecord);
|
|
345
352
|
return {
|
|
346
353
|
agentId: agent?.agentId || null,
|
|
347
354
|
promptHash: statusRecord?.promptHash || null,
|
|
@@ -445,6 +445,19 @@ function normalizeClaudeOutputFormat(value, label = "executors.claude.outputForm
|
|
|
445
445
|
return normalized;
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
+
export function normalizeClaudeEffort(value, label = "executors.claude.effort", fallback = null) {
|
|
449
|
+
const normalized = String(value ?? "")
|
|
450
|
+
.trim()
|
|
451
|
+
.toLowerCase();
|
|
452
|
+
if (!normalized) {
|
|
453
|
+
return fallback;
|
|
454
|
+
}
|
|
455
|
+
if (!["low", "medium", "high", "max"].includes(normalized)) {
|
|
456
|
+
throw new Error(`${label} must be one of: low, medium, high, max`);
|
|
457
|
+
}
|
|
458
|
+
return normalized;
|
|
459
|
+
}
|
|
460
|
+
|
|
448
461
|
function normalizeOpenCodeFormat(value, label = "executors.opencode.format") {
|
|
449
462
|
const normalized = String(value || "default")
|
|
450
463
|
.trim()
|
|
@@ -519,6 +532,11 @@ function normalizeExecutorProfile(rawProfile = {}, label = "executors.profiles.<
|
|
|
519
532
|
rawProfile.claude.permissionPromptTool,
|
|
520
533
|
null,
|
|
521
534
|
),
|
|
535
|
+
effort: normalizeClaudeEffort(
|
|
536
|
+
rawProfile.claude.effort,
|
|
537
|
+
`${label}.claude.effort`,
|
|
538
|
+
null,
|
|
539
|
+
),
|
|
522
540
|
maxTurns: normalizeOptionalPositiveInt(
|
|
523
541
|
rawProfile.claude.maxTurns,
|
|
524
542
|
`${label}.claude.maxTurns`,
|
|
@@ -685,6 +703,7 @@ function normalizeExecutors(rawExecutors = {}) {
|
|
|
685
703
|
executors.claude?.permissionPromptTool,
|
|
686
704
|
null,
|
|
687
705
|
),
|
|
706
|
+
effort: normalizeClaudeEffort(executors.claude?.effort, "executors.claude.effort", null),
|
|
688
707
|
maxTurns: normalizeOptionalPositiveInt(executors.claude?.maxTurns, "executors.claude.maxTurns"),
|
|
689
708
|
mcpConfig: normalizeOptionalStringOrStringArray(
|
|
690
709
|
executors.claude?.mcpConfig,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { analyzeMessageBoardCommunication } from "./coordination.mjs";
|
|
4
|
-
import { commsAgeSummary, deploymentSummary
|
|
4
|
+
import { commsAgeSummary, deploymentSummary } from "./dashboard-state.mjs";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_REFRESH_MS,
|
|
7
7
|
DEFAULT_WAVE_LANE,
|
|
@@ -81,20 +81,125 @@ function isGlobalDashboardState(state) {
|
|
|
81
81
|
return Boolean(state && Array.isArray(state.waves) && !Array.isArray(state.agents));
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
const ANSI = {
|
|
85
|
+
reset: "\u001b[0m",
|
|
86
|
+
dim: "\u001b[2m",
|
|
87
|
+
red: "\u001b[31m",
|
|
88
|
+
green: "\u001b[32m",
|
|
89
|
+
yellow: "\u001b[33m",
|
|
90
|
+
blue: "\u001b[34m",
|
|
91
|
+
magenta: "\u001b[35m",
|
|
92
|
+
cyan: "\u001b[36m",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function paint(text, color, colorize = false) {
|
|
96
|
+
if (!colorize || !color) {
|
|
97
|
+
return text;
|
|
98
|
+
}
|
|
99
|
+
return `${color}${text}${ANSI.reset}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function paintState(text, state, colorize = false) {
|
|
103
|
+
const normalized = String(state || "").trim().toLowerCase();
|
|
104
|
+
const color =
|
|
105
|
+
normalized === "completed"
|
|
106
|
+
? ANSI.green
|
|
107
|
+
: normalized === "failed" || normalized === "timed_out"
|
|
108
|
+
? ANSI.red
|
|
109
|
+
: normalized === "deploying" || normalized === "finalizing" || normalized === "validating"
|
|
110
|
+
? ANSI.yellow
|
|
111
|
+
: normalized === "running" || normalized === "coding" || normalized === "launching"
|
|
112
|
+
? ANSI.cyan
|
|
113
|
+
: normalized === "pending"
|
|
114
|
+
? ANSI.dim
|
|
115
|
+
: normalized === "dry-run"
|
|
116
|
+
? ANSI.magenta
|
|
117
|
+
: null;
|
|
118
|
+
return paint(text, color, colorize);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function paintLevel(text, level, colorize = false) {
|
|
122
|
+
const normalized = String(level || "").trim().toLowerCase();
|
|
123
|
+
const color =
|
|
124
|
+
normalized === "error"
|
|
125
|
+
? ANSI.red
|
|
126
|
+
: normalized === "warn"
|
|
127
|
+
? ANSI.yellow
|
|
128
|
+
: normalized === "info"
|
|
129
|
+
? ANSI.blue
|
|
130
|
+
: null;
|
|
131
|
+
return paint(text, color, colorize);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function paintExitCode(text, exitCode, colorize = false) {
|
|
135
|
+
if (exitCode === 0 || exitCode === "0") {
|
|
136
|
+
return paint(text, ANSI.green, colorize);
|
|
137
|
+
}
|
|
138
|
+
if (exitCode === null || exitCode === undefined || exitCode === "-") {
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
141
|
+
return paint(text, ANSI.red, colorize);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function paintDeploymentSummary(text, deploymentState, colorize = false) {
|
|
145
|
+
const normalized = String(deploymentState || "").trim().toLowerCase();
|
|
146
|
+
const color =
|
|
147
|
+
normalized === "healthy"
|
|
148
|
+
? ANSI.green
|
|
149
|
+
: normalized === "failed"
|
|
150
|
+
? ANSI.red
|
|
151
|
+
: normalized === "deploying" || normalized === "rolledover"
|
|
152
|
+
? ANSI.yellow
|
|
153
|
+
: null;
|
|
154
|
+
return paint(text, color, colorize);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function renderColoredCountsByState(agents, colorize = false) {
|
|
158
|
+
const counts = new Map();
|
|
159
|
+
for (const agent of agents || []) {
|
|
160
|
+
const key = agent?.state || "unknown";
|
|
161
|
+
counts.set(key, (counts.get(key) || 0) + 1);
|
|
162
|
+
}
|
|
163
|
+
return Array.from(counts.entries())
|
|
164
|
+
.toSorted((a, b) => a[0].localeCompare(b[0]))
|
|
165
|
+
.map(([state, count]) => `${paintState(state, state, colorize)}:${count}`)
|
|
166
|
+
.join(" ");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function paintWaveAgentSummary(summary, wave, colorize = false) {
|
|
170
|
+
const failed = Number(wave?.agentsFailed ?? 0) || 0;
|
|
171
|
+
const active = Number(wave?.agentsActive ?? 0) || 0;
|
|
172
|
+
const total = Number(wave?.agentsTotal ?? 0) || 0;
|
|
173
|
+
const completed = Number(wave?.agentsCompleted ?? 0) || 0;
|
|
174
|
+
const color =
|
|
175
|
+
failed > 0
|
|
176
|
+
? ANSI.red
|
|
177
|
+
: active > 0
|
|
178
|
+
? ANSI.cyan
|
|
179
|
+
: total > 0 && completed >= total
|
|
180
|
+
? ANSI.green
|
|
181
|
+
: ANSI.dim;
|
|
182
|
+
return paint(summary, color, colorize);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function renderWaveDashboard({ state, dashboardPath, messageBoardPath, lane, colorize = false }) {
|
|
85
186
|
if (!state) {
|
|
86
187
|
return `Dashboard file not found or invalid: ${dashboardPath}`;
|
|
87
188
|
}
|
|
88
189
|
const lines = [];
|
|
89
190
|
const laneName = String(state?.lane || lane || "").trim() || DEFAULT_WAVE_LANE;
|
|
90
191
|
lines.push(
|
|
91
|
-
`${laneName} Wave Dashboard | wave=${state.wave ?? "?"} | status=${
|
|
192
|
+
`${laneName} Wave Dashboard | wave=${state.wave ?? "?"} | status=${paintState(
|
|
193
|
+
state.status ?? "unknown",
|
|
194
|
+
state.status,
|
|
195
|
+
colorize,
|
|
196
|
+
)} | attempt=${state.attempt ?? "?"}/${state.maxAttempts ?? "?"}`,
|
|
92
197
|
);
|
|
93
198
|
lines.push(
|
|
94
199
|
`Updated ${formatAgeFromTimestamp(Date.parse(state.updatedAt || ""))} | Started ${state.startedAt || "n/a"} | Elapsed ${formatElapsed(state.startedAt)}`,
|
|
95
200
|
);
|
|
96
201
|
lines.push(`Run tag: ${state.runTag || "n/a"} | Wave file: ${state.waveFile || "n/a"}`);
|
|
97
|
-
lines.push(`Counts: ${
|
|
202
|
+
lines.push(`Counts: ${renderColoredCountsByState(state.agents || [], colorize) || "none"}`);
|
|
98
203
|
const comms = analyzeMessageBoardCommunication(messageBoardPath);
|
|
99
204
|
if (!comms.available) {
|
|
100
205
|
lines.push(`Comms: unavailable ${comms.reason || ""}`.trim());
|
|
@@ -116,10 +221,19 @@ function renderWaveDashboard({ state, dashboardPath, messageBoardPath, lane }) {
|
|
|
116
221
|
);
|
|
117
222
|
for (const agent of state.agents || []) {
|
|
118
223
|
lines.push(
|
|
119
|
-
`${pad(agent.agentId || "-", 8)} ${
|
|
120
|
-
agent.
|
|
121
|
-
|
|
122
|
-
|
|
224
|
+
`${pad(agent.agentId || "-", 8)} ${paintState(
|
|
225
|
+
pad(agent.state || "-", 12),
|
|
226
|
+
agent.state,
|
|
227
|
+
colorize,
|
|
228
|
+
)} ${pad(agent.attempts ?? 0, 8)} ${paintExitCode(
|
|
229
|
+
pad(agent.exitCode ?? "-", 6),
|
|
230
|
+
agent.exitCode,
|
|
231
|
+
colorize,
|
|
232
|
+
)} ${paintDeploymentSummary(
|
|
233
|
+
pad(deploymentSummary({ service: agent.deploymentService, state: agent.deploymentState }), 24),
|
|
234
|
+
agent.deploymentState,
|
|
235
|
+
colorize,
|
|
236
|
+
)} ${pad(
|
|
123
237
|
formatAgeFromTimestamp(Date.parse(agent.lastUpdateAt || "")),
|
|
124
238
|
12,
|
|
125
239
|
)} ${truncate(agent.detail || "", 72)}`,
|
|
@@ -130,7 +244,7 @@ function renderWaveDashboard({ state, dashboardPath, messageBoardPath, lane }) {
|
|
|
130
244
|
for (const event of (state.events || []).slice(-12)) {
|
|
131
245
|
const prefix = event.agentId ? `[${event.agentId}]` : "[wave]";
|
|
132
246
|
lines.push(
|
|
133
|
-
`${event.at || "n/a"} ${pad(event.level || "info", 5)} ${prefix} ${event.message || ""}`,
|
|
247
|
+
`${event.at || "n/a"} ${paintLevel(pad(event.level || "info", 5), event.level, colorize)} ${prefix} ${event.message || ""}`,
|
|
134
248
|
);
|
|
135
249
|
}
|
|
136
250
|
if ((state.events || []).length === 0) {
|
|
@@ -143,14 +257,29 @@ function renderWaveDashboard({ state, dashboardPath, messageBoardPath, lane }) {
|
|
|
143
257
|
return lines.join("\n");
|
|
144
258
|
}
|
|
145
259
|
|
|
146
|
-
function renderGlobalDashboard({ state, dashboardPath, lane }) {
|
|
260
|
+
function renderGlobalDashboard({ state, dashboardPath, lane, colorize = false }) {
|
|
147
261
|
if (!state) {
|
|
148
262
|
return `Dashboard file not found or invalid: ${dashboardPath}`;
|
|
149
263
|
}
|
|
264
|
+
const formatGlobalWaveAgentSummary = (wave) => {
|
|
265
|
+
const total = Number(wave?.agentsTotal ?? 0) || 0;
|
|
266
|
+
const completed = Number(wave?.agentsCompleted ?? 0) || 0;
|
|
267
|
+
const failed = Number(wave?.agentsFailed ?? 0) || 0;
|
|
268
|
+
const active = Number(wave?.agentsActive ?? 0) || 0;
|
|
269
|
+
const pending =
|
|
270
|
+
wave?.agentsPending === undefined || wave?.agentsPending === null
|
|
271
|
+
? Math.max(0, total - completed - failed - active)
|
|
272
|
+
: Number(wave.agentsPending ?? 0) || 0;
|
|
273
|
+
return `done ${completed}/${total} active ${active} pending ${pending} fail ${failed}`;
|
|
274
|
+
};
|
|
150
275
|
const lines = [];
|
|
151
276
|
const laneName = String(state?.lane || lane || "").trim() || DEFAULT_WAVE_LANE;
|
|
152
277
|
lines.push(
|
|
153
|
-
`${laneName} Wave Global Dashboard | run=${state.runId || "n/a"} | status=${
|
|
278
|
+
`${laneName} Wave Global Dashboard | run=${state.runId || "n/a"} | status=${paintState(
|
|
279
|
+
state.status || "unknown",
|
|
280
|
+
state.status,
|
|
281
|
+
colorize,
|
|
282
|
+
)}`,
|
|
154
283
|
);
|
|
155
284
|
lines.push(
|
|
156
285
|
`Updated ${formatAgeFromTimestamp(Date.parse(state.updatedAt || ""))} | Started ${state.startedAt || "n/a"} | Elapsed ${formatElapsed(state.startedAt)}`,
|
|
@@ -161,18 +290,18 @@ function renderGlobalDashboard({ state, dashboardPath, lane }) {
|
|
|
161
290
|
lines.push("");
|
|
162
291
|
lines.push("Waves:");
|
|
163
292
|
lines.push(
|
|
164
|
-
`${pad("Wave", 6)} ${pad("Status", 10)} ${pad("Attempt", 9)} ${pad("Agents",
|
|
293
|
+
`${pad("Wave", 6)} ${pad("Status", 10)} ${pad("Attempt", 9)} ${pad("Agents", 36)} ${pad("Started", 12)} ${pad("Last Message", 70)}`,
|
|
165
294
|
);
|
|
166
295
|
lines.push(
|
|
167
|
-
`${"-".repeat(6)} ${"-".repeat(10)} ${"-".repeat(9)} ${"-".repeat(
|
|
296
|
+
`${"-".repeat(6)} ${"-".repeat(10)} ${"-".repeat(9)} ${"-".repeat(36)} ${"-".repeat(12)} ${"-".repeat(70)}`,
|
|
168
297
|
);
|
|
169
298
|
for (const wave of state.waves || []) {
|
|
170
|
-
const agents =
|
|
299
|
+
const agents = formatGlobalWaveAgentSummary(wave);
|
|
171
300
|
lines.push(
|
|
172
|
-
`${pad(wave.wave ?? "-", 6)} ${pad(wave.status || "-", 10)} ${pad(
|
|
301
|
+
`${pad(wave.wave ?? "-", 6)} ${paintState(pad(wave.status || "-", 10), wave.status, colorize)} ${pad(
|
|
173
302
|
`${wave.attempt ?? 0}/${wave.maxAttempts ?? 0}`,
|
|
174
303
|
9,
|
|
175
|
-
)} ${pad(agents,
|
|
304
|
+
)} ${paintWaveAgentSummary(pad(agents, 36), wave, colorize)} ${pad(
|
|
176
305
|
formatAgeFromTimestamp(Date.parse(wave.startedAt || "")),
|
|
177
306
|
12,
|
|
178
307
|
)} ${truncate(wave.lastMessage || "", 70)}`,
|
|
@@ -191,7 +320,7 @@ function renderGlobalDashboard({ state, dashboardPath, lane }) {
|
|
|
191
320
|
for (const event of (state.events || []).slice(-16)) {
|
|
192
321
|
const waveTag = event.wave ? `wave:${event.wave}` : "wave:-";
|
|
193
322
|
lines.push(
|
|
194
|
-
`${event.at || "n/a"} ${pad(event.level || "info", 5)} [${waveTag}] ${event.message || ""}`,
|
|
323
|
+
`${event.at || "n/a"} ${paintLevel(pad(event.level || "info", 5), event.level, colorize)} [${waveTag}] ${event.message || ""}`,
|
|
195
324
|
);
|
|
196
325
|
}
|
|
197
326
|
if ((state.events || []).length === 0) {
|
|
@@ -200,10 +329,10 @@ function renderGlobalDashboard({ state, dashboardPath, lane }) {
|
|
|
200
329
|
return lines.join("\n");
|
|
201
330
|
}
|
|
202
331
|
|
|
203
|
-
export function renderDashboard({ state, dashboardPath, messageBoardPath, lane }) {
|
|
332
|
+
export function renderDashboard({ state, dashboardPath, messageBoardPath, lane, colorize = false }) {
|
|
204
333
|
return isGlobalDashboardState(state)
|
|
205
|
-
? renderGlobalDashboard({ state, dashboardPath, lane })
|
|
206
|
-
: renderWaveDashboard({ state, dashboardPath, messageBoardPath, lane });
|
|
334
|
+
? renderGlobalDashboard({ state, dashboardPath, lane, colorize })
|
|
335
|
+
: renderWaveDashboard({ state, dashboardPath, messageBoardPath, lane, colorize });
|
|
207
336
|
}
|
|
208
337
|
|
|
209
338
|
export async function runDashboardCli(argv) {
|
|
@@ -232,6 +361,7 @@ Options:
|
|
|
232
361
|
dashboardPath: options.dashboardFile,
|
|
233
362
|
messageBoardPath: boardPath,
|
|
234
363
|
lane: options.lane,
|
|
364
|
+
colorize: process.stdout.isTTY,
|
|
235
365
|
});
|
|
236
366
|
if (process.stdout.isTTY) {
|
|
237
367
|
process.stdout.write("\u001bc");
|
|
@@ -228,8 +228,10 @@ export function buildGlobalDashboardState({
|
|
|
228
228
|
startedAt: null,
|
|
229
229
|
completedAt: null,
|
|
230
230
|
agentsTotal: wave.agents.length,
|
|
231
|
+
agentsActive: 0,
|
|
231
232
|
agentsCompleted: 0,
|
|
232
233
|
agentsFailed: 0,
|
|
234
|
+
agentsPending: wave.agents.length,
|
|
233
235
|
helperAssignmentsOpen: 0,
|
|
234
236
|
inboundDependenciesOpen: 0,
|
|
235
237
|
outboundDependenciesOpen: 0,
|
|
@@ -306,10 +308,16 @@ export function syncGlobalWaveFromWaveDashboard(globalState, waveDashboard) {
|
|
|
306
308
|
}
|
|
307
309
|
const agents = Array.isArray(waveDashboard.agents) ? waveDashboard.agents : [];
|
|
308
310
|
entry.agentsTotal = agents.length;
|
|
311
|
+
entry.agentsActive = agents.filter((agent) =>
|
|
312
|
+
["launching", "running", "coding", "validating", "deploying", "finalizing"].includes(
|
|
313
|
+
agent.state,
|
|
314
|
+
),
|
|
315
|
+
).length;
|
|
309
316
|
entry.agentsCompleted = agents.filter((agent) => agent.state === "completed").length;
|
|
310
317
|
entry.agentsFailed = agents.filter(
|
|
311
318
|
(agent) => agent.state === "failed" || agent.state === "timed_out",
|
|
312
319
|
).length;
|
|
320
|
+
entry.agentsPending = agents.filter((agent) => agent.state === "pending").length;
|
|
313
321
|
const latestEvent = waveDashboard.events.at(-1);
|
|
314
322
|
entry.lastMessage = latestEvent?.message || entry.lastMessage || "";
|
|
315
323
|
entry.helperAssignmentsOpen = waveDashboard.helperAssignmentsOpen || 0;
|
|
@@ -217,6 +217,7 @@ function buildClaudeLaunchSpec({ agent, promptPath, logPath, overlayDir }) {
|
|
|
217
217
|
appendSingleValueFlag(tokens, "--agent", executor.claude.agent);
|
|
218
218
|
appendSingleValueFlag(tokens, "--permission-mode", executor.claude.permissionMode);
|
|
219
219
|
appendSingleValueFlag(tokens, "--permission-prompt-tool", executor.claude.permissionPromptTool);
|
|
220
|
+
appendSingleValueFlag(tokens, "--effort", executor.claude.effort);
|
|
220
221
|
appendSingleValueFlag(tokens, "--max-turns", executor.claude.maxTurns);
|
|
221
222
|
appendRepeatedFlag(tokens, "--mcp-config", executor.claude.mcpConfig);
|
|
222
223
|
appendSingleValueFlag(tokens, "--settings", settingsPath);
|
|
@@ -299,6 +300,55 @@ function buildLocalLaunchSpec({ promptPath, logPath }) {
|
|
|
299
300
|
};
|
|
300
301
|
}
|
|
301
302
|
|
|
303
|
+
function buildLaunchLimitsMetadata(agent) {
|
|
304
|
+
const executor = agent?.executorResolved || {};
|
|
305
|
+
const executorId = normalizeExecutorMode(executor.id || DEFAULT_EXECUTOR_MODE);
|
|
306
|
+
const attemptTimeoutMinutes = executor?.budget?.minutes ?? null;
|
|
307
|
+
if (executorId === "claude") {
|
|
308
|
+
const source = executor?.claude?.maxTurnsSource || null;
|
|
309
|
+
return {
|
|
310
|
+
attemptTimeoutMinutes,
|
|
311
|
+
knownTurnLimit: executor?.claude?.maxTurns ?? null,
|
|
312
|
+
turnLimitSource: source,
|
|
313
|
+
notes:
|
|
314
|
+
source === "budget.turns"
|
|
315
|
+
? ["Known turn limit was derived from generic budget.turns."]
|
|
316
|
+
: [],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
if (executorId === "opencode") {
|
|
320
|
+
const source = executor?.opencode?.stepsSource || null;
|
|
321
|
+
return {
|
|
322
|
+
attemptTimeoutMinutes,
|
|
323
|
+
knownTurnLimit: executor?.opencode?.steps ?? null,
|
|
324
|
+
turnLimitSource: source,
|
|
325
|
+
notes:
|
|
326
|
+
source === "budget.turns"
|
|
327
|
+
? ["Known turn limit was derived from generic budget.turns."]
|
|
328
|
+
: [],
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (executorId === "codex") {
|
|
332
|
+
const profileNote = executor?.codex?.profileName
|
|
333
|
+
? ` via Codex profile ${executor.codex.profileName}`
|
|
334
|
+
: "";
|
|
335
|
+
return {
|
|
336
|
+
attemptTimeoutMinutes,
|
|
337
|
+
knownTurnLimit: null,
|
|
338
|
+
turnLimitSource: "not-set-by-wave",
|
|
339
|
+
notes: [
|
|
340
|
+
`Wave emits no Codex turn-limit flag; any effective ceiling may come from the selected Codex profile${profileNote} or the upstream Codex runtime.`,
|
|
341
|
+
],
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
attemptTimeoutMinutes,
|
|
346
|
+
knownTurnLimit: null,
|
|
347
|
+
turnLimitSource: "not-applicable",
|
|
348
|
+
notes: ["Local executor does not use model turn limits."],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
302
352
|
function buildCodexLaunchSpec({ agent, promptPath, logPath, skillProjection }) {
|
|
303
353
|
const executor = agent.executorResolved;
|
|
304
354
|
return {
|
|
@@ -329,16 +379,29 @@ function buildCodexLaunchSpec({ agent, promptPath, logPath, skillProjection }) {
|
|
|
329
379
|
export function buildExecutorLaunchSpec({ agent, promptPath, logPath, overlayDir, skillProjection }) {
|
|
330
380
|
const executorId = normalizeExecutorMode(agent?.executorResolved?.id || DEFAULT_EXECUTOR_MODE);
|
|
331
381
|
ensureDirectory(overlayDir);
|
|
382
|
+
const limits = buildLaunchLimitsMetadata(agent);
|
|
332
383
|
if (executorId === "local") {
|
|
333
|
-
return
|
|
384
|
+
return {
|
|
385
|
+
...buildLocalLaunchSpec({ promptPath, logPath }),
|
|
386
|
+
limits,
|
|
387
|
+
};
|
|
334
388
|
}
|
|
335
389
|
if (executorId === "claude") {
|
|
336
|
-
return
|
|
390
|
+
return {
|
|
391
|
+
...buildClaudeLaunchSpec({ agent, promptPath, logPath, overlayDir, skillProjection }),
|
|
392
|
+
limits,
|
|
393
|
+
};
|
|
337
394
|
}
|
|
338
395
|
if (executorId === "opencode") {
|
|
339
|
-
return
|
|
396
|
+
return {
|
|
397
|
+
...buildOpenCodeLaunchSpec({ agent, promptPath, logPath, overlayDir, skillProjection }),
|
|
398
|
+
limits,
|
|
399
|
+
};
|
|
340
400
|
}
|
|
341
|
-
return
|
|
401
|
+
return {
|
|
402
|
+
...buildCodexLaunchSpec({ agent, promptPath, logPath, skillProjection }),
|
|
403
|
+
limits,
|
|
404
|
+
};
|
|
342
405
|
}
|
|
343
406
|
|
|
344
407
|
export function commandForExecutor(executor, executorId = executor?.id) {
|
|
@@ -139,6 +139,7 @@ export async function launchAgentSession(lanePaths, params, { runTmuxFn }) {
|
|
|
139
139
|
env: launchSpec.env || {},
|
|
140
140
|
useRateLimitRetries: launchSpec.useRateLimitRetries === true,
|
|
141
141
|
invocationLines: launchSpec.invocationLines,
|
|
142
|
+
limits: launchSpec.limits || null,
|
|
142
143
|
skills: summarizeResolvedSkills(agent.skillsResolved),
|
|
143
144
|
});
|
|
144
145
|
return {
|