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
package/README.md
CHANGED
|
@@ -159,7 +159,7 @@ RAG-specific evals: see RAGAS (separate niche, complementary to omk). Full compa
|
|
|
159
159
|
|---|---|
|
|
160
160
|
| **One-line verdict** | `omk eval` six-tier verdict + ship recommendation + exit-code routing; HTML pill shares the same rules |
|
|
161
161
|
| **Six-dim evaluation** | Fact / Behavior / LLM-judge / Cost / Efficiency / Stability shown independently |
|
|
162
|
-
| **Multi-executor** | Claude CLI / Claude SDK / Codex CLI / Codex SDK /
|
|
162
|
+
| **Multi-executor** | Claude CLI / Claude SDK / Codex CLI / Codex SDK / DeepSeek Harness / OpenAI / Anthropic API / any custom command |
|
|
163
163
|
| **30+ assertion types** | substring, regex, JSON Schema, ROUGE/BLEU/Levenshtein similarity, agent tool-call assertions, semantic similarity, custom JS |
|
|
164
164
|
| **Statistical rigor** | Bootstrap CI / length-debias / saturation curve on by default; Krippendorff α auto-computed with a gold set. [Details →](docs/explanation/statistical-rigor.md) |
|
|
165
165
|
| **RAG metrics** | `faithfulness` / `answer_relevancy` / `context_recall` — anti-hallucination + answer relevance + context coverage |
|
|
@@ -177,6 +177,17 @@ RAG-specific evals: see RAGAS (separate niche, complementary to omk). Full compa
|
|
|
177
177
|
| **Traceability** | reports carry CLI version, Node version, artifact version fingerprint, judge prompt hash |
|
|
178
178
|
| **EN / ZH switch** | one-click language toggle in the HTML report |
|
|
179
179
|
|
|
180
|
+
### Run inside an existing DeepSeek Harness
|
|
181
|
+
|
|
182
|
+
Install OMK as a DSH bundle to reuse the profile's model, credentials, tools, and sandbox:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
dsh plugin --profile web add oh-my-knowledge
|
|
186
|
+
dsh --profile web
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Then run `/omk eval eval.yaml` inside DSH. Each sample gets an isolated DSH session while OMK still owns the report and statistics. See the [executor guide](docs/reference/executors.md#deepseek-harness-prefer-the-host-plugin).
|
|
190
|
+
|
|
180
191
|
## Documentation
|
|
181
192
|
|
|
182
193
|
The full docs are published at **[oh-my-knowledge.pages.dev](https://oh-my-knowledge.pages.dev)** — searchable, with an English / 简体中文 switcher. Key pages:
|
|
@@ -211,6 +222,7 @@ The full docs are published at **[oh-my-knowledge.pages.dev](https://oh-my-knowl
|
|
|
211
222
|
- Codex: install and authenticate the Codex CLI (`npm i -g @openai/codex`); Codex tasks in the ChatGPT desktop app select it automatically
|
|
212
223
|
- Claude: install and authenticate [Claude Code](https://claude.ai/code)
|
|
213
224
|
- API / other executors: configure them as described in [Executors](docs/reference/executors.md)
|
|
225
|
+
- Advanced `claude-sdk` / `codex-sdk` executors are optional and are not downloaded by the base OMK install. Install the matching SDK in the same local project or global npm prefix only when you select one; see [Executor prerequisites](docs/reference/executors.md#prerequisites).
|
|
214
226
|
|
|
215
227
|
## Security notice
|
|
216
228
|
|
package/README.zh.md
CHANGED
|
@@ -159,7 +159,7 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
|
|
|
159
159
|
|------|------|
|
|
160
160
|
| **Verdict 一行结论** | `omk eval` 六档判定 + ship 建议 + exit code 路由,与 HTML 报告 verdict pill 共享规则 |
|
|
161
161
|
| **六维评估** | 事实 / 行为 / LLM 评价 / 成本 / 效率 / 稳定性独立展示 |
|
|
162
|
-
| **多执行器** | 支持 Claude CLI / Claude SDK / Codex CLI / Codex SDK /
|
|
162
|
+
| **多执行器** | 支持 Claude CLI / Claude SDK / Codex CLI / Codex SDK / DeepSeek Harness / OpenAI / Anthropic API 及自定义命令 |
|
|
163
163
|
| **30+ 种断言** | 包含子串、正则、JSON Schema、ROUGE/BLEU/Levenshtein 相似度、Agent 工具调用、语义相似度、自定义函数等 |
|
|
164
164
|
| **统计严谨性** | Bootstrap CI / 长度去偏 / 饱和曲线默认开,Krippendorff α 提供 gold 集即自动计算。[详情 →](docs/zh/explanation/statistical-rigor.md) |
|
|
165
165
|
| **RAG metrics** | `faithfulness` / `answer_relevancy` / `context_recall` 三 metric — 反幻觉 + 切题度 + context 覆盖 |
|
|
@@ -177,6 +177,17 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
|
|
|
177
177
|
| **可追溯性** | 报告含 CLI 版本、Node 版本、知识载体版本指纹、judge prompt hash |
|
|
178
178
|
| **中英切换** | HTML 报告右上角一键切换语言 |
|
|
179
179
|
|
|
180
|
+
### 在已有 DeepSeek Harness 中运行
|
|
181
|
+
|
|
182
|
+
OMK 可以作为 DSH bundle 安装到现有 profile,直接复用其模型、凭证、工具与 sandbox:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
dsh plugin --profile web add oh-my-knowledge
|
|
186
|
+
dsh --profile web
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
进入 DSH 后运行 `/omk eval eval.yaml`。每条用例会使用独立 DSH session,报告仍由 OMK 生成。详见[执行器文档](docs/zh/reference/executors.md#deepseek-harness优先使用宿主插件)。
|
|
190
|
+
|
|
180
191
|
## 文档
|
|
181
192
|
|
|
182
193
|
完整文档已发布到 **[oh-my-knowledge.pages.dev/zh](https://oh-my-knowledge.pages.dev/zh/)** —— 可搜索,可切换英文。重点页面:
|
|
@@ -211,6 +222,7 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
|
|
|
211
222
|
- Codex:安装并登录 Codex CLI(`npm i -g @openai/codex`);ChatGPT desktop 的 Codex 任务会自动选择它
|
|
212
223
|
- Claude:安装并登录 [Claude Code](https://claude.ai/code)
|
|
213
224
|
- API / 其它执行器:按[执行器文档](docs/zh/reference/executors.md)配置
|
|
225
|
+
- 高级 `claude-sdk`/`codex-sdk` 执行器是可选能力,OMK 基础安装不再下载它们。仅在明确选择对应 SDK 时,才在 OMK 所在的本地项目或全局 npm prefix 安装;详见[执行器前置要求](docs/zh/reference/executors.md#前置要求)。
|
|
214
226
|
|
|
215
227
|
## 安全说明
|
|
216
228
|
|
|
@@ -27,6 +27,10 @@ Codex 是 omk 的一等 runtime。运行在 Codex 任务中时,`omk eval` / `d
|
|
|
27
27
|
|
|
28
28
|
普通终端想固定走 Codex 时,可以设置 `OMK_EXECUTOR=codex`;`OMK_MODEL` 可覆盖本机 Codex 配置,`OMK_JUDGE_MODELS` 可覆盖默认评委。逐次覆盖仍可使用 `--executor` / `--model` / `--judge-models`。Codex 不需要 Claude Code 风格的 `/omk` slash command,直接执行 CLI。
|
|
29
29
|
|
|
30
|
+
### 在 DeepSeek Harness 中
|
|
31
|
+
|
|
32
|
+
如果当前 DSH profile 已安装 `oh-my-knowledge` bundle,使用 `/omk eval <eval.yaml>`。该路径直接复用现有 DSH 的模型、凭证、工具与 sandbox,并为每条用例创建隔离 session;不要要求用户另起 DSH runtime。
|
|
33
|
+
|
|
30
34
|
## 第二步:理解用户意图
|
|
31
35
|
|
|
32
36
|
根据用户的描述,匹配对应的操作:
|
|
@@ -85,7 +85,7 @@ omk eval [flags]
|
|
|
85
85
|
- `--control-cwd` `option`:control 的 runtime context 目录
|
|
86
86
|
- `--dry-run` `boolean`:只 plan 不实跑
|
|
87
87
|
- `--effort` `option`:被测 LLM 扩展思考预算 low/medium/high/xhigh/max(默认 low;跨 effort 报告不严格可比)。
|
|
88
|
-
- `--executor` `option`:执行器:claude / claude-sdk / codex / codex-sdk /
|
|
88
|
+
- `--executor` `option`:执行器:claude / claude-sdk / codex / codex-sdk / anthropic-api / openai-api / 自定义命令。Codex 任务内自动用 codex;也可用 OMK_EXECUTOR 设置环境偏好。
|
|
89
89
|
- `--global` `boolean`:报告写全局 ~/.oh-my-knowledge/reports,而非项目 .omk/
|
|
90
90
|
- `--gold-dir` `option`:gold dataset 目录
|
|
91
91
|
- `--holdout-ratio` `option`:留出比例 0-1(如 0.3);切出 holdout 子集,对比 train/holdout 综合分检测过拟合
|
|
@@ -17,7 +17,7 @@ import { toolCallStatus } from '../shared/tool-call-status.js';
|
|
|
17
17
|
import { ownRecordValue, setOwnRecordValue } from '../shared/record-count.js';
|
|
18
18
|
import { getJudgePromptHash } from '../grading/judge.js';
|
|
19
19
|
import { getDiagnosticPromptHash, resolveDiagnosticTarget, } from '../grading/diagnostic.js';
|
|
20
|
-
import { getExecutorRuntimeFingerprint } from '../executors/runtime-fingerprint.js';
|
|
20
|
+
import { getExecutorRuntimeFingerprint } from '../executors/core/runtime-fingerprint.js';
|
|
21
21
|
import { parseReportDocument } from '../eval-core/report-document.js';
|
|
22
22
|
const IMPROVE_SYSTEM_PROMPT = `你是一个 AI 提示词改进专家。你的任务是分析评测结果中的薄弱环节,针对性地改进 skill(系统提示词),使其在评测中获得更高的分数。
|
|
23
23
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createExecutor } from '../executors/index.js';
|
|
2
|
-
import { executorSupportsSampleMocks } from '../executors/capabilities.js';
|
|
2
|
+
import { executorSupportsSampleMocks } from '../executors/core/capabilities.js';
|
|
3
3
|
import { DEFAULT_GATE_THRESHOLD } from '../eval-core/verdict.js';
|
|
4
4
|
import { sampleMockReferenceKeys } from '../shared/sample-contract.js';
|
|
5
5
|
const SYSTEM_PROMPT = `你是一个评测用例生成器。你的任务是根据用户提供的 skill(系统提示词)内容,生成高质量的评测用例。
|
|
@@ -16,10 +16,11 @@ import { DEFAULT_GATE_THRESHOLD } from '../../../eval-core/verdict.js';
|
|
|
16
16
|
import { EVALUATION_REPORT_SCHEMA_VERSION } from '../../../eval-core/evaluation-reporting.js';
|
|
17
17
|
import { findSingleTreatmentDeprecatedSamplesHint, hasUsableSamplesPath, } from '../../../inputs/sample-locator.js';
|
|
18
18
|
import { shellQuoteArg } from '../../../shared/shell-quote.js';
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
19
|
+
import { executorNamesForFamily } from '../../../executors/core/registry.js';
|
|
20
|
+
const CLAUDE_EXECUTORS = executorNamesForFamily('claude');
|
|
21
|
+
const CODEX_EXECUTORS = executorNamesForFamily('codex');
|
|
22
|
+
const OPENAI_API_EXECUTORS = executorNamesForFamily('openai-api');
|
|
23
|
+
const ANTHROPIC_API_EXECUTORS = executorNamesForFamily('anthropic-api');
|
|
23
24
|
function isDryRunReport(report) {
|
|
24
25
|
return Boolean(report && typeof report === 'object' && report.dryRun === true);
|
|
25
26
|
}
|
|
@@ -559,8 +560,8 @@ export default class Eval extends BaseCommand {
|
|
|
559
560
|
}),
|
|
560
561
|
executor: Flags.string({
|
|
561
562
|
description: bilingual({
|
|
562
|
-
zh: '执行器:claude / claude-sdk / codex / codex-sdk /
|
|
563
|
-
en: 'Executor: claude / claude-sdk / codex / codex-sdk /
|
|
563
|
+
zh: '执行器:claude / claude-sdk / codex / codex-sdk / anthropic-api / openai-api / 自定义命令。Codex 任务内自动用 codex;也可用 OMK_EXECUTOR 设置环境偏好。',
|
|
564
|
+
en: 'Executor: claude / claude-sdk / codex / codex-sdk / anthropic-api / openai-api / custom. Defaults to codex inside Codex tasks; OMK_EXECUTOR sets an environment preference.',
|
|
564
565
|
}),
|
|
565
566
|
}),
|
|
566
567
|
'judge-models': Flags.string({
|
|
@@ -25,7 +25,11 @@ const TARGET_SPECS = {
|
|
|
25
25
|
};
|
|
26
26
|
function packagedOmkAgentSkillDir() {
|
|
27
27
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
28
|
-
|
|
28
|
+
const packaged = resolve(here, '..', '..', 'assets', 'agent-skills', 'omk');
|
|
29
|
+
if (existsSync(packaged))
|
|
30
|
+
return packaged;
|
|
31
|
+
const workspace = resolve(here, '..', '..', '..', '.agents', 'skills', 'omk');
|
|
32
|
+
return existsSync(workspace) ? workspace : packaged;
|
|
29
33
|
}
|
|
30
34
|
function knownTarget(target) {
|
|
31
35
|
const home = homedir();
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import { tCli } from './i18n.js';
|
|
2
2
|
import { codexExecutorFlags, codexModelFlagValue, codexModelHint } from './codex-model-hint.js';
|
|
3
3
|
import { looksLikeLlmSetupFailure, looksLikeModelUnavailableFailure } from './llm-failure-classifier.js';
|
|
4
|
-
|
|
5
|
-
const CODEX_SAMPLE_EXECUTORS = new Set(['codex', 'codex-sdk']);
|
|
6
|
-
const OPENAI_API_SAMPLE_EXECUTORS = new Set(['openai-api']);
|
|
7
|
-
const ANTHROPIC_API_SAMPLE_EXECUTORS = new Set(['anthropic-api']);
|
|
4
|
+
import { executorFamily } from '../../executors/core/registry.js';
|
|
8
5
|
export function formatSampleGenerationFailureHint(message, executorName, lang, env = process.env) {
|
|
9
6
|
const executor = executorName?.trim();
|
|
10
7
|
if (!executor)
|
|
11
8
|
return '';
|
|
12
9
|
if (!looksLikeLlmSetupFailure(message))
|
|
13
10
|
return '';
|
|
14
|
-
|
|
11
|
+
const family = executorFamily(executor);
|
|
12
|
+
if (family === 'claude') {
|
|
15
13
|
return tCli('cli.gen.claude_auth_hint', lang, {
|
|
16
14
|
codexFlags: codexExecutorFlags(env),
|
|
17
15
|
codexModelHint: codexModelHint(lang, env),
|
|
18
16
|
openaiFlags: '--executor openai-api --model <openai-model>',
|
|
19
17
|
});
|
|
20
18
|
}
|
|
21
|
-
if (
|
|
19
|
+
if (family === 'codex') {
|
|
22
20
|
if (looksLikeModelUnavailableFailure(message)) {
|
|
23
21
|
return tCli('cli.gen.codex_model_hint', lang, {
|
|
24
22
|
codexFlags: codexExecutorFlags(env),
|
|
@@ -33,7 +31,7 @@ export function formatSampleGenerationFailureHint(message, executorName, lang, e
|
|
|
33
31
|
openaiFlags: '--executor openai-api --model <openai-model>',
|
|
34
32
|
});
|
|
35
33
|
}
|
|
36
|
-
if (
|
|
34
|
+
if (family === 'openai-api') {
|
|
37
35
|
if (looksLikeModelUnavailableFailure(message)) {
|
|
38
36
|
return tCli('cli.gen.openai_api_model_hint', lang, {
|
|
39
37
|
claudeFlags: '--executor claude --model sonnet',
|
|
@@ -47,7 +45,7 @@ export function formatSampleGenerationFailureHint(message, executorName, lang, e
|
|
|
47
45
|
codexModelHint: codexModelHint(lang, env),
|
|
48
46
|
});
|
|
49
47
|
}
|
|
50
|
-
if (
|
|
48
|
+
if (family === 'anthropic-api') {
|
|
51
49
|
if (looksLikeModelUnavailableFailure(message)) {
|
|
52
50
|
return tCli('cli.gen.anthropic_api_model_hint', lang, {
|
|
53
51
|
claudeFlags: '--executor claude --model sonnet',
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { accessSync, constants } from 'node:fs';
|
|
2
2
|
import { delimiter, join } from 'node:path';
|
|
3
3
|
import { getCodexModelSuggestion } from './codex-model-hint.js';
|
|
4
|
-
import {
|
|
4
|
+
import { executorFamily } from '../../executors/core/registry.js';
|
|
5
|
+
// Claude 的模型 alias 只属于 CLI runtime 选择策略,不是执行器共享默认值。
|
|
6
|
+
export const DEFAULT_CLAUDE_MODEL = 'sonnet';
|
|
7
|
+
export const DEFAULT_CLAUDE_JUDGE_MODEL = 'haiku';
|
|
5
8
|
function nonEmpty(value) {
|
|
6
9
|
const trimmed = value?.trim();
|
|
7
10
|
return trimmed ? trimmed : undefined;
|
|
@@ -27,7 +30,7 @@ export function isCodexHost(env = process.env) {
|
|
|
27
30
|
|| nonEmpty(env.CODEX_CI));
|
|
28
31
|
}
|
|
29
32
|
export function isCodexExecutor(executor) {
|
|
30
|
-
return executor === 'codex'
|
|
33
|
+
return executorFamily(executor) === 'codex';
|
|
31
34
|
}
|
|
32
35
|
export function resolveCliExecutor(explicitExecutor, options = {}) {
|
|
33
36
|
const env = options.env ?? process.env;
|
|
@@ -53,7 +56,7 @@ export function resolveCliModel(executor, explicitModel, options = {}) {
|
|
|
53
56
|
if (envModel)
|
|
54
57
|
return envModel;
|
|
55
58
|
if (!isCodexExecutor(executor))
|
|
56
|
-
return
|
|
59
|
+
return DEFAULT_CLAUDE_MODEL;
|
|
57
60
|
const suggestion = getCodexModelSuggestion(env);
|
|
58
61
|
if (suggestion.fromConfig)
|
|
59
62
|
return suggestion.model;
|
|
@@ -63,7 +66,7 @@ export function resolveCliModel(executor, explicitModel, options = {}) {
|
|
|
63
66
|
: `The Codex executor needs an explicit model. Pass --model <model>, set OMK_MODEL, or configure a top-level model in ${suggestion.configPath}.`);
|
|
64
67
|
}
|
|
65
68
|
export function defaultJudgeModel(executor, taskModel) {
|
|
66
|
-
return isCodexExecutor(executor) ? taskModel :
|
|
69
|
+
return isCodexExecutor(executor) ? taskModel : DEFAULT_CLAUDE_JUDGE_MODEL;
|
|
67
70
|
}
|
|
68
71
|
export function resolveRuntimeSelection(input, options = {}) {
|
|
69
72
|
const executor = resolveCliExecutor(input.executor, options);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ExecutorFn } from '../types/index.js';
|
|
2
|
+
type UnknownRecord = Record<string, unknown>;
|
|
3
|
+
export interface DshSessionLike {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly events: readonly UnknownRecord[];
|
|
6
|
+
readonly header: {
|
|
7
|
+
readonly cwd?: string;
|
|
8
|
+
readonly parentSession?: string;
|
|
9
|
+
readonly agentPreset?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface DshAgentLike {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly options: {
|
|
15
|
+
readonly provider?: string;
|
|
16
|
+
readonly model?: string;
|
|
17
|
+
};
|
|
18
|
+
readonly ctx: object;
|
|
19
|
+
readonly session: DshSessionLike;
|
|
20
|
+
followup(message: UnknownRecord): void;
|
|
21
|
+
whenIdle(): Promise<void>;
|
|
22
|
+
/** `kind` mirrors DSH's host-owned cancellation protocol. */
|
|
23
|
+
cancel(cause: Readonly<Record<'kind', 'hook'> & {
|
|
24
|
+
reason: string;
|
|
25
|
+
}>): void;
|
|
26
|
+
}
|
|
27
|
+
interface DshAgentScopeLike {
|
|
28
|
+
readonly agent?: DshAgentLike;
|
|
29
|
+
readonly systemPrompt: {
|
|
30
|
+
section(section: {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly order: number;
|
|
33
|
+
readonly text: string;
|
|
34
|
+
readonly complete: true;
|
|
35
|
+
}): () => void;
|
|
36
|
+
suppressRuntimeContext(): () => void;
|
|
37
|
+
};
|
|
38
|
+
readonly tools?: {
|
|
39
|
+
get(name: string, agent?: DshAgentLike): unknown;
|
|
40
|
+
restrict(filter: {
|
|
41
|
+
readonly deny: readonly string[];
|
|
42
|
+
}): () => void;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
interface DshAgentPresetsLike {
|
|
46
|
+
composedPreset(agentCtx: object): string | undefined;
|
|
47
|
+
composeFrom(agentCtx: object, parentCtx: object): string | undefined;
|
|
48
|
+
}
|
|
49
|
+
interface DshAgentHandleLike {
|
|
50
|
+
readonly agent: DshAgentLike;
|
|
51
|
+
dispose(): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
/** Minimal same-process DSH surface consumed by the OMK host adapter. */
|
|
54
|
+
export interface DshHostContextLike {
|
|
55
|
+
readonly agents: {
|
|
56
|
+
create(options: {
|
|
57
|
+
readonly sessionId: string;
|
|
58
|
+
readonly meta: {
|
|
59
|
+
readonly cwd: string;
|
|
60
|
+
readonly parentSession?: string;
|
|
61
|
+
readonly agentPreset?: string;
|
|
62
|
+
};
|
|
63
|
+
readonly agentOptions: {
|
|
64
|
+
readonly provider?: string;
|
|
65
|
+
readonly model: string;
|
|
66
|
+
};
|
|
67
|
+
readonly setup: (ctx: DshAgentScopeLike) => void;
|
|
68
|
+
}): Promise<DshAgentHandleLike>;
|
|
69
|
+
};
|
|
70
|
+
readonly tools?: {
|
|
71
|
+
schemas(scope?: DshAgentLike): readonly UnknownRecord[];
|
|
72
|
+
};
|
|
73
|
+
readonly agentPresets: DshAgentPresetsLike;
|
|
74
|
+
on(event: 'session/event', listener: (session: DshSessionLike, entry: UnknownRecord) => void): () => void;
|
|
75
|
+
on(event: 'session/created', listener: (session: DshSessionLike) => void): () => void;
|
|
76
|
+
}
|
|
77
|
+
export interface DshHostExecutorOptions {
|
|
78
|
+
/** Interactive DSH session that initiated the measurement. */
|
|
79
|
+
parentAgent?: DshAgentLike;
|
|
80
|
+
/** Provider inherited by fresh measurement sessions. */
|
|
81
|
+
provider?: string;
|
|
82
|
+
/** Cancellation owned by the DSH command or embedding surface. */
|
|
83
|
+
signal?: AbortSignal;
|
|
84
|
+
/** Maximum time spent waiting for cancellation or disposal to settle. */
|
|
85
|
+
cleanupTimeoutMs?: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build an OMK executor that creates fresh agents inside an already-running
|
|
89
|
+
* DSH plugin tree. The host keeps ownership of credentials, tools, policies,
|
|
90
|
+
* and persistence; OMK owns sample isolation and result projection.
|
|
91
|
+
*/
|
|
92
|
+
export declare function createDshHostExecutor(ctx: DshHostContextLike, options?: DshHostExecutorOptions): ExecutorFn;
|
|
93
|
+
export {};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { buildDshHostResult } from './protocol.js';
|
|
3
|
+
import { createDshHostRuntimeFingerprint } from '../executors/core/runtime-fingerprint.js';
|
|
4
|
+
import { DEFAULT_TIMEOUT_MS } from '../executors/core/limits.js';
|
|
5
|
+
const DEFAULT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
6
|
+
function errorMessage(error) {
|
|
7
|
+
return error instanceof Error ? error.message : String(error);
|
|
8
|
+
}
|
|
9
|
+
function textFromContent(value) {
|
|
10
|
+
if (typeof value === 'string')
|
|
11
|
+
return value;
|
|
12
|
+
if (!Array.isArray(value))
|
|
13
|
+
return '';
|
|
14
|
+
return value.flatMap((block) => {
|
|
15
|
+
if (typeof block !== 'object' || block === null || Array.isArray(block))
|
|
16
|
+
return [];
|
|
17
|
+
const record = block;
|
|
18
|
+
return record.type === 'text' && typeof record.text === 'string' ? [record.text] : [];
|
|
19
|
+
}).join('');
|
|
20
|
+
}
|
|
21
|
+
function lastAssistantText(events) {
|
|
22
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
23
|
+
const event = events[index];
|
|
24
|
+
if (event?.type !== 'assistant/message')
|
|
25
|
+
continue;
|
|
26
|
+
const data = typeof event.data === 'object' && event.data !== null && !Array.isArray(event.data)
|
|
27
|
+
? event.data
|
|
28
|
+
: undefined;
|
|
29
|
+
const message = typeof data?.message === 'object' && data.message !== null && !Array.isArray(data.message)
|
|
30
|
+
? data.message
|
|
31
|
+
: undefined;
|
|
32
|
+
const text = textFromContent(message?.content);
|
|
33
|
+
if (text.length > 0)
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
function failureResult(startedAt, error) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
output: null,
|
|
42
|
+
durationMs: Date.now() - startedAt,
|
|
43
|
+
durationApiMs: Date.now() - startedAt,
|
|
44
|
+
inputTokens: 0,
|
|
45
|
+
outputTokens: 0,
|
|
46
|
+
cacheReadTokens: 0,
|
|
47
|
+
cacheCreationTokens: 0,
|
|
48
|
+
tokenUsageReportedByExecutor: false,
|
|
49
|
+
costUSD: 0,
|
|
50
|
+
costReportedByExecutor: false,
|
|
51
|
+
stopReason: 'error',
|
|
52
|
+
numTurns: 0,
|
|
53
|
+
error: errorMessage(error),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function createPromptMessage(prompt) {
|
|
57
|
+
// `source.kind` mirrors the host-owned DSH message protocol and must retain its wire name.
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
id: randomUUID(),
|
|
60
|
+
role: 'user',
|
|
61
|
+
content: [Object.freeze({ type: 'text', text: prompt })],
|
|
62
|
+
source: Object.freeze({ kind: 'user' }),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function settleWithin(promise, timeoutMs) {
|
|
66
|
+
let timer;
|
|
67
|
+
const settled = await Promise.race([
|
|
68
|
+
promise.then(() => true),
|
|
69
|
+
new Promise((resolve) => {
|
|
70
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
71
|
+
}),
|
|
72
|
+
]);
|
|
73
|
+
if (timer !== undefined)
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
return settled;
|
|
76
|
+
}
|
|
77
|
+
async function waitForIdle(agent, timeoutMs, signal, cleanupTimeoutMs) {
|
|
78
|
+
if (signal?.aborted) {
|
|
79
|
+
agent.cancel({ kind: 'hook', reason: 'OMK evaluation command was aborted' });
|
|
80
|
+
const settled = await settleWithin(agent.whenIdle(), cleanupTimeoutMs);
|
|
81
|
+
return { outcome: 'aborted', cleanupTimedOut: !settled };
|
|
82
|
+
}
|
|
83
|
+
let timer;
|
|
84
|
+
let removeAbortListener;
|
|
85
|
+
const outcome = await Promise.race([
|
|
86
|
+
agent.whenIdle().then(() => 'idle'),
|
|
87
|
+
new Promise((resolve) => {
|
|
88
|
+
timer = setTimeout(() => resolve('timeout'), timeoutMs);
|
|
89
|
+
}),
|
|
90
|
+
...(signal === undefined ? [] : [new Promise((resolve) => {
|
|
91
|
+
const onAbort = () => resolve('aborted');
|
|
92
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
93
|
+
removeAbortListener = () => signal.removeEventListener('abort', onAbort);
|
|
94
|
+
})]),
|
|
95
|
+
]);
|
|
96
|
+
if (timer !== undefined)
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
removeAbortListener?.();
|
|
99
|
+
if (outcome === 'idle')
|
|
100
|
+
return { outcome, cleanupTimedOut: false };
|
|
101
|
+
agent.cancel({
|
|
102
|
+
kind: 'hook',
|
|
103
|
+
reason: outcome === 'timeout'
|
|
104
|
+
? `OMK sample timed out after ${timeoutMs}ms`
|
|
105
|
+
: 'OMK evaluation command was aborted',
|
|
106
|
+
});
|
|
107
|
+
const settled = await settleWithin(agent.whenIdle(), cleanupTimeoutMs);
|
|
108
|
+
return { outcome, cleanupTimedOut: !settled };
|
|
109
|
+
}
|
|
110
|
+
function assertSupportedIsolation(input) {
|
|
111
|
+
if (input.allowedSkills && input.allowedSkills.length > 0) {
|
|
112
|
+
throw new Error('dsh-host executor 不支持非空 skill 白名单;请注入待测 artifact,并使用 allowedSkills: [] 隔离环境 skill。');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build an OMK executor that creates fresh agents inside an already-running
|
|
117
|
+
* DSH plugin tree. The host keeps ownership of credentials, tools, policies,
|
|
118
|
+
* and persistence; OMK owns sample isolation and result projection.
|
|
119
|
+
*/
|
|
120
|
+
export function createDshHostExecutor(ctx, options = {}) {
|
|
121
|
+
const provider = options.provider ?? options.parentAgent?.options.provider;
|
|
122
|
+
const activeAgentPreset = options.parentAgent
|
|
123
|
+
? ctx.agentPresets.composedPreset(options.parentAgent.ctx)
|
|
124
|
+
: undefined;
|
|
125
|
+
// Preserve host schema order because DSH sends this order to the model; it is part of runtime identity.
|
|
126
|
+
const toolSchemas = ctx.tools?.schemas(options.parentAgent)
|
|
127
|
+
.filter((schema) => schema.name !== 'skill');
|
|
128
|
+
const runtimeFingerprint = (model) => (createDshHostRuntimeFingerprint(model, {
|
|
129
|
+
...(provider ? { provider } : {}),
|
|
130
|
+
...(activeAgentPreset
|
|
131
|
+
? { agentPreset: activeAgentPreset }
|
|
132
|
+
: {}),
|
|
133
|
+
...(toolSchemas ? { toolSchemas } : {}),
|
|
134
|
+
}));
|
|
135
|
+
const executor = async (input) => {
|
|
136
|
+
const startedAt = Date.now();
|
|
137
|
+
let handle;
|
|
138
|
+
const rootSessionId = `omk-${randomUUID()}`;
|
|
139
|
+
const descendants = new Set();
|
|
140
|
+
const orderedEvents = [];
|
|
141
|
+
const disposeCreated = ctx.on('session/created', (session) => {
|
|
142
|
+
const parent = session.header.parentSession;
|
|
143
|
+
if (parent !== rootSessionId && (parent === undefined || !descendants.has(parent)))
|
|
144
|
+
return;
|
|
145
|
+
descendants.add(String(session.id));
|
|
146
|
+
});
|
|
147
|
+
const disposeEvents = ctx.on('session/event', (session, event) => {
|
|
148
|
+
const sessionId = String(session.id);
|
|
149
|
+
if (sessionId === rootSessionId) {
|
|
150
|
+
orderedEvents.push({ sessionId, event, traceRole: 'main' });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (descendants.has(sessionId)) {
|
|
154
|
+
orderedEvents.push({ sessionId, event, traceRole: 'subagent' });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
try {
|
|
158
|
+
assertSupportedIsolation(input);
|
|
159
|
+
const cwd = input.cwd ?? process.cwd();
|
|
160
|
+
handle = await ctx.agents.create({
|
|
161
|
+
sessionId: rootSessionId,
|
|
162
|
+
meta: {
|
|
163
|
+
cwd,
|
|
164
|
+
...(options.parentAgent ? { parentSession: String(options.parentAgent.id) } : {}),
|
|
165
|
+
...(activeAgentPreset ? { agentPreset: activeAgentPreset } : {}),
|
|
166
|
+
},
|
|
167
|
+
agentOptions: {
|
|
168
|
+
...(provider ? { provider } : {}),
|
|
169
|
+
model: input.model,
|
|
170
|
+
},
|
|
171
|
+
setup(agentCtx) {
|
|
172
|
+
if (options.parentAgent) {
|
|
173
|
+
const composedPreset = ctx.agentPresets.composeFrom(agentCtx, options.parentAgent.ctx);
|
|
174
|
+
if (composedPreset !== activeAgentPreset) {
|
|
175
|
+
throw new Error(`DSH agent preset changed during measurement setup: ${activeAgentPreset ?? 'none'} -> ${composedPreset ?? 'none'}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
agentCtx.systemPrompt.section({
|
|
179
|
+
name: 'omk:evaluation',
|
|
180
|
+
order: 0,
|
|
181
|
+
text: input.system ?? '',
|
|
182
|
+
complete: true,
|
|
183
|
+
});
|
|
184
|
+
agentCtx.systemPrompt.suppressRuntimeContext();
|
|
185
|
+
const scopedAgent = agentCtx.agent;
|
|
186
|
+
if (agentCtx.tools?.get('skill', scopedAgent) !== undefined) {
|
|
187
|
+
agentCtx.tools.restrict({ deny: ['skill'] });
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
handle.agent.followup(createPromptMessage(input.prompt));
|
|
192
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
193
|
+
const cleanupTimeoutMs = options.cleanupTimeoutMs ?? DEFAULT_CLEANUP_TIMEOUT_MS;
|
|
194
|
+
const { outcome, cleanupTimedOut } = await waitForIdle(handle.agent, timeoutMs, options.signal, cleanupTimeoutMs);
|
|
195
|
+
const wallClockDurationMs = Date.now() - startedAt;
|
|
196
|
+
const events = [...handle.agent.session.events];
|
|
197
|
+
const result = buildDshHostResult({
|
|
198
|
+
rootSessionId,
|
|
199
|
+
finalResponse: lastAssistantText(events),
|
|
200
|
+
events: orderedEvents,
|
|
201
|
+
childSessionIds: [...descendants],
|
|
202
|
+
}, wallClockDurationMs);
|
|
203
|
+
if (outcome === 'idle')
|
|
204
|
+
return result;
|
|
205
|
+
return {
|
|
206
|
+
...result,
|
|
207
|
+
ok: false,
|
|
208
|
+
stopReason: outcome,
|
|
209
|
+
error: outcome === 'timeout'
|
|
210
|
+
? `dsh-host execution timed out after ${timeoutMs}ms${cleanupTimedOut ? `; cancellation did not settle within ${cleanupTimeoutMs}ms` : ''}`
|
|
211
|
+
: `dsh-host execution aborted by its DSH command${cleanupTimedOut ? `; cancellation did not settle within ${cleanupTimeoutMs}ms` : ''}`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return failureResult(startedAt, error);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
disposeEvents();
|
|
219
|
+
disposeCreated();
|
|
220
|
+
if (handle !== undefined) {
|
|
221
|
+
const disposal = handle.dispose().catch((error) => {
|
|
222
|
+
process.stderr.write(`[dsh-host] 评测 session 关闭失败:${errorMessage(error)}\n`);
|
|
223
|
+
});
|
|
224
|
+
const disposed = await settleWithin(disposal, options.cleanupTimeoutMs ?? DEFAULT_CLEANUP_TIMEOUT_MS);
|
|
225
|
+
if (!disposed) {
|
|
226
|
+
process.stderr.write('[dsh-host] 评测 session 关闭超出清理宽限期,已停止等待。\n');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
return Object.assign(executor, { runtimeFingerprint });
|
|
232
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type DshAgentLike, type DshHostContextLike } from './host-executor.js';
|
|
2
|
+
interface DshCommandInvocationLike {
|
|
3
|
+
readonly agent: DshAgentLike;
|
|
4
|
+
readonly rawInput: string;
|
|
5
|
+
readonly signal: AbortSignal;
|
|
6
|
+
}
|
|
7
|
+
type DshCommandResultLike = Readonly<Record<'kind', 'success'> & {
|
|
8
|
+
text?: string;
|
|
9
|
+
}> | Readonly<Record<'kind', 'error'> & {
|
|
10
|
+
text: string;
|
|
11
|
+
}>;
|
|
12
|
+
interface DshPluginContextLike extends DshHostContextLike {
|
|
13
|
+
readonly commands: {
|
|
14
|
+
register(definition: {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly description: string;
|
|
17
|
+
readonly input: {
|
|
18
|
+
readonly hint: string;
|
|
19
|
+
};
|
|
20
|
+
readonly handler: (invocation: DshCommandInvocationLike) => DshCommandResultLike | Promise<DshCommandResultLike>;
|
|
21
|
+
}): () => void;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export declare const name = "omk-dsh-plugin";
|
|
25
|
+
export declare const inject: string[];
|
|
26
|
+
/** Register `/omk eval <eval.yaml>` in every DSH command-capable surface. */
|
|
27
|
+
export declare function apply(ctx: DshPluginContextLike): void;
|
|
28
|
+
export {};
|