@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.
- package/CHANGELOG.md +30 -1
- package/LICENSE.md +21 -0
- package/README.md +18 -6
- package/docs/README.md +8 -4
- package/docs/agents/wave-security-role.md +1 -0
- package/docs/architecture/README.md +1 -1
- package/docs/concepts/operating-modes.md +1 -1
- package/docs/guides/author-and-run-waves.md +1 -1
- package/docs/guides/planner.md +2 -2
- package/docs/guides/{recommendations-0.9.1.md → recommendations-0.9.2.md} +7 -7
- package/docs/guides/sandboxed-environments.md +2 -2
- package/docs/plans/current-state.md +8 -2
- package/docs/plans/end-state-architecture.md +1 -1
- package/docs/plans/examples/wave-example-design-handoff.md +1 -1
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +42 -18
- package/docs/reference/cli-reference.md +1 -1
- package/docs/reference/coordination-and-closure.md +18 -1
- package/docs/reference/corridor.md +225 -0
- package/docs/reference/npmjs-token-publishing.md +2 -2
- package/docs/reference/package-publishing-flow.md +11 -11
- package/docs/reference/runtime-config/README.md +61 -3
- package/docs/reference/sample-waves.md +5 -5
- package/docs/reference/skills.md +1 -1
- package/docs/reference/wave-control.md +358 -27
- package/docs/roadmap.md +12 -19
- package/package.json +1 -1
- package/releases/manifest.json +22 -3
- package/scripts/wave-cli-bootstrap.mjs +52 -1
- package/scripts/wave-orchestrator/config.mjs +199 -3
- package/scripts/wave-orchestrator/context7.mjs +231 -29
- package/scripts/wave-orchestrator/coordination.mjs +14 -0
- package/scripts/wave-orchestrator/corridor.mjs +363 -0
- package/scripts/wave-orchestrator/derived-state-engine.mjs +38 -1
- package/scripts/wave-orchestrator/gate-engine.mjs +20 -0
- package/scripts/wave-orchestrator/install.mjs +34 -1
- package/scripts/wave-orchestrator/launcher-runtime.mjs +111 -7
- package/scripts/wave-orchestrator/planner.mjs +1 -0
- package/scripts/wave-orchestrator/projection-writer.mjs +23 -0
- package/scripts/wave-orchestrator/provider-runtime.mjs +104 -0
- package/scripts/wave-orchestrator/shared.mjs +1 -0
- package/scripts/wave-orchestrator/traces.mjs +25 -0
- package/scripts/wave-orchestrator/wave-control-client.mjs +14 -1
|
@@ -73,12 +73,22 @@ export const DEFAULT_CODEX_SANDBOX_MODE = "danger-full-access";
|
|
|
73
73
|
export const CODEX_SANDBOX_MODES = ["read-only", "workspace-write", "danger-full-access"];
|
|
74
74
|
export const DEFAULT_CLAUDE_COMMAND = "claude";
|
|
75
75
|
export const DEFAULT_OPENCODE_COMMAND = "opencode";
|
|
76
|
-
export const DEFAULT_WAVE_CONTROL_AUTH_TOKEN_ENV_VAR = "
|
|
76
|
+
export const DEFAULT_WAVE_CONTROL_AUTH_TOKEN_ENV_VAR = "WAVE_API_TOKEN";
|
|
77
|
+
export const LEGACY_WAVE_CONTROL_AUTH_TOKEN_ENV_VAR = "WAVE_CONTROL_AUTH_TOKEN";
|
|
77
78
|
export const DEFAULT_WAVE_CONTROL_ENDPOINT = "https://wave-control.up.railway.app/api/v1";
|
|
78
79
|
export const DEFAULT_WAVE_CONTROL_REPORT_MODE = "metadata-only";
|
|
79
80
|
export const DEFAULT_WAVE_CONTROL_REQUEST_TIMEOUT_MS = 5000;
|
|
80
81
|
export const DEFAULT_WAVE_CONTROL_FLUSH_BATCH_SIZE = 25;
|
|
81
82
|
export const DEFAULT_WAVE_CONTROL_MAX_PENDING_EVENTS = 1000;
|
|
83
|
+
export const WAVE_CONTROL_RUNTIME_CREDENTIAL_PROVIDERS = ["anthropic", "openai"];
|
|
84
|
+
export const EXTERNAL_PROVIDER_MODES = ["direct", "broker", "hybrid"];
|
|
85
|
+
export const DEFAULT_CONTEXT7_API_KEY_ENV_VAR = "CONTEXT7_API_KEY";
|
|
86
|
+
export const DEFAULT_CORRIDOR_API_TOKEN_ENV_VAR = "CORRIDOR_API_TOKEN";
|
|
87
|
+
export const DEFAULT_CORRIDOR_API_KEY_FALLBACK_ENV_VAR = "CORRIDOR_API_KEY";
|
|
88
|
+
export const DEFAULT_CORRIDOR_BASE_URL = "https://app.corridor.dev/api";
|
|
89
|
+
export const DEFAULT_CORRIDOR_SEVERITY_THRESHOLD = "critical";
|
|
90
|
+
export const DEFAULT_CORRIDOR_FINDING_STATES = ["open", "potential"];
|
|
91
|
+
export const CORRIDOR_SEVERITY_LEVELS = ["low", "medium", "high", "critical"];
|
|
82
92
|
export const DEFAULT_WAVE_CONTROL_SELECTED_ARTIFACT_KINDS = [
|
|
83
93
|
"trace-run-metadata",
|
|
84
94
|
"trace-quality",
|
|
@@ -295,6 +305,103 @@ function normalizeOptionalJsonObject(value, label) {
|
|
|
295
305
|
throw new Error(`${label} must be a JSON object`);
|
|
296
306
|
}
|
|
297
307
|
|
|
308
|
+
function normalizeExternalProviderMode(value, label, fallback = "direct") {
|
|
309
|
+
const normalized = String(value || fallback)
|
|
310
|
+
.trim()
|
|
311
|
+
.toLowerCase();
|
|
312
|
+
if (!EXTERNAL_PROVIDER_MODES.includes(normalized)) {
|
|
313
|
+
throw new Error(`${label} must be one of: ${EXTERNAL_PROVIDER_MODES.join(", ")}`);
|
|
314
|
+
}
|
|
315
|
+
return normalized;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function normalizeCorridorSeverity(value, label, fallback = DEFAULT_CORRIDOR_SEVERITY_THRESHOLD) {
|
|
319
|
+
const normalized = String(value || fallback)
|
|
320
|
+
.trim()
|
|
321
|
+
.toLowerCase();
|
|
322
|
+
if (!CORRIDOR_SEVERITY_LEVELS.includes(normalized)) {
|
|
323
|
+
throw new Error(`${label} must be one of: ${CORRIDOR_SEVERITY_LEVELS.join(", ")}`);
|
|
324
|
+
}
|
|
325
|
+
return normalized;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function normalizeExternalProviders(rawExternalProviders = {}, label = "externalProviders") {
|
|
329
|
+
const externalProviders =
|
|
330
|
+
rawExternalProviders &&
|
|
331
|
+
typeof rawExternalProviders === "object" &&
|
|
332
|
+
!Array.isArray(rawExternalProviders)
|
|
333
|
+
? rawExternalProviders
|
|
334
|
+
: {};
|
|
335
|
+
const context7 =
|
|
336
|
+
externalProviders.context7 &&
|
|
337
|
+
typeof externalProviders.context7 === "object" &&
|
|
338
|
+
!Array.isArray(externalProviders.context7)
|
|
339
|
+
? externalProviders.context7
|
|
340
|
+
: {};
|
|
341
|
+
const corridor =
|
|
342
|
+
externalProviders.corridor &&
|
|
343
|
+
typeof externalProviders.corridor === "object" &&
|
|
344
|
+
!Array.isArray(externalProviders.corridor)
|
|
345
|
+
? externalProviders.corridor
|
|
346
|
+
: {};
|
|
347
|
+
const context7Mode = normalizeExternalProviderMode(
|
|
348
|
+
context7.mode,
|
|
349
|
+
`${label}.context7.mode`,
|
|
350
|
+
"direct",
|
|
351
|
+
);
|
|
352
|
+
const corridorMode = normalizeExternalProviderMode(
|
|
353
|
+
corridor.mode,
|
|
354
|
+
`${label}.corridor.mode`,
|
|
355
|
+
"direct",
|
|
356
|
+
);
|
|
357
|
+
const normalized = {
|
|
358
|
+
context7: {
|
|
359
|
+
mode: context7Mode,
|
|
360
|
+
apiKeyEnvVar:
|
|
361
|
+
normalizeOptionalString(
|
|
362
|
+
context7.apiKeyEnvVar,
|
|
363
|
+
DEFAULT_CONTEXT7_API_KEY_ENV_VAR,
|
|
364
|
+
) || DEFAULT_CONTEXT7_API_KEY_ENV_VAR,
|
|
365
|
+
},
|
|
366
|
+
corridor: {
|
|
367
|
+
enabled: normalizeOptionalBoolean(corridor.enabled, false),
|
|
368
|
+
mode: corridorMode,
|
|
369
|
+
baseUrl: normalizeOptionalString(corridor.baseUrl, DEFAULT_CORRIDOR_BASE_URL),
|
|
370
|
+
apiTokenEnvVar:
|
|
371
|
+
normalizeOptionalString(
|
|
372
|
+
corridor.apiTokenEnvVar,
|
|
373
|
+
DEFAULT_CORRIDOR_API_TOKEN_ENV_VAR,
|
|
374
|
+
) || DEFAULT_CORRIDOR_API_TOKEN_ENV_VAR,
|
|
375
|
+
apiKeyFallbackEnvVar:
|
|
376
|
+
normalizeOptionalString(
|
|
377
|
+
corridor.apiKeyFallbackEnvVar,
|
|
378
|
+
DEFAULT_CORRIDOR_API_KEY_FALLBACK_ENV_VAR,
|
|
379
|
+
) || DEFAULT_CORRIDOR_API_KEY_FALLBACK_ENV_VAR,
|
|
380
|
+
teamId: normalizeOptionalString(corridor.teamId, null),
|
|
381
|
+
projectId: normalizeOptionalString(corridor.projectId, null),
|
|
382
|
+
severityThreshold: normalizeCorridorSeverity(
|
|
383
|
+
corridor.severityThreshold,
|
|
384
|
+
`${label}.corridor.severityThreshold`,
|
|
385
|
+
),
|
|
386
|
+
findingStates: normalizeOptionalStringArray(
|
|
387
|
+
corridor.findingStates,
|
|
388
|
+
DEFAULT_CORRIDOR_FINDING_STATES,
|
|
389
|
+
),
|
|
390
|
+
requiredAtClosure: normalizeOptionalBoolean(corridor.requiredAtClosure, true),
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
if (
|
|
394
|
+
normalized.corridor.enabled &&
|
|
395
|
+
normalized.corridor.mode === "direct" &&
|
|
396
|
+
(!normalized.corridor.teamId || !normalized.corridor.projectId)
|
|
397
|
+
) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`${label}.corridor.teamId and ${label}.corridor.projectId are required when corridor is enabled in direct mode`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return normalized;
|
|
403
|
+
}
|
|
404
|
+
|
|
298
405
|
function normalizeExecutorBudget(rawBudget = {}, label = "budget") {
|
|
299
406
|
const budget =
|
|
300
407
|
rawBudget && typeof rawBudget === "object" && !Array.isArray(rawBudget) ? rawBudget : {};
|
|
@@ -578,6 +685,31 @@ function normalizeWaveControl(rawWaveControl = {}, label = "waveControl") {
|
|
|
578
685
|
rawWaveControl && typeof rawWaveControl === "object" && !Array.isArray(rawWaveControl)
|
|
579
686
|
? rawWaveControl
|
|
580
687
|
: {};
|
|
688
|
+
const credentials = Array.isArray(waveControl.credentials) ? waveControl.credentials : [];
|
|
689
|
+
const normalizedCredentials = [];
|
|
690
|
+
const seenCredentialEnvVars = new Set();
|
|
691
|
+
for (const [index, entry] of credentials.entries()) {
|
|
692
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
693
|
+
throw new Error(`${label}.credentials[${index}] must be an object with id and envVar.`);
|
|
694
|
+
}
|
|
695
|
+
const id = String(entry.id || "").trim().toLowerCase();
|
|
696
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/.test(id)) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`${label}.credentials[${index}].id must match /^[a-z0-9][a-z0-9._-]*$/.`,
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
const envVar = String(entry.envVar || "").trim().toUpperCase();
|
|
702
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(envVar)) {
|
|
703
|
+
throw new Error(
|
|
704
|
+
`${label}.credentials[${index}].envVar must match /^[A-Z_][A-Z0-9_]*$/.`,
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
if (seenCredentialEnvVars.has(envVar)) {
|
|
708
|
+
throw new Error(`${label}.credentials contains duplicate envVar mappings for ${envVar}.`);
|
|
709
|
+
}
|
|
710
|
+
seenCredentialEnvVars.add(envVar);
|
|
711
|
+
normalizedCredentials.push({ id, envVar });
|
|
712
|
+
}
|
|
581
713
|
const reportMode = normalizeWaveControlReportMode(
|
|
582
714
|
waveControl.reportMode,
|
|
583
715
|
`${label}.reportMode`,
|
|
@@ -591,8 +723,36 @@ function normalizeWaveControl(rawWaveControl = {}, label = "waveControl") {
|
|
|
591
723
|
workspaceId: normalizeOptionalString(waveControl.workspaceId, null),
|
|
592
724
|
projectId: normalizeOptionalString(waveControl.projectId, null),
|
|
593
725
|
authTokenEnvVar:
|
|
594
|
-
normalizeOptionalString(
|
|
595
|
-
|
|
726
|
+
normalizeOptionalString(
|
|
727
|
+
waveControl.authTokenEnvVar,
|
|
728
|
+
DEFAULT_WAVE_CONTROL_AUTH_TOKEN_ENV_VAR,
|
|
729
|
+
) || DEFAULT_WAVE_CONTROL_AUTH_TOKEN_ENV_VAR,
|
|
730
|
+
authTokenEnvVars: Array.from(
|
|
731
|
+
new Set(
|
|
732
|
+
normalizeOptionalStringArray(
|
|
733
|
+
waveControl.authTokenEnvVars,
|
|
734
|
+
[
|
|
735
|
+
normalizeOptionalString(
|
|
736
|
+
waveControl.authTokenEnvVar,
|
|
737
|
+
DEFAULT_WAVE_CONTROL_AUTH_TOKEN_ENV_VAR,
|
|
738
|
+
) || DEFAULT_WAVE_CONTROL_AUTH_TOKEN_ENV_VAR,
|
|
739
|
+
LEGACY_WAVE_CONTROL_AUTH_TOKEN_ENV_VAR,
|
|
740
|
+
],
|
|
741
|
+
),
|
|
742
|
+
),
|
|
743
|
+
),
|
|
744
|
+
credentialProviders: normalizeOptionalStringArray(waveControl.credentialProviders, []).map(
|
|
745
|
+
(providerId, index) => {
|
|
746
|
+
const normalized = String(providerId || "").trim().toLowerCase();
|
|
747
|
+
if (!WAVE_CONTROL_RUNTIME_CREDENTIAL_PROVIDERS.includes(normalized)) {
|
|
748
|
+
throw new Error(
|
|
749
|
+
`${label}.credentialProviders[${index}] must be one of: ${WAVE_CONTROL_RUNTIME_CREDENTIAL_PROVIDERS.join(", ")}`,
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
return normalized;
|
|
753
|
+
},
|
|
754
|
+
),
|
|
755
|
+
credentials: normalizedCredentials,
|
|
596
756
|
reportMode,
|
|
597
757
|
uploadArtifactKinds: normalizeOptionalStringArray(
|
|
598
758
|
waveControl.uploadArtifactKinds,
|
|
@@ -1109,6 +1269,7 @@ export function loadWaveConfig(configPath = DEFAULT_WAVE_CONFIG_PATH) {
|
|
|
1109
1269
|
capabilityRouting: rawProject.capabilityRouting || {},
|
|
1110
1270
|
runtimePolicy: rawProject.runtimePolicy || {},
|
|
1111
1271
|
waveControl: rawProject.waveControl || {},
|
|
1272
|
+
externalProviders: rawProject.externalProviders || {},
|
|
1112
1273
|
lanes: projectLanes,
|
|
1113
1274
|
explicit: Boolean(rawProjects),
|
|
1114
1275
|
},
|
|
@@ -1130,6 +1291,10 @@ export function loadWaveConfig(configPath = DEFAULT_WAVE_CONFIG_PATH) {
|
|
|
1130
1291
|
capabilityRouting: normalizeCapabilityRouting(rawConfig.capabilityRouting),
|
|
1131
1292
|
runtimePolicy: normalizeRuntimePolicy(rawConfig.runtimePolicy),
|
|
1132
1293
|
waveControl: normalizeWaveControl(rawConfig.waveControl, "waveControl"),
|
|
1294
|
+
externalProviders: normalizeExternalProviders(
|
|
1295
|
+
rawConfig.externalProviders,
|
|
1296
|
+
"externalProviders",
|
|
1297
|
+
),
|
|
1133
1298
|
sharedPlanDocs,
|
|
1134
1299
|
lanes: legacyLanes,
|
|
1135
1300
|
projects,
|
|
@@ -1182,6 +1347,21 @@ export function resolveProjectProfile(config, projectInput = config.defaultProje
|
|
|
1182
1347
|
...config.runtimePolicy,
|
|
1183
1348
|
...(projectConfig.runtimePolicy || {}),
|
|
1184
1349
|
}),
|
|
1350
|
+
externalProviders: normalizeExternalProviders(
|
|
1351
|
+
{
|
|
1352
|
+
...config.externalProviders,
|
|
1353
|
+
...(projectConfig.externalProviders || {}),
|
|
1354
|
+
context7: {
|
|
1355
|
+
...(config.externalProviders?.context7 || {}),
|
|
1356
|
+
...(projectConfig.externalProviders?.context7 || {}),
|
|
1357
|
+
},
|
|
1358
|
+
corridor: {
|
|
1359
|
+
...(config.externalProviders?.corridor || {}),
|
|
1360
|
+
...(projectConfig.externalProviders?.corridor || {}),
|
|
1361
|
+
},
|
|
1362
|
+
},
|
|
1363
|
+
`projects.${projectId}.externalProviders`,
|
|
1364
|
+
),
|
|
1185
1365
|
waveControl: normalizeWaveControl(
|
|
1186
1366
|
{
|
|
1187
1367
|
...config.waveControl,
|
|
@@ -1256,6 +1436,21 @@ export function resolveLaneProfile(config, laneInput = config.defaultLane, proje
|
|
|
1256
1436
|
},
|
|
1257
1437
|
`${lane}.waveControl`,
|
|
1258
1438
|
);
|
|
1439
|
+
const externalProviders = normalizeExternalProviders(
|
|
1440
|
+
{
|
|
1441
|
+
...projectProfile.externalProviders,
|
|
1442
|
+
...(laneConfig.externalProviders || {}),
|
|
1443
|
+
context7: {
|
|
1444
|
+
...(projectProfile.externalProviders?.context7 || {}),
|
|
1445
|
+
...(laneConfig.externalProviders?.context7 || {}),
|
|
1446
|
+
},
|
|
1447
|
+
corridor: {
|
|
1448
|
+
...(projectProfile.externalProviders?.corridor || {}),
|
|
1449
|
+
...(laneConfig.externalProviders?.corridor || {}),
|
|
1450
|
+
},
|
|
1451
|
+
},
|
|
1452
|
+
`${lane}.externalProviders`,
|
|
1453
|
+
);
|
|
1259
1454
|
return {
|
|
1260
1455
|
projectId: projectProfile.projectId,
|
|
1261
1456
|
projectName: projectProfile.projectName,
|
|
@@ -1276,6 +1471,7 @@ export function resolveLaneProfile(config, laneInput = config.defaultLane, proje
|
|
|
1276
1471
|
skills,
|
|
1277
1472
|
capabilityRouting,
|
|
1278
1473
|
runtimePolicy,
|
|
1474
|
+
externalProviders,
|
|
1279
1475
|
waveControl,
|
|
1280
1476
|
paths: {
|
|
1281
1477
|
terminalsPath: normalizeRepoRelativePath(
|
|
@@ -9,6 +9,15 @@ import {
|
|
|
9
9
|
sleep,
|
|
10
10
|
writeJsonAtomic,
|
|
11
11
|
} from "./shared.mjs";
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_CONTEXT7_API_KEY_ENV_VAR,
|
|
14
|
+
DEFAULT_WAVE_CONTROL_ENDPOINT,
|
|
15
|
+
} from "./config.mjs";
|
|
16
|
+
import {
|
|
17
|
+
isDefaultWaveControlEndpoint,
|
|
18
|
+
readJsonResponse,
|
|
19
|
+
resolveWaveControlAuthToken,
|
|
20
|
+
} from "./provider-runtime.mjs";
|
|
12
21
|
|
|
13
22
|
export const DEFAULT_CONTEXT7_BUNDLE_INDEX_PATH = path.join(
|
|
14
23
|
REPO_ROOT,
|
|
@@ -277,16 +286,10 @@ function renderPrefetchedContextText({ selection, results, budget }) {
|
|
|
277
286
|
return trimContextText(sections.join("\n\n"), budget);
|
|
278
287
|
}
|
|
279
288
|
|
|
280
|
-
async function requestContext7(fetchImpl,
|
|
289
|
+
async function requestContext7(fetchImpl, request, { expectText = false, maxRetries = 3 } = {}) {
|
|
281
290
|
let lastError = null;
|
|
282
291
|
for (let attempt = 0; attempt < maxRetries; attempt += 1) {
|
|
283
|
-
const response = await
|
|
284
|
-
method: "GET",
|
|
285
|
-
headers: {
|
|
286
|
-
Authorization: `Bearer ${apiKey}`,
|
|
287
|
-
Accept: expectText ? "text/plain, application/json" : "application/json",
|
|
288
|
-
},
|
|
289
|
-
});
|
|
292
|
+
const response = await request();
|
|
290
293
|
if (response.ok) {
|
|
291
294
|
return expectText ? response.text() : response.json();
|
|
292
295
|
}
|
|
@@ -296,7 +299,7 @@ async function requestContext7(fetchImpl, url, { apiKey, expectText = false, max
|
|
|
296
299
|
: 0;
|
|
297
300
|
let payload = null;
|
|
298
301
|
try {
|
|
299
|
-
payload = await response
|
|
302
|
+
payload = await readJsonResponse(response, null);
|
|
300
303
|
} catch {
|
|
301
304
|
payload = null;
|
|
302
305
|
}
|
|
@@ -311,7 +314,193 @@ async function requestContext7(fetchImpl, url, { apiKey, expectText = false, max
|
|
|
311
314
|
throw lastError || new Error("Context7 request failed.");
|
|
312
315
|
}
|
|
313
316
|
|
|
314
|
-
|
|
317
|
+
function buildDirectContext7Requester(fetchImpl, apiKey) {
|
|
318
|
+
return {
|
|
319
|
+
async search(params) {
|
|
320
|
+
const url = `${CONTEXT7_SEARCH_URL}?${params.toString()}`;
|
|
321
|
+
return requestContext7(
|
|
322
|
+
fetchImpl,
|
|
323
|
+
() =>
|
|
324
|
+
fetchImpl(url, {
|
|
325
|
+
method: "GET",
|
|
326
|
+
headers: {
|
|
327
|
+
Authorization: `Bearer ${apiKey}`,
|
|
328
|
+
Accept: "application/json",
|
|
329
|
+
},
|
|
330
|
+
}),
|
|
331
|
+
);
|
|
332
|
+
},
|
|
333
|
+
async context(params) {
|
|
334
|
+
const url = `${CONTEXT7_CONTEXT_URL}?${params.toString()}`;
|
|
335
|
+
return requestContext7(
|
|
336
|
+
fetchImpl,
|
|
337
|
+
() =>
|
|
338
|
+
fetchImpl(url, {
|
|
339
|
+
method: "GET",
|
|
340
|
+
headers: {
|
|
341
|
+
Authorization: `Bearer ${apiKey}`,
|
|
342
|
+
Accept: "text/plain, application/json",
|
|
343
|
+
},
|
|
344
|
+
}),
|
|
345
|
+
{ expectText: true },
|
|
346
|
+
);
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function buildBrokerContext7Requester(fetchImpl, lanePaths) {
|
|
352
|
+
const waveControl = lanePaths?.waveControl || lanePaths?.laneProfile?.waveControl || {};
|
|
353
|
+
const endpoint = String(waveControl.endpoint || DEFAULT_WAVE_CONTROL_ENDPOINT).trim();
|
|
354
|
+
if (!endpoint || isDefaultWaveControlEndpoint(endpoint)) {
|
|
355
|
+
throw new Error("Context7 broker mode requires an owned Wave Control endpoint.");
|
|
356
|
+
}
|
|
357
|
+
const authToken = resolveWaveControlAuthToken(waveControl);
|
|
358
|
+
if (!authToken) {
|
|
359
|
+
throw new Error("WAVE_API_TOKEN is not set; skipping Context7 broker prefetch.");
|
|
360
|
+
}
|
|
361
|
+
const baseEndpoint = endpoint.replace(/\/$/, "");
|
|
362
|
+
return {
|
|
363
|
+
async search(params) {
|
|
364
|
+
return requestContext7(
|
|
365
|
+
fetchImpl,
|
|
366
|
+
() =>
|
|
367
|
+
fetchImpl(`${baseEndpoint}/providers/context7/search?${params.toString()}`, {
|
|
368
|
+
method: "GET",
|
|
369
|
+
headers: {
|
|
370
|
+
authorization: `Bearer ${authToken}`,
|
|
371
|
+
accept: "application/json",
|
|
372
|
+
},
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
},
|
|
376
|
+
async context(params) {
|
|
377
|
+
return requestContext7(
|
|
378
|
+
fetchImpl,
|
|
379
|
+
() =>
|
|
380
|
+
fetchImpl(`${baseEndpoint}/providers/context7/context?${params.toString()}`, {
|
|
381
|
+
method: "GET",
|
|
382
|
+
headers: {
|
|
383
|
+
authorization: `Bearer ${authToken}`,
|
|
384
|
+
accept: "text/plain, application/json",
|
|
385
|
+
},
|
|
386
|
+
}),
|
|
387
|
+
{ expectText: true },
|
|
388
|
+
);
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function buildHybridContext7Requester({
|
|
394
|
+
lanePaths,
|
|
395
|
+
fetchImpl,
|
|
396
|
+
directApiKey,
|
|
397
|
+
directApiKeyEnvVar,
|
|
398
|
+
}) {
|
|
399
|
+
const brokerRequester = buildBrokerContext7Requester(fetchImpl, lanePaths);
|
|
400
|
+
let directRequester = null;
|
|
401
|
+
let activeProviderMode = "broker";
|
|
402
|
+
let fallbackWarning = "";
|
|
403
|
+
|
|
404
|
+
const resolveDirectRequester = () => {
|
|
405
|
+
if (directRequester) {
|
|
406
|
+
return directRequester;
|
|
407
|
+
}
|
|
408
|
+
if (!directApiKey) {
|
|
409
|
+
throw new Error(`${directApiKeyEnvVar} is not set; skipping Context7 prefetch.`);
|
|
410
|
+
}
|
|
411
|
+
directRequester = buildDirectContext7Requester(fetchImpl, directApiKey);
|
|
412
|
+
return directRequester;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const runWithFallback = async (method, params) => {
|
|
416
|
+
if (activeProviderMode === "direct") {
|
|
417
|
+
return resolveDirectRequester()[method](params);
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
return await brokerRequester[method](params);
|
|
421
|
+
} catch (brokerError) {
|
|
422
|
+
let fallbackRequester = null;
|
|
423
|
+
try {
|
|
424
|
+
fallbackRequester = resolveDirectRequester();
|
|
425
|
+
} catch (fallbackUnavailableError) {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`Context7 broker request failed and direct fallback is unavailable: ${brokerError instanceof Error ? brokerError.message : String(brokerError)}; ${fallbackUnavailableError instanceof Error ? fallbackUnavailableError.message : String(fallbackUnavailableError)}`,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
activeProviderMode = "direct";
|
|
431
|
+
fallbackWarning =
|
|
432
|
+
fallbackWarning ||
|
|
433
|
+
`Context7 broker request failed; fell back to direct auth: ${brokerError instanceof Error ? brokerError.message : String(brokerError)}`;
|
|
434
|
+
try {
|
|
435
|
+
return await fallbackRequester[method](params);
|
|
436
|
+
} catch (fallbackError) {
|
|
437
|
+
throw new Error(
|
|
438
|
+
`Context7 broker request failed and direct fallback also failed: ${brokerError instanceof Error ? brokerError.message : String(brokerError)}; ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
requester: {
|
|
446
|
+
search(params) {
|
|
447
|
+
return runWithFallback("search", params);
|
|
448
|
+
},
|
|
449
|
+
context(params) {
|
|
450
|
+
return runWithFallback("context", params);
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
providerMode: "broker",
|
|
454
|
+
getProviderMode() {
|
|
455
|
+
return activeProviderMode;
|
|
456
|
+
},
|
|
457
|
+
getWarning() {
|
|
458
|
+
return fallbackWarning;
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function resolveContext7Requester({
|
|
464
|
+
lanePaths,
|
|
465
|
+
fetchImpl,
|
|
466
|
+
apiKey,
|
|
467
|
+
apiKeyEnvVar = DEFAULT_CONTEXT7_API_KEY_ENV_VAR,
|
|
468
|
+
}) {
|
|
469
|
+
const provider = lanePaths?.externalProviders?.context7 || {};
|
|
470
|
+
const mode = String(provider.mode || "direct").trim().toLowerCase();
|
|
471
|
+
const directApiKey = apiKey || process.env[provider.apiKeyEnvVar || apiKeyEnvVar] || "";
|
|
472
|
+
const direct = () => {
|
|
473
|
+
if (!directApiKey) {
|
|
474
|
+
throw new Error(`${provider.apiKeyEnvVar || apiKeyEnvVar} is not set; skipping Context7 prefetch.`);
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
requester: buildDirectContext7Requester(fetchImpl, directApiKey),
|
|
478
|
+
providerMode: "direct",
|
|
479
|
+
};
|
|
480
|
+
};
|
|
481
|
+
const broker = () => ({
|
|
482
|
+
requester: buildBrokerContext7Requester(fetchImpl, lanePaths),
|
|
483
|
+
providerMode: "broker",
|
|
484
|
+
});
|
|
485
|
+
if (mode === "broker") {
|
|
486
|
+
return broker();
|
|
487
|
+
}
|
|
488
|
+
if (mode === "hybrid") {
|
|
489
|
+
try {
|
|
490
|
+
return buildHybridContext7Requester({
|
|
491
|
+
lanePaths,
|
|
492
|
+
fetchImpl,
|
|
493
|
+
directApiKey,
|
|
494
|
+
directApiKeyEnvVar: provider.apiKeyEnvVar || apiKeyEnvVar,
|
|
495
|
+
});
|
|
496
|
+
} catch {
|
|
497
|
+
return direct();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return direct();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async function resolveLibraryId(requester, library, selection) {
|
|
315
504
|
if (library.libraryId) {
|
|
316
505
|
return {
|
|
317
506
|
libraryId: library.libraryId,
|
|
@@ -322,9 +511,7 @@ async function resolveLibraryId(fetchImpl, library, selection, apiKey) {
|
|
|
322
511
|
libraryName: library.libraryName,
|
|
323
512
|
query: selection.query || library.queryHint || library.libraryName,
|
|
324
513
|
});
|
|
325
|
-
const results = await
|
|
326
|
-
apiKey,
|
|
327
|
-
});
|
|
514
|
+
const results = await requester.search(params);
|
|
328
515
|
if (!Array.isArray(results) || results.length === 0) {
|
|
329
516
|
throw new Error(`Context7 search returned no matches for "${library.libraryName}".`);
|
|
330
517
|
}
|
|
@@ -334,8 +521,8 @@ async function resolveLibraryId(fetchImpl, library, selection, apiKey) {
|
|
|
334
521
|
};
|
|
335
522
|
}
|
|
336
523
|
|
|
337
|
-
async function fetchLibraryContext(
|
|
338
|
-
const resolvedLibrary = await resolveLibraryId(
|
|
524
|
+
async function fetchLibraryContext(requester, library, selection) {
|
|
525
|
+
const resolvedLibrary = await resolveLibraryId(requester, library, selection);
|
|
339
526
|
const query = compactSingleLine(
|
|
340
527
|
[selection.query, library.queryHint].filter(Boolean).join(". Focus: "),
|
|
341
528
|
320,
|
|
@@ -345,10 +532,7 @@ async function fetchLibraryContext(fetchImpl, library, selection, apiKey) {
|
|
|
345
532
|
query,
|
|
346
533
|
type: "txt",
|
|
347
534
|
});
|
|
348
|
-
const text = await
|
|
349
|
-
apiKey,
|
|
350
|
-
expectText: true,
|
|
351
|
-
});
|
|
535
|
+
const text = await requester.context(params);
|
|
352
536
|
return {
|
|
353
537
|
libraryId: resolvedLibrary.libraryId,
|
|
354
538
|
libraryName: resolvedLibrary.libraryName,
|
|
@@ -360,6 +544,7 @@ async function fetchLibraryContext(fetchImpl, library, selection, apiKey) {
|
|
|
360
544
|
export async function prefetchContext7ForSelection(
|
|
361
545
|
selection,
|
|
362
546
|
{
|
|
547
|
+
lanePaths = null,
|
|
363
548
|
cacheDir,
|
|
364
549
|
apiKey = process.env.CONTEXT7_API_KEY || "",
|
|
365
550
|
fetchImpl = globalThis.fetch,
|
|
@@ -397,13 +582,18 @@ export async function prefetchContext7ForSelection(
|
|
|
397
582
|
};
|
|
398
583
|
}
|
|
399
584
|
if (!apiKey) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
585
|
+
const providerMode = String(lanePaths?.externalProviders?.context7?.mode || "direct")
|
|
586
|
+
.trim()
|
|
587
|
+
.toLowerCase();
|
|
588
|
+
if (providerMode === "direct") {
|
|
589
|
+
return {
|
|
590
|
+
mode: "missing-key",
|
|
591
|
+
selection,
|
|
592
|
+
promptText: "",
|
|
593
|
+
snippetHash: "",
|
|
594
|
+
warning: "CONTEXT7_API_KEY is not set; skipping Context7 prefetch.",
|
|
595
|
+
};
|
|
596
|
+
}
|
|
407
597
|
}
|
|
408
598
|
|
|
409
599
|
ensureDirectory(cacheDir);
|
|
@@ -427,9 +617,15 @@ export async function prefetchContext7ForSelection(
|
|
|
427
617
|
}
|
|
428
618
|
|
|
429
619
|
try {
|
|
620
|
+
const requesterState = resolveContext7Requester({
|
|
621
|
+
lanePaths,
|
|
622
|
+
fetchImpl,
|
|
623
|
+
apiKey,
|
|
624
|
+
});
|
|
625
|
+
const { requester } = requesterState;
|
|
430
626
|
const results = [];
|
|
431
627
|
for (const library of selection.libraries) {
|
|
432
|
-
const result = await fetchLibraryContext(
|
|
628
|
+
const result = await fetchLibraryContext(requester, library, selection);
|
|
433
629
|
if (result.text) {
|
|
434
630
|
results.push(result);
|
|
435
631
|
}
|
|
@@ -450,12 +646,18 @@ export async function prefetchContext7ForSelection(
|
|
|
450
646
|
promptText,
|
|
451
647
|
snippetHash,
|
|
452
648
|
});
|
|
649
|
+
const providerMode =
|
|
650
|
+
typeof requesterState.getProviderMode === "function"
|
|
651
|
+
? requesterState.getProviderMode()
|
|
652
|
+
: requesterState.providerMode;
|
|
653
|
+
const warning =
|
|
654
|
+
typeof requesterState.getWarning === "function" ? requesterState.getWarning() : "";
|
|
453
655
|
return {
|
|
454
|
-
mode: "fetched",
|
|
656
|
+
mode: providerMode === "broker" ? "fetched-broker" : "fetched",
|
|
455
657
|
selection,
|
|
456
658
|
promptText,
|
|
457
659
|
snippetHash,
|
|
458
|
-
warning
|
|
660
|
+
warning,
|
|
459
661
|
};
|
|
460
662
|
} catch (error) {
|
|
461
663
|
return {
|
|
@@ -197,6 +197,8 @@ export function buildExecutionPrompt({
|
|
|
197
197
|
inboxPath = null,
|
|
198
198
|
inboxText = "",
|
|
199
199
|
context7 = null,
|
|
200
|
+
corridorContextPath = null,
|
|
201
|
+
corridorContextText = "",
|
|
200
202
|
componentPromotions = null,
|
|
201
203
|
evalTargets = null,
|
|
202
204
|
benchmarkCatalogPath = null,
|
|
@@ -215,6 +217,9 @@ export function buildExecutionPrompt({
|
|
|
215
217
|
? path.relative(REPO_ROOT, sharedSummaryPath)
|
|
216
218
|
: null;
|
|
217
219
|
const relativeInboxPath = inboxPath ? path.relative(REPO_ROOT, inboxPath) : null;
|
|
220
|
+
const relativeCorridorContextPath = corridorContextPath
|
|
221
|
+
? path.relative(REPO_ROOT, corridorContextPath)
|
|
222
|
+
: null;
|
|
218
223
|
const relativeSignalStatePath = signalStatePath
|
|
219
224
|
? path.relative(REPO_ROOT, signalStatePath)
|
|
220
225
|
: null;
|
|
@@ -537,6 +542,12 @@ export function buildExecutionPrompt({
|
|
|
537
542
|
`Agent inbox repo-relative path: ${relativeInboxPath}`,
|
|
538
543
|
]
|
|
539
544
|
: []),
|
|
545
|
+
...(corridorContextPath
|
|
546
|
+
? [
|
|
547
|
+
`Corridor context absolute path: ${corridorContextPath}`,
|
|
548
|
+
`Corridor context repo-relative path: ${relativeCorridorContextPath}`,
|
|
549
|
+
]
|
|
550
|
+
: []),
|
|
540
551
|
...(signalStatePath
|
|
541
552
|
? [
|
|
542
553
|
`Signal state absolute path: ${signalStatePath}`,
|
|
@@ -552,6 +563,9 @@ export function buildExecutionPrompt({
|
|
|
552
563
|
...(inboxText
|
|
553
564
|
? ["Current agent inbox:", "```markdown", inboxText, "```", ""]
|
|
554
565
|
: []),
|
|
566
|
+
...(corridorContextText
|
|
567
|
+
? ["Current Corridor context:", "```text", corridorContextText, "```", ""]
|
|
568
|
+
: []),
|
|
555
569
|
...(signalStatePath
|
|
556
570
|
? [
|
|
557
571
|
"Long-running signal loop:",
|