dsh-eval-guard 0.1.3 → 0.2.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 +13 -8
- package/assets/bomberman/workspace.zip +0 -0
- package/assets/ctf/workspace.zip +0 -0
- package/assets/land/workspace.zip +0 -0
- package/assets/tank/workspace.zip +0 -0
- package/assets/warehouse/workspace.zip +0 -0
- package/lib/client/index.js +34 -8
- package/lib/client/index.js.map +1 -1
- package/lib/index.js +392 -211
- package/native/dsh-eval-supervisor +0 -0
- package/native/supervisor.c +18 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,21 +6,26 @@
|
|
|
6
6
|
- 禁止联网、子代理、Agent Team/跨 agent 通信、读取其它工作区。
|
|
7
7
|
- 工具黑名单可配置;未知工具默认放行,但 DSH 内置与 dsh-ops 的工具会做参数路径审计。
|
|
8
8
|
- 每个用户消息后的单轮有可配置的最长时长(默认 90 分钟);到点只终止当前轮次,并递归清理子代理与后台任务。
|
|
9
|
-
-
|
|
9
|
+
- 设置页可安装五个任务包:**第 6 集 · 坦克大战**、**第 5 集 · 仓储调度**、**第 1 集 · 圈地**、**第 2 集 · 炸弹人**、**第 3 集 · 夺旗**:空工作区 → 放入打包好的 `workspace.zip` → 新会话填入提示词(由用户发送)。
|
|
10
10
|
|
|
11
11
|
插件默认关闭;关闭时不做任何行为。所有 run 记录、usage、violations 写入
|
|
12
|
-
$DSH_HOME/eval-guard/runs/<run-id>/,供独立的 ../tank-eval 评测程序读取。
|
|
12
|
+
$DSH_HOME/eval-guard/runs/<run-id>/,供独立的 ../tank-eval 与 ../warehouse-eval 评测程序读取。
|
|
13
13
|
|
|
14
14
|
## 任务来源
|
|
15
15
|
|
|
16
|
-
安装的评测任务来自开源项目 **meow-ai-arena
|
|
16
|
+
安装的评测任务来自开源项目 **meow-ai-arena**(MIT License,commit `6bd738b`,2026-10-16):
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
18
|
+
- 第 1 集 · 圈地:<https://github.com/chen-006/meow-ai-arena/tree/main/land>,素材 `land/workspace/`
|
|
19
|
+
- 第 2 集 · 炸弹人:<https://github.com/chen-006/meow-ai-arena/tree/main/bomberman>,素材 `bomberman/workspace/`
|
|
20
|
+
- 第 3 集 · 夺旗:<https://github.com/chen-006/meow-ai-arena/tree/main/ctf>,素材 `ctf/workspace/`
|
|
21
|
+
- 第 5 集 · 仓储调度:<https://github.com/chen-006/meow-ai-arena/tree/main/warehouse>,素材 `warehouse/workspace/` 的第 1 轮主程包,改编为单人单次 90 分钟
|
|
22
|
+
- 第 6 集 · 坦克大战:<https://github.com/chen-006/meow-ai-arena/tree/main/tank>,素材 `tank/workspace/`
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
仓库:<https://github.com/chen-006/meow-ai-arena>
|
|
25
|
+
|
|
26
|
+
本插件打包的 Linux 版 `workspace.zip` 在原素材基础上做了三处适配:把 `baseline.cpp` 编译为 Linux ELF 可执行文件并移出源码,文件名改为 `baseline`(不带 `.exe`);把 `clock.py` 改为单次 90 分钟版本;`README.md` 中对应的 baseline 示例同步改名。规则、引擎、本地裁判、模板等其余文件保持原样。安装任务时只把 `workspace.zip` 放入工作区,不再散装解压。
|
|
27
|
+
|
|
28
|
+
仓储调度任务包同样只放 `workspace.zip`;包内 `TASK.md` 已改写成单人单次 90 分钟版本,保留了原 `baseline/`、`src/`、`workloads/` 和 `tools/`。
|
|
24
29
|
|
|
25
30
|
## 构建
|
|
26
31
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client/index.js
CHANGED
|
@@ -111,6 +111,7 @@ window.__ModuleLoader__.load({
|
|
|
111
111
|
const [notice, setNotice] = (0, react.useState)("");
|
|
112
112
|
const [error, setError] = (0, react.useState)("");
|
|
113
113
|
const [workspaceId, setWorkspaceId] = (0, react.useState)("");
|
|
114
|
+
const [taskId, setTaskId] = (0, react.useState)("");
|
|
114
115
|
const [check, setCheck] = (0, react.useState)(null);
|
|
115
116
|
const [manual, setManual] = (0, react.useState)(null);
|
|
116
117
|
const [prompt, setPrompt] = (0, react.useState)("");
|
|
@@ -135,6 +136,9 @@ window.__ModuleLoader__.load({
|
|
|
135
136
|
}, 2e3);
|
|
136
137
|
return () => clearInterval(timer);
|
|
137
138
|
}, []);
|
|
139
|
+
(0, react.useEffect)(() => {
|
|
140
|
+
if (taskId === "" && state !== null && state.tasks.length > 0) setTaskId(state.tasks[0].id);
|
|
141
|
+
}, [state, taskId]);
|
|
138
142
|
const ready = snapshot.status === "ready" && snapshot.writable;
|
|
139
143
|
const save = async (patch) => {
|
|
140
144
|
if (!ready || snapshot.revision === void 0) return;
|
|
@@ -183,7 +187,7 @@ window.__ModuleLoader__.load({
|
|
|
183
187
|
setPendingEnable(false);
|
|
184
188
|
save({ enabled: true });
|
|
185
189
|
};
|
|
186
|
-
const loadManual = (id) => {
|
|
190
|
+
const loadManual = (id, task) => {
|
|
187
191
|
if (id === "") {
|
|
188
192
|
setCheck(null);
|
|
189
193
|
setManual(null);
|
|
@@ -192,14 +196,14 @@ window.__ModuleLoader__.load({
|
|
|
192
196
|
}
|
|
193
197
|
fetch(CHECK_ROUTE + "?workspaceId=" + encodeURIComponent(id)).then(readJson).then((result) => {
|
|
194
198
|
setCheck(result);
|
|
195
|
-
if (result.ok) fetch("/api/eval-guard/manual?workspaceId=" + encodeURIComponent(id)).then(readJson).then((paths) => {
|
|
199
|
+
if (result.ok) fetch("/api/eval-guard/manual?workspaceId=" + encodeURIComponent(id) + "&task=" + encodeURIComponent(task)).then(readJson).then((paths) => {
|
|
196
200
|
setManual(paths);
|
|
197
201
|
setPrompt(paths.prompt);
|
|
198
202
|
}).catch(() => void 0);
|
|
199
203
|
}).catch((reason) => setError(reason instanceof Error ? reason.message : String(reason)));
|
|
200
204
|
};
|
|
201
205
|
const install = async () => {
|
|
202
|
-
if (workspaceId === "") return;
|
|
206
|
+
if (workspaceId === "" || taskId === "") return;
|
|
203
207
|
setError("");
|
|
204
208
|
setNotice("");
|
|
205
209
|
const checked = await fetch(CHECK_ROUTE + "?workspaceId=" + encodeURIComponent(workspaceId)).then(readJson);
|
|
@@ -211,7 +215,10 @@ window.__ModuleLoader__.load({
|
|
|
211
215
|
const result = await fetch(INSTALL_ROUTE, {
|
|
212
216
|
method: "POST",
|
|
213
217
|
headers: { "content-type": "application/json" },
|
|
214
|
-
body: JSON.stringify({
|
|
218
|
+
body: JSON.stringify({
|
|
219
|
+
workspaceId,
|
|
220
|
+
task: taskId
|
|
221
|
+
})
|
|
215
222
|
}).then(readJson);
|
|
216
223
|
if (!result.ok || result.prompt === void 0) {
|
|
217
224
|
setError(result.error ?? "安装失败");
|
|
@@ -401,9 +408,28 @@ window.__ModuleLoader__.load({
|
|
|
401
408
|
style: note,
|
|
402
409
|
children: t("settings.taskHint")
|
|
403
410
|
}),
|
|
404
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
411
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
405
412
|
style: row,
|
|
406
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("settings.taskLabel"), ":
|
|
413
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("settings.taskLabel"), ":"] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
414
|
+
value: taskId,
|
|
415
|
+
disabled: state === null || state.tasks.length === 0,
|
|
416
|
+
onChange: (event) => {
|
|
417
|
+
const next = event.target.value;
|
|
418
|
+
setTaskId(next);
|
|
419
|
+
setCheck(null);
|
|
420
|
+
setManual(null);
|
|
421
|
+
setPrompt("");
|
|
422
|
+
if (workspaceId !== "") loadManual(workspaceId, next);
|
|
423
|
+
},
|
|
424
|
+
children: (state?.tasks ?? []).map((task) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
425
|
+
value: task.id,
|
|
426
|
+
children: task.label
|
|
427
|
+
}, task.id))
|
|
428
|
+
})]
|
|
429
|
+
}),
|
|
430
|
+
state !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
431
|
+
style: note,
|
|
432
|
+
children: state.tasks.find((task) => task.id === taskId)?.description ?? ""
|
|
407
433
|
}),
|
|
408
434
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
409
435
|
style: row,
|
|
@@ -413,7 +439,7 @@ window.__ModuleLoader__.load({
|
|
|
413
439
|
value: workspaceId,
|
|
414
440
|
onChange: (event) => {
|
|
415
441
|
setWorkspaceId(event.target.value);
|
|
416
|
-
loadManual(event.target.value);
|
|
442
|
+
loadManual(event.target.value, taskId);
|
|
417
443
|
},
|
|
418
444
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
419
445
|
value: "",
|
|
@@ -425,7 +451,7 @@ window.__ModuleLoader__.load({
|
|
|
425
451
|
}),
|
|
426
452
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
427
453
|
style: primary,
|
|
428
|
-
disabled: workspaceId === "",
|
|
454
|
+
disabled: workspaceId === "" || taskId === "",
|
|
429
455
|
onClick: () => void install(),
|
|
430
456
|
children: t("settings.check")
|
|
431
457
|
})
|
package/lib/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["useSyncExternalStore","useState","useState"],"sources":["../../src/shared.ts","../../src/client/bridge.ts","../../src/client/Settings.tsx","../../src/client/Toast.tsx","../../src/client/locales.ts","../../src/client/index.ts"],"sourcesContent":["/**\n * Wire types shared by the host and browser halves. Keep this module import-free:\n * the client bundle includes it without pulling host code.\n * @module dsh-eval-guard/shared\n */\n\n/** Settings namespace; also the Loader row id in cordis.patch.yml. */\nexport const SETTINGS_NS = 'eval-guard'\n\n/** Route paths owned by this plugin. */\nexport const STATE_ROUTE = '/api/eval-guard/state'\nexport const CHECK_ROUTE = '/api/eval-guard/check'\nexport const INSTALL_ROUTE = '/api/eval-guard/install'\nexport const PROMPT_ROUTE = '/api/eval-guard/prompt'\nexport const EVENTS_ROUTE = '/api/eval-guard/events'\nexport const REVEAL_ROUTE = '/api/eval-guard/reveal'\nexport const MANUAL_ROUTE = '/api/eval-guard/manual'\n\n/** The only task implemented today. */\nexport const TANK_TASK = 'tank'\nexport const TANK_TASK_LABEL = '第 6 集 · 坦克大战'\n\n/** One task offered by the installer. */\nexport interface TaskInfo {\n id: string\n label: string\n description: string\n}\n\n/** A live conversation the enable-time warning has to mention. */\nexport interface RunningSession {\n sessionId: string\n title: string\n cwd: string\n running: boolean\n jobs: number\n}\n\n/** One warning surfaced to the browser. */\nexport interface GuardEvent {\n seq: number\n at: number\n sessionId?: string\n runId?: string\n kind: 'tool-blocked' | 'path-denied' | 'process-denied' | 'wait-blocked' | 'timeout' | 'notice'\n tool?: string\n detail: string\n}\n\n/** Host answer for the enable pre-check. */\nexport interface GuardState {\n enabled: boolean\n running: RunningSession[]\n tasks: TaskInfo[]\n defaultBlacklist: string\n generatedDefault: boolean\n workspaceRoot: string\n runsRoot: string\n}\n\n/** Result of checking one workspace before install. */\nexport interface CheckResult {\n ok: boolean\n workspaceId: string\n path: string\n empty: boolean\n entries: string[]\n error?: string\n}\n\n/** Result of installing the task pack. */\nexport interface InstallResult {\n ok: boolean\n error?: string\n runId?: string\n workspaceId?: string\n workspacePath?: string\n prompt?: string\n}\n\n/** Manual-channel material locations. */\nexport interface ManualPaths {\n source: string\n prompt: string\n}\n\n/** Usage record written next to one run. */\nexport interface RunUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n reasoningTokens: number\n toolCalls: number\n turns: number\n steps: number\n wallTimeMs: number\n startedAt: number | null\n updatedAt: number\n}\n\n/** Metadata for one guarded task run. */\nexport interface RunRecord {\n runId: string\n task: string\n workspaceId: string\n workspacePath: string\n sessionId: string | null\n createdAt: number\n startedAt: number | null\n endedAt: number | null\n status: 'prepared' | 'active' | 'timeout' | 'ended'\n prompt: string\n timeLimitMinutes: number\n provider?: string\n model?: string\n title?: string\n}\n","/** Shell capabilities published to the Settings panel. */\nexport interface EvalWorkspace { id: string; label: string }\nexport interface EvalBridge {\n startSession(workspaceId: string, prompt: string): void\n workspaces(): readonly EvalWorkspace[]\n}\nlet current: EvalBridge | undefined\nexport function configureEvalBridge(next: EvalBridge | undefined): void { current = next }\nexport function evalBridge(): EvalBridge | undefined { return current }\n","/**\n * Settings panel for the evaluation guard.\n * @module dsh-eval-guard/client/Settings\n */\nimport { useEffect, useState, useSyncExternalStore, type CSSProperties, type ReactElement } from 'react'\nimport {\n CHECK_ROUTE, EVENTS_ROUTE, INSTALL_ROUTE, MANUAL_ROUTE, REVEAL_ROUTE, STATE_ROUTE,\n type CheckResult, type GuardEvent, type GuardState, type InstallResult, type ManualPaths,\n} from '../shared.ts'\nimport { evalBridge } from './bridge.ts'\n\ntype Snapshot = {\n status: 'loading' | 'ready' | 'unavailable'\n value?: Record<string, unknown>\n revision?: number\n writable: boolean\n}\ntype Form = {\n getSnapshot: () => Snapshot\n subscribe: (listener: () => void) => () => void\n mutate: (ops: readonly { op: 'set'; path: string[]; value: unknown }[], revision?: number) => Promise<boolean>\n}\ntype Props = { form: Form; t: (key: string) => string }\n\ninterface Draft {\n enabled: boolean\n timeLimitEnabled: boolean\n timeLimitMinutes: number\n forbiddenPaths: string\n toolBlacklistUseDefault: boolean\n toolBlacklist: string\n}\n\nconst EMPTY: Draft = {\n enabled: false,\n timeLimitEnabled: true,\n timeLimitMinutes: 90,\n forbiddenPaths: '',\n toolBlacklistUseDefault: true,\n toolBlacklist: '',\n}\n\nconst box: CSSProperties = { border: '1px solid var(--vscode-panel-border, #3c3c3c)', borderRadius: 8, padding: 14, marginBottom: 14 }\nconst row: CSSProperties = { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 8 }\nconst note: CSSProperties = { opacity: 0.72, fontSize: 12, lineHeight: 1.5, margin: '6px 0' }\nconst textarea: CSSProperties = { width: '100%', minHeight: 110, fontFamily: 'monospace', fontSize: 12, padding: 8, boxSizing: 'border-box' }\nconst button: CSSProperties = { padding: '5px 12px', borderRadius: 6, cursor: 'pointer' }\nconst primary: CSSProperties = { ...button, background: '#2f6fd0', color: '#fff', border: 'none' }\nconst danger: CSSProperties = { ...button, background: '#a33', color: '#fff', border: 'none' }\n\nasync function readJson<T>(response: Response): Promise<T> { return await response.json() as T }\n\nfunction normalize(value: Record<string, unknown> | undefined): Draft {\n return {\n enabled: value?.enabled === true,\n timeLimitEnabled: value?.timeLimitEnabled !== false,\n timeLimitMinutes: typeof value?.timeLimitMinutes === 'number' ? value.timeLimitMinutes : 90,\n forbiddenPaths: typeof value?.forbiddenPaths === 'string' ? value.forbiddenPaths : '',\n toolBlacklistUseDefault: value?.toolBlacklistUseDefault !== false,\n toolBlacklist: typeof value?.toolBlacklist === 'string' ? value.toolBlacklist : '',\n }\n}\n\n/** The settings card. */\nexport function EvalGuardSettings({ form, t }: Props): ReactElement {\n const snapshot = useSyncExternalStore(form.subscribe, form.getSnapshot, form.getSnapshot)\n const [draft, setDraft] = useState<Draft>(EMPTY)\n const [dirty, setDirty] = useState(false)\n const [state, setState] = useState<GuardState | null>(null)\n const [events, setEvents] = useState<GuardEvent[]>([])\n const [pendingEnable, setPendingEnable] = useState(false)\n const [notice, setNotice] = useState('')\n const [error, setError] = useState('')\n const [workspaceId, setWorkspaceId] = useState('')\n const [check, setCheck] = useState<CheckResult | null>(null)\n const [manual, setManual] = useState<ManualPaths | null>(null)\n const [prompt, setPrompt] = useState('')\n const [copied, setCopied] = useState('')\n const workspaces = evalBridge()?.workspaces() ?? []\n\n useEffect(() => {\n if (snapshot.status !== 'ready' || snapshot.value === undefined || dirty) return\n setDraft(normalize(snapshot.value))\n }, [snapshot, dirty])\n\n const loadState = (): void => {\n fetch(STATE_ROUTE).then(readJson<GuardState>).then(setState).catch(() => setState(null))\n }\n useEffect(() => {\n loadState()\n let since = 0\n const timer = setInterval(() => {\n fetch(EVENTS_ROUTE + '?since=' + String(since)).then(readJson<{ events: GuardEvent[] }>).then(data => {\n if (data.events.length === 0) return\n since = data.events[data.events.length - 1]!.seq\n setEvents(current => [...data.events, ...current].slice(0, 12))\n }).catch(() => undefined)\n }, 2000)\n return () => clearInterval(timer)\n }, [])\n\n const ready = snapshot.status === 'ready' && snapshot.writable\n const save = async (patch: Partial<Draft>): Promise<void> => {\n if (!ready || snapshot.revision === undefined) return\n const next = { ...draft, ...patch }\n const ops: { op: 'set'; path: string[]; value: unknown }[] = []\n for (const key of Object.keys(patch) as (keyof Draft)[]) ops.push({ op: 'set', path: [key], value: next[key] })\n try {\n const accepted = await form.mutate(ops, snapshot.revision)\n if (!accepted) { setError('保存冲突,请刷新后重试。'); return }\n setDraft(next)\n setDirty(false)\n setError('')\n loadState()\n } catch (reason: unknown) {\n setError(reason instanceof Error ? reason.message : String(reason))\n }\n }\n const edit = (patch: Partial<Draft>): void => {\n setDraft(current => ({ ...current, ...patch }))\n setDirty(true)\n }\n\n const toggleEnabled = (next: boolean): void => {\n if (!next) { void save({ enabled: false }); return }\n fetch(STATE_ROUTE).then(readJson<GuardState>).then(latest => {\n setState(latest)\n if (latest.running.length > 0) setPendingEnable(true)\n else void save({ enabled: true })\n }).catch(() => void save({ enabled: true }))\n }\n\n const forceEnable = (): void => {\n setPendingEnable(false)\n void save({ enabled: true })\n }\n\n const loadManual = (id: string): void => {\n if (id === '') { setCheck(null); setManual(null); setPrompt(''); return }\n fetch(CHECK_ROUTE + '?workspaceId=' + encodeURIComponent(id)).then(readJson<CheckResult>).then(result => {\n setCheck(result)\n if (result.ok) {\n fetch(MANUAL_ROUTE + '?workspaceId=' + encodeURIComponent(id)).then(readJson<ManualPaths>).then(paths => {\n setManual(paths)\n setPrompt(paths.prompt)\n }).catch(() => undefined)\n }\n }).catch((reason: unknown) => setError(reason instanceof Error ? reason.message : String(reason)))\n }\n\n const install = async (): Promise<void> => {\n if (workspaceId === '') return\n setError(''); setNotice('')\n const checked = await fetch(CHECK_ROUTE + '?workspaceId=' + encodeURIComponent(workspaceId)).then(readJson<CheckResult>)\n setCheck(checked)\n if (!checked.ok || !checked.empty) {\n setError(checked.error ?? t('settings.nonEmpty'))\n return\n }\n const result = await fetch(INSTALL_ROUTE, {\n method: 'POST', headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ workspaceId }),\n }).then(readJson<InstallResult>)\n if (!result.ok || result.prompt === undefined) { setError(result.error ?? '安装失败'); return }\n setPrompt(result.prompt)\n setNotice(t('settings.installOk'))\n evalBridge()?.startSession(workspaceId, result.prompt)\n loadState()\n }\n\n const copy = async (label: string, text: string): Promise<void> => {\n try {\n await navigator.clipboard.writeText(text)\n setCopied(label)\n setTimeout(() => setCopied(''), 1500)\n } catch { setError('复制失败') }\n }\n const reveal = (target: string): void => {\n void fetch(REVEAL_ROUTE, {\n method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ path: target }),\n })\n }\n const blacklistText = draft.toolBlacklistUseDefault ? state?.defaultBlacklist ?? '' : draft.toolBlacklist\n\n if (snapshot.status === 'loading') return <div style={box}>{t('settings.loading')}</div>\n return <div>\n {pendingEnable && <div style={{ ...box, borderColor: '#caa23a' }}>\n <strong>{t('settings.warningTitle')}</strong>\n <p style={note}>{t('settings.runningWarning')}</p>\n <ul style={{ ...note, paddingLeft: 18 }}>{(state?.running ?? []).map(item =>\n <li key={item.sessionId}>{item.title} · {item.cwd}{item.jobs > 0 ? ' · jobs: ' + String(item.jobs) : ''}</li>)}</ul>\n <div style={row}>\n <button style={button} onClick={() => { setPendingEnable(false); loadState() }}>{t('settings.runningCancel')}</button>\n <button style={danger} onClick={forceEnable}>{t('settings.runningForce')}</button>\n </div>\n </div>}\n\n <section style={box}>\n <div style={row}>\n <label><input type=\"checkbox\" checked={draft.enabled} disabled={!ready} onChange={event => toggleEnabled(event.target.checked)} /> {t('settings.enabled')}</label>\n </div>\n <p style={note}>{t('settings.enabledHint')}</p>\n </section>\n\n <section style={box}>\n <div style={row}>\n <label><input type=\"checkbox\" checked={draft.timeLimitEnabled} disabled={!ready} onChange={event => void save({ timeLimitEnabled: event.target.checked })} /> {t('settings.timeLimit')}</label>\n <input type=\"number\" min={1} max={1440} value={draft.timeLimitMinutes} disabled={!ready}\n style={{ width: 90 }} onChange={event => edit({ timeLimitMinutes: Number(event.target.value) })}\n onBlur={() => void save({ timeLimitMinutes: draft.timeLimitMinutes })} />\n <span style={note}>{t('settings.minutes')}</span>\n </div>\n <p style={note}>{t('settings.timeLimitHint')}</p>\n </section>\n\n <section style={box}>\n <strong>{t('settings.forbidden')}</strong>\n <p style={note}>{t('settings.forbiddenHint')}</p>\n <textarea style={textarea} value={draft.forbiddenPaths} disabled={!ready}\n onChange={event => edit({ forbiddenPaths: event.target.value })}\n onBlur={() => void save({ forbiddenPaths: draft.forbiddenPaths })} />\n </section>\n\n <section style={box}>\n <div style={row}>\n <strong>{t('settings.blacklist')}</strong>\n <button style={button} disabled={!ready} onClick={() => void save({ toolBlacklistUseDefault: true, toolBlacklist: '' })}>{t('settings.reset')}</button>\n </div>\n <p style={note}>{t('settings.blacklistHint')}</p>\n <textarea style={textarea} value={blacklistText} disabled={!ready}\n onChange={event => edit({ toolBlacklistUseDefault: false, toolBlacklist: event.target.value })}\n onBlur={() => void save({ toolBlacklistUseDefault: draft.toolBlacklistUseDefault, toolBlacklist: draft.toolBlacklist })} />\n {draft.toolBlacklistUseDefault && <p style={note}>{t('settings.editingDefault')}</p>}\n </section>\n\n <section style={box}>\n <strong>{t('settings.task')}</strong>\n <p style={note}>{t('settings.taskHint')}</p>\n <div style={row}>\n <span>{t('settings.taskLabel')}: 第 6 集 · 坦克大战</span>\n </div>\n <div style={row}>\n <span>{t('settings.workspace')}:</span>\n <select value={workspaceId} onChange={event => { setWorkspaceId(event.target.value); loadManual(event.target.value) }}>\n <option value=\"\">{t('settings.pickWorkspace')}</option>\n {workspaces.map(workspace => <option key={workspace.id} value={workspace.id}>{workspace.label}</option>)}\n </select>\n <button style={primary} disabled={workspaceId === ''} onClick={() => void install()}>{t('settings.check')}</button>\n </div>\n {workspaces.length === 0 && <p style={note}>{t('settings.noWorkspaces')}</p>}\n {check !== null && !check.empty && <p style={{ ...note, color: '#d99' }}>{t('settings.nonEmpty')} ({check.entries.slice(0, 6).join(', ')})</p>}\n {notice !== '' && <p style={{ ...note, color: '#7c7' }}>{notice}</p>}\n {error !== '' && <p style={{ ...note, color: '#d77' }}>{error}</p>}\n </section>\n\n <section style={box}>\n <strong>{t('settings.manual')}</strong>\n <p style={note}>{t('settings.manualHint')}</p>\n <div style={row}><span>{t('settings.source')}:</span><code style={{ wordBreak: 'break-all' }}>{manual?.source ?? '—'}</code>\n {manual !== null && <button style={button} onClick={() => reveal(manual.source)}>{t('settings.reveal')}</button>}</div>\n <div style={row}><span>{t('settings.target')}:</span><code style={{ wordBreak: 'break-all' }}>{check?.path ?? '—'}</code>\n {check?.path !== undefined && check.path !== '' && <button style={button} onClick={() => reveal(check.path)}>{t('settings.reveal')}</button>}</div>\n <div style={row}><button style={button} disabled={prompt === ''} onClick={() => void copy('prompt', prompt)}>{copied === 'prompt' ? t('settings.copied') : t('settings.copy')}</button></div>\n <textarea style={{ ...textarea, minHeight: 160 }} readOnly value={prompt} placeholder={t('settings.prompt')} />\n </section>\n\n <section style={box}>\n <strong>{t('settings.recent')}</strong>\n {events.length === 0 && <p style={note}>{t('settings.none')}</p>}\n {events.map(event => <div key={event.seq} style={{ ...note, borderTop: '1px solid #333', paddingTop: 4 }}>\n <strong>[{event.kind}]</strong> {event.tool !== undefined ? event.tool + ' · ' : ''}{event.detail}\n </div>)}\n </section>\n </div>\n}\n","/**\n * Frame-wide warning toasts fed by the host event route.\n * @module dsh-eval-guard/client/Toast\n */\nimport { useEffect, useState, type ReactElement } from 'react'\nimport { EVENTS_ROUTE, type GuardEvent } from '../shared.ts'\n\n/** One toast with a dismiss deadline. */\ninterface Shown { event: GuardEvent; until: number }\n\n/** Poll the host event feed and show new warnings. */\nexport function GuardToast({ t }: { t: (key: string) => string }): ReactElement | null {\n const [shown, setShown] = useState<Shown[]>([])\n useEffect(() => {\n let since = 0\n let alive = true\n const poll = (): void => {\n fetch(EVENTS_ROUTE + '?since=' + String(since)).then(async response => await response.json() as { events: GuardEvent[] }).then(data => {\n if (!alive || data.events.length === 0) return\n since = data.events[data.events.length - 1]!.seq\n const warnings = data.events.filter(event => event.kind !== 'notice')\n if (warnings.length === 0) return\n const now = Date.now()\n setShown(current => [...warnings.map(event => ({ event, until: now + 8000 })), ...current].slice(0, 5))\n }).catch(() => undefined)\n }\n const timer = setInterval(() => {\n poll()\n const now = Date.now()\n setShown(current => current.filter(item => item.until > now))\n }, 1500)\n poll()\n return () => { alive = false; clearInterval(timer) }\n }, [])\n if (shown.length === 0) return null\n return <div style={{ position: 'fixed', right: 18, bottom: 18, zIndex: 9999, display: 'flex', flexDirection: 'column', gap: 8, maxWidth: 420 }}>\n {shown.map(item => <div key={item.event.seq} style={{\n background: '#3a2a12', color: '#ffdca8', border: '1px solid #8a6a2a', borderRadius: 8,\n padding: '10px 12px', fontSize: 12, lineHeight: 1.5, boxShadow: '0 6px 24px rgba(0,0,0,.45)',\n }}>\n <strong>{t('settings.warningTitle')}</strong>\n <div>{item.event.detail}</div>\n </div>)}\n </div>\n}\n","/** Locale namespace and dictionaries for the browser half. */\nexport const NS = 'evalGuard'\n\nexport const zh = {\n 'settings.nav': '评测守护',\n 'settings.title': '评测守护',\n 'settings.enabled': '启用评测守护',\n 'settings.enabledHint': '启用后,所有用户主对话只能读写自己的工作区,禁止联网、子代理与跨 agent 通信,并按任务时限强制终止。默认关闭。',\n 'settings.runningWarning': '当前有正在运行的对话。启用评测守护会立即约束它们,是否强行开启?',\n 'settings.runningCancel': '放弃',\n 'settings.runningForce': '强行开启',\n 'settings.timeLimit': '开启最长时长',\n 'settings.timeLimitHint': '从每条用户消息发送时开始计时;到点只终止当前轮次,并递归清理子代理与后台任务。',\n 'settings.minutes': '分钟',\n 'settings.forbidden': '额外禁止路径',\n 'settings.forbiddenHint': '每行一个绝对路径;# 开头为注释;! 开头表示在禁止范围内放开该子路径;按行顺序生效。默认空。',\n 'settings.blacklist': '工具黑名单',\n 'settings.blacklistHint': '每行一个工具名;默认内容按当前已安装工具生成,不存在的工具会被忽略。',\n 'settings.reset': '恢复默认黑名单',\n 'settings.editingDefault': '编辑后将以自定义黑名单保存。',\n 'settings.task': '安装评测任务',\n 'settings.taskHint': '选择一个空的工作区,复制坦克大战素材并把初始提示词填入新会话输入框。',\n 'settings.taskLabel': '任务',\n 'settings.workspace': '工作区',\n 'settings.pickWorkspace': '请选择工作区',\n 'settings.check': '检测并安装',\n 'settings.nonEmpty': '工作区不是空的;未执行任何操作。',\n 'settings.manual': '手动通道',\n 'settings.manualHint': '手动复制素材:自己把素材目录内容复制到工作区根目录;手动填充提示词:复制下面的提示词。',\n 'settings.source': '素材路径',\n 'settings.target': '工作区路径',\n 'settings.copy': '复制',\n 'settings.copied': '已复制',\n 'settings.prompt': '初始提示词',\n 'settings.reveal': '在文件管理器中打开',\n 'settings.noWorkspaces': '没有可选的工作区。',\n 'settings.installOk': '任务素材已安装,提示词已填入新会话,请自行发送。',\n 'settings.loading': '加载中…',\n 'settings.recent': '最近事件',\n 'settings.none': '暂无。',\n 'settings.warningTitle': '评测守护警告',\n 'settings.close': '关闭',\n} as const\n\nexport type Key = keyof typeof zh\n\nexport const en: Record<Key, string> = {\n 'settings.nav': 'Eval Guard',\n 'settings.title': 'Eval Guard',\n 'settings.enabled': 'Enable evaluation guard',\n 'settings.enabledHint': 'When on, every user main conversation may only read/write its own workspace, with network, subagents and cross-agent channels blocked, and a wall-clock limit. Off by default.',\n 'settings.runningWarning': 'Conversations are running right now. Enabling the guard will constrain them immediately. Force enable?',\n 'settings.runningCancel': 'Cancel',\n 'settings.runningForce': 'Force enable',\n 'settings.timeLimit': 'Enable time limit',\n 'settings.timeLimitHint': 'The clock starts at each user message; at the deadline only the current turn is cancelled, and subagents/jobs are cleaned up recursively.',\n 'settings.minutes': 'minutes',\n 'settings.forbidden': 'Extra forbidden paths',\n 'settings.forbiddenHint': 'One absolute path per line; # comments; ! re-allows a subpath; later lines win. Empty by default.',\n 'settings.blacklist': 'Tool blacklist',\n 'settings.blacklistHint': 'One tool name per line; the default is generated from the tools installed now and missing tools are ignored.',\n 'settings.reset': 'Reset default blacklist',\n 'settings.editingDefault': 'Editing switches this to a custom blacklist.',\n 'settings.task': 'Install evaluation task',\n 'settings.taskHint': 'Pick an empty workspace, copy the tank pack in, and seed the initial prompt into a new conversation.',\n 'settings.taskLabel': 'Task',\n 'settings.workspace': 'Workspace',\n 'settings.pickWorkspace': 'Pick a workspace',\n 'settings.check': 'Check and install',\n 'settings.nonEmpty': 'The workspace is not empty; nothing was changed.',\n 'settings.manual': 'Manual channel',\n 'settings.manualHint': 'Manual material copy: copy the source folder contents into the workspace root yourself. Manual prompt: copy the text below.',\n 'settings.source': 'Material path',\n 'settings.target': 'Workspace path',\n 'settings.copy': 'Copy',\n 'settings.copied': 'Copied',\n 'settings.prompt': 'Initial prompt',\n 'settings.reveal': 'Open in file manager',\n 'settings.noWorkspaces': 'No workspace is available.',\n 'settings.installOk': 'Task installed and the prompt was seeded into a new conversation; send it yourself.',\n 'settings.loading': 'Loading…',\n 'settings.recent': 'Recent events',\n 'settings.none': 'None.',\n 'settings.warningTitle': 'Eval Guard warning',\n 'settings.close': 'Close',\n}\n","/**\n * Evaluation guard, browser half: the Settings section, the warning toast, and\n * the shell bridge used to seed a new conversation with the task prompt.\n * @module dsh-eval-guard/client\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { SETTINGS_NS } from '../shared.ts'\nimport { EvalGuardSettings } from './Settings.tsx'\nimport { GuardToast } from './Toast.tsx'\nimport { configureEvalBridge } from './bridge.ts'\nimport { en, NS, zh, type Key } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n evalGuard: Key\n }\n}\n\nexport const inject = ['slots', 'locale', 'configForms', 'uiWorkspace']\n\nexport function apply(ctx: Context): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }))\n configureEvalBridge({\n startSession: (workspaceId, prompt) => {\n ctx.uiWorkspace.startSession(workspaceId as Parameters<typeof ctx.uiWorkspace.startSession>[0], { prompt })\n },\n workspaces: () => {\n const service = (ctx as unknown as { get(key: string): unknown }).get('workspaces') as\n { list?: { getSnapshot(): { items?: readonly Record<string, unknown>[] } } } | undefined\n const items = service?.list?.getSnapshot().items ?? []\n return items.map((item, index) => ({\n id: typeof item['workspaceId'] === 'string' ? item['workspaceId'] : String(item['id'] ?? index),\n label: typeof item['title'] === 'string' && item['title'] !== ''\n ? item['title']\n : String(item['path'] ?? item['workspaceId'] ?? ('workspace ' + String(index + 1))),\n }))\n },\n })\n ctx.effect(() => () => { configureEvalBridge(undefined) })\n const t = ctx.locale.bind(NS)\n const form = ctx.configForms.get(SETTINGS_NS) as {\n getSnapshot(): unknown\n subscribe(listener: () => void): () => void\n mutate(ops: never, revision?: number): unknown\n }\n const formFace = {\n getSnapshot: () => form.getSnapshot(),\n subscribe: (listener: () => void) => form.subscribe(listener),\n mutate: (ops: never, revision?: number) => form.mutate(ops, revision),\n }\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section', id: SETTINGS_NS, order: 41,\n label: () => t('settings.nav'), locale: NS,\n inject: () => ({ form: formFace }),\n }, EvalGuardSettings))\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay', id: SETTINGS_NS + '.toast', locale: NS,\n inject: () => ({ t }),\n }, GuardToast))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["useSyncExternalStore","useState","useState"],"sources":["../../src/shared.ts","../../src/client/bridge.ts","../../src/client/Settings.tsx","../../src/client/Toast.tsx","../../src/client/locales.ts","../../src/client/index.ts"],"sourcesContent":["/**\n * Wire types shared by the host and browser halves. Keep this module import-free:\n * the client bundle includes it without pulling host code.\n * @module dsh-eval-guard/shared\n */\n\n/** Settings namespace; also the Loader row id in cordis.patch.yml. */\nexport const SETTINGS_NS = 'eval-guard'\n\n/** Route paths owned by this plugin. */\nexport const STATE_ROUTE = '/api/eval-guard/state'\nexport const CHECK_ROUTE = '/api/eval-guard/check'\nexport const INSTALL_ROUTE = '/api/eval-guard/install'\nexport const PROMPT_ROUTE = '/api/eval-guard/prompt'\nexport const EVENTS_ROUTE = '/api/eval-guard/events'\nexport const REVEAL_ROUTE = '/api/eval-guard/reveal'\nexport const MANUAL_ROUTE = '/api/eval-guard/manual'\n\n/** Supported task ids. */\nexport const TANK_TASK = 'tank'\nexport const TANK_TASK_LABEL = '第 6 集 · 坦克大战'\nexport const WAREHOUSE_TASK = 'warehouse'\nexport const WAREHOUSE_TASK_LABEL = '第 5 集 · 仓储调度'\nexport const LAND_TASK = 'land'\nexport const LAND_TASK_LABEL = '第 1 集 · 圈地'\nexport const BOMBERMAN_TASK = 'bomberman'\nexport const BOMBERMAN_TASK_LABEL = '第 2 集 · 炸弹人'\nexport const CTF_TASK = 'ctf'\nexport const CTF_TASK_LABEL = '第 3 集 · 夺旗'\n\n/** One task offered by the installer. */\nexport interface TaskInfo {\n id: string\n label: string\n description: string\n}\n\n/** A live conversation the enable-time warning has to mention. */\nexport interface RunningSession {\n sessionId: string\n title: string\n cwd: string\n running: boolean\n jobs: number\n}\n\n/** One warning surfaced to the browser. */\nexport interface GuardEvent {\n seq: number\n at: number\n sessionId?: string\n runId?: string\n kind: 'tool-blocked' | 'path-denied' | 'process-denied' | 'wait-blocked' | 'timeout' | 'notice'\n tool?: string\n detail: string\n}\n\n/** Host answer for the enable pre-check. */\nexport interface GuardState {\n enabled: boolean\n running: RunningSession[]\n tasks: TaskInfo[]\n defaultBlacklist: string\n generatedDefault: boolean\n workspaceRoot: string\n runsRoot: string\n}\n\n/** Result of checking one workspace before install. */\nexport interface CheckResult {\n ok: boolean\n workspaceId: string\n path: string\n empty: boolean\n entries: string[]\n error?: string\n}\n\n/** Result of installing the task pack. */\nexport interface InstallResult {\n ok: boolean\n error?: string\n runId?: string\n workspaceId?: string\n workspacePath?: string\n prompt?: string\n}\n\n/** Manual-channel material locations. */\nexport interface ManualPaths {\n source: string\n prompt: string\n}\n\n/** Usage record written next to one run. */\nexport interface RunUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n reasoningTokens: number\n toolCalls: number\n turns: number\n steps: number\n wallTimeMs: number\n startedAt: number | null\n updatedAt: number\n}\n\n/** Metadata for one guarded task run. */\nexport interface RunRecord {\n runId: string\n task: string\n workspaceId: string\n workspacePath: string\n sessionId: string | null\n createdAt: number\n startedAt: number | null\n endedAt: number | null\n status: 'prepared' | 'active' | 'timeout' | 'ended'\n prompt: string\n timeLimitMinutes: number\n provider?: string\n model?: string\n title?: string\n}\n","/** Shell capabilities published to the Settings panel. */\nexport interface EvalWorkspace { id: string; label: string }\nexport interface EvalBridge {\n startSession(workspaceId: string, prompt: string): void\n workspaces(): readonly EvalWorkspace[]\n}\nlet current: EvalBridge | undefined\nexport function configureEvalBridge(next: EvalBridge | undefined): void { current = next }\nexport function evalBridge(): EvalBridge | undefined { return current }\n","/**\n * Settings panel for the evaluation guard.\n * @module dsh-eval-guard/client/Settings\n */\nimport { useEffect, useState, useSyncExternalStore, type CSSProperties, type ReactElement } from 'react'\nimport {\n CHECK_ROUTE, EVENTS_ROUTE, INSTALL_ROUTE, MANUAL_ROUTE, REVEAL_ROUTE, STATE_ROUTE,\n type CheckResult, type GuardEvent, type GuardState, type InstallResult, type ManualPaths,\n} from '../shared.ts'\nimport { evalBridge } from './bridge.ts'\n\ntype Snapshot = {\n status: 'loading' | 'ready' | 'unavailable'\n value?: Record<string, unknown>\n revision?: number\n writable: boolean\n}\ntype Form = {\n getSnapshot: () => Snapshot\n subscribe: (listener: () => void) => () => void\n mutate: (ops: readonly { op: 'set'; path: string[]; value: unknown }[], revision?: number) => Promise<boolean>\n}\ntype Props = { form: Form; t: (key: string) => string }\n\ninterface Draft {\n enabled: boolean\n timeLimitEnabled: boolean\n timeLimitMinutes: number\n forbiddenPaths: string\n toolBlacklistUseDefault: boolean\n toolBlacklist: string\n}\n\nconst EMPTY: Draft = {\n enabled: false,\n timeLimitEnabled: true,\n timeLimitMinutes: 90,\n forbiddenPaths: '',\n toolBlacklistUseDefault: true,\n toolBlacklist: '',\n}\n\nconst box: CSSProperties = { border: '1px solid var(--vscode-panel-border, #3c3c3c)', borderRadius: 8, padding: 14, marginBottom: 14 }\nconst row: CSSProperties = { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 8 }\nconst note: CSSProperties = { opacity: 0.72, fontSize: 12, lineHeight: 1.5, margin: '6px 0' }\nconst textarea: CSSProperties = { width: '100%', minHeight: 110, fontFamily: 'monospace', fontSize: 12, padding: 8, boxSizing: 'border-box' }\nconst button: CSSProperties = { padding: '5px 12px', borderRadius: 6, cursor: 'pointer' }\nconst primary: CSSProperties = { ...button, background: '#2f6fd0', color: '#fff', border: 'none' }\nconst danger: CSSProperties = { ...button, background: '#a33', color: '#fff', border: 'none' }\n\nasync function readJson<T>(response: Response): Promise<T> { return await response.json() as T }\n\nfunction normalize(value: Record<string, unknown> | undefined): Draft {\n return {\n enabled: value?.enabled === true,\n timeLimitEnabled: value?.timeLimitEnabled !== false,\n timeLimitMinutes: typeof value?.timeLimitMinutes === 'number' ? value.timeLimitMinutes : 90,\n forbiddenPaths: typeof value?.forbiddenPaths === 'string' ? value.forbiddenPaths : '',\n toolBlacklistUseDefault: value?.toolBlacklistUseDefault !== false,\n toolBlacklist: typeof value?.toolBlacklist === 'string' ? value.toolBlacklist : '',\n }\n}\n\n/** The settings card. */\nexport function EvalGuardSettings({ form, t }: Props): ReactElement {\n const snapshot = useSyncExternalStore(form.subscribe, form.getSnapshot, form.getSnapshot)\n const [draft, setDraft] = useState<Draft>(EMPTY)\n const [dirty, setDirty] = useState(false)\n const [state, setState] = useState<GuardState | null>(null)\n const [events, setEvents] = useState<GuardEvent[]>([])\n const [pendingEnable, setPendingEnable] = useState(false)\n const [notice, setNotice] = useState('')\n const [error, setError] = useState('')\n const [workspaceId, setWorkspaceId] = useState('')\n const [taskId, setTaskId] = useState('')\n const [check, setCheck] = useState<CheckResult | null>(null)\n const [manual, setManual] = useState<ManualPaths | null>(null)\n const [prompt, setPrompt] = useState('')\n const [copied, setCopied] = useState('')\n const workspaces = evalBridge()?.workspaces() ?? []\n\n useEffect(() => {\n if (snapshot.status !== 'ready' || snapshot.value === undefined || dirty) return\n setDraft(normalize(snapshot.value))\n }, [snapshot, dirty])\n\n const loadState = (): void => {\n fetch(STATE_ROUTE).then(readJson<GuardState>).then(setState).catch(() => setState(null))\n }\n useEffect(() => {\n loadState()\n let since = 0\n const timer = setInterval(() => {\n fetch(EVENTS_ROUTE + '?since=' + String(since)).then(readJson<{ events: GuardEvent[] }>).then(data => {\n if (data.events.length === 0) return\n since = data.events[data.events.length - 1]!.seq\n setEvents(current => [...data.events, ...current].slice(0, 12))\n }).catch(() => undefined)\n }, 2000)\n return () => clearInterval(timer)\n }, [])\n\n useEffect(() => {\n if (taskId === '' && state !== null && state.tasks.length > 0) setTaskId(state.tasks[0]!.id)\n }, [state, taskId])\n\n const ready = snapshot.status === 'ready' && snapshot.writable\n const save = async (patch: Partial<Draft>): Promise<void> => {\n if (!ready || snapshot.revision === undefined) return\n const next = { ...draft, ...patch }\n const ops: { op: 'set'; path: string[]; value: unknown }[] = []\n for (const key of Object.keys(patch) as (keyof Draft)[]) ops.push({ op: 'set', path: [key], value: next[key] })\n try {\n const accepted = await form.mutate(ops, snapshot.revision)\n if (!accepted) { setError('保存冲突,请刷新后重试。'); return }\n setDraft(next)\n setDirty(false)\n setError('')\n loadState()\n } catch (reason: unknown) {\n setError(reason instanceof Error ? reason.message : String(reason))\n }\n }\n const edit = (patch: Partial<Draft>): void => {\n setDraft(current => ({ ...current, ...patch }))\n setDirty(true)\n }\n\n const toggleEnabled = (next: boolean): void => {\n if (!next) { void save({ enabled: false }); return }\n fetch(STATE_ROUTE).then(readJson<GuardState>).then(latest => {\n setState(latest)\n if (latest.running.length > 0) setPendingEnable(true)\n else void save({ enabled: true })\n }).catch(() => void save({ enabled: true }))\n }\n\n const forceEnable = (): void => {\n setPendingEnable(false)\n void save({ enabled: true })\n }\n\n const loadManual = (id: string, task: string): void => {\n if (id === '') { setCheck(null); setManual(null); setPrompt(''); return }\n fetch(CHECK_ROUTE + '?workspaceId=' + encodeURIComponent(id)).then(readJson<CheckResult>).then(result => {\n setCheck(result)\n if (result.ok) {\n fetch(MANUAL_ROUTE + '?workspaceId=' + encodeURIComponent(id) + '&task=' + encodeURIComponent(task)).then(readJson<ManualPaths>).then(paths => {\n setManual(paths)\n setPrompt(paths.prompt)\n }).catch(() => undefined)\n }\n }).catch((reason: unknown) => setError(reason instanceof Error ? reason.message : String(reason)))\n }\n\n const install = async (): Promise<void> => {\n if (workspaceId === '' || taskId === '') return\n setError(''); setNotice('')\n const checked = await fetch(CHECK_ROUTE + '?workspaceId=' + encodeURIComponent(workspaceId)).then(readJson<CheckResult>)\n setCheck(checked)\n if (!checked.ok || !checked.empty) {\n setError(checked.error ?? t('settings.nonEmpty'))\n return\n }\n const result = await fetch(INSTALL_ROUTE, {\n method: 'POST', headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ workspaceId, task: taskId }),\n }).then(readJson<InstallResult>)\n if (!result.ok || result.prompt === undefined) { setError(result.error ?? '安装失败'); return }\n setPrompt(result.prompt)\n setNotice(t('settings.installOk'))\n evalBridge()?.startSession(workspaceId, result.prompt)\n loadState()\n }\n\n const copy = async (label: string, text: string): Promise<void> => {\n try {\n await navigator.clipboard.writeText(text)\n setCopied(label)\n setTimeout(() => setCopied(''), 1500)\n } catch { setError('复制失败') }\n }\n const reveal = (target: string): void => {\n void fetch(REVEAL_ROUTE, {\n method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ path: target }),\n })\n }\n const blacklistText = draft.toolBlacklistUseDefault ? state?.defaultBlacklist ?? '' : draft.toolBlacklist\n\n if (snapshot.status === 'loading') return <div style={box}>{t('settings.loading')}</div>\n return <div>\n {pendingEnable && <div style={{ ...box, borderColor: '#caa23a' }}>\n <strong>{t('settings.warningTitle')}</strong>\n <p style={note}>{t('settings.runningWarning')}</p>\n <ul style={{ ...note, paddingLeft: 18 }}>{(state?.running ?? []).map(item =>\n <li key={item.sessionId}>{item.title} · {item.cwd}{item.jobs > 0 ? ' · jobs: ' + String(item.jobs) : ''}</li>)}</ul>\n <div style={row}>\n <button style={button} onClick={() => { setPendingEnable(false); loadState() }}>{t('settings.runningCancel')}</button>\n <button style={danger} onClick={forceEnable}>{t('settings.runningForce')}</button>\n </div>\n </div>}\n\n <section style={box}>\n <div style={row}>\n <label><input type=\"checkbox\" checked={draft.enabled} disabled={!ready} onChange={event => toggleEnabled(event.target.checked)} /> {t('settings.enabled')}</label>\n </div>\n <p style={note}>{t('settings.enabledHint')}</p>\n </section>\n\n <section style={box}>\n <div style={row}>\n <label><input type=\"checkbox\" checked={draft.timeLimitEnabled} disabled={!ready} onChange={event => void save({ timeLimitEnabled: event.target.checked })} /> {t('settings.timeLimit')}</label>\n <input type=\"number\" min={1} max={1440} value={draft.timeLimitMinutes} disabled={!ready}\n style={{ width: 90 }} onChange={event => edit({ timeLimitMinutes: Number(event.target.value) })}\n onBlur={() => void save({ timeLimitMinutes: draft.timeLimitMinutes })} />\n <span style={note}>{t('settings.minutes')}</span>\n </div>\n <p style={note}>{t('settings.timeLimitHint')}</p>\n </section>\n\n <section style={box}>\n <strong>{t('settings.forbidden')}</strong>\n <p style={note}>{t('settings.forbiddenHint')}</p>\n <textarea style={textarea} value={draft.forbiddenPaths} disabled={!ready}\n onChange={event => edit({ forbiddenPaths: event.target.value })}\n onBlur={() => void save({ forbiddenPaths: draft.forbiddenPaths })} />\n </section>\n\n <section style={box}>\n <div style={row}>\n <strong>{t('settings.blacklist')}</strong>\n <button style={button} disabled={!ready} onClick={() => void save({ toolBlacklistUseDefault: true, toolBlacklist: '' })}>{t('settings.reset')}</button>\n </div>\n <p style={note}>{t('settings.blacklistHint')}</p>\n <textarea style={textarea} value={blacklistText} disabled={!ready}\n onChange={event => edit({ toolBlacklistUseDefault: false, toolBlacklist: event.target.value })}\n onBlur={() => void save({ toolBlacklistUseDefault: draft.toolBlacklistUseDefault, toolBlacklist: draft.toolBlacklist })} />\n {draft.toolBlacklistUseDefault && <p style={note}>{t('settings.editingDefault')}</p>}\n </section>\n\n <section style={box}>\n <strong>{t('settings.task')}</strong>\n <p style={note}>{t('settings.taskHint')}</p>\n <div style={row}>\n <span>{t('settings.taskLabel')}:</span>\n <select value={taskId} disabled={state === null || state.tasks.length === 0}\n onChange={event => {\n const next = event.target.value\n setTaskId(next); setCheck(null); setManual(null); setPrompt('')\n if (workspaceId !== '') loadManual(workspaceId, next)\n }}>\n {(state?.tasks ?? []).map(task => <option key={task.id} value={task.id}>{task.label}</option>)}\n </select>\n </div>\n {state !== null && <p style={note}>{state.tasks.find(task => task.id === taskId)?.description ?? ''}</p>}\n <div style={row}>\n <span>{t('settings.workspace')}:</span>\n <select value={workspaceId} onChange={event => { setWorkspaceId(event.target.value); loadManual(event.target.value, taskId) }}>\n <option value=\"\">{t('settings.pickWorkspace')}</option>\n {workspaces.map(workspace => <option key={workspace.id} value={workspace.id}>{workspace.label}</option>)}\n </select>\n <button style={primary} disabled={workspaceId === '' || taskId === ''} onClick={() => void install()}>{t('settings.check')}</button>\n </div>\n {workspaces.length === 0 && <p style={note}>{t('settings.noWorkspaces')}</p>}\n {check !== null && !check.empty && <p style={{ ...note, color: '#d99' }}>{t('settings.nonEmpty')} ({check.entries.slice(0, 6).join(', ')})</p>}\n {notice !== '' && <p style={{ ...note, color: '#7c7' }}>{notice}</p>}\n {error !== '' && <p style={{ ...note, color: '#d77' }}>{error}</p>}\n </section>\n\n <section style={box}>\n <strong>{t('settings.manual')}</strong>\n <p style={note}>{t('settings.manualHint')}</p>\n <div style={row}><span>{t('settings.source')}:</span><code style={{ wordBreak: 'break-all' }}>{manual?.source ?? '—'}</code>\n {manual !== null && <button style={button} onClick={() => reveal(manual.source)}>{t('settings.reveal')}</button>}</div>\n <div style={row}><span>{t('settings.target')}:</span><code style={{ wordBreak: 'break-all' }}>{check?.path ?? '—'}</code>\n {check?.path !== undefined && check.path !== '' && <button style={button} onClick={() => reveal(check.path)}>{t('settings.reveal')}</button>}</div>\n <div style={row}><button style={button} disabled={prompt === ''} onClick={() => void copy('prompt', prompt)}>{copied === 'prompt' ? t('settings.copied') : t('settings.copy')}</button></div>\n <textarea style={{ ...textarea, minHeight: 160 }} readOnly value={prompt} placeholder={t('settings.prompt')} />\n </section>\n\n <section style={box}>\n <strong>{t('settings.recent')}</strong>\n {events.length === 0 && <p style={note}>{t('settings.none')}</p>}\n {events.map(event => <div key={event.seq} style={{ ...note, borderTop: '1px solid #333', paddingTop: 4 }}>\n <strong>[{event.kind}]</strong> {event.tool !== undefined ? event.tool + ' · ' : ''}{event.detail}\n </div>)}\n </section>\n </div>\n}\n","/**\n * Frame-wide warning toasts fed by the host event route.\n * @module dsh-eval-guard/client/Toast\n */\nimport { useEffect, useState, type ReactElement } from 'react'\nimport { EVENTS_ROUTE, type GuardEvent } from '../shared.ts'\n\n/** One toast with a dismiss deadline. */\ninterface Shown { event: GuardEvent; until: number }\n\n/** Poll the host event feed and show new warnings. */\nexport function GuardToast({ t }: { t: (key: string) => string }): ReactElement | null {\n const [shown, setShown] = useState<Shown[]>([])\n useEffect(() => {\n let since = 0\n let alive = true\n const poll = (): void => {\n fetch(EVENTS_ROUTE + '?since=' + String(since)).then(async response => await response.json() as { events: GuardEvent[] }).then(data => {\n if (!alive || data.events.length === 0) return\n since = data.events[data.events.length - 1]!.seq\n const warnings = data.events.filter(event => event.kind !== 'notice')\n if (warnings.length === 0) return\n const now = Date.now()\n setShown(current => [...warnings.map(event => ({ event, until: now + 8000 })), ...current].slice(0, 5))\n }).catch(() => undefined)\n }\n const timer = setInterval(() => {\n poll()\n const now = Date.now()\n setShown(current => current.filter(item => item.until > now))\n }, 1500)\n poll()\n return () => { alive = false; clearInterval(timer) }\n }, [])\n if (shown.length === 0) return null\n return <div style={{ position: 'fixed', right: 18, bottom: 18, zIndex: 9999, display: 'flex', flexDirection: 'column', gap: 8, maxWidth: 420 }}>\n {shown.map(item => <div key={item.event.seq} style={{\n background: '#3a2a12', color: '#ffdca8', border: '1px solid #8a6a2a', borderRadius: 8,\n padding: '10px 12px', fontSize: 12, lineHeight: 1.5, boxShadow: '0 6px 24px rgba(0,0,0,.45)',\n }}>\n <strong>{t('settings.warningTitle')}</strong>\n <div>{item.event.detail}</div>\n </div>)}\n </div>\n}\n","/** Locale namespace and dictionaries for the browser half. */\nexport const NS = 'evalGuard'\n\nexport const zh = {\n 'settings.nav': '评测守护',\n 'settings.title': '评测守护',\n 'settings.enabled': '启用评测守护',\n 'settings.enabledHint': '启用后,所有用户主对话只能读写自己的工作区,禁止联网、子代理与跨 agent 通信,并按任务时限强制终止。默认关闭。',\n 'settings.runningWarning': '当前有正在运行的对话。启用评测守护会立即约束它们,是否强行开启?',\n 'settings.runningCancel': '放弃',\n 'settings.runningForce': '强行开启',\n 'settings.timeLimit': '开启最长时长',\n 'settings.timeLimitHint': '从每条用户消息发送时开始计时;到点只终止当前轮次,并递归清理子代理与后台任务。',\n 'settings.minutes': '分钟',\n 'settings.forbidden': '额外禁止路径',\n 'settings.forbiddenHint': '每行一个绝对路径;# 开头为注释;! 开头表示在禁止范围内放开该子路径;按行顺序生效。默认空。',\n 'settings.blacklist': '工具黑名单',\n 'settings.blacklistHint': '每行一个工具名;默认内容按当前已安装工具生成,不存在的工具会被忽略。',\n 'settings.reset': '恢复默认黑名单',\n 'settings.editingDefault': '编辑后将以自定义黑名单保存。',\n 'settings.task': '安装评测任务',\n 'settings.taskHint': '选择一个空的工作区,复制坦克大战素材并把初始提示词填入新会话输入框。',\n 'settings.taskLabel': '任务',\n 'settings.workspace': '工作区',\n 'settings.pickWorkspace': '请选择工作区',\n 'settings.check': '检测并安装',\n 'settings.nonEmpty': '工作区不是空的;未执行任何操作。',\n 'settings.manual': '手动通道',\n 'settings.manualHint': '手动复制素材:自己把素材目录内容复制到工作区根目录;手动填充提示词:复制下面的提示词。',\n 'settings.source': '素材路径',\n 'settings.target': '工作区路径',\n 'settings.copy': '复制',\n 'settings.copied': '已复制',\n 'settings.prompt': '初始提示词',\n 'settings.reveal': '在文件管理器中打开',\n 'settings.noWorkspaces': '没有可选的工作区。',\n 'settings.installOk': '任务素材已安装,提示词已填入新会话,请自行发送。',\n 'settings.loading': '加载中…',\n 'settings.recent': '最近事件',\n 'settings.none': '暂无。',\n 'settings.warningTitle': '评测守护警告',\n 'settings.close': '关闭',\n} as const\n\nexport type Key = keyof typeof zh\n\nexport const en: Record<Key, string> = {\n 'settings.nav': 'Eval Guard',\n 'settings.title': 'Eval Guard',\n 'settings.enabled': 'Enable evaluation guard',\n 'settings.enabledHint': 'When on, every user main conversation may only read/write its own workspace, with network, subagents and cross-agent channels blocked, and a wall-clock limit. Off by default.',\n 'settings.runningWarning': 'Conversations are running right now. Enabling the guard will constrain them immediately. Force enable?',\n 'settings.runningCancel': 'Cancel',\n 'settings.runningForce': 'Force enable',\n 'settings.timeLimit': 'Enable time limit',\n 'settings.timeLimitHint': 'The clock starts at each user message; at the deadline only the current turn is cancelled, and subagents/jobs are cleaned up recursively.',\n 'settings.minutes': 'minutes',\n 'settings.forbidden': 'Extra forbidden paths',\n 'settings.forbiddenHint': 'One absolute path per line; # comments; ! re-allows a subpath; later lines win. Empty by default.',\n 'settings.blacklist': 'Tool blacklist',\n 'settings.blacklistHint': 'One tool name per line; the default is generated from the tools installed now and missing tools are ignored.',\n 'settings.reset': 'Reset default blacklist',\n 'settings.editingDefault': 'Editing switches this to a custom blacklist.',\n 'settings.task': 'Install evaluation task',\n 'settings.taskHint': 'Pick an empty workspace, copy the tank pack in, and seed the initial prompt into a new conversation.',\n 'settings.taskLabel': 'Task',\n 'settings.workspace': 'Workspace',\n 'settings.pickWorkspace': 'Pick a workspace',\n 'settings.check': 'Check and install',\n 'settings.nonEmpty': 'The workspace is not empty; nothing was changed.',\n 'settings.manual': 'Manual channel',\n 'settings.manualHint': 'Manual material copy: copy the source folder contents into the workspace root yourself. Manual prompt: copy the text below.',\n 'settings.source': 'Material path',\n 'settings.target': 'Workspace path',\n 'settings.copy': 'Copy',\n 'settings.copied': 'Copied',\n 'settings.prompt': 'Initial prompt',\n 'settings.reveal': 'Open in file manager',\n 'settings.noWorkspaces': 'No workspace is available.',\n 'settings.installOk': 'Task installed and the prompt was seeded into a new conversation; send it yourself.',\n 'settings.loading': 'Loading…',\n 'settings.recent': 'Recent events',\n 'settings.none': 'None.',\n 'settings.warningTitle': 'Eval Guard warning',\n 'settings.close': 'Close',\n}\n","/**\n * Evaluation guard, browser half: the Settings section, the warning toast, and\n * the shell bridge used to seed a new conversation with the task prompt.\n * @module dsh-eval-guard/client\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { SETTINGS_NS } from '../shared.ts'\nimport { EvalGuardSettings } from './Settings.tsx'\nimport { GuardToast } from './Toast.tsx'\nimport { configureEvalBridge } from './bridge.ts'\nimport { en, NS, zh, type Key } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n evalGuard: Key\n }\n}\n\nexport const inject = ['slots', 'locale', 'configForms', 'uiWorkspace']\n\nexport function apply(ctx: Context): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }))\n configureEvalBridge({\n startSession: (workspaceId, prompt) => {\n ctx.uiWorkspace.startSession(workspaceId as Parameters<typeof ctx.uiWorkspace.startSession>[0], { prompt })\n },\n workspaces: () => {\n const service = (ctx as unknown as { get(key: string): unknown }).get('workspaces') as\n { list?: { getSnapshot(): { items?: readonly Record<string, unknown>[] } } } | undefined\n const items = service?.list?.getSnapshot().items ?? []\n return items.map((item, index) => ({\n id: typeof item['workspaceId'] === 'string' ? item['workspaceId'] : String(item['id'] ?? index),\n label: typeof item['title'] === 'string' && item['title'] !== ''\n ? item['title']\n : String(item['path'] ?? item['workspaceId'] ?? ('workspace ' + String(index + 1))),\n }))\n },\n })\n ctx.effect(() => () => { configureEvalBridge(undefined) })\n const t = ctx.locale.bind(NS)\n const form = ctx.configForms.get(SETTINGS_NS) as {\n getSnapshot(): unknown\n subscribe(listener: () => void): () => void\n mutate(ops: never, revision?: number): unknown\n }\n const formFace = {\n getSnapshot: () => form.getSnapshot(),\n subscribe: (listener: () => void) => form.subscribe(listener),\n mutate: (ops: never, revision?: number) => form.mutate(ops, revision),\n }\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section', id: SETTINGS_NS, order: 41,\n label: () => t('settings.nav'), locale: NS,\n inject: () => ({ form: formFace }),\n }, EvalGuardSettings))\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay', id: SETTINGS_NS + '.toast', locale: NS,\n inject: () => ({ t }),\n }, GuardToast))\n}\n"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import {
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
8
|
//#region src/config.ts
|
|
9
9
|
/**
|
|
10
10
|
* Plugin configuration. The Settings page writes this document through the
|
|
@@ -42,9 +42,16 @@ const PROMPT_ROUTE = "/api/eval-guard/prompt";
|
|
|
42
42
|
const EVENTS_ROUTE = "/api/eval-guard/events";
|
|
43
43
|
const REVEAL_ROUTE = "/api/eval-guard/reveal";
|
|
44
44
|
const MANUAL_ROUTE = "/api/eval-guard/manual";
|
|
45
|
-
/**
|
|
45
|
+
/** Supported task ids. */
|
|
46
46
|
const TANK_TASK = "tank";
|
|
47
47
|
const TANK_TASK_LABEL = "第 6 集 · 坦克大战";
|
|
48
|
+
const WAREHOUSE_TASK = "warehouse";
|
|
49
|
+
const WAREHOUSE_TASK_LABEL = "第 5 集 · 仓储调度";
|
|
50
|
+
const LAND_TASK = "land";
|
|
51
|
+
const LAND_TASK_LABEL = "第 1 集 · 圈地";
|
|
52
|
+
const BOMBERMAN_TASK = "bomberman";
|
|
53
|
+
const BOMBERMAN_TASK_LABEL = "第 2 集 · 炸弹人";
|
|
54
|
+
const CTF_TASK_LABEL = "第 3 集 · 夺旗";
|
|
48
55
|
//#endregion
|
|
49
56
|
//#region src/policy.ts
|
|
50
57
|
/**
|
|
@@ -379,7 +386,8 @@ const SYSTEM_READ_DIRS = [
|
|
|
379
386
|
"/libx32",
|
|
380
387
|
"/etc",
|
|
381
388
|
"/opt",
|
|
382
|
-
"/sys"
|
|
389
|
+
"/sys",
|
|
390
|
+
"/var/lib"
|
|
383
391
|
];
|
|
384
392
|
/** Ancestors of a path, outermost first. */
|
|
385
393
|
function ancestorsOf(target) {
|
|
@@ -483,7 +491,10 @@ function writePolicy(workspace, rules) {
|
|
|
483
491
|
const lines = [];
|
|
484
492
|
lines.push(line("workspace", workspace));
|
|
485
493
|
for (const system of SYSTEM_READ_DIRS) try {
|
|
486
|
-
if (fs.statSync(system).isDirectory())
|
|
494
|
+
if (fs.statSync(system).isDirectory()) {
|
|
495
|
+
lines.push(line("read", system));
|
|
496
|
+
for (const ancestor of ancestorsOf(system)) lines.push(line("exact", ancestor));
|
|
497
|
+
}
|
|
487
498
|
} catch {}
|
|
488
499
|
lines.push(line("read", "/proc"));
|
|
489
500
|
lines.push(line("exact", "/"));
|
|
@@ -507,6 +518,7 @@ function writePolicy(workspace, rules) {
|
|
|
507
518
|
}
|
|
508
519
|
for (const manifest of manifests) lines.push(line("read", manifest));
|
|
509
520
|
for (const dev of [
|
|
521
|
+
"/dev",
|
|
510
522
|
"/dev/null",
|
|
511
523
|
"/dev/zero",
|
|
512
524
|
"/dev/full",
|
|
@@ -1268,10 +1280,10 @@ var Guard = class {
|
|
|
1268
1280
|
});
|
|
1269
1281
|
}
|
|
1270
1282
|
/** Build the prompt for one task with only installed tools listed. */
|
|
1271
|
-
promptFor(workspacePath, source) {
|
|
1283
|
+
promptFor(workspacePath, source, task = this.task) {
|
|
1272
1284
|
const present = new Set(this.toolCache);
|
|
1273
1285
|
const blacklist = parseBlacklist(this.blacklistText()).filter((name) => isBaseBlacklistName(name) || present.has(name));
|
|
1274
|
-
return
|
|
1286
|
+
return task.prompt(blacklist, {
|
|
1275
1287
|
source,
|
|
1276
1288
|
workspacePath,
|
|
1277
1289
|
minutes: this.configuration.timeLimitMinutes
|
|
@@ -1287,169 +1299,53 @@ var Guard = class {
|
|
|
1287
1299
|
}
|
|
1288
1300
|
};
|
|
1289
1301
|
//#endregion
|
|
1290
|
-
//#region src/routes.ts
|
|
1291
|
-
/**
|
|
1292
|
-
* Browser-facing routes: enable pre-check, workspace check, task install,
|
|
1293
|
-
* manual-channel paths, and the warning event feed.
|
|
1294
|
-
* @module dsh-eval-guard/routes
|
|
1295
|
-
*/
|
|
1296
|
-
/** One JSON reply. */
|
|
1297
|
-
function json(value, status = 200) {
|
|
1298
|
-
return new Response(JSON.stringify(value), {
|
|
1299
|
-
status,
|
|
1300
|
-
headers: {
|
|
1301
|
-
"content-type": "application/json",
|
|
1302
|
-
"cache-control": "no-store"
|
|
1303
|
-
}
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
/** Read a query parameter. */
|
|
1307
|
-
function query(request, key) {
|
|
1308
|
-
return new URL(request.url).searchParams.get(key) ?? "";
|
|
1309
|
-
}
|
|
1310
|
-
/** Whether one path is inside another. */
|
|
1311
|
-
function under(target, root) {
|
|
1312
|
-
const relative = path.relative(root, target);
|
|
1313
|
-
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
1314
|
-
}
|
|
1315
|
-
/**
|
|
1316
|
-
* Register every route this plugin owns.
|
|
1317
|
-
* @param ctx - host context.
|
|
1318
|
-
* @param guard - the guard instance.
|
|
1319
|
-
* @param tasks - task service.
|
|
1320
|
-
*/
|
|
1321
|
-
function registerRoutes(ctx, guard, tasks) {
|
|
1322
|
-
ctx.inject(["connection"], (child) => {
|
|
1323
|
-
const connection = child.get("connection");
|
|
1324
|
-
const register = connection?.fetch?.register?.bind(connection.fetch);
|
|
1325
|
-
if (register === void 0) {
|
|
1326
|
-
ctx.logger.warn("eval-guard: connection.fetch is unavailable; the Settings page cannot reach the host");
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
const add = (route) => {
|
|
1330
|
-
child.effect(() => register(route), "eval-guard: route " + route.path);
|
|
1331
|
-
};
|
|
1332
|
-
add({
|
|
1333
|
-
path: STATE_ROUTE,
|
|
1334
|
-
methods: ["GET"],
|
|
1335
|
-
requestBody: "buffered",
|
|
1336
|
-
fetch: async () => {
|
|
1337
|
-
return json({
|
|
1338
|
-
enabled: guard.active,
|
|
1339
|
-
running: guard.runningSessions(),
|
|
1340
|
-
tasks: [{
|
|
1341
|
-
id: "tank",
|
|
1342
|
-
label: "第 6 集 · 坦克大战",
|
|
1343
|
-
description: "1v1 坦克大战 bot;提交 submission/bot.cpp。"
|
|
1344
|
-
}],
|
|
1345
|
-
defaultBlacklist: guard.defaultBlacklist(),
|
|
1346
|
-
generatedDefault: guard.configuration.toolBlacklistUseDefault,
|
|
1347
|
-
workspaceRoot: dshHome(),
|
|
1348
|
-
runsRoot: path.join(guardRoot(), "runs")
|
|
1349
|
-
});
|
|
1350
|
-
}
|
|
1351
|
-
});
|
|
1352
|
-
add({
|
|
1353
|
-
path: CHECK_ROUTE,
|
|
1354
|
-
methods: ["GET"],
|
|
1355
|
-
requestBody: "buffered",
|
|
1356
|
-
fetch: async (request) => {
|
|
1357
|
-
const workspaceId = query(request, "workspaceId");
|
|
1358
|
-
if (workspaceId === "") return json({ error: "workspaceId is required" }, 400);
|
|
1359
|
-
return json(tasks.check(workspaceId));
|
|
1360
|
-
}
|
|
1361
|
-
});
|
|
1362
|
-
add({
|
|
1363
|
-
path: INSTALL_ROUTE,
|
|
1364
|
-
methods: ["POST"],
|
|
1365
|
-
requestBody: "buffered",
|
|
1366
|
-
fetch: async (request) => {
|
|
1367
|
-
let body = {};
|
|
1368
|
-
try {
|
|
1369
|
-
body = await request.json();
|
|
1370
|
-
} catch {
|
|
1371
|
-
return json({ error: "invalid body" }, 400);
|
|
1372
|
-
}
|
|
1373
|
-
const workspaceId = typeof body.workspaceId === "string" ? body.workspaceId : "";
|
|
1374
|
-
if (workspaceId === "") return json({ error: "workspaceId is required" }, 400);
|
|
1375
|
-
return json(tasks.install(workspaceId));
|
|
1376
|
-
}
|
|
1377
|
-
});
|
|
1378
|
-
add({
|
|
1379
|
-
path: PROMPT_ROUTE,
|
|
1380
|
-
methods: ["GET"],
|
|
1381
|
-
requestBody: "buffered",
|
|
1382
|
-
fetch: async (request) => {
|
|
1383
|
-
const workspaceId = query(request, "workspaceId");
|
|
1384
|
-
return json({ prompt: tasks.prompt(workspaceId) });
|
|
1385
|
-
}
|
|
1386
|
-
});
|
|
1387
|
-
add({
|
|
1388
|
-
path: MANUAL_ROUTE,
|
|
1389
|
-
methods: ["GET"],
|
|
1390
|
-
requestBody: "buffered",
|
|
1391
|
-
fetch: async (request) => {
|
|
1392
|
-
const workspaceId = query(request, "workspaceId");
|
|
1393
|
-
return json(tasks.manualPaths(workspaceId));
|
|
1394
|
-
}
|
|
1395
|
-
});
|
|
1396
|
-
add({
|
|
1397
|
-
path: EVENTS_ROUTE,
|
|
1398
|
-
methods: ["GET"],
|
|
1399
|
-
requestBody: "buffered",
|
|
1400
|
-
fetch: async (request) => {
|
|
1401
|
-
const since = Number(query(request, "since"));
|
|
1402
|
-
return json({ events: guard.recent(Number.isFinite(since) ? since : 0) });
|
|
1403
|
-
}
|
|
1404
|
-
});
|
|
1405
|
-
add({
|
|
1406
|
-
path: REVEAL_ROUTE,
|
|
1407
|
-
methods: ["POST"],
|
|
1408
|
-
requestBody: "buffered",
|
|
1409
|
-
fetch: async (request) => {
|
|
1410
|
-
let body = {};
|
|
1411
|
-
try {
|
|
1412
|
-
body = await request.json();
|
|
1413
|
-
} catch {
|
|
1414
|
-
return json({ error: "invalid body" }, 400);
|
|
1415
|
-
}
|
|
1416
|
-
const target = typeof body.path === "string" ? path.resolve(body.path) : "";
|
|
1417
|
-
if (target === "") return json({ error: "path is required" }, 400);
|
|
1418
|
-
const material = path.resolve(guardRoot(), "material");
|
|
1419
|
-
const workspaces = child.get("workspaceRegistry")?.list?.() ?? [];
|
|
1420
|
-
if (!(under(target, material) || workspaces.some((workspace) => under(target, workspace.path)))) return json({ error: "path is not revealable" }, 403);
|
|
1421
|
-
if (!fs.existsSync(target)) return json({ error: "path does not exist" }, 404);
|
|
1422
|
-
try {
|
|
1423
|
-
spawn("xdg-open", [target], {
|
|
1424
|
-
detached: true,
|
|
1425
|
-
stdio: "ignore"
|
|
1426
|
-
}).unref();
|
|
1427
|
-
} catch (error) {
|
|
1428
|
-
return json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
1429
|
-
}
|
|
1430
|
-
return json({ ok: true });
|
|
1431
|
-
}
|
|
1432
|
-
});
|
|
1433
|
-
});
|
|
1434
|
-
}
|
|
1435
|
-
//#endregion
|
|
1436
1302
|
//#region src/task.ts
|
|
1437
1303
|
/**
|
|
1438
|
-
* The tank task pack: workspace
|
|
1304
|
+
* The tank task pack: packaged workspace.zip install, prompt construction, and the one
|
|
1439
1305
|
* run record the evaluator later reads.
|
|
1440
1306
|
* @module dsh-eval-guard/task
|
|
1441
1307
|
*/
|
|
1442
|
-
/**
|
|
1443
|
-
const TASKS = [
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1308
|
+
/** Tasks offered by the installer. */
|
|
1309
|
+
const TASKS = [
|
|
1310
|
+
{
|
|
1311
|
+
id: TANK_TASK,
|
|
1312
|
+
label: TANK_TASK_LABEL,
|
|
1313
|
+
description: "1v1 坦克大战 bot:解压 workspace.zip 后编写 workspace/submission/bot.cpp,评测时与基准及其它提交打循环赛。"
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
id: WAREHOUSE_TASK,
|
|
1317
|
+
label: WAREHOUSE_TASK_LABEL,
|
|
1318
|
+
description: "单人单次仓储调度优化:解压 workspace.zip 后改写 workspace/src/,在输出逐字节不变的前提下尽量提速。"
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
id: LAND_TASK,
|
|
1322
|
+
label: LAND_TASK_LABEL,
|
|
1323
|
+
description: "圈地 1v1/4 人混战 bot:解压 workspace.zip 后编写 workspace/submission/bot.cpp。"
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
id: BOMBERMAN_TASK,
|
|
1327
|
+
label: BOMBERMAN_TASK_LABEL,
|
|
1328
|
+
description: "炸弹人 2–10 人 bot:解压 workspace.zip 后编写 workspace/submission/bot.cpp。"
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
id: "ctf",
|
|
1332
|
+
label: CTF_TASK_LABEL,
|
|
1333
|
+
description: "夺旗 2–15 人 bot:解压 workspace.zip 后编写 workspace/submission/bot.cpp。"
|
|
1334
|
+
}
|
|
1335
|
+
];
|
|
1448
1336
|
/** Format a blacklist for the prompt. */
|
|
1449
1337
|
function blacklistBlock(blacklist) {
|
|
1450
1338
|
if (blacklist.length === 0) return "(当前没有需要特别列出的工具。)";
|
|
1451
1339
|
return blacklist.join("\n");
|
|
1452
1340
|
}
|
|
1341
|
+
/** Wording shared by every task: difficulty, strong-agent comparison, no early submit. */
|
|
1342
|
+
const COMPETITION_LINES = [
|
|
1343
|
+
"## 竞争强度",
|
|
1344
|
+
"",
|
|
1345
|
+
"- 这个任务并不像看起来那么简单,不要低估隐藏边界、正确性细节和对手强度。",
|
|
1346
|
+
"- 你的结果会与 meow-ai-arena 中其他强 agent(如 GPT-6 Astra、Opus、Kimi K3 等)的提交在同一口径下比较,而不只是和基准 bot 比较;请按能进入前列的标准要求自己。",
|
|
1347
|
+
"- 不要过早交卷:在时间上限内尽量多验证、迭代和完善,只有确实完成时才结束。"
|
|
1348
|
+
];
|
|
1453
1349
|
/** The tank task definition. */
|
|
1454
1350
|
const TANK_DEFINITION = {
|
|
1455
1351
|
id: TANK_TASK,
|
|
@@ -1469,24 +1365,165 @@ const TANK_DEFINITION = {
|
|
|
1469
1365
|
"",
|
|
1470
1366
|
"## 交付",
|
|
1471
1367
|
"",
|
|
1472
|
-
"-
|
|
1473
|
-
"- 交付前检查并关闭所有后台任务;最多 " + String(options.minutes ?? 90) + " 分钟,到点会被强制终止。",
|
|
1368
|
+
"- 解压工作区里的 workspace.zip,把最终 bot 保存到解压后的 workspace/submission/bot.cpp(只需要这一个文件)。",
|
|
1474
1369
|
"- 最后用几句话说明你的策略。",
|
|
1475
1370
|
"",
|
|
1371
|
+
"## 时间规则",
|
|
1372
|
+
"",
|
|
1373
|
+
"- 本次是**连续 " + String(options.minutes ?? 90) + " 分钟的单次任务**,从对话开始时开始计时。",
|
|
1374
|
+
"- 交付前检查并关闭所有后台任务;到点会被强制终止。",
|
|
1375
|
+
"- clock.py 从解压那一刻开始计算,而实际计时从对话开始计算;它**只能作为参考**,请尽快解压以减小误差。实际时间以评测守护为准。",
|
|
1376
|
+
"",
|
|
1377
|
+
"## 本次评测禁止调用的工具",
|
|
1378
|
+
"",
|
|
1379
|
+
blacklistBlock(blacklist),
|
|
1380
|
+
"",
|
|
1381
|
+
"## 工作区内容",
|
|
1382
|
+
"",
|
|
1383
|
+
"工作区里有一个 workspace.zip;解压后进入 workspace/ 目录,先读 README.md 和 RULES.md,规则引擎是 tank.hpp。",
|
|
1384
|
+
"本地工具(arena.py、check、play、batch、vs 等)和起步模板都在解压后的 workspace/ 里,可直接使用。",
|
|
1385
|
+
"",
|
|
1386
|
+
...COMPETITION_LINES,
|
|
1387
|
+
"",
|
|
1388
|
+
"开始吧。"
|
|
1389
|
+
].join("\n");
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
/** The warehouse single-run optimization task. */
|
|
1393
|
+
const WAREHOUSE_DEFINITION = {
|
|
1394
|
+
id: WAREHOUSE_TASK,
|
|
1395
|
+
label: WAREHOUSE_TASK_LABEL,
|
|
1396
|
+
description: TASKS[1].description,
|
|
1397
|
+
asset: path.join(packageRoot(), "assets", "warehouse", "workspace.zip"),
|
|
1398
|
+
prompt(blacklist, options) {
|
|
1399
|
+
return [
|
|
1400
|
+
"这是一场用于评测 AI agent 能力的仓储调度优化任务。你需要在当前工作区里独立完成它。",
|
|
1401
|
+
"",
|
|
1402
|
+
"## 环境与限制",
|
|
1403
|
+
"",
|
|
1404
|
+
"- 你只能读写当前工作区内的文件;工作区以外的任何位置都不能访问,包括 /tmp。",
|
|
1405
|
+
"- 禁止联网、禁止调用其他 AI 或子代理、禁止与其他 agent 通信或读取其他工作区。",
|
|
1406
|
+
"- 评测守护会阻止并记录违规行为;违规可能导致本次评测无效。",
|
|
1407
|
+
"- 任务必须自主完成:不要向用户提问、不要请求审批、不要等待任何用户输入。",
|
|
1408
|
+
"",
|
|
1409
|
+
"## 交付",
|
|
1410
|
+
"",
|
|
1411
|
+
"- 解压工作区里的 workspace.zip,进入解压后的 workspace/ 目录;只能修改 workspace/src/ 下的文件。",
|
|
1412
|
+
"- 结束时 workspace/src/ 整个目录就是提交;不要修改 baseline/、workloads/、tools/。",
|
|
1413
|
+
"- 最后用几句话说明你做了什么优化、如何验证输出不变。",
|
|
1414
|
+
"",
|
|
1415
|
+
"## 时间规则",
|
|
1416
|
+
"",
|
|
1417
|
+
"- 本次是**连续 " + String(options.minutes ?? 90) + " 分钟的单次任务**,从对话开始时开始计时。",
|
|
1418
|
+
"- 交付前检查并关闭所有后台任务;到点会被强制终止,以当时的 src/ 为准。",
|
|
1419
|
+
"- clock.py 从解压那一刻开始计算,而实际计时从对话开始计算;它**只能作为参考**,请尽快解压以减小误差。实际时间以评测守护为准。",
|
|
1420
|
+
"",
|
|
1476
1421
|
"## 本次评测禁止调用的工具",
|
|
1477
1422
|
"",
|
|
1478
1423
|
blacklistBlock(blacklist),
|
|
1479
1424
|
"",
|
|
1480
1425
|
"## 工作区内容",
|
|
1481
1426
|
"",
|
|
1482
|
-
"
|
|
1483
|
-
"
|
|
1484
|
-
"
|
|
1427
|
+
"解压后 workspace/ 里有 TASK.md、SPEC.md、baseline/、src/、workloads/public/ 和 tools/。",
|
|
1428
|
+
"先读 TASK.md、baseline 代码和 SPEC.md;SPEC.md 是旧文档,与基线代码不一致时以基线代码的行为为准。",
|
|
1429
|
+
"编译命令固定为 g++ -O2 -std=c++20 src/*.cpp;输出必须与 baseline 逐字节相同,包括所有能通过 tools/check_input.py --full 的输入。",
|
|
1430
|
+
"用 python3 tools/bench.py 检查正确性和耗时,用 tools/gen.py 生成更多输入和基线对拍。",
|
|
1431
|
+
"",
|
|
1432
|
+
...COMPETITION_LINES,
|
|
1485
1433
|
"",
|
|
1486
1434
|
"开始吧。"
|
|
1487
1435
|
].join("\n");
|
|
1488
1436
|
}
|
|
1489
1437
|
};
|
|
1438
|
+
/** Shared prompt for the three bot-writing tasks. */
|
|
1439
|
+
function botTaskPrompt(intro, submit, workspaceLines, blacklist, options) {
|
|
1440
|
+
return [
|
|
1441
|
+
intro,
|
|
1442
|
+
"",
|
|
1443
|
+
"## 环境与限制",
|
|
1444
|
+
"",
|
|
1445
|
+
"- 你只能读写当前工作区内的文件;工作区以外的任何位置都不能访问,包括 /tmp。",
|
|
1446
|
+
"- 禁止联网、禁止调用其他 AI 或子代理、禁止与其他 agent 通信或读取其他工作区。",
|
|
1447
|
+
"- 评测守护会阻止并记录违规行为;违规可能导致本次评测无效。",
|
|
1448
|
+
"- 任务必须自主完成:不要向用户提问、不要请求审批、不要等待任何用户输入。",
|
|
1449
|
+
"",
|
|
1450
|
+
"## 交付",
|
|
1451
|
+
"",
|
|
1452
|
+
submit,
|
|
1453
|
+
"- 最后用几句话说明你的策略。",
|
|
1454
|
+
"",
|
|
1455
|
+
"## 时间规则",
|
|
1456
|
+
"",
|
|
1457
|
+
"- 本次是**连续 " + String(options.minutes ?? 90) + " 分钟的单次任务**,从对话开始时开始计时。",
|
|
1458
|
+
"- 交付前检查并关闭所有后台任务;到点会被强制终止。",
|
|
1459
|
+
"- clock.py 从解压那一刻开始计算,而实际计时从对话开始计算;它**只能作为参考**,请尽快解压以减小误差。实际时间以评测守护为准。",
|
|
1460
|
+
"",
|
|
1461
|
+
"## 本次评测禁止调用的工具",
|
|
1462
|
+
"",
|
|
1463
|
+
blacklistBlock(blacklist),
|
|
1464
|
+
"",
|
|
1465
|
+
"## 工作区内容",
|
|
1466
|
+
"",
|
|
1467
|
+
...workspaceLines,
|
|
1468
|
+
"",
|
|
1469
|
+
...COMPETITION_LINES,
|
|
1470
|
+
"",
|
|
1471
|
+
"开始吧。"
|
|
1472
|
+
].join("\n");
|
|
1473
|
+
}
|
|
1474
|
+
/** Land (territory) bot task. */
|
|
1475
|
+
const LAND_DEFINITION = {
|
|
1476
|
+
id: LAND_TASK,
|
|
1477
|
+
label: LAND_TASK_LABEL,
|
|
1478
|
+
description: TASKS[2].description,
|
|
1479
|
+
asset: path.join(packageRoot(), "assets", "land", "workspace.zip"),
|
|
1480
|
+
prompt(blacklist, options) {
|
|
1481
|
+
return botTaskPrompt("这是一场用于评测 AI agent 能力的圈地对战 bot 编程任务。你需要在当前工作区里独立完成它。", "- 解压工作区里的 workspace.zip,进入 workspace/;从 starter/bot.cpp 起步,最终把单文件程序保存到 workspace/submission/bot.cpp。", [
|
|
1482
|
+
"解压后 workspace/ 里有 RULES.md、TASK.md、engine/、anchors/、starter/ 和 submission/。",
|
|
1483
|
+
"先读 RULES.md、TASK.md、engine/game.py 和 starter/bot.cpp;规则以 engine/game.py 为准。",
|
|
1484
|
+
"本地裁判:python3 engine/referee.py submission/bot.cpp anchors/greedy.cpp --games 10(单挑),也可加多个 bot 打混战。",
|
|
1485
|
+
"只能提交一个 bot.cpp,编译参数固定为 g++ -O2 -std=c++20,单文件、只用 C++ 标准库。"
|
|
1486
|
+
], blacklist, options);
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
/** Bomberman bot task. */
|
|
1490
|
+
const BOMBERMAN_DEFINITION = {
|
|
1491
|
+
id: BOMBERMAN_TASK,
|
|
1492
|
+
label: BOMBERMAN_TASK_LABEL,
|
|
1493
|
+
description: TASKS[3].description,
|
|
1494
|
+
asset: path.join(packageRoot(), "assets", "bomberman", "workspace.zip"),
|
|
1495
|
+
prompt(blacklist, options) {
|
|
1496
|
+
return botTaskPrompt("这是一场用于评测 AI agent 能力的炸弹人对战 bot 编程任务。你需要在当前工作区里独立完成它。", "- 解压工作区里的 workspace.zip,进入 workspace/;最终把单文件程序保存到 workspace/submission/bot.cpp。", [
|
|
1497
|
+
"解压后 workspace/ 里有 README.md、RULES.md、engine/、baseline.cpp 和 submission/。",
|
|
1498
|
+
"先读 README.md、RULES.md 和 engine/engine.py;baseline.cpp 是公开练习对手,可以编译、对战和复盘。",
|
|
1499
|
+
"同一程序要支持 2–10 人;编译参数固定为 g++ -O2 -std=c++20,单文件、只用 C++ 标准库。",
|
|
1500
|
+
"用 engine/arena.py 或 README 里的本地命令做对战和回放验证。"
|
|
1501
|
+
], blacklist, options);
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
/** CTF (capture the flag) bot task. */
|
|
1505
|
+
const CTF_DEFINITION = {
|
|
1506
|
+
id: "ctf",
|
|
1507
|
+
label: CTF_TASK_LABEL,
|
|
1508
|
+
description: TASKS[4].description,
|
|
1509
|
+
asset: path.join(packageRoot(), "assets", "ctf", "workspace.zip"),
|
|
1510
|
+
prompt(blacklist, options) {
|
|
1511
|
+
return botTaskPrompt("这是一场用于评测 AI agent 能力的夺旗对战 bot 编程任务。你需要在当前工作区里独立完成它。", "- 解压工作区里的 workspace.zip,进入 workspace/;最终把单文件程序保存到 workspace/submission/bot.cpp。", [
|
|
1512
|
+
"解压后 workspace/ 里有 README.md、RULES.md、arena.py、engine.py、core.py、baseline.cpp 和 submission/bot.cpp 模板。",
|
|
1513
|
+
"先读 README.md、RULES.md 和 submission/bot.cpp;一个程序统一控制己方 3 个角色,支持 2–15 个阵营。",
|
|
1514
|
+
"本地对战:python3 arena.py play submission/bot.cpp baseline --seed 7,或 arena.py batch;baseline.cpp 是公开练习对手。",
|
|
1515
|
+
"编译参数固定为 g++ -O2 -std=c++20,单文件、只用 C++ 标准库。"
|
|
1516
|
+
], blacklist, options);
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
/** All task definitions keyed by id. */
|
|
1520
|
+
const TASK_DEFINITIONS = {
|
|
1521
|
+
[TANK_TASK]: TANK_DEFINITION,
|
|
1522
|
+
[WAREHOUSE_TASK]: WAREHOUSE_DEFINITION,
|
|
1523
|
+
[LAND_TASK]: LAND_DEFINITION,
|
|
1524
|
+
[BOMBERMAN_TASK]: BOMBERMAN_DEFINITION,
|
|
1525
|
+
["ctf"]: CTF_DEFINITION
|
|
1526
|
+
};
|
|
1490
1527
|
/** Install, check, and prompt services for the task pack. */
|
|
1491
1528
|
var TaskService = class {
|
|
1492
1529
|
guard;
|
|
@@ -1527,23 +1564,36 @@ var TaskService = class {
|
|
|
1527
1564
|
entries
|
|
1528
1565
|
};
|
|
1529
1566
|
}
|
|
1530
|
-
/**
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1567
|
+
/** A known task definition, or undefined. */
|
|
1568
|
+
definition(taskId) {
|
|
1569
|
+
return TASK_DEFINITIONS[taskId];
|
|
1570
|
+
}
|
|
1571
|
+
/** Copy the packaged workspace.zip to a stable material path for install/manual use. */
|
|
1572
|
+
materialZip(taskId = TANK_TASK) {
|
|
1573
|
+
const definition = TASK_DEFINITIONS[taskId];
|
|
1574
|
+
if (definition === void 0) throw new Error("unknown task: " + taskId);
|
|
1575
|
+
const dir = path.join(guardRoot(), "material", taskId);
|
|
1539
1576
|
fs.mkdirSync(dir, { recursive: true });
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
fs.
|
|
1543
|
-
|
|
1577
|
+
const target = path.join(dir, "workspace.zip");
|
|
1578
|
+
const source = definition.asset;
|
|
1579
|
+
if (!fs.existsSync(source)) throw new Error("task asset is missing: " + source);
|
|
1580
|
+
let stale = true;
|
|
1581
|
+
try {
|
|
1582
|
+
stale = !fs.existsSync(target) || fs.statSync(target).size !== fs.statSync(source).size;
|
|
1583
|
+
} catch {
|
|
1584
|
+
stale = true;
|
|
1585
|
+
}
|
|
1586
|
+
if (stale) fs.copyFileSync(source, target);
|
|
1587
|
+
return target;
|
|
1544
1588
|
}
|
|
1545
|
-
/** Install the task pack into an empty workspace. */
|
|
1546
|
-
install(workspaceId, promptOverride) {
|
|
1589
|
+
/** Install the selected task pack into an empty workspace. */
|
|
1590
|
+
install(workspaceId, taskId = TANK_TASK, promptOverride) {
|
|
1591
|
+
const definition = TASK_DEFINITIONS[taskId];
|
|
1592
|
+
if (definition === void 0) return {
|
|
1593
|
+
ok: false,
|
|
1594
|
+
workspaceId,
|
|
1595
|
+
error: "未知任务:" + taskId
|
|
1596
|
+
};
|
|
1547
1597
|
const checked = this.check(workspaceId);
|
|
1548
1598
|
if (!checked.ok) return {
|
|
1549
1599
|
ok: false,
|
|
@@ -1563,7 +1613,7 @@ var TaskService = class {
|
|
|
1563
1613
|
error: "请先启用评测守护,再安装评测任务。"
|
|
1564
1614
|
};
|
|
1565
1615
|
try {
|
|
1566
|
-
this.
|
|
1616
|
+
fs.copyFileSync(this.materialZip(taskId), path.join(checked.path, "workspace.zip"));
|
|
1567
1617
|
} catch (error) {
|
|
1568
1618
|
return {
|
|
1569
1619
|
ok: false,
|
|
@@ -1573,10 +1623,10 @@ var TaskService = class {
|
|
|
1573
1623
|
};
|
|
1574
1624
|
}
|
|
1575
1625
|
const runId = this.newRunId();
|
|
1576
|
-
const prompt = promptOverride ?? this.guard.promptFor(checked.path, this.
|
|
1626
|
+
const prompt = promptOverride ?? this.guard.promptFor(checked.path, this.materialZip(taskId), definition);
|
|
1577
1627
|
const run = {
|
|
1578
1628
|
runId,
|
|
1579
|
-
task:
|
|
1629
|
+
task: definition.id,
|
|
1580
1630
|
workspaceId,
|
|
1581
1631
|
workspacePath: checked.path,
|
|
1582
1632
|
sessionId: null,
|
|
@@ -1597,47 +1647,178 @@ var TaskService = class {
|
|
|
1597
1647
|
};
|
|
1598
1648
|
}
|
|
1599
1649
|
/** The prompt for manual copying, without installing anything. */
|
|
1600
|
-
prompt(workspaceId) {
|
|
1650
|
+
prompt(workspaceId, taskId = TANK_TASK) {
|
|
1651
|
+
const definition = TASK_DEFINITIONS[taskId];
|
|
1652
|
+
if (definition === void 0) return "";
|
|
1601
1653
|
const workspace = this.registry.get(workspaceId);
|
|
1602
|
-
return this.guard.promptFor(workspace?.path ?? "", this.
|
|
1654
|
+
return this.guard.promptFor(workspace?.path ?? "", this.materialZip(taskId), definition);
|
|
1603
1655
|
}
|
|
1604
1656
|
/** Filesystem paths for the manual channel. */
|
|
1605
|
-
manualPaths(workspaceId) {
|
|
1657
|
+
manualPaths(workspaceId, taskId = TANK_TASK) {
|
|
1658
|
+
const definition = TASK_DEFINITIONS[taskId];
|
|
1659
|
+
if (definition === void 0) return {
|
|
1660
|
+
source: "",
|
|
1661
|
+
prompt: ""
|
|
1662
|
+
};
|
|
1606
1663
|
const workspace = this.registry.get(workspaceId);
|
|
1607
1664
|
return {
|
|
1608
|
-
source: this.
|
|
1609
|
-
prompt: this.guard.promptFor(workspace?.path ?? "", this.
|
|
1665
|
+
source: this.materialZip(taskId),
|
|
1666
|
+
prompt: this.guard.promptFor(workspace?.path ?? "", this.materialZip(taskId), definition)
|
|
1610
1667
|
};
|
|
1611
1668
|
}
|
|
1612
|
-
/** Extract a workspace.zip into a destination, stripping the workspace/ prefix. */
|
|
1613
|
-
extract(zip, destination) {
|
|
1614
|
-
if (!fs.existsSync(zip)) throw new Error("task asset is missing: " + zip);
|
|
1615
|
-
fs.mkdirSync(path.join(guardRoot(), "tmp"), { recursive: true });
|
|
1616
|
-
const temporary = fs.mkdtempSync(path.join(guardRoot(), "tmp", "extract-"));
|
|
1617
|
-
try {
|
|
1618
|
-
execFileSync("unzip", [
|
|
1619
|
-
"-q",
|
|
1620
|
-
"-o",
|
|
1621
|
-
zip,
|
|
1622
|
-
"-d",
|
|
1623
|
-
temporary
|
|
1624
|
-
], { stdio: "ignore" });
|
|
1625
|
-
const inner = path.join(temporary, "workspace");
|
|
1626
|
-
if (!fs.existsSync(inner)) throw new Error("task asset has no workspace/ root");
|
|
1627
|
-
fs.cpSync(inner, destination, { recursive: true });
|
|
1628
|
-
} finally {
|
|
1629
|
-
fs.rmSync(temporary, {
|
|
1630
|
-
recursive: true,
|
|
1631
|
-
force: true
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
1669
|
/** A unique run id. */
|
|
1636
1670
|
newRunId() {
|
|
1637
1671
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 14) + "-" + Math.random().toString(16).slice(2, 8);
|
|
1638
1672
|
}
|
|
1639
1673
|
};
|
|
1640
1674
|
//#endregion
|
|
1675
|
+
//#region src/routes.ts
|
|
1676
|
+
/**
|
|
1677
|
+
* Browser-facing routes: enable pre-check, workspace check, task install,
|
|
1678
|
+
* manual-channel paths, and the warning event feed.
|
|
1679
|
+
* @module dsh-eval-guard/routes
|
|
1680
|
+
*/
|
|
1681
|
+
/** One JSON reply. */
|
|
1682
|
+
function json(value, status = 200) {
|
|
1683
|
+
return new Response(JSON.stringify(value), {
|
|
1684
|
+
status,
|
|
1685
|
+
headers: {
|
|
1686
|
+
"content-type": "application/json",
|
|
1687
|
+
"cache-control": "no-store"
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
/** Read a query parameter. */
|
|
1692
|
+
function query(request, key) {
|
|
1693
|
+
return new URL(request.url).searchParams.get(key) ?? "";
|
|
1694
|
+
}
|
|
1695
|
+
/** Whether one path is inside another. */
|
|
1696
|
+
function under(target, root) {
|
|
1697
|
+
const relative = path.relative(root, target);
|
|
1698
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Register every route this plugin owns.
|
|
1702
|
+
* @param ctx - host context.
|
|
1703
|
+
* @param guard - the guard instance.
|
|
1704
|
+
* @param tasks - task service.
|
|
1705
|
+
*/
|
|
1706
|
+
function registerRoutes(ctx, guard, tasks) {
|
|
1707
|
+
ctx.inject(["connection"], (child) => {
|
|
1708
|
+
const connection = child.get("connection");
|
|
1709
|
+
const register = connection?.fetch?.register?.bind(connection.fetch);
|
|
1710
|
+
if (register === void 0) {
|
|
1711
|
+
ctx.logger.warn("eval-guard: connection.fetch is unavailable; the Settings page cannot reach the host");
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const add = (route) => {
|
|
1715
|
+
child.effect(() => register(route), "eval-guard: route " + route.path);
|
|
1716
|
+
};
|
|
1717
|
+
add({
|
|
1718
|
+
path: STATE_ROUTE,
|
|
1719
|
+
methods: ["GET"],
|
|
1720
|
+
requestBody: "buffered",
|
|
1721
|
+
fetch: async () => {
|
|
1722
|
+
return json({
|
|
1723
|
+
enabled: guard.active,
|
|
1724
|
+
running: guard.runningSessions(),
|
|
1725
|
+
tasks: [...TASKS],
|
|
1726
|
+
defaultBlacklist: guard.defaultBlacklist(),
|
|
1727
|
+
generatedDefault: guard.configuration.toolBlacklistUseDefault,
|
|
1728
|
+
workspaceRoot: dshHome(),
|
|
1729
|
+
runsRoot: path.join(guardRoot(), "runs")
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
});
|
|
1733
|
+
add({
|
|
1734
|
+
path: CHECK_ROUTE,
|
|
1735
|
+
methods: ["GET"],
|
|
1736
|
+
requestBody: "buffered",
|
|
1737
|
+
fetch: async (request) => {
|
|
1738
|
+
const workspaceId = query(request, "workspaceId");
|
|
1739
|
+
if (workspaceId === "") return json({ error: "workspaceId is required" }, 400);
|
|
1740
|
+
return json(tasks.check(workspaceId));
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
add({
|
|
1744
|
+
path: INSTALL_ROUTE,
|
|
1745
|
+
methods: ["POST"],
|
|
1746
|
+
requestBody: "buffered",
|
|
1747
|
+
fetch: async (request) => {
|
|
1748
|
+
let body = {};
|
|
1749
|
+
try {
|
|
1750
|
+
body = await request.json();
|
|
1751
|
+
} catch {
|
|
1752
|
+
return json({ error: "invalid body" }, 400);
|
|
1753
|
+
}
|
|
1754
|
+
const workspaceId = typeof body.workspaceId === "string" ? body.workspaceId : "";
|
|
1755
|
+
const taskId = typeof body.task === "string" && body.task !== "" ? body.task : TANK_TASK;
|
|
1756
|
+
if (workspaceId === "") return json({ error: "workspaceId is required" }, 400);
|
|
1757
|
+
return json(tasks.install(workspaceId, taskId));
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
add({
|
|
1761
|
+
path: PROMPT_ROUTE,
|
|
1762
|
+
methods: ["GET"],
|
|
1763
|
+
requestBody: "buffered",
|
|
1764
|
+
fetch: async (request) => {
|
|
1765
|
+
const workspaceId = query(request, "workspaceId");
|
|
1766
|
+
const taskId = query(request, "task") || "tank";
|
|
1767
|
+
if (tasks.definition(taskId) === void 0) return json({ error: "unknown task" }, 400);
|
|
1768
|
+
return json({ prompt: tasks.prompt(workspaceId, taskId) });
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
add({
|
|
1772
|
+
path: MANUAL_ROUTE,
|
|
1773
|
+
methods: ["GET"],
|
|
1774
|
+
requestBody: "buffered",
|
|
1775
|
+
fetch: async (request) => {
|
|
1776
|
+
const workspaceId = query(request, "workspaceId");
|
|
1777
|
+
const taskId = query(request, "task") || "tank";
|
|
1778
|
+
if (tasks.definition(taskId) === void 0) return json({ error: "unknown task" }, 400);
|
|
1779
|
+
return json(tasks.manualPaths(workspaceId, taskId));
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
add({
|
|
1783
|
+
path: EVENTS_ROUTE,
|
|
1784
|
+
methods: ["GET"],
|
|
1785
|
+
requestBody: "buffered",
|
|
1786
|
+
fetch: async (request) => {
|
|
1787
|
+
const since = Number(query(request, "since"));
|
|
1788
|
+
return json({ events: guard.recent(Number.isFinite(since) ? since : 0) });
|
|
1789
|
+
}
|
|
1790
|
+
});
|
|
1791
|
+
add({
|
|
1792
|
+
path: REVEAL_ROUTE,
|
|
1793
|
+
methods: ["POST"],
|
|
1794
|
+
requestBody: "buffered",
|
|
1795
|
+
fetch: async (request) => {
|
|
1796
|
+
let body = {};
|
|
1797
|
+
try {
|
|
1798
|
+
body = await request.json();
|
|
1799
|
+
} catch {
|
|
1800
|
+
return json({ error: "invalid body" }, 400);
|
|
1801
|
+
}
|
|
1802
|
+
const target = typeof body.path === "string" ? path.resolve(body.path) : "";
|
|
1803
|
+
if (target === "") return json({ error: "path is required" }, 400);
|
|
1804
|
+
const material = path.resolve(guardRoot(), "material");
|
|
1805
|
+
const workspaces = child.get("workspaceRegistry")?.list?.() ?? [];
|
|
1806
|
+
if (!(under(target, material) || workspaces.some((workspace) => under(target, workspace.path)))) return json({ error: "path is not revealable" }, 403);
|
|
1807
|
+
if (!fs.existsSync(target)) return json({ error: "path does not exist" }, 404);
|
|
1808
|
+
try {
|
|
1809
|
+
spawn("xdg-open", [target], {
|
|
1810
|
+
detached: true,
|
|
1811
|
+
stdio: "ignore"
|
|
1812
|
+
}).unref();
|
|
1813
|
+
} catch (error) {
|
|
1814
|
+
return json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
1815
|
+
}
|
|
1816
|
+
return json({ ok: true });
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
//#endregion
|
|
1641
1822
|
//#region src/index.ts
|
|
1642
1823
|
/** Stable Cordis plugin name. */
|
|
1643
1824
|
const name = "eval-guard";
|
|
@@ -1691,7 +1872,7 @@ function apply(ctx, config) {
|
|
|
1691
1872
|
guard.dispose();
|
|
1692
1873
|
}, "eval-guard: dispose");
|
|
1693
1874
|
guard.update(config ?? {});
|
|
1694
|
-
ctx.logger.info("eval-guard: task %s ready (%d task definition(s))",
|
|
1875
|
+
ctx.logger.info("eval-guard: task %s ready (%d task definition(s))", TASKS.map((task) => task.id).join(","), TASKS.length);
|
|
1695
1876
|
});
|
|
1696
1877
|
}
|
|
1697
1878
|
//#endregion
|
|
Binary file
|
package/native/supervisor.c
CHANGED
|
@@ -271,6 +271,13 @@ static int is_dev_path(const char *path) {
|
|
|
271
271
|
return 0;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
static int is_exact_policy_path(const char *path) {
|
|
275
|
+
for (int i = 0; i < g_exact_count; i++) {
|
|
276
|
+
if (strcmp(path, g_exact[i]) == 0) return 1;
|
|
277
|
+
}
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
274
281
|
static int base_read_allowed(const char *path) {
|
|
275
282
|
if (g_workspace[0] != '\0' && starts_with_seg(path, g_workspace)) return 1;
|
|
276
283
|
if (is_dev_path(path)) return 1;
|
|
@@ -509,14 +516,18 @@ static int check_notification(pid_t pid, const struct seccomp_notif *req, int *w
|
|
|
509
516
|
int domain = (int)a0;
|
|
510
517
|
*write_access = 0;
|
|
511
518
|
snprintf(path_out, MAX_PATH, "<network:socket>");
|
|
512
|
-
|
|
519
|
+
/* AF_UNIX/AF_NETLINK stay inside the private network namespace and are
|
|
520
|
+
* needed by NSS, Python and the dynamic loader. AF_INET/AF_INET6 and raw
|
|
521
|
+
* sockets are still blocked. */
|
|
522
|
+
if (domain == AF_UNIX || domain == AF_NETLINK) return 1;
|
|
513
523
|
return 0;
|
|
514
524
|
}
|
|
515
525
|
if (is_network_syscall(nr)) {
|
|
516
526
|
*write_access = 0;
|
|
517
527
|
snprintf(path_out, MAX_PATH, "<network:%s>", syscall_name(nr));
|
|
518
|
-
|
|
519
|
-
|
|
528
|
+
/* Only AF_UNIX/AF_NETLINK sockets can exist now; their traffic is local
|
|
529
|
+
* to the private namespace and cannot reach the host network. */
|
|
530
|
+
return 1;
|
|
520
531
|
}
|
|
521
532
|
if (nr == SYS_io_uring_setup || nr == SYS_ptrace || nr == SYS_open_by_handle_at) {
|
|
522
533
|
*write_access = 0;
|
|
@@ -630,6 +641,10 @@ static int check_notification(pid_t pid, const struct seccomp_notif *req, int *w
|
|
|
630
641
|
}
|
|
631
642
|
snprintf(path_out, MAX_PATH, "%s", path);
|
|
632
643
|
*write_access = write;
|
|
644
|
+
/* `mkdir -p` walks every existing ancestor and calls mkdir on each one.
|
|
645
|
+
* Creating an already-existing workspace ancestor is a kernel no-op, not an
|
|
646
|
+
* out-of-workspace write. */
|
|
647
|
+
if (write && existed && (nr == SYS_mkdir || nr == SYS_mkdirat) && is_exact_policy_path(path)) return 1;
|
|
633
648
|
/* bwrap writes the user-namespace uid/gid maps under /proc before it execs
|
|
634
649
|
* the target. Nothing the target runs may write under /proc. */
|
|
635
650
|
if (write && g_current_pid > 0 && is_bwrap(g_current_pid) && starts_with_seg(path, "/proc")) return 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-eval-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Evaluation guard for DSH: workspace-only filesystem, no network/subagents, wall-clock limits, and task installation for the meow-ai-arena tank task.",
|
|
6
6
|
"license": "MIT",
|