agent-life-bridge 0.1.1
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/.env.example +20 -0
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/bin/agent-life-bridge.mjs +13 -0
- package/config.example.json +130 -0
- package/config.multi-agent.example.json +88 -0
- package/dist/agents/agent-life-session-map-store.d.ts +21 -0
- package/dist/agents/agent-life-session-map-store.d.ts.map +1 -0
- package/dist/agents/agent-life-session-map-store.js +78 -0
- package/dist/agents/agent-life-session-map-store.js.map +1 -0
- package/dist/agents/agent-resolver.d.ts +12 -0
- package/dist/agents/agent-resolver.d.ts.map +1 -0
- package/dist/agents/agent-resolver.js +114 -0
- package/dist/agents/agent-resolver.js.map +1 -0
- package/dist/agents/agent-router.d.ts +8 -0
- package/dist/agents/agent-router.d.ts.map +1 -0
- package/dist/agents/agent-router.js +72 -0
- package/dist/agents/agent-router.js.map +1 -0
- package/dist/agents/cli-backends.d.ts +25 -0
- package/dist/agents/cli-backends.d.ts.map +1 -0
- package/dist/agents/cli-backends.js +252 -0
- package/dist/agents/cli-backends.js.map +1 -0
- package/dist/agents/cli-output.d.ts +43 -0
- package/dist/agents/cli-output.d.ts.map +1 -0
- package/dist/agents/cli-output.js +352 -0
- package/dist/agents/cli-output.js.map +1 -0
- package/dist/agents/cli-runner.d.ts +32 -0
- package/dist/agents/cli-runner.d.ts.map +1 -0
- package/dist/agents/cli-runner.js +861 -0
- package/dist/agents/cli-runner.js.map +1 -0
- package/dist/agents/cli-session-store.d.ts +53 -0
- package/dist/agents/cli-session-store.d.ts.map +1 -0
- package/dist/agents/cli-session-store.js +263 -0
- package/dist/agents/cli-session-store.js.map +1 -0
- package/dist/agents/codex-app-server-runtime.d.ts +80 -0
- package/dist/agents/codex-app-server-runtime.d.ts.map +1 -0
- package/dist/agents/codex-app-server-runtime.js +1049 -0
- package/dist/agents/codex-app-server-runtime.js.map +1 -0
- package/dist/agents/fch-runtime-context.d.ts +28 -0
- package/dist/agents/fch-runtime-context.d.ts.map +1 -0
- package/dist/agents/fch-runtime-context.js +65 -0
- package/dist/agents/fch-runtime-context.js.map +1 -0
- package/dist/agents/model-selection.d.ts +28 -0
- package/dist/agents/model-selection.d.ts.map +1 -0
- package/dist/agents/model-selection.js +40 -0
- package/dist/agents/model-selection.js.map +1 -0
- package/dist/agents/models-config.d.ts +28 -0
- package/dist/agents/models-config.d.ts.map +1 -0
- package/dist/agents/models-config.js +43 -0
- package/dist/agents/models-config.js.map +1 -0
- package/dist/channels/agent-life.d.ts +9 -0
- package/dist/channels/agent-life.d.ts.map +1 -0
- package/dist/channels/agent-life.js +407 -0
- package/dist/channels/agent-life.js.map +1 -0
- package/dist/channels/command-gating.d.ts +20 -0
- package/dist/channels/command-gating.d.ts.map +1 -0
- package/dist/channels/command-gating.js +43 -0
- package/dist/channels/command-gating.js.map +1 -0
- package/dist/channels/draft-stream-controls.d.ts +21 -0
- package/dist/channels/draft-stream-controls.d.ts.map +1 -0
- package/dist/channels/draft-stream-controls.js +43 -0
- package/dist/channels/draft-stream-controls.js.map +1 -0
- package/dist/channels/draft-stream-loop.d.ts +31 -0
- package/dist/channels/draft-stream-loop.d.ts.map +1 -0
- package/dist/channels/draft-stream-loop.js +60 -0
- package/dist/channels/draft-stream-loop.js.map +1 -0
- package/dist/channels/mention-gating.d.ts +18 -0
- package/dist/channels/mention-gating.d.ts.map +1 -0
- package/dist/channels/mention-gating.js +20 -0
- package/dist/channels/mention-gating.js.map +1 -0
- package/dist/channels/registry.d.ts +5 -0
- package/dist/channels/registry.d.ts.map +1 -0
- package/dist/channels/registry.js +14 -0
- package/dist/channels/registry.js.map +1 -0
- package/dist/channels/run-state-machine.d.ts +21 -0
- package/dist/channels/run-state-machine.d.ts.map +1 -0
- package/dist/channels/run-state-machine.js +41 -0
- package/dist/channels/run-state-machine.js.map +1 -0
- package/dist/channels/session-envelope.d.ts +5 -0
- package/dist/channels/session-envelope.d.ts.map +1 -0
- package/dist/channels/session-envelope.js +16 -0
- package/dist/channels/session-envelope.js.map +1 -0
- package/dist/channels/session-id.d.ts +3 -0
- package/dist/channels/session-id.d.ts.map +1 -0
- package/dist/channels/session-id.js +11 -0
- package/dist/channels/session-id.js.map +1 -0
- package/dist/channels/session.d.ts +18 -0
- package/dist/channels/session.d.ts.map +1 -0
- package/dist/channels/session.js +35 -0
- package/dist/channels/session.js.map +1 -0
- package/dist/channels/typing-lifecycle.d.ts +16 -0
- package/dist/channels/typing-lifecycle.d.ts.map +1 -0
- package/dist/channels/typing-lifecycle.js +31 -0
- package/dist/channels/typing-lifecycle.js.map +1 -0
- package/dist/cli/cmd-add-agent.d.ts +19 -0
- package/dist/cli/cmd-add-agent.d.ts.map +1 -0
- package/dist/cli/cmd-add-agent.js +362 -0
- package/dist/cli/cmd-add-agent.js.map +1 -0
- package/dist/cli/cmd-config.d.ts +3 -0
- package/dist/cli/cmd-config.d.ts.map +1 -0
- package/dist/cli/cmd-config.js +16 -0
- package/dist/cli/cmd-config.js.map +1 -0
- package/dist/cli/cmd-doctor.d.ts +3 -0
- package/dist/cli/cmd-doctor.d.ts.map +1 -0
- package/dist/cli/cmd-doctor.js +127 -0
- package/dist/cli/cmd-doctor.js.map +1 -0
- package/dist/cli/cmd-logs.d.ts +3 -0
- package/dist/cli/cmd-logs.d.ts.map +1 -0
- package/dist/cli/cmd-logs.js +26 -0
- package/dist/cli/cmd-logs.js.map +1 -0
- package/dist/cli/cmd-onboard.d.ts +5 -0
- package/dist/cli/cmd-onboard.d.ts.map +1 -0
- package/dist/cli/cmd-onboard.js +53 -0
- package/dist/cli/cmd-onboard.js.map +1 -0
- package/dist/cli/cmd-restart.d.ts +3 -0
- package/dist/cli/cmd-restart.d.ts.map +1 -0
- package/dist/cli/cmd-restart.js +22 -0
- package/dist/cli/cmd-restart.js.map +1 -0
- package/dist/cli/cmd-start.d.ts +19 -0
- package/dist/cli/cmd-start.d.ts.map +1 -0
- package/dist/cli/cmd-start.js +783 -0
- package/dist/cli/cmd-start.js.map +1 -0
- package/dist/cli/cmd-status.d.ts +3 -0
- package/dist/cli/cmd-status.d.ts.map +1 -0
- package/dist/cli/cmd-status.js +16 -0
- package/dist/cli/cmd-status.js.map +1 -0
- package/dist/cli/cmd-stop.d.ts +9 -0
- package/dist/cli/cmd-stop.d.ts.map +1 -0
- package/dist/cli/cmd-stop.js +59 -0
- package/dist/cli/cmd-stop.js.map +1 -0
- package/dist/cli/cmd-update.d.ts +3 -0
- package/dist/cli/cmd-update.d.ts.map +1 -0
- package/dist/cli/cmd-update.js +127 -0
- package/dist/cli/cmd-update.js.map +1 -0
- package/dist/cli/cmd-version.d.ts +4 -0
- package/dist/cli/cmd-version.d.ts.map +1 -0
- package/dist/cli/cmd-version.js +12 -0
- package/dist/cli/cmd-version.js.map +1 -0
- package/dist/cli/pid-file.d.ts +23 -0
- package/dist/cli/pid-file.d.ts.map +1 -0
- package/dist/cli/pid-file.js +136 -0
- package/dist/cli/pid-file.js.map +1 -0
- package/dist/cli/run-main.d.ts +16 -0
- package/dist/cli/run-main.d.ts.map +1 -0
- package/dist/cli/run-main.js +114 -0
- package/dist/cli/run-main.js.map +1 -0
- package/dist/cli/update-check.d.ts +15 -0
- package/dist/cli/update-check.d.ts.map +1 -0
- package/dist/cli/update-check.js +187 -0
- package/dist/cli/update-check.js.map +1 -0
- package/dist/commands/prefix-router.d.ts +17 -0
- package/dist/commands/prefix-router.d.ts.map +1 -0
- package/dist/commands/prefix-router.js +79 -0
- package/dist/commands/prefix-router.js.map +1 -0
- package/dist/config/agent-dirs.d.ts +24 -0
- package/dist/config/agent-dirs.d.ts.map +1 -0
- package/dist/config/agent-dirs.js +71 -0
- package/dist/config/agent-dirs.js.map +1 -0
- package/dist/config/config.d.ts +4 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +42 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/default-config.d.ts +9 -0
- package/dist/config/default-config.d.ts.map +1 -0
- package/dist/config/default-config.js +94 -0
- package/dist/config/default-config.js.map +1 -0
- package/dist/config/env-vars.d.ts +15 -0
- package/dist/config/env-vars.d.ts.map +1 -0
- package/dist/config/env-vars.js +16 -0
- package/dist/config/env-vars.js.map +1 -0
- package/dist/config/merge-config.d.ts +7 -0
- package/dist/config/merge-config.d.ts.map +1 -0
- package/dist/config/merge-config.js +45 -0
- package/dist/config/merge-config.js.map +1 -0
- package/dist/config/paths.d.ts +25 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +31 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/zod-schema.d.ts +3114 -0
- package/dist/config/zod-schema.d.ts.map +1 -0
- package/dist/config/zod-schema.js +217 -0
- package/dist/config/zod-schema.js.map +1 -0
- package/dist/entry.d.ts +3 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +70 -0
- package/dist/entry.js.map +1 -0
- package/dist/logger.d.ts +5 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +42 -0
- package/dist/logger.js.map +1 -0
- package/dist/types/index.d.ts +215 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +62 -0
package/.env.example
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# .env.example — agent-life-bridge 环境变量
|
|
2
|
+
#
|
|
3
|
+
# 环境变量可覆盖 config.json 中的对应值
|
|
4
|
+
|
|
5
|
+
# 配置文件路径(默认按搜索顺序:./config.json → ~/.agent-life-bridge/config.json)
|
|
6
|
+
# AGENT_LIFE_BRIDGE_CONFIG=./config.json
|
|
7
|
+
|
|
8
|
+
# Agent Life 连接(覆盖 channels["agent-life"])
|
|
9
|
+
# AGENT_LIFE_BRIDGE_GATEWAY_URL=https://www.m2a.chat/gateway/api
|
|
10
|
+
# AGENT_LIFE_BRIDGE_BOT_TOKEN=bot_xxxxxxxxxxxxxxxxxx
|
|
11
|
+
|
|
12
|
+
# 日志级别(覆盖 logging.level)
|
|
13
|
+
# AGENT_LIFE_BRIDGE_LOG_LEVEL=info
|
|
14
|
+
|
|
15
|
+
# 临时文件目录(覆盖 files.temp_dir)
|
|
16
|
+
# AGENT_LIFE_BRIDGE_TEMP_DIR=/tmp/agent-life-bridge/files
|
|
17
|
+
|
|
18
|
+
# LLM API Keys(用于 Pi Agent 模式)
|
|
19
|
+
# ANTHROPIC_API_KEY=sk-ant-xxx
|
|
20
|
+
# OPENAI_API_KEY=sk-xxx
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# agent-life-bridge
|
|
2
|
+
|
|
3
|
+
本地 Agent 网关,部署在客户本地机器上,通过长轮询连接 agent-life 平台,把消息转发给本机运行的 Claude Code / Codex 等 Agent 运行时。
|
|
4
|
+
|
|
5
|
+
核心特点:
|
|
6
|
+
|
|
7
|
+
- 客户端主动外连,无需暴露入站端口
|
|
8
|
+
- 支持 `claude-cli/...`、`codex-cli/...`、`qwen-cli/...` 这类 CLI 模型引用
|
|
9
|
+
- 支持多个 agent-life account 与多 agent 配置,并按绑定规则路由
|
|
10
|
+
- 提供 `onboard`、`add-agent`、`doctor`、`logs`、`config` 等运维命令
|
|
11
|
+
- 支持前台运行和守护进程模式
|
|
12
|
+
- 支持消息进度草稿、附件下载、图片输入和 `/br:*` 桥接指令
|
|
13
|
+
|
|
14
|
+
## 架构概览
|
|
15
|
+
|
|
16
|
+
链路可以简化为:
|
|
17
|
+
|
|
18
|
+
`agent-life cloud -> 长轮询 -> agent-life-bridge -> 本地 CLI backend / Agent Runtime`
|
|
19
|
+
|
|
20
|
+
其中:
|
|
21
|
+
|
|
22
|
+
- agent-life 负责云端消息入口
|
|
23
|
+
- agent-life-bridge 通过 `getUpdates` 长轮询接收消息,负责路由、会话管理、日志、附件下载和回复回传
|
|
24
|
+
- 本地 Agent Runtime 负责实际执行 Claude Code、Codex、Qwen Code 等 CLI
|
|
25
|
+
- 每个 agent 拥有独立的 CLI 会话状态目录,agent-life 会话与 CLI runtime session 会持久化映射
|
|
26
|
+
|
|
27
|
+
## 快速开始
|
|
28
|
+
|
|
29
|
+
### 方式 A:npm 全局安装
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g agent-life-bridge@latest
|
|
33
|
+
|
|
34
|
+
# 交互式初始化:gateway URL 可直接回车使用默认值,随后输入 bot token、选择模型
|
|
35
|
+
alb onboard
|
|
36
|
+
|
|
37
|
+
# 前台启动
|
|
38
|
+
alb start
|
|
39
|
+
|
|
40
|
+
# 或后台启动
|
|
41
|
+
alb start --daemon
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 方式 B:源码运行
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd agent-life-bridge
|
|
48
|
+
pnpm install
|
|
49
|
+
pnpm build
|
|
50
|
+
cp config.example.json config.json
|
|
51
|
+
# 编辑 config.json
|
|
52
|
+
node dist/entry.js start
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
开发模式:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pnpm dev
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## CLI 命令
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
alb start # 前台启动
|
|
65
|
+
alb start --daemon # 后台启动
|
|
66
|
+
alb start -c ./config.json # 指定配置文件
|
|
67
|
+
alb restart --daemon # 重启为后台模式
|
|
68
|
+
alb stop # 停止守护进程
|
|
69
|
+
alb restart # 重启
|
|
70
|
+
alb status # 查看运行状态
|
|
71
|
+
alb onboard # 交互式初始化
|
|
72
|
+
alb add-agent # 交互式追加一个 agent
|
|
73
|
+
alb add-agent codex-assistant codex-cli/gpt-5.4 codex-bot /workspace/app
|
|
74
|
+
alb add-agent --account-id office-bot
|
|
75
|
+
alb add-agent --create-account office-bot --account-gateway-url https://agent-life.example.com --account-bot-token bot_xxx
|
|
76
|
+
alb doctor # 检查环境、CLI、API Key 和配置
|
|
77
|
+
alb logs # 查看最近日志
|
|
78
|
+
alb config # 输出当前配置
|
|
79
|
+
alb update # npm 全局自更新;如果当前实例有运行状态,会尝试更新后重启
|
|
80
|
+
alb version # 查看版本
|
|
81
|
+
alb help # 查看帮助
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 配置
|
|
85
|
+
|
|
86
|
+
### 配置文件搜索顺序
|
|
87
|
+
|
|
88
|
+
当前实现会按以下顺序读取第一个存在的配置文件:
|
|
89
|
+
|
|
90
|
+
1. `alb start -c /path/to/config.json`
|
|
91
|
+
2. 环境变量 `AGENT_LIFE_BRIDGE_CONFIG`
|
|
92
|
+
3. 当前目录 `./config.json`
|
|
93
|
+
4. 用户目录 `~/.agent-life-bridge/config.json`
|
|
94
|
+
|
|
95
|
+
### 最小配置示例
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"channels": {
|
|
100
|
+
"agent-life": {
|
|
101
|
+
"accounts": {
|
|
102
|
+
"dlb-default": {
|
|
103
|
+
"gateway_url": "https://www.m2a.chat/gateway/api",
|
|
104
|
+
"bot_token": "bot_xxx",
|
|
105
|
+
"poll_timeout": 30,
|
|
106
|
+
"retry_interval": 5,
|
|
107
|
+
"max_retry_interval": 60
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"agents": {
|
|
113
|
+
"defaults": {
|
|
114
|
+
"model": {
|
|
115
|
+
"primary": "claude-cli/opus-4.6"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"list": [
|
|
119
|
+
{
|
|
120
|
+
"id": "default",
|
|
121
|
+
"default": true
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
"bindings": [
|
|
126
|
+
{
|
|
127
|
+
"agentId": "default",
|
|
128
|
+
"match": {
|
|
129
|
+
"channel": "*"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
完整示例见:
|
|
137
|
+
|
|
138
|
+
- [config.example.json](./config.example.json)
|
|
139
|
+
- [config.multi-agent.example.json](./config.multi-agent.example.json)
|
|
140
|
+
|
|
141
|
+
agent-life channel 配置读取时兼容旧的顶层 `gateway_url` / `bot_token` 写法,但新的配置写入会统一落到 `channels["agent-life"].accounts`。
|
|
142
|
+
|
|
143
|
+
兼容性说明:
|
|
144
|
+
|
|
145
|
+
- 老配置仍可继续使用:顶层 `channels["agent-life"].gateway_url` / `bot_token` 仍然会被读取
|
|
146
|
+
- 新示例配置统一使用 `channels["agent-life"].accounts` 格式
|
|
147
|
+
- `add-agent` 会兼容读取老配置,并在保存时自动迁移成 `channels["agent-life"].accounts`
|
|
148
|
+
- `onboard` 会先备份已有配置,再按新的 `channels["agent-life"].accounts` 格式重建配置
|
|
149
|
+
|
|
150
|
+
### 关键配置项
|
|
151
|
+
|
|
152
|
+
- `channels["agent-life"].accounts.<accountId>.gateway_url`:agent-life 网关地址
|
|
153
|
+
- `channels["agent-life"].accounts.<accountId>.bot_token`:bot token
|
|
154
|
+
- `channels["agent-life"].accounts.<accountId>.poll_timeout`:长轮询超时时间,默认 30 秒,最大 60 秒
|
|
155
|
+
- `channels["agent-life"].accounts.<accountId>.retry_interval`:失败后的初始重试间隔,默认 5 秒
|
|
156
|
+
- `channels["agent-life"].accounts.<accountId>.max_retry_interval`:最大重试间隔,默认 60 秒
|
|
157
|
+
- `agents.defaults.model.primary`:主模型引用,例如 `claude-cli/opus-4.6`、`codex-cli/gpt-5.4` 或 `qwen-cli/qwen3-coder-plus`
|
|
158
|
+
- `agents.defaults.model.fallbacks`:备用模型链配置;当前执行链路只使用 `primary`,自动 fallback 逻辑尚未接入
|
|
159
|
+
- `agents.defaults.cliBackends`:CLI backend 启动参数与运行模式
|
|
160
|
+
- `agents.list`:命名 agent 列表
|
|
161
|
+
- `bindings`:消息路由规则,可按 `channel`、`accountId`、`peer.id` 等条件匹配
|
|
162
|
+
- `files.temp_dir`:文件中转目录
|
|
163
|
+
- `logging.level`:日志级别
|
|
164
|
+
|
|
165
|
+
### 多 agent 配置
|
|
166
|
+
|
|
167
|
+
多 agent 模式通过 `agents.list` 定义运行实例,通过 `bindings` 决定消息路由到哪个 agent。
|
|
168
|
+
|
|
169
|
+
运行时会把 `agents.defaults` 与 `agents.list[]` 中的 agent 配置合并:
|
|
170
|
+
|
|
171
|
+
- agent 未配置 `workspace` 时,使用启动进程的当前目录
|
|
172
|
+
- agent 的 `model` 可以是字符串,也可以是 `{ "primary": "...", "fallbacks": [...] }`
|
|
173
|
+
- agent 级别的 `cliBackends` 会深度覆盖默认 backend 配置
|
|
174
|
+
- agent 级别的 `env` 会覆盖默认环境变量,`clearEnv` 会追加
|
|
175
|
+
|
|
176
|
+
追加 agent 时,也可以直接运行:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
alb add-agent
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
该命令会在现有配置上:
|
|
183
|
+
|
|
184
|
+
- 追加一个 `agents.list[]` 项
|
|
185
|
+
- 可选绑定已有 agent-life `accountId`
|
|
186
|
+
- 可选创建新的 agent-life account 并立刻绑定
|
|
187
|
+
- 兼容旧的顶层 agent-life 配置写法,并在保存时自动迁移成 `channels["agent-life"].accounts["dlb-default"]`
|
|
188
|
+
- 写入前自动备份原配置文件
|
|
189
|
+
|
|
190
|
+
如果现有 account 已被其他 agent 占用,交互流程会提示该 account 当前绑定到哪个 agent,并允许直接创建新的 account 后继续绑定。
|
|
191
|
+
|
|
192
|
+
也可以显式指定:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
alb add-agent codex-assistant codex-cli/gpt-5.4 codex-bot /workspace/app
|
|
196
|
+
alb add-agent --account-id office-bot
|
|
197
|
+
alb add-agent --create-account office-bot --account-gateway-url https://agent-life.example.com --account-bot-token bot_xxx
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
例如:
|
|
201
|
+
|
|
202
|
+
- 一个 bot 绑定到 Codex
|
|
203
|
+
- 一个 bot 绑定到 Claude Code
|
|
204
|
+
- 不同工作区使用不同 `workspace`
|
|
205
|
+
- 通过预置 `cliSessionId` 复用既有 CLI 会话
|
|
206
|
+
|
|
207
|
+
详见 [config.multi-agent.example.json](./config.multi-agent.example.json)。
|
|
208
|
+
|
|
209
|
+
### 路由规则
|
|
210
|
+
|
|
211
|
+
`bindings[].match` 中声明的条件是 AND 关系,全部满足才会命中。多个 binding 同时命中时,按具体程度评分选择最具体的一条;同分时使用配置文件中靠前的 binding。
|
|
212
|
+
|
|
213
|
+
匹配字段包括:
|
|
214
|
+
|
|
215
|
+
- `channel`:通道 ID,当前内置 `agent-life`,也支持 `*` 兜底
|
|
216
|
+
- `accountId`:agent-life account ID
|
|
217
|
+
- `peer.kind`:`private`、`group` 或 `multi_bot`
|
|
218
|
+
- `peer.id`:会话或群 ID
|
|
219
|
+
- `userId`:发送者 ID
|
|
220
|
+
|
|
221
|
+
如果没有 binding 命中,会落到默认 agent;默认 agent 为第一个 `default: true` 的 agent,没有显式默认值时使用 `agents.list[0]`。
|
|
222
|
+
|
|
223
|
+
### 环境变量覆盖
|
|
224
|
+
|
|
225
|
+
环境变量可覆盖配置文件中的对应值:
|
|
226
|
+
|
|
227
|
+
| 环境变量 | 覆盖字段 | 说明 |
|
|
228
|
+
|---|---|---|
|
|
229
|
+
| `AGENT_LIFE_BRIDGE_CONFIG` | 配置文件路径 | 指定要加载的 `config.json` |
|
|
230
|
+
| `AGENT_LIFE_BRIDGE_GATEWAY_URL` | `channels["agent-life"].accounts["dlb-default"].gateway_url` 或旧格式 `channels["agent-life"].gateway_url` | 优先覆盖默认 agent-life 账号;旧配置会覆盖顶层网关地址 |
|
|
231
|
+
| `AGENT_LIFE_BRIDGE_BOT_TOKEN` | `channels["agent-life"].accounts["dlb-default"].bot_token` 或旧格式 `channels["agent-life"].bot_token` | 优先覆盖默认 agent-life 账号;旧配置会覆盖顶层 Bot Token |
|
|
232
|
+
| `AGENT_LIFE_BRIDGE_LOG_LEVEL` | `logging.level` | 日志级别 |
|
|
233
|
+
| `AGENT_LIFE_BRIDGE_TEMP_DIR` | `files.temp_dir` | 临时文件目录 |
|
|
234
|
+
| `ANTHROPIC_API_KEY` | 环境变量 | 供诊断或后续 API 模式使用 |
|
|
235
|
+
| `OPENAI_API_KEY` | 环境变量 | 供诊断或后续 API 模式使用 |
|
|
236
|
+
|
|
237
|
+
## 模型与运行模式
|
|
238
|
+
|
|
239
|
+
当前消息执行链路仅支持 CLI 模式模型,也就是:
|
|
240
|
+
|
|
241
|
+
- `claude-cli/...`
|
|
242
|
+
- `codex-cli/...`
|
|
243
|
+
- `qwen-cli/...`
|
|
244
|
+
|
|
245
|
+
配置层已经允许 `anthropic/...`、`openai/...` 这类 API provider 引用,但当前主运行时尚未实现 API 模式;若把它们设为主模型,程序会返回“当前仅支持 CLI 模式模型”。
|
|
246
|
+
|
|
247
|
+
内置 CLI backend 默认行为:
|
|
248
|
+
|
|
249
|
+
- `claude-cli`:执行 `claude`,总是带 CLI session,按串行模式处理同一会话
|
|
250
|
+
- `codex-cli`:默认使用 Codex app-server 运行模式,session 复用策略为 `existing`
|
|
251
|
+
- `qwen-cli`:执行 `qwen -p --output-format json`,支持 `--resume {sessionId}`
|
|
252
|
+
|
|
253
|
+
可以在 `agents.defaults.cliBackends` 或单个 agent 的 `cliBackends` 中覆盖 command、参数、环境变量、session 参数、图片参数、输出格式和 watchdog 超时。
|
|
254
|
+
|
|
255
|
+
## 消息行为
|
|
256
|
+
|
|
257
|
+
收到 agent-life 消息后,bridge 会把消息封装成统一的 session envelope:
|
|
258
|
+
|
|
259
|
+
- 文本使用 `message.text` 或 `message.caption`
|
|
260
|
+
- 文档和图片会先下载到 `~/.agent-life-bridge/data/files/`
|
|
261
|
+
- 图片在支持 `imageArg` 的 backend 中会作为独立图片参数传入;其他附件会以 `Attachment: <localPath>` 形式追加到 prompt
|
|
262
|
+
- 群聊发送者信息会作为不可信 metadata 附加到 prompt,便于本地 CLI 区分用户
|
|
263
|
+
|
|
264
|
+
回复时会优先发送草稿进度并最终编辑成完整回复。如果最终文本包含 `/tmp/...` 或 `file:///tmp/...` 文件路径,bridge 会尝试把这些文件作为图片或文档回传;用户请求中带有“发给我/发送给我”等回传意图时,也会从请求文本中提取这类临时文件路径。文本类文件会补 UTF-8 BOM 以便客户端打开。
|
|
265
|
+
|
|
266
|
+
### 消息内指令
|
|
267
|
+
|
|
268
|
+
bridge 会拦截以下 slash 指令:
|
|
269
|
+
|
|
270
|
+
- `/br:status`:返回 bridge 运行状态、当前 agent、provider 和 session ID
|
|
271
|
+
- `/br:new`:为当前会话创建新的 CLI 会话分支;`/new` 是兼容别名
|
|
272
|
+
- `/br:stop`:返回停止请求提示;当前后端暂不支持强制终止正在执行的 CLI turn
|
|
273
|
+
- `/cli:commit ...`:以 CLI passthrough 模式转发为 `/commit ...`,仅保留安全的选项参数
|
|
274
|
+
|
|
275
|
+
其他消息会直接交给当前 agent 的 CLI backend。
|
|
276
|
+
|
|
277
|
+
## 数据目录
|
|
278
|
+
|
|
279
|
+
运行时数据默认保存在 `~/.agent-life-bridge/`:
|
|
280
|
+
|
|
281
|
+
```text
|
|
282
|
+
~/.agent-life-bridge/
|
|
283
|
+
├── config.json
|
|
284
|
+
├── agent-life-bridge.pid
|
|
285
|
+
├── runtime.json
|
|
286
|
+
├── agents/
|
|
287
|
+
│ └── <agentId>/
|
|
288
|
+
│ └── cli-sessions.json
|
|
289
|
+
├── data/
|
|
290
|
+
│ ├── files/
|
|
291
|
+
│ ├── logs/
|
|
292
|
+
│ └── sessions/
|
|
293
|
+
│ ├── agent-life-session-map.json
|
|
294
|
+
│ └── cli-sessions.json
|
|
295
|
+
└── skills/
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
其中:
|
|
299
|
+
|
|
300
|
+
- `config.json`:全局配置文件
|
|
301
|
+
- `agent-life-bridge.pid`:守护进程 PID 文件
|
|
302
|
+
- `runtime.json`:当前运行实例的启动参数和工作目录,用于自更新后的重启
|
|
303
|
+
- `agents/<agentId>/cli-sessions.json`:每个 agent 的 CLI 会话映射
|
|
304
|
+
- `data/sessions/agent-life-session-map.json`:agent-life 消息与本地 agent/session 的映射
|
|
305
|
+
- `data/sessions/cli-sessions.json`:旧版 CLI 会话文件;首次启动会复制到默认 agent 的状态目录作为迁移备份
|
|
306
|
+
- `data/files/`:从 agent-life 下载的附件
|
|
307
|
+
- `data/logs/`:按天写入的日志文件,文件名形如 `agent-life-bridge-YYYY-MM-DD.log`
|
|
308
|
+
- `skills/`:自定义技能目录
|
|
309
|
+
|
|
310
|
+
## 项目结构
|
|
311
|
+
|
|
312
|
+
```text
|
|
313
|
+
agent-life-bridge/
|
|
314
|
+
├── bin/ # CLI 入口
|
|
315
|
+
├── scripts/ # 构建脚本
|
|
316
|
+
├── src/
|
|
317
|
+
│ ├── cli/ # start / stop / onboard / doctor 等命令
|
|
318
|
+
│ ├── agents/ # Agent 解析、路由、CLI 执行
|
|
319
|
+
│ ├── channels/ # Channel 插件(如 agent-life)
|
|
320
|
+
│ ├── commands/ # 消息内 slash 指令路由
|
|
321
|
+
│ ├── config/ # 配置加载、校验、默认值
|
|
322
|
+
│ ├── providers/ # 预留 provider 扩展目录
|
|
323
|
+
│ ├── types/ # 类型定义
|
|
324
|
+
│ └── logger.ts # 日志
|
|
325
|
+
├── extensions/ # workspace 扩展点
|
|
326
|
+
├── packages/ # workspace 包目录
|
|
327
|
+
├── skills/ # 本地技能目录
|
|
328
|
+
├── config.example.json
|
|
329
|
+
├── config.multi-agent.example.json
|
|
330
|
+
└── package.json
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## 开发
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
pnpm install
|
|
337
|
+
pnpm build
|
|
338
|
+
pnpm typecheck
|
|
339
|
+
pnpm start
|
|
340
|
+
pnpm dev
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
环境要求:
|
|
344
|
+
|
|
345
|
+
- Node.js >= 22.0.0
|
|
346
|
+
- pnpm >= 9.0.0
|
|
347
|
+
|
|
348
|
+
## 许可证
|
|
349
|
+
|
|
350
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// CLI entry point
|
|
4
|
+
// Used by: npm install -g agent-life-bridge / npx alb
|
|
5
|
+
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { dirname, resolve } from 'node:path'
|
|
8
|
+
import { pathToFileURL } from 'node:url'
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
|
|
12
|
+
// Import the compiled entry point
|
|
13
|
+
await import(pathToFileURL(resolve(__dirname, '..', 'dist', 'entry.js')).href)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"channels": {
|
|
3
|
+
"agent-life": {
|
|
4
|
+
"poll_timeout": 30,
|
|
5
|
+
"retry_interval": 5,
|
|
6
|
+
"max_retry_interval": 60,
|
|
7
|
+
"accounts": {
|
|
8
|
+
"dlb-default": {
|
|
9
|
+
"gateway_url": "https://www.m2a.chat/gateway/api",
|
|
10
|
+
"bot_token": "bot_xxx",
|
|
11
|
+
"poll_timeout": 30,
|
|
12
|
+
"retry_interval": 5,
|
|
13
|
+
"max_retry_interval": 60
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"agents": {
|
|
19
|
+
"defaults": {
|
|
20
|
+
"model": {
|
|
21
|
+
"primary": "claude-cli/opus-4.6",
|
|
22
|
+
"fallbacks": [
|
|
23
|
+
"codex-cli/gpt-5.4"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"cliBackends": {
|
|
27
|
+
"claude-cli": {
|
|
28
|
+
"command": "claude",
|
|
29
|
+
"sessionMode": "always",
|
|
30
|
+
"serialize": true,
|
|
31
|
+
"reliability": {
|
|
32
|
+
"watchdog": {
|
|
33
|
+
"fresh": {
|
|
34
|
+
"noOutputTimeoutMs": 600000
|
|
35
|
+
},
|
|
36
|
+
"resume": {
|
|
37
|
+
"noOutputTimeoutMs": 600000
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"codex-cli": {
|
|
43
|
+
"command": "codex",
|
|
44
|
+
"runtimeMode": "app-server",
|
|
45
|
+
"appServer": {
|
|
46
|
+
"listen": "stdio://",
|
|
47
|
+
"startupTimeoutMs": 15000,
|
|
48
|
+
"requestTimeoutMs": 30000,
|
|
49
|
+
"serviceName": "agent-life-bridge",
|
|
50
|
+
"experimentalApi": false,
|
|
51
|
+
"approvalPolicy": "never",
|
|
52
|
+
"sandbox": "danger-full-access",
|
|
53
|
+
"persistExtendedHistory": false,
|
|
54
|
+
"experimentalRawEvents": false
|
|
55
|
+
},
|
|
56
|
+
"sessionMode": "existing",
|
|
57
|
+
"serialize": true
|
|
58
|
+
},
|
|
59
|
+
"qwen-cli": {
|
|
60
|
+
"command": "qwen",
|
|
61
|
+
"args": [
|
|
62
|
+
"-p",
|
|
63
|
+
"--output-format",
|
|
64
|
+
"json"
|
|
65
|
+
],
|
|
66
|
+
"output": "json",
|
|
67
|
+
"resumeOutput": "json",
|
|
68
|
+
"runtimeMode": "exec",
|
|
69
|
+
"input": "arg",
|
|
70
|
+
"modelArg": "--model",
|
|
71
|
+
"sessionArg": "--session-id",
|
|
72
|
+
"resumeArgs": [
|
|
73
|
+
"-p",
|
|
74
|
+
"--resume",
|
|
75
|
+
"{sessionId}",
|
|
76
|
+
"--output-format",
|
|
77
|
+
"json"
|
|
78
|
+
],
|
|
79
|
+
"sessionIdFields": [
|
|
80
|
+
"session_id",
|
|
81
|
+
"sessionId"
|
|
82
|
+
],
|
|
83
|
+
"sessionMode": "always",
|
|
84
|
+
"serialize": true,
|
|
85
|
+
"reliability": {
|
|
86
|
+
"watchdog": {
|
|
87
|
+
"fresh": {
|
|
88
|
+
"noOutputTimeoutRatio": 0.8,
|
|
89
|
+
"minMs": 180000,
|
|
90
|
+
"maxMs": 600000
|
|
91
|
+
},
|
|
92
|
+
"resume": {
|
|
93
|
+
"noOutputTimeoutRatio": 0.3,
|
|
94
|
+
"minMs": 60000,
|
|
95
|
+
"maxMs": 180000
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"list": [
|
|
103
|
+
{
|
|
104
|
+
"id": "default",
|
|
105
|
+
"default": true
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"bindings": [
|
|
110
|
+
{
|
|
111
|
+
"agentId": "default",
|
|
112
|
+
"match": {
|
|
113
|
+
"channel": "*"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"skills": {
|
|
118
|
+
"dirs": [
|
|
119
|
+
"./skills"
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
"files": {
|
|
123
|
+
"temp_dir": "/tmp/agent-life-bridge/files",
|
|
124
|
+
"max_file_size": 52428800
|
|
125
|
+
},
|
|
126
|
+
"logging": {
|
|
127
|
+
"level": "info",
|
|
128
|
+
"pretty": false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"channels": {
|
|
3
|
+
"agent-life": {
|
|
4
|
+
"accounts": {
|
|
5
|
+
"codex-bot": {
|
|
6
|
+
"gateway_url": "https://www.m2a.chat/gateway/api",
|
|
7
|
+
"bot_token": "bot_codex_xxx",
|
|
8
|
+
"poll_timeout": 30,
|
|
9
|
+
"retry_interval": 5,
|
|
10
|
+
"max_retry_interval": 60
|
|
11
|
+
},
|
|
12
|
+
"claude-bot": {
|
|
13
|
+
"gateway_url": "https://www.m2a.chat/gateway/api",
|
|
14
|
+
"bot_token": "bot_claude_xxx",
|
|
15
|
+
"poll_timeout": 30,
|
|
16
|
+
"retry_interval": 5,
|
|
17
|
+
"max_retry_interval": 60
|
|
18
|
+
},
|
|
19
|
+
"qwen-bot": {
|
|
20
|
+
"gateway_url": "https://www.m2a.chat/gateway/api",
|
|
21
|
+
"bot_token": "bot_qwen_xxx",
|
|
22
|
+
"poll_timeout": 30,
|
|
23
|
+
"retry_interval": 5,
|
|
24
|
+
"max_retry_interval": 60
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"agents": {
|
|
30
|
+
"defaults": {
|
|
31
|
+
"model": {
|
|
32
|
+
"primary": "claude-cli/opus-4.6"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"list": [
|
|
36
|
+
{
|
|
37
|
+
"id": "codex-assistant",
|
|
38
|
+
"default": true,
|
|
39
|
+
"name": "Codex 助理",
|
|
40
|
+
"model": "codex-cli/gpt-5.4",
|
|
41
|
+
"workspace": "/workspace/app",
|
|
42
|
+
"cliSessionId": "thread_xxxxxxxxxxxxxxxx"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "claudecode-assistant",
|
|
46
|
+
"name": "Claude Code 助理",
|
|
47
|
+
"model": "claude-cli/opus-4.6",
|
|
48
|
+
"workspace": "/workspace/app",
|
|
49
|
+
"cliSessionId": "01970df8-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "qwen-assistant",
|
|
53
|
+
"name": "Qwen 助理",
|
|
54
|
+
"model": "qwen-cli/qwen3-coder-plus",
|
|
55
|
+
"workspace": "/workspace/app"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"bindings": [
|
|
60
|
+
{
|
|
61
|
+
"agentId": "codex-assistant",
|
|
62
|
+
"comment": "codex bot",
|
|
63
|
+
"match": {
|
|
64
|
+
"channel": "agent-life",
|
|
65
|
+
"accountId": "codex-bot"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"agentId": "claudecode-assistant",
|
|
70
|
+
"comment": "claude bot",
|
|
71
|
+
"match": {
|
|
72
|
+
"channel": "agent-life",
|
|
73
|
+
"accountId": "claude-bot"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"agentId": "qwen-assistant",
|
|
78
|
+
"comment": "qwen bot",
|
|
79
|
+
"match": {
|
|
80
|
+
"channel": "agent-life",
|
|
81
|
+
"accountId": "qwen-bot"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"logging": {
|
|
86
|
+
"level": "info"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface AgentLifeSessionMapEntry {
|
|
2
|
+
agentLifeMsgId: number;
|
|
3
|
+
sessionId: string;
|
|
4
|
+
agentId: string;
|
|
5
|
+
applyId: string;
|
|
6
|
+
userId: string;
|
|
7
|
+
updatedAt: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class AgentLifeSessionMapStore {
|
|
10
|
+
private readonly filePath;
|
|
11
|
+
private cache;
|
|
12
|
+
private queue;
|
|
13
|
+
constructor(filePath?: string);
|
|
14
|
+
upsert(entry: Omit<AgentLifeSessionMapEntry, 'updatedAt'>): Promise<AgentLifeSessionMapEntry>;
|
|
15
|
+
get(agentLifeMsgId: number, agentId: string): Promise<AgentLifeSessionMapEntry | undefined>;
|
|
16
|
+
list(): Promise<Record<string, AgentLifeSessionMapEntry>>;
|
|
17
|
+
private mutate;
|
|
18
|
+
private load;
|
|
19
|
+
private save;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=agent-life-session-map-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-life-session-map-store.d.ts","sourceRoot":"","sources":["../../src/agents/agent-life-session-map-store.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAeD,qBAAa,wBAAwB;IAIvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAHrC,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,KAAK,CAAoB;gBAEJ,QAAQ,SAAmC;IAElE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,wBAAwB,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAW7F,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC;IAM3F,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;YAOjD,MAAM;YAkBN,IAAI;YAoBJ,IAAI;CAMnB"}
|