@bolloon/bolloon-agent 0.3.50 → 0.3.52
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.
|
@@ -79,17 +79,17 @@ export class DenyPipeline {
|
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
81
|
* 构建 permission-mode checker.
|
|
82
|
-
* default 模式: 禁用
|
|
82
|
+
* default 模式: 禁用 git 写操作 (commit/push/branch — "不搞乱")
|
|
83
83
|
* bypassPermissions: 放行所有
|
|
84
84
|
*
|
|
85
|
-
* 2026-08-10: write_file/edit_file/delete_file 移出 default
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
85
|
+
* 2026-08-10: shell_exec / write_file / edit_file / delete_file / terminal 移出 default
|
|
86
|
+
* 禁列表 — 它们已有各自护栏 (shell_exec 命令白名单 / terminal denylist-only /
|
|
87
|
+
* write_file checkWritePath), 用户明确"灵活一点, 少围栏, 核心的东西不碰不搞乱就行";
|
|
88
|
+
* 实测日志 "write_file 被权限拦了" → LLM 只能绕道, 任务无法推进.
|
|
89
89
|
*/
|
|
90
90
|
static permissionChecker() {
|
|
91
91
|
const DEFAULT_DENY_TOOLS = new Set([
|
|
92
|
-
'
|
|
92
|
+
'git_commit', 'git_push', 'git_branch',
|
|
93
93
|
]);
|
|
94
94
|
return (ctx) => {
|
|
95
95
|
if (ctx.permissionMode === 'bypassPermissions') {
|
|
@@ -649,6 +649,42 @@ export function registerBuiltinTools(ctx) {
|
|
|
649
649
|
return { success: true, output: result.output };
|
|
650
650
|
}
|
|
651
651
|
});
|
|
652
|
+
// 2026-08-10: terminal — 灵活终端写命令 (用户要求: bolloon 自己写命令进 terminal, 少围栏).
|
|
653
|
+
// 与 shell_exec 的区别: 接受**完整 shell 命令字符串** (管道/重定向/写文件都行),
|
|
654
|
+
// 护栏只挡高危破坏模式 (checkTerminalCommand: 提权/格式化/删根/.bolloon 数据), 其余放行.
|
|
655
|
+
ctx.tools.set('terminal', {
|
|
656
|
+
name: 'terminal',
|
|
657
|
+
description: '执行完整 shell 命令 (支持管道/重定向/写文件/跑脚本). 护栏只挡高危破坏操作 (sudo/格式化/rm -rf 根目录/写 ~/.bolloon 数据), 其余灵活放行. 适合: 写 HTML 文件、跑 python/node 脚本、查系统状态、装依赖.',
|
|
658
|
+
parameters: { command: '完整 shell 命令 (必填, 如: echo "<html>" > /tmp/site/index.html && ls /tmp/site)', timeoutMs: '超时毫秒, 默认 30000' },
|
|
659
|
+
execute: async (args) => {
|
|
660
|
+
const raw = String(args.command || '').trim();
|
|
661
|
+
if (!raw)
|
|
662
|
+
return { success: false, error: 'command 必填' };
|
|
663
|
+
const { checkTerminalCommand } = await import('./shell-guard.js');
|
|
664
|
+
const guard = checkTerminalCommand(raw);
|
|
665
|
+
if (!guard.allowed) {
|
|
666
|
+
return { success: false, error: `[terminal-guard] ${guard.reason}`, deniedByGuard: true };
|
|
667
|
+
}
|
|
668
|
+
const { exec } = await import('child_process');
|
|
669
|
+
const timeoutMs = Number(args.timeoutMs) || 30000;
|
|
670
|
+
return new Promise((resolve) => {
|
|
671
|
+
exec(raw, {
|
|
672
|
+
cwd: process.cwd(),
|
|
673
|
+
timeout: timeoutMs,
|
|
674
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
675
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
676
|
+
}, (err, stdout, stderr) => {
|
|
677
|
+
const output = ((stdout || '') + (stderr ? `\n[stderr]\n${stderr}` : '')).trim().slice(0, 8000);
|
|
678
|
+
if (err) {
|
|
679
|
+
resolve({ success: false, error: `exit ${err.code ?? '?'}: ${String(err.message || '').slice(0, 200)}`, output: output || undefined });
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
resolve({ success: true, output: output || '(无输出)' });
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
});
|
|
652
688
|
// self_improve
|
|
653
689
|
ctx.tools.set('self_improve', {
|
|
654
690
|
name: 'self_improve',
|
|
@@ -335,6 +335,44 @@ export const SELF_IMPROVE_COOLDOWN_MS = 6 * 60 * 60 * 1000;
|
|
|
335
335
|
/** @deprecated 用 getSandboxCwd() */
|
|
336
336
|
export const SHELL_SANDBOX_CWD = path.resolve(process.cwd(), '.bolloon-shell-sandbox');
|
|
337
337
|
// ============================================================================
|
|
338
|
+
// terminal 工具宽松护栏 (2026-08-10) — denylist-only, 不碰核心即可
|
|
339
|
+
// 与 checkCommand 的区别: 不查命令白名单/参数 allowlist — 完整 shell 命令字符串放行,
|
|
340
|
+
// 只挡"高危破坏/碰核心数据"模式. 用户明确: 灵活一点, 少围栏, 核心的东西不碰不搞乱.
|
|
341
|
+
// ============================================================================
|
|
342
|
+
const TERMINAL_DENY_PATTERNS = [
|
|
343
|
+
/\bsudo\b/, // 提权
|
|
344
|
+
/\bsu\b/,
|
|
345
|
+
/\bmkfs\b/, // 格式化
|
|
346
|
+
/\bshred\b/, // 擦除
|
|
347
|
+
/\bdd\s+.*of=\/dev\//, // 写设备
|
|
348
|
+
/\brm\s+-(rf|fr)\b/, // 递归删除 (rm 单个文件允许)
|
|
349
|
+
/\bchmod\s+-R\s+777\b/, // 全权限
|
|
350
|
+
/\bcurl\b[^|]*\|\s*(sh|bash)\b/, // curl|sh 远程执行
|
|
351
|
+
/\bwget\b[^|]*\|\s*(sh|bash)\b/,
|
|
352
|
+
/\b:\(\)\s*\{[^}]*\}\s*;\s*:/, // fork bomb
|
|
353
|
+
/\b(>|>>)\s*(\/etc\/|\/usr\/|\/System\/|\/bin\/|\/sbin\/)/, // 写系统目录
|
|
354
|
+
/[\/\s]\.bolloon\b/, // Bolloon 数据 (sessions/persona/keys; 覆盖 ~/.bolloon, $HOME/.bolloon, 空格.bolloon)
|
|
355
|
+
/[\/\s]\.(diap|hermes|openclaw)\b/, // 其他 agent 数据
|
|
356
|
+
/\brm\s+-rf\s+(\/|~|\*|\.|\$HOME)\b/, // 删根/家/通配
|
|
357
|
+
/\bgit\s+push\b[^|]*\s+(-f|--force)/, // 强推
|
|
358
|
+
/\bgit\s+reset\s+--hard\b/,
|
|
359
|
+
/\bkill\s+-9\s+\d+\b/, // 杀进程 (保留给用户)
|
|
360
|
+
];
|
|
361
|
+
/** 检查完整 shell 命令 (terminal 工具用). denylist-only: 命中高危模式即拒, 否则放行. */
|
|
362
|
+
export function checkTerminalCommand(rawCmd) {
|
|
363
|
+
const cmd = (rawCmd || '').trim();
|
|
364
|
+
if (!cmd)
|
|
365
|
+
return { allowed: false, reason: '命令为空', matchedBy: 'fallback-deny' };
|
|
366
|
+
for (const pattern of TERMINAL_DENY_PATTERNS) {
|
|
367
|
+
if (pattern.test(cmd)) {
|
|
368
|
+
auditShellCall('denied', cmd, [], `terminal 高危模式 ${pattern}`);
|
|
369
|
+
return { allowed: false, reason: `命令命中高危护栏 ${pattern} (核心不碰: 提权/格式化/删根/.bolloon 数据). 换一条安全命令.`, matchedBy: 'arg-denylist' };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
auditShellCall('allowed', cmd, []);
|
|
373
|
+
return { allowed: true };
|
|
374
|
+
}
|
|
375
|
+
// ============================================================================
|
|
338
376
|
// 写策略 / 审计路径 (供 API 端点用)
|
|
339
377
|
// ============================================================================
|
|
340
378
|
export const POLICY_AUDIT_PATH_PUBLIC = POLICY_AUDIT_PATH;
|
package/dist/web/client.js
CHANGED
|
@@ -1544,6 +1544,57 @@
|
|
|
1544
1544
|
var newChannelBtn = document.getElementById("new-channel-btn");
|
|
1545
1545
|
var newChannelInput = document.getElementById("new-channel-input");
|
|
1546
1546
|
var channelNameEl = document.getElementById("channel-name");
|
|
1547
|
+
var rokidBridge = null;
|
|
1548
|
+
var rokidBridgeStatus = "unavailable";
|
|
1549
|
+
function getRokidBridge() {
|
|
1550
|
+
if (typeof window === "undefined") return null;
|
|
1551
|
+
return window.Capacitor?.Plugins?.RokidBridge || null;
|
|
1552
|
+
}
|
|
1553
|
+
function publishRokidStatus(status, detail = {}) {
|
|
1554
|
+
rokidBridgeStatus = status;
|
|
1555
|
+
if (typeof window !== "undefined") {
|
|
1556
|
+
window.__bolloonRokidStatus = { status, ...detail };
|
|
1557
|
+
window.dispatchEvent(new CustomEvent("bolloon:rokid-status", { detail: window.__bolloonRokidStatus }));
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
async function initRokidBridge() {
|
|
1561
|
+
const bridge = getRokidBridge();
|
|
1562
|
+
if (!bridge) {
|
|
1563
|
+
publishRokidStatus("unavailable");
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
rokidBridge = bridge;
|
|
1567
|
+
try {
|
|
1568
|
+
await bridge.addListener?.("rokidEvent", (event) => {
|
|
1569
|
+
if (event?.type === "connected") publishRokidStatus("connected", { device: event.device });
|
|
1570
|
+
else if (event?.type === "disconnected") publishRokidStatus("disconnected", { reason: event.reason });
|
|
1571
|
+
else if (event?.type === "error") publishRokidStatus("error", { error: event.error || event.message });
|
|
1572
|
+
window.dispatchEvent(new CustomEvent("bolloon:rokid-event", { detail: event }));
|
|
1573
|
+
});
|
|
1574
|
+
const result = await bridge.connect?.();
|
|
1575
|
+
publishRokidStatus("connected", { deviceId: result?.deviceId, mode: result?.mode });
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
publishRokidStatus("error", { error: error?.message || String(error) });
|
|
1578
|
+
console.warn("[Rokid] bridge unavailable:", error);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
function sendRokidText(text, metadata = {}) {
|
|
1582
|
+
if (!rokidBridge || !text) return;
|
|
1583
|
+
Promise.resolve(rokidBridge.sendMessage?.({
|
|
1584
|
+
text,
|
|
1585
|
+
channelId: currentChannelId || void 0,
|
|
1586
|
+
metadata
|
|
1587
|
+
})).catch((error) => {
|
|
1588
|
+
publishRokidStatus("error", { error: error?.message || String(error) });
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
if (typeof window !== "undefined") {
|
|
1592
|
+
window.BolloonRokid = {
|
|
1593
|
+
connect: initRokidBridge,
|
|
1594
|
+
sendMessage: sendRokidText,
|
|
1595
|
+
getStatus: () => ({ status: rokidBridgeStatus })
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1547
1598
|
var eventSources = /* @__PURE__ */ new Map();
|
|
1548
1599
|
var currentChannelId = null;
|
|
1549
1600
|
var activeChannelId = null;
|
|
@@ -2823,6 +2874,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
2823
2874
|
MR_replaceStreamingText?.(data.content || "");
|
|
2824
2875
|
MR_finalizeTimelineAsMessage(getRendererCtx());
|
|
2825
2876
|
}
|
|
2877
|
+
sendRokidText(data.content || "", { source: "ai" });
|
|
2826
2878
|
} else if (data.type === "reply-preview") {
|
|
2827
2879
|
const previewContent = data.content || "";
|
|
2828
2880
|
const oldPreviews = container.querySelectorAll(".message-ai.preview");
|
|
@@ -2896,6 +2948,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
2896
2948
|
setSendMode("abort");
|
|
2897
2949
|
const container = messagesContainers.get(currentChannelId) || messagesEl;
|
|
2898
2950
|
addMessage2(text, "user", true, container);
|
|
2951
|
+
sendRokidText(text, { source: "user" });
|
|
2899
2952
|
if (container) container.scrollTop = container.scrollHeight;
|
|
2900
2953
|
if (currentPreviewBubble) {
|
|
2901
2954
|
currentPreviewBubble.remove();
|
|
@@ -3983,6 +4036,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
3983
4036
|
}
|
|
3984
4037
|
}
|
|
3985
4038
|
async function init() {
|
|
4039
|
+
void initRokidBridge();
|
|
3986
4040
|
const themeData = await loadTheme();
|
|
3987
4041
|
currentAgentId = themeData.agentId || `agent_${generateId().substring(0, 8)}`;
|
|
3988
4042
|
if (!themeData.agentId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bolloon/bolloon-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
|
|
6
6
|
"main": "dist/cli-entry.js",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"build:main": "tsc && node scripts/copy-constraint-runtime.mjs",
|
|
30
30
|
"build:cli": "node scripts/build-cli.js",
|
|
31
31
|
"build:web": "npx tsx scripts/build-web.ts",
|
|
32
|
+
"build:rokid-sdk": "npm --prefix ../rokid run build",
|
|
33
|
+
"test:rokid-sdk": "npm --prefix ../rokid test",
|
|
32
34
|
"build:electron": "tsc -p tsconfig.electron.json",
|
|
33
35
|
"build:all": "npm run build:main && npm run build:web && npm run build:electron && npm run build:cli",
|
|
34
36
|
"start": "node dist/index.js",
|
|
@@ -55,6 +57,7 @@
|
|
|
55
57
|
"dependencies": {
|
|
56
58
|
"@bolloon/bolloon-agent": "^0.3.22",
|
|
57
59
|
"@bolloon/constraint-runtime": "0.1.0",
|
|
60
|
+
"@capacitor/android": "^8.4.1",
|
|
58
61
|
"@capacitor/core": "^8.4.1",
|
|
59
62
|
"@capacitor/ios": "^8.4.2",
|
|
60
63
|
"@chainsafe/libp2p-noise": "^17.0.0",
|