@steerable/agent-shell 0.6.15
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/LICENSE +91 -0
- package/contracts/tool-contract.json +326 -0
- package/dist/attachments.d.ts +41 -0
- package/dist/attachments.js +147 -0
- package/dist/brand.d.ts +24 -0
- package/dist/brand.js +92 -0
- package/dist/host/http-routes.d.ts +21 -0
- package/dist/host/http-routes.js +55 -0
- package/dist/host/ipc.d.ts +11 -0
- package/dist/host/ipc.js +20 -0
- package/dist/host/pack-assembly.d.ts +86 -0
- package/dist/host/pack-assembly.js +32 -0
- package/dist/host/runtime.d.ts +69 -0
- package/dist/host/runtime.js +207 -0
- package/dist/host/visible-terminal-exec.d.ts +21 -0
- package/dist/host/visible-terminal-exec.js +151 -0
- package/dist/hosted-web-search.d.ts +13 -0
- package/dist/hosted-web-search.js +82 -0
- package/dist/image-attachment.d.ts +39 -0
- package/dist/image-attachment.js +133 -0
- package/dist/insights/flush.d.ts +10 -0
- package/dist/insights/flush.js +139 -0
- package/dist/insights/record.d.ts +17 -0
- package/dist/insights/record.js +44 -0
- package/dist/json-store.d.ts +9 -0
- package/dist/json-store.js +22 -0
- package/dist/llm/index.d.ts +23 -0
- package/dist/llm/index.js +105 -0
- package/dist/llm/ollama.d.ts +23 -0
- package/dist/llm/ollama.js +242 -0
- package/dist/llm/openai-compat.d.ts +20 -0
- package/dist/llm/openai-compat.js +199 -0
- package/dist/llm/sidecar-provider.d.ts +37 -0
- package/dist/llm/sidecar-provider.js +163 -0
- package/dist/llm/tool-choice.d.ts +35 -0
- package/dist/llm/tool-choice.js +85 -0
- package/dist/llm/types.d.ts +122 -0
- package/dist/llm/types.js +1 -0
- package/dist/local-backend/agent-capability.d.ts +101 -0
- package/dist/local-backend/agent-capability.js +174 -0
- package/dist/local-backend/ai-title.d.ts +43 -0
- package/dist/local-backend/ai-title.js +173 -0
- package/dist/local-backend/auto-continue-helper.d.ts +80 -0
- package/dist/local-backend/auto-continue-helper.js +83 -0
- package/dist/local-backend/branch-helper.d.ts +24 -0
- package/dist/local-backend/branch-helper.js +27 -0
- package/dist/local-backend/context-compactor.d.ts +81 -0
- package/dist/local-backend/context-compactor.js +213 -0
- package/dist/local-backend/coreloop-stream.d.ts +245 -0
- package/dist/local-backend/coreloop-stream.js +277 -0
- package/dist/local-backend/deferred-detector.d.ts +15 -0
- package/dist/local-backend/deferred-detector.js +124 -0
- package/dist/local-backend/history-helper.d.ts +30 -0
- package/dist/local-backend/history-helper.js +34 -0
- package/dist/local-backend/interrupted-helper.d.ts +29 -0
- package/dist/local-backend/interrupted-helper.js +25 -0
- package/dist/local-backend/live-stream.d.ts +36 -0
- package/dist/local-backend/live-stream.js +23 -0
- package/dist/local-backend/llm-diagnose.d.ts +37 -0
- package/dist/local-backend/llm-diagnose.js +284 -0
- package/dist/local-backend/message-triggers.d.ts +27 -0
- package/dist/local-backend/message-triggers.js +67 -0
- package/dist/local-backend/pack-backend-routes.d.ts +31 -0
- package/dist/local-backend/pack-backend-routes.js +62 -0
- package/dist/local-backend/pack-turn-hooks.d.ts +37 -0
- package/dist/local-backend/pack-turn-hooks.js +67 -0
- package/dist/local-backend/prompt-builder.d.ts +101 -0
- package/dist/local-backend/prompt-builder.js +246 -0
- package/dist/local-backend/regenerate-helper.d.ts +62 -0
- package/dist/local-backend/regenerate-helper.js +75 -0
- package/dist/local-backend/router.d.ts +175 -0
- package/dist/local-backend/router.js +3139 -0
- package/dist/local-backend/skill-install.d.ts +19 -0
- package/dist/local-backend/skill-install.js +71 -0
- package/dist/local-backend/skill-loader.d.ts +92 -0
- package/dist/local-backend/skill-loader.js +146 -0
- package/dist/local-backend/skills/00-identity/SKILL.md +32 -0
- package/dist/local-backend/skills/10-goal/SKILL.md +59 -0
- package/dist/local-backend/skills/11-loop/SKILL.md +71 -0
- package/dist/local-backend/skills/12-create-skill/SKILL.md +88 -0
- package/dist/local-backend/skills/70-plan-mode/SKILL.md +58 -0
- package/dist/local-backend/skills/80-tool-usage/SKILL.md +70 -0
- package/dist/local-backend/skills/81-anti-deferred/SKILL.md +53 -0
- package/dist/local-backend/skills/82-data-grounding/SKILL.md +56 -0
- package/dist/local-backend/skills/85-local-exec/SKILL.md +86 -0
- package/dist/local-backend/skills/86-proactive-coding/SKILL.md +51 -0
- package/dist/local-backend/subagent-profiles.d.ts +30 -0
- package/dist/local-backend/subagent-profiles.js +74 -0
- package/dist/local-backend/task-process.d.ts +12 -0
- package/dist/local-backend/task-process.js +176 -0
- package/dist/local-backend/task-service.d.ts +135 -0
- package/dist/local-backend/task-service.js +565 -0
- package/dist/local-backend/turn-duration.d.ts +2 -0
- package/dist/local-backend/turn-duration.js +9 -0
- package/dist/local-backend/turn-timeline.d.ts +16 -0
- package/dist/local-backend/turn-timeline.js +42 -0
- package/dist/local-backend/worktree-service.d.ts +84 -0
- package/dist/local-backend/worktree-service.js +243 -0
- package/dist/local-edit.d.ts +48 -0
- package/dist/local-edit.js +44 -0
- package/dist/local-executor.d.ts +255 -0
- package/dist/local-executor.js +881 -0
- package/dist/local-script-registry.d.ts +28 -0
- package/dist/local-script-registry.js +63 -0
- package/dist/log.d.ts +13 -0
- package/dist/log.js +12 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +855 -0
- package/dist/mcp-executor.d.ts +45 -0
- package/dist/mcp-executor.js +241 -0
- package/dist/mcp-server-registry.d.ts +104 -0
- package/dist/mcp-server-registry.js +234 -0
- package/dist/preload-default.d.ts +1 -0
- package/dist/preload-default.js +9 -0
- package/dist/preload.cjs +395 -0
- package/dist/preload.d.ts +20 -0
- package/dist/preload.js +411 -0
- package/dist/product-config.d.ts +43 -0
- package/dist/product-config.js +26 -0
- package/dist/project-registry.d.ts +55 -0
- package/dist/project-registry.js +106 -0
- package/dist/project-rules.d.ts +15 -0
- package/dist/project-rules.js +102 -0
- package/dist/runtime.d.ts +62 -0
- package/dist/runtime.js +217 -0
- package/dist/scenario/pack.d.ts +8 -0
- package/dist/scenario/pack.js +1 -0
- package/dist/scenario/registry.d.ts +24 -0
- package/dist/scenario/registry.js +31 -0
- package/dist/server/http-server.d.ts +39 -0
- package/dist/server/http-server.js +361 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +107 -0
- package/dist/server/sse-bus.d.ts +14 -0
- package/dist/server/sse-bus.js +31 -0
- package/dist/shell-adapt.d.ts +21 -0
- package/dist/shell-adapt.js +104 -0
- package/dist/sidecar/boot.d.ts +36 -0
- package/dist/sidecar/boot.js +343 -0
- package/dist/sidecar/egress-hint.d.ts +15 -0
- package/dist/sidecar/egress-hint.js +46 -0
- package/dist/sidecar/egress-proxy.d.ts +183 -0
- package/dist/sidecar/egress-proxy.js +419 -0
- package/dist/sidecar/errors.d.ts +22 -0
- package/dist/sidecar/errors.js +38 -0
- package/dist/sidecar/exec-sandbox.d.ts +48 -0
- package/dist/sidecar/exec-sandbox.js +94 -0
- package/dist/sidecar/handle.d.ts +32 -0
- package/dist/sidecar/handle.js +53 -0
- package/dist/sidecar/index.d.ts +14 -0
- package/dist/sidecar/index.js +13 -0
- package/dist/sidecar/proxy-detect.d.ts +50 -0
- package/dist/sidecar/proxy-detect.js +182 -0
- package/dist/sidecar/reverse-approval.d.ts +55 -0
- package/dist/sidecar/reverse-approval.js +86 -0
- package/dist/sidecar/reverse-ask-user.d.ts +34 -0
- package/dist/sidecar/reverse-ask-user.js +59 -0
- package/dist/sidecar/reverse-spawn.d.ts +19 -0
- package/dist/sidecar/reverse-spawn.js +161 -0
- package/dist/sidecar/reverse-tools.d.ts +29 -0
- package/dist/sidecar/reverse-tools.js +106 -0
- package/dist/sidecar/safety-patterns.d.ts +41 -0
- package/dist/sidecar/safety-patterns.js +157 -0
- package/dist/sidecar/storage-path.d.ts +14 -0
- package/dist/sidecar/storage-path.js +35 -0
- package/dist/sidecar/supervisor.d.ts +218 -0
- package/dist/sidecar/supervisor.js +932 -0
- package/dist/sidecar/types.d.ts +601 -0
- package/dist/sidecar/types.js +1 -0
- package/dist/single-instance.d.ts +11 -0
- package/dist/single-instance.js +21 -0
- package/dist/storage/empty-chats.d.ts +9 -0
- package/dist/storage/empty-chats.js +16 -0
- package/dist/storage/index.d.ts +373 -0
- package/dist/storage/index.js +1158 -0
- package/dist/storage/insights-redact.d.ts +2 -0
- package/dist/storage/insights-redact.js +30 -0
- package/dist/storage/insights-settings.d.ts +53 -0
- package/dist/storage/insights-settings.js +92 -0
- package/dist/storage/llm-settings.d.ts +120 -0
- package/dist/storage/llm-settings.js +233 -0
- package/dist/storage/local-store-singleton.d.ts +28 -0
- package/dist/storage/local-store-singleton.js +38 -0
- package/dist/storage/message-order.d.ts +25 -0
- package/dist/storage/message-order.js +27 -0
- package/dist/storage/pack-migrations.d.ts +22 -0
- package/dist/storage/pack-migrations.js +24 -0
- package/dist/storage/pack-seeds.d.ts +36 -0
- package/dist/storage/pack-seeds.js +42 -0
- package/dist/storage/telemetry-settings.d.ts +38 -0
- package/dist/storage/telemetry-settings.js +59 -0
- package/dist/storage/usage-summary.d.ts +55 -0
- package/dist/storage/usage-summary.js +38 -0
- package/dist/storage/web-search-settings.d.ts +38 -0
- package/dist/storage/web-search-settings.js +74 -0
- package/dist/storage/write-lease.d.ts +26 -0
- package/dist/storage/write-lease.js +74 -0
- package/dist/terminal-manager.d.ts +83 -0
- package/dist/terminal-manager.js +506 -0
- package/dist/tool-router.d.ts +228 -0
- package/dist/tool-router.js +930 -0
- package/dist/tool-search-rank.d.ts +42 -0
- package/dist/tool-search-rank.js +96 -0
- package/package.json +67 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W4.1.1 reverse channel: serve the sidecar's `host.process.spawn` by
|
|
3
|
+
* spawning the command confined on Windows via the win-spawn-helper Rust
|
|
4
|
+
* binary (restricted token + Job Object; contract: steerable-framework
|
|
5
|
+
* docs/spec/safety.md "Host capability surface").
|
|
6
|
+
*
|
|
7
|
+
* Fail closed by construction: on non-Windows platforms (which have local
|
|
8
|
+
* rewriter backends and never legitimately route here) or when the helper
|
|
9
|
+
* binary is absent, the handler throws — the sidecar surfaces a tool error
|
|
10
|
+
* and the command never runs unconfined.
|
|
11
|
+
*
|
|
12
|
+
* Enforcement honesty: the helper confines filesystem writes only. When the
|
|
13
|
+
* sidecar policy also constrains the network (`network: false` or an
|
|
14
|
+
* allow-list), the helper's exit frame lists those fields under
|
|
15
|
+
* `sandbox.notEnforced` and this handler reports `partial`.
|
|
16
|
+
*/
|
|
17
|
+
import { spawn } from 'node:child_process';
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { createInterface } from 'node:readline';
|
|
22
|
+
import log from 'electron-log';
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = dirname(__filename);
|
|
25
|
+
/** Helper spawn supervision cap; the sidecar's own tool timeout is shorter. */
|
|
26
|
+
const HELPER_TIMEOUT_MS = 30 * 60 * 1000;
|
|
27
|
+
export function resolveWinSpawnHelperPath() {
|
|
28
|
+
const binary = 'win-spawn-helper.exe';
|
|
29
|
+
// 显式覆盖(测试/调试逃生门):设置后只认这一条路径。指向不存在的
|
|
30
|
+
// 路径即可强制走"helper 缺失"的失败分支,与 dev checkout 是否已构建
|
|
31
|
+
// Rust 二进制无关,保证失败分支的测试确定性。
|
|
32
|
+
const override = process.env.DEEPPATH_WIN_SPAWN_HELPER;
|
|
33
|
+
if (override !== undefined) {
|
|
34
|
+
return override && existsSync(override) ? override : null;
|
|
35
|
+
}
|
|
36
|
+
const resourcesPath = process.resourcesPath;
|
|
37
|
+
const candidates = [];
|
|
38
|
+
if (resourcesPath) {
|
|
39
|
+
candidates.push(join(resourcesPath, 'win-spawn-helper', binary));
|
|
40
|
+
}
|
|
41
|
+
// Dev checkout: native/ builds into resources/windows-spawn-helper/.
|
|
42
|
+
candidates.push(join(__dirname, '..', '..', 'resources', 'windows-spawn-helper', binary));
|
|
43
|
+
for (const candidate of candidates) {
|
|
44
|
+
if (existsSync(candidate))
|
|
45
|
+
return candidate;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
export function createProcessSpawnHandler() {
|
|
50
|
+
return async (params) => {
|
|
51
|
+
if (process.platform !== 'win32') {
|
|
52
|
+
throw new Error('host.process.spawn is the no-rewriter route for Windows; ' +
|
|
53
|
+
`this host is ${process.platform} — the sidecar should have rewritten the command locally`);
|
|
54
|
+
}
|
|
55
|
+
const p = (params ?? {});
|
|
56
|
+
const command = typeof p.command === 'string' ? p.command : '';
|
|
57
|
+
if (!command.trim()) {
|
|
58
|
+
throw new Error('host.process.spawn: missing command');
|
|
59
|
+
}
|
|
60
|
+
const helper = resolveWinSpawnHelperPath();
|
|
61
|
+
if (!helper) {
|
|
62
|
+
throw new Error('win-spawn-helper.exe not found (packaged resource or resources/windows-spawn-helper); ' +
|
|
63
|
+
'refusing to run the command unconfined');
|
|
64
|
+
}
|
|
65
|
+
const cwd = typeof p.cwd === 'string' && p.cwd ? p.cwd : process.cwd();
|
|
66
|
+
const policy = p.policy ?? {};
|
|
67
|
+
return await runHelper(helper, {
|
|
68
|
+
command,
|
|
69
|
+
cwd,
|
|
70
|
+
writable_roots: policy.writableRoots ?? [],
|
|
71
|
+
network: policy.network ?? false,
|
|
72
|
+
allowed_hosts: policy.allowedHosts ?? [],
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function runHelper(helperPath, spec) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const child = spawn(helperPath, [], {
|
|
79
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
80
|
+
windowsHide: true,
|
|
81
|
+
});
|
|
82
|
+
let stdout = '';
|
|
83
|
+
let stderr = '';
|
|
84
|
+
let truncated = false;
|
|
85
|
+
let timedOut = false;
|
|
86
|
+
let settled = false;
|
|
87
|
+
const timer = setTimeout(() => {
|
|
88
|
+
if (settled)
|
|
89
|
+
return;
|
|
90
|
+
timedOut = true;
|
|
91
|
+
log.warn('[host-spawn] helper timeout; tree-killing confined child');
|
|
92
|
+
try {
|
|
93
|
+
child.stdin.write('{"kill":true}\n');
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// stdin already closed — the helper is gone; destroy below.
|
|
97
|
+
}
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
if (!settled)
|
|
100
|
+
child.kill();
|
|
101
|
+
}, 5000).unref();
|
|
102
|
+
}, HELPER_TIMEOUT_MS);
|
|
103
|
+
timer.unref();
|
|
104
|
+
const lines = createInterface({ input: child.stdout });
|
|
105
|
+
lines.on('line', (line) => {
|
|
106
|
+
let frame;
|
|
107
|
+
try {
|
|
108
|
+
frame = JSON.parse(line);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
log.warn('[host-spawn] unparseable helper frame', line.slice(0, 200));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (frame.type === 'stdout') {
|
|
115
|
+
stdout += frame.data ?? '';
|
|
116
|
+
}
|
|
117
|
+
else if (frame.type === 'stderr') {
|
|
118
|
+
stderr += frame.data ?? '';
|
|
119
|
+
}
|
|
120
|
+
else if (frame.type === 'error') {
|
|
121
|
+
settled = true;
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
reject(new Error(`win-spawn-helper setup failed: ${frame.message ?? 'unknown'}`));
|
|
124
|
+
}
|
|
125
|
+
else if (frame.type === 'exit') {
|
|
126
|
+
settled = true;
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
truncated = Boolean(frame.stdoutTruncated || frame.stderrTruncated);
|
|
129
|
+
const notEnforced = frame.sandbox?.notEnforced ?? [];
|
|
130
|
+
resolve({
|
|
131
|
+
exitCode: typeof frame.code === 'number' ? frame.code : 1,
|
|
132
|
+
stdout,
|
|
133
|
+
stderr: timedOut
|
|
134
|
+
? `${stderr}\n[killed by host: exceeded ${HELPER_TIMEOUT_MS / 60_000}min spawn cap]`
|
|
135
|
+
: stderr,
|
|
136
|
+
truncated,
|
|
137
|
+
sandbox: {
|
|
138
|
+
backend: frame.sandbox?.backend ?? 'windows-restricted-token',
|
|
139
|
+
enforcement: notEnforced.length === 0 ? 'full' : 'partial',
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
child.on('error', (err) => {
|
|
145
|
+
if (settled)
|
|
146
|
+
return;
|
|
147
|
+
settled = true;
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
reject(new Error(`failed to spawn win-spawn-helper: ${err.message}`));
|
|
150
|
+
});
|
|
151
|
+
child.on('exit', (code) => {
|
|
152
|
+
if (settled)
|
|
153
|
+
return;
|
|
154
|
+
settled = true;
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
// Helper exited without an exit frame: crash between spawn and report.
|
|
157
|
+
reject(new Error(`win-spawn-helper exited prematurely (code ${code})`));
|
|
158
|
+
});
|
|
159
|
+
child.stdin.write(JSON.stringify(spec) + '\n');
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A4 reverse channel: serve the sidecar-hosted CoreLoop's `tool.invoke`
|
|
3
|
+
* requests by executing the tool in this process (shell / files / MCP live
|
|
4
|
+
* in Electron, not in Python).
|
|
5
|
+
*
|
|
6
|
+
* Hard-gates critical shell commands — the TS loop only annotates them, but
|
|
7
|
+
* on the CoreLoop path the framework's blocking semantics apply.
|
|
8
|
+
*/
|
|
9
|
+
import type { ToolRouter } from '../tool-router.js';
|
|
10
|
+
import type { SidecarReverseHandler } from './types.js';
|
|
11
|
+
export interface ReverseToolDeps {
|
|
12
|
+
toolRouter: ToolRouter;
|
|
13
|
+
onBlocked?: (info: {
|
|
14
|
+
command: string;
|
|
15
|
+
rules: string[];
|
|
16
|
+
}) => void;
|
|
17
|
+
/**
|
|
18
|
+
* 项目模式:按 chatId 解析项目根(绝对路径),无绑定/已删除返回 null。
|
|
19
|
+
* main.ts 注入 LocalBackendRouter.resolveChatProject 的适配;未注入时
|
|
20
|
+
* 项目围栏不生效(CLI/test 场景)。
|
|
21
|
+
*/
|
|
22
|
+
resolveProjectRoot?: (chatId: string) => string | null;
|
|
23
|
+
/**
|
|
24
|
+
* 额外放行的只读根(会话附件目录等)。写入仍只受 projectRoot 围栏约束,
|
|
25
|
+
* 这里只放宽 local_read_file 的读取范围。
|
|
26
|
+
*/
|
|
27
|
+
resolveAdditionalReadRoots?: (chatId: string) => string[];
|
|
28
|
+
}
|
|
29
|
+
export declare function createToolInvokeHandler(deps: ReverseToolDeps): SidecarReverseHandler;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A4 reverse channel: serve the sidecar-hosted CoreLoop's `tool.invoke`
|
|
3
|
+
* requests by executing the tool in this process (shell / files / MCP live
|
|
4
|
+
* in Electron, not in Python).
|
|
5
|
+
*
|
|
6
|
+
* Hard-gates critical shell commands — the TS loop only annotates them, but
|
|
7
|
+
* on the CoreLoop path the framework's blocking semantics apply.
|
|
8
|
+
*/
|
|
9
|
+
import { classifyShellCommand } from './safety-patterns.js';
|
|
10
|
+
import { compactToolResultJson } from '../local-backend/context-compactor.js';
|
|
11
|
+
import { normalizeToolPolicy } from '../local-backend/agent-capability.js';
|
|
12
|
+
/**
|
|
13
|
+
* 模型上下文硬顶:喂给 sidecar 的工具结果必须待在框架逐项 10k token 断言线
|
|
14
|
+
* 以下(2026-08-28 E2E 录制实锤:local_exec_shell 对 node_modules 跑
|
|
15
|
+
* `grep -R` 返回了 ~68k token——终端采集的 256KiB 上限是 UI 防爆兜底,
|
|
16
|
+
* 不是模型预算)。完整输出用户始终能在可见终端里看到,这里只截断喂给
|
|
17
|
+
* 模型的那份。复用 context-compactor 的既有策略(8000 字符总量 /
|
|
18
|
+
* 2000 单字段,最坏情况纯 CJK 约 4.8k token)。
|
|
19
|
+
*/
|
|
20
|
+
function capResultForModelContext(out) {
|
|
21
|
+
const json = JSON.stringify(out);
|
|
22
|
+
if (json.length <= 8000)
|
|
23
|
+
return out;
|
|
24
|
+
return JSON.parse(compactToolResultJson(json));
|
|
25
|
+
}
|
|
26
|
+
export function createToolInvokeHandler(deps) {
|
|
27
|
+
return async (params) => {
|
|
28
|
+
const p = (params ?? {});
|
|
29
|
+
const name = typeof p.name === 'string' ? p.name : '';
|
|
30
|
+
const toolArgs = p.arguments ?? {};
|
|
31
|
+
if (!name) {
|
|
32
|
+
return { success: false, error: 'missing tool name', needsFollowup: false };
|
|
33
|
+
}
|
|
34
|
+
// Plan mode is enforced at the execution layer too, not just by tool
|
|
35
|
+
// advertisement — a pseudo-recovered call could name a write tool that
|
|
36
|
+
// was never advertised.
|
|
37
|
+
if (p.context?.mode === 'plan') {
|
|
38
|
+
const schema = deps.toolRouter.listSchemas().find((s) => s.name === name);
|
|
39
|
+
if (schema && schema.mode !== 'read') {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: `blocked by plan mode: ${name} is a write tool`,
|
|
43
|
+
needsFollowup: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// 项目模式硬围栏(W4-2 接线补齐):CoreLoop 路径此前把 projectRoot 丢
|
|
48
|
+
// 了——toolContext 只带 mode,ToolRouter 的项目沙箱(cwd 收口 + 文件
|
|
49
|
+
// 路径围栏)在 sidecar 驱动的回合里形同虚设。这里从 chat 绑定解析
|
|
50
|
+
// 项目根并随调用上下文下发,与旧 TS 循环语义对齐。
|
|
51
|
+
// 4.6b:调用方显式带 workspaceRoot(worktree 任务的隔离工作区)时
|
|
52
|
+
// 优先采用——围栏根收窄到 worktree,任务摸不到主检出。
|
|
53
|
+
const projectRoot = typeof p.context?.workspaceRoot === 'string' && p.context.workspaceRoot
|
|
54
|
+
? p.context.workspaceRoot
|
|
55
|
+
: p.context?.chatId
|
|
56
|
+
? (deps.resolveProjectRoot?.(p.context.chatId) ?? null)
|
|
57
|
+
: null;
|
|
58
|
+
const additionalReadRoots = p.context?.chatId
|
|
59
|
+
? (deps.resolveAdditionalReadRoots?.(p.context.chatId) ?? [])
|
|
60
|
+
: [];
|
|
61
|
+
if (name === 'local_exec_shell') {
|
|
62
|
+
const classification = classifyShellCommand(String(toolArgs.command ?? ''));
|
|
63
|
+
if (classification.severity === 'critical') {
|
|
64
|
+
deps.onBlocked?.({
|
|
65
|
+
command: String(toolArgs.command ?? '').slice(0, 200),
|
|
66
|
+
rules: classification.matchedRules,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: `blocked by shell safety policy (critical): ${classification.matchedRules.join(', ')}`,
|
|
71
|
+
needsFollowup: false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// 智能体工具策略随 toolContext 下发(router 按本轮生效的智能体解析),
|
|
76
|
+
// 未配置策略时不进调用上下文——那种回合的上下文与旧版逐字段一致。
|
|
77
|
+
const normalizedPolicy = p.context?.toolPolicy
|
|
78
|
+
? normalizeToolPolicy(p.context.toolPolicy)
|
|
79
|
+
: null;
|
|
80
|
+
const toolPolicy = normalizedPolicy && normalizedPolicy.mode !== 'all' ? normalizedPolicy : null;
|
|
81
|
+
const execContext = projectRoot || additionalReadRoots.length > 0 || p.context?.chatId || toolPolicy
|
|
82
|
+
? {
|
|
83
|
+
projectRoot,
|
|
84
|
+
chatId: p.context?.chatId,
|
|
85
|
+
// 4.6a:chatId 随上下文透传——task_run/status/result 与
|
|
86
|
+
// worktree_* 工具按它把任务绑定到来源对话。
|
|
87
|
+
...(toolPolicy ? { toolPolicy } : {}),
|
|
88
|
+
...(additionalReadRoots.length > 0 ? { additionalReadRoots } : {}),
|
|
89
|
+
}
|
|
90
|
+
: undefined;
|
|
91
|
+
try {
|
|
92
|
+
const out = await deps.toolRouter.execute({ name, arguments: toolArgs }, execContext);
|
|
93
|
+
if (out && typeof out === 'object' && 'success' in out) {
|
|
94
|
+
return capResultForModelContext(out);
|
|
95
|
+
}
|
|
96
|
+
return capResultForModelContext({ success: true, data: { value: out } });
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: err instanceof Error ? err.message : String(err),
|
|
102
|
+
needsFollowup: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface CommandSafetyConfig {
|
|
2
|
+
disabledPatternIds?: string[];
|
|
3
|
+
hiddenPatternIds?: string[];
|
|
4
|
+
customPatterns?: Array<{
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
pattern: string;
|
|
8
|
+
category: string;
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export type PatternSeverity = 'critical' | 'warning';
|
|
13
|
+
export type PatternPlatform = 'all' | 'unix' | 'windows';
|
|
14
|
+
export interface SafetyPatternDef {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
description: string;
|
|
18
|
+
pattern: string;
|
|
19
|
+
category: string;
|
|
20
|
+
severity: PatternSeverity;
|
|
21
|
+
platform: PatternPlatform;
|
|
22
|
+
}
|
|
23
|
+
export declare const SAFETY_CATEGORIES: Record<string, string>;
|
|
24
|
+
export declare const BUILTIN_PATTERNS: SafetyPatternDef[];
|
|
25
|
+
/**
|
|
26
|
+
* 根据用户配置计算当前生效的正则模式列表
|
|
27
|
+
*/
|
|
28
|
+
export declare function getActivePatterns(config?: CommandSafetyConfig | null): RegExp[];
|
|
29
|
+
/**
|
|
30
|
+
* 按分类分组返回内置模式
|
|
31
|
+
*/
|
|
32
|
+
export declare function getPatternsByCategory(): Record<string, SafetyPatternDef[]>;
|
|
33
|
+
/**
|
|
34
|
+
* 默认空配置(所有内置模式均启用,无自定义模式)
|
|
35
|
+
*/
|
|
36
|
+
export declare const DEFAULT_COMMAND_SAFETY_CONFIG: CommandSafetyConfig;
|
|
37
|
+
export interface ShellCommandClassification {
|
|
38
|
+
severity: 'safe' | PatternSeverity;
|
|
39
|
+
matchedRules: string[];
|
|
40
|
+
}
|
|
41
|
+
export declare function classifyShellCommand(command: string, config?: CommandSafetyConfig | null): ShellCommandClassification;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// ─── Categories ──────────────────────────────────────────────────────
|
|
2
|
+
export const SAFETY_CATEGORIES = {
|
|
3
|
+
file_ops: '文件操作',
|
|
4
|
+
system: '系统管理',
|
|
5
|
+
process: '进程管理',
|
|
6
|
+
network: '网络安全',
|
|
7
|
+
package: '包管理',
|
|
8
|
+
vcs: '版本控制',
|
|
9
|
+
container: '容器管理',
|
|
10
|
+
file_write: '文件写入',
|
|
11
|
+
windows: 'Windows / PowerShell',
|
|
12
|
+
};
|
|
13
|
+
// ─── Built-in patterns ──────────────────────────────────────────────
|
|
14
|
+
// Merged from local-actions.ts (web, severity=warning) and
|
|
15
|
+
// local-executor.ts (desktop, severity=critical).
|
|
16
|
+
export const BUILTIN_PATTERNS = [
|
|
17
|
+
// ── file_ops ──
|
|
18
|
+
{ id: 'rm', label: 'rm 删除', description: '匹配 rm 命令', pattern: '\\brm\\s', category: 'file_ops', severity: 'warning', platform: 'unix' },
|
|
19
|
+
{ id: 'rm_end', label: 'rm(行尾)', description: '匹配行尾的 rm', pattern: '\\brm$', category: 'file_ops', severity: 'warning', platform: 'unix' },
|
|
20
|
+
{ id: 'rm_rf_root', label: 'rm -rf /', description: '递归删除根目录', pattern: 'rm\\s+-rf\\s+\\/(?:\\s|$)', category: 'file_ops', severity: 'critical', platform: 'unix' },
|
|
21
|
+
{ id: 'rmdir', label: 'rmdir 删除目录', description: '匹配 rmdir 命令', pattern: '\\brmdir\\s', category: 'file_ops', severity: 'warning', platform: 'unix' },
|
|
22
|
+
{ id: 'mv', label: 'mv 移动/重命名', description: '匹配 mv 命令', pattern: '\\bmv\\s.*\\/', category: 'file_ops', severity: 'warning', platform: 'unix' },
|
|
23
|
+
{ id: 'shred', label: 'shred 安全擦除', description: '不可恢复地擦除文件', pattern: '\\bshred\\b', category: 'file_ops', severity: 'warning', platform: 'unix' },
|
|
24
|
+
{ id: 'truncate', label: 'truncate 截断文件', description: '截断文件内容', pattern: '\\btruncate\\b', category: 'file_ops', severity: 'warning', platform: 'unix' },
|
|
25
|
+
// ── system ──
|
|
26
|
+
{ id: 'sudo', label: 'sudo 提权', description: '以超级用户权限执行', pattern: '\\bsudo\\s', category: 'system', severity: 'critical', platform: 'unix' },
|
|
27
|
+
{ id: 'mkfs', label: 'mkfs 格式化磁盘', description: '创建文件系统(格式化)', pattern: '\\bmkfs\\b', category: 'system', severity: 'critical', platform: 'unix' },
|
|
28
|
+
{ id: 'dd', label: 'dd 磁盘写入', description: '底层磁盘数据复制', pattern: '\\bdd\\s', category: 'system', severity: 'warning', platform: 'unix' },
|
|
29
|
+
{ id: 'dd_if', label: 'dd if= 磁盘镜像', description: '使用 dd if= 读写磁盘', pattern: '\\bdd\\s+if=', category: 'system', severity: 'critical', platform: 'unix' },
|
|
30
|
+
{ id: 'format', label: 'format 格式化', description: '格式化磁盘', pattern: '\\bformat\\b', category: 'system', severity: 'warning', platform: 'all' },
|
|
31
|
+
{ id: 'shutdown', label: 'shutdown 关机', description: '关闭系统', pattern: '\\bshutdown\\b', category: 'system', severity: 'warning', platform: 'all' },
|
|
32
|
+
{ id: 'reboot', label: 'reboot 重启', description: '重启系统', pattern: '\\breboot\\b', category: 'system', severity: 'warning', platform: 'all' },
|
|
33
|
+
{ id: 'chmod', label: 'chmod 修改权限', description: '修改文件权限', pattern: '\\bchmod\\s', category: 'system', severity: 'warning', platform: 'unix' },
|
|
34
|
+
{ id: 'chmod_777_root', label: 'chmod -R 777 /', description: '递归赋予根目录所有权限', pattern: 'chmod\\s+-R\\s+777\\s+\\/(?:\\s|$)', category: 'system', severity: 'critical', platform: 'unix' },
|
|
35
|
+
{ id: 'chown', label: 'chown 修改所有者', description: '修改文件所有者', pattern: '\\bchown\\s', category: 'system', severity: 'warning', platform: 'unix' },
|
|
36
|
+
{ id: 'fork_bomb', label: 'Fork Bomb', description: ':(){ :|:& };: fork 炸弹', pattern: ':\\(\\)\\s*\\{\\s*:\\|:&\\s*\\};:', category: 'system', severity: 'critical', platform: 'unix' },
|
|
37
|
+
// ── process ──
|
|
38
|
+
{ id: 'kill', label: 'kill 终止进程', description: '向进程发送信号', pattern: '\\bkill\\s', category: 'process', severity: 'warning', platform: 'unix' },
|
|
39
|
+
{ id: 'killall', label: 'killall 终止所有', description: '按名称终止进程', pattern: '\\bkillall\\s', category: 'process', severity: 'warning', platform: 'unix' },
|
|
40
|
+
// ── network ──
|
|
41
|
+
{ id: 'curl_pipe_sh', label: 'curl | sh', description: '从网络下载并直接执行脚本', pattern: '\\bcurl\\s.*\\|\\s*(sh|bash|zsh)', category: 'network', severity: 'warning', platform: 'unix' },
|
|
42
|
+
{ id: 'wget_pipe_sh', label: 'wget | sh', description: '从网络下载并直接执行脚本', pattern: '\\bwget\\s.*\\|\\s*(sh|bash|zsh)', category: 'network', severity: 'warning', platform: 'unix' },
|
|
43
|
+
{ id: 'redirect_dev', label: '重定向到 /dev/', description: '向设备文件写入数据', pattern: '>\\s*\\/dev\\/', category: 'network', severity: 'warning', platform: 'unix' },
|
|
44
|
+
// ── package ──
|
|
45
|
+
{ id: 'npm_publish', label: 'npm publish', description: '发布/取消发布 npm 包', pattern: '\\bnpm\\s+(publish|unpublish)', category: 'package', severity: 'warning', platform: 'all' },
|
|
46
|
+
{ id: 'pip_install', label: 'pip install', description: '安装 Python 包', pattern: '\\bpip\\s+install\\b', category: 'package', severity: 'warning', platform: 'all' },
|
|
47
|
+
{ id: 'npm_install', label: 'npm install', description: '安装 npm 包', pattern: '\\bnpm\\s+install\\b', category: 'package', severity: 'warning', platform: 'all' },
|
|
48
|
+
{ id: 'yarn_add', label: 'yarn add', description: '添加 yarn 依赖', pattern: '\\byarn\\s+add\\b', category: 'package', severity: 'warning', platform: 'all' },
|
|
49
|
+
{ id: 'pnpm_add', label: 'pnpm add', description: '添加 pnpm 依赖', pattern: '\\bpnpm\\s+add\\b', category: 'package', severity: 'warning', platform: 'all' },
|
|
50
|
+
{ id: 'uv_add', label: 'uv add', description: '添加 uv 依赖', pattern: '\\buv\\s+add\\b', category: 'package', severity: 'warning', platform: 'all' },
|
|
51
|
+
{ id: 'apt_install', label: 'apt install/remove', description: '系统包管理器操作', pattern: '\\bapt(-get)?\\s+(install|remove|purge)', category: 'package', severity: 'warning', platform: 'unix' },
|
|
52
|
+
{ id: 'brew_install', label: 'brew install/uninstall', description: 'Homebrew 包管理器操作', pattern: '\\bbrew\\s+(install|uninstall|remove)', category: 'package', severity: 'warning', platform: 'unix' },
|
|
53
|
+
// ── vcs ──
|
|
54
|
+
{ id: 'git_push', label: 'git push / reset --hard', description: 'Git 远程推送或硬重置', pattern: '\\bgit\\s+(push|reset\\s+--hard|clean\\s+-fd)', category: 'vcs', severity: 'warning', platform: 'all' },
|
|
55
|
+
// ── container ──
|
|
56
|
+
{ id: 'docker_rm', label: 'docker rm/rmi/prune', description: '删除容器/镜像或清理系统', pattern: '\\bdocker\\s+(rm|rmi|system\\s+prune)', category: 'container', severity: 'warning', platform: 'all' },
|
|
57
|
+
// ── file_write ──
|
|
58
|
+
{ id: 'redirect_overwrite', label: '> / >> 重定向写入', description: '文件重定向覆盖或追加', pattern: '\\b(>\\s|>>)\\s*[^|]', category: 'file_write', severity: 'warning', platform: 'all' },
|
|
59
|
+
{ id: 'tee', label: 'tee 写入文件', description: '将输出写入文件', pattern: '\\btee\\s', category: 'file_write', severity: 'warning', platform: 'unix' },
|
|
60
|
+
{ id: 'sed_inplace', label: 'sed -i 原地修改', description: '直接修改文件内容', pattern: '\\bsed\\s+-i', category: 'file_write', severity: 'warning', platform: 'unix' },
|
|
61
|
+
// ── windows ──
|
|
62
|
+
{ id: 'win_del', label: 'del 删除', description: 'Windows 删除命令', pattern: '\\bdel\\s', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
63
|
+
{ id: 'win_rd', label: 'rd 删除目录', description: 'Windows 删除目录', pattern: '\\brd\\s', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
64
|
+
{ id: 'win_rd_end', label: 'rd(行尾)', description: '匹配行尾的 rd', pattern: '\\brd$', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
65
|
+
{ id: 'win_rdel', label: 'rdel', description: 'Windows rdel 命令', pattern: '\\brdel\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
66
|
+
{ id: 'win_del_force', label: 'del /f /s /q 强制删除', description: '强制递归删除整个驱动器', pattern: '\\bdel\\s+\\/f\\s+\\/s\\s+\\/q\\s+[a-z]:\\\\', category: 'windows', severity: 'critical', platform: 'windows' },
|
|
67
|
+
{ id: 'win_rd_force', label: 'rd /s /q 强制删除目录', description: '强制递归删除整个驱动器目录', pattern: '\\brd\\s+\\/s\\s+\\/q\\s+[a-z]:\\\\', category: 'windows', severity: 'critical', platform: 'windows' },
|
|
68
|
+
{ id: 'win_remove_item', label: 'Remove-Item', description: 'PowerShell 删除项', pattern: '\\bRemove-Item\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
69
|
+
{ id: 'win_stop_process', label: 'Stop-Process', description: 'PowerShell 终止进程', pattern: '\\bStop-Process\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
70
|
+
{ id: 'win_stop_computer', label: 'Stop-Computer', description: 'PowerShell 关机', pattern: '\\bStop-Computer\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
71
|
+
{ id: 'win_restart_computer', label: 'Restart-Computer', description: 'PowerShell 重启', pattern: '\\bRestart-Computer\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
72
|
+
{ id: 'win_set_execution_policy', label: 'Set-ExecutionPolicy', description: '修改脚本执行策略', pattern: '\\bSet-ExecutionPolicy\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
73
|
+
{ id: 'win_format_volume', label: 'Format-Volume', description: 'PowerShell 格式化卷', pattern: '\\bFormat-Volume\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
74
|
+
{ id: 'win_clear_disk', label: 'Clear-Disk', description: 'PowerShell 清除磁盘', pattern: '\\bClear-Disk\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
75
|
+
{ id: 'win_wsl', label: 'wsl 子系统', description: '调用 WSL 子系统', pattern: '\\bwsl\\s', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
76
|
+
{ id: 'win_powershell_cmd', label: 'powershell -Command', description: '通过 PowerShell 执行命令', pattern: '\\bpowershell\\s.*-[Cc]ommand', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
77
|
+
{ id: 'win_pwsh', label: 'pwsh', description: 'PowerShell Core', pattern: '\\bpwsh\\s', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
78
|
+
{ id: 'win_cmd_c', label: 'cmd /c', description: 'CMD 执行命令', pattern: '\\bcmd\\s*\\/c\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
79
|
+
{ id: 'win_reg', label: 'reg delete/add', description: '注册表操作', pattern: '\\breg\\s+(delete|add)\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
80
|
+
{ id: 'win_net', label: 'net user/stop/start', description: '网络和用户管理', pattern: '\\bnet\\s+(user|stop|start)\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
81
|
+
{ id: 'win_sc', label: 'sc delete/stop/config', description: '服务管理', pattern: '\\bsc\\s+(delete|stop|config)\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
82
|
+
{ id: 'win_diskpart', label: 'diskpart', description: '磁盘分区工具', pattern: '\\bdiskpart\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
83
|
+
{ id: 'win_bcdedit', label: 'bcdedit', description: '启动配置编辑', pattern: '\\bbcdedit\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
84
|
+
{ id: 'win_sfc', label: 'sfc', description: '系统文件检查器', pattern: '\\bsfc\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
85
|
+
{ id: 'win_dism', label: 'dism', description: '部署映像服务和管理', pattern: '\\bdism\\b', category: 'windows', severity: 'warning', platform: 'windows' },
|
|
86
|
+
// 只匹配"format <盘符>:"形式的磁盘格式化(含 format.com),避免误伤
|
|
87
|
+
// PowerShell 的 Format-List / Format-Table 等格式化输出 cmdlet
|
|
88
|
+
// (Format-Volume 由上面的 win_format_volume 单独覆盖)。
|
|
89
|
+
{ id: 'win_format_cmd', label: 'format(Windows)', description: 'Windows 格式化磁盘命令', pattern: '\\bformat(\\.com)?\\s+[a-z]:', category: 'windows', severity: 'critical', platform: 'windows' },
|
|
90
|
+
];
|
|
91
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
92
|
+
/**
|
|
93
|
+
* 根据用户配置计算当前生效的正则模式列表
|
|
94
|
+
*/
|
|
95
|
+
export function getActivePatterns(config) {
|
|
96
|
+
const disabled = new Set(config?.disabledPatternIds ?? []);
|
|
97
|
+
const patterns = BUILTIN_PATTERNS
|
|
98
|
+
.filter(p => !disabled.has(p.id))
|
|
99
|
+
.map(p => {
|
|
100
|
+
const flags = p.platform === 'windows' ? 'i' : undefined;
|
|
101
|
+
return new RegExp(p.pattern, flags);
|
|
102
|
+
});
|
|
103
|
+
if (config?.customPatterns) {
|
|
104
|
+
for (const cp of config.customPatterns) {
|
|
105
|
+
if (!cp.enabled)
|
|
106
|
+
continue;
|
|
107
|
+
try {
|
|
108
|
+
patterns.push(new RegExp(cp.pattern));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// skip invalid regex
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return patterns;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 按分类分组返回内置模式
|
|
119
|
+
*/
|
|
120
|
+
export function getPatternsByCategory() {
|
|
121
|
+
const grouped = {};
|
|
122
|
+
for (const p of BUILTIN_PATTERNS) {
|
|
123
|
+
if (!grouped[p.category])
|
|
124
|
+
grouped[p.category] = [];
|
|
125
|
+
grouped[p.category].push(p);
|
|
126
|
+
}
|
|
127
|
+
return grouped;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 默认空配置(所有内置模式均启用,无自定义模式)
|
|
131
|
+
*/
|
|
132
|
+
export const DEFAULT_COMMAND_SAFETY_CONFIG = {
|
|
133
|
+
disabledPatternIds: [],
|
|
134
|
+
hiddenPatternIds: [],
|
|
135
|
+
customPatterns: [],
|
|
136
|
+
};
|
|
137
|
+
export function classifyShellCommand(command, config) {
|
|
138
|
+
const normalized = command.trim();
|
|
139
|
+
if (!normalized) {
|
|
140
|
+
return { severity: 'safe', matchedRules: [] };
|
|
141
|
+
}
|
|
142
|
+
const disabled = new Set(config?.disabledPatternIds ?? []);
|
|
143
|
+
const matches = BUILTIN_PATTERNS
|
|
144
|
+
.filter(rule => !disabled.has(rule.id))
|
|
145
|
+
.filter(rule => {
|
|
146
|
+
const flags = rule.platform === 'windows' ? 'i' : undefined;
|
|
147
|
+
return new RegExp(rule.pattern, flags).test(normalized);
|
|
148
|
+
});
|
|
149
|
+
if (!matches.length) {
|
|
150
|
+
return { severity: 'safe', matchedRules: [] };
|
|
151
|
+
}
|
|
152
|
+
const severity = matches.some(item => item.severity === 'critical') ? 'critical' : 'warning';
|
|
153
|
+
return {
|
|
154
|
+
severity,
|
|
155
|
+
matchedRules: matches.map(item => item.id),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 1. `STEERABLE_SIDECAR_STORAGE_PATH` 显式覆盖(dev 脚本与测试的逃生门——
|
|
3
|
+
* with-flavor.mjs 会给开发实例注入 sessions-dev-<flavor>.db);
|
|
4
|
+
* 2. `DEEPPATH_USER_DATA_DIR`(BS 模式/只读 HOME 场景)优先于 `~/.steerable`:
|
|
5
|
+
* sidecar 库落到 `<userDataDir>/sidecar/sessions-<flavor>.db`,保证 HOME
|
|
6
|
+
* 不可写时 BS server 仍能启动 sidecar;
|
|
7
|
+
* 3. 按 flavor 隔离默认路径:`<flavor>` 非 generic 时 → sessions-<flavor>.db,
|
|
8
|
+
* generic → sessions.db(历史默认,保留存量数据)。
|
|
9
|
+
*
|
|
10
|
+
* 写锁是跨进程互斥的(Windows 命名 mutex / POSIX flock),两个宿主共用同
|
|
11
|
+
* 一库时后到者 boot 失败并无限重启。flavor 的 userData 目录本已隔离,
|
|
12
|
+
* sidecar 库同样隔离后两个 flavor 才能真正同机共存。
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveSidecarStoragePath(): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidecar 持久化库(sessions/traces/history)路径解析。
|
|
3
|
+
*
|
|
4
|
+
* 单独成模块而不是留在 boot.ts:boot.ts 会拉起 localStore 单例(进程内
|
|
5
|
+
* 独占写锁),只想读 history 的调用方(如后台任务过程投影)不该为一个
|
|
6
|
+
* 路径付这个代价。
|
|
7
|
+
*/
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { getBrand } from '../brand.js';
|
|
11
|
+
/**
|
|
12
|
+
* 1. `STEERABLE_SIDECAR_STORAGE_PATH` 显式覆盖(dev 脚本与测试的逃生门——
|
|
13
|
+
* with-flavor.mjs 会给开发实例注入 sessions-dev-<flavor>.db);
|
|
14
|
+
* 2. `DEEPPATH_USER_DATA_DIR`(BS 模式/只读 HOME 场景)优先于 `~/.steerable`:
|
|
15
|
+
* sidecar 库落到 `<userDataDir>/sidecar/sessions-<flavor>.db`,保证 HOME
|
|
16
|
+
* 不可写时 BS server 仍能启动 sidecar;
|
|
17
|
+
* 3. 按 flavor 隔离默认路径:`<flavor>` 非 generic 时 → sessions-<flavor>.db,
|
|
18
|
+
* generic → sessions.db(历史默认,保留存量数据)。
|
|
19
|
+
*
|
|
20
|
+
* 写锁是跨进程互斥的(Windows 命名 mutex / POSIX flock),两个宿主共用同
|
|
21
|
+
* 一库时后到者 boot 失败并无限重启。flavor 的 userData 目录本已隔离,
|
|
22
|
+
* sidecar 库同样隔离后两个 flavor 才能真正同机共存。
|
|
23
|
+
*/
|
|
24
|
+
export function resolveSidecarStoragePath() {
|
|
25
|
+
const override = process.env.STEERABLE_SIDECAR_STORAGE_PATH;
|
|
26
|
+
if (override)
|
|
27
|
+
return override;
|
|
28
|
+
// 按 flavor 派生(开放字符串,0.3g)——不含任何产品分支。
|
|
29
|
+
const flavor = getBrand().flavor;
|
|
30
|
+
const name = flavor === 'generic' ? 'sessions.db' : `sessions-${flavor}.db`;
|
|
31
|
+
const userDataDir = process.env.DEEPPATH_USER_DATA_DIR;
|
|
32
|
+
if (userDataDir)
|
|
33
|
+
return path.join(userDataDir, 'sidecar', name);
|
|
34
|
+
return path.join(homedir(), '.steerable', name);
|
|
35
|
+
}
|