dsh-llm-verifier 0.1.13 → 0.1.16

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 (90) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +328 -301
  3. package/cordis.patch.yml +4 -4
  4. package/lib/caller-BWwSVNZf.js.map +1 -0
  5. package/lib/client.js +176 -14
  6. package/lib/client.js.map +1 -0
  7. package/lib/core.js.map +1 -0
  8. package/lib/index.js +400 -51
  9. package/lib/index.js.map +1 -0
  10. package/lib/types/auto.d.ts.map +1 -0
  11. package/lib/types/auto.js +8 -2
  12. package/lib/types/auto.js.map +1 -0
  13. package/lib/types/cache.d.ts.map +1 -0
  14. package/lib/types/cache.js.map +1 -0
  15. package/lib/types/caller.d.ts.map +1 -0
  16. package/lib/types/caller.js.map +1 -0
  17. package/lib/types/client-i18n.d.ts +27 -0
  18. package/lib/types/client-i18n.d.ts.map +1 -0
  19. package/lib/types/client-i18n.js +18 -0
  20. package/lib/types/client-i18n.js.map +1 -0
  21. package/lib/types/client.d.ts +15 -2
  22. package/lib/types/client.d.ts.map +1 -0
  23. package/lib/types/client.js +137 -10
  24. package/lib/types/client.js.map +1 -0
  25. package/lib/types/config.d.ts +4 -0
  26. package/lib/types/config.d.ts.map +1 -0
  27. package/lib/types/config.js +5 -3
  28. package/lib/types/config.js.map +1 -0
  29. package/lib/types/core.d.ts.map +1 -0
  30. package/lib/types/core.js.map +1 -0
  31. package/lib/types/engine.d.ts.map +1 -0
  32. package/lib/types/engine.js.map +1 -0
  33. package/lib/types/images.d.ts.map +1 -0
  34. package/lib/types/images.js.map +1 -0
  35. package/lib/types/index.d.ts +2 -0
  36. package/lib/types/index.d.ts.map +1 -0
  37. package/lib/types/index.js +162 -9
  38. package/lib/types/index.js.map +1 -0
  39. package/lib/types/plan-gate.d.ts +27 -0
  40. package/lib/types/plan-gate.d.ts.map +1 -0
  41. package/lib/types/plan-gate.js +60 -0
  42. package/lib/types/plan-gate.js.map +1 -0
  43. package/lib/types/router.d.ts +28 -32
  44. package/lib/types/router.d.ts.map +1 -0
  45. package/lib/types/router.js +41 -9
  46. package/lib/types/router.js.map +1 -0
  47. package/lib/types/session.d.ts +11 -0
  48. package/lib/types/session.d.ts.map +1 -0
  49. package/lib/types/session.js +41 -11
  50. package/lib/types/session.js.map +1 -0
  51. package/lib/types/statistics.d.ts.map +1 -0
  52. package/lib/types/statistics.js.map +1 -0
  53. package/lib/types/team-gate.d.ts +20 -0
  54. package/lib/types/team-gate.d.ts.map +1 -0
  55. package/lib/types/team-gate.js +59 -0
  56. package/lib/types/team-gate.js.map +1 -0
  57. package/lib/types/top-logprobs.d.ts.map +1 -0
  58. package/lib/types/top-logprobs.js.map +1 -0
  59. package/lib/types/topic-storage.d.ts.map +1 -0
  60. package/lib/types/topic-storage.js.map +1 -0
  61. package/package.json +114 -112
  62. package/src/auto.test.ts +95 -86
  63. package/src/auto.ts +140 -133
  64. package/src/cache.ts +115 -115
  65. package/src/caller.test.ts +34 -34
  66. package/src/caller.ts +151 -151
  67. package/src/client-i18n.ts +18 -0
  68. package/src/client.tsx +383 -213
  69. package/src/config.ts +189 -183
  70. package/src/core.test.ts +71 -71
  71. package/src/core.ts +205 -205
  72. package/src/engine.test.ts +136 -136
  73. package/src/engine.ts +140 -140
  74. package/src/images.ts +33 -33
  75. package/src/index.ts +394 -247
  76. package/src/parity.test.ts +85 -85
  77. package/src/plan-gate.test.ts +56 -0
  78. package/src/plan-gate.ts +66 -0
  79. package/src/router.test.ts +107 -89
  80. package/src/router.ts +361 -309
  81. package/src/session.test.ts +71 -36
  82. package/src/session.ts +132 -103
  83. package/src/statistics.test.ts +80 -80
  84. package/src/statistics.ts +328 -328
  85. package/src/team-gate.test.ts +92 -0
  86. package/src/team-gate.ts +80 -0
  87. package/src/top-logprobs.test.ts +84 -84
  88. package/src/top-logprobs.ts +153 -153
  89. package/src/topic-storage.test.ts +31 -31
  90. package/src/topic-storage.ts +42 -42
package/package.json CHANGED
@@ -1,112 +1,114 @@
1
- {
2
- "name": "dsh-llm-verifier",
3
- "version": "0.1.13",
4
- "description": "Configurable DSH-native LLM verifier with a Web settings page",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/Aa728848/dsh-llm-verifier.git"
8
- },
9
- "homepage": "https://github.com/Aa728848/dsh-llm-verifier#readme",
10
- "bugs": {
11
- "url": "https://github.com/Aa728848/dsh-llm-verifier/issues"
12
- },
13
- "keywords": [
14
- "deepseek",
15
- "dsh",
16
- "verifier",
17
- "llm",
18
- "cordis",
19
- "plugin"
20
- ],
21
- "author": "eddyskywalker",
22
- "type": "module",
23
- "main": "lib/index.js",
24
- "types": "lib/types/index.d.ts",
25
- "exports": {
26
- ".": {
27
- "types": "./lib/types/index.d.ts",
28
- "default": "./lib/index.js"
29
- },
30
- "./core": {
31
- "types": "./lib/types/core.d.ts",
32
- "default": "./lib/core.js"
33
- },
34
- "./caller": {
35
- "types": "./lib/types/caller.d.ts",
36
- "default": "./lib/caller.js"
37
- },
38
- "./package.json": "./package.json",
39
- "./client": {
40
- "types": "./lib/types/client.d.ts",
41
- "default": "./lib/client.js"
42
- }
43
- },
44
- "dsh": {
45
- "bundle": {
46
- "patch": "./cordis.patch.yml"
47
- },
48
- "client": {
49
- "inject": [
50
- "@deepseek-ai/dsh-client-connection",
51
- "@deepseek-ai/dsh-client-runtime",
52
- "@deepseek-ai/dsh-client-ui-slots",
53
- "@deepseek-ai/dsh-client-ui-settings",
54
- "@deepseek-ai/dsh-client-ui-conversation",
55
- "@deepseek-ai/dsh-client-locale",
56
- "@deepseek-ai/dsh-api-remotes"
57
- ],
58
- "platform": "web"
59
- }
60
- },
61
- "peerDependencies": {
62
- "@deepseek-ai/cordis": "^4.0.1 || ^4.0.2",
63
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2",
64
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2",
65
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2",
66
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2",
67
- "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2",
68
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2",
69
- "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2",
70
- "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2"
71
- },
72
- "dependencies": {
73
- "schemastery": "^3.18.0"
74
- },
75
- "devDependencies": {
76
- "@deepseek-ai/cordis": "^4.0.1",
77
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
78
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
79
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
80
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
81
- "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2",
82
- "@types/node": "^22.20.0",
83
- "tsdown": "0.22.2",
84
- "typescript": "~5.7.2",
85
- "vitest": "^3.0.0",
86
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
87
- "@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2",
88
- "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2",
89
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
90
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
91
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
92
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.2",
93
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.1-rc.2",
94
- "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
95
- "react": "^18.2.0",
96
- "@types/react": "~18.3.1",
97
- "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2"
98
- },
99
- "files": [
100
- "lib/**/*.js",
101
- "lib/**/*.d.ts",
102
- "src",
103
- "cordis.patch.yml",
104
- "README.md"
105
- ],
106
- "scripts": {
107
- "build": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && tsdown",
108
- "typecheck": "tsc --noEmit -p tsconfig.build.json",
109
- "test": "vitest run"
110
- },
111
- "license": "MIT"
112
- }
1
+ {
2
+ "name": "dsh-llm-verifier",
3
+ "version": "0.1.16",
4
+ "description": "Configurable DSH-native LLM verifier with a Web settings page",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Aa728848/dsh-llm-verifier.git"
8
+ },
9
+ "homepage": "https://github.com/Aa728848/dsh-llm-verifier#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/Aa728848/dsh-llm-verifier/issues"
12
+ },
13
+ "keywords": [
14
+ "deepseek",
15
+ "dsh",
16
+ "verifier",
17
+ "llm",
18
+ "cordis",
19
+ "plugin"
20
+ ],
21
+ "author": "eddyskywalker",
22
+ "type": "module",
23
+ "main": "lib/index.js",
24
+ "types": "lib/types/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./lib/types/index.d.ts",
28
+ "default": "./lib/index.js"
29
+ },
30
+ "./core": {
31
+ "types": "./lib/types/core.d.ts",
32
+ "default": "./lib/core.js"
33
+ },
34
+ "./caller": {
35
+ "types": "./lib/types/caller.d.ts",
36
+ "default": "./lib/caller.js"
37
+ },
38
+ "./package.json": "./package.json",
39
+ "./client": {
40
+ "types": "./lib/types/client.d.ts",
41
+ "default": "./lib/client.js"
42
+ }
43
+ },
44
+ "dsh": {
45
+ "bundle": {
46
+ "patch": "./cordis.patch.yml"
47
+ },
48
+ "client": {
49
+ "inject": [
50
+ "@deepseek-ai/dsh-client-connection",
51
+ "@deepseek-ai/dsh-client-runtime",
52
+ "@deepseek-ai/dsh-client-ui-slots",
53
+ "@deepseek-ai/dsh-client-ui-settings",
54
+ "@deepseek-ai/dsh-client-ui-conversation",
55
+ "@deepseek-ai/dsh-client-locale",
56
+ "@deepseek-ai/dsh-api-remotes"
57
+ ],
58
+ "platform": "web"
59
+ }
60
+ },
61
+ "peerDependencies": {
62
+ "@deepseek-ai/cordis": "^4.0.1 || ^4.0.2",
63
+ "@deepseek-ai/dsh-tools": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2 || ^0.1.3 || ^0.1.4 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ~0.1.5",
64
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2 || ^0.1.3 || ^0.1.4 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ~0.1.5",
65
+ "@deepseek-ai/dsh-session": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2 || ^0.1.3 || ^0.1.4 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ~0.1.5",
66
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2 || ^0.1.3 || ^0.1.4 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ~0.1.5",
67
+ "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2 || ^0.1.3 || ^0.1.4 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ~0.1.5",
68
+ "@deepseek-ai/dsh-settings": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2 || ^0.1.3 || ^0.1.4 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ~0.1.5",
69
+ "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2 || ^0.1.3 || ^0.1.4 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ~0.1.5",
70
+ "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2 || ^0.1.2-alpha.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ~0.1.2 || ^0.1.3 || ^0.1.4 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ~0.1.5"
71
+ },
72
+ "dependencies": {
73
+ "schemastery": "^3.18.0"
74
+ },
75
+ "devDependencies": {
76
+ "@deepseek-ai/cordis": "^4.0.1",
77
+ "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
78
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
79
+ "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
80
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
81
+ "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2",
82
+ "@types/node": "^22.20.0",
83
+ "tsdown": "0.22.2",
84
+ "typescript": "~5.7.2",
85
+ "vitest": "^3.0.0",
86
+ "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
87
+ "@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2",
88
+ "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2",
89
+ "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
90
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
91
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
92
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.2",
93
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.1-rc.2",
94
+ "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
95
+ "react": "^18.2.0",
96
+ "@types/react": "~18.3.1",
97
+ "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2"
98
+ },
99
+ "files": [
100
+ "lib",
101
+ "src",
102
+ "cordis.patch.yml",
103
+ "README.md"
104
+ ],
105
+ "scripts": {
106
+ "build": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && tsdown",
107
+ "typecheck": "tsc --noEmit -p tsconfig.build.json",
108
+ "typecheck:local": "tsc --noEmit -p tsconfig.local.json",
109
+ "test": "vitest run",
110
+ "verify:release": "pnpm run typecheck && pnpm test && pnpm run build",
111
+ "prepublishOnly": "pnpm run verify:release"
112
+ },
113
+ "license": "MIT"
114
+ }
package/src/auto.test.ts CHANGED
@@ -1,86 +1,95 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { Session } from '@deepseek-ai/dsh-session'
3
- import { createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
4
- import { AutoVerificationBudget, analyzeAutoTask, automaticFeedback } from './auto.ts'
5
-
6
- function taskSession() {
7
- const session = Session.create('session-00000000-0000-4000-8000-000000000009' as never)
8
- session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'Implement and test the feature' }], source: { kind: 'user' } }), { surfaceOp: 'append' })
9
- return session
10
- }
11
-
12
- function call(session: ReturnType<typeof taskSession>, name: string, id: string) {
13
- session.append('tool/call', { turn: 1, step: 1, callId: id as never, name, arguments: '{}' })
14
- session.append('tool/result', { turn: 1, step: 1, message: createToolResultMessage({ callId: id as never, content: [{ type: 'text', text: 'ok' }], isError: false }) }, { surfaceOp: 'append' })
15
- }
16
-
17
- const smart = { mode: 'smart' as const, minToolCalls: 3, maxPerTask: 2, maxPerSession: 8 }
18
-
19
- describe('automatic verification policy', () => {
20
- it('requires consequential work and enough evidence in smart mode', () => {
21
- const session = taskSession()
22
- call(session, 'read', 'one')
23
- call(session, 'grep', 'two')
24
- expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: false, reason: 'no-consequential-work' })
25
- call(session, 'edit', 'three')
26
- expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: true, toolCalls: 3, consequentialToolCalls: 1 })
27
- })
28
-
29
- it('does not count a failed consequential call paired with an unrelated success', () => {
30
- const session = taskSession()
31
- session.append('tool/call', { turn: 1, step: 1, callId: 'bad' as never, name: 'edit', arguments: '{}' })
32
- session.append('tool/result', { turn: 1, step: 1, message: createToolResultMessage({ callId: 'bad' as never, content: [{ type: 'text', text: 'failed' }], isError: true }), error: { name: 'Error', code: 'FAILED' } }, { surfaceOp: 'append' })
33
- call(session, 'read', 'good')
34
- expect(analyzeAutoTask(session.events, smart)).toMatchObject({ consequentialToolCalls: 0, eligible: false, reason: 'no-consequential-work' })
35
- })
36
-
37
- it('strict mode accepts one completed consequential call', () => {
38
- const session = taskSession()
39
- call(session, 'ssh_exec', 'one')
40
- expect(analyzeAutoTask(session.events, { ...smart, mode: 'strict' })).toMatchObject({ eligible: true, reason: 'strict-eligible' })
41
- })
42
-
43
- it('manual verification suppresses the automatic gate', () => {
44
- const session = taskSession()
45
- call(session, 'edit', 'one')
46
- call(session, 'pwsh', 'two')
47
- call(session, 'verifier_current_session', 'three')
48
- expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: false, reason: 'already-verified', hasManualSessionVerification: true })
49
- })
50
-
51
- it('resets per-task attempts for a new direct user message and enforces the session cap', () => {
52
- const session = taskSession()
53
- call(session, 'edit', 'one')
54
- call(session, 'read', 'two')
55
- call(session, 'pwsh', 'three')
56
- const agent = { id: session.id, session } as never
57
- const budget = new AutoVerificationBudget()
58
- const policy = { ...smart, maxPerTask: 1, maxPerSession: 2 }
59
- expect(budget.claim(agent, analyzeAutoTask(session.events, policy), policy)).toBe(true)
60
- expect(budget.claim(agent, analyzeAutoTask(session.events, policy), policy)).toBe(false)
61
- session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'Do another task' }], source: { kind: 'user' } }), { surfaceOp: 'append' })
62
- call(session, 'write', 'four')
63
- call(session, 'read', 'five')
64
- call(session, 'pwsh', 'six')
65
- expect(budget.claim(agent, analyzeAutoTask(session.events, policy), policy)).toBe(true)
66
- session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'Third task' }], source: { kind: 'user' } }), { surfaceOp: 'append' })
67
- call(session, 'edit', 'seven')
68
- call(session, 'read', 'eight')
69
- call(session, 'pwsh', 'nine')
70
- expect(budget.claim(agent, analyzeAutoTask(session.events, policy), policy)).toBe(false)
71
- })
72
-
73
- it('builds actionable low-score feedback', () => {
74
- expect(automaticFeedback(0.42, 0.31, 'A', 0.65)).toContain('42.0%')
75
- expect(automaticFeedback(0.42, 0.31, 'A', 0.65)).toContain('verification command')
76
- })
77
-
78
- it('recognizes PTC mode tool/code-dispatch events for eligibility', () => {
79
- const session = taskSession()
80
- session.append('tool/code-dispatch', { subCallId: 'c1' as never, name: 'read', arguments: '{}', isError: false, content: [{ type: 'text', text: 'data' }] })
81
- session.append('tool/code-dispatch', { subCallId: 'c2' as never, name: 'grep', arguments: '{}', isError: false, content: [{ type: 'text', text: 'match' }] })
82
- expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: false, reason: 'no-consequential-work' })
83
- session.append('tool/code-dispatch', { subCallId: 'c3' as never, name: 'edit', arguments: '{}', isError: false, content: [{ type: 'text', text: 'done' }] })
84
- expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: true, toolCalls: 3, consequentialToolCalls: 1 })
85
- })
86
- })
1
+ import { describe, expect, it } from 'vitest'
2
+ import { Session } from '@deepseek-ai/dsh-session'
3
+ import { createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
4
+ import { AutoVerificationBudget, analyzeAutoTask, automaticFeedback } from './auto.ts'
5
+
6
+ function taskSession() {
7
+ const session = Session.create('session-00000000-0000-4000-8000-000000000009' as never)
8
+ session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'Implement and test the feature' }], source: { kind: 'user' } }), { surfaceOp: 'append' })
9
+ return session
10
+ }
11
+
12
+ function call(session: ReturnType<typeof taskSession>, name: string, id: string) {
13
+ session.append('tool/call', { turn: 1, step: 1, callId: id as never, name, arguments: '{}' })
14
+ session.append('tool/result', { turn: 1, step: 1, message: createToolResultMessage({ callId: id as never, content: [{ type: 'text', text: 'ok' }], isError: false }) }, { surfaceOp: 'append' })
15
+ }
16
+
17
+ const smart = { mode: 'smart' as const, minToolCalls: 3, maxPerTask: 2, maxPerSession: 8 }
18
+
19
+ describe('automatic verification policy', () => {
20
+ it('requires consequential work and enough evidence in smart mode', () => {
21
+ const session = taskSession()
22
+ call(session, 'read', 'one')
23
+ call(session, 'grep', 'two')
24
+ expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: false, reason: 'no-consequential-work' })
25
+ call(session, 'edit', 'three')
26
+ expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: true, toolCalls: 3, consequentialToolCalls: 1 })
27
+ })
28
+
29
+ it('does not count a failed consequential call paired with an unrelated success', () => {
30
+ const session = taskSession()
31
+ session.append('tool/call', { turn: 1, step: 1, callId: 'bad' as never, name: 'edit', arguments: '{}' })
32
+ session.append('tool/result', { turn: 1, step: 1, message: createToolResultMessage({ callId: 'bad' as never, content: [{ type: 'text', text: 'failed' }], isError: true }), error: { name: 'Error', code: 'FAILED' } }, { surfaceOp: 'append' })
33
+ call(session, 'read', 'good')
34
+ expect(analyzeAutoTask(session.events, smart)).toMatchObject({ consequentialToolCalls: 0, eligible: false, reason: 'no-consequential-work' })
35
+ })
36
+
37
+ it('strict mode accepts one completed consequential call', () => {
38
+ const session = taskSession()
39
+ call(session, 'ssh_exec', 'one')
40
+ expect(analyzeAutoTask(session.events, { ...smart, mode: 'strict' })).toMatchObject({ eligible: true, reason: 'strict-eligible' })
41
+ })
42
+
43
+ it('manual verification suppresses the automatic gate', () => {
44
+ const session = taskSession()
45
+ call(session, 'edit', 'one')
46
+ call(session, 'pwsh', 'two')
47
+ call(session, 'verifier_current_session', 'three')
48
+ expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: false, reason: 'already-verified', hasManualSessionVerification: true })
49
+ })
50
+
51
+ it('resets per-task attempts for a new direct user message and enforces the session cap', () => {
52
+ const session = taskSession()
53
+ call(session, 'edit', 'one')
54
+ call(session, 'read', 'two')
55
+ call(session, 'pwsh', 'three')
56
+ const agent = { id: session.id, session } as never
57
+ const budget = new AutoVerificationBudget()
58
+ const policy = { ...smart, maxPerTask: 1, maxPerSession: 2 }
59
+ expect(budget.claim(agent, analyzeAutoTask(session.events, policy), policy)).toBe(true)
60
+ expect(budget.claim(agent, analyzeAutoTask(session.events, policy), policy)).toBe(false)
61
+ session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'Do another task' }], source: { kind: 'user' } }), { surfaceOp: 'append' })
62
+ call(session, 'write', 'four')
63
+ call(session, 'read', 'five')
64
+ call(session, 'pwsh', 'six')
65
+ expect(budget.claim(agent, analyzeAutoTask(session.events, policy), policy)).toBe(true)
66
+ session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'Third task' }], source: { kind: 'user' } }), { surfaceOp: 'append' })
67
+ call(session, 'edit', 'seven')
68
+ call(session, 'read', 'eight')
69
+ call(session, 'pwsh', 'nine')
70
+ expect(budget.claim(agent, analyzeAutoTask(session.events, policy), policy)).toBe(false)
71
+ })
72
+
73
+ it('builds actionable low-score feedback', () => {
74
+ expect(automaticFeedback(0.42, 0.31, 'A', 0.65)).toContain('42.0%')
75
+ expect(automaticFeedback(0.42, 0.31, 'A', 0.65)).toContain('verification command')
76
+ })
77
+
78
+ it('recognizes PTC mode tool/code-dispatch events for eligibility', () => {
79
+ const session = taskSession()
80
+ session.append('tool/code-dispatch', { subCallId: 'c1' as never, name: 'read', arguments: '{}', isError: false, content: [{ type: 'text', text: 'data' }] })
81
+ session.append('tool/code-dispatch', { subCallId: 'c2' as never, name: 'grep', arguments: '{}', isError: false, content: [{ type: 'text', text: 'match' }] })
82
+ expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: false, reason: 'no-consequential-work' })
83
+ session.append('tool/code-dispatch', { subCallId: 'c3' as never, name: 'edit', arguments: '{}', isError: false, content: [{ type: 'text', text: 'done' }] })
84
+ expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: true, toolCalls: 3, consequentialToolCalls: 1 })
85
+ })
86
+
87
+ it('counts DSH 0.1.5 tool/ptc-dispatch events the same way', () => {
88
+ const session = taskSession()
89
+ session.append('tool/ptc-dispatch' as never, { subCallId: 'p1', name: 'read', arguments: '{}', isError: false, content: [{ type: 'text', text: 'data' }] } as never)
90
+ session.append('tool/ptc-dispatch' as never, { subCallId: 'p2', name: 'grep', arguments: '{}', isError: false, content: [{ type: 'text', text: 'match' }] } as never)
91
+ expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: false, reason: 'no-consequential-work' })
92
+ session.append('tool/ptc-dispatch' as never, { subCallId: 'p3', name: 'edit', arguments: '{}', isError: false, content: [{ type: 'text', text: 'done' }] } as never)
93
+ expect(analyzeAutoTask(session.events, smart)).toMatchObject({ eligible: true, toolCalls: 3, consequentialToolCalls: 1 })
94
+ })
95
+ })