botmux 2.106.0 → 2.106.1-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +10 -10
- package/README.md +10 -10
- package/dist/adapters/backend/sandbox.d.ts +20 -6
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +150 -26
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +1 -1
- package/dist/adapters/backend/zellij-observe-backend.d.ts +1 -1
- package/dist/adapters/backend/zellij-observe-backend.js +1 -1
- package/dist/adapters/cli/read-isolation.d.ts +1 -2
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js +4 -51
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/bot-registry.d.ts +1 -65
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +0 -145
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -30
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +0 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -13
- package/dist/config.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +1 -2
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +2 -47
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/idle-worker-sweeper.d.ts +1 -1
- package/dist/core/idle-worker-sweeper.js +1 -1
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +1 -2
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/core/session-manager.d.ts +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +6 -51
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +2 -7
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/worker-pool.d.ts +0 -4
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +9 -27
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/core/zellij-adopt-discovery.js +1 -1
- package/dist/core/zellij-adopt-discovery.js.map +1 -1
- package/dist/daemon.d.ts +0 -144
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +65 -3386
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/federated-group-core.js +1 -1
- package/dist/dashboard/federated-group-core.js.map +1 -1
- package/dist/dashboard/federation-api.js +1 -1
- package/dist/dashboard/settings-write-applier.d.ts +1 -31
- package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
- package/dist/dashboard/settings-write-applier.js +0 -47
- package/dist/dashboard/settings-write-applier.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +0 -22
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/insights.js +19 -19
- package/dist/dashboard/web/insights.js.map +1 -1
- package/dist/dashboard/web/sessions.js +2 -2
- package/dist/dashboard/web/settings-page.d.ts.map +1 -1
- package/dist/dashboard/web/settings-page.js +46 -101
- package/dist/dashboard/web/settings-page.js.map +1 -1
- package/dist/dashboard-web/app.js +3 -3
- package/dist/dashboard-web/chunks/{bot-defaults-page-3OIY7N76.js → bot-defaults-page-4IF5NB4O.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-DKKOGZCG.js → chunk-2KX53XHS.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-BNEXMNQD.js → chunk-NHI3SY2K.js} +3 -3
- package/dist/dashboard-web/chunks/{chunk-VFCACTRF.js → chunk-RRBSPQVK.js} +1 -1
- package/dist/dashboard-web/chunks/{connectors-VDOAKUW3.js → connectors-WZR7MAXE.js} +1 -1
- package/dist/dashboard-web/chunks/{groups-page-CX5AN4XG.js → groups-page-NK6U5JIU.js} +1 -1
- package/dist/dashboard-web/chunks/{insights-page-DOMRL4S6.js → insights-page-2TN3ZR33.js} +1 -1
- package/dist/dashboard-web/chunks/{overview-page-AZBAXVZ7.js → overview-page-TADULZBE.js} +1 -1
- package/dist/dashboard-web/chunks/{roles-page-3FEAH6S4.js → roles-page-J6CRSIJ6.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-CTQEA7IE.js → schedules-6RONVOOV.js} +1 -1
- package/dist/dashboard-web/chunks/{sessions-LVZCXO2Z.js → sessions-I4EZWL5Q.js} +1 -1
- package/dist/dashboard-web/chunks/{sessions-page-NHBJDSBC.js → sessions-page-YTQC2EM6.js} +1 -1
- package/dist/dashboard-web/chunks/settings-page-TRYWNJ5Y.js +2 -0
- package/dist/dashboard-web/chunks/{skills-page-VUG3EPWX.js → skills-page-ZZLGLEEL.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-ZKL3YMGN.js → team-federation-page-QYAP7XJF.js} +1 -1
- package/dist/dashboard-web/chunks/{workflows-4BYQQBFP.js → workflows-2L4DKNCK.js} +1 -1
- package/dist/dashboard-web/index.html +1 -1
- package/dist/dashboard-web/style.css +16 -122
- package/dist/dashboard.js +3 -367
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +0 -23
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +1 -58
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +6 -9
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +6 -9
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +1 -1
- package/dist/im/lark/card-builder.js +1 -1
- package/dist/im/lark/card-handler.d.ts +0 -3
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +0 -8
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +0 -10
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +3 -82
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/grant-command.d.ts +1 -1
- package/dist/im/lark/grant-command.js +1 -1
- package/dist/im/lark/message-parser.d.ts +0 -6
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +0 -14
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/mir-runner.js +149 -1
- package/dist/mir-runner.js.map +1 -1
- package/dist/services/deployment-identity.d.ts +1 -1
- package/dist/services/deployment-identity.d.ts.map +1 -1
- package/dist/services/deployment-identity.js +1 -1
- package/dist/services/deployment-identity.js.map +1 -1
- package/dist/services/doc-subs-store.js +1 -1
- package/dist/services/team-board-store.js +1 -1
- package/dist/services/team-board-store.js.map +1 -1
- package/dist/services/trigger-types.d.ts +1 -1
- package/dist/services/trigger-types.d.ts.map +1 -1
- package/dist/services/voice/index.d.ts +1 -6
- package/dist/services/voice/index.d.ts.map +1 -1
- package/dist/services/voice/index.js +5 -12
- package/dist/services/voice/index.js.map +1 -1
- package/dist/setup/lark-scopes.json +0 -5
- package/dist/setup/open-platform-automation.d.ts +0 -20
- package/dist/setup/open-platform-automation.d.ts.map +1 -1
- package/dist/setup/open-platform-automation.js +1 -78
- package/dist/setup/open-platform-automation.js.map +1 -1
- package/dist/setup/verify-permissions.d.ts +0 -6
- package/dist/setup/verify-permissions.d.ts.map +1 -1
- package/dist/setup/verify-permissions.js +0 -17
- package/dist/setup/verify-permissions.js.map +1 -1
- package/dist/types.d.ts +0 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/daemon-discovery.d.ts +0 -2
- package/dist/utils/daemon-discovery.d.ts.map +1 -1
- package/dist/utils/daemon-discovery.js +0 -2
- package/dist/utils/daemon-discovery.js.map +1 -1
- package/dist/worker.js +11 -51
- package/dist/worker.js.map +1 -1
- package/dist/workflows/definition.d.ts +14 -14
- package/dist/workflows/events/payloads.d.ts +8 -8
- package/dist/workflows/events/schema.d.ts +452 -452
- package/dist/workflows/v3/contract.d.ts +3 -3
- package/dist/workflows/v3/contract.js +3 -3
- package/dist/workflows/v3/runtime.js +1 -1
- package/dist/workflows/v3/runtime.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/vc-agent.d.ts +0 -2
- package/dist/cli/vc-agent.d.ts.map +0 -1
- package/dist/cli/vc-agent.js +0 -331
- package/dist/cli/vc-agent.js.map +0 -1
- package/dist/dashboard-web/chunks/settings-page-R23I3IZG.js +0 -2
- package/dist/mir-prompt.d.ts +0 -16
- package/dist/mir-prompt.d.ts.map +0 -1
- package/dist/mir-prompt.js +0 -170
- package/dist/mir-prompt.js.map +0 -1
- package/dist/services/vc-meeting-runtime-store.d.ts +0 -59
- package/dist/services/vc-meeting-runtime-store.d.ts.map +0 -1
- package/dist/services/vc-meeting-runtime-store.js +0 -339
- package/dist/services/vc-meeting-runtime-store.js.map +0 -1
- package/dist/vc-agent/cards.d.ts +0 -49
- package/dist/vc-agent/cards.d.ts.map +0 -1
- package/dist/vc-agent/cards.js +0 -505
- package/dist/vc-agent/cards.js.map +0 -1
- package/dist/vc-agent/meeting-state.d.ts +0 -39
- package/dist/vc-agent/meeting-state.d.ts.map +0 -1
- package/dist/vc-agent/meeting-state.js +0 -186
- package/dist/vc-agent/meeting-state.js.map +0 -1
- package/dist/vc-agent/normalizer.d.ts +0 -13
- package/dist/vc-agent/normalizer.d.ts.map +0 -1
- package/dist/vc-agent/normalizer.js +0 -470
- package/dist/vc-agent/normalizer.js.map +0 -1
- package/dist/vc-agent/polling-source.d.ts +0 -64
- package/dist/vc-agent/polling-source.d.ts.map +0 -1
- package/dist/vc-agent/polling-source.js +0 -181
- package/dist/vc-agent/polling-source.js.map +0 -1
- package/dist/vc-agent/push-source.d.ts +0 -13
- package/dist/vc-agent/push-source.d.ts.map +0 -1
- package/dist/vc-agent/push-source.js +0 -109
- package/dist/vc-agent/push-source.js.map +0 -1
- package/dist/vc-agent/realtime/audio-source.d.ts +0 -6
- package/dist/vc-agent/realtime/audio-source.d.ts.map +0 -1
- package/dist/vc-agent/realtime/audio-source.js +0 -17
- package/dist/vc-agent/realtime/audio-source.js.map +0 -1
- package/dist/vc-agent/realtime/endpoint.d.ts +0 -6
- package/dist/vc-agent/realtime/endpoint.d.ts.map +0 -1
- package/dist/vc-agent/realtime/endpoint.js +0 -25
- package/dist/vc-agent/realtime/endpoint.js.map +0 -1
- package/dist/vc-agent/realtime/errors.d.ts +0 -9
- package/dist/vc-agent/realtime/errors.d.ts.map +0 -1
- package/dist/vc-agent/realtime/errors.js +0 -17
- package/dist/vc-agent/realtime/errors.js.map +0 -1
- package/dist/vc-agent/realtime/events.d.ts +0 -24
- package/dist/vc-agent/realtime/events.d.ts.map +0 -1
- package/dist/vc-agent/realtime/events.js +0 -190
- package/dist/vc-agent/realtime/events.js.map +0 -1
- package/dist/vc-agent/realtime/frontier.d.ts +0 -34
- package/dist/vc-agent/realtime/frontier.d.ts.map +0 -1
- package/dist/vc-agent/realtime/frontier.js +0 -40
- package/dist/vc-agent/realtime/frontier.js.map +0 -1
- package/dist/vc-agent/realtime/index.d.ts +0 -11
- package/dist/vc-agent/realtime/index.d.ts.map +0 -1
- package/dist/vc-agent/realtime/index.js +0 -11
- package/dist/vc-agent/realtime/index.js.map +0 -1
- package/dist/vc-agent/realtime/pacer.d.ts +0 -21
- package/dist/vc-agent/realtime/pacer.d.ts.map +0 -1
- package/dist/vc-agent/realtime/pacer.js +0 -80
- package/dist/vc-agent/realtime/pacer.js.map +0 -1
- package/dist/vc-agent/realtime/protobuf.d.ts +0 -33
- package/dist/vc-agent/realtime/protobuf.d.ts.map +0 -1
- package/dist/vc-agent/realtime/protobuf.js +0 -159
- package/dist/vc-agent/realtime/protobuf.js.map +0 -1
- package/dist/vc-agent/realtime/protocol.d.ts +0 -4
- package/dist/vc-agent/realtime/protocol.d.ts.map +0 -1
- package/dist/vc-agent/realtime/protocol.js +0 -59
- package/dist/vc-agent/realtime/protocol.js.map +0 -1
- package/dist/vc-agent/realtime/session.d.ts +0 -39
- package/dist/vc-agent/realtime/session.d.ts.map +0 -1
- package/dist/vc-agent/realtime/session.js +0 -221
- package/dist/vc-agent/realtime/session.js.map +0 -1
- package/dist/vc-agent/realtime/transport.d.ts +0 -23
- package/dist/vc-agent/realtime/transport.d.ts.map +0 -1
- package/dist/vc-agent/realtime/transport.js +0 -112
- package/dist/vc-agent/realtime/transport.js.map +0 -1
- package/dist/vc-agent/realtime/types.d.ts +0 -84
- package/dist/vc-agent/realtime/types.d.ts.map +0 -1
- package/dist/vc-agent/realtime/types.js +0 -5
- package/dist/vc-agent/realtime/types.js.map +0 -1
- package/dist/vc-agent/types.d.ts +0 -143
- package/dist/vc-agent/types.d.ts.map +0 -1
- package/dist/vc-agent/types.js +0 -2
- package/dist/vc-agent/types.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
<a href="#design-philosophy">Design</a> ·
|
|
16
16
|
<a href="#key-advantages">Advantages</a> ·
|
|
17
17
|
<a href="#5-minute-setup">Quick Start</a> ·
|
|
18
|
-
<a href="https://
|
|
18
|
+
<a href="https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/"><b>📖 Docs</b></a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
21
|
[中文](README.md) | English
|
|
22
22
|
|
|
23
23
|
**Plug any AI coding CLI into Feishu/Lark — every DM, group or topic gets its own CLI session, with live-streaming cards, a web terminal, and zero glue code.**
|
|
24
24
|
|
|
25
|
-
> 📖 **Full docs** (commands / config / best practices / troubleshooting): **<https://
|
|
25
|
+
> 📖 **Full docs** (commands / config / best practices / troubleshooting): **<https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/>** — this README only covers why and how to get started fast.
|
|
26
26
|
|
|
27
27
|
| Lark Streaming Cards | Web Terminal | tmux Session Management | Multi-Bot Collaboration |
|
|
28
28
|
|:-:|:-:|:-:|:-:|
|
|
@@ -223,7 +223,7 @@ On mobile/tablet, a floating shortcut toolbar provides Esc, Ctrl+C, Tab, arrow k
|
|
|
223
223
|
|
|
224
224
|
### Multi-Bot Collaboration
|
|
225
225
|
|
|
226
|
-
Run multiple Lark bots on a single machine, each mapped to a different CLI. In the same group chat, messages are routed via @mention — each bot gets its own isolated CLI process. With a single bot in the group, it responds automatically without @. In a regular (non-topic) group, `@<bot1> @<bot2> /t xxx` spawns one independent thread per mentioned bot anchored at the same message. Send `@<bot1> @<bot2> /introduce` once so they register each other's open_id; afterwards each bot can explicitly @-mention the others from within its own session (commands: [📖 Docs · Slash Commands](https://
|
|
226
|
+
Run multiple Lark bots on a single machine, each mapped to a different CLI. In the same group chat, messages are routed via @mention — each bot gets its own isolated CLI process. With a single bot in the group, it responds automatically without @. In a regular (non-topic) group, `@<bot1> @<bot2> /t xxx` spawns one independent thread per mentioned bot anchored at the same message. Send `@<bot1> @<bot2> /introduce` once so they register each other's open_id; afterwards each bot can explicitly @-mention the others from within its own session (commands: [📖 Docs · Slash Commands](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/slash-commands)).
|
|
227
227
|
|
|
228
228
|
### Multi-Topic Collaboration
|
|
229
229
|
|
|
@@ -387,16 +387,16 @@ Notes:
|
|
|
387
387
|
|
|
388
388
|
The full reference — commands, config, best practices, troubleshooting — lives in the docs site; not duplicated here —
|
|
389
389
|
|
|
390
|
-
### 👉 https://
|
|
390
|
+
### 👉 https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/
|
|
391
391
|
|
|
392
392
|
| Topic | Docs |
|
|
393
393
|
|-------|------|
|
|
394
|
-
| Slash commands / CLI commands / agent-facing subcommands | [Commands](https://
|
|
395
|
-
| `bots.json` fields / env vars / file locations | [Configuration](https://
|
|
396
|
-
| Multi-CLI adapters (incl. wrapper / gateway integration) | [Adapters](https://
|
|
397
|
-
| Scenario-based best practices (Oncall / alerting-ops / solo dev / team) | [Best Practices](https://
|
|
398
|
-
| Common pitfalls / FAQ | [Pitfalls](https://
|
|
399
|
-
| Features: scheduled tasks / Oncall / Dashboard / multi-bot / session relay | [Schedule](https://
|
|
394
|
+
| Slash commands / CLI commands / agent-facing subcommands | [Commands](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/slash-commands) |
|
|
395
|
+
| `bots.json` fields / env vars / file locations | [Configuration](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/bots-json) |
|
|
396
|
+
| Multi-CLI adapters (incl. wrapper / gateway integration) | [Adapters](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/adapters) |
|
|
397
|
+
| Scenario-based best practices (Oncall / alerting-ops / solo dev / team) | [Best Practices](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/best-practices) |
|
|
398
|
+
| Common pitfalls / FAQ | [Pitfalls](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/pitfalls) · [FAQ](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/faq) |
|
|
399
|
+
| Features: scheduled tasks / Oncall / Dashboard / multi-bot / session relay | [Schedule](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/schedule) · [Oncall](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/oncall) · [Dashboard](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/dashboard) · [Multi-bot](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/multi-bot) · [Relay](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/relay) |
|
|
400
400
|
|
|
401
401
|
## Contributing
|
|
402
402
|
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
15
|
-
<a href="#设计理念">设计理念</a> · <a href="#核心优势">核心优势</a> · <a href="#5-分钟快速接入">快速接入</a> · <a href="https://
|
|
15
|
+
<a href="#设计理念">设计理念</a> · <a href="#核心优势">核心优势</a> · <a href="#5-分钟快速接入">快速接入</a> · <a href="https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/"><b>📖 文档</b></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
**飞书 + AI 编程 CLI——私聊 / 群聊 / 话题,每个会话一个独立 CLI 进程,实时流式回传。** Daemon 监听飞书消息,为每个新会话自动启动独立 CLI 进程(Claude Code / Codex / Cursor / Gemini / OpenCode / Antigravity / GitHub Copilot / Kimi Code),提供实时流式卡片和可交互 Web 终端。
|
|
25
25
|
|
|
26
|
-
> 📖 **完整文档**(命令 / 配置 / 最佳实践 / 排错):**<https://
|
|
26
|
+
> 📖 **完整文档**(命令 / 配置 / 最佳实践 / 排错):**<https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/>** ——本 README 只讲为什么用它和怎么快速上手。
|
|
27
27
|
|
|
28
28
|
## 演示
|
|
29
29
|
|
|
@@ -92,7 +92,7 @@ botmux 可以管理 CLI 无关的自定义 Skill Registry,并按 bot 配置在
|
|
|
92
92
|
|
|
93
93
|
### 多机器人协作
|
|
94
94
|
|
|
95
|
-
同一台机器上可运行多个飞书机器人,每个机器人可对应不同的 CLI。同一群聊中通过 @mention 路由消息,仅有一个机器人时无需 @ 自动响应;多机器人时 `@<bot1> @<bot2> /t xxx` 可让每个被 @ 的机器人在同一条消息上各自独立开新话题。先发一次 `@<bot1> @<bot2> /introduce` 让它们互相登记 open_id,之后各 bot 就能在自己的会话里显式 @mention 对方协作(命令详见 [📖 文档 · 斜杠命令](https://
|
|
95
|
+
同一台机器上可运行多个飞书机器人,每个机器人可对应不同的 CLI。同一群聊中通过 @mention 路由消息,仅有一个机器人时无需 @ 自动响应;多机器人时 `@<bot1> @<bot2> /t xxx` 可让每个被 @ 的机器人在同一条消息上各自独立开新话题。先发一次 `@<bot1> @<bot2> /introduce` 让它们互相登记 open_id,之后各 bot 就能在自己的会话里显式 @mention 对方协作(命令详见 [📖 文档 · 斜杠命令](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/slash-commands))。
|
|
96
96
|
|
|
97
97
|
### 多话题协作模式
|
|
98
98
|
|
|
@@ -384,16 +384,16 @@ PersonalAgent 默认配好事件订阅 + bot 能力,正常情况下不用动
|
|
|
384
384
|
|
|
385
385
|
命令、配置、最佳实践、排错的完整内容都在文档站,这里不再重复 ——
|
|
386
386
|
|
|
387
|
-
### 👉 https://
|
|
387
|
+
### 👉 https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/
|
|
388
388
|
|
|
389
389
|
| 主题 | 文档 |
|
|
390
390
|
|------|------|
|
|
391
|
-
| 斜杠命令 / CLI 命令 / 会话内子命令 | [命令参考](https://
|
|
392
|
-
| `bots.json` 字段 / 环境变量 / 文件位置 | [配置参考](https://
|
|
393
|
-
| 多 CLI 适配器(含 wrapper / 网关接入) | [适配器](https://
|
|
394
|
-
| 按场景的最佳实践(Oncall / 报警运维 / 个人研发 / 多人协作) | [最佳实践](https://
|
|
395
|
-
| 常见踩坑 / FAQ 排错 | [踩坑](https://
|
|
396
|
-
| 功能详解:定时任务 / Oncall / Dashboard / 多机器人协作 / 会话接力 | [定时](https://
|
|
391
|
+
| 斜杠命令 / CLI 命令 / 会话内子命令 | [命令参考](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/slash-commands) |
|
|
392
|
+
| `bots.json` 字段 / 环境变量 / 文件位置 | [配置参考](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/bots-json) |
|
|
393
|
+
| 多 CLI 适配器(含 wrapper / 网关接入) | [适配器](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/adapters) |
|
|
394
|
+
| 按场景的最佳实践(Oncall / 报警运维 / 个人研发 / 多人协作) | [最佳实践](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/best-practices) |
|
|
395
|
+
| 常见踩坑 / FAQ 排错 | [踩坑](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/pitfalls) · [FAQ](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/faq) |
|
|
396
|
+
| 功能详解:定时任务 / Oncall / Dashboard / 多机器人协作 / 会话接力 | [定时](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/schedule) · [Oncall](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/oncall) · [Dashboard](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/dashboard) · [多 bot](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/multi-bot) · [接力](https://bytedance.aiforce.cloud/app/app_4k9smq6rdxher/relay) |
|
|
397
397
|
|
|
398
398
|
## 贡献
|
|
399
399
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare function sandboxVartmpRoot(): string;
|
|
1
2
|
/**
|
|
2
3
|
* Mount an overlayfs: reads fall through to `lower` (real, zero copy); writes
|
|
3
4
|
* copy-up into `upper` (the landable changeset). `work` is overlayfs scratch on
|
|
@@ -123,7 +124,7 @@ export interface SandboxSpawn {
|
|
|
123
124
|
outbox: string;
|
|
124
125
|
/** Project overlay UPPER dir — THE LANDABLE CHANGESET (used by sandbox-land). */
|
|
125
126
|
workDir: string;
|
|
126
|
-
/** HOME overlay UPPER dir (/var/tmp/botmux-sbx
|
|
127
|
+
/** HOME overlay UPPER dir (/var/tmp/botmux-sbx-<uid>/<sid>/home-upper). The sandboxed
|
|
127
128
|
* CLI's $HOME writes — INCLUDING its session jsonl under CLAUDE_CONFIG_DIR —
|
|
128
129
|
* land here (invisible at the real path). The worker redirects its bridge/idle
|
|
129
130
|
* watch into this via sandboxedClaudeDataDir() so it sees the CLI's turns. */
|
|
@@ -149,10 +150,12 @@ export declare function sandboxedClaudeDataDir(sessionId: string, realDataDir: s
|
|
|
149
150
|
* is off / unsupported / a required overlay mount fails (fail-safe = the worker
|
|
150
151
|
* treats null as a hard error and does NOT silently run unsandboxed).
|
|
151
152
|
*
|
|
152
|
-
* Layout under <dataDir>/sandboxes/<sessionId>/:
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* overlayfs forbids upper/work inside
|
|
153
|
+
* Layout under <dataDir>/sandboxes/<sessionId>/: shimbin, proj-upper (the
|
|
154
|
+
* landable changeset), proj-work, proj-merged, home-merged. The HOME overlay's
|
|
155
|
+
* upper/work and the daemon-mediated outbox live under
|
|
156
|
+
* /var/tmp/botmux-sbx-<uid>/<sessionId>/ because overlayfs forbids upper/work inside
|
|
157
|
+
* the lower (= the real home), and keeping the outbox outside HOME avoids a
|
|
158
|
+
* nested bind under the HOME overlay on bwrap hosts that hang on that pattern.
|
|
156
159
|
*/
|
|
157
160
|
export declare function prepareSandbox(opts: {
|
|
158
161
|
/** Whether the sandbox is on for THIS session (per-bot BotConfig.sandbox OR
|
|
@@ -194,7 +197,10 @@ export declare function prepareSandbox(opts: {
|
|
|
194
197
|
* path back so the watcher can keep servicing the live CLI's `botmux send`, plus
|
|
195
198
|
* the workDir (upper changeset for landing) and a cleanup that tears the residue
|
|
196
199
|
* down at close/exit. Returns null if the session has no sandbox tree on disk
|
|
197
|
-
* (never sandboxed). Linux-only, mirrors prepareSandbox's layout.
|
|
200
|
+
* (never sandboxed). Linux-only, mirrors prepareSandbox's layout. New sessions
|
|
201
|
+
* keep outbox under /var/tmp/botmux-sbx-<uid>/<sid>/outbox; fall back to legacy
|
|
202
|
+
* /var/tmp/botmux-sbx/<sid>/outbox or <dataDir>/sandboxes/<sid>/outbox for
|
|
203
|
+
* sessions that were already running before the layout changed.
|
|
198
204
|
*/
|
|
199
205
|
export declare function attachSandboxOutbox(opts: {
|
|
200
206
|
sessionId: string;
|
|
@@ -269,6 +275,14 @@ export declare function validateRelayRequest(req: RelayRequest): {
|
|
|
269
275
|
* be swapped under us. Returns false (reject) on symlink / non-regular / any error.
|
|
270
276
|
*/
|
|
271
277
|
export declare function materializeOutboxFile(outbox: string, name: string, dest: string): boolean;
|
|
278
|
+
/**
|
|
279
|
+
* TOCTOU-safe read of the `.req.json` trigger file itself. The request file is
|
|
280
|
+
* also sandbox-controlled input, so it needs the same hardening as content and
|
|
281
|
+
* attachments: no symlinks, no FIFO/device files, no blocking opens, and a small
|
|
282
|
+
* size cap before JSON.parse. Returns null on any unsafe/non-regular/oversized
|
|
283
|
+
* input so the watcher can reject and keep its event loop moving.
|
|
284
|
+
*/
|
|
285
|
+
export declare function readRelayRequestFile(reqPath: string): string | null;
|
|
272
286
|
/**
|
|
273
287
|
* Daemon/worker-side outbox watcher. The sandboxed `botmux send` (relay mode)
|
|
274
288
|
* drops `<id>.req.json`; we validate (validateRelayRequest) and then MATERIALIZE
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/adapters/backend/sandbox.ts"],"names":[],"mappings":"AAkCA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAqB1G;AAED,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;gFACgF;AAChF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQnD;AAqCD,MAAM,WAAW,WAAW;IAC1B;0FACsF;IACtF,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAC;IACtB;2DACuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+EAA+E;IAC/E,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C;;sEAEkE;IAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;sDAEkD;IAClD,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;6DAEyD;IACzD,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,8EAA8E;IAC9E,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,CA6B5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,CAU7E;AAoCD;;;8DAG8D;AAC9D,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GACvE,MAAM,EAAE,CAaV;AAUD;+DAC+D;AAC/D,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB;;;mFAG+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;mDAWmD;AACnD,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAIrF;AAMD
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/adapters/backend/sandbox.ts"],"names":[],"mappings":"AAkCA,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AA6CD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAqB1G;AAED,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;gFACgF;AAChF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQnD;AAqCD,MAAM,WAAW,WAAW;IAC1B;0FACsF;IACtF,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAC;IACtB;2DACuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+EAA+E;IAC/E,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C;;sEAEkE;IAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;sDAEkD;IAClD,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;6DAEyD;IACzD,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,8EAA8E;IAC9E,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,CA6B5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,CAU7E;AAoCD;;;8DAG8D;AAC9D,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GACvE,MAAM,EAAE,CAaV;AAUD;+DAC+D;AAC/D,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB;;;mFAG+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;mDAWmD;AACnD,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAIrF;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC;;sFAEkF;IAClF,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;2EACuE;IACvE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;+EAC2E;IAC3E,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;oEAEgE;IAChE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;qEACiE;IACjE,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;iDAE6C;IAC7C,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,yEAAyE;IACzE,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,GAAG,YAAY,GAAG,IAAI,CAuJtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,IAAI,CA8BjJ;AAkCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAoCzF;AAOD,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AASD,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAsC1H;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAiBzF;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsBnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,IAAI,CAyF5G"}
|
|
@@ -20,14 +20,61 @@
|
|
|
20
20
|
* sandbox-exec approach and is handled elsewhere.
|
|
21
21
|
*/
|
|
22
22
|
import { homedir } from 'node:os';
|
|
23
|
-
import { mkdirSync, existsSync, writeFileSync, chmodSync, readdirSync, readFileSync, rmSync, statSync, realpathSync, openSync, fstatSync, readSync, writeSync, closeSync, constants as fsConstants } from 'node:fs';
|
|
23
|
+
import { mkdirSync, existsSync, writeFileSync, chmodSync, readdirSync, readFileSync, rmSync, statSync, lstatSync, realpathSync, openSync, fstatSync, readSync, writeSync, closeSync, constants as fsConstants } from 'node:fs';
|
|
24
24
|
import { atomicWriteFileSync } from '../../utils/atomic-write.js';
|
|
25
25
|
import { join, dirname, relative, resolve } from 'node:path';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
27
|
import { spawn, spawnSync } from 'node:child_process';
|
|
28
28
|
/** Host root for the HOME overlay's upper/work — MUST be OUTSIDE the home lower
|
|
29
|
-
* (overlayfs forbids upper/work inside lower).
|
|
30
|
-
|
|
29
|
+
* (overlayfs forbids upper/work inside lower). Keep it per-uid: /var/tmp is a
|
|
30
|
+
* shared multi-user surface, and a global 0700 root would let the first user
|
|
31
|
+
* that starts sandbox block every other Unix user on the same machine. */
|
|
32
|
+
const LEGACY_VARTMP_ROOT = '/var/tmp/botmux-sbx';
|
|
33
|
+
export function sandboxVartmpRoot() {
|
|
34
|
+
const uid = process.getuid?.() ?? 0;
|
|
35
|
+
return `/var/tmp/botmux-sbx-${uid}`;
|
|
36
|
+
}
|
|
37
|
+
function legacySandboxVartmpDir(sessionId) {
|
|
38
|
+
return join(LEGACY_VARTMP_ROOT, sessionId);
|
|
39
|
+
}
|
|
40
|
+
function sandboxVartmpDir(sessionId) {
|
|
41
|
+
return join(sandboxVartmpRoot(), sessionId);
|
|
42
|
+
}
|
|
43
|
+
function sandboxHomeUpperDir(sessionId) {
|
|
44
|
+
const next = join(sandboxVartmpDir(sessionId), 'home-upper');
|
|
45
|
+
const legacy = join(legacySandboxVartmpDir(sessionId), 'home-upper');
|
|
46
|
+
// Published versions stored HOME upper under the global /var/tmp/botmux-sbx.
|
|
47
|
+
// A daemon upgrade may reattach a still-running tmux sandbox whose bwrap
|
|
48
|
+
// namespace continues writing that old overlay upper. If the new per-uid upper
|
|
49
|
+
// does not exist, keep the bridge pointed at the legacy path.
|
|
50
|
+
if (existsSync(legacy) && !existsSync(next))
|
|
51
|
+
return legacy;
|
|
52
|
+
return next;
|
|
53
|
+
}
|
|
54
|
+
/** Create a daemon-private directory and repair legacy/default mkdir modes.
|
|
55
|
+
* Anything under /var/tmp is on a shared multi-user surface, while the outbox
|
|
56
|
+
* carries in-flight Lark message bodies/attachments. Refuse to proceed unless
|
|
57
|
+
* the path is a real directory owned by this daemon user, and group/other bits
|
|
58
|
+
* are gone after chmod. */
|
|
59
|
+
function ensurePrivateDir(dir) {
|
|
60
|
+
try {
|
|
61
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
62
|
+
const lst = lstatSync(dir);
|
|
63
|
+
if (lst.isSymbolicLink() || !lst.isDirectory())
|
|
64
|
+
return false;
|
|
65
|
+
const stBefore = statSync(dir);
|
|
66
|
+
const uid = process.getuid?.();
|
|
67
|
+
if (typeof uid === 'number' && stBefore.uid !== uid)
|
|
68
|
+
return false;
|
|
69
|
+
chmodSync(dir, 0o700); // repair dirs created before this hardening or under a loose umask
|
|
70
|
+
const st = statSync(dir);
|
|
71
|
+
return st.isDirectory() && (typeof uid !== 'number' || st.uid === uid) && (st.mode & 0o077) === 0;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.error(`[sandbox] failed to prepare private dir ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
31
78
|
// ───────────────────────────── overlay primitives ────────────────────────────
|
|
32
79
|
/**
|
|
33
80
|
* Mount an overlayfs: reads fall through to `lower` (real, zero copy); writes
|
|
@@ -302,7 +349,7 @@ export function sandboxEnabled() {
|
|
|
302
349
|
export function sandboxedClaudeDataDir(sessionId, realDataDir) {
|
|
303
350
|
const raw = relative(homedir(), realDataDir);
|
|
304
351
|
const rel = raw.startsWith('..') ? relative(resolveSandboxMountPath(homedir()), realDataDir) : raw;
|
|
305
|
-
return join(
|
|
352
|
+
return join(sandboxHomeUpperDir(sessionId), rel);
|
|
306
353
|
}
|
|
307
354
|
/** Proxy env vars forwarded into the sandbox so the CLI reaches the API even on
|
|
308
355
|
* the tmux backend (which otherwise only forwards a fixed whitelist). */
|
|
@@ -312,10 +359,12 @@ const PROXY_ENV_KEYS = ['http_proxy', 'https_proxy', 'HTTP_PROXY', 'HTTPS_PROXY'
|
|
|
312
359
|
* is off / unsupported / a required overlay mount fails (fail-safe = the worker
|
|
313
360
|
* treats null as a hard error and does NOT silently run unsandboxed).
|
|
314
361
|
*
|
|
315
|
-
* Layout under <dataDir>/sandboxes/<sessionId>/:
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* overlayfs forbids upper/work inside
|
|
362
|
+
* Layout under <dataDir>/sandboxes/<sessionId>/: shimbin, proj-upper (the
|
|
363
|
+
* landable changeset), proj-work, proj-merged, home-merged. The HOME overlay's
|
|
364
|
+
* upper/work and the daemon-mediated outbox live under
|
|
365
|
+
* /var/tmp/botmux-sbx-<uid>/<sessionId>/ because overlayfs forbids upper/work inside
|
|
366
|
+
* the lower (= the real home), and keeping the outbox outside HOME avoids a
|
|
367
|
+
* nested bind under the HOME overlay on bwrap hosts that hang on that pattern.
|
|
319
368
|
*/
|
|
320
369
|
export function prepareSandbox(opts) {
|
|
321
370
|
if (!opts.enabled)
|
|
@@ -329,7 +378,6 @@ export function prepareSandbox(opts) {
|
|
|
329
378
|
return null;
|
|
330
379
|
const dataDir = resolveSandboxMountPath(opts.dataDir);
|
|
331
380
|
const sessionRoot = join(dataDir, 'sandboxes', opts.sessionId);
|
|
332
|
-
const outbox = join(sessionRoot, 'outbox');
|
|
333
381
|
const shimBin = join(sessionRoot, 'shimbin');
|
|
334
382
|
const empties = join(sessionRoot, 'empties');
|
|
335
383
|
const projUpper = join(sessionRoot, 'proj-upper'); // THE LANDABLE CHANGESET
|
|
@@ -337,9 +385,12 @@ export function prepareSandbox(opts) {
|
|
|
337
385
|
const projMerged = join(sessionRoot, 'proj-merged');
|
|
338
386
|
const homeMerged = join(sessionRoot, 'home-merged'); // merged may live under sessionRoot
|
|
339
387
|
// HOME overlay upper/work MUST be OUTSIDE the home lower (overlayfs constraint).
|
|
340
|
-
const vartmp =
|
|
388
|
+
const vartmp = sandboxVartmpDir(opts.sessionId);
|
|
389
|
+
const outbox = join(vartmp, 'outbox');
|
|
341
390
|
const homeUpper = join(vartmp, 'home-upper');
|
|
342
391
|
const homeWork = join(vartmp, 'home-work');
|
|
392
|
+
if (!ensurePrivateDir(sandboxVartmpRoot()) || !ensurePrivateDir(vartmp) || !ensurePrivateDir(outbox))
|
|
393
|
+
return null;
|
|
343
394
|
for (const d of [outbox, shimBin, empties])
|
|
344
395
|
mkdirSync(d, { recursive: true });
|
|
345
396
|
const home = resolveSandboxMountPath(homedir());
|
|
@@ -475,6 +526,10 @@ export function prepareSandbox(opts) {
|
|
|
475
526
|
rmSync(vartmp, { recursive: true, force: true });
|
|
476
527
|
}
|
|
477
528
|
catch { /* */ }
|
|
529
|
+
try {
|
|
530
|
+
rmSync(legacySandboxVartmpDir(opts.sessionId), { recursive: true, force: true });
|
|
531
|
+
}
|
|
532
|
+
catch { /* */ }
|
|
478
533
|
},
|
|
479
534
|
};
|
|
480
535
|
}
|
|
@@ -487,25 +542,37 @@ export function prepareSandbox(opts) {
|
|
|
487
542
|
* path back so the watcher can keep servicing the live CLI's `botmux send`, plus
|
|
488
543
|
* the workDir (upper changeset for landing) and a cleanup that tears the residue
|
|
489
544
|
* down at close/exit. Returns null if the session has no sandbox tree on disk
|
|
490
|
-
* (never sandboxed). Linux-only, mirrors prepareSandbox's layout.
|
|
545
|
+
* (never sandboxed). Linux-only, mirrors prepareSandbox's layout. New sessions
|
|
546
|
+
* keep outbox under /var/tmp/botmux-sbx-<uid>/<sid>/outbox; fall back to legacy
|
|
547
|
+
* /var/tmp/botmux-sbx/<sid>/outbox or <dataDir>/sandboxes/<sid>/outbox for
|
|
548
|
+
* sessions that were already running before the layout changed.
|
|
491
549
|
*/
|
|
492
550
|
export function attachSandboxOutbox(opts) {
|
|
493
551
|
if (process.platform !== 'linux')
|
|
494
552
|
return null;
|
|
495
553
|
const dataDir = resolveSandboxMountPath(opts.dataDir);
|
|
496
554
|
const sessionRoot = join(dataDir, 'sandboxes', opts.sessionId);
|
|
497
|
-
const
|
|
555
|
+
const vartmp = sandboxVartmpDir(opts.sessionId);
|
|
556
|
+
const newOutbox = join(vartmp, 'outbox');
|
|
557
|
+
const legacyVartmpOutbox = join(legacySandboxVartmpDir(opts.sessionId), 'outbox');
|
|
558
|
+
const legacyOutbox = join(sessionRoot, 'outbox');
|
|
498
559
|
const projUpper = join(sessionRoot, 'proj-upper');
|
|
499
|
-
if (!existsSync(
|
|
560
|
+
if (!existsSync(newOutbox) && !existsSync(legacyVartmpOutbox) && !existsSync(legacyOutbox) && !existsSync(projUpper))
|
|
500
561
|
return null; // never sandboxed
|
|
562
|
+
const outbox = existsSync(newOutbox) ? newOutbox : (existsSync(legacyVartmpOutbox) ? legacyVartmpOutbox : (existsSync(legacyOutbox) ? legacyOutbox : newOutbox));
|
|
501
563
|
// Ensure the outbox exists (the watcher reads it); never (re)mount here.
|
|
502
|
-
|
|
503
|
-
|
|
564
|
+
if (outbox === newOutbox) {
|
|
565
|
+
if (!ensurePrivateDir(sandboxVartmpRoot()) || !ensurePrivateDir(vartmp) || !ensurePrivateDir(outbox))
|
|
566
|
+
return null;
|
|
504
567
|
}
|
|
505
|
-
|
|
568
|
+
else if (outbox === legacyVartmpOutbox) {
|
|
569
|
+
if (!ensurePrivateDir(legacySandboxVartmpDir(opts.sessionId)) || !ensurePrivateDir(outbox))
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
else if (!ensurePrivateDir(outbox))
|
|
573
|
+
return null;
|
|
506
574
|
const projMerged = join(sessionRoot, 'proj-merged');
|
|
507
575
|
const homeMerged = join(sessionRoot, 'home-merged');
|
|
508
|
-
const vartmp = join(VARTMP_ROOT, opts.sessionId);
|
|
509
576
|
return {
|
|
510
577
|
outbox,
|
|
511
578
|
workDir: projUpper,
|
|
@@ -520,6 +587,10 @@ export function attachSandboxOutbox(opts) {
|
|
|
520
587
|
rmSync(vartmp, { recursive: true, force: true });
|
|
521
588
|
}
|
|
522
589
|
catch { /* */ }
|
|
590
|
+
try {
|
|
591
|
+
rmSync(legacySandboxVartmpDir(opts.sessionId), { recursive: true, force: true });
|
|
592
|
+
}
|
|
593
|
+
catch { /* */ }
|
|
523
594
|
},
|
|
524
595
|
};
|
|
525
596
|
}
|
|
@@ -534,13 +605,17 @@ function reclaimSandbox(dataDir, sid) {
|
|
|
534
605
|
}
|
|
535
606
|
catch { /* */ }
|
|
536
607
|
try {
|
|
537
|
-
rmSync(join(
|
|
608
|
+
rmSync(join(sandboxVartmpRoot(), sid), { recursive: true, force: true });
|
|
609
|
+
}
|
|
610
|
+
catch { /* */ }
|
|
611
|
+
try {
|
|
612
|
+
rmSync(legacySandboxVartmpDir(sid), { recursive: true, force: true });
|
|
538
613
|
}
|
|
539
614
|
catch { /* */ }
|
|
540
615
|
}
|
|
541
616
|
/** Scan the process table for sandbox session-ids referenced by any running
|
|
542
617
|
* process's argv. A live bwrap's bind/overlay paths contain `sandboxes/<sid>`
|
|
543
|
-
* and `botmux-sbx
|
|
618
|
+
* and `botmux-sbx-<uid>/<sid>`, so this physically detects which sandbox dirs are
|
|
544
619
|
* still in use — by overlay sessions AND old clone-model sessions alike. Used as
|
|
545
620
|
* a hard guard so the sweep never deletes a dir out from under a live CLI. */
|
|
546
621
|
function liveSandboxSids() {
|
|
@@ -552,7 +627,7 @@ function liveSandboxSids() {
|
|
|
552
627
|
catch {
|
|
553
628
|
return live;
|
|
554
629
|
}
|
|
555
|
-
const re = /(?:sandboxes|botmux-sbx)\/([^/\0]+)/g;
|
|
630
|
+
const re = /(?:sandboxes|botmux-sbx(?:-[^/\0]+)?)\/([^/\0]+)/g;
|
|
556
631
|
for (const pid of pids) {
|
|
557
632
|
if (!/^\d+$/.test(pid))
|
|
558
633
|
continue;
|
|
@@ -602,9 +677,9 @@ export function sweepOrphanSandboxes(dataDir, activeSessionIds) {
|
|
|
602
677
|
return;
|
|
603
678
|
} // no sandboxes dir yet
|
|
604
679
|
// Grace before reclaiming an ACTIVE-but-unmounted sandbox: a worker that just
|
|
605
|
-
// (re)spawned creates the
|
|
606
|
-
//
|
|
607
|
-
// in-progress session's
|
|
680
|
+
// (re)spawned creates the sandbox dirs a few syscalls BEFORE it mounts the
|
|
681
|
+
// overlay. Without this, a sweep firing in that tiny window would nuke an
|
|
682
|
+
// in-progress session's shimbin/project tree. Non-active orphans are reclaimed immediately
|
|
608
683
|
// (no live worker can be mid-spawn for a session that isn't active).
|
|
609
684
|
const ACTIVE_DEAD_GRACE_MS = 60_000;
|
|
610
685
|
const now = Date.now();
|
|
@@ -749,6 +824,49 @@ export function materializeOutboxFile(outbox, name, dest) {
|
|
|
749
824
|
closeSync(outFd);
|
|
750
825
|
}
|
|
751
826
|
}
|
|
827
|
+
const RELAY_REQUEST_MAX_BYTES = 64 * 1024;
|
|
828
|
+
/**
|
|
829
|
+
* TOCTOU-safe read of the `.req.json` trigger file itself. The request file is
|
|
830
|
+
* also sandbox-controlled input, so it needs the same hardening as content and
|
|
831
|
+
* attachments: no symlinks, no FIFO/device files, no blocking opens, and a small
|
|
832
|
+
* size cap before JSON.parse. Returns null on any unsafe/non-regular/oversized
|
|
833
|
+
* input so the watcher can reject and keep its event loop moving.
|
|
834
|
+
*/
|
|
835
|
+
export function readRelayRequestFile(reqPath) {
|
|
836
|
+
let fd;
|
|
837
|
+
try {
|
|
838
|
+
fd = openSync(reqPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
|
|
839
|
+
}
|
|
840
|
+
catch {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
try {
|
|
844
|
+
const st = fstatSync(fd);
|
|
845
|
+
if (!st.isFile())
|
|
846
|
+
return null;
|
|
847
|
+
if (st.size > RELAY_REQUEST_MAX_BYTES)
|
|
848
|
+
return null;
|
|
849
|
+
const chunks = [];
|
|
850
|
+
let total = 0;
|
|
851
|
+
const buf = Buffer.alloc(Math.min(8192, RELAY_REQUEST_MAX_BYTES));
|
|
852
|
+
for (;;) {
|
|
853
|
+
const n = readSync(fd, buf, 0, buf.length, null);
|
|
854
|
+
if (n <= 0)
|
|
855
|
+
break;
|
|
856
|
+
total += n;
|
|
857
|
+
if (total > RELAY_REQUEST_MAX_BYTES)
|
|
858
|
+
return null;
|
|
859
|
+
chunks.push(Buffer.from(buf.subarray(0, n)));
|
|
860
|
+
}
|
|
861
|
+
return Buffer.concat(chunks, total).toString('utf8');
|
|
862
|
+
}
|
|
863
|
+
catch {
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
finally {
|
|
867
|
+
closeSync(fd);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
752
870
|
/**
|
|
753
871
|
* Daemon/worker-side outbox watcher. The sandboxed `botmux send` (relay mode)
|
|
754
872
|
* drops `<id>.req.json`; we validate (validateRelayRequest) and then MATERIALIZE
|
|
@@ -768,7 +886,7 @@ export function startOutboxWatcher(outbox, baseEnv, sessionId) {
|
|
|
768
886
|
const finish = (id, reqPath, name, staged, code, stdout, stderr) => {
|
|
769
887
|
// 原子写:沙盒侧 CLI 在 existsSync 轮询这个 res.json,rename 保证它读到完整 JSON。
|
|
770
888
|
try {
|
|
771
|
-
atomicWriteFileSync(join(outbox, `${id}.res.json`), JSON.stringify({ code, stdout, stderr }));
|
|
889
|
+
atomicWriteFileSync(join(outbox, `${id}.res.json`), JSON.stringify({ code, stdout, stderr }), { mode: 0o600 });
|
|
772
890
|
}
|
|
773
891
|
catch { /* */ }
|
|
774
892
|
try {
|
|
@@ -799,8 +917,13 @@ export function startOutboxWatcher(outbox, baseEnv, sessionId) {
|
|
|
799
917
|
const id = name.slice(0, -'.req.json'.length);
|
|
800
918
|
const staged = [];
|
|
801
919
|
let req;
|
|
920
|
+
const reqText = readRelayRequestFile(reqPath);
|
|
921
|
+
if (reqText === null) {
|
|
922
|
+
finish(id, reqPath, name, staged, 1, '', 'relay rejected: request not a regular file or too large');
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
802
925
|
try {
|
|
803
|
-
req = JSON.parse(
|
|
926
|
+
req = JSON.parse(reqText);
|
|
804
927
|
}
|
|
805
928
|
catch {
|
|
806
929
|
finish(id, reqPath, name, staged, 1, '', 'relay: bad json');
|
|
@@ -812,7 +935,8 @@ export function startOutboxWatcher(outbox, baseEnv, sessionId) {
|
|
|
812
935
|
continue;
|
|
813
936
|
}
|
|
814
937
|
try {
|
|
815
|
-
mkdirSync(staging, { recursive: true });
|
|
938
|
+
mkdirSync(staging, { recursive: true, mode: 0o700 });
|
|
939
|
+
chmodSync(staging, 0o700);
|
|
816
940
|
}
|
|
817
941
|
catch { /* */ }
|
|
818
942
|
// Materialize content (TOCTOU-safe) into the private staging dir.
|