oh-my-knowledge 0.52.3 → 0.54.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 (106) hide show
  1. package/README.md +21 -1
  2. package/README.zh.md +21 -1
  3. package/dist/assets/agent-skills/omk/SKILL.md +6 -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 +30 -0
  16. package/dist/dsh-plugin/index.js +275 -0
  17. package/dist/dsh-plugin/observe.d.ts +47 -0
  18. package/dist/dsh-plugin/observe.js +359 -0
  19. package/dist/dsh-plugin/protocol.d.ts +21 -0
  20. package/dist/dsh-plugin/protocol.js +229 -0
  21. package/dist/dsh-plugin/trace-adapter.d.ts +48 -0
  22. package/dist/dsh-plugin/trace-adapter.js +590 -0
  23. package/dist/eval-core/comparability.js +3 -0
  24. package/dist/eval-core/evaluation-execution.js +5 -13
  25. package/dist/eval-core/evaluation-reporting.d.ts +7 -4
  26. package/dist/eval-core/evaluation-reporting.js +39 -18
  27. package/dist/eval-core/judge-independence.d.ts +1 -1
  28. package/dist/eval-core/judge-independence.js +1 -1
  29. package/dist/eval-core/report-document.js +6 -0
  30. package/dist/eval-core/resume-compatibility.d.ts +1 -0
  31. package/dist/eval-core/resume-compatibility.js +5 -4
  32. package/dist/eval-workflows/batch-evaluation-workflow.js +1 -1
  33. package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +4 -2
  34. package/dist/eval-workflows/evaluation-pipeline.d.ts +3 -1
  35. package/dist/eval-workflows/evaluation-pipeline.js +7 -3
  36. package/dist/eval-workflows/run-evaluation.d.ts +4 -2
  37. package/dist/eval-workflows/run-evaluation.js +7 -3
  38. package/dist/executors/{anthropic-api.d.ts → anthropic/api.d.ts} +1 -1
  39. package/dist/executors/{anthropic-api.js → anthropic/api.js} +4 -2
  40. package/dist/executors/{claude-cli.d.ts → anthropic/claude/cli.d.ts} +1 -1
  41. package/dist/executors/{claude-cli.js → anthropic/claude/cli.js} +5 -3
  42. package/dist/executors/anthropic/claude/protocol.d.ts +87 -0
  43. package/dist/executors/{claude-protocol.js → anthropic/claude/protocol.js} +6 -6
  44. package/dist/executors/{claude-sdk.d.ts → anthropic/claude/sdk.d.ts} +2 -2
  45. package/dist/executors/{claude-sdk.js → anthropic/claude/sdk.js} +8 -5
  46. package/dist/executors/anthropic/claude/trace.d.ts +9 -0
  47. package/dist/executors/{claude-sdk-trace.js → anthropic/claude/trace.js} +5 -5
  48. package/dist/executors/{capabilities.d.ts → core/capabilities.d.ts} +3 -5
  49. package/dist/executors/{capabilities.js → core/capabilities.js} +4 -11
  50. package/dist/executors/core/http.d.ts +6 -0
  51. package/dist/executors/core/http.js +19 -0
  52. package/dist/executors/core/limits.d.ts +2 -0
  53. package/dist/executors/core/limits.js +2 -0
  54. package/dist/executors/core/optional-dependencies.d.ts +7 -0
  55. package/dist/executors/core/optional-dependencies.js +35 -0
  56. package/dist/executors/core/registry.d.ts +145 -0
  57. package/dist/executors/core/registry.js +127 -0
  58. package/dist/executors/core/runtime-fingerprint.d.ts +13 -0
  59. package/dist/executors/{runtime-fingerprint.js → core/runtime-fingerprint.js} +119 -59
  60. package/dist/executors/core/runtime.d.ts +12 -0
  61. package/dist/executors/core/runtime.js +61 -0
  62. package/dist/executors/core/subprocess.d.ts +44 -0
  63. package/dist/executors/{shared.js → core/subprocess.js} +20 -156
  64. package/dist/executors/index.d.ts +4 -4
  65. package/dist/executors/index.js +26 -15
  66. package/dist/executors/{openai-api.d.ts → openai/api.d.ts} +1 -1
  67. package/dist/executors/{openai-api.js → openai/api.js} +4 -2
  68. package/dist/executors/{codex-cli.d.ts → openai/codex/cli.d.ts} +3 -3
  69. package/dist/executors/{codex-cli.js → openai/codex/cli.js} +5 -3
  70. package/dist/executors/openai/codex/protocol.d.ts +72 -0
  71. package/dist/executors/{codex-protocol.js → openai/codex/protocol.js} +33 -2
  72. package/dist/executors/{codex-sdk.d.ts → openai/codex/sdk.d.ts} +18 -4
  73. package/dist/executors/{codex-sdk.js → openai/codex/sdk.js} +7 -4
  74. package/dist/executors/{codex-cli-trace.d.ts → openai/codex/trace.d.ts} +2 -2
  75. package/dist/executors/{codex-cli-trace.js → openai/codex/trace.js} +4 -4
  76. package/dist/executors/{script.d.ts → script/index.d.ts} +1 -1
  77. package/dist/executors/{script.js → script/index.js} +6 -4
  78. package/dist/grading/judge.d.ts +1 -1
  79. package/dist/grading/judge.js +1 -1
  80. package/dist/observability/conversation-catalog.js +1 -1
  81. package/dist/observability/experience.js +4 -0
  82. package/dist/observability/inbox.d.ts +4 -1
  83. package/dist/observability/inbox.js +7 -2
  84. package/dist/observability/trace-ir.d.ts +6 -3
  85. package/dist/observability/turn-index.js +4 -0
  86. package/dist/renderer/conversation-renderer.js +20 -6
  87. package/dist/renderer/html-renderer.js +30 -11
  88. package/dist/renderer/knowledge-debugger-renderer.js +6 -1
  89. package/dist/renderer/observation-inbox-renderer.js +2 -2
  90. package/dist/renderer/trajectory-live.d.ts +1 -0
  91. package/dist/renderer/trajectory-live.js +5 -2
  92. package/dist/shared/trace-source-kind.js +1 -0
  93. package/dist/types/executor.d.ts +13 -2
  94. package/dist/types/judge.d.ts +2 -2
  95. package/dist/types/observability.d.ts +1 -1
  96. package/dist/types/trace.d.ts +1 -1
  97. package/package.json +25 -5
  98. package/dist/executors/claude-protocol.d.ts +0 -28
  99. package/dist/executors/claude-sdk-trace.d.ts +0 -9
  100. package/dist/executors/codex-protocol.d.ts +0 -24
  101. package/dist/executors/gemini.d.ts +0 -2
  102. package/dist/executors/gemini.js +0 -156
  103. package/dist/executors/runtime-fingerprint.d.ts +0 -6
  104. package/dist/executors/shared.d.ts +0 -226
  105. /package/dist/executors/{script-command.d.ts → script/command.d.ts} +0 -0
  106. /package/dist/executors/{script-command.js → script/command.js} +0 -0
package/README.md CHANGED
@@ -16,6 +16,8 @@ Observe real-world performance, measure version differences, and determine wheth
16
16
 
17
17
  **Same model. Same evaluation samples. Only the knowledge artifact changes.**
18
18
 
19
+ **DeepSeek Harness users:** install OMK as a native bundle, reuse the current profile for controlled evaluations, and open persisted DSH task trajectories in Studio. [Set up the DSH host plugin →](docs/reference/executors.md#deepseek-harness-prefer-the-host-plugin)
20
+
19
21
  ![omk knowledge artifact evaluation flow: doctor / eval / observe / sample / evolve loop](./docs/public/omk-knowledge-flow-en-animated.gif)
20
22
 
21
23
  📖 **Full documentation: [oh-my-knowledge.pages.dev](https://oh-my-knowledge.pages.dev)** (searchable, English / 简体中文)
@@ -159,7 +161,7 @@ RAG-specific evals: see RAGAS (separate niche, complementary to omk). Full compa
159
161
  |---|---|
160
162
  | **One-line verdict** | `omk eval` six-tier verdict + ship recommendation + exit-code routing; HTML pill shares the same rules |
161
163
  | **Six-dim evaluation** | Fact / Behavior / LLM-judge / Cost / Efficiency / Stability shown independently |
162
- | **Multi-executor** | Claude CLI / Claude SDK / Codex CLI / Codex SDK / OpenAI / Gemini / Anthropic API / any custom command |
164
+ | **Multi-executor** | Claude CLI / Claude SDK / Codex CLI / Codex SDK / DeepSeek Harness / OpenAI / Anthropic API / any custom command |
163
165
  | **30+ assertion types** | substring, regex, JSON Schema, ROUGE/BLEU/Levenshtein similarity, agent tool-call assertions, semantic similarity, custom JS |
164
166
  | **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
167
  | **RAG metrics** | `faithfulness` / `answer_relevancy` / `context_recall` — anti-hallucination + answer relevance + context coverage |
@@ -177,6 +179,23 @@ RAG-specific evals: see RAGAS (separate niche, complementary to omk). Full compa
177
179
  | **Traceability** | reports carry CLI version, Node version, artifact version fingerprint, judge prompt hash |
178
180
  | **EN / ZH switch** | one-click language toggle in the HTML report |
179
181
 
182
+ ### Run inside an existing DeepSeek Harness
183
+
184
+ Install OMK as a DSH bundle to reuse the profile's model, credentials, tools, and sandbox:
185
+
186
+ ```bash
187
+ dsh plugin --profile web add oh-my-knowledge
188
+ dsh --profile web
189
+ ```
190
+
191
+ Inside DSH:
192
+
193
+ - `/omk eval eval.yaml` runs every sample in an isolated DSH session while OMK owns the report and statistics;
194
+ - `/omk observe` lists recent terminal sessions;
195
+ - `/omk observe <session-id>` reads a consistent snapshot and returns its Studio Task Trajectory URL.
196
+
197
+ Observe uses the profile's `sessionPersistence` directly, so users do not export or locate JSONL / SQLite files. The first version is offline-only and does not live-follow a session that is still being written. See the [executor guide](docs/reference/executors.md#deepseek-harness-prefer-the-host-plugin) and [observe guide](docs/guides/observe-production.md#inspect-a-task-inside-deepseek-harness).
198
+
180
199
  ## Documentation
181
200
 
182
201
  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 +230,7 @@ The full docs are published at **[oh-my-knowledge.pages.dev](https://oh-my-knowl
211
230
  - Codex: install and authenticate the Codex CLI (`npm i -g @openai/codex`); Codex tasks in the ChatGPT desktop app select it automatically
212
231
  - Claude: install and authenticate [Claude Code](https://claude.ai/code)
213
232
  - API / other executors: configure them as described in [Executors](docs/reference/executors.md)
233
+ - 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
234
 
215
235
  ## Security notice
216
236
 
package/README.zh.md CHANGED
@@ -16,6 +16,8 @@
16
16
 
17
17
  **相同模型,相同评测用例,只改变知识载体。**
18
18
 
19
+ **DeepSeek Harness 用户:** OMK 可作为原生 bundle 安装,复用当前 profile 做受控评测,并在 Studio 打开已持久化的 DSH 任务轨迹。[接入 DSH 宿主插件 →](docs/zh/reference/executors.md#deepseek-harness优先使用宿主插件)
20
+
19
21
  ![omk 知识载体评测流程:doctor / eval / observe / sample / evolve 闭环](./docs/public/omk-knowledge-flow-animated.gif)
20
22
 
21
23
  📖 **完整文档:[oh-my-knowledge.pages.dev/zh](https://oh-my-knowledge.pages.dev/zh/)**(可搜索,可切换英文)
@@ -159,7 +161,7 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
159
161
  |------|------|
160
162
  | **Verdict 一行结论** | `omk eval` 六档判定 + ship 建议 + exit code 路由,与 HTML 报告 verdict pill 共享规则 |
161
163
  | **六维评估** | 事实 / 行为 / LLM 评价 / 成本 / 效率 / 稳定性独立展示 |
162
- | **多执行器** | 支持 Claude CLI / Claude SDK / Codex CLI / Codex SDK / OpenAI / Gemini / Anthropic API 及自定义命令 |
164
+ | **多执行器** | 支持 Claude CLI / Claude SDK / Codex CLI / Codex SDK / DeepSeek Harness / OpenAI / Anthropic API 及自定义命令 |
163
165
  | **30+ 种断言** | 包含子串、正则、JSON Schema、ROUGE/BLEU/Levenshtein 相似度、Agent 工具调用、语义相似度、自定义函数等 |
164
166
  | **统计严谨性** | Bootstrap CI / 长度去偏 / 饱和曲线默认开,Krippendorff α 提供 gold 集即自动计算。[详情 →](docs/zh/explanation/statistical-rigor.md) |
165
167
  | **RAG metrics** | `faithfulness` / `answer_relevancy` / `context_recall` 三 metric — 反幻觉 + 切题度 + context 覆盖 |
@@ -177,6 +179,23 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
177
179
  | **可追溯性** | 报告含 CLI 版本、Node 版本、知识载体版本指纹、judge prompt hash |
178
180
  | **中英切换** | HTML 报告右上角一键切换语言 |
179
181
 
182
+ ### 在已有 DeepSeek Harness 中运行
183
+
184
+ OMK 可以作为 DSH bundle 安装到现有 profile,直接复用其模型、凭证、工具与 sandbox:
185
+
186
+ ```bash
187
+ dsh plugin --profile web add oh-my-knowledge
188
+ dsh --profile web
189
+ ```
190
+
191
+ 进入 DSH 后:
192
+
193
+ - `/omk eval eval.yaml`:每条用例使用独立 DSH session,报告仍由 OMK 生成;
194
+ - `/omk observe`:列出最近已结束的 session;
195
+ - `/omk observe <session-id>`:只读摄取一致快照,并返回 Studio 任务轨迹链接。
196
+
197
+ observe 直接使用 profile 的 `sessionPersistence`,无需导出或定位 JSONL/SQLite 文件;首版不实时跟随正在写入的 session。详见[执行器文档](docs/zh/reference/executors.md#deepseek-harness优先使用宿主插件)与[观测指南](docs/zh/guides/observe-production.md#在-deepseek-harness-中查看任务轨迹)。
198
+
180
199
  ## 文档
181
200
 
182
201
  完整文档已发布到 **[oh-my-knowledge.pages.dev/zh](https://oh-my-knowledge.pages.dev/zh/)** —— 可搜索,可切换英文。重点页面:
@@ -211,6 +230,7 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
211
230
  - Codex:安装并登录 Codex CLI(`npm i -g @openai/codex`);ChatGPT desktop 的 Codex 任务会自动选择它
212
231
  - Claude:安装并登录 [Claude Code](https://claude.ai/code)
213
232
  - API / 其它执行器:按[执行器文档](docs/zh/reference/executors.md)配置
233
+ - 高级 `claude-sdk`/`codex-sdk` 执行器是可选能力,OMK 基础安装不再下载它们。仅在明确选择对应 SDK 时,才在 OMK 所在的本地项目或全局 npm prefix 安装;详见[执行器前置要求](docs/zh/reference/executors.md#前置要求)。
214
234
 
215
235
  ## 安全说明
216
236
 
@@ -27,6 +27,12 @@ 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
+
34
+ 查看真实 DSH 任务轨迹时,先运行 `/omk observe` 列出最近已结束的 session,再运行 `/omk observe <session-id>`。OMK 通过当前 profile 的 `sessionPersistence` 只读摄取一致快照,并返回 Studio 任务轨迹链接;不要求用户导出或定位 JSONL/SQLite 文件。首版不实时跟随正在写入的 session,也不默认选择发起 observe 命令的当前 session。
35
+
30
36
  ## 第二步:理解用户意图
31
37
 
32
38
  根据用户的描述,匹配对应的操作:
@@ -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 / openai-api / gemini / 自定义命令。Codex 任务内自动用 codex;也可用 OMK_EXECUTOR 设置环境偏好。
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
- const CLAUDE_EXECUTORS = new Set(['claude', 'claude-sdk']);
20
- const CODEX_EXECUTORS = new Set(['codex', 'codex-sdk']);
21
- const OPENAI_API_EXECUTORS = new Set(['openai-api']);
22
- const ANTHROPIC_API_EXECUTORS = new Set(['anthropic-api']);
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 / openai-api / gemini / 自定义命令。Codex 任务内自动用 codex;也可用 OMK_EXECUTOR 设置环境偏好。',
563
- en: 'Executor: claude / claude-sdk / codex / codex-sdk / openai-api / gemini / custom. Defaults to codex inside Codex tasks; OMK_EXECUTOR sets an environment preference.',
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
- return resolve(here, '..', '..', 'assets', 'agent-skills', 'omk');
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
- const CLAUDE_SAMPLE_EXECUTORS = new Set(['claude', 'claude-sdk']);
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
- if (CLAUDE_SAMPLE_EXECUTORS.has(executor)) {
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 (CODEX_SAMPLE_EXECUTORS.has(executor)) {
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 (OPENAI_API_SAMPLE_EXECUTORS.has(executor)) {
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 (ANTHROPIC_API_SAMPLE_EXECUTORS.has(executor)) {
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,3 +1,5 @@
1
+ export declare const DEFAULT_CLAUDE_MODEL = "sonnet";
2
+ export declare const DEFAULT_CLAUDE_JUDGE_MODEL = "haiku";
1
3
  export interface RuntimeResolutionOptions {
2
4
  env?: NodeJS.ProcessEnv;
3
5
  commandExists?: (command: string, env: NodeJS.ProcessEnv) => boolean;
@@ -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 { DEFAULT_MODEL, JUDGE_MODEL } from '../../executors/shared.js';
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' || executor === 'codex-sdk';
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 DEFAULT_MODEL;
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 : JUDGE_MODEL;
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,3 @@
1
+ - insert:
2
+ - id: omk
3
+ name: oh-my-knowledge/dist/dsh-plugin/index.js
@@ -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,30 @@
1
+ import { type DshAgentLike, type DshHostContextLike } from './host-executor.js';
2
+ import { type DshSessionPersistenceLike } from './observe.js';
3
+ interface DshCommandInvocationLike {
4
+ readonly agent: DshAgentLike;
5
+ readonly rawInput: string;
6
+ readonly signal: AbortSignal;
7
+ }
8
+ type DshCommandResultLike = Readonly<Record<'kind', 'success'> & {
9
+ text?: string;
10
+ }> | Readonly<Record<'kind', 'error'> & {
11
+ text: string;
12
+ }>;
13
+ interface DshPluginContextLike extends DshHostContextLike {
14
+ get?(name: 'sessionPersistence'): DshSessionPersistenceLike | undefined;
15
+ readonly commands: {
16
+ register(definition: {
17
+ readonly name: string;
18
+ readonly description: string;
19
+ readonly input: {
20
+ readonly hint: string;
21
+ };
22
+ readonly handler: (invocation: DshCommandInvocationLike) => DshCommandResultLike | Promise<DshCommandResultLike>;
23
+ }): () => void;
24
+ };
25
+ }
26
+ export declare const name = "omk-dsh-plugin";
27
+ export declare const inject: string[];
28
+ /** Register `/omk eval` and `/omk observe` in every DSH command-capable surface. */
29
+ export declare function apply(ctx: DshPluginContextLike): () => void;
30
+ export {};