@bolloon/bolloon-agent 0.1.37 → 0.1.39
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/dist/agents/constraint-layer.js +1 -2
- package/package.json +1 -1
- package/scripts/auto-evolve-oneshot.sh +155 -0
- package/scripts/auto-evolve-snapshot.sh +136 -0
- package/scripts/build-cli.js +216 -0
- package/scripts/detect-schema-changes.sh +48 -0
- package/scripts/postinstall.js +153 -0
- package/dist/bollharness-integration/llm/pi-ai.js +0 -397
- package/dist/bollharness-integration/pi-ecosystem-colony/index.js +0 -365
- package/dist/bollharness-integration/pi-ecosystem-goals/index.js +0 -458
- package/dist/bollharness-integration/pi-ecosystem-judgment/decision.js +0 -300
- package/dist/bollharness-integration/pi-ecosystem-judgment/distillation.js +0 -291
- package/dist/bollharness-integration/pi-ecosystem-judgment/index.js +0 -445
- package/dist/bollharness-integration/pi-ecosystem-mcp/index.js +0 -331
- package/dist/bollharness-integration/pi-ecosystem-subagents/index.js +0 -303
- package/dist/constraints/commands.js +0 -100
- package/dist/constraints/permissions.js +0 -37
- package/dist/constraints/runtime.js +0 -135
- package/dist/constraints/session.js +0 -48
- package/dist/constraints/system-init.js +0 -51
- package/dist/constraints/tools.js +0 -104
- package/dist/llm/minimax-provider.js +0 -46
- package/dist/llm/minimax.js +0 -45
- package/dist/pi-ecosystem-colony/index.js +0 -365
- package/dist/runtime/context/minimax-prompt.js +0 -178
- package/dist/runtime/context/sys-prompt.js +0 -1
- package/dist/social/ant-colony/AdaptiveHeartbeat.js +0 -101
- package/dist/social/ant-colony/PheromoneEngine.js +0 -227
- package/dist/social/ant-colony/index.js +0 -6
- package/dist/social/ant-colony/types.js +0 -24
- package/dist/utils/double.js +0 -6
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* ConstraintLayer - Guardrails for safe autonomous document processing
|
|
3
3
|
* Part of OpenClaw dual-layer architecture (Constraint Layer + Execution Layer)
|
|
4
4
|
*/
|
|
5
|
-
import { ToolPermissionContext } from '
|
|
6
|
-
import { BudgetTracker } from '../constraint-runtime/src/constraint/budget.js';
|
|
5
|
+
import { ToolPermissionContext, BudgetTracker } from '@bolloon/constraint-runtime';
|
|
7
6
|
/**
|
|
8
7
|
* System prompt for constraint layer
|
|
9
8
|
* Defines boundaries and autonomous permissions
|
package/package.json
CHANGED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# auto-evolve-oneshot.sh — 阶段 D 单次修复 (shell 版)
|
|
3
|
+
#
|
|
4
|
+
# 流程 (全本地, 用 MINIMAX_API_KEY 调 LLM):
|
|
5
|
+
# 1. 跑 vitest, 抓 fail
|
|
6
|
+
# 2. 让 LLM 修
|
|
7
|
+
# 3. 解析 LLM 输出的 diff, 写到 staging
|
|
8
|
+
# 4. 跑 reviewer (护栏 4)
|
|
9
|
+
# 5. PASS → git apply + commit (护栏 1 拦)
|
|
10
|
+
|
|
11
|
+
set -uo pipefail # 不加 -e: vitest 失败要继续
|
|
12
|
+
|
|
13
|
+
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
|
14
|
+
cd "$REPO"
|
|
15
|
+
|
|
16
|
+
echo "[oneshot] REPO=$REPO"
|
|
17
|
+
|
|
18
|
+
# 1. 跑 vitest
|
|
19
|
+
npx vitest run --reporter=json --no-color 2>/dev/null > /tmp/vt-out.json
|
|
20
|
+
TOTAL_FAIL=$(python3 -c "
|
|
21
|
+
import json
|
|
22
|
+
d = json.load(open('/tmp/vt-out.json'))
|
|
23
|
+
print(sum(1 for f in d.get('testResults',[]) for a in f.get('assertionResults',[]) if a.get('status')=='failed'))
|
|
24
|
+
")
|
|
25
|
+
echo "[oneshot] vitest failed=$TOTAL_FAIL"
|
|
26
|
+
|
|
27
|
+
if [ "$TOTAL_FAIL" = "0" ]; then
|
|
28
|
+
echo "[oneshot] ✅ 全部通过, 不需要修"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# 2. 抽 fail 信息
|
|
33
|
+
FAIL_SUMMARY=$(python3 -c "
|
|
34
|
+
import json
|
|
35
|
+
d = json.load(open('/tmp/vt-out.json'))
|
|
36
|
+
for f in d.get('testResults', []):
|
|
37
|
+
for a in f.get('assertionResults', []):
|
|
38
|
+
if a.get('status') == 'failed':
|
|
39
|
+
print('FILE:', f.get('name'))
|
|
40
|
+
print('TEST:', a.get('fullName') or a.get('title'))
|
|
41
|
+
print('ERROR:')
|
|
42
|
+
for m in a.get('failureMessages', [])[:2]:
|
|
43
|
+
print(m[:600])
|
|
44
|
+
print('---')
|
|
45
|
+
" | head -60)
|
|
46
|
+
echo "[oneshot] 2. 调 LLM 修..."
|
|
47
|
+
|
|
48
|
+
# 3. 用 LLM 生成修复
|
|
49
|
+
# prompt: 要 LLM 输出 ```diff ... ``` 块
|
|
50
|
+
# 3. 写 prompt 到文件 (避免 shell 反引号冲突)
|
|
51
|
+
PROMPT_FILE="/tmp/oneshot-prompt.txt"
|
|
52
|
+
cat > "$PROMPT_FILE" <<PROMPT_EOF
|
|
53
|
+
你是一个谨慎的代码修复助手. 你的工作是修复失败的测试.
|
|
54
|
+
|
|
55
|
+
约束: 改动最小, 不改测试, 不引入 any/unknown/@ts-ignore.
|
|
56
|
+
|
|
57
|
+
输出格式: 严格只输出一个 \`\`\`diff ... \`\`\` 块, 第一个字符 \`\`\`diff, 最后一个字符 \`\`\`. 中间是 unified diff (--- a/path +++ b/path 风格). 不要在 diff 块外输出任何文字.
|
|
58
|
+
|
|
59
|
+
FAIL 信息:
|
|
60
|
+
$(cat /tmp/vt-out.json | python3 -c "
|
|
61
|
+
import json
|
|
62
|
+
d = json.load(open('/tmp/vt-out.json'))
|
|
63
|
+
for f in d.get('testResults', []):
|
|
64
|
+
for a in f.get('assertionResults', []):
|
|
65
|
+
if a.get('status') == 'failed':
|
|
66
|
+
print('FILE:', f.get('name'))
|
|
67
|
+
print('TEST:', a.get('fullName') or a.get('title'))
|
|
68
|
+
print('ERROR:')
|
|
69
|
+
for m in a.get('failureMessages', [])[:2]:
|
|
70
|
+
print(m[:600])
|
|
71
|
+
print('---')
|
|
72
|
+
" | head -60)
|
|
73
|
+
|
|
74
|
+
请**只**输出 diff 块:
|
|
75
|
+
PROMPT_EOF
|
|
76
|
+
|
|
77
|
+
LLM_OUTPUT=$(npx tsx -r dotenv/config -e "
|
|
78
|
+
import { initMinimax } from './src/llm/pi-ai.js';
|
|
79
|
+
import * as fs from 'fs';
|
|
80
|
+
const prompt = fs.readFileSync('$PROMPT_FILE', 'utf-8');
|
|
81
|
+
const client = initMinimax();
|
|
82
|
+
const text = await client.generateText({ messages: [{ role: 'user', content: prompt }], maxTokens: 4096, temperature: 0.2 });
|
|
83
|
+
process.stdout.write(text || '');
|
|
84
|
+
" 2>/tmp/llm-err.log)
|
|
85
|
+
|
|
86
|
+
if [ -z "$LLM_OUTPUT" ]; then
|
|
87
|
+
echo "[oneshot] ❌ LLM 没返回"
|
|
88
|
+
exit 2
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# 4. 解析 diff
|
|
92
|
+
DIFF=$(echo "$LLM_OUTPUT" | python3 -c "
|
|
93
|
+
import sys, re
|
|
94
|
+
text = sys.stdin.read()
|
|
95
|
+
m = re.search(r'\`\`\`diff\s*([\s\S]*?)\`\`\`', text)
|
|
96
|
+
if m:
|
|
97
|
+
diff = m.group(1).strip()
|
|
98
|
+
if not diff.endswith('\n'):
|
|
99
|
+
diff += '\n'
|
|
100
|
+
print(diff)
|
|
101
|
+
else:
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
" 2>/dev/null) || {
|
|
104
|
+
echo "[oneshot] ❌ LLM 输出没 diff 块"
|
|
105
|
+
echo "--- LLM 原始输出 (前 800) ---"
|
|
106
|
+
echo "$LLM_OUTPUT" | head -c 800
|
|
107
|
+
echo ""
|
|
108
|
+
exit 3
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
echo "[oneshot] 拿到 diff: $(echo "$DIFF" | wc -l) lines"
|
|
112
|
+
|
|
113
|
+
# 5. 写 staging
|
|
114
|
+
ID="oneshot-$(date +%s)"
|
|
115
|
+
mkdir -p "staging/auto-evolve/$ID"
|
|
116
|
+
echo "$DIFF" > "staging/auto-evolve/$ID/$ID.patch"
|
|
117
|
+
echo "$ID" > "staging/auto-evolve/$ID/.patch-id"
|
|
118
|
+
echo "[oneshot] patch 写到 staging/auto-evolve/$ID/"
|
|
119
|
+
|
|
120
|
+
# 6. 跑 reviewer
|
|
121
|
+
echo "[oneshot] 3. 跑 reviewer..."
|
|
122
|
+
npx tsx -r dotenv/config scripts/diff-reviewer.ts "$ID" > /tmp/reviewer.log 2>&1 || true
|
|
123
|
+
VERDICT=$(python3 -c "
|
|
124
|
+
import json
|
|
125
|
+
try:
|
|
126
|
+
print(json.load(open('staging/auto-evolve/$ID/.review-verdict')).get('verdict','UNKNOWN'))
|
|
127
|
+
except:
|
|
128
|
+
print('UNKNOWN')
|
|
129
|
+
")
|
|
130
|
+
echo "[oneshot] reviewer verdict: $VERDICT"
|
|
131
|
+
|
|
132
|
+
# 7. apply + commit
|
|
133
|
+
if [ "$VERDICT" = "PASS" ]; then
|
|
134
|
+
echo "[oneshot] 4. apply + commit"
|
|
135
|
+
if git apply --recount --whitespace=fix "staging/auto-evolve/$ID/$ID.patch" 2>/tmp/apply.err; then
|
|
136
|
+
git add -A
|
|
137
|
+
git commit -m "auto-evolve: $ID (LLM 修复)"
|
|
138
|
+
echo "[oneshot] ✅ 提交成功"
|
|
139
|
+
# 验证
|
|
140
|
+
npx vitest run --reporter=json --no-color 2>/dev/null > /tmp/vt-after.json
|
|
141
|
+
AFTER_FAIL=$(python3 -c "
|
|
142
|
+
import json
|
|
143
|
+
print(sum(1 for f in json.load(open('/tmp/vt-after.json')).get('testResults',[]) for a in f.get('assertionResults',[]) if a.get('status')=='failed'))
|
|
144
|
+
")
|
|
145
|
+
echo "[oneshot] 修复后 fail: $AFTER_FAIL (之前 $TOTAL_FAIL)"
|
|
146
|
+
else
|
|
147
|
+
echo "[oneshot] ❌ git apply 失败:"
|
|
148
|
+
cat /tmp/apply.err
|
|
149
|
+
exit 4
|
|
150
|
+
fi
|
|
151
|
+
else
|
|
152
|
+
echo "[oneshot] ❌ reviewer verdict=$VERDICT, 不 apply"
|
|
153
|
+
cat /tmp/reviewer.log | head -10
|
|
154
|
+
exit 5
|
|
155
|
+
fi
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# auto-evolve-snapshot.sh — 阶段 C 护栏 2 + 3
|
|
3
|
+
#
|
|
4
|
+
# 用法 (LLM 改源码前必调):
|
|
5
|
+
# bash scripts/auto-evolve-snapshot.sh # 打 baseline tag + 记当前 HEAD
|
|
6
|
+
# bash scripts/auto-evolve-snapshot.sh apply <patch-id> # 人类批准后合并 staging → main
|
|
7
|
+
# bash scripts/auto-evolve-snapshot.sh list # 列所有 baseline
|
|
8
|
+
# bash scripts/auto-evolve-snapshot.sh rollback <tag> # 回滚到指定 baseline
|
|
9
|
+
#
|
|
10
|
+
# 流程:
|
|
11
|
+
# 1. LLM 改之前调 snapshot: 当前 HEAD 打 auto-evolve-baseline-<ts> tag
|
|
12
|
+
# 2. LLM 改 staging/auto-evolve/<patch-id>/ (不进 src/)
|
|
13
|
+
# 3. 护栏 1 (lefthook) 跑 vitest + tsc, 坏就 abort
|
|
14
|
+
# 4. 护栏 4 (reviewer hook) 审 diff, 通过才准 apply
|
|
15
|
+
# 5. 人类调 apply: git apply staging/auto-evolve/<patch-id>/*.patch → src/
|
|
16
|
+
# 6. 出问题: 调 rollback → git reset --hard <tag>
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
21
|
+
cd "$REPO_ROOT"
|
|
22
|
+
|
|
23
|
+
STAGING_DIR="staging/auto-evolve"
|
|
24
|
+
TAG_PREFIX="auto-evolve-baseline-"
|
|
25
|
+
|
|
26
|
+
cmd="${1:-snapshot}"
|
|
27
|
+
patch_id="${2:-}"
|
|
28
|
+
|
|
29
|
+
case "$cmd" in
|
|
30
|
+
snapshot)
|
|
31
|
+
# 护栏 2: 打 baseline tag
|
|
32
|
+
if ! git diff --quiet HEAD 2>/dev/null; then
|
|
33
|
+
echo "❌ 有未提交改动. 先 git commit 或 git stash"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
37
|
+
tag="${TAG_PREFIX}${ts}"
|
|
38
|
+
git tag -a "$tag" -m "auto-evolve baseline @ ${ts}" HEAD
|
|
39
|
+
echo "✅ baseline tag: $tag"
|
|
40
|
+
echo "$tag" > .last-auto-evolve-baseline
|
|
41
|
+
echo " 回滚命令: bash $0 rollback $tag"
|
|
42
|
+
;;
|
|
43
|
+
|
|
44
|
+
prepare)
|
|
45
|
+
# LLM 改之前调: 创建 staging 目录
|
|
46
|
+
if [ -z "$patch_id" ]; then
|
|
47
|
+
echo "用法: $0 prepare <patch-id>"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
mkdir -p "$STAGING_DIR/$patch_id"
|
|
51
|
+
echo "$patch_id" > "$STAGING_DIR/$patch_id/.patch-id"
|
|
52
|
+
echo "✅ staging 创建: $STAGING_DIR/$patch_id/"
|
|
53
|
+
echo " LLM 改完后把 patch 放这里: $STAGING_DIR/$patch_id/*.patch"
|
|
54
|
+
;;
|
|
55
|
+
|
|
56
|
+
apply)
|
|
57
|
+
# 人类批准: 把 staging 的 patch 合并到 src/
|
|
58
|
+
if [ -z "$patch_id" ]; then
|
|
59
|
+
echo "用法: $0 apply <patch-id>"
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
patch_dir="$STAGING_DIR/$patch_id"
|
|
63
|
+
if [ ! -d "$patch_dir" ]; then
|
|
64
|
+
echo "❌ staging 不存在: $patch_dir"
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
# 必须先 snapshot
|
|
68
|
+
if [ ! -f .last-auto-evolve-baseline ]; then
|
|
69
|
+
echo "❌ 没有 baseline. 先跑: $0 snapshot"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
baseline="$(cat .last-auto-evolve-baseline)"
|
|
73
|
+
|
|
74
|
+
# 应用所有 patch
|
|
75
|
+
applied=0
|
|
76
|
+
for p in "$patch_dir"/*.patch; do
|
|
77
|
+
[ -e "$p" ] || continue
|
|
78
|
+
echo " applying: $p"
|
|
79
|
+
if ! git apply --check "$p"; then
|
|
80
|
+
echo "❌ patch 不可用: $p (可能已应用过)"
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
git apply "$p"
|
|
84
|
+
applied=$((applied + 1))
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
if [ $applied -eq 0 ]; then
|
|
88
|
+
echo "❌ staging 里没有 .patch 文件"
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo "✅ 应用 $applied 个 patch"
|
|
93
|
+
echo " 建议现在跑: npm test + git commit -m 'auto-evolve: $patch_id'"
|
|
94
|
+
echo " 出问题回滚: $0 rollback $baseline"
|
|
95
|
+
;;
|
|
96
|
+
|
|
97
|
+
rollback)
|
|
98
|
+
# 回滚到指定 baseline
|
|
99
|
+
if [ -z "$patch_id" ]; then
|
|
100
|
+
echo "用法: $0 rollback <tag>"
|
|
101
|
+
echo "可用的 baseline:"
|
|
102
|
+
git tag -l "${TAG_PREFIX}*" | sort -r | head -10
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
if ! git tag -l | grep -qx "$patch_id"; then
|
|
106
|
+
echo "❌ tag 不存在: $patch_id"
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
echo "⚠️ 将回滚到 $patch_id (hard reset, 丢弃之后所有改动)"
|
|
110
|
+
read -p "确认? [y/N] " -n 1 -r
|
|
111
|
+
echo
|
|
112
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
113
|
+
git reset --hard "$patch_id"
|
|
114
|
+
echo "✅ 回滚到 $patch_id"
|
|
115
|
+
else
|
|
116
|
+
echo "取消"
|
|
117
|
+
fi
|
|
118
|
+
;;
|
|
119
|
+
|
|
120
|
+
list)
|
|
121
|
+
echo "auto-evolve baselines (最近 10):"
|
|
122
|
+
git tag -l "${TAG_PREFIX}*" | sort -r | head -10 | while read tag; do
|
|
123
|
+
msg="$(git tag -l --format='%(contents)' "$tag" | head -1)"
|
|
124
|
+
echo " $tag — $msg"
|
|
125
|
+
done
|
|
126
|
+
if [ -f .last-auto-evolve-baseline ]; then
|
|
127
|
+
echo ""
|
|
128
|
+
echo "当前 baseline: $(cat .last-auto-evolve-baseline)"
|
|
129
|
+
fi
|
|
130
|
+
;;
|
|
131
|
+
|
|
132
|
+
*)
|
|
133
|
+
echo "用法: $0 {snapshot|prepare <id>|apply <id>|rollback <tag>|list}"
|
|
134
|
+
exit 1
|
|
135
|
+
;;
|
|
136
|
+
esac
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 构建脚本
|
|
3
|
+
*
|
|
4
|
+
* 生成 bin/bolloon.js 和 bin/bolloon.cmd 入口文件
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
const rootDir = path.join(__dirname, '..');
|
|
14
|
+
|
|
15
|
+
const binDir = path.join(rootDir, 'bin');
|
|
16
|
+
if (!fs.existsSync(binDir)) {
|
|
17
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Windows 批处理入口
|
|
21
|
+
const winContent = `@echo off
|
|
22
|
+
set "BOLLOON_ROOT=%~dp0"
|
|
23
|
+
set "BOLLOON_ROOT=%BOLLOON_ROOT:~0,-1%"
|
|
24
|
+
|
|
25
|
+
REM 确定入口文件
|
|
26
|
+
set "ENTRY=%BOLLOON_ROOT%\\dist\\index.js"
|
|
27
|
+
if not exist "%ENTRY%" (
|
|
28
|
+
set "ENTRY=%BOLLOON_ROOT%\\src\\index.ts"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
node "%ENTRY%" %*
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
fs.writeFileSync(path.join(binDir, 'bolloon.cmd'), winContent);
|
|
35
|
+
|
|
36
|
+
// Unix/Linux/Mac 入口脚本
|
|
37
|
+
const unixContent = `#!/usr/bin/env node
|
|
38
|
+
const path = require("path");
|
|
39
|
+
const { spawn } = require("child_process");
|
|
40
|
+
const fs = require("fs");
|
|
41
|
+
|
|
42
|
+
const RESET = "\\x1b[0m";
|
|
43
|
+
const BOLD = "\\x1b[1m";
|
|
44
|
+
const CYAN = "\\x1b[36m";
|
|
45
|
+
const GREEN = "\\x1b[32m";
|
|
46
|
+
const MAGENTA = "\\x1b[35m";
|
|
47
|
+
|
|
48
|
+
function log(msg, color) {
|
|
49
|
+
console.log((color || RESET) + msg + RESET);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function printBanner() {
|
|
53
|
+
console.log("\\n" + CYAN + BOLD + [
|
|
54
|
+
" ╔═══════════════════════════════════════════╗",
|
|
55
|
+
" ║ 🤖 Bolloon Agent ║",
|
|
56
|
+
" ║ P2P AI Document Processor ║",
|
|
57
|
+
" ╚═══════════════════════════════════════════╝"
|
|
58
|
+
].join("\\n") + RESET + "\\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getMainEntry() {
|
|
62
|
+
const distDir = path.dirname(require.main.filename);
|
|
63
|
+
const distIndex = path.join(distDir, "index.js");
|
|
64
|
+
if (fs.existsSync(distIndex)) {
|
|
65
|
+
return distIndex;
|
|
66
|
+
}
|
|
67
|
+
return path.join(process.cwd(), "src", "index.ts");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseArgs() {
|
|
71
|
+
const args = process.argv.slice(2);
|
|
72
|
+
if (args.length === 0) {
|
|
73
|
+
return { mode: "gui", args: [] };
|
|
74
|
+
}
|
|
75
|
+
const first = args[0];
|
|
76
|
+
switch (first) {
|
|
77
|
+
case "-v":
|
|
78
|
+
case "--version":
|
|
79
|
+
return { mode: "version", args: [] };
|
|
80
|
+
case "-h":
|
|
81
|
+
case "--help":
|
|
82
|
+
return { mode: "help", args: [] };
|
|
83
|
+
case "-g":
|
|
84
|
+
case "--gui":
|
|
85
|
+
return { mode: "gui", args: args.slice(1) };
|
|
86
|
+
case "-w":
|
|
87
|
+
case "--web":
|
|
88
|
+
return { mode: "web", args: args.slice(1) };
|
|
89
|
+
case "-c":
|
|
90
|
+
case "--cli":
|
|
91
|
+
return { mode: "cli", args: args.slice(1) };
|
|
92
|
+
default:
|
|
93
|
+
return { mode: "passthrough", args };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function startElectron(additionalArgs) {
|
|
98
|
+
try {
|
|
99
|
+
const electron = require("electron");
|
|
100
|
+
const distDir = path.dirname(require.main.filename);
|
|
101
|
+
let mainPath = path.join(distDir, "electron.js");
|
|
102
|
+
if (!fs.existsSync(mainPath)) {
|
|
103
|
+
mainPath = path.join(process.cwd(), "src", "electron.ts");
|
|
104
|
+
}
|
|
105
|
+
log("启动 Electron...", CYAN);
|
|
106
|
+
const child = spawn(electron, [mainPath, ...additionalArgs], {
|
|
107
|
+
stdio: "inherit",
|
|
108
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
109
|
+
});
|
|
110
|
+
child.on("error", (err) => {
|
|
111
|
+
log("Electron 启动失败: " + err.message, MAGENTA);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
|
114
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
115
|
+
} catch (err) {
|
|
116
|
+
log("Electron 不可用,切换到 Web 模式...", CYAN);
|
|
117
|
+
await startWebServer(additionalArgs);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function startWebServer(additionalArgs) {
|
|
122
|
+
const mainPath = getMainEntry();
|
|
123
|
+
const webArgs = ["--web", ...additionalArgs];
|
|
124
|
+
log("启动 Web 服务...", CYAN);
|
|
125
|
+
const child = spawn(process.execPath, [mainPath, ...webArgs], { stdio: "inherit" });
|
|
126
|
+
child.on("error", (err) => {
|
|
127
|
+
log("Web 服务启动失败: " + err.message, MAGENTA);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
});
|
|
130
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function startCLI(additionalArgs) {
|
|
134
|
+
const mainPath = getMainEntry();
|
|
135
|
+
log("启动命令行界面...", CYAN);
|
|
136
|
+
const child = spawn(process.execPath, [mainPath, ...additionalArgs], { stdio: "inherit" });
|
|
137
|
+
child.on("error", (err) => {
|
|
138
|
+
log("CLI 启动失败: " + err.message, MAGENTA);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
});
|
|
141
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function main() {
|
|
145
|
+
const { mode, args } = parseArgs();
|
|
146
|
+
switch (mode) {
|
|
147
|
+
case "version":
|
|
148
|
+
console.log("Bolloon Agent v0.1.1");
|
|
149
|
+
break;
|
|
150
|
+
case "help":
|
|
151
|
+
printBanner();
|
|
152
|
+
console.log(BOLD + "用法:" + RESET + " bolloon [选项] [命令] [参数]");
|
|
153
|
+
console.log(BOLD + "选项:" + RESET + " --gui, -g 启动图形界面");
|
|
154
|
+
console.log(" --web, -w 启动 Web UI");
|
|
155
|
+
console.log(" --cli, -c 启动命令行界面");
|
|
156
|
+
console.log(" --version, -v 显示版本");
|
|
157
|
+
console.log(" --help, -h 显示帮助");
|
|
158
|
+
console.log(BOLD + "示例:" + RESET + " bolloon # 启动图形界面");
|
|
159
|
+
console.log(" bolloon --web # 启动 Web UI");
|
|
160
|
+
console.log(" bolloon --read file # 读取文档");
|
|
161
|
+
break;
|
|
162
|
+
case "gui":
|
|
163
|
+
printBanner();
|
|
164
|
+
await startElectron(args);
|
|
165
|
+
break;
|
|
166
|
+
case "web":
|
|
167
|
+
printBanner();
|
|
168
|
+
await startWebServer(args);
|
|
169
|
+
break;
|
|
170
|
+
case "cli":
|
|
171
|
+
await startCLI(args);
|
|
172
|
+
break;
|
|
173
|
+
case "passthrough":
|
|
174
|
+
const mainPath = getMainEntry();
|
|
175
|
+
const child = spawn(process.execPath, [mainPath, ...args], { stdio: "inherit" });
|
|
176
|
+
child.on("error", (err) => {
|
|
177
|
+
log("执行失败: " + err.message, MAGENTA);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
});
|
|
180
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
log("未知模式: " + mode, MAGENTA);
|
|
184
|
+
printBanner();
|
|
185
|
+
console.log("输入 --help 查看帮助");
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
main().catch((err) => {
|
|
191
|
+
console.error("Fatal error:", err);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
});
|
|
194
|
+
`;
|
|
195
|
+
|
|
196
|
+
fs.writeFileSync(path.join(binDir, 'bolloon.cjs'), unixContent);
|
|
197
|
+
|
|
198
|
+
// 确保 bin/bolloon.js 存在(npm link 需要)
|
|
199
|
+
// 优先用符号链接(POSIX),Windows 上若权限不足则退化为复制文件
|
|
200
|
+
const jsSymlink = path.join(binDir, 'bolloon.js');
|
|
201
|
+
if (fs.existsSync(jsSymlink)) fs.unlinkSync(jsSymlink);
|
|
202
|
+
try {
|
|
203
|
+
fs.symlinkSync('bolloon.cjs', jsSymlink);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err && err.code === 'EPERM') {
|
|
206
|
+
fs.copyFileSync(path.join(binDir, 'bolloon.cjs'), jsSymlink);
|
|
207
|
+
console.warn(' ⚠ symlink 不支持(Windows),已退化为文件复制');
|
|
208
|
+
} else {
|
|
209
|
+
throw err;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log("✓ CLI 构建完成");
|
|
214
|
+
console.log(" bin/bolloon.cjs - CommonJS 入口");
|
|
215
|
+
console.log(" bin/bolloon.js - 符号链接 -> bolloon.cjs");
|
|
216
|
+
console.log(" bin/bolloon.cmd - Windows 入口");
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# detect-schema-changes.sh — 阶段 C 护栏 6
|
|
3
|
+
#
|
|
4
|
+
# 检查 LLM 改的文件里有没有动 schema (interface / type / enum 声明)
|
|
5
|
+
# 有则强制要求 reviewer 双签 (在 .auto-evolve-review-required 文件)
|
|
6
|
+
#
|
|
7
|
+
# 用法 (在 staging 准备好 patch 后, apply 前):
|
|
8
|
+
# bash scripts/detect-schema-changes.sh <patch-id>
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
13
|
+
cd "$REPO_ROOT"
|
|
14
|
+
|
|
15
|
+
patch_id="${1:-}"
|
|
16
|
+
if [ -z "$patch_id" ]; then
|
|
17
|
+
echo "用法: $0 <patch-id>"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
staging_dir="staging/auto-evolve/$patch_id"
|
|
22
|
+
if [ ! -d "$staging_dir" ]; then
|
|
23
|
+
echo "❌ staging 不存在: $staging_dir"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
flag="$staging_dir/.schema-changed"
|
|
28
|
+
rm -f "$flag"
|
|
29
|
+
|
|
30
|
+
found=0
|
|
31
|
+
for patch in "$staging_dir"/*.patch; do
|
|
32
|
+
[ -e "$patch" ] || continue
|
|
33
|
+
# 检查 patch 里有没有新增/修改 interface、type、enum 声明
|
|
34
|
+
if grep -E '^\+.*(interface |type [A-Z][a-zA-Z0-9_]* =|enum [A-Z][a-zA-Z0-9_]*)' "$patch" > /dev/null 2>&1; then
|
|
35
|
+
found=1
|
|
36
|
+
echo "⚠️ schema 改动检测到: $patch"
|
|
37
|
+
grep -E '^\+.*(interface |type [A-Z][a-zA-Z0-9_]* =|enum [A-Z][a-zA-Z0-9_]*)' "$patch" | head -3
|
|
38
|
+
fi
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
if [ $found -eq 1 ]; then
|
|
42
|
+
touch "$flag"
|
|
43
|
+
echo ""
|
|
44
|
+
echo "🚨 schema 改动标记: $flag"
|
|
45
|
+
echo " 护栏 6 触发: apply 时会强制要求双签 reviewer"
|
|
46
|
+
else
|
|
47
|
+
echo "✅ 无 schema 改动, 走单签"
|
|
48
|
+
fi
|