dsh-omni-router 1.2.0 → 1.3.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 +2 -0
- package/README.zh-CN.md +2 -0
- package/package.json +1 -1
- package/src/omni-router.mjs +67 -1
- package/test/omni-router.test.mjs +27 -0
package/README.md
CHANGED
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
- **状态持久化**:分类/思维模式/任务类型写入会话事件,resume 可恢复。
|
|
56
56
|
- **降级保护**:Plan Mode 不可用时自动限制为只读工具。
|
|
57
57
|
- **Hybrid 分类**:启发式置信度 + 低置信度时 LLM 结构化分类。
|
|
58
|
+
- **Policy Engine**:`buildPolicyDecision` 统一输出完整决策对象(taskType/complexity/risk/executionMode/approval/verification/gitPolicy)。
|
|
59
|
+
- **Repository Snapshot**:`buildRepositorySnapshot` 识别包管理器、测试框架、框架、入口点。
|
|
58
60
|
- **Policy/State Orchestration**:用 `workflowPolicy` 状态机代替大量重复 prompt。
|
|
59
61
|
- **Agent 选择**:`selectAgentForTask` 推荐 frontend/backend/db/browser/security/review agent。
|
|
60
62
|
- **Router Benchmark**:内置 `benchmark/run.mjs`(399 个任务),可评估 accuracy / false-direct rate。
|
package/README.zh-CN.md
CHANGED
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
- **状态持久化**:分类/思维模式/任务类型写入会话事件,resume 可恢复。
|
|
56
56
|
- **降级保护**:Plan Mode 不可用时自动限制为只读工具。
|
|
57
57
|
- **Hybrid 分类**:启发式置信度 + 低置信度时 LLM 结构化分类。
|
|
58
|
+
- **Policy Engine**:`buildPolicyDecision` 统一输出完整决策对象(taskType/complexity/risk/executionMode/approval/verification/gitPolicy)。
|
|
59
|
+
- **Repository Snapshot**:`buildRepositorySnapshot` 识别包管理器、测试框架、框架、入口点。
|
|
58
60
|
- **Policy/State Orchestration**:用 `workflowPolicy` 状态机代替大量重复 prompt。
|
|
59
61
|
- **Agent 选择**:`selectAgentForTask` 推荐 frontend/backend/db/browser/security/review agent。
|
|
60
62
|
- **Router Benchmark**:内置 `benchmark/run.mjs`(399 个任务),可评估 accuracy / false-direct rate。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-omni-router",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Omni Router - auto-routing DeepSeek Harness preset: simple tasks run directly, complex tasks plan first with approval gate, coding tasks get TDD/delivery-gate guidance.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
package/src/omni-router.mjs
CHANGED
|
@@ -368,7 +368,7 @@ export function estimateRisk(text) {
|
|
|
368
368
|
if (/(生产环境|production|prod|密钥|secret|token|drop database|drop table|rm -rf)/.test(normalized)) {
|
|
369
369
|
reasons.push('production/secret/destructive')
|
|
370
370
|
}
|
|
371
|
-
if (/(schema|migration|drop table|drop database|连接池|auth|权限|deploy|ci\/cd|配置|config|Webhook
|
|
371
|
+
if (/(schema|migration|drop table|drop database|连接池|auth|权限|deploy|ci\/cd|配置|config|Webhook|验签|签名|对账|网关|退款|扣款|删除.*(数据库|字段|表|生产|配置|auth|用户)|删掉.*(数据库|字段|表|生产|配置|auth|用户))/.test(normalized)) {
|
|
372
372
|
reasons.push('schema/auth/delete/deploy')
|
|
373
373
|
}
|
|
374
374
|
if (/(数据库|db|redis|登录|login|session|token|订单|order|支付|payment|业务逻辑|重构|refactor|api|接口|核心)/.test(normalized)) {
|
|
@@ -426,6 +426,66 @@ export function workflowPolicy(taskType, complexity, risk) {
|
|
|
426
426
|
}
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Policy Engine: unify all routing dimensions into one decision object.
|
|
431
|
+
* This is the "brain first layer" described in the Omni roadmap.
|
|
432
|
+
*/
|
|
433
|
+
export function buildPolicyDecision(taskText, config = {}) {
|
|
434
|
+
const complexity = classifyComplexity(taskText, config)
|
|
435
|
+
const taskType = classifyTaskType(taskText)
|
|
436
|
+
const risk = estimateRisk(taskText).level
|
|
437
|
+
const highRisk = ['high', 'critical'].includes(risk)
|
|
438
|
+
const executionMode = complexity === 'plan' || highRisk ? 'plan' : 'direct'
|
|
439
|
+
const coding = ['bugfix', 'feature', 'refactor', 'test'].includes(taskType)
|
|
440
|
+
return {
|
|
441
|
+
taskType,
|
|
442
|
+
complexity,
|
|
443
|
+
risk,
|
|
444
|
+
reasoningMode: complexity === 'plan' || highRisk ? 'max' : 'balanced',
|
|
445
|
+
contextStrategy: 'dependency-aware',
|
|
446
|
+
executionMode,
|
|
447
|
+
approvalRequired: executionMode === 'plan' || highRisk,
|
|
448
|
+
confidence: heuristicComplexity(taskText, config).confidence,
|
|
449
|
+
verification: coding ? ['unit', 'integration', 'regression'] : [],
|
|
450
|
+
gitPolicy: {
|
|
451
|
+
requireBranch: coding,
|
|
452
|
+
},
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Project Brain first step: build a lightweight repository snapshot from root
|
|
458
|
+
* entries. Later this will be backed by git/ripgrep/tree-sitter.
|
|
459
|
+
*/
|
|
460
|
+
export function buildRepositorySnapshot(entries) {
|
|
461
|
+
const names = new Set((Array.isArray(entries) ? entries : []).map((entry) => entry.name))
|
|
462
|
+
const packageManager = names.has('pnpm-lock.yaml') ? 'pnpm'
|
|
463
|
+
: names.has('package-lock.json') ? 'npm'
|
|
464
|
+
: names.has('yarn.lock') ? 'yarn'
|
|
465
|
+
: names.has('bun.lockb') ? 'bun'
|
|
466
|
+
: null
|
|
467
|
+
const testFramework = names.has('vitest.config.ts') || names.has('vitest.config.js') ? 'vitest'
|
|
468
|
+
: names.has('jest.config.js') || names.has('jest.config.ts') ? 'jest'
|
|
469
|
+
: names.has('cypress.config.ts') || names.has('cypress.config.js') ? 'cypress'
|
|
470
|
+
: names.has('mocha.opts') ? 'mocha'
|
|
471
|
+
: null
|
|
472
|
+
const framework = names.has('next.config.js') || names.has('next.config.mjs') ? 'next'
|
|
473
|
+
: names.has('vite.config.ts') || names.has('vite.config.js') ? 'vite'
|
|
474
|
+
: names.has('angular.json') ? 'angular'
|
|
475
|
+
: names.has('nuxt.config.ts') ? 'nuxt'
|
|
476
|
+
: null
|
|
477
|
+
const entryPoints = ['src', 'lib', 'app', 'api', 'server', 'client', 'test', 'tests']
|
|
478
|
+
.filter((name) => names.has(name))
|
|
479
|
+
return {
|
|
480
|
+
packageManager,
|
|
481
|
+
testFramework,
|
|
482
|
+
framework,
|
|
483
|
+
entryPoints,
|
|
484
|
+
hasReadme: names.has('README.md'),
|
|
485
|
+
hasPackageJson: names.has('package.json'),
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
429
489
|
/**
|
|
430
490
|
* Build a compact project-context summary from root entries and key file
|
|
431
491
|
* contents. This is injected into plan mode so the model starts with context
|
|
@@ -808,6 +868,12 @@ export function apply(ctx, config = {}) {
|
|
|
808
868
|
text: `Risk level: ${state.riskLevel}. High or critical risk requires plan approval before mutation.`,
|
|
809
869
|
})
|
|
810
870
|
}
|
|
871
|
+
const policyDecision = buildPolicyDecision(state.firstText || '', config)
|
|
872
|
+
sections.push({
|
|
873
|
+
name: 'omni-router:policy-decision',
|
|
874
|
+
order: 37,
|
|
875
|
+
text: `Policy decision: ${JSON.stringify(policyDecision)}`,
|
|
876
|
+
})
|
|
811
877
|
const policy = workflowPolicy(taskType, state.kind || 'direct', state.riskLevel || 'low')
|
|
812
878
|
sections.push({
|
|
813
879
|
name: 'omni-router:policy',
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
buildContextGraph,
|
|
7
7
|
buildContextSummary,
|
|
8
8
|
buildDependencyHints,
|
|
9
|
+
buildPolicyDecision,
|
|
10
|
+
buildRepositorySnapshot,
|
|
9
11
|
classifyComplexity,
|
|
10
12
|
classifyTaskType,
|
|
11
13
|
classifyThinkingMode,
|
|
@@ -356,3 +358,28 @@ test('extractSymbolsFromText finds function/class/const names', () => {
|
|
|
356
358
|
assert.ok(symbols.includes('TOKEN_KEY'))
|
|
357
359
|
assert.ok(symbols.includes('refreshSession'))
|
|
358
360
|
})
|
|
361
|
+
|
|
362
|
+
test('buildPolicyDecision returns a unified policy object', () => {
|
|
363
|
+
const policy = buildPolicyDecision('修复退款偶发重复扣款', {})
|
|
364
|
+
assert.equal(policy.taskType, 'bugfix')
|
|
365
|
+
assert.equal(policy.complexity, 'plan')
|
|
366
|
+
assert.equal(policy.risk, 'high')
|
|
367
|
+
assert.equal(policy.executionMode, 'plan')
|
|
368
|
+
assert.equal(policy.approvalRequired, true)
|
|
369
|
+
assert.ok(Array.isArray(policy.verification))
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
test('buildRepositorySnapshot detects project metadata from root entries', () => {
|
|
373
|
+
const entries = [
|
|
374
|
+
{ name: 'package.json', type: 'file' },
|
|
375
|
+
{ name: 'pnpm-lock.yaml', type: 'file' },
|
|
376
|
+
{ name: 'src', type: 'directory' },
|
|
377
|
+
{ name: 'tests', type: 'directory' },
|
|
378
|
+
{ name: 'vitest.config.ts', type: 'file' },
|
|
379
|
+
{ name: 'README.md', type: 'file' },
|
|
380
|
+
]
|
|
381
|
+
const snapshot = buildRepositorySnapshot(entries)
|
|
382
|
+
assert.equal(snapshot.packageManager, 'pnpm')
|
|
383
|
+
assert.equal(snapshot.testFramework, 'vitest')
|
|
384
|
+
assert.ok(snapshot.entryPoints.includes('src'))
|
|
385
|
+
})
|