@chllming/wave-orchestration 0.9.1 → 0.9.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/LICENSE.md +21 -0
  3. package/README.md +18 -6
  4. package/docs/README.md +8 -4
  5. package/docs/agents/wave-security-role.md +1 -0
  6. package/docs/architecture/README.md +1 -1
  7. package/docs/concepts/operating-modes.md +1 -1
  8. package/docs/guides/author-and-run-waves.md +1 -1
  9. package/docs/guides/planner.md +2 -2
  10. package/docs/guides/{recommendations-0.9.1.md → recommendations-0.9.2.md} +7 -7
  11. package/docs/guides/sandboxed-environments.md +2 -2
  12. package/docs/plans/current-state.md +8 -2
  13. package/docs/plans/end-state-architecture.md +1 -1
  14. package/docs/plans/examples/wave-example-design-handoff.md +1 -1
  15. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  16. package/docs/plans/migration.md +42 -18
  17. package/docs/reference/cli-reference.md +1 -1
  18. package/docs/reference/coordination-and-closure.md +18 -1
  19. package/docs/reference/corridor.md +225 -0
  20. package/docs/reference/npmjs-token-publishing.md +2 -2
  21. package/docs/reference/package-publishing-flow.md +11 -11
  22. package/docs/reference/runtime-config/README.md +61 -3
  23. package/docs/reference/sample-waves.md +5 -5
  24. package/docs/reference/skills.md +1 -1
  25. package/docs/reference/wave-control.md +358 -27
  26. package/docs/roadmap.md +12 -19
  27. package/package.json +1 -1
  28. package/releases/manifest.json +22 -3
  29. package/scripts/wave-cli-bootstrap.mjs +52 -1
  30. package/scripts/wave-orchestrator/config.mjs +199 -3
  31. package/scripts/wave-orchestrator/context7.mjs +231 -29
  32. package/scripts/wave-orchestrator/coordination.mjs +14 -0
  33. package/scripts/wave-orchestrator/corridor.mjs +363 -0
  34. package/scripts/wave-orchestrator/derived-state-engine.mjs +38 -1
  35. package/scripts/wave-orchestrator/gate-engine.mjs +20 -0
  36. package/scripts/wave-orchestrator/install.mjs +34 -1
  37. package/scripts/wave-orchestrator/launcher-runtime.mjs +111 -7
  38. package/scripts/wave-orchestrator/planner.mjs +1 -0
  39. package/scripts/wave-orchestrator/projection-writer.mjs +23 -0
  40. package/scripts/wave-orchestrator/provider-runtime.mjs +104 -0
  41. package/scripts/wave-orchestrator/shared.mjs +1 -0
  42. package/scripts/wave-orchestrator/traces.mjs +25 -0
  43. package/scripts/wave-orchestrator/wave-control-client.mjs +14 -1
@@ -1,6 +1,11 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { buildExecutionPrompt } from "./coordination.mjs";
4
+ import {
5
+ materializeWaveCorridorContext,
6
+ renderCorridorPromptContext,
7
+ waveCorridorContextPath,
8
+ } from "./corridor.mjs";
4
9
  import {
5
10
  DEFAULT_AGENT_RATE_LIMIT_BASE_DELAY_SECONDS,
6
11
  DEFAULT_AGENT_RATE_LIMIT_MAX_DELAY_SECONDS,
@@ -15,7 +20,12 @@ import {
15
20
  import { readStatusCodeIfPresent } from "./dashboard-state.mjs";
16
21
  import { buildExecutorLaunchSpec } from "./executors.mjs";
17
22
  import { hashAgentPromptFingerprint, prefetchContext7ForSelection } from "./context7.mjs";
18
- import { isDesignAgent, resolveDesignReportPath, resolveWaveRoleBindings } from "./role-helpers.mjs";
23
+ import {
24
+ isDesignAgent,
25
+ isSecurityReviewAgent,
26
+ resolveDesignReportPath,
27
+ resolveWaveRoleBindings,
28
+ } from "./role-helpers.mjs";
19
29
  import {
20
30
  resolveAgentSkills,
21
31
  summarizeResolvedSkills,
@@ -30,6 +40,44 @@ import {
30
40
  spawnAgentProcessRunner,
31
41
  terminateAgentProcessRuntime,
32
42
  } from "./agent-process-runner.mjs";
43
+ import {
44
+ requestWaveControlCredentialEnv,
45
+ requestWaveControlProviderEnv,
46
+ } from "./provider-runtime.mjs";
47
+
48
+ function redactPreviewEnv(env = {}, redactedKeys = []) {
49
+ const output = { ...(env || {}) };
50
+ for (const key of redactedKeys) {
51
+ if (Object.prototype.hasOwnProperty.call(output, key)) {
52
+ output[key] = "[redacted]";
53
+ }
54
+ }
55
+ return output;
56
+ }
57
+
58
+ function buildDryRunContext7Preview(selection) {
59
+ if (
60
+ !selection ||
61
+ selection.bundleId === "none" ||
62
+ !Array.isArray(selection.libraries) ||
63
+ selection.libraries.length === 0
64
+ ) {
65
+ return {
66
+ mode: "none",
67
+ selection,
68
+ promptText: "",
69
+ snippetHash: "",
70
+ warning: "",
71
+ };
72
+ }
73
+ return {
74
+ mode: "dry-run",
75
+ selection,
76
+ promptText: "",
77
+ snippetHash: "",
78
+ warning: "Context7 prefetch skipped during dry-run preview.",
79
+ };
80
+ }
33
81
 
34
82
  export function refreshResolvedSkillsForRun(runInfo, waveDefinition, lanePaths) {
35
83
  runInfo.agent.skillsResolved = resolveAgentSkills(
@@ -141,13 +189,31 @@ export async function launchAgentSession(
141
189
  fs.rmSync(runtimePath, { force: true });
142
190
  }
143
191
 
144
- const context7 = await prefetchContext7ForSelection(agent.context7Resolved, {
145
- cacheDir: lanePaths.context7CacheDir,
146
- disabled: !context7Enabled,
147
- });
192
+ const resolvedWaveDefinition = waveDefinition || { deployEnvironments: [] };
193
+ const context7 = dryRun
194
+ ? buildDryRunContext7Preview(agent.context7Resolved || null)
195
+ : await prefetchContext7ForSelection(agent.context7Resolved, {
196
+ lanePaths,
197
+ cacheDir: lanePaths.context7CacheDir,
198
+ disabled: !context7Enabled,
199
+ });
200
+ const integrationAgentId =
201
+ waveDefinition?.integrationAgentId || lanePaths.integrationAgentId || "A8";
202
+ const shouldLoadCorridorContext =
203
+ lanePaths.externalProviders?.corridor?.enabled === true &&
204
+ (isSecurityReviewAgent(agent) || agent.agentId === integrationAgentId);
205
+ const corridorContext = !dryRun && shouldLoadCorridorContext
206
+ ? await materializeWaveCorridorContext(lanePaths, resolvedWaveDefinition)
207
+ : null;
208
+ const corridorContextPath = !dryRun && shouldLoadCorridorContext
209
+ ? waveCorridorContextPath(lanePaths, wave)
210
+ : null;
211
+ const corridorContextText =
212
+ dryRun && shouldLoadCorridorContext
213
+ ? "Corridor context omitted in dry-run preview."
214
+ : renderCorridorPromptContext(corridorContext);
148
215
  const overlayDir = path.join(lanePaths.executorOverlaysDir, `wave-${wave}`, agent.slug);
149
216
  ensureDirectory(overlayDir);
150
- const resolvedWaveDefinition = waveDefinition || { deployEnvironments: [] };
151
217
  const skillsResolved =
152
218
  agent.skillsResolved ||
153
219
  resolveAgentSkills(agent, resolvedWaveDefinition, {
@@ -176,6 +242,8 @@ export async function launchAgentSession(
176
242
  inboxPath,
177
243
  inboxText,
178
244
  context7,
245
+ corridorContextPath,
246
+ corridorContextText,
179
247
  componentPromotions: resolvedWaveDefinition.componentPromotions,
180
248
  evalTargets: resolvedWaveDefinition.evalTargets,
181
249
  benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
@@ -201,11 +269,45 @@ export async function launchAgentSession(
201
269
  overlayDir,
202
270
  skillProjection: agent.skillsResolved,
203
271
  });
272
+ const requestedCredentialProviders = Array.isArray(lanePaths.waveControl?.credentialProviders)
273
+ ? lanePaths.waveControl.credentialProviders
274
+ : [];
275
+ const requestedCredentials = Array.isArray(lanePaths.waveControl?.credentials)
276
+ ? lanePaths.waveControl.credentials
277
+ : [];
278
+ const leasedProviderEnv =
279
+ !dryRun && requestedCredentialProviders.length > 0
280
+ ? await requestWaveControlProviderEnv(fetch, lanePaths.waveControl, requestedCredentialProviders)
281
+ : {};
282
+ const leasedCredentialEnv =
283
+ !dryRun && requestedCredentials.length > 0
284
+ ? await requestWaveControlCredentialEnv(fetch, lanePaths.waveControl, requestedCredentials)
285
+ : {};
286
+ const overlappingLeasedEnvVars = Object.keys(leasedProviderEnv).filter((key) =>
287
+ Object.prototype.hasOwnProperty.call(leasedCredentialEnv, key),
288
+ );
289
+ if (overlappingLeasedEnvVars.length > 0) {
290
+ throw new Error(
291
+ `Wave Control leased duplicate environment variables: ${overlappingLeasedEnvVars.join(", ")}.`,
292
+ );
293
+ }
294
+ const leasedEnv = {
295
+ ...leasedProviderEnv,
296
+ ...leasedCredentialEnv,
297
+ };
298
+ if (Object.keys(leasedEnv).length > 0) {
299
+ launchSpec.env = {
300
+ ...(launchSpec.env || {}),
301
+ ...leasedEnv,
302
+ };
303
+ }
204
304
  const resolvedExecutorMode = launchSpec.executorId || agent.executorResolved?.id || "codex";
205
305
  writeJsonAtomic(path.join(overlayDir, "launch-preview.json"), {
206
306
  executorId: resolvedExecutorMode,
207
307
  command: launchSpec.command,
208
- env: launchSpec.env || {},
308
+ env: redactPreviewEnv(launchSpec.env || {}, Object.keys(leasedEnv)),
309
+ credentialProviders: requestedCredentialProviders,
310
+ credentials: requestedCredentials,
209
311
  useRateLimitRetries: launchSpec.useRateLimitRetries === true,
210
312
  invocationLines: launchSpec.invocationLines,
211
313
  limits: launchSpec.limits || null,
@@ -215,6 +317,7 @@ export async function launchAgentSession(
215
317
  return {
216
318
  promptHash,
217
319
  context7,
320
+ corridorContext,
218
321
  executorId: resolvedExecutorMode,
219
322
  launchSpec,
220
323
  dryRun: true,
@@ -339,6 +442,7 @@ export async function launchAgentSession(
339
442
  return {
340
443
  promptHash,
341
444
  context7,
445
+ corridorContext,
342
446
  executorId: resolvedExecutorMode,
343
447
  skills: summarizeResolvedSkills(agent.skillsResolved),
344
448
  runtimePath,
@@ -2625,6 +2625,7 @@ async function runAgenticDraftFlow(options = {}) {
2625
2625
  request,
2626
2626
  });
2627
2627
  const plannerContext7Prefetch = await prefetchContext7ForSelection(plannerContext7Selection, {
2628
+ lanePaths,
2628
2629
  cacheDir: lanePaths.context7CacheDir,
2629
2630
  });
2630
2631
  const promptText = buildPlannerPromptText({
@@ -126,6 +126,8 @@ export function writeWaveAttemptTraceProjection({
126
126
  capabilityAssignments: derivedState.capabilityAssignments,
127
127
  dependencySnapshot: derivedState.dependencySnapshot,
128
128
  securitySummary: derivedState.securitySummary,
129
+ corridorSummary: derivedState.corridorSummary,
130
+ corridorSummaryPath: derivedState.corridorSummaryPath,
129
131
  integrationSummary: derivedState.integrationSummary,
130
132
  integrationMarkdownPath: derivedState.integrationMarkdownPath,
131
133
  proofRegistryPath: lanePaths.proofDir ? waveProofRegistryPath(lanePaths, wave.wave) : null,
@@ -191,6 +193,7 @@ export function writeWaveRelaunchProjection({
191
193
  }
192
194
 
193
195
  function renderWaveSecuritySummaryMarkdown(securitySummary) {
196
+ const corridor = securitySummary?.corridor || null;
194
197
  return [
195
198
  `# Wave ${securitySummary.wave} Security Summary`,
196
199
  "",
@@ -199,7 +202,18 @@ function renderWaveSecuritySummaryMarkdown(securitySummary) {
199
202
  `- Total findings: ${securitySummary.totalFindings || 0}`,
200
203
  `- Total approvals: ${securitySummary.totalApprovals || 0}`,
201
204
  `- Reviewers: ${(securitySummary.agents || []).length}`,
205
+ `- Corridor: ${corridor ? (corridor.ok ? (corridor.blocking ? "blocking" : "clear") : "fetch-failed") : "not-configured"}`,
202
206
  "",
207
+ ...(corridor
208
+ ? [
209
+ "## Corridor",
210
+ `- Source: ${corridor.source || corridor.providerMode || "unknown"}`,
211
+ `- Matched findings: ${(corridor.matchedFindings || []).length}`,
212
+ `- Blocking findings: ${(corridor.blockingFindings || []).length}`,
213
+ ...(corridor.error ? [`- Error: ${corridor.error}`] : []),
214
+ "",
215
+ ]
216
+ : []),
203
217
  "## Reviews",
204
218
  ...((securitySummary.agents || []).length > 0
205
219
  ? securitySummary.agents.map(
@@ -220,6 +234,7 @@ function renderIntegrationSection(title, items) {
220
234
  }
221
235
 
222
236
  function renderIntegrationSummaryMarkdown(integrationSummary) {
237
+ const corridor = integrationSummary?.corridor || null;
223
238
  return [
224
239
  `# Wave ${integrationSummary.wave} Integration Summary`,
225
240
  "",
@@ -239,6 +254,7 @@ function renderIntegrationSummaryMarkdown(integrationSummary) {
239
254
  `- Inbound dependencies: ${(integrationSummary.inboundDependencies || []).length}`,
240
255
  `- Outbound dependencies: ${(integrationSummary.outboundDependencies || []).length}`,
241
256
  `- Helper assignments: ${(integrationSummary.helperAssignments || []).length}`,
257
+ `- Corridor blocking findings: ${(corridor?.blockingFindings || []).length}`,
242
258
  "",
243
259
  ...renderIntegrationSection("## Open Claims", integrationSummary.openClaims),
244
260
  ...renderIntegrationSection("## Conflicting Claims", integrationSummary.conflictingClaims),
@@ -251,6 +267,13 @@ function renderIntegrationSummaryMarkdown(integrationSummary) {
251
267
  ...renderIntegrationSection("## Proof Gaps", integrationSummary.proofGaps),
252
268
  ...renderIntegrationSection("## Deploy Risks", integrationSummary.deployRisks),
253
269
  ...renderIntegrationSection("## Security Findings", integrationSummary.securityFindings),
270
+ ...renderIntegrationSection(
271
+ "## Corridor Findings",
272
+ corridor?.matchedFindings?.map(
273
+ (finding) =>
274
+ `${finding.severity || "unknown"} ${finding.affectedFile || "unknown-file"}: ${finding.title || "finding"}`,
275
+ ) || [],
276
+ ),
254
277
  ...renderIntegrationSection("## Security Approvals", integrationSummary.securityApprovals),
255
278
  ...renderIntegrationSection("## Inbound Dependencies", integrationSummary.inboundDependencies),
256
279
  ...renderIntegrationSection("## Outbound Dependencies", integrationSummary.outboundDependencies),
@@ -0,0 +1,104 @@
1
+ import { DEFAULT_WAVE_CONTROL_ENDPOINT } from "./config.mjs";
2
+
3
+ export function resolveEnvValue(envVars, env = process.env) {
4
+ for (const envVar of Array.isArray(envVars) ? envVars : [envVars]) {
5
+ const value = envVar ? String(env[envVar] || "").trim() : "";
6
+ if (value) {
7
+ return value;
8
+ }
9
+ }
10
+ return "";
11
+ }
12
+
13
+ export function resolveWaveControlAuthToken(waveControl = {}, env = process.env) {
14
+ const envVars = Array.isArray(waveControl?.authTokenEnvVars)
15
+ ? waveControl.authTokenEnvVars
16
+ : [waveControl?.authTokenEnvVar].filter(Boolean);
17
+ return resolveEnvValue(envVars, env);
18
+ }
19
+
20
+ export function isDefaultWaveControlEndpoint(endpoint) {
21
+ const normalized = String(endpoint || "").trim().replace(/\/+$/, "");
22
+ return normalized === String(DEFAULT_WAVE_CONTROL_ENDPOINT).trim().replace(/\/+$/, "");
23
+ }
24
+
25
+ export async function readJsonResponse(response, fallback = null) {
26
+ try {
27
+ return await response.json();
28
+ } catch {
29
+ return fallback;
30
+ }
31
+ }
32
+
33
+ export async function requestProvider(fetchImpl, url, options = {}) {
34
+ const response = await fetchImpl(url, options);
35
+ if (response.ok) {
36
+ return response;
37
+ }
38
+ const payload = await readJsonResponse(response, null);
39
+ throw new Error(
40
+ `${options.method || "GET"} ${url} failed (${response.status}): ${payload?.error || payload?.message || response.statusText || "unknown error"}`,
41
+ );
42
+ }
43
+
44
+ export async function requestWaveControlProviderEnv(fetchImpl, waveControl = {}, providers = []) {
45
+ const endpoint = String(waveControl.endpoint || DEFAULT_WAVE_CONTROL_ENDPOINT).trim();
46
+ if (!endpoint) {
47
+ throw new Error("Wave Control endpoint is not configured.");
48
+ }
49
+ if (isDefaultWaveControlEndpoint(endpoint)) {
50
+ throw new Error("Wave Control provider credential leasing requires an owned Wave Control deployment.");
51
+ }
52
+ const token = resolveWaveControlAuthToken(waveControl);
53
+ if (!token) {
54
+ throw new Error("WAVE_API_TOKEN is not set; Wave Control credential leasing is unavailable.");
55
+ }
56
+ const response = await requestProvider(
57
+ fetchImpl,
58
+ `${endpoint.replace(/\/$/, "")}/runtime/provider-env`,
59
+ {
60
+ method: "POST",
61
+ headers: {
62
+ authorization: `Bearer ${token}`,
63
+ "content-type": "application/json",
64
+ accept: "application/json",
65
+ },
66
+ body: JSON.stringify({ providers }),
67
+ },
68
+ );
69
+ const payload = await readJsonResponse(response, null);
70
+ return payload?.env && typeof payload.env === "object" && !Array.isArray(payload.env)
71
+ ? payload.env
72
+ : {};
73
+ }
74
+
75
+ export async function requestWaveControlCredentialEnv(fetchImpl, waveControl = {}, credentials = []) {
76
+ const endpoint = String(waveControl.endpoint || DEFAULT_WAVE_CONTROL_ENDPOINT).trim();
77
+ if (!endpoint) {
78
+ throw new Error("Wave Control endpoint is not configured.");
79
+ }
80
+ if (isDefaultWaveControlEndpoint(endpoint)) {
81
+ throw new Error("Wave Control credential leasing requires an owned Wave Control deployment.");
82
+ }
83
+ const token = resolveWaveControlAuthToken(waveControl);
84
+ if (!token) {
85
+ throw new Error("WAVE_API_TOKEN is not set; Wave Control credential leasing is unavailable.");
86
+ }
87
+ const response = await requestProvider(
88
+ fetchImpl,
89
+ `${endpoint.replace(/\/$/, "")}/runtime/credential-env`,
90
+ {
91
+ method: "POST",
92
+ headers: {
93
+ authorization: `Bearer ${token}`,
94
+ "content-type": "application/json",
95
+ accept: "application/json",
96
+ },
97
+ body: JSON.stringify({ credentials }),
98
+ },
99
+ );
100
+ const payload = await readJsonResponse(response, null);
101
+ return payload?.env && typeof payload.env === "object" && !Array.isArray(payload.env)
102
+ ? payload.env
103
+ : {};
104
+ }
@@ -279,6 +279,7 @@ export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
279
279
  executors: laneProfile.executors,
280
280
  skills: laneProfile.skills,
281
281
  capabilityRouting: laneProfile.capabilityRouting,
282
+ externalProviders: laneProfile.externalProviders,
282
283
  projectId: buildTelemetryProjectId(laneProfile.waveControl || { projectId }),
283
284
  projectRootDir: path.join(REPO_ROOT, laneProfile.projectRootDir || "."),
284
285
  runtimeVersion: readRuntimeVersion(),
@@ -798,6 +798,8 @@ export function writeTraceBundle({
798
798
  capabilityAssignments = [],
799
799
  dependencySnapshot = null,
800
800
  securitySummary = null,
801
+ corridorSummary = null,
802
+ corridorSummaryPath = null,
801
803
  integrationSummary,
802
804
  integrationMarkdownPath,
803
805
  proofRegistryPath = null,
@@ -860,6 +862,28 @@ export function writeTraceBundle({
860
862
  "json",
861
863
  true,
862
864
  );
865
+ const corridorArtifact =
866
+ corridorSummaryPath || corridorSummary
867
+ ? corridorSummaryPath
868
+ ? copyArtifactDescriptor(
869
+ dir,
870
+ corridorSummaryPath,
871
+ path.join(dir, "corridor.json"),
872
+ false,
873
+ )
874
+ : writeArtifactDescriptor(
875
+ dir,
876
+ path.join(dir, "corridor.json"),
877
+ corridorSummary || {},
878
+ "json",
879
+ false,
880
+ )
881
+ : {
882
+ path: "corridor.json",
883
+ required: false,
884
+ present: false,
885
+ sha256: null,
886
+ };
863
887
  const integrationArtifact = writeArtifactDescriptor(
864
888
  dir,
865
889
  path.join(dir, "integration.json"),
@@ -1023,6 +1047,7 @@ export function writeTraceBundle({
1023
1047
  capabilityAssignments: capabilityAssignmentsArtifact,
1024
1048
  dependencySnapshot: dependencySnapshotArtifact,
1025
1049
  security: securityArtifact,
1050
+ corridor: corridorArtifact,
1026
1051
  integration: integrationArtifact,
1027
1052
  integrationMarkdown: integrationMarkdownArtifact,
1028
1053
  proofRegistry: proofRegistryArtifact,
@@ -154,6 +154,19 @@ function resolveWaveControlConfig(lanePaths, overrides = {}) {
154
154
  };
155
155
  }
156
156
 
157
+ function resolveWaveControlAuthToken(config) {
158
+ const authVars = Array.isArray(config.authTokenEnvVars)
159
+ ? config.authTokenEnvVars
160
+ : [config.authTokenEnvVar].filter(Boolean);
161
+ for (const envVar of authVars) {
162
+ const value = envVar ? String(process.env[envVar] || "").trim() : "";
163
+ if (value) {
164
+ return value;
165
+ }
166
+ }
167
+ return "";
168
+ }
169
+
157
170
  function buildWorkspaceId(lanePaths, config) {
158
171
  return normalizeText(config.workspaceId, buildWorkspaceTmuxToken(REPO_ROOT));
159
172
  }
@@ -505,7 +518,7 @@ export async function flushWaveControlQueue(lanePaths, options = {}) {
505
518
 
506
519
  try {
507
520
  const ingestUrl = resolveIngestUrl(config.endpoint);
508
- const authToken = config.authTokenEnvVar ? process.env[config.authTokenEnvVar] || "" : "";
521
+ const authToken = resolveWaveControlAuthToken(config);
509
522
  await postBatch(
510
523
  ingestUrl,
511
524
  authToken,