agent-loft 0.2.19 → 0.2.21
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.md +52 -29
- package/package.json +1 -1
- package/src/channels/feishu/attach.ts +48 -0
- package/src/channels/feishu/bot.ts +308 -66
- package/src/channels/feishu/cards.ts +97 -0
- package/src/channels/feishu/client.ts +22 -0
- package/src/channels/feishu/envelope.ts +9 -1
- package/src/channels/web/api.ts +24 -0
- package/src/core/command-catalog.ts +13 -1
- package/src/core/commands.ts +2 -1
- package/src/core/hub.ts +17 -5
- package/web/app.js +180 -43
- package/web/style.css +27 -0
package/README.md
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# agent-loft
|
|
4
4
|
|
|
5
|
-
**多个 agent 的工作台。** 把本机的 **Claude Code**、**Codex
|
|
5
|
+
**多个 agent 的工作台。** 把本机的 **Claude Code**、**Codex**(以后还有更多)放到同一张台面上统一管理,在网页、飞书和 Mac 桌面端随时驱动它们,进展推送到飞书、iPhone 和 Mac。
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
> 📖 完整的产品介绍和使用指南见 **[docs/产品介绍.md](docs/产品介绍.md)**:六大核心亮点、快速上手、使用指南、命令速查、常见问题和故障排查。
|
|
6
10
|
|
|
7
11
|
由 `feishu-claude-bridge` 和 `feishu-codex-bridge` 合并而来:一个进程、一份配置,飞书和网页共用同一套会话、审批和模型切换。
|
|
8
12
|
|
|
@@ -10,56 +14,71 @@
|
|
|
10
14
|
|
|
11
15
|
这里的 Loft 不是阁楼,是**工作台**:agent 是干活的人,agent-loft 是它们共用的那张台面。
|
|
12
16
|
|
|
13
|
-
- **多 agent,一张台面**:Claude Code、Codex
|
|
17
|
+
- **多 agent,一张台面**:Claude Code、Codex 各有所长,不该各开一个窗口、各记一套会话。它们在这里共用工作目录、会话列表、审批和模型切换,同一条会话里随时换人接手,各自的上下文都保留着。
|
|
14
18
|
- **统一管理,而不是再造一个 agent**:agent-loft 不自己做模型、不改 agent 的行为,只负责把官方的 agent 接进来、管起来。新的 agent 接一个驱动就能上台。
|
|
15
19
|
- **人在哪,台面就在哪**:电脑上用网页,手机上用 PWA 或飞书。同一个会话、同一个授权请求,在哪个渠道处理都行,其他地方自动同步。
|
|
16
20
|
- **跑在你自己的机器上**:代码、凭据和会话都留在本机,agent-loft 只是一层安全的遥控。
|
|
17
21
|
|
|
18
22
|
Logo 就是这个意思:一大两小几颗星(多个 agent)站在同一张台面上。
|
|
19
23
|
|
|
24
|
+
## 六大核心亮点
|
|
25
|
+
|
|
26
|
+
| | 亮点 | 说明 |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| 🤖 | **同时管理多个 agent** | Claude Code(官方 Claude Agent SDK)和 Codex(官方 `codex app-server`)共用会话列表、工作目录和授权。同一条会话里用 `/claude`、`/codex`、`/agent` 切换,每个 agent 保留自己的上下文 |
|
|
29
|
+
| 🔀 | **飞书机器人灵活绑定 agent** | 机器人数量不限,每个机器人可以只对应一个 agent,也可以对应多个(在会话里切换)。网页上扫码就能创建,改完立即生效;由服务端主动连向飞书,家里没有公网 IP 也能在外面用 |
|
|
30
|
+
| 🔗 | **飞书续接 Web 任务** | 飞书里发 `/web` 接上一条 Web 会话,或者在网页侧栏右键会话选「在飞书继续…」。之后回复、授权、提问都发到飞书,发 `/detach` 回来 |
|
|
31
|
+
| 🖥️ | **完整的 Web 工作台** | 流式回复、附件、语音输入、模型和思考程度切换、上下文占用、从任意回复分叉、按项目分组的侧栏、全部设置,电脑和手机(PWA)都能用 |
|
|
32
|
+
| 🔔 | **飞书 + 苹果系统推送** | 除了飞书消息,Web 会话还能推送到 iPhone、iPad 锁屏和 Mac 通知中心(标准 Web Push,不需要开发者账号),图标上显示未读数 |
|
|
33
|
+
| 🗂️ | **桌面端同时接入多台设备** | macOS 桌面端同时连多台服务器,菜单栏一眼看完各台的额度、待处理和运行中的会话 |
|
|
34
|
+
|
|
20
35
|
## 能做什么
|
|
21
36
|
|
|
22
|
-
- **多个 agent**:Claude Code
|
|
23
|
-
- **多个飞书机器人**:一个机器人对应一个 agent,或者一个机器人能切换多个 agent,都在设置页里勾选。机器人能在 Web
|
|
24
|
-
-
|
|
25
|
-
-
|
|
37
|
+
- **多个 agent**:Claude Code、Codex。两个 agent 共用工作目录,能看到对方改过的文件,但聊天记录各自独立。
|
|
38
|
+
- **多个飞书机器人**:一个机器人对应一个 agent,或者一个机器人能切换多个 agent,都在设置页里勾选。机器人能在 Web 上扫码创建(Lark 国际版用「手动添加」),改完配置立即生效,不用重启。
|
|
39
|
+
- 群聊里要 @ 机器人;不同话题是各自独立的会话;支持文字、富文本、图片、文件、语音和视频。
|
|
40
|
+
- 思考和工具调用显示在实时刷新的过程卡片里,最终回复是普通消息。
|
|
41
|
+
- 陌生人发消息会拿到配对码,在设置页批准后才能使用;每个机器人单独授权。
|
|
42
|
+
- **Web 工作台**:电脑和手机都能用,界面参照 ChatGPT。
|
|
43
|
+
- 回复逐字显示,markdown 和代码高亮,思考过程和工具调用可以折叠;每条回复下方显示用时、费用和完成时间,可以复制,也可以「从这里分叉」成一条新会话(原会话不动)。
|
|
26
44
|
- 授权卡、提问卡直接在页面上点。
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- 侧栏底部显示 Claude / Codex 的订阅额度(5 小时 / 7 天窗口,悬停看重置时间);空闲时按设置的间隔自动刷新(默认 10 分钟),跑完一轮后也会刷新。
|
|
39
|
-
- **跨渠道同步**:飞书里的会话在 Web 上也能打开,接着聊。同一个授权请求同时出现在飞书卡片和网页上,哪边先点算哪边,另一边自动变成「已处理」。
|
|
40
|
-
- **设置都在网页上**:工作目录、各 agent 的默认模型和思考程度、Codex 沙箱、飞书机器人、配对批准、Claude 的「总是允许」名单、登录设备管理。
|
|
45
|
+
- 附件:粘贴、拖拽,手机上可以拍照;可以预览和下载。附件存在服务端的专用上传目录里,开新会话或切换目录时清理。
|
|
46
|
+
- 语音输入:用浏览器自带的语音识别(Chrome / Edge / Safari,需要 HTTPS)。
|
|
47
|
+
- 新对话页:点大标题里的目录名,在目录树里逐层选工作目录;输入框上方选 agent,右下角选模型和思考程度。会话进行中用 `/agent` 切换 agent,模型和思考程度随时可换。
|
|
48
|
+
- 输入框里的小圆环显示上下文窗口占用;模型按钮显示实际的模型和思考程度(如「Opus 5.5 high」)。
|
|
49
|
+
- 侧栏分「最近」和「项目」两组:Web 会话按工作目录归类,飞书机器人各占一行(右键看历史会话);分叉出来的会话用虚线挂在原会话下面;会话标题由 agent 自动生成,双击重命名。
|
|
50
|
+
- 侧栏底部显示 Claude / Codex 的订阅额度(5 小时 / 7 天窗口,以及多久后重置)。
|
|
51
|
+
- 「添加到主屏幕」后当 App 用;切到后台再回来会自动重连,补上错过的内容。
|
|
52
|
+
- **系统通知**:Web 会话回复完成、出错、需要授权或提问时推送到手机 / 电脑(设置 → 通知 → 「在此设备开启通知」,可以发测试通知)。飞书会话由飞书提醒,正在前台看着的会话不推。需要 HTTPS 访问,iPhone 需 iOS 16.4+ 且从主屏幕图标打开。
|
|
53
|
+
- **跨渠道同步**:飞书里的会话在 Web 上也能打开接着聊。同一个授权请求同时出现在飞书卡片和网页上,哪边先点算哪边,另一边自动变成「已处理」。
|
|
54
|
+
- **授权**:允许一次 / 本会话全部允许 / 总是允许(按钮上写明放行范围)/ 拒绝。`/autoedit on` 让 Claude Code 在这条会话里改工作目录内的文件不再询问(Bash 仍询问)。超时一律按拒绝处理。
|
|
55
|
+
- **设置都在网页上**:工作目录、会话闲置过期时间、局域网访问和端口、各 agent 的默认模型和思考程度、可选模型、Codex 沙箱、飞书机器人、配对批准、Claude 的「总是允许」名单、通知设备、登录设备管理、服务端升级。
|
|
41
56
|
|
|
42
57
|
## 安装
|
|
43
58
|
|
|
59
|
+
需要 Linux 或 macOS,Node.js 22 及以上。
|
|
60
|
+
|
|
44
61
|
```bash
|
|
45
62
|
npm install -g agent-loft # 或者在仓库里 npm install 后用 node bin/cli.js
|
|
46
63
|
agent-loft start # 前台运行,日志里会打印首次登录用的设置码
|
|
47
|
-
agent-loft service install # 注册为开机自启服务(systemd / launchd
|
|
64
|
+
agent-loft service install # 注册为开机自启服务(systemd / launchd),日志在 ~/.agent-loft/logs/
|
|
48
65
|
```
|
|
49
66
|
|
|
50
|
-
打开 `http://<机器 IP>:8800
|
|
67
|
+
打开 `http://<机器 IP>:8800`(本机用 `http://localhost:8800`):第一次需要输入日志里的设置码,然后设一个登录密码。设置码找不到的话,直接运行 `agent-loft passwd` 设密码。
|
|
68
|
+
|
|
69
|
+
到 设置 → Agent 检查 Claude Code、Codex 装好、登录了没有:Claude Code 随 agent-loft 一起安装,只需在服务端终端里登录;Codex 可以点「直接在服务器上安装」。设置 → 通用 里能看到服务端版本,有新版本时点「升级」即可(`npm install -g agent-loft@latest`,以系统服务运行时会自动重启,打开的页面会自动刷新)。
|
|
51
70
|
|
|
52
|
-
|
|
71
|
+
在外面只用飞书的话不需要任何网络配置(机器人由服务端主动连向飞书)。在外面用网页、系统推送和语音输入需要 HTTPS,见下面的「安全」和[产品介绍里的「从外网访问」](docs/产品介绍.md#从外网访问)。
|
|
53
72
|
|
|
54
73
|
### macOS 桌面端
|
|
55
74
|
|
|
56
75
|
到 [Releases](https://github.com/qijieinnet/agent-loft/releases) 下载 `desktop-v*` 里对应芯片的 DMG(Apple 芯片选 arm64,Intel 选 x64),拖进「应用程序」。还没有 Apple 开发者签名,第一次打开如果提示无法验证,到 系统设置 → 隐私与安全性 里点「仍要打开」。
|
|
57
76
|
|
|
58
|
-
-
|
|
59
|
-
- 菜单栏图标:悬停看各服务器的 Claude / Codex
|
|
77
|
+
- 同时连多台服务器,顶部标签栏切换、拖动排序;每台各自登录,切走了也在后台连着,回复完成、需要授权时弹系统通知,程序坞图标显示未读数。
|
|
78
|
+
- 菜单栏图标:悬停看各服务器的 Claude / Codex 额度、待处理和运行中的会话;双击打开主窗口。同一个账号的额度只查一次。
|
|
60
79
|
- 支持前面挡着 Cloudflare Access 的服务器:验证页直接在窗口里完成,过期会提醒;也可以配 Service Token 免验证。
|
|
61
|
-
- 首次打开可以选「这台 Mac 也作为服务端」:一键在终端里安装 agent-loft(没有 Node.js 会先用 Homebrew
|
|
62
|
-
-
|
|
80
|
+
- 首次打开可以选「这台 Mac 也作为服务端」:一键在终端里安装 agent-loft(没有 Node.js 会先用 Homebrew 装)并开机自启,装好自动连上。可选开启「本机有会话在跑时不休眠」。
|
|
81
|
+
- 设置(`⌘,`)的服务器列表里展开某台服务器,能看它的 Agent 环境和版本,有新版本时点「升级服务端」;桌面端自己的更新在同一页的「更新」里点「更新并重启」。
|
|
63
82
|
|
|
64
83
|
桌面端只是个壳(代码在 `desktop/`),服务端照常用 npm 安装和升级。
|
|
65
84
|
|
|
@@ -85,7 +104,9 @@ agent-loft service install
|
|
|
85
104
|
| `agent-loft service install \| uninstall \| restart \| status` | 开机自启 |
|
|
86
105
|
| `agent-loft doctor` | 体检 |
|
|
87
106
|
|
|
88
|
-
|
|
107
|
+
`aloft` 是 `agent-loft` 的简写。
|
|
108
|
+
|
|
109
|
+
聊天里的命令(飞书和 Web 通用;Web 输入框打 `/` 会弹出命令补全,飞书里打 `/` 会弹出机器人的指令面板(agent-loft 自动注册,约 5~8 分钟生效;只发一个 `/` 也会回一张可点击的命令卡片),顺序和显示项在 设置 → 命令 里调整):`/new` `/fork` `/sessions [all]` `/resume <id>` `/model [名称]` `/effort <程度>` `/agent [名称]`(或者直接发 `/claude`、`/codex`)`/stop` `/cd <目录>` `/pwd` `/status` `/autoedit [on|off]` `/approve <id> allow-once|allow-always|allow-session|deny` `/help`;只在飞书里有的:`/web` `/detach`
|
|
89
110
|
|
|
90
111
|
自定义命令:设置 → 命令 → 「添加命令」,填命令名、说明和一段提示词,之后发 `/命令名 参数` 就会把这段提示词发给 agent(提示词里的 `$ARGUMENTS` 换成参数,没写就把参数接在后面)。按 agent 分开存,可以编辑、删除,也会同步到飞书指令面板。
|
|
91
112
|
|
|
@@ -95,7 +116,7 @@ agent-loft service install
|
|
|
95
116
|
|
|
96
117
|
- Web 登录密码用 scrypt 哈希保存。首次设置必须用启动日志里的一次性设置码,防止「谁先打开页面谁就成了主人」。
|
|
97
118
|
- 登录态通过 HttpOnly cookie 保存,服务端只存它的哈希。同一 IP 连续输错 5 次锁 15 分钟。所有写请求和 WebSocket 都校验 Origin。页面开启了严格的 CSP。
|
|
98
|
-
- 暴露到公网时,只通过 HTTPS 反向代理(比如 Lucky
|
|
119
|
+
- 暴露到公网时,只通过 HTTPS 反向代理(比如 Lucky)或 Cloudflare Tunnel 访问,并把不在本机的反代 IP 加进 `web.trustedProxies`(只有从这些地址来的连接才采信 `X-Forwarded-*`)。不要直接把 8800 端口映射出去。局域网访问可以在 设置 → 通用 → 网络访问 里关掉。
|
|
99
120
|
- 审批超时一律按拒绝处理。Codex 默认沙箱为 `workspace-write`,不会因为 `~/.codex/config.toml` 里配了 `never` 或 `danger-full-access` 就绕过审批。
|
|
100
121
|
- agent 子进程拿不到本服务自己的凭据(`FEISHU_*`、`AGENT_LOFT_*` 等环境变量会被剔除)。
|
|
101
122
|
|
|
@@ -116,6 +137,8 @@ src/
|
|
|
116
137
|
feishu/ 飞书:一个 FeishuBot 对应一个机器人,支持多个
|
|
117
138
|
web/ HTTP + WebSocket + 鉴权
|
|
118
139
|
web/ 前端(原生 JS,不需要构建)
|
|
140
|
+
desktop/ macOS 桌面端(Electron 外壳)
|
|
141
|
+
docs/ 产品介绍和截图
|
|
119
142
|
```
|
|
120
143
|
|
|
121
144
|
### 接入新的消息渠道(比如 Telegram)
|
package/package.json
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// 飞书聊天「挂到」Web 会话上:挂上之后,这个飞书聊天说的话、发的命令都送进那条 Web 会话,
|
|
2
|
+
// 那条会话的回复、授权、提问也画到这个飞书聊天里。发 /detach 回到聊天自己的会话。
|
|
3
|
+
//
|
|
4
|
+
// 只是一层路由,会话本身(agent 进程、历史、审批)一点不动。按飞书会话键记:
|
|
5
|
+
// feishu:<机器人id>:<chatId>[:<话题id>] → web:<…>
|
|
6
|
+
// 一条 Web 会话可以同时被好几个飞书聊天挂着。要落盘:重启之后飞书那边还接着它聊。
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { dataDir, readJson, writeJsonAtomic } from "../../home.js";
|
|
9
|
+
|
|
10
|
+
type Shape = Record<string, { target: string; at: number }>;
|
|
11
|
+
|
|
12
|
+
const storePath = path.join(dataDir, "feishu-attach.json");
|
|
13
|
+
let cache: Shape | null = null;
|
|
14
|
+
|
|
15
|
+
function load(): Shape {
|
|
16
|
+
cache ??= readJson<Shape>(storePath, {});
|
|
17
|
+
return cache;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function save(): void {
|
|
21
|
+
writeJsonAtomic(storePath, cache ?? {});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 这个飞书会话挂着的 Web 会话键 */
|
|
25
|
+
export function attachedTarget(feishuKey: string): string | undefined {
|
|
26
|
+
return load()[feishuKey]?.target;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function attach(feishuKey: string, target: string): void {
|
|
30
|
+
load()[feishuKey] = { target, at: Date.now() };
|
|
31
|
+
save();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 返回原来挂着的目标(没挂着返回 undefined) */
|
|
35
|
+
export function detach(feishuKey: string): string | undefined {
|
|
36
|
+
const prev = load()[feishuKey]?.target;
|
|
37
|
+
if (prev === undefined) return undefined;
|
|
38
|
+
delete load()[feishuKey];
|
|
39
|
+
save();
|
|
40
|
+
return prev;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 挂在 target 上的所有飞书会话键(可选只要某个前缀的,也就是某个机器人的) */
|
|
44
|
+
export function watchersOf(target: string, prefix = ""): string[] {
|
|
45
|
+
return Object.entries(load())
|
|
46
|
+
.filter(([k, v]) => v.target === target && k.startsWith(prefix))
|
|
47
|
+
.map(([k]) => k);
|
|
48
|
+
}
|