dsh-omni-router 2.0.0 → 2.2.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 CHANGED
@@ -71,7 +71,9 @@
71
71
  - **Evidence Engine / Write Locks**:`src/evidence-engine.mjs` 统一证据协议与存储;DAG scheduler 通过 `selectReadyBatch` 避免 write overlap 并行冲突。
72
72
  - **Stage 3/5**:Recursive Project Index、contextBudget 接入、Per-task Context、Missing capability handling、OmniBench v2 prompt generator。
73
73
  - **Stage 4(部分)**:`compileDagToWorkflow` 将 Mission DAG 编译为 DSH-native workflow outline。
74
- - **P1/P3 能力**:Real capability sandbox(已接入 `omni_mission_run`,按 role + capability 生成 toolFilter)、Capability Resolver v2(risk/reliability 综合打分)、OmniBench v2 scaffold(manifest schema + runner stub + prepare script + DAG-to-plan compile)。
74
+ - **P1/P3 能力**:Real capability sandbox(已接入 `omni_mission_run`,按 role + capability 生成 toolFilter)、Capability Resolver v2(risk/reliability 综合打分)、OmniBench v2 runner(manifest schema + prepare + prompt generation + `--exec` 本地执行/结果采集 + DAG-to-plan compile)。
75
+ - **2.1 收敛增强**:Mission Resume(`omni_mission_resume` 跨 session 继续 DAG,`runDagLoop` 支持 `onProgress` 周期保存)、Harness evidence extraction(`extractHarnessEvidence` 从 commands/tests/toolCalls/EVIDENCE_JSON 提取机器可验证证据)、context token budget(`maxContextTokens` 真正限制检索输出)、semantic failure retrieval + evidence-backed skill distillation、OmniBench v2 real runner。
76
+ - **2.2 Capability Auto-Provisioning(优化7)**:Capability Auditor(baseline/task-time audit)、Gap Detection、Plugin/Skill Quality Scoring、Minimal Capability Set Solver、Discovery Adapters(marketplace/hub/static registry)、Trusted Auto Provisioning(recommend/auto-trusted/manual)、Post-install probe、Install rollback transaction、Capability performance learning / redundancy detection。
75
77
  - **TaskDecision**:`createTaskDecision` 生成唯一决策对象,Policy/Runtime 统一消费,避免重复 classify 分叉。
76
78
  - **Evidence Protocol**:`src/evidence.mjs` 用结构化 command/file/test/finding 证据判定 PASS/FAIL;`isQaPass` 已支持从 QA 输出解析 JSON evidence,不再只信文本。
77
79
  - **Task Compiler**:`compileTask` 生成 objective / constraints / non-goals / acceptance / hidden assumptions / ambiguities / invariants / risk / artifacts;`compileTaskWithLLM` 可用 LLM 增强。
@@ -153,7 +155,7 @@ Then restart DSH and select **Omni Router** in a new session.
153
155
  - `/omni direct` — enter direct mode.
154
156
  - `/omni mode spec|react|balanced` — set thinking mode.
155
157
  - `/omni reroute plan|direct` — adaptively reroute current task.
156
- - Model tools: `omni_status`, `omni_plan`, `omni_direct`, `omni_mode`, `omni_reroute`, `omni_delegate`, `omni_memory`, `omni_benchmark`, `omni_mission_run`, `omni_visual_check`.
158
+ - Model tools: `omni_status`, `omni_plan`, `omni_direct`, `omni_mode`, `omni_reroute`, `omni_delegate`, `omni_memory`, `omni_benchmark`, `omni_mission_run`, `omni_mission_resume`, `omni_capability_audit`, `omni_capability_provision`, `omni_capability_probe`, `omni_visual_check`.
157
159
 
158
160
  ## Configuration
159
161
 
package/README.zh-CN.md CHANGED
@@ -71,7 +71,9 @@
71
71
  - **Evidence Engine / Write Locks**:`src/evidence-engine.mjs` 统一证据协议与存储;DAG scheduler 通过 `selectReadyBatch` 避免 write overlap 并行冲突。
72
72
  - **Stage 3/5**:Recursive Project Index、contextBudget 接入、Per-task Context、Missing capability handling、OmniBench v2 prompt generator。
73
73
  - **Stage 4(部分)**:`compileDagToWorkflow` 将 Mission DAG 编译为 DSH-native workflow outline。
74
- - **P1/P3 能力**:Real capability sandbox(已接入 `omni_mission_run`,按 role + capability 生成 toolFilter)、Capability Resolver v2(risk/reliability 综合打分)、OmniBench v2 scaffold(manifest schema + runner stub + prepare script + DAG-to-plan compile)。
74
+ - **P1/P3 能力**:Real capability sandbox(已接入 `omni_mission_run`,按 role + capability 生成 toolFilter)、Capability Resolver v2(risk/reliability 综合打分)、OmniBench v2 runner(manifest schema + prepare + prompt generation + `--exec` 本地执行/结果采集 + DAG-to-plan compile)。
75
+ - **2.1 收敛增强**:Mission Resume(`omni_mission_resume` 跨 session 继续 DAG,`runDagLoop` 支持 `onProgress` 周期保存)、Harness evidence extraction(`extractHarnessEvidence` 从 commands/tests/toolCalls/EVIDENCE_JSON 提取机器可验证证据)、context token budget(`maxContextTokens` 真正限制检索输出)、semantic failure retrieval + evidence-backed skill distillation、OmniBench v2 real runner。
76
+ - **2.2 Capability Auto-Provisioning(优化7)**:Capability Auditor(baseline/task-time audit)、Gap Detection、Plugin/Skill Quality Scoring、Minimal Capability Set Solver、Discovery Adapters(marketplace/hub/static registry)、Trusted Auto Provisioning(recommend/auto-trusted/manual)、Post-install probe、Install rollback transaction、Capability performance learning / redundancy detection。
75
77
  - **TaskDecision**:`createTaskDecision` 生成唯一决策对象,Policy/Runtime 统一消费,避免重复 classify 分叉。
76
78
  - **Evidence Protocol**:`src/evidence.mjs` 用结构化 command/file/test/finding 证据判定 PASS/FAIL;`isQaPass` 已支持从 QA 输出解析 JSON evidence,不再只信文本。
77
79
  - **Task Compiler**:`compileTask` 生成 objective / constraints / non-goals / acceptance / hidden assumptions / ambiguities / invariants / risk / artifacts;`compileTaskWithLLM` 可用 LLM 增强。
@@ -153,7 +155,7 @@ node scripts/install-preset.mjs
153
155
  - `/omni direct` — 进入直接执行模式。
154
156
  - `/omni mode spec|react|balanced` — 设置思维模式。
155
157
  - `/omni reroute plan|direct` — 动态切换当前任务路由。
156
- - 模型工具:`omni_status` / `omni_plan` / `omni_direct` / `omni_mode` / `omni_reroute` / `omni_delegate` / `omni_memory` / `omni_benchmark` / `omni_mission_run` / `omni_visual_check`。
158
+ - 模型工具:`omni_status` / `omni_plan` / `omni_direct` / `omni_mode` / `omni_reroute` / `omni_delegate` / `omni_memory` / `omni_benchmark` / `omni_mission_run` / `omni_mission_resume` / `omni_capability_audit` / `omni_capability_provision` / `omni_capability_probe` / `omni_visual_check`。
157
159
 
158
160
  ## 配置
159
161
 
@@ -1,8 +1,8 @@
1
- # OmniBench v2 (scaffold)
1
+ # OmniBench v2
2
2
 
3
3
  Goal: move from 10 small fixture tasks to a reproducible multi-repo benchmark.
4
4
 
5
- ## Requirements (from 优化5.md)
5
+ ## Requirements (from 优化5.md / 优化6.md)
6
6
 
7
7
  - 30~100 real GitHub repos
8
8
  - fixed starting commit
@@ -25,18 +25,18 @@ Goal: move from 10 small fixture tasks to a reproducible multi-repo benchmark.
25
25
  "task": "Fix intermittent login timeout under concurrent session refresh.",
26
26
  "acceptance": ["regression test added", "all tests pass"],
27
27
  "hiddenTests": ["tests/hidden/session-concurrent.test.js"],
28
+ "setupCommand": "npm ci",
29
+ "baselineCommand": "npm test",
30
+ "agentCommand": "node path/to/dsh-agent-runner.mjs",
31
+ "verifyCommand": "npm run test:hidden",
28
32
  "timeoutMs": 7200000,
29
33
  "runs": 5
30
34
  }
31
35
  ```
32
36
 
33
- See `manifest.example.json`.
34
-
35
- ## Runner stub
36
-
37
- `node benchmark/omnibench-v2/runner-stub.mjs <manifest.json>` validates the
38
- manifest and prints a run plan. Actual execution will be driven by DSH
39
- subagents in a future integration.
37
+ `setupCommand`, `baselineCommand`, `agentCommand`, and `verifyCommand` are
38
+ optional. `agentCommand` receives the generated prompt as a single quoted
39
+ argument; when present, `--exec` executes it inside the checked-out repo.
40
40
 
41
41
  ## Generate DSH prompts
42
42
 
@@ -46,7 +46,29 @@ npm run omnibench:v2:generate -- benchmark/omnibench-v2/manifest.local.example.j
46
46
 
47
47
  This writes one prompt per repo/arm/run under `benchmark/omnibench-v2/prompts/`.
48
48
  Open each prompt in a DSH Desktop session with the Omni Router preset to
49
- execute, then collect the results.
49
+ execute manually, or wire `agentCommand` to your DSH CLI/runner for automation.
50
+
51
+ ## Run locally (automated)
52
+
53
+ ```bash
54
+ # Plan + prompt generation only (default)
55
+ node benchmark/omnibench-v2/run.mjs benchmark/omnibench-v2/manifest.local.example.json
56
+
57
+ # Execute each run with the manifest's agentCommand
58
+ node benchmark/omnibench-v2/run.mjs benchmark/omnibench-v2/manifest.local.example.json --exec
59
+
60
+ # Override the agent command for every run
61
+ node benchmark/omnibench-v2/run.mjs benchmark/omnibench-v2/manifest.local.example.json --exec --agent-command "node agent.mjs"
62
+ ```
63
+
64
+ The runner clones/checks out each repo, optionally runs setup/baseline/verify
65
+ commands, captures exit codes and output, and writes a timestamped JSON result
66
+ file to `benchmark/omnibench-v2/results/`.
67
+
68
+ ## Runner stub
69
+
70
+ `node benchmark/omnibench-v2/runner-stub.mjs <manifest.json>` still validates
71
+ the manifest and prints the run plan for quick checks.
50
72
 
51
73
  ## Local / offline usage
52
74
 
@@ -57,6 +79,7 @@ existing `Omni group` fixture:
57
79
  ```bash
58
80
  npm run omnibench:v2:prepare -- benchmark/omnibench-v2/manifest.local.example.json
59
81
  npm run omnibench:v2:plan -- benchmark/omnibench-v2/manifest.local.example.json
82
+ npm run omnibench:v2:generate -- benchmark/omnibench-v2/manifest.local.example.json
60
83
  ```
61
84
 
62
85
  `git clone` works with local paths, so no network is required. Replace `repo`
@@ -2,20 +2,24 @@
2
2
  /**
3
3
  * OmniBench v2 runner / prompt generator.
4
4
  *
5
- * Generates per-repo per-arm per-run DSH prompts. Actual execution must happen
6
- * inside DSH Desktop; this script makes that reproducible.
5
+ * Generates per-repo per-arm per-run DSH prompts. With `--exec` and a
6
+ * configured `agentCommand` (or `--agent-command`), it also executes the run
7
+ * locally, runs baseline/verify commands, and writes machine-readable results.
7
8
  *
8
9
  * Usage:
9
- * node benchmark/omnibench-v2/run.mjs <manifest.json>
10
+ * node benchmark/omnibench-v2/run.mjs <manifest.json> # plan + prompts
11
+ * node benchmark/omnibench-v2/run.mjs <manifest.json> --exec # execute agentCommand runs
12
+ * node benchmark/omnibench-v2/run.mjs <manifest.json> --exec --agent-command "node agent.mjs"
10
13
  */
11
14
 
12
15
  import fs from 'node:fs'
13
16
  import path from 'node:path'
14
- import { fileURLToPath } from 'node:url'
17
+ import { execSync } from 'node:child_process'
18
+ import { fileURLToPath, pathToFileURL } from 'node:url'
15
19
 
16
20
  const here = path.dirname(fileURLToPath(import.meta.url))
17
21
 
18
- function buildPrompt(manifest, arm) {
22
+ export function buildPrompt(manifest, arm) {
19
23
  const criteria = (manifest.acceptance || []).map((c) => `- ${c}`).join('\n')
20
24
  const task = `Task:\n${manifest.task}\n\nAcceptance criteria:\n${criteria}`
21
25
  if (arm === 'raw') {
@@ -24,10 +28,54 @@ function buildPrompt(manifest, arm) {
24
28
  return `${task}\n\nYou are using the Omni control plane. Follow engineering methodology, verify with real evidence, and do not claim completion without checks. When done, reply exactly "BENCHMARK: PASS" if you verified all criteria, otherwise "BENCHMARK: FAIL".`
25
29
  }
26
30
 
27
- function main() {
28
- const manifestPath = process.argv[2] || path.join(here, 'manifest.local.example.json')
31
+ export function readManifests(manifestPath) {
29
32
  const manifests = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
30
- const list = Array.isArray(manifests) ? manifests : [manifests]
33
+ return Array.isArray(manifests) ? manifests : [manifests]
34
+ }
35
+
36
+ export function validateManifests(list) {
37
+ const errors = []
38
+ for (const m of list) {
39
+ if (!m.id) errors.push('missing id')
40
+ if (!m.repo) errors.push(`${m.id || '?'}: missing repo`)
41
+ if (!m.commit) errors.push(`${m.id || '?'}: missing commit`)
42
+ if (!m.task) errors.push(`${m.id || '?'}: missing task`)
43
+ if (!Array.isArray(m.acceptance) || !m.acceptance.length) errors.push(`${m.id || '?'}: missing acceptance`)
44
+ if (!m.runs || m.runs < 3) errors.push(`${m.id || '?'}: runs should be >= 3`)
45
+ }
46
+ return errors
47
+ }
48
+
49
+ export function ensureRepo(m) {
50
+ const workDir = path.resolve(process.env.OMNIBENCH_REPOS || path.join(here, 'repos'))
51
+ const dir = path.join(workDir, m.id)
52
+ fs.mkdirSync(workDir, { recursive: true })
53
+ if (!fs.existsSync(path.join(dir, '.git'))) {
54
+ console.log(`cloning ${m.repo} -> ${dir}`)
55
+ execSync(`git clone "${m.repo}" "${dir}"`, { stdio: 'inherit' })
56
+ }
57
+ console.log(`checkout ${m.id} @ ${m.commit}`)
58
+ execSync(`git -C "${dir}" checkout ${m.commit}`, { stdio: 'inherit' })
59
+ return dir
60
+ }
61
+
62
+ export function runCommand(command, cwd, label) {
63
+ if (!command) return { skipped: true }
64
+ const start = Date.now()
65
+ try {
66
+ const output = execSync(command, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })
67
+ return { skipped: false, exitCode: 0, output, durationMs: Date.now() - start }
68
+ } catch (error) {
69
+ return {
70
+ skipped: false,
71
+ exitCode: error.status ?? 1,
72
+ output: String(error.stdout || '') + String(error.stderr || ''),
73
+ durationMs: Date.now() - start,
74
+ }
75
+ }
76
+ }
77
+
78
+ export function generatePrompts(list) {
31
79
  const outDir = path.join(here, 'prompts')
32
80
  fs.mkdirSync(outDir, { recursive: true })
33
81
  let count = 0
@@ -40,8 +88,115 @@ function main() {
40
88
  }
41
89
  }
42
90
  }
43
- console.log(`Generated ${count} DSH prompts in ${outDir}`)
44
- console.log('Run each prompt inside a DSH Desktop session with the Omni Router preset, then collect results.')
91
+ return count
92
+ }
93
+
94
+ export function writeResults(results, resultsDir) {
95
+ fs.mkdirSync(resultsDir, { recursive: true })
96
+ const file = path.join(resultsDir, `omnibench-v2-${Date.now()}.json`)
97
+ fs.writeFileSync(file, JSON.stringify(results, null, 2), 'utf8')
98
+ return file
99
+ }
100
+
101
+ function main() {
102
+ const manifestPath = process.argv[2] || path.join(here, 'manifest.local.example.json')
103
+ const execMode = process.argv.includes('--exec')
104
+ const agentCommandOverride = (() => {
105
+ const idx = process.argv.indexOf('--agent-command')
106
+ return idx !== -1 ? process.argv[idx + 1] : null
107
+ })()
108
+ const resultsDir = path.resolve(process.env.OMNIBENCH_RESULTS || path.join(here, 'results'))
109
+
110
+ const list = readManifests(manifestPath)
111
+ const errors = validateManifests(list)
112
+ if (errors.length) {
113
+ console.error('Invalid manifest:')
114
+ for (const e of errors) console.error(`- ${e}`)
115
+ process.exit(1)
116
+ }
117
+ const promptCount = generatePrompts(list)
118
+
119
+ let totalRuns = 0
120
+ for (const m of list) totalRuns += (m.runs || 1) * 2
121
+ console.log(`Generated ${promptCount} DSH prompts in ${path.join(here, 'prompts')}`)
122
+ console.log(`OmniBench v2 run plan (${totalRuns} agent runs)`)
123
+
124
+ if (!execMode) {
125
+ for (const m of list) {
126
+ console.log(`- ${m.id}: ${m.repo} @ ${m.commit} (${m.language}/${m.framework}) runs=${m.runs} -> raw ${m.runs} + omni ${m.runs}`)
127
+ }
128
+ console.log('Pass --exec and configure agentCommand to execute runs locally.')
129
+ return
130
+ }
131
+
132
+ const results = []
133
+ for (const m of list) {
134
+ const repoDir = ensureRepo(m)
135
+ for (const arm of ['raw', 'omni']) {
136
+ for (let i = 1; i <= (m.runs || 3); i++) {
137
+ const agentCommand = agentCommandOverride || m.agentCommand
138
+ const promptFile = path.join(here, 'prompts', `${m.id}-${arm}-${i}.txt`)
139
+ const prompt = fs.readFileSync(promptFile, 'utf8')
140
+ console.log(`\n=== ${m.id} ${arm} run ${i} ===`)
141
+ if (m.setupCommand) {
142
+ const setup = runCommand(m.setupCommand, repoDir, 'setup')
143
+ console.log(`setup exit=${setup.exitCode ?? 'skip'}`)
144
+ }
145
+ const baseline = m.baselineCommand ? runCommand(m.baselineCommand, repoDir, 'baseline') : null
146
+ if (baseline) console.log(`baseline exit=${baseline.exitCode ?? 'skip'}`)
147
+
148
+ let agent = null
149
+ if (agentCommand) {
150
+ const command = `${agentCommand} ${JSON.stringify(prompt)}`
151
+ agent = runCommand(command, repoDir, 'agent')
152
+ console.log(`agent exit=${agent.exitCode ?? 'skip'} duration=${agent.durationMs ?? 0}ms`)
153
+ } else {
154
+ console.log(`agentCommand not configured; run prompt manually in DSH Desktop:\n ${promptFile}`)
155
+ }
156
+
157
+ const verify = m.verifyCommand ? runCommand(m.verifyCommand, repoDir, 'verify') : null
158
+ if (verify) console.log(`verify exit=${verify.exitCode ?? 'skip'}`)
159
+
160
+ let success = null
161
+ if (verify && verify.skipped !== true) {
162
+ success = verify.exitCode === 0
163
+ } else if (agent && agent.skipped !== true) {
164
+ success = agent.exitCode === 0 && /BENCHMARK:\s*PASS/i.test(agent.output || '')
165
+ }
166
+
167
+ results.push({
168
+ id: m.id,
169
+ arm,
170
+ run: i,
171
+ repo: m.repo,
172
+ commit: m.commit,
173
+ task: m.task,
174
+ success,
175
+ agentExitCode: agent?.exitCode ?? null,
176
+ verifyExitCode: verify?.exitCode ?? null,
177
+ durationMs: (agent?.durationMs || 0) + (verify?.durationMs || 0),
178
+ baselineExitCode: baseline?.exitCode ?? null,
179
+ agentOutput: agent?.output?.slice(0, 4000) || '',
180
+ verifyOutput: verify?.output?.slice(0, 4000) || '',
181
+ metrics: {
182
+ tokens: 0,
183
+ cost: 0,
184
+ toolCalls: 0,
185
+ },
186
+ promptFile,
187
+ ranAt: new Date().toISOString(),
188
+ })
189
+ }
190
+ }
191
+ }
192
+
193
+ const file = writeResults(results, resultsDir)
194
+ const executed = results.filter((r) => r.success !== null).length
195
+ const passed = results.filter((r) => r.success === true).length
196
+ console.log(`\nWrote ${results.length} result record(s) to ${file}`)
197
+ console.log(`Executed ${executed}/${results.length} runs; passed ${passed}.`)
45
198
  }
46
199
 
47
- main()
200
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
201
+ main()
202
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-omni-router",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Omni - a reliability and orchestration control plane for DeepSeek Harness, designed to turn fast models into dependable long-horizon software-engineering agents.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -41,7 +41,7 @@
41
41
  }
42
42
  },
43
43
  "scripts": {
44
- "test": "node test/omni-router.test.mjs && node test/agent-chain.test.mjs && node test/skill-suggest.test.mjs && node test/methodology.test.mjs && node test/compat.test.mjs && node test/project-brain.test.mjs && node test/mission-planner.test.mjs && node test/agent-runtime.test.mjs && node test/judge.test.mjs && node test/memory.test.mjs && node test/memory-v3.test.mjs && node test/memory-engine.test.mjs && node test/mission-resume.test.mjs && node test/engineering-benchmark.test.mjs && node test/benchmark-runner.test.mjs && node test/benchmark-results.test.mjs && node test/continuous-eval.test.mjs && node --no-warnings test/project-brain-v2.test.mjs && node --no-warnings test/project-brain-v3.test.mjs && node test/visual-qa.test.mjs && node test/evidence.test.mjs && node test/evidence-store.test.mjs && node test/evidence-engine.test.mjs && node test/task-decision.test.mjs && node test/task-compiler.test.mjs && node test/capability-brain.test.mjs && node test/capability-manifest.test.mjs && node test/capability-sandbox.test.mjs && node test/planner-dag.test.mjs && node test/dynamic-context.test.mjs && node test/mission-dag.test.mjs && node test/failure-taxonomy.test.mjs && node test/context-expansion.test.mjs && node test/hybrid-retrieval.test.mjs && node test/dependency-graph.test.mjs && node test/ast-provider.test.mjs && node test/experience-router.test.mjs && node test/benchmark-tasks.test.mjs && node test/bundle-installer.test.mjs",
44
+ "test": "node test/omni-router.test.mjs && node test/agent-chain.test.mjs && node test/skill-suggest.test.mjs && node test/methodology.test.mjs && node test/compat.test.mjs && node test/project-brain.test.mjs && node test/mission-planner.test.mjs && node test/agent-runtime.test.mjs && node test/judge.test.mjs && node test/memory.test.mjs && node test/memory-v3.test.mjs && node test/memory-engine.test.mjs && node test/mission-resume.test.mjs && node test/engineering-benchmark.test.mjs && node test/benchmark-runner.test.mjs && node test/benchmark-results.test.mjs && node test/continuous-eval.test.mjs && node --no-warnings test/project-brain-v2.test.mjs && node --no-warnings test/project-brain-v3.test.mjs && node test/visual-qa.test.mjs && node test/evidence.test.mjs && node test/evidence-store.test.mjs && node test/evidence-engine.test.mjs && node test/task-decision.test.mjs && node test/task-compiler.test.mjs && node test/capability-brain.test.mjs && node test/capability-manifest.test.mjs && node test/capability-sandbox.test.mjs && node test/capability-auditor.test.mjs && node test/capability-quality.test.mjs && node test/capability-solver.test.mjs && node test/capability-provisioner.test.mjs && node test/capability-performance.test.mjs && node test/planner-dag.test.mjs && node test/dynamic-context.test.mjs && node test/mission-dag.test.mjs && node test/failure-taxonomy.test.mjs && node test/context-expansion.test.mjs && node test/hybrid-retrieval.test.mjs && node test/dependency-graph.test.mjs && node test/ast-provider.test.mjs && node test/experience-router.test.mjs && node test/benchmark-tasks.test.mjs && node test/bundle-installer.test.mjs && node test/omnibench-runner.test.mjs",
45
45
  "benchmark": "node benchmark/run.mjs",
46
46
  "benchmark:engineering": "node benchmark/engineering-run.mjs",
47
47
  "benchmark:compare": "node benchmark/compare.mjs",
@@ -50,6 +50,7 @@
50
50
  "omnibench:v2:plan": "node benchmark/omnibench-v2/runner-stub.mjs",
51
51
  "omnibench:v2:prepare": "node benchmark/omnibench-v2/prepare.mjs",
52
52
  "omnibench:v2:generate": "node benchmark/omnibench-v2/run.mjs",
53
+ "omnibench:v2:run": "node benchmark/omnibench-v2/run.mjs --exec",
53
54
  "analyze": "node benchmark/analyze.mjs",
54
55
  "llm-eval": "node benchmark/llm-eval.mjs",
55
56
  "install:preset": "node scripts/install-preset.mjs"
@@ -165,6 +165,7 @@ export async function runDagLoop(dag, {
165
165
  maxRepairs = 0,
166
166
  maxSameActionRetries = 0,
167
167
  budget = {},
168
+ onProgress = null,
168
169
  } = {}) {
169
170
  const b = {
170
171
  maxSteps: maxSteps ?? budget.steps ?? 50,
@@ -231,6 +232,18 @@ export async function runDagLoop(dag, {
231
232
  actions.push({ taskId: task.id, observation })
232
233
  }
233
234
  step += 1
235
+ if (typeof onProgress === 'function') {
236
+ try {
237
+ await onProgress({
238
+ dag: current,
239
+ step,
240
+ actions,
241
+ metrics: { step, replanCount, repairCount, sameActionCount, tokenUsage, cost, toolCalls },
242
+ })
243
+ } catch {
244
+ // Progress callbacks are best-effort; they must never abort the mission.
245
+ }
246
+ }
234
247
  }
235
248
 
236
249
  const done = isMissionDagComplete(current)
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Capability Auditor.
3
+ *
4
+ * Answers "what does the current Harness environment lack?" for both a
5
+ * baseline (first-run / after DSH update) and a specific task. It is the
6
+ * front door of the Capability Auto-Provisioning loop.
7
+ */
8
+
9
+ export const OMNI_CODING_BASELINE = [
10
+ 'repository.read',
11
+ 'repository.search',
12
+ 'source.write',
13
+ 'shell',
14
+ 'test.run',
15
+ 'debugging',
16
+ 'verification',
17
+ 'code.review',
18
+ 'git.workflow',
19
+ ]
20
+
21
+ const SEVERITY = {
22
+ 'repository.read': 'high',
23
+ 'repository.search': 'high',
24
+ 'source.write': 'high',
25
+ 'shell': 'high',
26
+ 'test.run': 'high',
27
+ 'debugging': 'high',
28
+ 'verification': 'high',
29
+ 'code.review': 'medium',
30
+ 'git.workflow': 'medium',
31
+ 'browser.navigation': 'medium',
32
+ 'browser.interaction': 'medium',
33
+ 'browser.screenshot': 'medium',
34
+ 'frontend.validation': 'medium',
35
+ 'github.remote': 'medium',
36
+ 'gitlab.remote': 'medium',
37
+ 'db.inspection': 'low',
38
+ 'container.run': 'low',
39
+ 'security.review': 'medium',
40
+ 'performance.analysis': 'low',
41
+ }
42
+
43
+ export function severityForCapability(capability) {
44
+ return SEVERITY[capability] || 'low'
45
+ }
46
+
47
+ export function auditCapabilities(brain, required = []) {
48
+ const availableSet = new Set()
49
+ for (const provider of brain?.capabilities || []) {
50
+ for (const cap of provider.capabilities || []) availableSet.add(cap)
51
+ }
52
+ const requiredList = [...new Set(required.map((c) => String(c).trim()).filter(Boolean))]
53
+ const available = requiredList.filter((c) => availableSet.has(c))
54
+ const missing = requiredList.filter((c) => !availableSet.has(c))
55
+ return {
56
+ required: requiredList,
57
+ available,
58
+ missing,
59
+ coverage: requiredList.length ? Math.round((available.length / requiredList.length) * 1000) / 1000 : 1,
60
+ gaps: missing.map((capability) => ({
61
+ capability,
62
+ severity: severityForCapability(capability),
63
+ })),
64
+ }
65
+ }
66
+
67
+ export function baselineAudit(brain, baseline = OMNI_CODING_BASELINE) {
68
+ return auditCapabilities(brain, baseline)
69
+ }
70
+
71
+ export function taskTimeAudit(brain, requirements = []) {
72
+ return auditCapabilities(brain, requirements)
73
+ }
74
+
75
+ export function formatCapabilityAudit(audit = {}) {
76
+ const lines = [
77
+ `Capability audit: ${audit.available?.length || 0}/${audit.required?.length || 0} available (coverage ${audit.coverage ?? 0})`,
78
+ ]
79
+ if (audit.required?.length) lines.push(`Required: ${audit.required.join(', ')}`)
80
+ if (audit.available?.length) lines.push(`Available: ${audit.available.join(', ')}`)
81
+ if (audit.missing?.length) {
82
+ lines.push(`Missing (${audit.missing.length}):`)
83
+ for (const gap of audit.gaps || []) {
84
+ lines.push(`- ${gap.capability} [${gap.severity}]`)
85
+ }
86
+ } else {
87
+ lines.push('No missing capabilities.')
88
+ }
89
+ return lines.join('\n')
90
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Capability Performance Learning.
3
+ *
4
+ * Tracks whether a provisioned plugin/skill actually improves task success,
5
+ * reduces false completion, or only adds token/catalog overhead. This feeds
6
+ * demotion/removal decisions and benchmark plugin-contribution analysis.
7
+ */
8
+
9
+ export function createPerformanceRegistry(initial = {}) {
10
+ return {
11
+ providers: { ...(initial.providers || {}) },
12
+ }
13
+ }
14
+
15
+ export function recordProvisionOutcome(registry, providerId, outcome = {}) {
16
+ const previous = registry.providers[providerId] || {
17
+ runs: 0,
18
+ successBefore: null,
19
+ successAfter: null,
20
+ falseCompletionBefore: null,
21
+ falseCompletionAfter: null,
22
+ tokensBefore: null,
23
+ tokensAfter: null,
24
+ toolErrorsBefore: null,
25
+ toolErrorsAfter: null,
26
+ }
27
+ const next = {
28
+ ...previous,
29
+ runs: (previous.runs || 0) + 1,
30
+ successBefore: outcome.successBefore ?? previous.successBefore,
31
+ successAfter: outcome.successAfter ?? previous.successAfter,
32
+ falseCompletionBefore: outcome.falseCompletionBefore ?? previous.falseCompletionBefore,
33
+ falseCompletionAfter: outcome.falseCompletionAfter ?? previous.falseCompletionAfter,
34
+ tokensBefore: outcome.tokensBefore ?? previous.tokensBefore,
35
+ tokensAfter: outcome.tokensAfter ?? previous.tokensAfter,
36
+ toolErrorsBefore: outcome.toolErrorsBefore ?? previous.toolErrorsBefore,
37
+ toolErrorsAfter: outcome.toolErrorsAfter ?? previous.toolErrorsAfter,
38
+ lastUpdated: new Date().toISOString(),
39
+ }
40
+ return {
41
+ ...registry,
42
+ providers: {
43
+ ...registry.providers,
44
+ [providerId]: next,
45
+ },
46
+ }
47
+ }
48
+
49
+ export function evaluateProviderValue(record = {}) {
50
+ const hasBefore = record.successBefore !== null && record.successBefore !== undefined
51
+ const hasAfter = record.successAfter !== null && record.successAfter !== undefined
52
+ if (!hasBefore || !hasAfter) return { value: 0, label: 'unknown' }
53
+
54
+ const successDelta = record.successAfter - record.successBefore
55
+ const falseCompletionDelta = (record.falseCompletionBefore ?? 0) - (record.falseCompletionAfter ?? 0)
56
+ const tokenRatio = record.tokensBefore ? Math.max(0, ((record.tokensAfter ?? 0) - record.tokensBefore) / record.tokensBefore) : 0
57
+ const toolErrorDelta = (record.toolErrorsAfter ?? 0) - (record.toolErrorsBefore ?? 0)
58
+
59
+ const value = (
60
+ successDelta * 0.5 +
61
+ falseCompletionDelta * 0.3 -
62
+ tokenRatio * 0.1 -
63
+ Math.max(0, toolErrorDelta) * 0.1
64
+ )
65
+ const rounded = Math.round(value * 1000) / 1000
66
+ let label = 'neutral'
67
+ if (rounded >= 0.05) label = 'high'
68
+ else if (rounded > 0) label = 'medium'
69
+ else if (rounded < 0) label = 'negative'
70
+ return { value: rounded, label, successDelta, falseCompletionDelta, tokenRatio, toolErrorDelta }
71
+ }
72
+
73
+ export function recommendDemotion(registry, providerId, options = {}) {
74
+ const record = registry.providers[providerId]
75
+ if (!record) return { providerId, recommendation: 'unknown', reason: 'no performance record' }
76
+ const value = evaluateProviderValue(record)
77
+ const usageDays = Number(options.usageDays ?? 0)
78
+ const uniqueCapabilities = Number(options.uniqueCapabilities ?? 0)
79
+ const coveredBy = options.coveredBy || []
80
+
81
+ if (value.label === 'negative' && usageDays >= 30 && uniqueCapabilities === 0) {
82
+ return { providerId, recommendation: 'remove', reason: 'negative value, no unique capability, low usage', value }
83
+ }
84
+ if (value.label === 'negative') {
85
+ return { providerId, recommendation: 'demote', reason: 'negative measured value', value }
86
+ }
87
+ if (usageDays >= 90 && uniqueCapabilities === 0 && coveredBy.length > 0) {
88
+ return { providerId, recommendation: 'remove', reason: 'redundant and unused for 90+ days', value }
89
+ }
90
+ if (value.label === 'high' || value.label === 'medium') {
91
+ return { providerId, recommendation: 'keep', reason: 'positive measured value', value }
92
+ }
93
+ return { providerId, recommendation: 'watch', reason: 'insufficient signal', value }
94
+ }
95
+
96
+ export function formatPerformanceRegistry(registry = {}) {
97
+ const lines = []
98
+ for (const [id, record] of Object.entries(registry.providers || {})) {
99
+ const value = evaluateProviderValue(record)
100
+ lines.push(`- ${id}: ${value.label} (${value.value}) runs=${record.runs || 0}`)
101
+ }
102
+ return lines.length ? lines.join('\n') : '(empty performance registry)'
103
+ }