当前阶段支持从飞书话题真实调度 Codex、Claude Code 或 DimAgent,并用同一张卡片实时展示当前动作、工具轨迹、耗时和上下文;成功后答案回到卡片正文,任务也可由发起人随时停止。同一话题会续接 CLI 上下文,会话和恢复指针都可跨进程重启恢复。一个进程可以按注册表启动多台职责不同的 bot,每台 bot 使用独立凭证、默认引擎、接入模式和角色说明,同时保留 @ 提及、富文本代码以及图片和文件下载能力。
插件化设计:运行中的 Agent OS 本质上是一个 Cordis 根 Context,平台、执行引擎、斜杠命令、会话、任务编排和 bot 协作都是挂载在它上面的插件,通过 ctx.<service> 与类型化事件协作,而不是互相导入具体实现。
- 插件装配:
cordis.yml声明启用哪些插件及参数。移除一个条目或设置disabled: true即可下线对应能力;新增能力只需写一个新插件并在src/plugins/loader.ts的注册表里登记名字。 - 服务:
ctx.config(bot 注册表)、ctx.sessions(会话模型)、ctx.cli(执行引擎与调度)、ctx.lark(飞书平台)、ctx.cards(卡片渲染)、ctx.commands(斜杠命令)、ctx.tasks(任务编排)、ctx.collaboration(bot 协作)。消费方通过inject声明依赖,Cordis 按依赖自动决定启动顺序。 - 事件:lark 插件发出
bot/message与bot/card-action,router 路由插件消费并派发;任务完成后 tasks 服务广播task/result,collaboration 插件监听并决定是否自动交接——协作是可选插件,移除后任务编排不受影响。 - 引擎与命令都是插件:
src/plugins/engines/*.ts通过ctx.cli.register()登记 Codex/Claude/DimAgent;src/plugins/commands/*.ts通过ctx.commands.register()登记/help、/new、/resume、/compact、/status、/cd、/close。新增执行引擎或斜杠命令 = 新增一个插件。
默认 cordis.yml 内容:
plugins:
- name: config
config:
botsPath: config/bots.json
- name: sessions
- name: cli
- name: engines/claude
- name: engines/codex
- name: engines/dimagent
- name: lark
- name: cards
- name: commands
- name: commands/help
- name: commands/new
- name: commands/resume
- name: commands/compact
- name: commands/status
- name: commands/cd
- name: commands/close
- name: collaboration
- name: tasks
- name: router底层纯函数模块(src/core/*、src/cli/*、src/im/*)保持无框架依赖,由服务插件复用;src/index.ts 只是创建根 Context 并挂载 loader 的引导入口。
- 在飞书开放平台创建“飞书智能体应用”。
- 确认应用已启用机器人能力,并订阅
im.message.receive_v1事件。 - 事件接收方式选择“使用长连接接收事件”。
- 创建一个话题群,在群设置的“群机器人”中加入该应用。
- 为每台 bot 创建应用并把 App ID、App Secret 写入本地
.env。bot 的对应关系在config/bots.json中维护。
.env 已被 Git 忽略,禁止提交真实凭证。
先复制配置模板,再按本机 bot 填写配置:
Copy-Item config/bots.example.json config/bots.jsonconfig/bots.json 的每一项包含稳定的 id、凭证环境变量名、defaultCli、workspace、systemPrompt 和可选的 accessMode、enabled、reviewBy、collaborationMaxRounds。accessMode 可填写 headless 或 acp,未填写时默认 headless;acp 是标准接入能力,由 engines/acp 插件提供,任何 defaultCli 都可声明(前提是该引擎注册了对应接入模式,运行时由 CLI 注册表校验)。collaborationMaxRounds 默认是 2,只能设置为 1 到 4。示例文件可以提交,实际配置已被 Git 忽略:
{
"bots": [
{
"id": "developer",
"appIdEnv": "FEISHU_DEVELOPER_APP_ID",
"appSecretEnv": "FEISHU_DEVELOPER_APP_SECRET",
"defaultCli": "dimagent",
"accessMode": "acp",
"workspace": ".",
"systemPrompt": "你是主力开发助手,负责理解需求并完成实现。",
"reviewBy": "reviewer",
"collaborationMaxRounds": 2
},
{
"id": "reviewer",
"appIdEnv": "FEISHU_REVIEWER_APP_ID",
"appSecretEnv": "FEISHU_REVIEWER_APP_SECRET",
"defaultCli": "codex",
"workspace": ".",
"systemPrompt": "你是审查助手,负责检查实现、发现风险并给出修改建议。",
"enabled": true
},
{
"id": "assistant",
"appIdEnv": "FEISHU_ASSISTANT_APP_ID",
"appSecretEnv": "FEISHU_ASSISTANT_APP_SECRET",
"defaultCli": "codex",
"workspace": ".",
"systemPrompt": "你是个人助理,负责协助用户处理日常事务、信息整理、计划安排和执行跟进。",
"enabled": true
}
]
}appIdEnv 和 appSecretEnv 指向 .env 中的真实凭证变量。停用 bot 时设置 enabled: false,它不会读取凭证或建立长连接;全部停用或配置字段错误时程序会在启动阶段退出。修改 .env 或 config/*.json 会触发 pnpm start 自动重启。群里 @哪台 bot,就由哪台 bot 接手,程序无需再次判断目标应用。
个人助理使用独立的飞书应用凭证和 assistant bot ID,不参与开发 bot 的自动代码审查链。启用前请在 .env 中填写 FEISHU_ASSISTANT_APP_ID 和 FEISHU_ASSISTANT_APP_SECRET,并把本地 config/bots.json 中的 assistant.enabled 设置为 true。
reviewBy 填另一台已启用 bot 的 ID。开发 bot 的普通任务成功后,会在原话题发送审查卡片,再发送一条真正提及审查 bot 的富文本消息;审查 bot 会继承来源 bot 的工作目录并建立独立 CLI 会话。审查完成后,最终回答会登记为下一项协作任务,沿用同一个 taskId 并把 round 加一,再自动交回来源 bot。达到 collaborationMaxRounds 后只发送完成通知,不再继续派活,避免两个 bot 无限循环。目标不存在、未启用或指向自己时,程序会在启动阶段拒绝配置。
pnpm build
pnpm test
pnpm start看到 ws client ready 后,在测试话题群里 @机器人 发送消息。
先确认本机终端可以找到两个 CLI:
codex --version
claude --versionCodex 尚未安装时执行:
npm install -g @openai/codex
codex完成登录后,在项目目录验证非交互模式:
codex exec --json --sandbox danger-full-access --skip-git-repo-check "只回复:Codex 已就绪"Claude Code 尚未安装时执行:
npm install -g @anthropic-ai/claude-code
claude首次运行 claude 时完成 Anthropic 登录。使用兼容模型服务时,把供应商地址、认证令牌和模型配置保存在各 CLI 的用户级配置中,也可以使用 CC Switch 切换服务;不要把模型密钥写入项目或提交仓库。
飞书无法展示 Claude Code 的交互式权限确认,因此 Agent OS 会以 --dangerously-skip-permissions 无人值守运行 Claude。只应把 bot 指向明确可信、可随时回退的工作目录。
每台 bot 的默认工作目录和新话题默认引擎由 config/bots.json 决定:
{
"id": "developer",
"defaultCli": "claude",
"workspace": "C:\\你的\\项目\\绝对路径"
}workspace可以填写相对路径或绝对路径;相对路径从 Agent OS 启动目录解析,未填写时兼容读取CLI_WORKDIR、CLAUDE_WORKDIR,最后回退当前目录。- 工作目录决定 CLI 读取、修改和执行命令的项目,启动时会检查路径存在且是文件夹。
- 话题创建时复制 bot 的默认目录;同一 bot 的不同话题可以分别使用不同项目。
- 在话题中发送
/cd查看目录,发送/cd <目录>切换目录。相对路径以当前话题目录为基准,目录变化会清除旧 CLI 会话,下一条任务重新建立上下文。 - 已持久化话题继续使用自己的
cliId;修改 bot 的defaultCli只影响之后创建的新话题。
Codex 通过 codex exec --json --sandbox danger-full-access --skip-git-repo-check 运行,拥有完整系统访问权限;同一话题追问使用带 --sandbox danger-full-access 的 codex exec resume。Claude Code 通过 claude -p --output-format stream-json --verbose 运行,权限和模型后端沿用用户级 Claude Code 配置。
安装并先在交互界面完成 provider、模型和 MCP 配置:
npm install -g dimcode
dimDimAgent 的官方 CLI 入口是 dim;如果使用自定义命令名,可在 .env 中通过 DIMAGENT_COMMAND 覆盖。
bot 通过 accessMode 选择接入方式,未填写时默认 headless:
{
"id": "developer",
"defaultCli": "dimagent",
"accessMode": "acp",
"workspace": "."
}headless:每轮调用dim exec --json --policy full-access,续聊使用dim exec resume <session-id>。acp:由engines/acp插件以标准 ACP 协议接入——维护单个常驻dim acp进程,任务在同一进程上并发执行,新建或恢复 session 后把消息分片、工具状态与 token 用量映射到实时卡片;空闲自动回收、崩溃自动重连。- ACP 和 headless 都复用
~/.dimcode/v2/中的 provider、模型、MCP 与凭据配置。 - 飞书没有同步权限确认界面;ACP 遇到权限请求时会自动选择
allow_always或allow_once,与 headless 的full-access行为保持一致。只应配置可信且可回退的工作目录。 - 同一话题会自动续接 DimAgent session;当前
/resume不枚举 DimAgent 自身数据库中的历史会话,/compact也暂不调用 DimAgent 原生整理协议。
新话题可发送 /dimagent <任务> 显式选择 DimAgent;接入模式仍取该 bot 的 accessMode 配置。engines/acp 插件通过 cordis.yml 的 engines 列表声明 ACP 引擎(id/command/args),因此任何提供 ACP server 的 CLI 都能以相同方式接入;未注册的引擎与接入模式组合会在运行时明确报错。
分别在话题根消息和已有话题中 @机器人。终端应输出:
[收到] chat=oc_xxx threadId=omt_xxx rootId=om_xxx sender=ou_xxx
原文: @_user_1 帮我看看 @_user_2 的代码
还原: @MyBot 帮我看看 @运营专家 的代码
mentions: @_user_1=MyBot(ou_xxx), @_user_2=运营专家(ou_xxx)
[卡片] 已发送 message_id=om_xxx inThread=true
根消息的 rootId 可能为空,但话题消息会带 threadId;已有话题内的回复通常同时带 threadId 和 rootId。任务卡片应留在当前话题。
text 消息正文中的 @_user_N 会被还原为显示名;post 消息会保留 at 占位符,并在后续统一还原提及。富文本中的 text、链接、行内代码、代码块、Markdown 和换行也会进入 CLI 提示词,图片仍由资源下载链路单独处理。
不要只发送裸图片。在话题编辑器中输入 @机器人 帮我看看这张图,再把文字和图片作为同一条消息发送,以覆盖 post 内嵌图片分支。
资源会保存到:
data/downloads/
图片根据响应 Content-Type 保存为 jpg、png、gif、webp、bmp 或 ico;无法识别时使用 .img。普通文件优先保留原文件扩展名,无法识别时使用 .bin。
PowerShell 中可以检查下载结果:
Get-ChildItem -LiteralPath .\data\downloads验证范围:
@机器人 + 文字 + JPEG/PNG/WebP:扩展名应与真实格式一致@机器人 + 文字 + 普通文件:应保留原文件扩展名- 日志应出现
[下载] image|file → data\downloads\...
下载失败时:
234003:检查message_id和资源 key 是否来自同一条消息234004:检查机器人是否仍在当前群里- 权限错误:确认应用已获得读取消息资源所需的消息权限
- 日志成功但找不到文件:确认从项目根目录运行
pnpm start
群聊中不带 @机器人 的普通消息默认不会推送给应用。
在新话题发送一个容易核对的只读任务:
@机器人 请读取 package.json,告诉我项目名称和主要依赖,不要修改文件
系统会依次执行:
- 创建或复用当前话题的 Agent OS 会话,并切换为
active。 - 在原话题发送蓝色的“Codex · 执行中”或“Claude Code · 执行中”卡片。
- 后台启动真实 CLI 子进程,飞书长连接仍可处理
/status和/close。 - 按行解析 stdout 中的 JSONL;一行可产生多个统一事件,普通诊断噪音会被忽略。
- 工具、上下文事件实时汇总成稳定快照,打印到终端并以一秒上限刷新原卡片。
- 成功时把原卡片更新为绿色,答案置于正文;超出卡片上限的剩余内容继续回复到同一话题。
- 失败时卡片变红并折叠技术详情;停止时卡片变灰,不会写入迟到的成功状态。
- 最后清理运行记录,把未关闭的会话持久化为
idle。
终端会输出实际引擎和工作目录:
[CLI] id=claude command=claude
[CLI] id=codex command=codex
[Bot DEVELOPER] default_cli=claude access_mode=headless workspace=C:\你的\项目
[CLI] 启动 engine=codex access_mode=headless cwd=C:\你的\项目
[CLI] codex 完成 session_id=019f...
Claude Code 的 session_id 来自 system/init 或最终 result 事件;Codex 的会话标识来自 thread.started.thread_id,最终回答取最后一个 item.completed 的 agent_message。供应商事件先由各自适配器翻译,再交给通用 Runner 处理。
适配器会把供应商 JSONL 统一为会话、工具开始、工具结束、上下文、最终结果和错误事件。Claude Code 同一条 assistant 消息中的上下文用量及多个 tool_use 都会保留;tool_result 通过调用 ID 与开始事件配对。最终结果还会保留耗时、轮次、输入/输出/缓存 Token 和模型上下文窗口等真实统计。
Codex 使用 item.started/item.completed 中的 command_execution、file_change、web_search 和 mcp_tool_call 展示命令、文件修改、搜索与外部工具轨迹,并用 item.id 配对。最终回答来自 agent_message,输入、输出、缓存输入和总 Token 来自随后到达的 turn.completed;Runner 会合并两条事件。Codex 没有提供的 Claude 对应字段保持为空,不进行估算。
高频事件由 TaskProgressTracker 汇总:它支持并行工具调用,记录耗时、失败状态、本轮第一次和最新一次上下文,最多保留最近 12 条完成活动。快照仍会打印到终端,例如:
[进度] 读取文件 detail=package.json tools=0/1 context=18432
[进度] 正在分析执行结果 tools=1/1 context=18432
同一份快照还会进入飞书卡片。ThrottledCardUpdater 在一秒窗口内只保留最新状态,并串行提交更新,避免高频工具事件触发限流或让画面来回跳动。没有新事件时,每秒心跳仍会推进耗时。
运行中卡片把当前动作放在顶部,只显示最近 3 条轨迹和“停止任务”按钮。成功后答案回到正文顶部,执行统计与最近 8 条轨迹收进折叠面板;失败显示可重试提示并折叠技术错误;取消使用灰色终态。
回答不超过 900 个字符时直接展示,更长时显示预览和折叠全文。超过卡片 6000 字符上限的剩余部分会按不超过 4000 字符的文本消息继续发送,并尽量在换行处切分。
停止按钮通过 card.action.trigger 回调。操作者身份只读取飞书平台回传的 open_id,并与任务发起人比较;按钮同时携带会话 ID 和每轮唯一运行 ID,因此旧卡片不能停止同一话题后来启动的新任务。按钮只停止本轮,/close 会停止本轮并关闭整个会话。
运行 pnpm start 后,在飞书新话题发送:
@机器人 请读取 package.json 和 src/index.ts,总结项目的启动流程
终端应持续出现 [进度],飞书卡片应每秒最多更新一次,完成后答案出现在绿色卡片正文。同一话题继续追问仍会续接原 CLI 会话。再发送一个长任务并点击“停止任务”,发起人会收到成功 Toast,卡片随后变灰,同一话题仍可继续提问;其他群成员点击时只会收到权限警告。
子进程使用参数数组且不启用 shell。飞书消息中的引号、换行、反引号或 $() 都只会成为提示词内容,不能拼接成额外系统命令。Windows 下会绕过 npm 的 .cmd/无扩展名包装器,直接启动真实 Node 入口或 exe,仍然保持 shell=false。每轮默认不设执行时限;调用方显式传入 timeoutMs 时才会自动超时,或由 /close 取消并终止 CLI 及其整棵子进程树。
Codex 的 app-server 默认通过 stdio 通信,不需要额外的 --stdio 参数。首次 codex exec 使用 --sandbox danger-full-access;codex exec resume 使用 --dangerously-bypass-approvals-and-sandbox,因为续聊子命令不接受 --sandbox。Codex 返回 stream disconnected before completion: Upstream request failed 时,Runner 会把它视为瞬时流式断开,最多自动重试 5 次,依次等待 1 秒、1.5 秒、2 秒、2.5 秒、3 秒;已经建立 CLI 会话时优先使用续聊参数,没有会话 ID 时重新发起同一任务。Claude、认证、权限、会话失效和其他普通错误不会自动重试;用户在等待期间发送 /close 也会立即取消重试。
插件按 cordis.yml 依次装配,启动日志先显示注册数量和会话恢复,再打印 CLI 引擎与每台 bot 的默认配置,随后出现连接成功:
[配置] 已加载 3 个 bot 注册表
[会话] 已恢复 0 个会话
[Bot DEVELOPER] 已连接 name=开发助手 open_id=ou_developer
[Bot REVIEWER] 已连接 name=审查助手 open_id=ou_reviewer
[Bot ASSISTANT] 已连接 name=个人助理 open_id=ou_assistant
[CLI] id=claude command=claude
[CLI] id=codex command=codex
[Bot DEVELOPER] default_cli=claude access_mode=headless workspace=C:\你的\项目
[Bot REVIEWER] default_cli=codex access_mode=headless workspace=C:\审查\项目
[Bot ASSISTANT] default_cli=codex access_mode=headless workspace=C:\你的\项目
Agent OS 启动完成
分别新开三个话题,向开发助手、审查助手和个人助理各发一条任务。开发助手应使用 Claude Code,审查助手和个人助理应使用 Codex;同一话题分别 @ 三台 bot 时,/status 返回的机器人 ID、执行引擎和 CLI 会话 ID 也应各自独立。
单独发送 /codex、/claude 或 /dimagent 会提示补充任务,不会启动进程;在已建立的话题发送与原引擎不同的前缀,系统会要求新开话题,避免混用不同引擎的会话 ID。新话题可用对应的 /<引擎> <任务> 前缀显式选择执行引擎。
真实任务运行期间发送 /close,AbortController 会终止对应子进程。会话保持 closed,不会再发送绿色成功卡片或最终回答。
CLI 返回的会话标识会保存为 Session.cliSessionId。同一话题下一轮会自动调用 Claude Code 的 --resume <session_id> 或 Codex 的 exec resume <thread_id>;新话题没有恢复指针,会从干净上下文开始。
配置 reviewBy 后,普通开发任务成功会按以下顺序交接:
- 开发 bot 更新原任务卡片为成功状态。
- Agent OS 在内存收件箱登记
dispatchId、来源/目标 bot、原话题taskId、当前round、maxRounds、完整审查提示词和工作目录。 - 开发 bot 回复一张“代码审查已发起”卡片,再回复一条带任务编号的
post消息,真实@审查 bot。 - 只有被提及且匹配任务编号的目标 bot 会领取交接单;领取后立即删除,重复事件不会再次执行。
- 审查 bot 在来源工作目录中启动自己的 CLI 会话,完成后把最终回答作为下一项任务交回开发 bot;开发 bot 会在同一项目中处理反馈。达到轮次上限后,原话题只收到“本轮协作已完成”通知。
卡片只负责展示项目、角色和审查说明,不能替代真实提及。交接单当前保存在内存中,服务在投递后重启会丢失尚未领取的任务;验证交接链路时不要重启服务。
- 将开发 bot 的
reviewBy配为reviewer,并让两台 bot 的workspace都指向同一个真实项目。 - 运行
pnpm build、pnpm test后启动pnpm start,日志应打印两台 bot 的name和open_id。 - 在新话题发送
@开发助手 阅读 TASK.md,完成里面的功能并运行验证。。 - 开发任务完成后,应看到“代码审查已发起”卡片和一条新的
@审查助手富文本消息。 - 审查 bot 应在同一项目中独立检查改动,完成后看到“审查意见已返回”卡片和一条新的
@开发助手富文本消息。 - 开发 bot 应在同一项目中处理审查反馈;达到
collaborationMaxRounds后停止自动交接。 - 向非目标 bot 转发这条通知、删除任务编号或重复投递时,目标 bot 都不应启动 CLI。
- 新开话题发送“请记住暗号‘Agent 操作系统’,只回复‘记住了’”。
- 等待完成后,在同一话题追问“我刚才让你记住的暗号是什么?”,回答应包含“Agent 操作系统”。
- 发送
/status,应同时看到 Agent OS 的“会话”和执行引擎的“CLI 会话”。 - 打开
data/sessions.json,对应记录应包含非空cliSessionId。 - 重启机器人并在原话题继续追问,上下文仍应保留。
- 新开另一个话题询问暗号,它不应继承上一话题的上下文。
会话地址按以下优先级确定:
threadId || rootId || messageId
再与 chatId 和当前 botId 组合为查找键。因此同一 bot 在同一群聊、同一话题里的追问会复用相同会话;不同 bot、不同话题或不同群会创建独立会话。普通群和单聊没有话题 ID 时,每条消息使用自身 messageId 创建会话。
会话状态流转:
creating → active → idle → active
└────→ idle(命令)
└──────────────→ closed
creating:刚创建,尚未执行active:Codex 或 Claude Code 子进程正在运行idle:上一轮完成,可以继续追问closed:话题会话已关闭,不再接受任务
执行中的普通消息会收到“当前会话还在执行”的提示,不会启动第二段任务。
每台 bot 的默认执行引擎和接入模式由注册表决定,会话类型支持 claude、codex 和 dimagent。引擎与接入模式只在话题首次创建会话时确定,之后的普通追问、显式引擎前缀和重启恢复都不能改变它。内存中的会话映射会同步保存到 data/sessions.json,程序重启后按原 bot、群聊和话题恢复;旧快照没有 accessMode 时按 headless 恢复。
程序启动时会先读取:
data/sessions.json
文件不存在时按首次启动处理,日志会显示:
[配置] 已加载 2 个 bot 注册表
[会话] 已恢复 0 个会话
每次创建会话、切换状态、更新 CLI 恢复指针或记录待重试任务时,SessionManager 都会保存完整快照。保存成功后内存和磁盘一起前进;首次创建保存失败会删除刚建立的内存会话,状态或恢复信息保存失败则回滚到原值。
磁盘存储遵循以下规则:
- 每条记录先经过 Zod 校验,坏记录会被过滤并从清理后的文件中移除。
- 每条记录包含
botId和绝对workspaceDir;升级前缺少botId或workspaceDir的旧记录会按 bot 默认目录补齐,并在加载后自动重写为新结构。 - 重启时仍为
creating或active的会话恢复成idle,因为旧任务进程已经不存在。 - Codex、Claude 和 DimAgent 都可以保存恢复指针。
- 旧记录可以没有
cliSessionId;首次任务成功后写入,新记录的空字符串会被视为坏数据。 - CLI 首次返回会话 ID 时会立即写入快照;即使任务随后被停止、超时或进程重启,下一条消息仍会优先尝试续接原会话。
- 若 CLI 明确返回会话不存在或已失效,Agent OS 会清除旧指针;下一次“继续执行”会用原始任务重新建立会话,避免无限重试坏 ID。
- 任务启动前会临时写入
retryPrompt,成功后立即删除;若失败发生在 CLI 返回会话 ID 之前,发送“继续执行”等明确重试指令会重放原任务。 - 并发保存通过写入队列串行执行,确保后触发的状态不会被旧快照覆盖。
- 数据先完整写入
sessions.json.tmp,再用rename替换正式文件,避免留下半截 JSON。
PowerShell 中可以检查当前快照:
Get-Content -LiteralPath .\data\sessions.json -Encoding utf8重启验收步骤:
- 在飞书新话题发送任务,等待卡片完成。
- 在原话题发送
@机器人 /status,记下“会话”和“CLI 会话”两个 ID;磁盘状态应为idle。 - 在终端按
Ctrl+C,然后重新运行pnpm start。 - 启动日志应显示
[会话] 已恢复 1 个会话,数量以实际已有话题为准。 - 在原话题再次发送
@机器人 /status,两个会话 ID 都应与重启前相同,状态为“空闲”。 - 新开话题发送消息,应创建不同的会话 ID。
命令可以直接发送,也可以带机器人提及:
/status
@机器人 /status
/help
/close
/new
/resume
/compact 保留接口约定,省略排查过程
/claude 检查 package.json
/codex 查看当前目录结构
/status:返回 Agent OS 会话 ID、状态、执行引擎、CLI 会话 ID、工作目录、话题 ID 和更新时间/new:清空当前话题绑定的 CLI 会话;旧会话仍由引擎保留/resume:读取当前工作目录中的 Claude/Codex 原生会话并用卡片选择恢复/compact [要求]:在当前 CLI 会话内调用引擎原生上下文整理;Claude 支持附加要求,Codex 使用默认策略/cd:查看当前工作目录/cd <目录>:切换当前话题的工作目录/help:列出会话控制和引擎选择命令/close:关闭当前话题会话/claude <任务>:新话题使用 Claude Code/codex <任务>:新话题使用 Codex/dimagent <任务>:新话题使用 DimAgent(接入模式取 bot 的accessMode)
在一个已经完成过任务的话题中依次发送:
/new:收到绿色“新会话已就绪”卡片;下一条任务会建立新 CLI 会话,旧记录仍可恢复。/resume:卡片只列出当前workspaceDir的原生会话,显示标题、更新时间和短 ID;点击“恢复”后当前记录标记为当前会话。/compact 保留架构决定和待办事项:蓝色卡片显示整理进度,完成后变为绿色;CLI 会话 ID 保持不变。短 Claude 会话会显示“暂时无需整理”。
按钮回调会重新读取当前工作目录并校验 CLI 会话 ID,历史记录被删除或移出目录后不会被恢复。整理和恢复期间仍遵守单话题单任务状态,发起人可以使用停止按钮取消 compact。
启动 pnpm start 后,在 bot 的新话题依次发送:
@机器人 /cd
@机器人 /cd ../another-project
@机器人 /status
/status 应显示新的绝对工作目录,CLI 会话应为“尚未建立”;下一条任务会在新目录启动。另一个话题或另一台 bot 的 /status 应继续显示各自目录。对不存在的目录执行 /cd 时会提示错误,原目录不会改变;执行中的任务切换会被拒绝。
如果任务仍在执行,/close 会通过 AbortController 终止后台 CLI 子进程,并且不会写入绿色成功终态或回复最终答案。关闭后在同一话题发送普通消息,只会收到“请新开一个话题”的提醒。
建议按以下顺序验证:
- 新话题发送任务,日志显示
[会话] 新建 ... status=creating - 任务完成后日志显示相同 ID 的
status=idle - 同一话题继续发送任务,日志显示
[会话] 复用 - 执行中发送普通消息,收到忙碌提示
- 发送
/status和/help检查命令回复 - 新任务执行中发送
/close,日志出现[CLI] 任务已取消 engine=... - 关闭的话题继续发送消息,收到新开话题提示
- 新开话题后应得到不同的会话 ID,并能正常完成任务
自己发送一条消息后,把日志中 sender= 后面的 ou_ 值写入 .env:
OWNER_OPEN_ID=ou_你的OpenID后续需要点名提醒真人时,可以发送飞书的 at 标签:
await bot.reply(
message.messageId,
`<at user_id="${process.env.OWNER_OPEN_ID}"></at> 收到,这条是点名回复`,
);暂时不需要申请“获取群信息”权限;等需要枚举群成员时,再使用群成员列表接口。