@lytharalab/dsh-cons-auto-review 1.1.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.
@@ -0,0 +1,121 @@
1
+ {
2
+ "enabled": true,
3
+ "inheritSubagents": true,
4
+ "rules": {
5
+ "enabled": true,
6
+ "allowOutsideReads": true,
7
+ "includeTempRoots": true,
8
+ "additionalWritableRoots": [],
9
+ "protectedPaths": [
10
+ "**/.git/**",
11
+ "**/.dsh/**",
12
+ "**/.dcar/**",
13
+ "**/.ssh/**",
14
+ "**/.aws/**",
15
+ "**/.gnupg/**",
16
+ "**/.env",
17
+ "**/.env.*",
18
+ "**/.npmrc",
19
+ "**/credentials.json"
20
+ ],
21
+ "safeEnvironmentFiles": [
22
+ ".env.example",
23
+ ".env.sample",
24
+ ".env.template"
25
+ ],
26
+ "readTools": [
27
+ "read",
28
+ "read_image"
29
+ ],
30
+ "searchTools": [
31
+ "glob",
32
+ "grep",
33
+ "ls"
34
+ ],
35
+ "writeTools": [
36
+ "write",
37
+ "edit"
38
+ ],
39
+ "harmlessTools": [
40
+ "todo_write"
41
+ ],
42
+ "alwaysReviewTools": [],
43
+ "trustedTools": [],
44
+ "customTools": [],
45
+ "maxArgumentBytes": 2097152
46
+ },
47
+ "shell": {
48
+ "enabled": true,
49
+ "tools": [
50
+ "bash",
51
+ "pwsh"
52
+ ],
53
+ "readCommands": [
54
+ "pwd",
55
+ "ls",
56
+ "cat",
57
+ "head",
58
+ "tail",
59
+ "wc",
60
+ "rg",
61
+ "grep",
62
+ "echo",
63
+ "Get-Location",
64
+ "Get-ChildItem",
65
+ "Get-Content",
66
+ "Write-Output"
67
+ ],
68
+ "workspaceMutationCommands": [
69
+ "mkdir",
70
+ "touch"
71
+ ],
72
+ "trustedCommands": [],
73
+ "maxCommandChars": 8192
74
+ },
75
+ "review": {
76
+ "provider": "",
77
+ "model": "",
78
+ "reasoningEffort": "",
79
+ "preferLowReasoning": true,
80
+ "timeoutMs": 120000,
81
+ "maxTokens": 8192,
82
+ "maxTokensLimit": 32768,
83
+ "tokenLimitRetries": 2,
84
+ "tokenGrowthFactor": 2,
85
+ "chineseReasonRetries": 1,
86
+ "maxReasonChars": 600,
87
+ "showAction": true,
88
+ "maxActionChars": 3000,
89
+ "temperature": 0,
90
+ "retries": 0,
91
+ "retryDelayMs": 300,
92
+ "concurrency": 2,
93
+ "maxQueued": 128,
94
+ "onError": "ask",
95
+ "onDeny": "ask",
96
+ "policyAppend": "",
97
+ "maxInputChars": 180000,
98
+ "allowAsk": true
99
+ },
100
+ "cache": {
101
+ "enabled": false,
102
+ "ttlMs": 30000,
103
+ "maxEntries": 128
104
+ },
105
+ "audit": {
106
+ "enabled": true,
107
+ "file": "",
108
+ "maxBytes": 5242880,
109
+ "historyLimit": 200,
110
+ "includeArguments": false,
111
+ "includeReasons": true
112
+ },
113
+ "commands": {
114
+ "enabled": true,
115
+ "name": "dcar",
116
+ "allowSessionConfig": true
117
+ },
118
+ "lifecycle": {
119
+ "fallbackPreset": "workspace-write"
120
+ }
121
+ }
@@ -0,0 +1,30 @@
1
+ # Add this overlay after the installed DCAR package layer in your profile.
2
+ - id: dcar
3
+ name: '@lytharalab/dsh-cons-auto-review'
4
+ config:
5
+ rules:
6
+ includeTempRoots: true
7
+ allowOutsideReads: true
8
+ additionalWritableRoots: []
9
+ alwaysReviewTools: []
10
+ shell:
11
+ trustedCommands: []
12
+ review:
13
+ timeoutMs: 120000
14
+ maxTokens: 8192
15
+ maxTokensLimit: 32768
16
+ tokenLimitRetries: 2
17
+ preferLowReasoning: true
18
+ concurrency: 2
19
+ retries: 0
20
+ onDeny: ask
21
+ onError: ask
22
+ cache:
23
+ enabled: false
24
+ audit:
25
+ file: .dcar/audit.jsonl
26
+ includeArguments: false
27
+ historyLimit: 200
28
+ commands:
29
+ enabled: true
30
+ allowSessionConfig: true
@@ -0,0 +1,12 @@
1
+ # Provider/model values must refer to a configured DSH route; replace these placeholders.
2
+ - id: dcar
3
+ name: '@lytharalab/dsh-cons-auto-review'
4
+ config:
5
+ review:
6
+ provider: your-review-provider
7
+ model: your-review-model
8
+ maxTokens: 8192
9
+ maxTokensLimit: 32768
10
+ tokenLimitRetries: 2
11
+ preferLowReasoning: true
12
+ timeoutMs: 120000
@@ -0,0 +1,13 @@
1
+ - id: dcar
2
+ name: '@lytharalab/dsh-cons-auto-review'
3
+ config:
4
+ rules:
5
+ allowOutsideReads: false
6
+ includeTempRoots: false
7
+ review:
8
+ onError: deny
9
+ onDeny: deny
10
+ allowAsk: false
11
+ timeoutMs: 30000
12
+ cache:
13
+ enabled: false
package/icon.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="16" fill="#14253d"/><path d="M32 10 49 17v15c0 12-10 20-17 23-7-3-17-11-17-23V17z" fill="#48d1b6"/><path d="m23 32 6 6 13-14" fill="none" stroke="#14253d" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/></svg>
package/locale/en.json ADDED
@@ -0,0 +1 @@
1
+ {"meta":{"title":"Con's Auto Review","description":"CAutoR: deterministic workspace checks first; LLM review only for escalations."}}
package/locale/zh.json ADDED
@@ -0,0 +1 @@
1
+ {"meta":{"title":"Con's 自动审查","description":"CAutoR:确定性程序规则先审查,仅将无法自动批准的操作升级至 LLM Auto Review。"}}
package/package.json ADDED
@@ -0,0 +1,108 @@
1
+ {
2
+ "name": "@lytharalab/dsh-cons-auto-review",
3
+ "version": "1.1.0",
4
+ "description": "Con's 自动审查(DCAR):确定性规则优先、LLM 按需升级的 DeepSeek Harness 工具审查插件,提供 CAutoR 权限模式。Deterministic-first auto review for DeepSeek Harness.",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.d.ts",
11
+ "default": "./src/index.js"
12
+ },
13
+ "./core": "./src/engine.js",
14
+ "./config": "./src/config.js",
15
+ "./cordis.patch.yml": "./cordis.patch.yml",
16
+ "./locale/*.json": "./locale/*.json",
17
+ "./package.json": "./package.json",
18
+ "./permission-presets": "./src/permission-presets.js"
19
+ },
20
+ "files": [
21
+ "src",
22
+ "locale",
23
+ "cordis.patch.yml",
24
+ "icon.svg",
25
+ "docs",
26
+ "examples",
27
+ "README.md",
28
+ "CHANGELOG.md",
29
+ "LICENSE",
30
+ "THIRD_PARTY_NOTICES.md",
31
+ "THIRD_PARTY_LICENSE.txt"
32
+ ],
33
+ "engines": {
34
+ "node": "^22.19.0 || >=24.0.0"
35
+ },
36
+ "license": "MIT",
37
+ "author": "Con89524 / LytharaLab",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/LytharaLab/DSH-Cons-Auto-Review.git"
41
+ },
42
+ "homepage": "https://github.com/LytharaLab/DSH-Cons-Auto-Review#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/LytharaLab/DSH-Cons-Auto-Review/issues"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org/"
49
+ },
50
+ "keywords": [
51
+ "dsh-plugin",
52
+ "deepseek-harness",
53
+ "deepseek",
54
+ "cordis",
55
+ "auto-review",
56
+ "approval",
57
+ "permission-preset",
58
+ "sandbox",
59
+ "CAutoR",
60
+ "DCAR"
61
+ ],
62
+ "scripts": {
63
+ "test": "node --test tests/*.test.js",
64
+ "check": "node scripts/check.mjs",
65
+ "verify": "npm run check && npm test",
66
+ "prepublishOnly": "npm run verify",
67
+ "pack:release": "npm pack --ignore-scripts"
68
+ },
69
+ "peerDependencies": {
70
+ "@deepseek-ai/cordis": "^4.0.0",
71
+ "@deepseek-ai/dsh-llm": ">=0.0.1-rc.1",
72
+ "@deepseek-ai/dsh-permission-presets": "0.1.7-rc.2 || 0.2.0-rc.2",
73
+ "@deepseek-ai/dsh-sandbox": ">=0.0.1-rc.1",
74
+ "@deepseek-ai/dsh-util-values": ">=0.0.1-rc.1",
75
+ "@deepseek-ai/schemastery": "*",
76
+ "@deepseek-ai/dsh-user-approval": "0.1.7-rc.2 || 0.2.0-rc.2"
77
+ },
78
+ "devDependencies": {
79
+ "@deepseek-ai/cordis": "4.0.4",
80
+ "@deepseek-ai/cordis-plugin-loader": "1.0.5",
81
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
82
+ "@deepseek-ai/dsh-commands": "0.2.0-rc.2",
83
+ "@deepseek-ai/dsh-fs": "0.2.0-rc.2",
84
+ "@deepseek-ai/dsh-fs-local": "0.2.0-rc.2",
85
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
86
+ "@deepseek-ai/dsh-permission-presets": "0.2.0-rc.2",
87
+ "@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
88
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
89
+ "@deepseek-ai/dsh-scope": "0.2.0-rc.2",
90
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
91
+ "@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
92
+ "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
93
+ "@deepseek-ai/dsh-shell": "0.2.0-rc.2",
94
+ "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
95
+ "@deepseek-ai/dsh-tool-fs": "0.2.0-rc.2",
96
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
97
+ "@deepseek-ai/dsh-user-approval": "0.2.0-rc.2",
98
+ "@deepseek-ai/dsh-util-values": "0.2.0-rc.2",
99
+ "@deepseek-ai/schemastery": "3.18.4",
100
+ "yaml": "2.8.3"
101
+ },
102
+ "icon": "./icon.svg",
103
+ "dsh": {
104
+ "bundle": {
105
+ "patch": "./cordis.patch.yml"
106
+ }
107
+ }
108
+ }
package/src/async.js ADDED
@@ -0,0 +1,66 @@
1
+ /** Abort-aware queue and timers. */
2
+ export function abortError(signal) {
3
+ return signal.reason instanceof Error
4
+ ? signal.reason
5
+ : new Error(String(signal.reason ?? "Aborted"));
6
+ }
7
+ export function abortable(promise, signal) {
8
+ if (signal.aborted) return Promise.reject(abortError(signal));
9
+ return new Promise((resolve, reject) => {
10
+ const onAbort = () => reject(abortError(signal));
11
+ signal.addEventListener("abort", onAbort, { once: true });
12
+ Promise.resolve(promise)
13
+ .then(resolve, reject)
14
+ .finally(() => signal.removeEventListener("abort", onAbort));
15
+ });
16
+ }
17
+ export function delay(ms, signal) {
18
+ return new Promise((resolve, reject) => {
19
+ if (signal.aborted) {
20
+ reject(abortError(signal));
21
+ return;
22
+ }
23
+ const timer = setTimeout(() => {
24
+ signal.removeEventListener("abort", onAbort);
25
+ resolve();
26
+ }, ms);
27
+ function onAbort() {
28
+ clearTimeout(timer);
29
+ reject(abortError(signal));
30
+ }
31
+ signal.addEventListener("abort", onAbort, { once: true });
32
+ });
33
+ }
34
+ export class ReviewQueue {
35
+ active = 0;
36
+ waiting = [];
37
+ async acquire(limit, maxQueued, signal) {
38
+ signal.throwIfAborted();
39
+ if (this.active < limit) {
40
+ this.active++;
41
+ return () => this.release();
42
+ }
43
+ if (this.waiting.length >= maxQueued)
44
+ throw new Error("DCAR review queue is full");
45
+ return new Promise((resolve, reject) => {
46
+ const entry = {
47
+ resolve,
48
+ reject,
49
+ signal,
50
+ onAbort: () => {
51
+ this.waiting = this.waiting.filter((x) => x !== entry);
52
+ reject(abortError(signal));
53
+ },
54
+ };
55
+ signal.addEventListener("abort", entry.onAbort, { once: true });
56
+ this.waiting.push(entry);
57
+ });
58
+ }
59
+ release() {
60
+ const entry = this.waiting.shift();
61
+ if (entry) {
62
+ entry.signal.removeEventListener("abort", entry.onAbort);
63
+ entry.resolve(() => this.release());
64
+ } else this.active--;
65
+ }
66
+ }
package/src/audit.js ADDED
@@ -0,0 +1,125 @@
1
+ /** Session-scoped counters, bounded history, and optional redacted JSONL audit. */
2
+ import { createHash } from "node:crypto";
3
+ import { appendFile, mkdir, rename, stat } from "node:fs/promises";
4
+ import { dirname, isAbsolute, resolve } from "node:path";
5
+ export const hash = (value) =>
6
+ createHash("sha256").update(JSON.stringify(value)).digest("hex");
7
+ export class Audit {
8
+ sessions = new Map();
9
+ tail = Promise.resolve();
10
+ state(id) {
11
+ let state = this.sessions.get(id);
12
+ if (!state) {
13
+ state = {
14
+ total: 0,
15
+ deterministic: 0,
16
+ escalated: 0,
17
+ llmRequests: 0,
18
+ tokenLimitRetries: 0,
19
+ translationRequests: 0,
20
+ cacheHits: 0,
21
+ allowed: 0,
22
+ asked: 0,
23
+ denied: 0,
24
+ errors: 0,
25
+ cancelled: 0,
26
+ history: [],
27
+ };
28
+ this.sessions.set(id, state);
29
+ }
30
+ return state;
31
+ }
32
+ request(id) {
33
+ this.state(id).llmRequests++;
34
+ }
35
+ record(exec, event, c) {
36
+ const id = String(exec.agent.session.id),
37
+ s = this.state(id);
38
+ s.total++;
39
+ s[event.layer === "program" ? "deterministic" : "escalated"]++;
40
+ if (event.cacheHit) s.cacheHits++;
41
+ if (event.error) s.errors++;
42
+ s.tokenLimitRetries += event.tokenRetries ?? 0;
43
+ s.translationRequests += event.translationAttempts ?? 0;
44
+ s[
45
+ { allow: "allowed", ask: "asked", deny: "denied", cancel: "cancelled" }[
46
+ event.decision
47
+ ]
48
+ ]++;
49
+ if (!c.audit.enabled) return;
50
+ const row = {
51
+ time: new Date().toISOString(),
52
+ sessionId: id,
53
+ callId: String(exec.callId),
54
+ tool: exec.name,
55
+ argumentHash: hash(exec.arguments),
56
+ layer: event.layer,
57
+ decision: event.decision,
58
+ rule: event.code,
59
+ durationMs: event.durationMs,
60
+ cacheHit: !!event.cacheHit,
61
+ reviewAttempts: event.reviewAttempts ?? 0,
62
+ tokenLimitRetries: event.tokenRetries ?? 0,
63
+ translationAttempts: event.translationAttempts ?? 0,
64
+ lastMaxTokens: event.lastMaxTokens ?? 0,
65
+ };
66
+ if (event.errorCode) row.errorCode = event.errorCode;
67
+ if (c.audit.includeReasons) {
68
+ row.reason = String(event.reason ?? "").slice(0, 2000);
69
+ if (event.displayReason)
70
+ row.displayReason = String(event.displayReason).slice(0, 2000);
71
+ }
72
+ if (c.audit.includeArguments) row.arguments = exec.arguments;
73
+ s.history.push(row);
74
+ if (s.history.length > c.audit.historyLimit)
75
+ s.history.splice(0, s.history.length - c.audit.historyLimit);
76
+ if (c.audit.file) {
77
+ const cwd = exec.agent.session.header.cwd;
78
+ const file = isAbsolute(c.audit.file)
79
+ ? c.audit.file
80
+ : resolve(cwd, c.audit.file);
81
+ this.tail = this.tail
82
+ .then(async () => {
83
+ await mkdir(dirname(file), { recursive: true });
84
+ try {
85
+ if (
86
+ (await stat(file)).size +
87
+ Buffer.byteLength(JSON.stringify(row)) +
88
+ 1 >
89
+ c.audit.maxBytes
90
+ )
91
+ await rename(file, `${file}.1`);
92
+ } catch (error) {
93
+ if (error.code !== "ENOENT") throw error;
94
+ }
95
+ await appendFile(file, `${JSON.stringify(row)}\n`, {
96
+ encoding: "utf8",
97
+ mode: 0o600,
98
+ });
99
+ })
100
+ .catch((error) => {
101
+ this.lastError = String(error.message ?? error);
102
+ });
103
+ }
104
+ }
105
+ stats(id) {
106
+ const { history, ...s } = this.state(String(id));
107
+ return {
108
+ ...s,
109
+ savedReviewRequests: s.deterministic,
110
+ deterministicRate: s.total
111
+ ? `${((s.deterministic / s.total) * 100).toFixed(1)}%`
112
+ : "0%",
113
+ auditError: this.lastError ?? null,
114
+ };
115
+ }
116
+ history(id, n = 20) {
117
+ return this.state(String(id)).history.slice(-n);
118
+ }
119
+ reset(id) {
120
+ this.sessions.delete(String(id));
121
+ }
122
+ async flush() {
123
+ await this.tail;
124
+ }
125
+ }