open-multi-agent-kit 0.78.6 → 0.78.7
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/AGENTS.md +1 -1
- package/CHANGELOG.md +26 -0
- package/MATURITY.md +2 -2
- package/README.md +19 -4
- package/dist/brand/night-city.theme.json +80 -0
- package/dist/brand/palette.d.ts +73 -177
- package/dist/brand/palette.js +115 -39
- package/dist/brand/rust-forge.theme.json +216 -0
- package/dist/brand/theme-compiled.d.ts +61 -0
- package/dist/brand/theme-compiled.js +241 -0
- package/dist/brand/theme.js +50 -37
- package/dist/cli/release-promotion-gate.d.ts +8 -1
- package/dist/cli/release-promotion-gate.js +20 -9
- package/dist/cli/root.d.ts +1 -0
- package/dist/cli/root.js +2 -1
- package/dist/cli/theme/index.d.ts +11 -2
- package/dist/cli/theme/index.js +6 -1
- package/dist/cli/theme/oklab-quantize.d.ts +29 -0
- package/dist/cli/theme/oklab-quantize.js +105 -0
- package/dist/cli/theme/render-table.d.ts +59 -0
- package/dist/cli/theme/render-table.js +112 -0
- package/dist/cli/theme/status-frame.d.ts +10 -0
- package/dist/cli/theme/status-frame.js +22 -0
- package/dist/cli/theme/terminal-capability.d.ts +6 -1
- package/dist/cli/theme/terminal-capability.js +20 -4
- package/dist/cli/theme/theme-doc.d.ts +22 -0
- package/dist/cli/theme/theme-doc.js +97 -0
- package/dist/cli/theme/theme-registry.js +39 -18
- package/dist/cli/theme/tier-explain.d.ts +21 -0
- package/dist/cli/theme/tier-explain.js +60 -0
- package/dist/cli/ui/green-rain-renderer.js +3 -2
- package/dist/cli/ui/neon-grid-renderer.js +4 -3
- package/dist/cli/ui/rust-forge-renderer.js +17 -3
- package/dist/cli/ui/system24-renderer.d.ts +18 -1
- package/dist/cli/ui/system24-renderer.js +39 -16
- package/dist/cli/v2/chat-repl.js +3 -1
- package/dist/cli/v2/cli-v2-skeleton.d.ts +6 -0
- package/dist/cli/v2/cli-v2-skeleton.js +121 -9
- package/dist/cli/v2/interactive-prompt.d.ts +1 -0
- package/dist/cli/v2/interactive-prompt.js +6 -2
- package/dist/commands/chat/core.js +2 -2
- package/dist/commands/chat/runtime.js +2 -0
- package/dist/commands/chat/utils.js +12 -4
- package/dist/commands/cockpit/render.js +102 -64
- package/dist/commands/cockpit/utils.d.ts +2 -0
- package/dist/commands/design.js +18 -16
- package/dist/commands/doctor/checks.d.ts +0 -1
- package/dist/commands/doctor/checks.js +1 -36
- package/dist/commands/doctor/report.js +0 -11
- package/dist/commands/init/content.d.ts +1 -1
- package/dist/commands/init/content.js +3 -11
- package/dist/commands/init.js +2 -2
- package/dist/commands/mcp/config.d.ts +32 -0
- package/dist/commands/mcp/config.js +264 -0
- package/dist/commands/mcp/doctor-fix.d.ts +12 -0
- package/dist/commands/mcp/doctor-fix.js +261 -0
- package/dist/commands/mcp/doctor.d.ts +55 -0
- package/dist/commands/mcp/doctor.js +401 -0
- package/dist/commands/mcp/list.d.ts +1 -0
- package/dist/commands/mcp/list.js +56 -0
- package/dist/commands/mcp/shared.d.ts +47 -0
- package/dist/commands/mcp/shared.js +192 -0
- package/dist/commands/mcp/test.d.ts +5 -0
- package/dist/commands/mcp/test.js +457 -0
- package/dist/commands/mcp.d.ts +8 -104
- package/dist/commands/mcp.js +5 -1603
- package/dist/contracts/proof.d.ts +1 -1
- package/dist/goal/control-loop.d.ts +15 -0
- package/dist/goal/control-loop.js +39 -5
- package/dist/hud/render.d.ts +2 -0
- package/dist/hud/render.js +7 -1
- package/dist/mcp/secret-scanner.d.ts +6 -0
- package/dist/mcp/secret-scanner.js +44 -11
- package/dist/memory/graph-delta-log.d.ts +137 -0
- package/dist/memory/graph-delta-log.js +650 -0
- package/dist/memory/graph-viewer.js +13 -32
- package/dist/memory/local-graph-memory-store.d.ts +36 -3
- package/dist/memory/local-graph-memory-store.js +255 -10
- package/dist/orchestration/log-streamer.js +13 -8
- package/dist/orchestration/routing.d.ts +4 -0
- package/dist/orchestration/routing.js +28 -3
- package/dist/orchestration/state-persister.js +8 -1
- package/dist/providers/anthropic-messages-runner.d.ts +17 -0
- package/dist/providers/anthropic-messages-runner.js +197 -0
- package/dist/providers/model-registry.d.ts +1 -1
- package/dist/providers/model-registry.js +37 -3
- package/dist/providers/provider-runtime.js +27 -11
- package/dist/providers/thinking-levels.js +4 -1
- package/dist/providers/types.d.ts +3 -3
- package/dist/runtime/contracts/weakness-remediation.d.ts +9 -1
- package/dist/runtime/contracts/weakness-remediation.js +9 -1
- package/dist/runtime/router-v2-scoring.d.ts +8 -3
- package/dist/runtime/router-v2-scoring.js +27 -22
- package/dist/runtime/runtime-router.d.ts +5 -0
- package/dist/runtime/runtime-router.js +40 -8
- package/dist/runtime/sandbox-profile.d.ts +29 -0
- package/dist/runtime/sandbox-profile.js +101 -0
- package/dist/runtime/slash-commands.js +300 -2
- package/dist/runtime/tool-dispatch-contracts.d.ts +15 -0
- package/dist/runtime/tool-dispatch-contracts.js +12 -0
- package/dist/runtime/weights-config.d.ts +90 -0
- package/dist/runtime/weights-config.js +249 -0
- package/dist/schema/envelope.schema.d.ts +18 -18
- package/dist/schema/evidence.schema.d.ts +2 -2
- package/dist/schema/proof-bundle.schema.d.ts +6 -6
- package/dist/schema/proof-bundle.schema.js +0 -1
- package/dist/schema/provider.schema.d.ts +2 -2
- package/dist/schema/run-manifest.schema.d.ts +16 -16
- package/dist/theme/ansi.js +9 -2
- package/dist/theme/extended-palette.d.ts +85 -0
- package/dist/theme/extended-palette.js +108 -0
- package/dist/theme/layout.js +29 -8
- package/dist/theme/metrics.js +5 -5
- package/dist/theme/parallel.js +25 -15
- package/dist/ui/omk-sigil.js +5 -14
- package/dist/ui/omk-working-sweep.js +5 -15
- package/dist/util/chat-agent-mode.d.ts +11 -0
- package/dist/util/chat-agent-mode.js +41 -8
- package/dist/util/chat-cockpit.d.ts +9 -0
- package/dist/util/chat-cockpit.js +77 -20
- package/dist/util/fs/core.d.ts +5 -0
- package/dist/util/fs/core.js +35 -0
- package/dist/util/fs/internal.d.ts +14 -0
- package/dist/util/fs/internal.js +169 -0
- package/dist/util/fs/kimi-sync.d.ts +41 -0
- package/dist/util/fs/kimi-sync.js +495 -0
- package/dist/util/fs/logo.d.ts +2 -0
- package/dist/util/fs/logo.js +78 -0
- package/dist/util/fs/manifest.d.ts +6 -0
- package/dist/util/fs/manifest.js +40 -0
- package/dist/util/fs/mcp-diagnose.d.ts +15 -0
- package/dist/util/fs/mcp-diagnose.js +204 -0
- package/dist/util/fs/mcp-runtime-config.d.ts +23 -0
- package/dist/util/fs/mcp-runtime-config.js +412 -0
- package/dist/util/fs/paths.d.ts +13 -0
- package/dist/util/fs/paths.js +82 -0
- package/dist/util/fs/preflight.d.ts +27 -0
- package/dist/util/fs/preflight.js +271 -0
- package/dist/util/fs.d.ts +8 -128
- package/dist/util/fs.js +8 -1757
- package/dist/util/terminal-layout.js +5 -1
- package/dist/util/version.js +2 -1
- package/docs/2026-06-11/critical-issues.md +20 -0
- package/docs/2026-06-11/improvements.md +14 -0
- package/docs/2026-06-11/init-checklist.md +25 -0
- package/docs/2026-06-11/plan.md +20 -0
- package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +223 -0
- package/docs/adr/0001-no-native-rust-lane.md +52 -0
- package/docs/codex-oauth-setup.md +14 -0
- package/docs/decisions/2026-06-10-git-history-bloat.md +61 -0
- package/docs/decisions/ADR-theme-dark-only-assets.md +43 -0
- package/docs/getting-started.md +1 -1
- package/docs/headroom-omk-final-summary.md +188 -0
- package/docs/headroom-omk-integration.md +394 -0
- package/docs/headroom-omk-setup-guide.md +422 -0
- package/docs/headroom-omk-usage-examples.md +371 -0
- package/docs/provider-maturity.md +1 -1
- package/docs/versioning.md +3 -3
- package/package.json +10 -10
- package/readmeasset/.npmignore +2 -0
- package/readmeasset/ASSET_INDEX.md +7 -5
- package/readmeasset/ASSET_PROVENANCE.md +21 -5
- package/readmeasset/omk-control-surfaces.svg +55 -0
- package/readmeasset/omk-core-loop.svg +1 -0
- package/readmeasset/omk-logo-mark.svg +1 -0
- package/readmeasset/omk-release-assertions.svg +51 -0
- package/dist/native/linux-x64/omk-safety +0 -0
- package/dist/util/native-safety.d.ts +0 -28
- package/dist/util/native-safety.js +0 -118
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { renderPromptDigest } from "../goal/prompt-digest.js";
|
|
2
|
+
import { inferNodeRisk } from "./router.js";
|
|
3
|
+
import { contextPreflightErrorMessage, preflightProviderMessages, } from "./context-preflight.js";
|
|
4
|
+
export function createAnthropicMessagesReadOnlyTaskRunner(options) {
|
|
5
|
+
let currentOnThinking;
|
|
6
|
+
const runner = {
|
|
7
|
+
get onThinking() {
|
|
8
|
+
return currentOnThinking;
|
|
9
|
+
},
|
|
10
|
+
set onThinking(fn) {
|
|
11
|
+
currentOnThinking = fn;
|
|
12
|
+
},
|
|
13
|
+
fork(onThinking) {
|
|
14
|
+
const forked = createAnthropicMessagesReadOnlyTaskRunner(options);
|
|
15
|
+
forked.onThinking = onThinking;
|
|
16
|
+
return forked;
|
|
17
|
+
},
|
|
18
|
+
async run(node, env, signal) {
|
|
19
|
+
const risk = inferNodeRisk(node);
|
|
20
|
+
const advisoryMode = env.OMK_PROVIDER_AUTHORITY === "advisory" && risk === "write";
|
|
21
|
+
if (risk !== "read" && !advisoryMode) {
|
|
22
|
+
return deny(node, `${options.provider} runner is read-only/advisory; write/shell/merge authority stays on the authority provider`);
|
|
23
|
+
}
|
|
24
|
+
if (node.routing?.requiresToolCalling === true || node.routing?.requiresMcp === true) {
|
|
25
|
+
return deny(node, `${options.provider} runner does not receive tool or MCP authority`);
|
|
26
|
+
}
|
|
27
|
+
if (!options.apiKey) {
|
|
28
|
+
return deny(node, `${options.provider} API key is missing (${options.apiKeyEnv ?? "provider env"}); provider-neutral authority fallback is required`);
|
|
29
|
+
}
|
|
30
|
+
const model = env.OMK_PROVIDER_MODEL || options.model;
|
|
31
|
+
currentOnThinking?.(`${options.provider} ${env.OMK_PROVIDER_AUTHORITY ?? "direct"} worker: ${node.name}`);
|
|
32
|
+
try {
|
|
33
|
+
const messages = [
|
|
34
|
+
{
|
|
35
|
+
role: "system",
|
|
36
|
+
content: [
|
|
37
|
+
`You are a ${options.provider} read-only/advisory worker inside OMK.`,
|
|
38
|
+
"OMK and the configured authority provider are the root orchestrator and final authority.",
|
|
39
|
+
"Do not claim file writes, shell execution, secret access, MCP access, or merge authority.",
|
|
40
|
+
advisoryMode ? "For this file-affecting node, provide advisory strategy only." : "",
|
|
41
|
+
"Return concise findings, evidence, risks, and recommended authority-provider follow-up.",
|
|
42
|
+
].filter(Boolean).join(" "),
|
|
43
|
+
},
|
|
44
|
+
{ role: "user", content: buildNodePrompt(node, env, options) },
|
|
45
|
+
];
|
|
46
|
+
const preflight = await preflightProviderMessages(messages, {
|
|
47
|
+
provider: options.provider,
|
|
48
|
+
model,
|
|
49
|
+
contextWindow: options.contextWindow,
|
|
50
|
+
reservedOutputTokens: options.reservedOutputTokens ?? options.maxTokens ?? 4096,
|
|
51
|
+
safetyMarginTokens: options.safetyMarginTokens,
|
|
52
|
+
runId: env.OMK_RUN_ID,
|
|
53
|
+
nodeId: node.id,
|
|
54
|
+
projectRoot: env.OMK_PROJECT_ROOT,
|
|
55
|
+
});
|
|
56
|
+
if (!preflight.ok) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
exitCode: 1,
|
|
60
|
+
stdout: "",
|
|
61
|
+
stderr: contextPreflightErrorMessage(preflight.report),
|
|
62
|
+
metadata: { contextPreflight: preflight.report },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const content = await completeMessages({
|
|
66
|
+
...options,
|
|
67
|
+
model,
|
|
68
|
+
messages: preflight.messages,
|
|
69
|
+
signal,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
exitCode: 0,
|
|
74
|
+
stdout: `[${node.id}:${node.role}:${options.provider}] ${content}\n`,
|
|
75
|
+
stderr: "",
|
|
76
|
+
metadata: preflight.report.compacted ? { contextPreflight: preflight.report } : undefined,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
exitCode: 1,
|
|
83
|
+
stdout: "",
|
|
84
|
+
stderr: err instanceof Error ? err.message : String(err),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
return runner;
|
|
90
|
+
}
|
|
91
|
+
async function completeMessages(options) {
|
|
92
|
+
const credential = options.apiKey?.trim();
|
|
93
|
+
if (!credential)
|
|
94
|
+
throw new Error(`${options.provider} API key is missing`);
|
|
95
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
96
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
97
|
+
// Anthropic Messages API separates system messages from the messages array
|
|
98
|
+
const systemMessages = [];
|
|
99
|
+
const chatMessages = options.messages
|
|
100
|
+
.filter((m) => {
|
|
101
|
+
if (m.role === "system") {
|
|
102
|
+
systemMessages.push(m.content);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
})
|
|
107
|
+
.map((m) => ({ role: m.role, content: m.content }));
|
|
108
|
+
const body = {
|
|
109
|
+
model: options.model,
|
|
110
|
+
max_tokens: options.maxTokens ?? 4096,
|
|
111
|
+
messages: chatMessages,
|
|
112
|
+
};
|
|
113
|
+
if (systemMessages.length > 0) {
|
|
114
|
+
body.system = systemMessages.join("\n");
|
|
115
|
+
}
|
|
116
|
+
const response = await fetchImpl(`${baseUrl}/v1/messages`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"content-type": "application/json",
|
|
120
|
+
"x-api-key": credential,
|
|
121
|
+
"anthropic-version": "2023-06-01",
|
|
122
|
+
...safeExtraHeaders(options.headers),
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify(body),
|
|
125
|
+
signal: options.signal,
|
|
126
|
+
});
|
|
127
|
+
const text = await response.text();
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
throw new Error(`${options.provider} HTTP ${response.status}: ${sanitizeProviderErrorText(text, credential)}`);
|
|
130
|
+
}
|
|
131
|
+
const parsed = JSON.parse(text);
|
|
132
|
+
const contentBlock = parsed.content?.find((c) => c.type === "text" || typeof c.text === "string");
|
|
133
|
+
const content = contentBlock?.text;
|
|
134
|
+
if (typeof content !== "string")
|
|
135
|
+
throw new Error(`${options.provider} response missing assistant content`);
|
|
136
|
+
return content;
|
|
137
|
+
}
|
|
138
|
+
function buildNodePrompt(node, env, options) {
|
|
139
|
+
return [
|
|
140
|
+
options.promptPrefix?.trim(),
|
|
141
|
+
`DAG node: ${node.id}`,
|
|
142
|
+
`Name: ${node.name}`,
|
|
143
|
+
`Role: ${node.role}`,
|
|
144
|
+
`Task type: ${env.OMK_TASK_TYPE ?? "general"}`,
|
|
145
|
+
`Complexity: ${env.OMK_COMPLEXITY ?? "moderate"}`,
|
|
146
|
+
`Provider: ${options.provider}`,
|
|
147
|
+
`Model: ${env.OMK_PROVIDER_MODEL ?? options.model}`,
|
|
148
|
+
`Authority: ${env.OMK_PROVIDER_AUTHORITY ?? "direct"}`,
|
|
149
|
+
`Provider route reason: ${env.OMK_PROVIDER_ROUTE_REASON ?? ""}`,
|
|
150
|
+
`Routing rationale: ${node.routing?.rationale ?? ""}`,
|
|
151
|
+
renderPromptDigest("Goal context digest from authority provider", env.OMK_GOAL_CONTEXT ?? env.OMK_GOAL, {
|
|
152
|
+
maxKeywords: 18,
|
|
153
|
+
maxPhrases: 3,
|
|
154
|
+
}),
|
|
155
|
+
renderList("Skills visible to authority provider", node.routing?.skills ?? []),
|
|
156
|
+
renderList("MCP hints visible to authority provider only", node.routing?.mcpServers ?? [], { showWhenEmpty: true }),
|
|
157
|
+
renderList("Tool hints visible to authority provider only", node.routing?.tools ?? [], { showWhenEmpty: true }),
|
|
158
|
+
"Required output:",
|
|
159
|
+
"- Summary",
|
|
160
|
+
"- Evidence or file/symbol references if known",
|
|
161
|
+
"- Risks/unknowns",
|
|
162
|
+
"- Recommended authority-provider follow-up",
|
|
163
|
+
].filter((section) => Boolean(section)).join("\n").trim() || `Analyze DAG node ${node.id}.`;
|
|
164
|
+
}
|
|
165
|
+
function renderList(title, items, options = {}) {
|
|
166
|
+
if (items.length === 0)
|
|
167
|
+
return options.showWhenEmpty === true ? `${title}:\n- none` : "";
|
|
168
|
+
return `${title}:\n${items.map((item) => `- ${item}`).join("\n")}`;
|
|
169
|
+
}
|
|
170
|
+
function safeExtraHeaders(headers) {
|
|
171
|
+
const safe = {};
|
|
172
|
+
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
173
|
+
const normalized = name.trim();
|
|
174
|
+
const lower = normalized.toLowerCase();
|
|
175
|
+
const trimmedValue = value?.trim();
|
|
176
|
+
if (!normalized || !trimmedValue)
|
|
177
|
+
continue;
|
|
178
|
+
if (lower === "x-api-key" || lower === "content-type" || lower === "anthropic-version")
|
|
179
|
+
continue;
|
|
180
|
+
safe[normalized] = trimmedValue;
|
|
181
|
+
}
|
|
182
|
+
return safe;
|
|
183
|
+
}
|
|
184
|
+
function sanitizeProviderErrorText(text, credential) {
|
|
185
|
+
return text
|
|
186
|
+
.replaceAll(credential, "[redacted]")
|
|
187
|
+
.replace(/x-api-key\s+\S+/g, "x-api-key [redacted]")
|
|
188
|
+
.slice(0, 500);
|
|
189
|
+
}
|
|
190
|
+
function deny(node, reason) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
exitCode: 1,
|
|
194
|
+
stdout: "",
|
|
195
|
+
stderr: `[${node.id}:${node.role}] ${reason}`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DeepSeekConfigPathOptions } from "./deepseek/deepseek-config.js";
|
|
2
2
|
import type { ProviderAuthMethod, ProviderId, ProviderKind, ProviderModelRef, ProviderPlanKind, ProviderPolicy, ProviderProfileType, ProviderWireApi } from "./types.js";
|
|
3
|
-
export declare const KNOWN_PROVIDER_IDS: readonly ["mimo", "kimi", "deepseek", "qwen", "codex", "openrouter"];
|
|
3
|
+
export declare const KNOWN_PROVIDER_IDS: readonly ["mimo", "kimi", "deepseek", "qwen", "codex", "openrouter", "anthropic"];
|
|
4
4
|
export declare const QWEN_DASHSCOPE_COMPAT_BASE_URL = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
|
|
5
5
|
export declare const OPENROUTER_COMPAT_BASE_URL = "https://openrouter.ai/api/v1";
|
|
6
6
|
export interface ProviderRegistryEntry {
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
import { runShell } from "../util/shell.js";
|
|
2
2
|
import { readOmkProvidersConfig, writeOmkProvidersConfig, } from "./deepseek/deepseek-config.js";
|
|
3
3
|
import { DEFAULT_AUTHORITY_PROVIDER } from "./types.js";
|
|
4
|
-
export const KNOWN_PROVIDER_IDS = ["mimo", "kimi", "deepseek", "qwen", "codex", "openrouter"];
|
|
4
|
+
export const KNOWN_PROVIDER_IDS = ["mimo", "kimi", "deepseek", "qwen", "codex", "openrouter", "anthropic"];
|
|
5
5
|
export const QWEN_DASHSCOPE_COMPAT_BASE_URL = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
|
|
6
6
|
export const OPENROUTER_COMPAT_BASE_URL = "https://openrouter.ai/api/v1";
|
|
7
7
|
const DEFAULT_PROVIDER_CONFIGS = {
|
|
8
|
+
anthropic: {
|
|
9
|
+
enabled: false,
|
|
10
|
+
kind: "openai-compatible",
|
|
11
|
+
baseUrl: "https://api.anthropic.com",
|
|
12
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
13
|
+
defaultModel: "claude-fable-5",
|
|
14
|
+
aliases: {
|
|
15
|
+
default: "claude-fable-5",
|
|
16
|
+
fable: "claude-fable-5",
|
|
17
|
+
"fable-5": "claude-fable-5",
|
|
18
|
+
"claude-fable-5": "claude-fable-5",
|
|
19
|
+
"opus-4-8": "claude-opus-4-8",
|
|
20
|
+
"claude-opus-4-8": "claude-opus-4-8",
|
|
21
|
+
},
|
|
22
|
+
capabilities: ["read", "review", "qa", "research", "advisory"],
|
|
23
|
+
contextWindow: 200_000,
|
|
24
|
+
reservedOutputTokens: 8192,
|
|
25
|
+
wireApi: "anthropic-messages",
|
|
26
|
+
auth: { method: "oauth" },
|
|
27
|
+
profileType: "runtime",
|
|
28
|
+
planKind: "openai-api",
|
|
29
|
+
routing: "advisory",
|
|
30
|
+
headers: { "anthropic-version": "2023-06-01" },
|
|
31
|
+
},
|
|
8
32
|
mimo: {
|
|
9
33
|
enabled: true,
|
|
10
34
|
kind: "openai-compatible",
|
|
@@ -108,6 +132,10 @@ const DEFAULT_PROVIDER_CONFIGS = {
|
|
|
108
132
|
auto: "openrouter/auto",
|
|
109
133
|
"openrouter-default": "openrouter/auto",
|
|
110
134
|
"openrouter/auto": "openrouter/auto",
|
|
135
|
+
fable: "anthropic/claude-fable-5",
|
|
136
|
+
"fable-5": "anthropic/claude-fable-5",
|
|
137
|
+
"claude-fable-5": "anthropic/claude-fable-5",
|
|
138
|
+
"anthropic/claude-fable-5": "anthropic/claude-fable-5",
|
|
111
139
|
},
|
|
112
140
|
capabilities: ["read", "research", "review", "qa", "advisory"],
|
|
113
141
|
contextWindow: 128_000,
|
|
@@ -146,6 +174,8 @@ export function normalizeProviderId(value) {
|
|
|
146
174
|
return "qwen";
|
|
147
175
|
if (lower === "openrouter" || lower === "openrouter-ai" || lower === "claude" || lower === "anthropic")
|
|
148
176
|
return "openrouter";
|
|
177
|
+
if (lower === "anthropic-api" || lower === "anthropic-direct" || lower === "claude-api")
|
|
178
|
+
return "anthropic";
|
|
149
179
|
return lower;
|
|
150
180
|
}
|
|
151
181
|
export function parseProviderModelArg(value) {
|
|
@@ -182,7 +212,7 @@ export function parseProviderModelArg(value) {
|
|
|
182
212
|
return withThinking({ provider: "codex", model });
|
|
183
213
|
if (model.startsWith("deepseek-v4-"))
|
|
184
214
|
return withThinking({ provider: "deepseek", model });
|
|
185
|
-
if (model.startsWith("claude-") || model.startsWith("gpt-"))
|
|
215
|
+
if (model.startsWith("claude-") || model.startsWith("gpt-") || model.startsWith("anthropic/"))
|
|
186
216
|
return withThinking({ provider: "openrouter", model });
|
|
187
217
|
return withThinking({ model });
|
|
188
218
|
}
|
|
@@ -202,8 +232,12 @@ export function normalizeModelAlias(value) {
|
|
|
202
232
|
return "claude-sonnet";
|
|
203
233
|
if (lower === "opus")
|
|
204
234
|
return "claude-opus";
|
|
235
|
+
if (lower === "opus-4-8" || lower === "claude-opus-4-8")
|
|
236
|
+
return "claude-opus-4-8";
|
|
205
237
|
if (lower === "haiku")
|
|
206
238
|
return "claude-haiku";
|
|
239
|
+
if (lower === "fable" || lower === "fable-5" || lower === "claude-fable-5")
|
|
240
|
+
return "anthropic/claude-fable-5";
|
|
207
241
|
if (lower === "flash" || lower === "thinking" || lower === "non-thinking" || lower === "reasoner")
|
|
208
242
|
return "deepseek-v4-flash";
|
|
209
243
|
if (lower === "pro")
|
|
@@ -403,7 +437,7 @@ function isKnownProviderId(value) {
|
|
|
403
437
|
return KNOWN_PROVIDER_IDS.includes(value);
|
|
404
438
|
}
|
|
405
439
|
function normalizeWireApi(value) {
|
|
406
|
-
if (value === "kimi-native" || value === "openai-chat-completions" || value === "openai-responses" || value === "external-cli")
|
|
440
|
+
if (value === "kimi-native" || value === "openai-chat-completions" || value === "openai-responses" || value === "anthropic-messages" || value === "external-cli")
|
|
407
441
|
return value;
|
|
408
442
|
return undefined;
|
|
409
443
|
}
|
|
@@ -9,6 +9,7 @@ import { DEFAULT_AUTHORITY_PROVIDER } from "./types.js";
|
|
|
9
9
|
import { createDeepSeekProvider } from "./deepseek-provider.js";
|
|
10
10
|
import { createProviderRouter } from "./provider-router.js";
|
|
11
11
|
import { createOpenAICompatibleReadOnlyTaskRunner } from "./openai-compatible-runner.js";
|
|
12
|
+
import { createAnthropicMessagesReadOnlyTaskRunner } from "./anthropic-messages-runner.js";
|
|
12
13
|
import { createCodexCliAdvisoryTaskRunner } from "./codex-cli-runner.js";
|
|
13
14
|
import { providerDoctorStatus, readProviderRegistry } from "./model-registry.js";
|
|
14
15
|
import { createContextBroker } from "../runtime/context-broker.js";
|
|
@@ -70,17 +71,32 @@ export async function createProviderBackedTaskRunner(options) {
|
|
|
70
71
|
continue;
|
|
71
72
|
const apiKey = entry.apiKeyEnv ? process.env[entry.apiKeyEnv] : undefined;
|
|
72
73
|
if (apiKey && entry.baseUrl) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
if (entry.wireApi === "anthropic-messages") {
|
|
75
|
+
providerRunners[entry.id] = createAnthropicMessagesReadOnlyTaskRunner({
|
|
76
|
+
provider: entry.id,
|
|
77
|
+
baseUrl: entry.baseUrl,
|
|
78
|
+
apiKey,
|
|
79
|
+
apiKeyEnv: entry.apiKeyEnv,
|
|
80
|
+
model: entry.defaultModel,
|
|
81
|
+
promptPrefix: options.deepseekPromptPrefix,
|
|
82
|
+
headers: openAICompatibleHeaders(entry, process.env),
|
|
83
|
+
contextWindow: entry.contextWindow,
|
|
84
|
+
reservedOutputTokens: entry.reservedOutputTokens,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
providerRunners[entry.id] = createOpenAICompatibleReadOnlyTaskRunner({
|
|
89
|
+
provider: entry.id,
|
|
90
|
+
baseUrl: entry.baseUrl,
|
|
91
|
+
apiKey,
|
|
92
|
+
apiKeyEnv: entry.apiKeyEnv,
|
|
93
|
+
model: entry.defaultModel,
|
|
94
|
+
promptPrefix: options.deepseekPromptPrefix,
|
|
95
|
+
headers: openAICompatibleHeaders(entry, process.env),
|
|
96
|
+
contextWindow: entry.contextWindow,
|
|
97
|
+
reservedOutputTokens: entry.reservedOutputTokens,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
84
100
|
providerModels[entry.id] = providerModelDefault(entry);
|
|
85
101
|
}
|
|
86
102
|
else if (providerPolicy === entry.id) {
|
|
@@ -4,14 +4,17 @@ const PROVIDER_LEVELS = {
|
|
|
4
4
|
mimo: ["medium", "high", "xhigh", "max"],
|
|
5
5
|
deepseek: ["off", "high", "xhigh", "max"],
|
|
6
6
|
qwen: ["off", "medium", "high", "max"],
|
|
7
|
+
anthropic: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
7
8
|
openrouter: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
9
|
+
duckcoding: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
8
10
|
codex: ["minimal", "low", "medium", "high", "xhigh"],
|
|
9
11
|
};
|
|
10
12
|
const MODEL_LEVELS = [
|
|
11
13
|
{ pattern: /kimi|mimo/i, levels: PROVIDER_LEVELS.kimi },
|
|
12
14
|
{ pattern: /deepseek/i, levels: PROVIDER_LEVELS.deepseek },
|
|
13
15
|
{ pattern: /qwen/i, levels: PROVIDER_LEVELS.qwen },
|
|
14
|
-
{ pattern: /
|
|
16
|
+
{ pattern: /duck[-_ ]?coding/i, levels: PROVIDER_LEVELS.duckcoding },
|
|
17
|
+
{ pattern: /fable|claude|sonnet|opus|haiku|anthropic|gpt|openrouter/i, levels: PROVIDER_LEVELS.anthropic },
|
|
15
18
|
{ pattern: /codex/i, levels: PROVIDER_LEVELS.codex },
|
|
16
19
|
];
|
|
17
20
|
export function thinkingLevelsFor(provider, model) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TaskResult, TaskRunner } from "../contracts/orchestration.js";
|
|
2
|
-
export type KnownProviderId = "mimo" | "kimi" | "deepseek" | "codex" | "qwen" | "openrouter";
|
|
2
|
+
export type KnownProviderId = "mimo" | "kimi" | "deepseek" | "codex" | "qwen" | "openrouter" | "anthropic";
|
|
3
3
|
export type ProviderId = KnownProviderId | (string & {});
|
|
4
4
|
export type ProviderPolicy = "auto" | ProviderId;
|
|
5
5
|
export declare const DEFAULT_AUTHORITY_PROVIDER: ProviderId;
|
|
@@ -12,7 +12,7 @@ export declare function resolveRuntimeFallbackChain(availableRuntimes?: readonly
|
|
|
12
12
|
export type ProviderRisk = "read" | "write" | "shell" | "merge";
|
|
13
13
|
export type ProviderComplexity = "simple" | "moderate" | "complex";
|
|
14
14
|
export type ProviderKind = "kimi-native" | "openai-compatible" | "external-cli" | "codex-cli" | "local";
|
|
15
|
-
export type ProviderWireApi = "kimi-native" | "openai-chat-completions" | "openai-responses" | "external-cli";
|
|
15
|
+
export type ProviderWireApi = "kimi-native" | "openai-chat-completions" | "openai-responses" | "anthropic-messages" | "external-cli";
|
|
16
16
|
export type ProviderAuthMethod = "api-key-env" | "oauth" | "external-cli" | "none";
|
|
17
17
|
export type ProviderProfileType = "runtime" | "compatibility";
|
|
18
18
|
export type ProviderPlanKind = "runtime" | "openai-api" | "chatgpt-plan" | "claude-code-plan" | "gemini-cli-plan" | "qwen-coding-plan" | "openrouter-credits" | "openrouter-byok";
|
|
@@ -85,7 +85,7 @@ export interface ProviderRouteDecision {
|
|
|
85
85
|
/** Decision trace entries for forensic replay */
|
|
86
86
|
trace?: import("../contracts/replay.js").DecisionTraceEntry[];
|
|
87
87
|
}
|
|
88
|
-
export type ProviderFailureKind = "availability" | "transient" | "policy" | "quota" | "unknown";
|
|
88
|
+
export type ProviderFailureKind = "availability" | "transient" | "policy" | "quota" | "refusal" | "unknown";
|
|
89
89
|
export interface ProviderAvailability {
|
|
90
90
|
provider: ProviderId;
|
|
91
91
|
available: boolean;
|
|
@@ -19,7 +19,15 @@ export declare const BETA_PRIOR_ALPHA0 = 1;
|
|
|
19
19
|
export declare const BETA_PRIOR_BETA0 = 1;
|
|
20
20
|
/** Default public surface budget K (max items). */
|
|
21
21
|
export declare const SURFACE_BUDGET_K = 8;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Algorithm 8 release gate weights — raw (unnormalized) historical values.
|
|
24
|
+
*
|
|
25
|
+
* @deprecated Source of truth is the omk.weights.v1 contract
|
|
26
|
+
* (schemas/omk.weights.v1.json, mirrored by DEFAULT_WEIGHTS in
|
|
27
|
+
* src/runtime/weights-config.ts). The release promotion gate now consumes
|
|
28
|
+
* normalized effective weights via releaseGateEffective(); these raw values
|
|
29
|
+
* (positive Σ = 1.05) are kept only for backward compatibility.
|
|
30
|
+
*/
|
|
23
31
|
export declare const RELEASE_GATE_WEIGHTS: {
|
|
24
32
|
readonly ci: 0.15;
|
|
25
33
|
readonly build: 0.1;
|
|
@@ -21,7 +21,15 @@ export const BETA_PRIOR_BETA0 = 1;
|
|
|
21
21
|
/** Default public surface budget K (max items). */
|
|
22
22
|
export const SURFACE_BUDGET_K = 8;
|
|
23
23
|
// ── Phase 5 Release Promotion Gate ──────────────────────────────
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Algorithm 8 release gate weights — raw (unnormalized) historical values.
|
|
26
|
+
*
|
|
27
|
+
* @deprecated Source of truth is the omk.weights.v1 contract
|
|
28
|
+
* (schemas/omk.weights.v1.json, mirrored by DEFAULT_WEIGHTS in
|
|
29
|
+
* src/runtime/weights-config.ts). The release promotion gate now consumes
|
|
30
|
+
* normalized effective weights via releaseGateEffective(); these raw values
|
|
31
|
+
* (positive Σ = 1.05) are kept only for backward compatibility.
|
|
32
|
+
*/
|
|
25
33
|
export const RELEASE_GATE_WEIGHTS = {
|
|
26
34
|
ci: 0.15,
|
|
27
35
|
build: 0.10,
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Router V2 Scoring Engine — Bayesian-smoothed evidence calibration (Algorithm 6).
|
|
3
3
|
*
|
|
4
|
-
* Composite formula:
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
4
|
+
* Composite formula (omk.weights.v1 routerV2Composite vector, normalize:true):
|
|
5
|
+
* ŵ_E*E + ŵ_conf*conf + ŵ_cap*cap + ŵ_mat*mat + ŵ_lat*lat + ŵ_cost*cost
|
|
6
|
+
* - p̂_fail*pen - p̂_blast*blast
|
|
7
|
+
*
|
|
8
|
+
* Historical raw weights (0.25/0.15/0.20/0.15/0.10/0.10 − 0.15/0.10) had a
|
|
9
|
+
* positive Σ = 0.95; normalization divides weights AND penalties by the same
|
|
10
|
+
* factor (1/0.95), a pure uniform scaling. The composite is ranking-only
|
|
11
|
+
* (no absolute threshold compares it), so rankings are identical.
|
|
7
12
|
*/
|
|
8
13
|
import type { AgentRuntime } from "./agent-runtime.js";
|
|
9
14
|
import type { EvidenceHistoryEntry, NodeIntent, RuntimeScoreV2, RouterV2Options, RouterV2ScoringEngine, BlastRadiusParams } from "./contracts/router-v2.js";
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Router V2 Scoring Engine — Bayesian-smoothed evidence calibration (Algorithm 6).
|
|
3
3
|
*
|
|
4
|
-
* Composite formula:
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
4
|
+
* Composite formula (omk.weights.v1 routerV2Composite vector, normalize:true):
|
|
5
|
+
* ŵ_E*E + ŵ_conf*conf + ŵ_cap*cap + ŵ_mat*mat + ŵ_lat*lat + ŵ_cost*cost
|
|
6
|
+
* - p̂_fail*pen - p̂_blast*blast
|
|
7
|
+
*
|
|
8
|
+
* Historical raw weights (0.25/0.15/0.20/0.15/0.10/0.10 − 0.15/0.10) had a
|
|
9
|
+
* positive Σ = 0.95; normalization divides weights AND penalties by the same
|
|
10
|
+
* factor (1/0.95), a pure uniform scaling. The composite is ranking-only
|
|
11
|
+
* (no absolute threshold compares it), so rankings are identical.
|
|
7
12
|
*/
|
|
8
13
|
import { computeBlastRadiusPenalty } from "./blast-radius.js";
|
|
14
|
+
import { intentCapabilityWeights, routerV2CompositeEffective } from "./weights-config.js";
|
|
9
15
|
const ALPHA_0 = 1;
|
|
10
16
|
const BETA_0 = 1;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
documentation: [["read", 0.25], ["write", 0.15], ["review", 0.15], ["toolCalling", 0.1]],
|
|
20
|
-
"shell-operation": [["shell", 0.4], ["read", 0.15], ["write", 0.1]],
|
|
21
|
-
};
|
|
17
|
+
/**
|
|
18
|
+
* Intent → capability emphasis vectors, sourced verbatim from the
|
|
19
|
+
* omk.weights.v1 intentCapability vector family (normalize:false — sub-unit
|
|
20
|
+
* sums are intentional feature-fit emphasis templates).
|
|
21
|
+
*/
|
|
22
|
+
const INTENT_CAPABILITY_WEIGHTS = intentCapabilityWeights();
|
|
23
|
+
/** Effective (normalized) composite weights/penalties — raw values ÷ 0.95. */
|
|
24
|
+
const COMPOSITE_EFFECTIVE = routerV2CompositeEffective();
|
|
22
25
|
function runtimeCapabilityEnabled(capabilities, capability) {
|
|
23
26
|
if (capability === "toolCalling") {
|
|
24
27
|
return capabilities.toolCalling === true || capabilities.supportsToolCalling === true;
|
|
@@ -93,14 +96,16 @@ export function createRouterV2ScoringEngine(options = {}, blastRadiusFn = comput
|
|
|
93
96
|
const latencyScore = computeLatencyScore(runtime);
|
|
94
97
|
const costScore = computeCostScore(runtime);
|
|
95
98
|
const blastRadiusPenalty = enableBlastRadius ? blastRadiusFn(blastRadiusParams) : 0;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
const cw = COMPOSITE_EFFECTIVE.weights;
|
|
100
|
+
const cp = COMPOSITE_EFFECTIVE.penalties;
|
|
101
|
+
const composite = cw.bayesianEvidence * bayesianEvidenceScore +
|
|
102
|
+
cw.confidence * confidence +
|
|
103
|
+
cw.capabilityFit * capabilityFit +
|
|
104
|
+
cw.maturity * maturityScore +
|
|
105
|
+
cw.latency * latencyScore +
|
|
106
|
+
cw.cost * costScore -
|
|
107
|
+
cp.recentFailure * recentFailurePenalty -
|
|
108
|
+
cp.blastRadius * blastRadiusPenalty;
|
|
104
109
|
return {
|
|
105
110
|
runtimeId: runtime.id,
|
|
106
111
|
bayesianEvidenceScore,
|
|
@@ -48,4 +48,9 @@ export declare function createRuntimeRouter(options?: RuntimeRouterOptions): {
|
|
|
48
48
|
setRuntimes(nextRuntimes: AgentRuntime[]): void;
|
|
49
49
|
invalidateCache: () => void;
|
|
50
50
|
};
|
|
51
|
+
/** Test-only: deterministic capability-ordered sort using the precomputed score cache.
|
|
52
|
+
* Mirrors the production comparator so tests can assert identical ordering to the
|
|
53
|
+
* pre-change recompute-in-comparator reference. */
|
|
54
|
+
export declare function sortRuntimesByCapabilityScore(runtimes: readonly AgentRuntime[], intent: NodeIntent): AgentRuntime[];
|
|
55
|
+
export declare function computeRuntimeCapabilityScore(runtime: AgentRuntime, intent: NodeIntent): number;
|
|
51
56
|
export {};
|
|
@@ -137,7 +137,8 @@ export function createRuntimeRouter(options = {}) {
|
|
|
137
137
|
score: scores[i],
|
|
138
138
|
composite: computeComposite(scores[i], r, intent),
|
|
139
139
|
}));
|
|
140
|
-
|
|
140
|
+
const capabilityScoreCache = buildCapabilityScoreCache(supporting, intent);
|
|
141
|
+
scored.sort((a, b) => compareScoredRuntimes(a, b, intent, capabilityScoreCache));
|
|
141
142
|
const primary = scored[0].runtime;
|
|
142
143
|
const fallbacks = scored.slice(1).map((s) => s.runtime);
|
|
143
144
|
const bestScore = scored[0].score;
|
|
@@ -161,7 +162,10 @@ export function createRuntimeRouter(options = {}) {
|
|
|
161
162
|
runtime: r,
|
|
162
163
|
composite: computeRuntimeCapabilityScore(r, intent),
|
|
163
164
|
}));
|
|
164
|
-
|
|
165
|
+
// Reuse the capability scores already computed above instead of recomputing
|
|
166
|
+
// them inside the comparator on every comparison.
|
|
167
|
+
const capabilityScoreCache = new Map(scored.map((s) => [s.runtime, s.composite]));
|
|
168
|
+
scored.sort((a, b) => compareRuntimeCandidates(a.runtime, b.runtime, intent, capabilityScoreCache));
|
|
165
169
|
const primary = scored[0].runtime;
|
|
166
170
|
const fallbacks = scored.slice(1).map((s) => s.runtime);
|
|
167
171
|
return {
|
|
@@ -337,6 +341,7 @@ export function createRuntimeRouter(options = {}) {
|
|
|
337
341
|
throw new UnsupportedRuntimeError(capsule, detectedRuntimeLabels(runtimes));
|
|
338
342
|
}
|
|
339
343
|
const preferredRuntimeIds = task.providerPolicy?.fallbackChain ?? options.fallbackChain ?? [];
|
|
344
|
+
const capabilityScoreCache = buildCapabilityScoreCache(candidates, intent);
|
|
340
345
|
candidates.sort((a, b) => {
|
|
341
346
|
const runtimeDelta = runtimePreferenceIndex(a.id, preferredRuntimeIds)
|
|
342
347
|
- runtimePreferenceIndex(b.id, preferredRuntimeIds);
|
|
@@ -348,7 +353,7 @@ export function createRuntimeRouter(options = {}) {
|
|
|
348
353
|
if (providerDelta !== 0)
|
|
349
354
|
return providerDelta;
|
|
350
355
|
}
|
|
351
|
-
return compareRuntimeCandidates(a, b, intent);
|
|
356
|
+
return compareRuntimeCandidates(a, b, intent, capabilityScoreCache);
|
|
352
357
|
});
|
|
353
358
|
let lastError;
|
|
354
359
|
for (const runtime of candidates) {
|
|
@@ -574,14 +579,14 @@ function computeComposite(score, runtime, intent) {
|
|
|
574
579
|
0.15 * computeRuntimeCapabilityScore(runtime, intent) +
|
|
575
580
|
0.05 * runtimePriorityScore(runtime));
|
|
576
581
|
}
|
|
577
|
-
function compareScoredRuntimes(a, b, intent) {
|
|
582
|
+
function compareScoredRuntimes(a, b, intent, scoreCache) {
|
|
578
583
|
const compositeDelta = b.composite - a.composite;
|
|
579
584
|
if (compositeDelta !== 0)
|
|
580
585
|
return compositeDelta;
|
|
581
|
-
return compareRuntimeCandidates(a.runtime, b.runtime, intent);
|
|
586
|
+
return compareRuntimeCandidates(a.runtime, b.runtime, intent, scoreCache);
|
|
582
587
|
}
|
|
583
|
-
function compareRuntimeCandidates(a, b, intent) {
|
|
584
|
-
const capabilityDelta =
|
|
588
|
+
function compareRuntimeCandidates(a, b, intent, scoreCache) {
|
|
589
|
+
const capabilityDelta = capabilityScoreFromCache(scoreCache, b, intent) - capabilityScoreFromCache(scoreCache, a, intent);
|
|
585
590
|
if (capabilityDelta !== 0)
|
|
586
591
|
return capabilityDelta;
|
|
587
592
|
const priorityDelta = b.priority - a.priority;
|
|
@@ -589,7 +594,34 @@ function compareRuntimeCandidates(a, b, intent) {
|
|
|
589
594
|
return priorityDelta;
|
|
590
595
|
return a.id.localeCompare(b.id);
|
|
591
596
|
}
|
|
592
|
-
|
|
597
|
+
// Precompute each candidate's capability score ONCE before sorting. Without this the
|
|
598
|
+
// comparator recomputed `computeRuntimeCapabilityScore` for both operands on every
|
|
599
|
+
// comparison, making the sort O(r log r * c) instead of O(r log r). Keyed by runtime
|
|
600
|
+
// object reference so identical ids with distinct objects can never alias.
|
|
601
|
+
function buildCapabilityScoreCache(runtimes, intent) {
|
|
602
|
+
const cache = new Map();
|
|
603
|
+
for (const runtime of runtimes) {
|
|
604
|
+
if (!cache.has(runtime))
|
|
605
|
+
cache.set(runtime, computeRuntimeCapabilityScore(runtime, intent));
|
|
606
|
+
}
|
|
607
|
+
return cache;
|
|
608
|
+
}
|
|
609
|
+
function capabilityScoreFromCache(cache, runtime, intent) {
|
|
610
|
+
const cached = cache.get(runtime);
|
|
611
|
+
if (cached !== undefined)
|
|
612
|
+
return cached;
|
|
613
|
+
const computed = computeRuntimeCapabilityScore(runtime, intent);
|
|
614
|
+
cache.set(runtime, computed);
|
|
615
|
+
return computed;
|
|
616
|
+
}
|
|
617
|
+
/** Test-only: deterministic capability-ordered sort using the precomputed score cache.
|
|
618
|
+
* Mirrors the production comparator so tests can assert identical ordering to the
|
|
619
|
+
* pre-change recompute-in-comparator reference. */
|
|
620
|
+
export function sortRuntimesByCapabilityScore(runtimes, intent) {
|
|
621
|
+
const cache = buildCapabilityScoreCache(runtimes, intent);
|
|
622
|
+
return [...runtimes].sort((a, b) => compareRuntimeCandidates(a, b, intent, cache));
|
|
623
|
+
}
|
|
624
|
+
export function computeRuntimeCapabilityScore(runtime, intent) {
|
|
593
625
|
const capabilities = runtime.capabilities;
|
|
594
626
|
if (capabilities == null)
|
|
595
627
|
return 0;
|
|
@@ -23,3 +23,32 @@ export interface CreateRuntimeSandboxProfileOptions {
|
|
|
23
23
|
readonly notes?: readonly string[];
|
|
24
24
|
}
|
|
25
25
|
export declare function createRuntimeSandboxProfile(options: CreateRuntimeSandboxProfileOptions): RuntimeSandboxProfile;
|
|
26
|
+
/**
|
|
27
|
+
* Error thrown by {@link assertWritable} when a path is outside every writable
|
|
28
|
+
* root. Carries only the resolved target and roots (no secret values).
|
|
29
|
+
*/
|
|
30
|
+
export declare class SandboxWriteDeniedError extends Error {
|
|
31
|
+
readonly target: string;
|
|
32
|
+
readonly roots: readonly string[];
|
|
33
|
+
constructor(target: string, roots: readonly string[]);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Return true when `p` is writable under `roots`.
|
|
37
|
+
*
|
|
38
|
+
* Rules:
|
|
39
|
+
* - Empty/undefined `roots` => unrestricted (returns true). This preserves the
|
|
40
|
+
* current "metadata only" behavior; enforcement activates only when a
|
|
41
|
+
* sandbox profile explicitly sets non-empty `writableRoots`.
|
|
42
|
+
* - Both target and roots are resolved via {@link resolveRealPathBestEffort},
|
|
43
|
+
* which (a) collapses `.`/`..` segments and (b) realpath-resolves the deepest
|
|
44
|
+
* existing ancestor so a SYMLINK whose real target escapes every root is
|
|
45
|
+
* DENIED — even when the final write target does not exist yet.
|
|
46
|
+
* - Prefix matching is segment-boundary safe: target must equal a root or sit
|
|
47
|
+
* under `root + path.sep`, so root `/a/b` does NOT match sibling `/a/bc`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function isPathWritable(p: string, roots: readonly string[] | undefined): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Throw {@link SandboxWriteDeniedError} when `p` is not writable under `roots`.
|
|
52
|
+
* No-op when `roots` is empty/undefined (safe default => unrestricted).
|
|
53
|
+
*/
|
|
54
|
+
export declare function assertWritable(p: string, roots: readonly string[] | undefined): void;
|