oh-my-knowledge 0.52.3 → 0.53.0

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 (88) hide show
  1. package/README.md +13 -1
  2. package/README.zh.md +13 -1
  3. package/dist/assets/agent-skills/omk/SKILL.md +4 -0
  4. package/dist/assets/agent-skills/omk/references/commands.md +1 -1
  5. package/dist/authoring/evolver.js +1 -1
  6. package/dist/authoring/generator.js +1 -1
  7. package/dist/cli/commands/eval/index.js +7 -6
  8. package/dist/cli/commands/install.js +5 -1
  9. package/dist/cli/lib/generation-failure-hint.js +6 -8
  10. package/dist/cli/lib/runtime-defaults.d.ts +2 -0
  11. package/dist/cli/lib/runtime-defaults.js +7 -4
  12. package/dist/dsh-plugin/cordis.patch.yml +3 -0
  13. package/dist/dsh-plugin/host-executor.d.ts +93 -0
  14. package/dist/dsh-plugin/host-executor.js +232 -0
  15. package/dist/dsh-plugin/index.d.ts +28 -0
  16. package/dist/dsh-plugin/index.js +156 -0
  17. package/dist/dsh-plugin/protocol.d.ts +21 -0
  18. package/dist/dsh-plugin/protocol.js +229 -0
  19. package/dist/eval-core/comparability.js +3 -0
  20. package/dist/eval-core/evaluation-execution.js +5 -13
  21. package/dist/eval-core/evaluation-reporting.d.ts +7 -4
  22. package/dist/eval-core/evaluation-reporting.js +39 -18
  23. package/dist/eval-core/judge-independence.d.ts +1 -1
  24. package/dist/eval-core/judge-independence.js +1 -1
  25. package/dist/eval-core/report-document.js +6 -0
  26. package/dist/eval-core/resume-compatibility.d.ts +1 -0
  27. package/dist/eval-core/resume-compatibility.js +5 -4
  28. package/dist/eval-workflows/batch-evaluation-workflow.js +1 -1
  29. package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +4 -2
  30. package/dist/eval-workflows/evaluation-pipeline.d.ts +3 -1
  31. package/dist/eval-workflows/evaluation-pipeline.js +7 -3
  32. package/dist/eval-workflows/run-evaluation.d.ts +4 -2
  33. package/dist/eval-workflows/run-evaluation.js +7 -3
  34. package/dist/executors/{anthropic-api.d.ts → anthropic/api.d.ts} +1 -1
  35. package/dist/executors/{anthropic-api.js → anthropic/api.js} +4 -2
  36. package/dist/executors/{claude-cli.d.ts → anthropic/claude/cli.d.ts} +1 -1
  37. package/dist/executors/{claude-cli.js → anthropic/claude/cli.js} +5 -3
  38. package/dist/executors/anthropic/claude/protocol.d.ts +87 -0
  39. package/dist/executors/{claude-protocol.js → anthropic/claude/protocol.js} +6 -6
  40. package/dist/executors/{claude-sdk.d.ts → anthropic/claude/sdk.d.ts} +2 -2
  41. package/dist/executors/{claude-sdk.js → anthropic/claude/sdk.js} +8 -5
  42. package/dist/executors/anthropic/claude/trace.d.ts +9 -0
  43. package/dist/executors/{claude-sdk-trace.js → anthropic/claude/trace.js} +5 -5
  44. package/dist/executors/{capabilities.d.ts → core/capabilities.d.ts} +3 -5
  45. package/dist/executors/{capabilities.js → core/capabilities.js} +4 -11
  46. package/dist/executors/core/http.d.ts +6 -0
  47. package/dist/executors/core/http.js +19 -0
  48. package/dist/executors/core/limits.d.ts +2 -0
  49. package/dist/executors/core/limits.js +2 -0
  50. package/dist/executors/core/optional-dependencies.d.ts +7 -0
  51. package/dist/executors/core/optional-dependencies.js +35 -0
  52. package/dist/executors/core/registry.d.ts +145 -0
  53. package/dist/executors/core/registry.js +127 -0
  54. package/dist/executors/core/runtime-fingerprint.d.ts +13 -0
  55. package/dist/executors/{runtime-fingerprint.js → core/runtime-fingerprint.js} +119 -59
  56. package/dist/executors/core/runtime.d.ts +12 -0
  57. package/dist/executors/core/runtime.js +61 -0
  58. package/dist/executors/core/subprocess.d.ts +44 -0
  59. package/dist/executors/{shared.js → core/subprocess.js} +20 -156
  60. package/dist/executors/index.d.ts +4 -4
  61. package/dist/executors/index.js +26 -15
  62. package/dist/executors/{openai-api.d.ts → openai/api.d.ts} +1 -1
  63. package/dist/executors/{openai-api.js → openai/api.js} +4 -2
  64. package/dist/executors/{codex-cli.d.ts → openai/codex/cli.d.ts} +3 -3
  65. package/dist/executors/{codex-cli.js → openai/codex/cli.js} +5 -3
  66. package/dist/executors/openai/codex/protocol.d.ts +72 -0
  67. package/dist/executors/{codex-protocol.js → openai/codex/protocol.js} +33 -2
  68. package/dist/executors/{codex-sdk.d.ts → openai/codex/sdk.d.ts} +18 -4
  69. package/dist/executors/{codex-sdk.js → openai/codex/sdk.js} +7 -4
  70. package/dist/executors/{codex-cli-trace.d.ts → openai/codex/trace.d.ts} +2 -2
  71. package/dist/executors/{codex-cli-trace.js → openai/codex/trace.js} +4 -4
  72. package/dist/executors/{script.d.ts → script/index.d.ts} +1 -1
  73. package/dist/executors/{script.js → script/index.js} +6 -4
  74. package/dist/grading/judge.d.ts +1 -1
  75. package/dist/grading/judge.js +1 -1
  76. package/dist/renderer/html-renderer.js +30 -11
  77. package/dist/types/executor.d.ts +13 -2
  78. package/dist/types/judge.d.ts +2 -2
  79. package/package.json +21 -5
  80. package/dist/executors/claude-protocol.d.ts +0 -28
  81. package/dist/executors/claude-sdk-trace.d.ts +0 -9
  82. package/dist/executors/codex-protocol.d.ts +0 -24
  83. package/dist/executors/gemini.d.ts +0 -2
  84. package/dist/executors/gemini.js +0 -156
  85. package/dist/executors/runtime-fingerprint.d.ts +0 -6
  86. package/dist/executors/shared.d.ts +0 -226
  87. /package/dist/executors/{script-command.d.ts → script/command.d.ts} +0 -0
  88. /package/dist/executors/{script-command.js → script/command.js} +0 -0
@@ -0,0 +1,156 @@
1
+ import { dirname, join, resolve } from 'node:path';
2
+ import { computeVerdict } from '../eval-core/verdict.js';
3
+ import { configVariantsToSpecs, loadEvalConfig } from '../inputs/eval-config.js';
4
+ import { runEvaluation, runMultiple } from '../eval-workflows/run-evaluation.js';
5
+ import { createDshHostExecutor, } from './host-executor.js';
6
+ export const name = 'omk-dsh-plugin';
7
+ export const inject = ['agentPresets', 'agents', 'commands', 'tools'];
8
+ const USAGE = '用法:/omk eval <eval.yaml>';
9
+ function commandPath(rawInput) {
10
+ const match = /^\s*eval\s+(.+?)\s*$/u.exec(rawInput);
11
+ if (!match)
12
+ return undefined;
13
+ const value = match[1]?.trim();
14
+ if (!value)
15
+ return undefined;
16
+ if ((value.startsWith('"') && value.endsWith('"'))
17
+ || (value.startsWith("'") && value.endsWith("'"))) {
18
+ return value.slice(1, -1);
19
+ }
20
+ return value;
21
+ }
22
+ function projectRoot(configPath) {
23
+ return dirname(configPath);
24
+ }
25
+ function modelFor(agent, configured) {
26
+ const model = configured?.trim() || agent.options.model?.trim();
27
+ if (!model) {
28
+ throw new Error('当前 DSH session 没有可继承的模型,且 eval.yaml 未配置 model。');
29
+ }
30
+ return model;
31
+ }
32
+ function normalizeJudgeModels(configured, model) {
33
+ if (!configured || configured.length === 0)
34
+ return [{ executor: 'dsh-host', model }];
35
+ return configured.map((judge) => {
36
+ if (judge.executor === 'dsh-host') {
37
+ throw new Error('dsh-host 是 OMK 内部执行器标识;评委要复用当前 DSH 时,请使用 executor: dsh 或省略 judgeModels。');
38
+ }
39
+ return {
40
+ ...judge,
41
+ executor: judge.executor === 'dsh' ? 'dsh-host' : judge.executor,
42
+ };
43
+ });
44
+ }
45
+ async function executeEvalCommand(ctx, invocation, requestedPath) {
46
+ const cwd = invocation.agent.session.header.cwd ?? process.cwd();
47
+ const configPath = resolve(cwd, requestedPath);
48
+ const config = loadEvalConfig(configPath);
49
+ if (config.executor) {
50
+ return {
51
+ kind: 'error',
52
+ text: 'DSH 内运行 OMK 时,被测执行器固定为当前 DSH;请删除 eval.yaml 中的顶层 executor。',
53
+ };
54
+ }
55
+ if (config.effort) {
56
+ return {
57
+ kind: 'error',
58
+ text: 'DSH 的 reasoning effort 是 provider-owned 枚举,当前无法与 OMK 五档无损映射;请删除 eval.yaml 中的 effort,并在 DSH profile 中固定模型推理配置。',
59
+ };
60
+ }
61
+ const model = modelFor(invocation.agent, config.model);
62
+ const executor = createDshHostExecutor(ctx, {
63
+ parentAgent: invocation.agent,
64
+ signal: invocation.signal,
65
+ });
66
+ const judgeModels = normalizeJudgeModels(config.judgeModels, model);
67
+ const currentSessionProvesConnectivity = config.model === undefined
68
+ && ((config.noJudge ?? false) || judgeModels.every((judge) => (judge.executor === 'dsh-host' && judge.model === model)));
69
+ const root = projectRoot(configPath);
70
+ const outputDir = join(root, '.omk', 'reports');
71
+ const options = {
72
+ samplesPath: config.samples,
73
+ skillDir: join(root, 'skills'),
74
+ variantSpecs: configVariantsToSpecs(config.variants),
75
+ model,
76
+ outputDir,
77
+ noJudge: config.noJudge ?? false,
78
+ concurrency: config.concurrency ?? 1,
79
+ timeoutMs: config.timeoutMs,
80
+ noCache: config.noCache ?? false,
81
+ executorName: 'dsh-host',
82
+ executorOverrides: { 'dsh-host': executor },
83
+ judgeModels,
84
+ skipConnectivity: currentSessionProvesConnectivity,
85
+ skipDoctor: config.skipDoctor ?? false,
86
+ lang: 'zh',
87
+ mcpConfig: config.mcpConfig,
88
+ bootstrap: config.bootstrap ?? true,
89
+ bootstrapSamples: config.bootstrapSamples,
90
+ holdoutRatio: config.holdoutRatio,
91
+ judgeRepeat: config.judgeRepeat,
92
+ lengthDebias: config.lengthDebias,
93
+ budget: config.budget,
94
+ strictBaseline: config.strictBaseline,
95
+ noDiagnostic: config.noDiagnostic,
96
+ };
97
+ const result = config.repeat && config.repeat > 1
98
+ ? await runMultiple({ ...options, repeat: config.repeat })
99
+ : await runEvaluation(options);
100
+ const report = result.report;
101
+ const goldMessages = [];
102
+ if (config.goldDir) {
103
+ const { attachGoldAgreementToReport } = await import('../grading/gold-cli.js');
104
+ const gold = attachGoldAgreementToReport({
105
+ report,
106
+ goldDir: config.goldDir,
107
+ outputDir,
108
+ samples: config.bootstrapSamples,
109
+ });
110
+ if (gold.result && gold.gold) {
111
+ const alpha = Number.isFinite(gold.result.agreement.alpha)
112
+ ? gold.result.agreement.alpha.toFixed(3)
113
+ : '不可计算';
114
+ goldMessages.push(`Gold 一致性:Krippendorff α=${alpha},N=${gold.result.agreement.sampleCount},标注者=${gold.gold.metadata.annotator}`);
115
+ if (gold.result.contaminationWarning) {
116
+ goldMessages.push(`⚠ Gold 污染提示:${gold.result.contaminationWarning}`);
117
+ }
118
+ }
119
+ else {
120
+ goldMessages.push(`⚠ Gold 数据未加载:${gold.loadIssues.join(';') || config.goldDir}`);
121
+ }
122
+ }
123
+ const verdict = computeVerdict(report);
124
+ return {
125
+ kind: 'success',
126
+ text: [
127
+ `OMK 评测完成:${verdict.level}`,
128
+ verdict.headline,
129
+ `报告:${report.id}`,
130
+ ...(result.filePath ? [`文件:${result.filePath}`] : []),
131
+ ...goldMessages,
132
+ ].join('\n'),
133
+ };
134
+ }
135
+ /** Register `/omk eval <eval.yaml>` in every DSH command-capable surface. */
136
+ export function apply(ctx) {
137
+ ctx.commands.register({
138
+ name: 'omk',
139
+ description: '在当前 DeepSeek Harness 中运行 OMK 对照评测',
140
+ input: { hint: 'eval <eval.yaml>' },
141
+ async handler(invocation) {
142
+ const path = commandPath(invocation.rawInput);
143
+ if (!path)
144
+ return { kind: 'error', text: USAGE };
145
+ try {
146
+ return await executeEvalCommand(ctx, invocation, path);
147
+ }
148
+ catch (error) {
149
+ return {
150
+ kind: 'error',
151
+ text: error instanceof Error ? error.message : String(error),
152
+ };
153
+ }
154
+ },
155
+ });
156
+ }
@@ -0,0 +1,21 @@
1
+ import type { ExecResult } from '../types/index.js';
2
+ type UnknownRecord = Record<string, unknown>;
3
+ /** Host-owned DSH events consumed by OMK without importing DSH runtime types. */
4
+ export interface DshHostRunResult {
5
+ rootSessionId: string;
6
+ finalResponse: string;
7
+ /** Root and descendant events in host-observed receive order. */
8
+ events: Array<{
9
+ sessionId: string;
10
+ event: UnknownRecord;
11
+ traceRole: 'main' | 'subagent';
12
+ }>;
13
+ childSessionIds: string[];
14
+ }
15
+ /**
16
+ * Map DSH's append-only session log into OMK's source-neutral executor result.
17
+ * Root and descendant token/tool evidence are included; final output remains
18
+ * the root session's last assistant text.
19
+ */
20
+ export declare function buildDshHostResult(result: DshHostRunResult, wallClockDurationMs: number): ExecResult;
21
+ export {};
@@ -0,0 +1,229 @@
1
+ import { checkedSumTokenCounts, optionalTokenCount } from '../shared/token-usage.js';
2
+ import { normalizeToolIdentity } from '../shared/tool-identity.js';
3
+ import { safeSliceForJson } from '../util/safe-slice.js';
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ function nonEmptyString(value) {
8
+ return typeof value === 'string' && value.trim() ? value : undefined;
9
+ }
10
+ function finiteInteger(value) {
11
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0
12
+ ? value
13
+ : undefined;
14
+ }
15
+ function eventTimestamp(event) {
16
+ const time = finiteInteger(event.time);
17
+ if (time === undefined)
18
+ return undefined;
19
+ try {
20
+ return new Date(time).toISOString();
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ function contentBlocks(value) {
27
+ return Array.isArray(value) ? value.filter(isRecord) : [];
28
+ }
29
+ function textFromBlocks(value) {
30
+ if (typeof value === 'string')
31
+ return value;
32
+ return contentBlocks(value)
33
+ .flatMap((block) => block.type === 'text' && typeof block.text === 'string'
34
+ ? [block.text]
35
+ : [])
36
+ .join('');
37
+ }
38
+ function serializableToolInput(value) {
39
+ if (typeof value !== 'string')
40
+ return value ?? null;
41
+ try {
42
+ return JSON.parse(value);
43
+ }
44
+ catch {
45
+ return value;
46
+ }
47
+ }
48
+ function boundedToolOutput(value) {
49
+ if (typeof value === 'string')
50
+ return safeSliceForJson(value, 50_000);
51
+ return value ?? null;
52
+ }
53
+ function resultBlock(data) {
54
+ const message = isRecord(data.message) ? data.message : undefined;
55
+ return contentBlocks(message?.content).find((block) => block.type === 'tool-result');
56
+ }
57
+ function collectEvents(result) {
58
+ return result.events.map(({ sessionId, event, traceRole }) => ({
59
+ sessionId,
60
+ event,
61
+ traceRole,
62
+ }));
63
+ }
64
+ function terminalReason(events, rootSessionId) {
65
+ const terminal = [...events].reverse().find(({ sessionId, event }) => (sessionId === rootSessionId && event.type === 'turn/end'));
66
+ if (!terminal || !isRecord(terminal.event.data)) {
67
+ return { stopReason: 'unknown', error: 'dsh runtime reached idle without a root turn/end event' };
68
+ }
69
+ const reason = isRecord(terminal.event.data.reason) ? terminal.event.data.reason : undefined;
70
+ const stopReason = nonEmptyString(reason?.kind) ?? 'unknown';
71
+ if (stopReason === 'error') {
72
+ const failure = isRecord(reason?.error) ? reason.error : undefined;
73
+ return {
74
+ stopReason,
75
+ error: nonEmptyString(failure?.message) ?? 'dsh turn failed',
76
+ };
77
+ }
78
+ if (['aborted', 'blocked', 'interrupted'].includes(stopReason)) {
79
+ return { stopReason, error: `dsh turn ended with ${stopReason}` };
80
+ }
81
+ if (stopReason === 'max-tokens') {
82
+ return { stopReason, error: 'dsh turn ended after reaching the output-token limit' };
83
+ }
84
+ return { stopReason };
85
+ }
86
+ /**
87
+ * Map DSH's append-only session log into OMK's source-neutral executor result.
88
+ * Root and descendant token/tool evidence are included; final output remains
89
+ * the root session's last assistant text.
90
+ */
91
+ export function buildDshHostResult(result, wallClockDurationMs) {
92
+ const events = collectEvents(result);
93
+ const tools = new Map();
94
+ const orderedTools = [];
95
+ const turns = [];
96
+ let inputTokens = 0;
97
+ let outputTokens = 0;
98
+ let cacheReadTokens = 0;
99
+ let cacheCreationTokens = 0;
100
+ let usageReported = true;
101
+ let assistantMessages = 0;
102
+ let rootTurns = 0;
103
+ for (const record of events) {
104
+ const { event, sessionId, traceRole } = record;
105
+ const data = isRecord(event.data) ? event.data : {};
106
+ const eventType = nonEmptyString(event.type);
107
+ if (eventType === 'turn/end' && sessionId === result.rootSessionId)
108
+ rootTurns += 1;
109
+ if (eventType === 'assistant/message') {
110
+ assistantMessages += 1;
111
+ const message = isRecord(data.message) ? data.message : {};
112
+ const text = textFromBlocks(message.content);
113
+ const usage = isRecord(data.usage) ? data.usage : undefined;
114
+ const nextInput = optionalTokenCount(usage?.inputTokens);
115
+ const nextOutput = optionalTokenCount(usage?.outputTokens);
116
+ const nextCacheRead = usage?.cacheReadTokens === undefined
117
+ ? 0
118
+ : optionalTokenCount(usage.cacheReadTokens);
119
+ const nextCacheWrite = usage?.cacheWriteTokens === undefined
120
+ ? 0
121
+ : optionalTokenCount(usage.cacheWriteTokens);
122
+ const sums = usage && nextInput !== undefined && nextOutput !== undefined
123
+ && nextCacheRead !== undefined && nextCacheWrite !== undefined
124
+ ? {
125
+ input: checkedSumTokenCounts(inputTokens, nextInput),
126
+ output: checkedSumTokenCounts(outputTokens, nextOutput),
127
+ cacheRead: checkedSumTokenCounts(cacheReadTokens, nextCacheRead),
128
+ cacheWrite: checkedSumTokenCounts(cacheCreationTokens, nextCacheWrite),
129
+ }
130
+ : undefined;
131
+ if (!sums || Object.values(sums).some((value) => value === undefined)) {
132
+ usageReported = false;
133
+ }
134
+ else {
135
+ inputTokens = sums.input ?? inputTokens;
136
+ outputTokens = sums.output ?? outputTokens;
137
+ cacheReadTokens = sums.cacheRead ?? cacheReadTokens;
138
+ cacheCreationTokens = sums.cacheWrite ?? cacheCreationTokens;
139
+ }
140
+ turns.push({
141
+ role: 'assistant',
142
+ content: text,
143
+ });
144
+ continue;
145
+ }
146
+ if (eventType === 'user/message') {
147
+ const message = isRecord(data.message) ? data.message : data;
148
+ turns.push({ role: 'user', content: textFromBlocks(message.content) });
149
+ continue;
150
+ }
151
+ if (eventType === 'tool/call') {
152
+ const callId = nonEmptyString(data.callId);
153
+ const sourceName = nonEmptyString(data.name);
154
+ if (!callId || !sourceName)
155
+ continue;
156
+ const identity = normalizeToolIdentity({ sourceName });
157
+ const timestamp = eventTimestamp(event);
158
+ const info = {
159
+ tool: identity.name,
160
+ ...(identity.sourceName && { sourceTool: identity.sourceName }),
161
+ ...(identity.namespace && { toolNamespace: identity.namespace }),
162
+ ...(identity.provider && { toolProvider: identity.provider }),
163
+ input: serializableToolInput(data.arguments),
164
+ output: null,
165
+ status: 'unknown',
166
+ statusSource: 'unknown',
167
+ success: false,
168
+ callInstanceId: `${sessionId}:${callId}`,
169
+ toolUseId: callId,
170
+ ...(timestamp && { timestamp }),
171
+ sourceTrace: `dsh-host:${sessionId}`,
172
+ traceRole,
173
+ };
174
+ const turn = {
175
+ role: 'tool',
176
+ content: 'null',
177
+ toolCalls: [info],
178
+ };
179
+ const mutable = { info, turn };
180
+ tools.set(`${sessionId}\0${callId}`, mutable);
181
+ orderedTools.push(mutable);
182
+ turns.push(turn);
183
+ continue;
184
+ }
185
+ if (eventType === 'tool/result') {
186
+ const block = resultBlock(data);
187
+ const callId = nonEmptyString(block?.toolCallId);
188
+ if (!callId)
189
+ continue;
190
+ const tool = tools.get(`${sessionId}\0${callId}`);
191
+ if (!tool)
192
+ continue;
193
+ const failed = Boolean(data.error) || block?.isError === true;
194
+ const resultContent = contentBlocks(block?.content);
195
+ const textOutput = textFromBlocks(resultContent);
196
+ tool.info.output = boundedToolOutput(textOutput || resultContent);
197
+ tool.info.status = failed ? 'failure' : 'success';
198
+ tool.info.statusSource = 'runtime';
199
+ tool.info.success = !failed;
200
+ tool.turn.content = typeof tool.info.output === 'string'
201
+ ? tool.info.output
202
+ : JSON.stringify(tool.info.output);
203
+ }
204
+ }
205
+ const terminal = terminalReason(events, result.rootSessionId);
206
+ const output = result.finalResponse || null;
207
+ const successfulStop = terminal.stopReason === 'completed';
208
+ const error = terminal.error ?? (!output ? 'dsh runtime produced no root assistant output' : undefined);
209
+ return {
210
+ ok: successfulStop && Boolean(output) && !error,
211
+ output,
212
+ durationMs: wallClockDurationMs,
213
+ durationApiMs: wallClockDurationMs,
214
+ inputTokens,
215
+ outputTokens,
216
+ cacheReadTokens,
217
+ cacheCreationTokens,
218
+ tokenUsageReportedByExecutor: usageReported && assistantMessages > 0,
219
+ costUSD: 0,
220
+ costReportedByExecutor: false,
221
+ stopReason: terminal.stopReason,
222
+ numTurns: rootTurns,
223
+ fullNumTurns: assistantMessages,
224
+ numSubAgents: new Set(result.childSessionIds).size,
225
+ ...(error && { error }),
226
+ turns,
227
+ toolCalls: orderedTools.map(({ info }) => info),
228
+ };
229
+ }
@@ -44,6 +44,9 @@ function packageReasons(pkg, label) {
44
44
  }
45
45
  function runtimeUnverifiableReasons(runtime) {
46
46
  const reasons = [];
47
+ if (runtime.auditability?.status === 'partial') {
48
+ reasons.push(...(runtime.auditability.reasons ?? ['runtime auditability partial']));
49
+ }
47
50
  if (runtime.runtimeKind === 'api')
48
51
  return reasons;
49
52
  if (runtime.runtimeKind === 'agent-sdk') {
@@ -5,22 +5,14 @@ import { grade } from '../grading/index.js';
5
5
  import { checkFacts } from './fact-checker.js';
6
6
  import { resolveExecutionStrategy } from './execution-strategy.js';
7
7
  import { DEFAULT_CACHE_DIR, DEFAULT_ISOLATED_CWD_DIR } from './default-dirs.js';
8
- import { getExecutorRuntimeFingerprint } from '../executors/runtime-fingerprint.js';
8
+ import { resolveExecutorRuntimeFingerprint } from '../executors/core/runtime-fingerprint.js';
9
+ import { isRegisteredExecutorName } from '../executors/core/registry.js';
9
10
  import { ownRecordValue, setOwnRecordValue, } from '../shared/record-count.js';
10
11
  import { executorResultValidationError, normalizeExecResultToolIdentities, } from '../shared/executor-result.js';
11
12
  import { hashSampleExecutionDependencies } from './sample-fingerprint.js';
12
13
  import { resolveDiagnosticTarget } from '../grading/diagnostic.js';
13
14
  import { dirname, join, resolve } from 'node:path';
14
15
  import { mkdir, mkdtemp, rm } from 'node:fs/promises';
15
- const PREFLIGHT_RUNTIME_LABEL_EXECUTORS = new Set([
16
- 'claude',
17
- 'claude-sdk',
18
- 'codex',
19
- 'codex-sdk',
20
- 'gemini',
21
- 'anthropic-api',
22
- 'openai-api',
23
- ]);
24
16
  async function runWithConcurrency(tasks, concurrency, fn) {
25
17
  let index = 0;
26
18
  async function worker() {
@@ -119,9 +111,9 @@ export async function executeTasks({ tasks, executor, executorName, model, noJud
119
111
  const total = tasks.length;
120
112
  onProgress?.({ phase: 'start', completed: idx, total, sample_id: task.sample_id, variant: task.variant });
121
113
  const executionPlan = resolveExecutionStrategy(task, model, timeoutMs, verbose, effort, samplesBaseDir);
122
- const executorRuntime = getExecutorRuntimeFingerprint(effectiveExecutorName, model, {
114
+ const executorRuntime = resolveExecutorRuntimeFingerprint(effectiveExecutorName, model, {
123
115
  skillDir: executionPlan.input.skillDir,
124
- });
116
+ }, executor);
125
117
  let execResult;
126
118
  // include allowedSkills in cache key so isolation-on / isolation-off runs
127
119
  // don't share cache entries, and include runtime fingerprint so a binary/SDK bump
@@ -390,7 +382,7 @@ export async function executeTasks({ tasks, executor, executorName, model, noJud
390
382
  return { results, totalCostUSD, skipped, budgetExhausted };
391
383
  }
392
384
  export function preflightRuntimeLabel(executorName, model) {
393
- return PREFLIGHT_RUNTIME_LABEL_EXECUTORS.has(executorName) ? `${executorName}:${model}` : `custom:${model}`;
385
+ return isRegisteredExecutorName(executorName) ? `${executorName}:${model}` : `custom:${model}`;
394
386
  }
395
387
  export async function preflight(executor, model, timeoutMs = 180000, label) {
396
388
  const result = await executor({
@@ -1,18 +1,19 @@
1
- import { getExecutorRuntimeFingerprint } from '../executors/runtime-fingerprint.js';
1
+ import { getExecutorRuntimeFingerprint } from '../executors/core/runtime-fingerprint.js';
2
2
  import type { Artifact, Report, Sample, Task, VariantResult, GitInfo, EvaluationJob, EvaluationRequest, EvaluationRun, ReportDocument } from '../types/index.js';
3
3
  export declare const DEFAULT_OUTPUT_DIR: string;
4
4
  export declare const EVALUATION_REPORT_SCHEMA_VERSION = 5;
5
5
  export declare function hashString(str: string): string;
6
6
  export { hashSample } from './sample-fingerprint.js';
7
7
  export declare function getCliVersion(): string;
8
- export declare function getGitInfo(): GitInfo | null;
9
- export declare function buildExecutorRuntimesByVariant({ variants, model, executorName, tasks, artifacts, request, }: {
8
+ export declare function getGitInfo(cwd?: string): GitInfo | null;
9
+ export declare function buildExecutorRuntimesByVariant({ variants, model, executorName, tasks, artifacts, request, executor, }: {
10
10
  variants: string[];
11
11
  model: string;
12
12
  executorName: string;
13
13
  tasks: Task[];
14
14
  artifacts: Artifact[];
15
15
  request?: Pick<EvaluationRequest, 'skillDir' | 'timeoutMs'>;
16
+ executor?: import('../types/index.js').ExecutorFn;
16
17
  }): Record<string, ReturnType<typeof getExecutorRuntimeFingerprint>>;
17
18
  interface AggregateReportOptions {
18
19
  runId: string;
@@ -31,8 +32,10 @@ interface AggregateReportOptions {
31
32
  run?: EvaluationRun;
32
33
  job?: EvaluationJob;
33
34
  layeredStats?: boolean;
35
+ executor?: import('../types/index.js').ExecutorFn;
36
+ judgeExecutors?: Readonly<Record<string, import('../types/index.js').ExecutorFn>>;
34
37
  }
35
- export declare function aggregateReport({ runId, variants, model, judgeModel, noJudge, executorName, samples, samplesBaseDir, tasks, results, totalCostUSD, artifacts, request, run, job, layeredStats, }: AggregateReportOptions): Report;
38
+ export declare function aggregateReport({ runId, variants, model, judgeModel, noJudge, executorName, samples, samplesBaseDir, tasks, results, totalCostUSD, artifacts, request, run, job, layeredStats, executor, judgeExecutors, }: AggregateReportOptions): Report;
36
39
  export type PersistableReport = ReportDocument;
37
40
  export declare function persistReport(report: PersistableReport, outputDir: string | null): string | null;
38
41
  /**
@@ -13,7 +13,7 @@ import { buildVariantConfig, resolveExecutionStrategy } from './execution-strate
13
13
  import { getJudgePromptHash } from '../grading/judge.js';
14
14
  import { getDiagnosticPromptHash, resolveDiagnosticTarget, } from '../grading/diagnostic.js';
15
15
  import { bootstrapMeanCI, bootstrapPairedDiffCI, DEFAULT_BOOTSTRAP_ALPHA, DEFAULT_BOOTSTRAP_SAMPLES, } from './bootstrap.js';
16
- import { getExecutorRuntimeFingerprint } from '../executors/runtime-fingerprint.js';
16
+ import { resolveExecutorRuntimeFingerprint, } from '../executors/core/runtime-fingerprint.js';
17
17
  import { ownRecordValue, setOwnRecordValue, } from '../shared/record-count.js';
18
18
  import { writeJsonFileAtomic } from '../shared/atomic-json.js';
19
19
  import { hashSample } from './sample-fingerprint.js';
@@ -39,15 +39,25 @@ export { hashSample } from './sample-fingerprint.js';
39
39
  export function getCliVersion() {
40
40
  return PKG.version;
41
41
  }
42
- export function getGitInfo() {
43
- // stdio 静默 stderr:在非 git 目录(如 omk init 出来的 demo)里 rev-parse 会打印
44
- // `fatal: not a git repository` 到终端。catch 已把失败兜成 null(报告省略 git 信息),
45
- // 这条 fatal 对用户是纯噪声,吞掉它。与 skill-loader GIT_PROBE_STDIO 同口径。
42
+ export function getGitInfo(cwd = process.cwd()) {
43
+ // porcelain v2 branch header 一次返回 commit / branch,后续状态行同时表达 dirty。
44
+ // 相比 rev-parse ×2 + status,少启动两个同步 Git 子进程;报告字段语义不变。
45
+ // stdio 静默 stderr:非 git 目录仍返回 null,不把 fatal 噪声泄漏给用户。
46
46
  const gitProbeStdio = ['ignore', 'pipe', 'ignore'];
47
47
  try {
48
- const commit = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf-8', stdio: gitProbeStdio }).trim();
49
- const branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf-8', stdio: gitProbeStdio }).trim();
50
- const dirty = execFileSync('git', ['status', '--porcelain'], { encoding: 'utf-8', stdio: gitProbeStdio }).trim().length > 0;
48
+ const output = execFileSync('git', ['status', '--porcelain=v2', '--branch'], {
49
+ cwd,
50
+ encoding: 'utf-8',
51
+ stdio: gitProbeStdio,
52
+ });
53
+ const lines = output.split('\n');
54
+ const oid = lines.find((line) => line.startsWith('# branch.oid '))?.slice('# branch.oid '.length).trim();
55
+ const head = lines.find((line) => line.startsWith('# branch.head '))?.slice('# branch.head '.length).trim();
56
+ if (!oid || oid === '(initial)' || !head)
57
+ return null;
58
+ const commit = oid;
59
+ const branch = head === '(detached)' ? 'HEAD' : head;
60
+ const dirty = lines.some((line) => line.length > 0 && !line.startsWith('# '));
51
61
  return { commit, commitShort: commit.slice(0, 7), branch, dirty };
52
62
  }
53
63
  catch {
@@ -64,15 +74,15 @@ function commonRuntime(runtimes) {
64
74
  function representativeRuntime(runtimes) {
65
75
  return Object.values(runtimes)[0];
66
76
  }
67
- export function buildExecutorRuntimesByVariant({ variants, model, executorName, tasks, artifacts, request, }) {
77
+ export function buildExecutorRuntimesByVariant({ variants, model, executorName, tasks, artifacts, request, executor, }) {
68
78
  const runtimes = {};
69
79
  for (const task of tasks) {
70
80
  if (ownRecordValue(runtimes, task.variant))
71
81
  continue;
72
82
  const executionPlan = resolveExecutionStrategy(task, model, request?.timeoutMs, false);
73
- setOwnRecordValue(runtimes, task.variant, getExecutorRuntimeFingerprint(executorName, model, {
83
+ setOwnRecordValue(runtimes, task.variant, resolveExecutorRuntimeFingerprint(executorName, model, {
74
84
  skillDir: executionPlan.input.skillDir,
75
- }));
85
+ }, executor));
76
86
  }
77
87
  for (const variant of variants) {
78
88
  if (ownRecordValue(runtimes, variant))
@@ -86,13 +96,13 @@ export function buildExecutorRuntimesByVariant({ variants, model, executorName,
86
96
  : artifact?.locator
87
97
  ? dirname(artifact.locator)
88
98
  : request?.skillDir);
89
- setOwnRecordValue(runtimes, variant, getExecutorRuntimeFingerprint(executorName, model, {
99
+ setOwnRecordValue(runtimes, variant, resolveExecutorRuntimeFingerprint(executorName, model, {
90
100
  skillDir: fallbackSkillDir,
91
- }));
101
+ }, executor));
92
102
  }
93
103
  return runtimes;
94
104
  }
95
- export function aggregateReport({ runId, variants, model, judgeModel, noJudge, executorName, samples, samplesBaseDir, tasks, results, totalCostUSD, artifacts, request, run, job, layeredStats, }) {
105
+ export function aggregateReport({ runId, variants, model, judgeModel, noJudge, executorName, samples, samplesBaseDir, tasks, results, totalCostUSD, artifacts, request, run, job, layeredStats, executor, judgeExecutors, }) {
96
106
  const summary = {};
97
107
  for (const variant of variants) {
98
108
  const entries = Object.values(results)
@@ -172,10 +182,18 @@ export function aggregateReport({ runId, variants, model, judgeModel, noJudge, e
172
182
  const sampleHashes = Object.fromEntries(samples.map((sample) => [sample.sample_id, hashSample(sample, samplesBaseDir)]));
173
183
  const judgeRepeat = request?.judgeRepeat && request.judgeRepeat > 1 ? request.judgeRepeat : undefined;
174
184
  const runtimeOptions = { skillDir: request?.skillDir };
175
- const executorRuntimes = buildExecutorRuntimesByVariant({ variants, model, executorName, tasks, artifacts, request });
185
+ const executorRuntimes = buildExecutorRuntimesByVariant({
186
+ variants,
187
+ model,
188
+ executorName,
189
+ tasks,
190
+ artifacts,
191
+ request,
192
+ executor,
193
+ });
176
194
  const executorRuntime = commonRuntime(executorRuntimes)
177
195
  ?? representativeRuntime(executorRuntimes)
178
- ?? getExecutorRuntimeFingerprint(executorName, model, runtimeOptions);
196
+ ?? resolveExecutorRuntimeFingerprint(executorName, model, runtimeOptions, executor);
179
197
  // request.judgeModels is the authoritative source (always non-empty in new schema).
180
198
  // Fallback synthesizes a 1-entry from positional judgeModel/executorName for any
181
199
  // legacy caller not yet migrated to the array. noJudge ⇒ runtime undefined per entry.
@@ -183,7 +201,9 @@ export function aggregateReport({ runId, variants, model, judgeModel, noJudge, e
183
201
  const judgeModelsMeta = requestJudges.map((jc) => ({
184
202
  executor: jc.executor,
185
203
  model: jc.model,
186
- ...(noJudge ? {} : { runtime: getExecutorRuntimeFingerprint(jc.executor, jc.model, runtimeOptions) }),
204
+ ...(noJudge ? {} : {
205
+ runtime: resolveExecutorRuntimeFingerprint(jc.executor, jc.model, runtimeOptions, judgeExecutors?.[jc.executor]),
206
+ }),
187
207
  }));
188
208
  const diagnosticEnabled = request?.noDiagnostic !== true;
189
209
  const diagnosticTarget = resolveDiagnosticTarget(requestJudges, executorName, model);
@@ -192,7 +212,8 @@ export function aggregateReport({ runId, variants, model, judgeModel, noJudge, e
192
212
  enabled: true,
193
213
  executor: diagnosticTarget.executor,
194
214
  model: diagnosticTarget.model,
195
- runtime: getExecutorRuntimeFingerprint(diagnosticTarget.executor, diagnosticTarget.model),
215
+ runtime: resolveExecutorRuntimeFingerprint(diagnosticTarget.executor, diagnosticTarget.model, {}, judgeExecutors?.[diagnosticTarget.executor]
216
+ ?? (diagnosticTarget.executor === executorName ? executor : undefined)),
196
217
  promptHash: getDiagnosticPromptHash(),
197
218
  }
198
219
  : { enabled: false };
@@ -1,5 +1,5 @@
1
1
  import type { Report } from '../types/index.js';
2
- import { type ExecutorVendor } from '../executors/shared.js';
2
+ import { type ExecutorVendor } from '../executors/core/registry.js';
3
3
  /**
4
4
  * 评委独立性分析(单一来源,verdict caveat 与 analysis 诊断共用)。
5
5
  *
@@ -1,4 +1,4 @@
1
- import { executorVendor } from '../executors/shared.js';
1
+ import { executorVendor } from '../executors/core/registry.js';
2
2
  export function analyzeJudgeIndependence(report) {
3
3
  const judges = report.meta?.judgeModels ?? [];
4
4
  const judgeVendors = judges.map((j) => executorVendor(j.executor));
@@ -71,6 +71,12 @@ function isRuntimeFingerprint(value) {
71
71
  || !['reported', 'not-reported', 'unknown'].includes(String(value.capabilities.costUSD))
72
72
  || !['native', 'best-effort', 'none', 'unknown'].includes(String(value.capabilities.trace))
73
73
  || !['full', 'full-no-partial', 'cwd-only', 'none', 'unknown'].includes(String(value.capabilities.skillIsolation))
74
+ || (value.auditability !== undefined
75
+ && (!isRecord(value.auditability)
76
+ || !['complete', 'partial'].includes(String(value.auditability.status))
77
+ || (value.auditability.reasons !== undefined
78
+ && (!Array.isArray(value.auditability.reasons)
79
+ || !value.auditability.reasons.every((reason) => typeof reason === 'string')))))
74
80
  || (value.sdk !== undefined && !isRuntimePackage(value.sdk)))
75
81
  return false;
76
82
  if (value.binary === undefined)
@@ -17,6 +17,7 @@ export interface ResumeCompatibilityInput {
17
17
  samplesBaseDir?: string;
18
18
  tasks: Task[];
19
19
  artifacts: Artifact[];
20
+ executorOverrides?: Readonly<Record<string, import('../types/index.js').ExecutorFn>>;
20
21
  }
21
22
  export interface ResumeCompatibilityResult {
22
23
  compatible: boolean;