@chllming/wave-orchestration 0.5.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 +41 -0
- package/README.md +549 -0
- package/docs/agents/wave-deploy-verifier-role.md +34 -0
- package/docs/agents/wave-documentation-role.md +30 -0
- package/docs/agents/wave-evaluator-role.md +43 -0
- package/docs/agents/wave-infra-role.md +34 -0
- package/docs/agents/wave-integration-role.md +32 -0
- package/docs/agents/wave-launcher-role.md +37 -0
- package/docs/context7/bundles.json +91 -0
- package/docs/plans/component-cutover-matrix.json +112 -0
- package/docs/plans/component-cutover-matrix.md +49 -0
- package/docs/plans/context7-wave-orchestrator.md +130 -0
- package/docs/plans/current-state.md +44 -0
- package/docs/plans/master-plan.md +16 -0
- package/docs/plans/migration.md +23 -0
- package/docs/plans/wave-orchestrator.md +254 -0
- package/docs/plans/waves/wave-0.md +165 -0
- package/docs/reference/github-packages-setup.md +52 -0
- package/docs/reference/migration-0.2-to-0.5.md +622 -0
- package/docs/reference/npmjs-trusted-publishing.md +55 -0
- package/docs/reference/repository-guidance.md +18 -0
- package/docs/reference/runtime-config/README.md +85 -0
- package/docs/reference/runtime-config/claude.md +105 -0
- package/docs/reference/runtime-config/codex.md +81 -0
- package/docs/reference/runtime-config/opencode.md +93 -0
- package/docs/research/agent-context-sources.md +57 -0
- package/docs/roadmap.md +626 -0
- package/package.json +53 -0
- package/releases/manifest.json +101 -0
- package/scripts/context7-api-check.sh +21 -0
- package/scripts/context7-export-env.sh +52 -0
- package/scripts/research/agent-context-archive.mjs +472 -0
- package/scripts/research/generate-agent-context-indexes.mjs +85 -0
- package/scripts/research/import-agent-context-archive.mjs +793 -0
- package/scripts/research/manifests/harness-and-blackboard-2026-03-21.mjs +201 -0
- package/scripts/wave-autonomous.mjs +13 -0
- package/scripts/wave-cli-bootstrap.mjs +27 -0
- package/scripts/wave-dashboard.mjs +11 -0
- package/scripts/wave-human-feedback.mjs +11 -0
- package/scripts/wave-launcher.mjs +11 -0
- package/scripts/wave-local-executor.mjs +13 -0
- package/scripts/wave-orchestrator/agent-state.mjs +416 -0
- package/scripts/wave-orchestrator/autonomous.mjs +367 -0
- package/scripts/wave-orchestrator/clarification-triage.mjs +605 -0
- package/scripts/wave-orchestrator/config.mjs +848 -0
- package/scripts/wave-orchestrator/context7.mjs +464 -0
- package/scripts/wave-orchestrator/coord-cli.mjs +286 -0
- package/scripts/wave-orchestrator/coordination-store.mjs +987 -0
- package/scripts/wave-orchestrator/coordination.mjs +768 -0
- package/scripts/wave-orchestrator/dashboard-renderer.mjs +254 -0
- package/scripts/wave-orchestrator/dashboard-state.mjs +473 -0
- package/scripts/wave-orchestrator/dep-cli.mjs +219 -0
- package/scripts/wave-orchestrator/docs-queue.mjs +75 -0
- package/scripts/wave-orchestrator/executors.mjs +385 -0
- package/scripts/wave-orchestrator/feedback.mjs +372 -0
- package/scripts/wave-orchestrator/install.mjs +540 -0
- package/scripts/wave-orchestrator/launcher.mjs +3879 -0
- package/scripts/wave-orchestrator/ledger.mjs +332 -0
- package/scripts/wave-orchestrator/local-executor.mjs +263 -0
- package/scripts/wave-orchestrator/replay.mjs +246 -0
- package/scripts/wave-orchestrator/roots.mjs +10 -0
- package/scripts/wave-orchestrator/routing-state.mjs +542 -0
- package/scripts/wave-orchestrator/shared.mjs +405 -0
- package/scripts/wave-orchestrator/terminals.mjs +209 -0
- package/scripts/wave-orchestrator/traces.mjs +1094 -0
- package/scripts/wave-orchestrator/wave-files.mjs +1923 -0
- package/scripts/wave.mjs +103 -0
- package/wave.config.json +115 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readJsonOrNull, toIsoTimestamp, writeJsonAtomic } from "./shared.mjs";
|
|
2
|
+
|
|
3
|
+
export function buildDocsQueue({
|
|
4
|
+
lane,
|
|
5
|
+
wave,
|
|
6
|
+
summariesByAgentId = {},
|
|
7
|
+
sharedPlanDocs = [],
|
|
8
|
+
componentPromotions = [],
|
|
9
|
+
runtimeAssignments = [],
|
|
10
|
+
}) {
|
|
11
|
+
const items = [];
|
|
12
|
+
for (const [agentId, summary] of Object.entries(summariesByAgentId || {})) {
|
|
13
|
+
if (!summary?.docDelta) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (summary.docDelta.state === "owned") {
|
|
17
|
+
for (const docPath of summary.docDelta.paths || []) {
|
|
18
|
+
items.push({
|
|
19
|
+
id: `${agentId}:owned:${docPath}`,
|
|
20
|
+
kind: "owned-doc",
|
|
21
|
+
agentId,
|
|
22
|
+
ownerAgentId: agentId,
|
|
23
|
+
path: docPath,
|
|
24
|
+
summary: `Owned documentation update required in ${docPath}`,
|
|
25
|
+
detail: summary.docDelta.detail || "",
|
|
26
|
+
targets: [agentId],
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (summary.docDelta.state === "shared-plan") {
|
|
31
|
+
for (const docPath of summary.docDelta.paths || sharedPlanDocs) {
|
|
32
|
+
items.push({
|
|
33
|
+
id: `${agentId}:shared:${docPath}`,
|
|
34
|
+
kind: "shared-plan",
|
|
35
|
+
agentId,
|
|
36
|
+
ownerAgentId: null,
|
|
37
|
+
path: docPath,
|
|
38
|
+
summary: `Shared-plan reconciliation required in ${docPath}`,
|
|
39
|
+
detail: summary.docDelta.detail || "",
|
|
40
|
+
targets: [],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const promotion of componentPromotions || []) {
|
|
46
|
+
items.push({
|
|
47
|
+
id: `component-matrix:${promotion.componentId}`,
|
|
48
|
+
kind: "component-matrix",
|
|
49
|
+
agentId: null,
|
|
50
|
+
ownerAgentId: null,
|
|
51
|
+
path: promotion.componentId,
|
|
52
|
+
summary: `Component matrix currentLevel must reflect ${promotion.componentId} -> ${promotion.targetLevel}`,
|
|
53
|
+
detail: "",
|
|
54
|
+
targets: [],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const releaseNotesRequired = items.some((item) => item.kind === "shared-plan");
|
|
58
|
+
return {
|
|
59
|
+
lane,
|
|
60
|
+
wave: wave.wave || wave,
|
|
61
|
+
createdAt: toIsoTimestamp(),
|
|
62
|
+
updatedAt: toIsoTimestamp(),
|
|
63
|
+
releaseNotesRequired,
|
|
64
|
+
runtimeAssignments,
|
|
65
|
+
items,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function writeDocsQueue(filePath, payload) {
|
|
70
|
+
writeJsonAtomic(filePath, payload);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function readDocsQueue(filePath) {
|
|
74
|
+
return readJsonOrNull(filePath);
|
|
75
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_CODEX_COMMAND,
|
|
6
|
+
DEFAULT_CODEX_SANDBOX_MODE,
|
|
7
|
+
DEFAULT_EXECUTOR_MODE,
|
|
8
|
+
normalizeExecutorMode,
|
|
9
|
+
} from "./config.mjs";
|
|
10
|
+
import {
|
|
11
|
+
PACKAGE_ROOT,
|
|
12
|
+
REPO_ROOT,
|
|
13
|
+
ensureDirectory,
|
|
14
|
+
shellQuote,
|
|
15
|
+
writeJsonAtomic,
|
|
16
|
+
writeTextAtomic,
|
|
17
|
+
} from "./shared.mjs";
|
|
18
|
+
|
|
19
|
+
function appendSingleValueFlag(tokens, flag, value) {
|
|
20
|
+
if (value === null || value === undefined || value === "") {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
tokens.push(flag, shellQuote(value));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function appendRepeatedFlag(tokens, flag, values) {
|
|
27
|
+
const list = Array.isArray(values) ? values.filter(Boolean) : [];
|
|
28
|
+
if (list.length === 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
for (const value of list) {
|
|
32
|
+
tokens.push(flag, shellQuote(value));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function appendBooleanFlag(tokens, flag, enabled) {
|
|
37
|
+
if (!enabled) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
tokens.push(flag);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isPlainObject(value) {
|
|
44
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function cloneJson(value) {
|
|
48
|
+
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function mergeJsonObjects(...sources) {
|
|
52
|
+
const merged = {};
|
|
53
|
+
for (const source of sources) {
|
|
54
|
+
if (!isPlainObject(source)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
for (const [key, value] of Object.entries(source)) {
|
|
58
|
+
if (isPlainObject(value) && isPlainObject(merged[key])) {
|
|
59
|
+
merged[key] = mergeJsonObjects(merged[key], value);
|
|
60
|
+
} else {
|
|
61
|
+
merged[key] = cloneJson(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return merged;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function mergeUniqueStringArrays(...lists) {
|
|
69
|
+
const merged = [];
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
for (const list of lists) {
|
|
72
|
+
for (const value of Array.isArray(list) ? list : []) {
|
|
73
|
+
const normalized = String(value || "").trim();
|
|
74
|
+
if (!normalized || seen.has(normalized)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
seen.add(normalized);
|
|
78
|
+
merged.push(normalized);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return merged;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveRepoFilePath(filePath) {
|
|
85
|
+
if (!filePath) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return path.isAbsolute(filePath) ? filePath : path.join(REPO_ROOT, filePath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readJsonObjectFile(filePath, label) {
|
|
92
|
+
const absolutePath = resolveRepoFilePath(filePath);
|
|
93
|
+
if (!absolutePath || !fs.existsSync(absolutePath)) {
|
|
94
|
+
throw new Error(`${label} file not found: ${filePath}`);
|
|
95
|
+
}
|
|
96
|
+
let parsed;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(fs.readFileSync(absolutePath, "utf8"));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new Error(`Invalid ${label} JSON at ${filePath}: ${error.message}`);
|
|
101
|
+
}
|
|
102
|
+
if (!isPlainObject(parsed)) {
|
|
103
|
+
throw new Error(`${label} must be a JSON object: ${filePath}`);
|
|
104
|
+
}
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildClaudeSettingsPath(executor, overlayDir) {
|
|
109
|
+
const inlineSettings = executor.claude.settingsJson || null;
|
|
110
|
+
const inlineHooks = executor.claude.hooksJson || null;
|
|
111
|
+
const inlineAllowedHttpHookUrls = Array.isArray(executor.claude.allowedHttpHookUrls)
|
|
112
|
+
? executor.claude.allowedHttpHookUrls.filter(Boolean)
|
|
113
|
+
: [];
|
|
114
|
+
const hasInlineOverlay =
|
|
115
|
+
Boolean(inlineSettings) ||
|
|
116
|
+
Boolean(inlineHooks) ||
|
|
117
|
+
inlineAllowedHttpHookUrls.length > 0;
|
|
118
|
+
if (!hasInlineOverlay) {
|
|
119
|
+
return executor.claude.settings || null;
|
|
120
|
+
}
|
|
121
|
+
const baseSettings = executor.claude.settings
|
|
122
|
+
? readJsonObjectFile(executor.claude.settings, "Claude settings")
|
|
123
|
+
: {};
|
|
124
|
+
const merged = mergeJsonObjects(
|
|
125
|
+
baseSettings,
|
|
126
|
+
inlineSettings,
|
|
127
|
+
inlineHooks ? { hooks: inlineHooks } : null,
|
|
128
|
+
inlineAllowedHttpHookUrls.length > 0
|
|
129
|
+
? { allowedHttpHookUrls: inlineAllowedHttpHookUrls }
|
|
130
|
+
: null,
|
|
131
|
+
);
|
|
132
|
+
const settingsPath = path.join(overlayDir, "claude-settings.json");
|
|
133
|
+
writeJsonAtomic(settingsPath, merged);
|
|
134
|
+
return settingsPath;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function buildOpenCodeConfig({ agent, executor, agentName, promptFileName, overlayDir }) {
|
|
138
|
+
const promptAgent = {
|
|
139
|
+
description: `Wave agent ${agent.agentId}: ${agent.title}`,
|
|
140
|
+
mode: "primary",
|
|
141
|
+
prompt: `{file:./${promptFileName}}`,
|
|
142
|
+
...(executor.opencode.model || executor.model
|
|
143
|
+
? { model: executor.opencode.model || executor.model }
|
|
144
|
+
: {}),
|
|
145
|
+
...(executor.opencode.steps ? { steps: executor.opencode.steps } : {}),
|
|
146
|
+
...(executor.opencode.permission ? { permission: executor.opencode.permission } : {}),
|
|
147
|
+
};
|
|
148
|
+
const baseConfig = isPlainObject(executor.opencode.configJson) ? executor.opencode.configJson : {};
|
|
149
|
+
const baseAgentConfig = isPlainObject(baseConfig.agent) ? baseConfig.agent : {};
|
|
150
|
+
const config = mergeJsonObjects(baseConfig, {
|
|
151
|
+
$schema: baseConfig.$schema || "https://opencode.ai/config.json",
|
|
152
|
+
instructions: mergeUniqueStringArrays(baseConfig.instructions, executor.opencode.instructions),
|
|
153
|
+
agent: {
|
|
154
|
+
...baseAgentConfig,
|
|
155
|
+
[agentName]: mergeJsonObjects(baseAgentConfig[agentName], promptAgent),
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
const configPath = path.join(overlayDir, "opencode.json");
|
|
159
|
+
writeJsonAtomic(configPath, config);
|
|
160
|
+
return configPath;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderHarnessSystemPrompt(agent, executorId) {
|
|
164
|
+
return [
|
|
165
|
+
"You are running inside the Wave orchestration harness.",
|
|
166
|
+
`Resolved executor: ${executorId}.`,
|
|
167
|
+
`Assigned wave agent: ${agent.agentId} (${agent.title}).`,
|
|
168
|
+
"Treat the incoming task prompt as the authoritative assignment.",
|
|
169
|
+
"Preserve structured Wave markers exactly when they are required by the task.",
|
|
170
|
+
"Do not omit or rewrite message-board requirements, exit-contract requirements, or ownership boundaries.",
|
|
171
|
+
"Prefer plain text output unless the task explicitly requires code blocks.",
|
|
172
|
+
].join("\n");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function buildCodexExecInvocation(
|
|
176
|
+
promptPath,
|
|
177
|
+
logPath,
|
|
178
|
+
codexSandboxMode,
|
|
179
|
+
command = DEFAULT_CODEX_COMMAND,
|
|
180
|
+
options = {},
|
|
181
|
+
) {
|
|
182
|
+
const tokens = [
|
|
183
|
+
command,
|
|
184
|
+
"--ask-for-approval never",
|
|
185
|
+
"exec",
|
|
186
|
+
"--skip-git-repo-check",
|
|
187
|
+
`--sandbox ${shellQuote(codexSandboxMode || DEFAULT_CODEX_SANDBOX_MODE)}`,
|
|
188
|
+
];
|
|
189
|
+
appendSingleValueFlag(tokens, "--model", options.model);
|
|
190
|
+
appendSingleValueFlag(tokens, "--profile", options.profileName);
|
|
191
|
+
appendRepeatedFlag(tokens, "-c", options.config);
|
|
192
|
+
appendBooleanFlag(tokens, "--search", options.search);
|
|
193
|
+
appendRepeatedFlag(tokens, "--image", options.images);
|
|
194
|
+
appendRepeatedFlag(tokens, "--add-dir", options.addDirs);
|
|
195
|
+
appendBooleanFlag(tokens, "--json", options.json);
|
|
196
|
+
appendBooleanFlag(tokens, "--ephemeral", options.ephemeral);
|
|
197
|
+
tokens.push("-", `< ${shellQuote(promptPath)}`, `2>&1 | tee -a ${shellQuote(logPath)}`);
|
|
198
|
+
return tokens.join(" ");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function buildClaudeLaunchSpec({ agent, promptPath, logPath, overlayDir }) {
|
|
202
|
+
const executor = agent.executorResolved;
|
|
203
|
+
const systemPromptPath = path.join(overlayDir, "claude-system-prompt.txt");
|
|
204
|
+
writeTextAtomic(systemPromptPath, `${renderHarnessSystemPrompt(agent, "claude")}\n`);
|
|
205
|
+
const tokens = [executor.claude.command, "-p", "--no-session-persistence"];
|
|
206
|
+
const settingsPath = buildClaudeSettingsPath(executor, overlayDir);
|
|
207
|
+
appendSingleValueFlag(tokens, "--output-format", executor.claude.outputFormat || "text");
|
|
208
|
+
appendSingleValueFlag(tokens, "--model", executor.claude.model || executor.model);
|
|
209
|
+
appendSingleValueFlag(tokens, "--agent", executor.claude.agent);
|
|
210
|
+
appendSingleValueFlag(tokens, "--permission-mode", executor.claude.permissionMode);
|
|
211
|
+
appendSingleValueFlag(tokens, "--permission-prompt-tool", executor.claude.permissionPromptTool);
|
|
212
|
+
appendSingleValueFlag(tokens, "--max-turns", executor.claude.maxTurns);
|
|
213
|
+
appendRepeatedFlag(tokens, "--mcp-config", executor.claude.mcpConfig);
|
|
214
|
+
appendSingleValueFlag(tokens, "--settings", settingsPath);
|
|
215
|
+
appendRepeatedFlag(tokens, "--allowedTools", executor.claude.allowedTools);
|
|
216
|
+
appendRepeatedFlag(tokens, "--disallowedTools", executor.claude.disallowedTools);
|
|
217
|
+
if (executor.claude.strictMcpConfig) {
|
|
218
|
+
tokens.push("--strict-mcp-config");
|
|
219
|
+
}
|
|
220
|
+
tokens.push(
|
|
221
|
+
executor.claude.appendSystemPromptMode === "replace"
|
|
222
|
+
? "--system-prompt-file"
|
|
223
|
+
: "--append-system-prompt-file",
|
|
224
|
+
shellQuote(systemPromptPath),
|
|
225
|
+
);
|
|
226
|
+
return {
|
|
227
|
+
executorId: "claude",
|
|
228
|
+
command: executor.claude.command,
|
|
229
|
+
useRateLimitRetries: true,
|
|
230
|
+
invocationLines: [
|
|
231
|
+
`task_prompt=$(cat ${shellQuote(promptPath)})`,
|
|
232
|
+
`${tokens.join(" ")} "$task_prompt" 2>&1 | tee -a ${shellQuote(logPath)}`,
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function buildOpenCodeLaunchSpec({ agent, promptPath, logPath, overlayDir }) {
|
|
238
|
+
const executor = agent.executorResolved;
|
|
239
|
+
const requestedAgentName = String(executor.opencode.agent || `wave-${agent.agentId}`)
|
|
240
|
+
.trim()
|
|
241
|
+
.toLowerCase()
|
|
242
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
243
|
+
.replace(/-+/g, "-")
|
|
244
|
+
.replace(/^-+|-+$/g, "");
|
|
245
|
+
const agentName = requestedAgentName || `wave-${agent.agentId.toLowerCase()}`;
|
|
246
|
+
const promptFileName = "opencode-agent-prompt.txt";
|
|
247
|
+
const promptFilePath = path.join(overlayDir, promptFileName);
|
|
248
|
+
writeTextAtomic(promptFilePath, `${renderHarnessSystemPrompt(agent, "opencode")}\n`);
|
|
249
|
+
const configPath = buildOpenCodeConfig({
|
|
250
|
+
agent,
|
|
251
|
+
executor,
|
|
252
|
+
agentName,
|
|
253
|
+
promptFileName,
|
|
254
|
+
overlayDir,
|
|
255
|
+
});
|
|
256
|
+
const tokens = [executor.opencode.command, "run", "--agent", shellQuote(agentName)];
|
|
257
|
+
appendSingleValueFlag(tokens, "--model", executor.opencode.model || executor.model);
|
|
258
|
+
appendSingleValueFlag(tokens, "--format", executor.opencode.format || "default");
|
|
259
|
+
appendSingleValueFlag(tokens, "--attach", executor.opencode.attach);
|
|
260
|
+
appendRepeatedFlag(tokens, "--file", executor.opencode.files);
|
|
261
|
+
appendSingleValueFlag(tokens, "--title", `wave-${agent.agentId}`);
|
|
262
|
+
return {
|
|
263
|
+
executorId: "opencode",
|
|
264
|
+
command: executor.opencode.command,
|
|
265
|
+
useRateLimitRetries: true,
|
|
266
|
+
env: {
|
|
267
|
+
OPENCODE_CONFIG: configPath,
|
|
268
|
+
},
|
|
269
|
+
invocationLines: [
|
|
270
|
+
`task_prompt=$(cat ${shellQuote(promptPath)})`,
|
|
271
|
+
`${tokens.join(" ")} "$task_prompt" 2>&1 | tee -a ${shellQuote(logPath)}`,
|
|
272
|
+
],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function buildLocalLaunchSpec({ promptPath, logPath }) {
|
|
277
|
+
return {
|
|
278
|
+
executorId: "local",
|
|
279
|
+
command: "node",
|
|
280
|
+
useRateLimitRetries: false,
|
|
281
|
+
invocationLines: [
|
|
282
|
+
`node ${shellQuote(path.join(PACKAGE_ROOT, "scripts", "wave-local-executor.mjs"))} --prompt-file ${shellQuote(
|
|
283
|
+
promptPath,
|
|
284
|
+
)} 2>&1 | tee ${shellQuote(logPath)}`,
|
|
285
|
+
],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function buildCodexLaunchSpec({ agent, promptPath, logPath }) {
|
|
290
|
+
const executor = agent.executorResolved;
|
|
291
|
+
return {
|
|
292
|
+
executorId: "codex",
|
|
293
|
+
command: executor.codex.command,
|
|
294
|
+
useRateLimitRetries: true,
|
|
295
|
+
invocationLines: [
|
|
296
|
+
buildCodexExecInvocation(
|
|
297
|
+
promptPath,
|
|
298
|
+
logPath,
|
|
299
|
+
executor.codex.sandbox,
|
|
300
|
+
executor.codex.command,
|
|
301
|
+
{
|
|
302
|
+
model: executor.model,
|
|
303
|
+
profileName: executor.codex.profileName,
|
|
304
|
+
config: executor.codex.config,
|
|
305
|
+
search: executor.codex.search,
|
|
306
|
+
images: executor.codex.images,
|
|
307
|
+
addDirs: executor.codex.addDirs,
|
|
308
|
+
json: executor.codex.json,
|
|
309
|
+
ephemeral: executor.codex.ephemeral,
|
|
310
|
+
},
|
|
311
|
+
),
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function buildExecutorLaunchSpec({ agent, promptPath, logPath, overlayDir }) {
|
|
317
|
+
const executorId = normalizeExecutorMode(agent?.executorResolved?.id || DEFAULT_EXECUTOR_MODE);
|
|
318
|
+
ensureDirectory(overlayDir);
|
|
319
|
+
if (executorId === "local") {
|
|
320
|
+
return buildLocalLaunchSpec({ promptPath, logPath });
|
|
321
|
+
}
|
|
322
|
+
if (executorId === "claude") {
|
|
323
|
+
return buildClaudeLaunchSpec({ agent, promptPath, logPath, overlayDir });
|
|
324
|
+
}
|
|
325
|
+
if (executorId === "opencode") {
|
|
326
|
+
return buildOpenCodeLaunchSpec({ agent, promptPath, logPath, overlayDir });
|
|
327
|
+
}
|
|
328
|
+
return buildCodexLaunchSpec({ agent, promptPath, logPath });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function commandForExecutor(executor, executorId = executor?.id) {
|
|
332
|
+
if (executorId === "codex") {
|
|
333
|
+
return executor?.codex?.command || DEFAULT_CODEX_COMMAND;
|
|
334
|
+
}
|
|
335
|
+
if (executorId === "claude") {
|
|
336
|
+
return executor?.claude?.command || "claude";
|
|
337
|
+
}
|
|
338
|
+
if (executorId === "opencode") {
|
|
339
|
+
return executor?.opencode?.command || "opencode";
|
|
340
|
+
}
|
|
341
|
+
return "node";
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function isExecutorCommandAvailable(command) {
|
|
345
|
+
const result = spawnSync("bash", ["-lc", `command -v ${shellQuote(command)}`], {
|
|
346
|
+
cwd: REPO_ROOT,
|
|
347
|
+
encoding: "utf8",
|
|
348
|
+
env: process.env,
|
|
349
|
+
});
|
|
350
|
+
return result.status === 0;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function preflightExecutorCommand(command, executorId) {
|
|
354
|
+
if (isExecutorCommandAvailable(command)) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const result = spawnSync("bash", ["-lc", `command -v ${shellQuote(command)}`], {
|
|
358
|
+
cwd: REPO_ROOT,
|
|
359
|
+
encoding: "utf8",
|
|
360
|
+
env: process.env,
|
|
361
|
+
});
|
|
362
|
+
const detail = (result.stderr || result.stdout || "").trim();
|
|
363
|
+
throw new Error(
|
|
364
|
+
`Executor "${executorId}" requires "${command}" on PATH${detail ? ` (${detail})` : ""}`,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function preflightExecutorsForWaves(waves) {
|
|
369
|
+
const seen = new Map();
|
|
370
|
+
for (const wave of waves) {
|
|
371
|
+
for (const agent of wave.agents) {
|
|
372
|
+
const executor = agent.executorResolved;
|
|
373
|
+
if (!executor) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const command = commandForExecutor(executor, executor.id);
|
|
377
|
+
const key = `${executor.id}:${command}`;
|
|
378
|
+
if (seen.has(key)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
seen.set(key, true);
|
|
382
|
+
preflightExecutorCommand(command, executor.id);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|