@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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +22 -0
- package/README.md +126 -0
- package/THIRD_PARTY_LICENSE.txt +21 -0
- package/THIRD_PARTY_NOTICES.md +10 -0
- package/cordis.patch.yml +5 -0
- package/docs/COMPATIBILITY.md +43 -0
- package/docs/CONFIGURATION.md +167 -0
- package/docs/RELEASING.md +108 -0
- package/examples/defaults.json +121 -0
- package/examples/desktop-config.patch.yml +30 -0
- package/examples/separate-review-model.patch.yml +12 -0
- package/examples/strict.patch.yml +13 -0
- package/icon.svg +1 -0
- package/locale/en.json +1 -0
- package/locale/zh.json +1 -0
- package/package.json +108 -0
- package/src/async.js +66 -0
- package/src/audit.js +125 -0
- package/src/config.js +240 -0
- package/src/containment.js +49 -0
- package/src/deterministic.js +58 -0
- package/src/engine.js +365 -0
- package/src/index.d.ts +110 -0
- package/src/index.js +396 -0
- package/src/paths.js +94 -0
- package/src/permission-adapter.js +75 -0
- package/src/permission-presets.js +77 -0
- package/src/presentation.js +48 -0
- package/src/preset-defaults.js +85 -0
- package/src/shell.js +197 -0
- package/src/upstream-reviewer.js +578 -0
|
@@ -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
|
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
|
+
}
|