@catheadowl/dsh-eval 0.2.0 → 0.2.1
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 +57 -0
- package/README.i18n.yaml +2 -2
- package/README.md +2 -5
- package/README.zh.md +2 -5
- package/bin/dsh-eval.mjs +335 -335
- package/bin/dsh-review.mjs +166 -154
- package/docs/README.md +3 -2
- package/docs/cross-turn.md +66 -0
- package/docs/host-wiring.md +1 -1
- package/docs/known-issues.md +9 -1
- package/docs/matchers.md +13 -1
- package/docs/review.md +12 -9
- package/package.json +7 -1
- package/src/adapters/dsh/review.mjs +70 -17
- package/src/assertions.mjs +499 -389
- package/src/discovery.mjs +190 -166
- package/src/driver/multi-turn-driver.mjs +163 -0
- package/src/experiment/review.mjs +118 -118
- package/src/index.mjs +4 -0
- package/src/mock/mock-adapter.mjs +73 -73
- package/src/mock/script.mjs +49 -49
- package/src/overlay.mjs +13 -0
- package/src/review-report.mjs +14 -1
- package/src/runner.mjs +21 -1
- package/src/sandbox.mjs +62 -1
- package/src/tool-validation.mjs +77 -77
- package/src/trace.mjs +293 -218
|
@@ -7,17 +7,18 @@ import { executeReviewExperiment } from '../../experiment/review.mjs'
|
|
|
7
7
|
import { CLI_RELATIVE_PATH } from '../../cli.mjs'
|
|
8
8
|
import { overlayDisableRows } from '../../overlay.mjs'
|
|
9
9
|
import {
|
|
10
|
-
resolveRealDshHome, stageSandboxHome, teardownSandbox, spawnHeadlessDsh,
|
|
10
|
+
resolveRealDshHome, stageSandboxHome, stagedPluginRows, teardownSandbox, spawnHeadlessDsh,
|
|
11
11
|
} from '../../sandbox.mjs'
|
|
12
12
|
import { loadTraceDir } from '../../trace.mjs'
|
|
13
13
|
import { validateToolBoundary, renderToolBoundaryEvidence } from '../../tool-validation.mjs'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Model-facing tool rows every shipped dsh profile mounts from `dsh-base`.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* validation (see `validateToolBoundary`) detects any residual
|
|
17
|
+
* A static supplementary guard on top of the blank-environment enumeration
|
|
18
|
+
* (host template tool rows never appear in `stagedPluginRows` — they are the
|
|
19
|
+
* sterile baseline, so their ids are enumerated here instead). Post-run
|
|
20
|
+
* tool boundary validation (see `validateToolBoundary`) detects any residual
|
|
21
|
+
* drift.
|
|
21
22
|
*/
|
|
22
23
|
const REVIEW_DISABLED_TOOL_ROWS = [
|
|
23
24
|
'tool-bash',
|
|
@@ -39,9 +40,34 @@ const REVIEW_DISABLED_TOOL_ROWS = [
|
|
|
39
40
|
'tool-workflow',
|
|
40
41
|
]
|
|
41
42
|
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Rows the reviewer session must KEEP even when an out-of-tree bundle or the
|
|
45
|
+
* profile patch touches them: model wiring and the session log — without
|
|
46
|
+
* these the reviewer cannot answer at all and the trace (tool boundary
|
|
47
|
+
* check, run artifacts) never materializes. These ids are host-template
|
|
48
|
+
* rows; an out-of-tree patch row targeting one of them only overrides its
|
|
49
|
+
* config, so keeping it enabled stays safe.
|
|
50
|
+
*/
|
|
51
|
+
const REVIEW_REQUIRED_ROWS = new Set([
|
|
52
|
+
'agent-default-model',
|
|
53
|
+
'session-title-llm',
|
|
54
|
+
'system-prompt',
|
|
55
|
+
'session-persistence-jsonl',
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Serialize the blank-environment overlay: disable every plugin row the
|
|
60
|
+
* staged profile composes beyond the host templates (`stagedPluginRows` —
|
|
61
|
+
* out-of-tree bundle rows + profile patch rows), UNION the static host tool
|
|
62
|
+
* rows, MINUS the model/session wiring the reviewer needs. With
|
|
63
|
+
* `keepPluginRows` (explicit opt-in to study the host plugin face itself)
|
|
64
|
+
* the enumeration is skipped and only the static tool lockdown remains.
|
|
65
|
+
*/
|
|
66
|
+
function buildReviewOverlayYaml(pluginRows, { keepPluginRows }) {
|
|
67
|
+
const disabled = new Set(keepPluginRows ? [] : pluginRows)
|
|
68
|
+
for (const row of REVIEW_DISABLED_TOOL_ROWS) disabled.add(row)
|
|
69
|
+
for (const row of REVIEW_REQUIRED_ROWS) disabled.delete(row)
|
|
70
|
+
return overlayDisableRows([...disabled])
|
|
45
71
|
}
|
|
46
72
|
|
|
47
73
|
/** Resolve and validate the compiled dsh CLI entry point. */
|
|
@@ -66,16 +92,22 @@ function executorCli(options) {
|
|
|
66
92
|
/**
|
|
67
93
|
* Create an executor compatible with executeReviewExperiment.
|
|
68
94
|
*
|
|
69
|
-
* The executor boots a
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
95
|
+
* The executor boots a blank review environment in an isolated DSH_HOME
|
|
96
|
+
* (staging/teardown mechanics shared with the behavior runner via
|
|
97
|
+
* sandbox.mjs): the staged profile's every out-of-tree plugin row and every
|
|
98
|
+
* host model-facing tool row is disabled via overlay (blank =
|
|
99
|
+
* dsh-base/dsh-headless templates + model wiring, nothing else — regardless
|
|
100
|
+
* of what the host profile carries), and the tool boundary is validated
|
|
101
|
+
* after the run.
|
|
75
102
|
*
|
|
76
103
|
* @param {object} options
|
|
77
|
-
* @param {string} [options.profile='headless'] - the
|
|
104
|
+
* @param {string} [options.profile='headless'] - the review profile (host
|
|
105
|
+
* templates + whatever the host machine carries; plugin rows are disabled
|
|
106
|
+
* by the overlay anyway).
|
|
78
107
|
* @param {Set<string>} [options.allowedTools] - tool names permitted in the reviewer's session (default: empty).
|
|
108
|
+
* @param {boolean} [options.keepPluginRows=false] - opt back into the host
|
|
109
|
+
* profile's plugin face (e.g. to review a plugin's own gate behavior);
|
|
110
|
+
* only the static tool lockdown remains.
|
|
79
111
|
*/
|
|
80
112
|
export function createDshHeadlessReviewExecutor(options) {
|
|
81
113
|
const cli = executorCli(options)
|
|
@@ -85,6 +117,7 @@ export function createDshHeadlessReviewExecutor(options) {
|
|
|
85
117
|
}
|
|
86
118
|
const timeoutMs = options.timeoutMs ?? 300_000
|
|
87
119
|
const allowedTools = options.allowedTools ?? new Set()
|
|
120
|
+
const keepPluginRows = options.keepPluginRows ?? false
|
|
88
121
|
|
|
89
122
|
return async function executeWithDsh(task) {
|
|
90
123
|
// A fresh process alone is not enough: dsh also stores settings, titles,
|
|
@@ -93,9 +126,18 @@ export function createDshHeadlessReviewExecutor(options) {
|
|
|
93
126
|
// while retaining the selected profile's model config and plugin links.
|
|
94
127
|
const runDir = mkdtempSync(join(tmpdir(), 'dsh-review-'))
|
|
95
128
|
const dshHome = join(runDir, 'dsh-home')
|
|
96
|
-
const overlayPath = join(runDir, 'review-overlay.yml')
|
|
97
|
-
writeFileSync(overlayPath, buildReviewOverlayYaml(), 'utf8')
|
|
98
129
|
stageSandboxHome(options.dshHome ?? resolveRealDshHome(), dshHome, profile)
|
|
130
|
+
// Blank environment by DEFAULT (review-blank-environment TODO): disable
|
|
131
|
+
// every row the staged profile composes beyond the host templates, so a
|
|
132
|
+
// host profile carrying out-of-tree plugins (gates included) can no
|
|
133
|
+
// longer steer or crash the reviewer. `keepPluginRows` opts back into
|
|
134
|
+
// the host plugin face deliberately. Enumeration happens on the STAGED
|
|
135
|
+
// copy, after staging — the staged home is what actually boots.
|
|
136
|
+
const overlayPath = join(runDir, 'review-overlay.yml')
|
|
137
|
+
writeFileSync(overlayPath, buildReviewOverlayYaml(
|
|
138
|
+
stagedPluginRows(dshHome, profile),
|
|
139
|
+
{ keepPluginRows },
|
|
140
|
+
), 'utf8')
|
|
99
141
|
|
|
100
142
|
try {
|
|
101
143
|
const { stdout, stderr, exitCode, timedOut } = await spawnHeadlessDsh({
|
|
@@ -141,6 +183,17 @@ export function createDshHeadlessReviewExecutor(options) {
|
|
|
141
183
|
boundaryError.result = result
|
|
142
184
|
throw boundaryError
|
|
143
185
|
}
|
|
186
|
+
// The ANSWER to the task, not the last message: stdout carries the
|
|
187
|
+
// headless CLI's final assistant message — whatever the reviewer
|
|
188
|
+
// said LAST. If any tail interaction intervened (a turn-close gate
|
|
189
|
+
// splice that slipped past the blank environment, an infra
|
|
190
|
+
// complaint), stdout holds that instead of the analysis. The
|
|
191
|
+
// trace's answerText (last assistant text before the first
|
|
192
|
+
// plugin-sourced injection) IS the analysis; stdout remains the
|
|
193
|
+
// fallback for trace-less runs.
|
|
194
|
+
result.answer = trace.answerText !== '' ? trace.answerText : stdout
|
|
195
|
+
} else {
|
|
196
|
+
result.answer = stdout
|
|
144
197
|
}
|
|
145
198
|
|
|
146
199
|
return result
|