@bolloon/bolloon-agent 0.1.34 → 0.1.35
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/.auto-evolve-calls +1 -0
- package/.last-auto-evolve-baseline +1 -0
- package/Bolloon.md +103 -0
- package/dist/agents/pi-sdk.js +264 -12
- package/dist/bootstrap/bootstrap.js +114 -0
- package/dist/bootstrap/context-collector.js +296 -0
- package/dist/bootstrap/lifecycle-hooks.js +109 -0
- package/dist/bootstrap/project-context.js +151 -0
- package/dist/index.js +11 -0
- package/dist/llm/pi-ai.js +31 -21
- package/dist/pi-ecosystem-judgment/adaptive-scan.js +231 -0
- package/dist/pi-ecosystem-judgment/causal-judge.js +449 -0
- package/dist/pi-ecosystem-judgment/detect-hook.js +168 -0
- package/dist/pi-ecosystem-judgment/distill-prompt.js +226 -0
- package/dist/pi-ecosystem-judgment/evolve-judgment.js +170 -0
- package/dist/pi-ecosystem-judgment/human-value-pipeline.js +21 -0
- package/dist/pi-ecosystem-judgment/human-value-store.js +283 -22
- package/dist/pi-ecosystem-judgment/injection-gate.js +166 -0
- package/dist/pi-ecosystem-judgment/monitor-gate.js +188 -0
- package/dist/security/builtin-guards.js +124 -0
- package/dist/security/context-router-tool.js +106 -0
- package/dist/security/react-harness.js +143 -0
- package/dist/security/tool-gate.js +235 -0
- package/dist/utils/auto-evolve-policy.js +117 -0
- package/dist/utils/clamp.js +7 -0
- package/dist/utils/double.js +6 -0
- package/dist/web/client.js +668 -204
- package/dist/web/index.html +24 -4
- package/dist/web/server.js +531 -10
- package/lefthook.yml +29 -0
- package/package.json +3 -2
- package/scripts/auto-evolve-loop.ts +376 -0
- package/scripts/auto-evolve-oneshot.sh +155 -0
- package/scripts/auto-evolve-snapshot.sh +136 -0
- package/scripts/detect-schema-changes.sh +48 -0
- package/scripts/diff-reviewer.ts +159 -0
- package/scripts/weekly-report.ts +364 -0
- package/src/agents/pi-sdk.ts +293 -15
- package/src/bootstrap/bootstrap.ts +132 -0
- package/src/bootstrap/context-collector.ts +342 -0
- package/src/bootstrap/lifecycle-hooks.ts +176 -0
- package/src/bootstrap/project-context.ts +163 -0
- package/src/index.ts +11 -0
- package/src/llm/pi-ai.ts +33 -22
- package/src/security/builtin-guards.ts +162 -0
- package/src/security/context-router-tool.ts +122 -0
- package/src/security/react-harness.ts +177 -0
- package/src/security/tool-gate.ts +294 -0
- package/src/utils/auto-evolve-policy.ts +138 -0
- package/src/utils/clamp.ts +5 -0
- package/src/web/client.js +668 -204
- package/src/web/index.html +24 -4
- package/src/web/server.ts +596 -10
- package/staging/auto-evolve/clean-001/.review-verdict +9 -0
- package/staging/auto-evolve/clean-001/clean-001.patch +14 -0
- package/staging/auto-evolve/e2e-001/.patch-id +1 -0
- package/staging/auto-evolve/e2e-001/.review-verdict +12 -0
- package/staging/auto-evolve/e2e-001/e2e-001.patch +11 -0
- package/staging/auto-evolve/test-bad/.review-verdict +12 -0
- package/staging/auto-evolve/test-bad/test-bad.patch +11 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Gate — 8 道安全 gate (调工具前/后的快速判断)
|
|
3
|
+
*
|
|
4
|
+
* 跟 harness-integration/gate-state-machine 区别:
|
|
5
|
+
* - 后者: 工程化工作流 8-gate (HARNESS-DEV 流程)
|
|
6
|
+
* - 本文件: 工具调用安全 8-gate (防越权 / 防注入 / 防越界)
|
|
7
|
+
*
|
|
8
|
+
* 每道 gate 独立可禁用, 调用方按顺序串联
|
|
9
|
+
* 任何 gate failed = 拒绝 tool 调用 + 返回 reason
|
|
10
|
+
*/
|
|
11
|
+
import { auditToolOutput } from './builtin-guards.js';
|
|
12
|
+
// ============================================================
|
|
13
|
+
// Gate 1: 白名单
|
|
14
|
+
// ============================================================
|
|
15
|
+
const TOOL_WHITELIST = new Set([
|
|
16
|
+
// 已有 tool (见 pi-sdk.ts registerTools)
|
|
17
|
+
'read_document', 'summarize_document', 'improve_document',
|
|
18
|
+
'list_peers', 'send_message', 'broadcast_message',
|
|
19
|
+
'get_identity', 'list_skills', 'create_judgment',
|
|
20
|
+
'shell_exec', 'shell',
|
|
21
|
+
'read', 'write', 'edit_file', 'list_files',
|
|
22
|
+
'list_sessions', 'get_session_state', 'list_messages',
|
|
23
|
+
'send_to_channel', 'create_channel',
|
|
24
|
+
// MCP 注册的工具
|
|
25
|
+
'mcp_tool',
|
|
26
|
+
]);
|
|
27
|
+
export const gateWhitelist = { gate: 'whitelist', allowed: true };
|
|
28
|
+
export function checkWhitelist(ctx) {
|
|
29
|
+
if (TOOL_WHITELIST.has(ctx.tool)) {
|
|
30
|
+
return gateWhitelist;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
gate: 'whitelist',
|
|
34
|
+
allowed: false,
|
|
35
|
+
reason: `工具 '${ctx.tool}' 不在白名单`,
|
|
36
|
+
evidence: `白名单: ${Array.from(TOOL_WHITELIST).slice(0, 5).join(', ')}...`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// ============================================================
|
|
40
|
+
// Gate 2: args 形状校验 (防 schema 注入)
|
|
41
|
+
// ============================================================
|
|
42
|
+
const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
43
|
+
export function checkSchema(ctx) {
|
|
44
|
+
// 1) 直接遍历 args (JSON.stringify 会丢 __proto__, 必须手动检查)
|
|
45
|
+
function walkKeys(obj, path = []) {
|
|
46
|
+
if (obj === null || typeof obj !== 'object')
|
|
47
|
+
return null;
|
|
48
|
+
for (const k of Object.keys(obj)) {
|
|
49
|
+
if (DANGEROUS_KEYS.includes(k)) {
|
|
50
|
+
return [...path, k].join('.');
|
|
51
|
+
}
|
|
52
|
+
const found = walkKeys(obj[k], [...path, k]);
|
|
53
|
+
if (found)
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const dangerousKey = walkKeys(ctx.args);
|
|
59
|
+
if (dangerousKey) {
|
|
60
|
+
return {
|
|
61
|
+
gate: 'schema',
|
|
62
|
+
allowed: false,
|
|
63
|
+
reason: `args 含 prototype pollution 风险 key: ${dangerousKey}`,
|
|
64
|
+
evidence: dangerousKey,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// 2) args 字符串过深 (>10000 字符) — 可能是 prompt injection 试图污染
|
|
68
|
+
const json = JSON.stringify(ctx.args);
|
|
69
|
+
if (json.length > 10000) {
|
|
70
|
+
return {
|
|
71
|
+
gate: 'schema',
|
|
72
|
+
allowed: false,
|
|
73
|
+
reason: `args 过长 (${json.length} 字符), 可能含 prompt injection`,
|
|
74
|
+
evidence: `前 100 字符: ${json.substring(0, 100)}`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return { gate: 'schema', allowed: true };
|
|
78
|
+
}
|
|
79
|
+
// ============================================================
|
|
80
|
+
// Gate 3: channel 权限 (channelId 是否被允许调该 tool)
|
|
81
|
+
// ============================================================
|
|
82
|
+
/** 工具 → 哪些 channelId 不允许 (黑名单模式, 留作扩展) */
|
|
83
|
+
const TOOL_CHANNEL_RESTRICT = {
|
|
84
|
+
// 'shell_exec': [/^ch_system_/], // 示例: 某些 system channel 禁 shell
|
|
85
|
+
};
|
|
86
|
+
export function checkChannel(ctx) {
|
|
87
|
+
if (!ctx.channelId) {
|
|
88
|
+
return { gate: 'channel', allowed: true };
|
|
89
|
+
}
|
|
90
|
+
const restrictions = TOOL_CHANNEL_RESTRICT[ctx.tool];
|
|
91
|
+
if (!restrictions) {
|
|
92
|
+
return { gate: 'channel', allowed: true };
|
|
93
|
+
}
|
|
94
|
+
for (const r of restrictions) {
|
|
95
|
+
if (typeof r === 'string' && r === ctx.channelId) {
|
|
96
|
+
return { gate: 'channel', allowed: false, reason: `channel '${ctx.channelId}' 不允许调 '${ctx.tool}'` };
|
|
97
|
+
}
|
|
98
|
+
if (r instanceof RegExp && r.test(ctx.channelId)) {
|
|
99
|
+
return { gate: 'channel', allowed: false, reason: `channel '${ctx.channelId}' 匹配禁模式 ${r}` };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { gate: 'channel', allowed: true };
|
|
103
|
+
}
|
|
104
|
+
// ============================================================
|
|
105
|
+
// Gate 4: 速率限制 (同 tool 1min 内最多 N 次)
|
|
106
|
+
// ============================================================
|
|
107
|
+
const RATE_LIMIT_PER_MIN = 5;
|
|
108
|
+
export function checkRate(ctx) {
|
|
109
|
+
const recent = ctx.recentCalls ?? [];
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const window = 60_000;
|
|
112
|
+
const sameTool = recent.filter((c) => c.tool === ctx.tool && now - c.ts < window);
|
|
113
|
+
if (sameTool.length >= RATE_LIMIT_PER_MIN) {
|
|
114
|
+
return {
|
|
115
|
+
gate: 'rate',
|
|
116
|
+
allowed: false,
|
|
117
|
+
reason: `工具 '${ctx.tool}' 在 1 分钟内已被调 ${sameTool.length} 次 (上限 ${RATE_LIMIT_PER_MIN})`,
|
|
118
|
+
evidence: `最近 ${sameTool.length} 次调用时间: ${sameTool.map((c) => new Date(c.ts).toISOString()).join(', ')}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return { gate: 'rate', allowed: true };
|
|
122
|
+
}
|
|
123
|
+
// ============================================================
|
|
124
|
+
// Gate 5: prompt injection 检测
|
|
125
|
+
// ============================================================
|
|
126
|
+
const INJECTION_PATTERNS = [
|
|
127
|
+
{ re: /\bignore (previous|all|above) (instructions?|prompts?)\b/i, label: 'ignore previous' },
|
|
128
|
+
{ re: /\b(disregard|forget) (everything|all|instructions?)\b/i, label: 'disregard' },
|
|
129
|
+
{ re: /\byou are now\b/i, label: 'role override' },
|
|
130
|
+
{ re: /\bnew instructions?:\s*\[/i, label: 'new instructions block' },
|
|
131
|
+
{ re: /<\|im_start\|>/, label: 'chatml tag' },
|
|
132
|
+
{ re: /<\|im_end\|>/, label: 'chatml tag' },
|
|
133
|
+
{ re: /\bSYSTEM:\s/i, label: 'system tag' },
|
|
134
|
+
];
|
|
135
|
+
export function checkInject(ctx) {
|
|
136
|
+
const concat = JSON.stringify(ctx.args);
|
|
137
|
+
for (const { re, label } of INJECTION_PATTERNS) {
|
|
138
|
+
if (re.test(concat)) {
|
|
139
|
+
return {
|
|
140
|
+
gate: 'inject',
|
|
141
|
+
allowed: false,
|
|
142
|
+
reason: `args 含 prompt injection 模式: ${label}`,
|
|
143
|
+
evidence: `匹配: ${label}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { gate: 'inject', allowed: true };
|
|
148
|
+
}
|
|
149
|
+
// ============================================================
|
|
150
|
+
// Gate 6: 输出审查 (tool 执行后, 审计 tool 返的字符串)
|
|
151
|
+
// 单独入口 auditOutput, 不是调 tool 前的 gate
|
|
152
|
+
// ============================================================
|
|
153
|
+
export function checkOutput(output) {
|
|
154
|
+
const hit = auditToolOutput(output);
|
|
155
|
+
if (!hit) {
|
|
156
|
+
return { gate: 'output', allowed: true };
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
gate: 'output',
|
|
160
|
+
allowed: hit.severity !== 'critical',
|
|
161
|
+
reason: hit.reason,
|
|
162
|
+
evidence: hit.evidence,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// ============================================================
|
|
166
|
+
// Gate 7: 链式调用限制 (单轮最多 5 个 tool, 防 agent 循环)
|
|
167
|
+
// ============================================================
|
|
168
|
+
const MAX_TOOL_CALLS_PER_TURN = 5;
|
|
169
|
+
export function checkChain(ctx) {
|
|
170
|
+
const count = ctx.toolCallCountInTurn ?? 0;
|
|
171
|
+
if (count >= MAX_TOOL_CALLS_PER_TURN) {
|
|
172
|
+
return {
|
|
173
|
+
gate: 'chain',
|
|
174
|
+
allowed: false,
|
|
175
|
+
reason: `单轮已调 ${count} 个 tool (上限 ${MAX_TOOL_CALLS_PER_TURN})`,
|
|
176
|
+
evidence: `当前轮 tool 调用次数: ${count}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return { gate: 'chain', allowed: true };
|
|
180
|
+
}
|
|
181
|
+
// ============================================================
|
|
182
|
+
// Gate 8: 黑名单 (复用 PreToolUse hook 已有 6 条规则, 重新实现于此)
|
|
183
|
+
// ============================================================
|
|
184
|
+
const DANGEROUS_CMD_PATTERNS = [
|
|
185
|
+
{ re: /\brm\s+(-[a-z]*f[a-z]*\s+)?-[a-z]*r[a-z]*\s+\//, reason: '禁止递归删除根目录' },
|
|
186
|
+
{ re: /\bgit\s+push\s+.*--force\b/, reason: '禁止 force push' },
|
|
187
|
+
{ re: /\brm\s+-rf\s+~\//, reason: '禁止递归删除 home' },
|
|
188
|
+
{ re: /\bdd\s+if=.*\s+of=\/dev\//, reason: '禁止 dd 覆盖块设备' },
|
|
189
|
+
{ re: /\bcurl\s+.*\|\s*(ba)?sh\b/, reason: '禁止 curl|sh 直执行' },
|
|
190
|
+
{ re: />\s*\/dev\/sd[a-z]/, reason: '禁止写裸设备' },
|
|
191
|
+
];
|
|
192
|
+
export function checkBlacklist(ctx) {
|
|
193
|
+
if (ctx.tool !== 'shell' && ctx.tool !== 'shell_exec' && ctx.tool !== 'bash') {
|
|
194
|
+
return { gate: 'blacklist', allowed: true };
|
|
195
|
+
}
|
|
196
|
+
const cmd = String(ctx.args.command || ctx.args.cmd || '');
|
|
197
|
+
for (const { re, reason } of DANGEROUS_CMD_PATTERNS) {
|
|
198
|
+
if (re.test(cmd)) {
|
|
199
|
+
return { gate: 'blacklist', allowed: false, reason, evidence: cmd.substring(0, 100) };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { gate: 'blacklist', allowed: true };
|
|
203
|
+
}
|
|
204
|
+
const TOOL_GATES = [
|
|
205
|
+
checkWhitelist,
|
|
206
|
+
checkSchema,
|
|
207
|
+
checkChannel,
|
|
208
|
+
checkRate,
|
|
209
|
+
checkInject,
|
|
210
|
+
checkChain, // 链式限制前置 (chain)
|
|
211
|
+
checkBlacklist,
|
|
212
|
+
// checkOutput 不在 tool.execute 前
|
|
213
|
+
];
|
|
214
|
+
export function runToolGates(ctx) {
|
|
215
|
+
const details = [];
|
|
216
|
+
for (const check of TOOL_GATES) {
|
|
217
|
+
try {
|
|
218
|
+
const r = check(ctx);
|
|
219
|
+
details.push(r);
|
|
220
|
+
if (!r.allowed) {
|
|
221
|
+
return { allowed: false, rejectedBy: r.gate, reason: r.reason, details };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
// 静默: 任意 gate 自身挂掉 = pass (fail-open)
|
|
226
|
+
console.warn(`[tool-gate] ${check.name} failed (non-fatal, allowing):`, err);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { allowed: true, details };
|
|
230
|
+
}
|
|
231
|
+
/** Post-call: 审查 tool 返的 output */
|
|
232
|
+
export function runOutputGate(output) {
|
|
233
|
+
const r = checkOutput(output);
|
|
234
|
+
return { allowed: r.allowed, rejectedBy: r.gate, reason: r.reason, details: [r] };
|
|
235
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Evolve Policy — 数据层自动进化统一网关
|
|
3
|
+
*
|
|
4
|
+
* 阶段 A: 数据层自动进化开关
|
|
5
|
+
*
|
|
6
|
+
* 单一事实源: 任何代码路径要"自动"修改 ~/.bolloon/ 下的数据层
|
|
7
|
+
* (judgments.json / persona.json / skills/ / agents.json),
|
|
8
|
+
* 都必须先调 isDataLayerAutoEvolveEnabled() / requireDataLayerAutoEvolve().
|
|
9
|
+
*
|
|
10
|
+
* 设计原则 (类 B 边界 + 8-gate 兼容):
|
|
11
|
+
* - 默认关闭 (fail-closed)
|
|
12
|
+
* - 3 种打开方式,任一为真即开:
|
|
13
|
+
* 1. env BOLLOON_AUTO_EVOLVE_DATA=1
|
|
14
|
+
* 2. ~/.bolloon/self-improve-policy.json dataLayerAutoEvolve: true
|
|
15
|
+
* 3. 类 B 自适应永远只读 (不查此开关) — 它天然不写库
|
|
16
|
+
* - 显式拒绝比默认拒绝多一道 log (审计 trail)
|
|
17
|
+
*
|
|
18
|
+
* 不变量:
|
|
19
|
+
* - 开关仅影响"无用户交互的自动写"路径
|
|
20
|
+
* - 用户在 UI 手动触发 / API 显式 accept 走原始路径, **不查此开关**
|
|
21
|
+
* (避免阻塞用户的正常接受操作)
|
|
22
|
+
* - 写入被拒绝时, 抛 AutoEvolveDisabledError (调用方选择 catch + fallback)
|
|
23
|
+
*/
|
|
24
|
+
import * as fs from 'fs/promises';
|
|
25
|
+
import * as os from 'os';
|
|
26
|
+
const ENV_KEY = 'BOLLOON_AUTO_EVOLVE_DATA';
|
|
27
|
+
const POLICY_PATH = () => (os.homedir() || process.env.HOME || '/tmp') + '/.bolloon/self-improve-policy.json';
|
|
28
|
+
export class AutoEvolveDisabledError extends Error {
|
|
29
|
+
constructor(reason) {
|
|
30
|
+
super(`[auto-evolve] 数据层自动进化已关闭: ${reason}`);
|
|
31
|
+
this.name = 'AutoEvolveDisabledError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let cachedPolicy = null;
|
|
35
|
+
const POLICY_TTL_MS = 5_000; // 5s 缓存, 避免每次写都读盘
|
|
36
|
+
/**
|
|
37
|
+
* 读 policy.json (带 5s 缓存)
|
|
38
|
+
*
|
|
39
|
+
* 注意: 必须保留完整 parsed 对象 — 调用方需要
|
|
40
|
+
* parsed.dataLayerAutoEvolve 等其他字段做 UI 展示.
|
|
41
|
+
*/
|
|
42
|
+
async function readPolicyFile() {
|
|
43
|
+
if (cachedPolicy && Date.now() - cachedPolicy.checkedAt < POLICY_TTL_MS && cachedPolicy.parsed) {
|
|
44
|
+
return cachedPolicy.parsed;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const content = await fs.readFile(POLICY_PATH(), 'utf-8');
|
|
48
|
+
const parsed = JSON.parse(content);
|
|
49
|
+
cachedPolicy = {
|
|
50
|
+
value: parsed.dataLayerAutoEvolve === true,
|
|
51
|
+
checkedAt: Date.now(),
|
|
52
|
+
parsed,
|
|
53
|
+
};
|
|
54
|
+
return parsed;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
cachedPolicy = { value: false, checkedAt: Date.now(), parsed: null };
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 核心查询: 数据层自动进化是否开启
|
|
63
|
+
*
|
|
64
|
+
* 任一为真:
|
|
65
|
+
* 1. env BOLLOON_AUTO_EVOLVE_DATA=1|true|yes
|
|
66
|
+
* 2. policy.json dataLayerAutoEvolve: true
|
|
67
|
+
*
|
|
68
|
+
* 同步读 env (env 永远先, 政策文件是补充).
|
|
69
|
+
*/
|
|
70
|
+
export function isDataLayerAutoEvolveEnabledSync() {
|
|
71
|
+
const env = (process.env[ENV_KEY] || '').toLowerCase();
|
|
72
|
+
if (env === '1' || env === 'true' || env === 'yes')
|
|
73
|
+
return true;
|
|
74
|
+
return false; // policy 文件需要 async, 同步版只看 env
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 异步版 (含 policy.json): 自动调用方用这个
|
|
78
|
+
*/
|
|
79
|
+
export async function isDataLayerAutoEvolveEnabled() {
|
|
80
|
+
if (isDataLayerAutoEvolveEnabledSync())
|
|
81
|
+
return true;
|
|
82
|
+
const p = await readPolicyFile();
|
|
83
|
+
return p?.dataLayerAutoEvolve === true;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 硬性护栏: 自动写数据层前必须调, 否则抛错
|
|
87
|
+
*
|
|
88
|
+
* @param caller 写操作的来源 (日志用, e.g. "adaptive-scan.deprecate")
|
|
89
|
+
*/
|
|
90
|
+
export async function requireDataLayerAutoEvolve(caller) {
|
|
91
|
+
const enabled = await isDataLayerAutoEvolveEnabled();
|
|
92
|
+
if (!enabled) {
|
|
93
|
+
throw new AutoEvolveDisabledError(`${caller} 尝试自动写数据层, 但 ${ENV_KEY} 未设且 policy.dataLayerAutoEvolve !== true`);
|
|
94
|
+
}
|
|
95
|
+
console.log(`[auto-evolve] ✅ 允许自动写 (caller=${caller})`);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 软性查询 + 日志: 给 UI 状态栏 / 调试用
|
|
99
|
+
*/
|
|
100
|
+
export async function getAutoEvolveStatus() {
|
|
101
|
+
const env = process.env[ENV_KEY] || '';
|
|
102
|
+
const envOn = isDataLayerAutoEvolveEnabledSync();
|
|
103
|
+
const p = await readPolicyFile();
|
|
104
|
+
const policyOn = p?.dataLayerAutoEvolve === true;
|
|
105
|
+
return {
|
|
106
|
+
enabled: envOn || policyOn,
|
|
107
|
+
env,
|
|
108
|
+
policyFilePath: POLICY_PATH(),
|
|
109
|
+
policyValue: policyOn,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 清除 policy 缓存 (测试用 / 实时热改 policy.json 后)
|
|
114
|
+
*/
|
|
115
|
+
export function clearAutoEvolvePolicyCache() {
|
|
116
|
+
cachedPolicy = null;
|
|
117
|
+
}
|