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 +4 -2
- package/README.zh-CN.md +4 -2
- package/benchmark/omnibench-v2/README.md +33 -10
- package/benchmark/omnibench-v2/run.mjs +166 -11
- package/package.json +3 -2
- package/src/agent-runtime.mjs +13 -0
- package/src/capability-auditor.mjs +90 -0
- package/src/capability-performance.mjs +103 -0
- package/src/capability-provisioner.mjs +223 -0
- package/src/capability-quality.mjs +90 -0
- package/src/capability-solver.mjs +88 -0
- package/src/context-expansion.mjs +9 -1
- package/src/dynamic-context.mjs +1 -0
- package/src/evidence.mjs +86 -0
- package/src/memory-v3.mjs +37 -5
- package/src/omni-router.mjs +367 -54
- package/test/agent-runtime.test.mjs +17 -0
- package/test/capability-auditor.test.mjs +43 -0
- package/test/capability-performance.test.mjs +64 -0
- package/test/capability-provisioner.test.mjs +96 -0
- package/test/capability-quality.test.mjs +62 -0
- package/test/capability-solver.test.mjs +51 -0
- package/test/dynamic-context.test.mjs +11 -0
- package/test/evidence.test.mjs +35 -0
- package/test/memory-v3.test.mjs +32 -0
- package/test/omnibench-runner.test.mjs +68 -0
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
|
|
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
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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,
|
|
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.
|
|
6
|
-
*
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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.
|
|
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"
|
package/src/agent-runtime.mjs
CHANGED
|
@@ -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
|
+
}
|