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.
- package/README.md +13 -1
- package/README.zh.md +13 -1
- package/dist/assets/agent-skills/omk/SKILL.md +4 -0
- package/dist/assets/agent-skills/omk/references/commands.md +1 -1
- package/dist/authoring/evolver.js +1 -1
- package/dist/authoring/generator.js +1 -1
- package/dist/cli/commands/eval/index.js +7 -6
- package/dist/cli/commands/install.js +5 -1
- package/dist/cli/lib/generation-failure-hint.js +6 -8
- package/dist/cli/lib/runtime-defaults.d.ts +2 -0
- package/dist/cli/lib/runtime-defaults.js +7 -4
- package/dist/dsh-plugin/cordis.patch.yml +3 -0
- package/dist/dsh-plugin/host-executor.d.ts +93 -0
- package/dist/dsh-plugin/host-executor.js +232 -0
- package/dist/dsh-plugin/index.d.ts +28 -0
- package/dist/dsh-plugin/index.js +156 -0
- package/dist/dsh-plugin/protocol.d.ts +21 -0
- package/dist/dsh-plugin/protocol.js +229 -0
- package/dist/eval-core/comparability.js +3 -0
- package/dist/eval-core/evaluation-execution.js +5 -13
- package/dist/eval-core/evaluation-reporting.d.ts +7 -4
- package/dist/eval-core/evaluation-reporting.js +39 -18
- package/dist/eval-core/judge-independence.d.ts +1 -1
- package/dist/eval-core/judge-independence.js +1 -1
- package/dist/eval-core/report-document.js +6 -0
- package/dist/eval-core/resume-compatibility.d.ts +1 -0
- package/dist/eval-core/resume-compatibility.js +5 -4
- package/dist/eval-workflows/batch-evaluation-workflow.js +1 -1
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +4 -2
- package/dist/eval-workflows/evaluation-pipeline.d.ts +3 -1
- package/dist/eval-workflows/evaluation-pipeline.js +7 -3
- package/dist/eval-workflows/run-evaluation.d.ts +4 -2
- package/dist/eval-workflows/run-evaluation.js +7 -3
- package/dist/executors/{anthropic-api.d.ts → anthropic/api.d.ts} +1 -1
- package/dist/executors/{anthropic-api.js → anthropic/api.js} +4 -2
- package/dist/executors/{claude-cli.d.ts → anthropic/claude/cli.d.ts} +1 -1
- package/dist/executors/{claude-cli.js → anthropic/claude/cli.js} +5 -3
- package/dist/executors/anthropic/claude/protocol.d.ts +87 -0
- package/dist/executors/{claude-protocol.js → anthropic/claude/protocol.js} +6 -6
- package/dist/executors/{claude-sdk.d.ts → anthropic/claude/sdk.d.ts} +2 -2
- package/dist/executors/{claude-sdk.js → anthropic/claude/sdk.js} +8 -5
- package/dist/executors/anthropic/claude/trace.d.ts +9 -0
- package/dist/executors/{claude-sdk-trace.js → anthropic/claude/trace.js} +5 -5
- package/dist/executors/{capabilities.d.ts → core/capabilities.d.ts} +3 -5
- package/dist/executors/{capabilities.js → core/capabilities.js} +4 -11
- package/dist/executors/core/http.d.ts +6 -0
- package/dist/executors/core/http.js +19 -0
- package/dist/executors/core/limits.d.ts +2 -0
- package/dist/executors/core/limits.js +2 -0
- package/dist/executors/core/optional-dependencies.d.ts +7 -0
- package/dist/executors/core/optional-dependencies.js +35 -0
- package/dist/executors/core/registry.d.ts +145 -0
- package/dist/executors/core/registry.js +127 -0
- package/dist/executors/core/runtime-fingerprint.d.ts +13 -0
- package/dist/executors/{runtime-fingerprint.js → core/runtime-fingerprint.js} +119 -59
- package/dist/executors/core/runtime.d.ts +12 -0
- package/dist/executors/core/runtime.js +61 -0
- package/dist/executors/core/subprocess.d.ts +44 -0
- package/dist/executors/{shared.js → core/subprocess.js} +20 -156
- package/dist/executors/index.d.ts +4 -4
- package/dist/executors/index.js +26 -15
- package/dist/executors/{openai-api.d.ts → openai/api.d.ts} +1 -1
- package/dist/executors/{openai-api.js → openai/api.js} +4 -2
- package/dist/executors/{codex-cli.d.ts → openai/codex/cli.d.ts} +3 -3
- package/dist/executors/{codex-cli.js → openai/codex/cli.js} +5 -3
- package/dist/executors/openai/codex/protocol.d.ts +72 -0
- package/dist/executors/{codex-protocol.js → openai/codex/protocol.js} +33 -2
- package/dist/executors/{codex-sdk.d.ts → openai/codex/sdk.d.ts} +18 -4
- package/dist/executors/{codex-sdk.js → openai/codex/sdk.js} +7 -4
- package/dist/executors/{codex-cli-trace.d.ts → openai/codex/trace.d.ts} +2 -2
- package/dist/executors/{codex-cli-trace.js → openai/codex/trace.js} +4 -4
- package/dist/executors/{script.d.ts → script/index.d.ts} +1 -1
- package/dist/executors/{script.js → script/index.js} +6 -4
- package/dist/grading/judge.d.ts +1 -1
- package/dist/grading/judge.js +1 -1
- package/dist/renderer/html-renderer.js +30 -11
- package/dist/types/executor.d.ts +13 -2
- package/dist/types/judge.d.ts +2 -2
- package/package.json +21 -5
- package/dist/executors/claude-protocol.d.ts +0 -28
- package/dist/executors/claude-sdk-trace.d.ts +0 -9
- package/dist/executors/codex-protocol.d.ts +0 -24
- package/dist/executors/gemini.d.ts +0 -2
- package/dist/executors/gemini.js +0 -156
- package/dist/executors/runtime-fingerprint.d.ts +0 -6
- package/dist/executors/shared.d.ts +0 -226
- /package/dist/executors/{script-command.d.ts → script/command.d.ts} +0 -0
- /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 {
|
|
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 =
|
|
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
|
|
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 {
|
|
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
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
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,
|
|
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,
|
|
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({
|
|
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
|
-
??
|
|
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 ? {} : {
|
|
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:
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import { executorVendor } from '../executors/
|
|
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;
|