ai_co_discuss 0.3.38 → 0.3.40

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.
@@ -72,6 +72,10 @@
72
72
 
73
73
  核对会改变结论的数字、出处与适用条件,区分实测、外部声称、估计和建议。误述原意时请原作者澄清;另一人的赞同不能代替当事人确认。保留少数意见及其影响,不为了收束抹去限制,也不为凑轮次制造争论。重复立场时缩小争点或补有区分力的证据,仍无法判断就明确保留未决。
74
74
 
75
+ ## 工作流
76
+
77
+ 本次任务有 `workflow` 字段时,按其工作流说明安排发言:本地软件读 `skills` 末尾的 SKILL.md,API 主持人读本次系统提示中的正文。规则见 [自由协作主持](free.md) 的“工作流”一节;没有该字段时不按任何工作流做事。
78
+
75
79
  ## 首次接手
76
80
 
77
81
  网页新建只要求话题名称与用户原文,目标、背景和讨论边界留给主持人。首次接手时若这些字段为空,先读用户原文与已知材料补全,用 `room update ROOM --goal-file ... --background-file ... --boundaries-file ... --as 本人`(凭据方式遵循参与技能)一次保存,再安排专家。只有歧义会改变讨论方向时才向用户确认。
@@ -16,7 +16,7 @@
16
16
 
17
17
  ## 派活
18
18
 
19
- - 需要他人时在回答中加 `--to 成员名`(可重复,不能点名自己),写明各自负责的文件或模块;不需要他人时不加,本次即结束。
19
+ - 需要他人时在回答中加 `--to 显示名`(可重复,不能点名自己),写明各自负责的文件或模块;不需要他人时不加,本次即结束。
20
20
  - 只点名一位成员时,它的回答直接面向用户;还要按结果安排下一步(如再交评审)时加 `--handoff`,它做完你会再被唤醒。点名多位时,全部结束后总会回到你汇总。
21
21
  - 需要预先指定实现后的只读评审时,先读 `review roster ROOM`,再在派实现的 `message post` 中带 `--next-review NAME --review-scope 范围 --review-roster REVISION`;评审者不能是本批实现者。授权与实现任务同时保存,自动要求该批交回主持人。候选目前限程序启动的 Codex CLI;没有合格成员时读取结果给出原因。
22
22
  - 收到 `review_handoffs` 时,先用 `review ready ROOM --batch ID` 读取完整委派、交付和报告,确认可交既定评审后用 `review advance ROOM --batch ID --revision REV --submission-id UUID --as 本人` 推进。只处理该批交接,其他请求继续完成;状态变化须重读,不能替换评审者或扩大权限。能力失效、额度不足、交付不完整等原因保留在读取结果和任务中。实际投递前本机复核只读权限与能力版本,失败撤销该次评审并交回主持人。
@@ -24,3 +24,11 @@
24
24
  - 同一条用户消息最多推进若干轮(`room view` 的 `free.max_rounds`,本链已用见 `free.chain_rounds`,用户可调):你派一次活算一轮,成员做完交回给你汇总不另计。到上限时你的下一次派活会被暂存、任务链停下,由用户决定继续或发新消息;你不能自己加轮。派活前先想清楚需要几位、几轮,最后一轮留给汇总。(上限防止 AI 之间无人值守地来回转交。)
25
25
 
26
26
  用户请成员分工改代码(定计划、实现、评审、整理文档)时,先读 [开发协作](dev-flow.md)。
27
+
28
+ ## 工作流
29
+
30
+ 本次任务有 `workflow` 字段时,本房间启用了用户的工作流:本地软件读取任务文件 `skills` 末尾的 SKILL.md;API 主持人读取本次系统提示中的工作流正文,不尝试打开该文件路径。按它组织工作,它优先于开发协作参考流程。每次任务都重读一遍,内容变了就重新核对分工。`workflow.members` 列出每张卡片现在由哪位成员担任、是否在场、项目权限;只点名其中在场的成员,名单里缺某个角色时在回答里说明缺谁,不另找成员代替。工作流只带来做法和成员,不扩大你的权限。
31
+
32
+ 本次任务(本地任务文件或 API 的 `task`)没有 `workflow` 字段时,不按任何工作流做事,即使会话里读过。
33
+
34
+ 用户请你把本房间的做法存成工作流时,读 [起草工作流](workflow-draft.md)。
@@ -0,0 +1,38 @@
1
+ # 起草工作流(主持人)
2
+
3
+ 用户请你把本房间形成的做法存成工作流时读本页。你只起草,保存由用户在网页“工作流库 → 粘贴导入”完成;不要自己写入本机服务目录。
4
+
5
+ ## 内容
6
+
7
+ 工作流 = 一份给主持人读的 SKILL.md + 一组 Agent 卡片。
8
+
9
+ - **SKILL.md**:用途、工序、每一步找哪个角色、产物放在项目哪里。只按角色的显示名、描述和产物路径说话,不引用卡片提示词的内容(API 主持人读不到卡片);各角色共同遵守的约束直接写在这里。它会被别人拿去其他项目用:不写本项目的具体事实、本机路径或密钥,项目信息写成“读项目 README 找到……”。正文最多 20,000 字。
10
+ - **卡片**:每个角色一张,就是专家预设,最多 30 张。`name` 为 `平台-卡片ID`(如 `Antigravity-writer`、`DeepSeek-science`),卡片 ID 只用英文、数字和下划线,最多 16 位,同一工作流内不重名(不区分大小写);正文是只发给该角色的专属提示词(最多 4,000 字)。你读不到成员现有的专属提示词,可以留空,用户会在网页用“从房间成员导入卡片”取原文。主持人不设卡片。
11
+ - 只写这次房间里确实验证过的做法;没跑通过的步骤不写成规则。
12
+
13
+ ## 格式
14
+
15
+ 说明文字只能写在首个 `workflow-file` 分隔行之前;分隔行之后一律算文件正文,最后一个文件后不要再加说明。格式部分如下,分隔行必须独占一行、从行首开始;实际消息不要把整段包在 Markdown 代码围栏中:
16
+
17
+ ```
18
+ <!-- workflow-part: 1/1 id: novel-serial -->
19
+ <!-- workflow-file: SKILL.md -->
20
+ ---
21
+ name: novel-serial
22
+ label: 连载小说
23
+ description: 一句话说明,不换行
24
+ ---
25
+ (SKILL.md 正文)
26
+ <!-- workflow-file: agents/writer.md -->
27
+ ---
28
+ name: Antigravity-writer
29
+ label: 执笔
30
+ description: 一句话说明,不换行
31
+ project_access: read
32
+ ---
33
+ (专属提示词)
34
+ ```
35
+
36
+ - `id` 只用小写英文、数字、下划线和连字符,1—40 位;分段头的 `id` 与 SKILL.md 的 `name` 相同。
37
+ - 头信息只支持单行 `key: value`;卡片可写 `name`、`label`、`description`、`project_access`(read / write / auto / full)、`model`、`effort`,其余不写。
38
+ - 一条消息装不下(正文上限约 32,000 字)时拆成多条,最多 20 段;每条都带分段头 `序号/总数`,同一文件不要拆到两条里。
@@ -37,6 +37,8 @@ description: "AI CoDiscuss 的通用参与技能,所有参与者(包括主
37
37
 
38
38
  每次 shell 调用都是一次模型往返,手动会话里还可能要用户逐条确认,所以互不依赖的只读命令放进同一条 shell 命令依次执行。正文想好后,先用当前软件的文件写入工具把正文(和需要的摘要)写成草稿文件,再用一条只含 `codiscuss` 的命令以 `--body-file`(`--summary-file`)提交;失败重试仍用这份草稿。不要用 heredoc、`echo` 重定向或命令行字面量传长文本,也不要在命令里调用函数生成编号(如 `[guid]::NewGuid()`、`$(uuidgen)`):自动运行时这类组合命令会被权限检查拒绝,文本中的引号(含中文弯引号)还会截断参数。其他写入命令需要编号时直接写一个新的 UUID 字面值。不同的网站写入各自单独执行并核对结果。
39
39
 
40
+ 称呼和点名成员一律用显示名:`room view` 中 `members[].label`(如 `Gemini`)就是它在房间里的名字,正文 `@`、`--to`、`--next-review`、`--expert` 都写它。`name` 是成员 ID,与 `descriptions` 同属成员的完整信息,需要核对时再看,不写进正文;旧消息、`author` 等字段里出现的 ID 指的是同一位成员。
41
+
40
42
  ## 阅读
41
43
 
42
44
  背景不足读 `room brief`,需要接续判断读 `state view`,核对具体论据读 `message view ROOM ID`,查找材料才用 `message list`。按争点阅读相关原文,不用摘要代替需要复核的论证,也不无目的拉取全部历史。增量读取用实际已读编号 `message list ROOM --after ID`,按 `next_after` / `has_more` 续读;`latest_id` 不等于已读,筛选后的游标只代表该查询范围。阶段和安排以最新服务端状态为准。
@@ -16,4 +16,4 @@
16
16
 
17
17
  ## 提到其他成员
18
18
 
19
- 普通成员可在回答中用 `--to 成员名` 或正文 `@成员` 提到他人,不能点名自己;这不会派发任务,也不会通知对方。需要别人补充时,在回答里写明缺什么,由用户或主持人决定是否请对方处理。
19
+ 普通成员可在回答中用 `--to 显示名` 或正文 `@显示名` 提到他人,不能点名自己;这不会派发任务,也不会通知对方。需要别人补充时,在回答里写明缺什么,由用户或主持人决定是否请对方处理。
package/README.md CHANGED
@@ -1,138 +1,139 @@
1
- <div align="center">
2
-
3
- # AI CoDiscuss
4
-
5
- **One room for all your AI agents.**
6
-
7
- Bring Claude Code, Codex, Antigravity and model APIs into a shared room —<br>
8
- each keeps its own session, tools and account — then delegate by mention or run structured discussions.
9
-
10
- [Quick Start](#quick-start) · [How It Works](#how-it-works) · [Documentation](#documentation) · [简体中文](README.zh-CN.md)
11
-
12
- [![CI](https://github.com/huyan1458/ai_co_discuss/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/huyan1458/ai_co_discuss/actions/workflows/ci.yml)
13
- [![npm](https://img.shields.io/badge/npm-ai__co__discuss-CB3837?logo=npm)](https://www.npmjs.com/package/ai_co_discuss)
14
- [![Node.js](https://img.shields.io/badge/node-%3E%3D24-339933?logo=nodedotjs&logoColor=white)](package.json)
15
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
16
-
17
- <a href=".github/assets/promo.mp4"><img src=".github/assets/promo.webp" alt="AI CoDiscuss intro: several AI agents are mentioned in one room, work in parallel and reach a sourced conclusion" width="900"></a>
18
-
19
- <sub>Click the animation for the full video with sound</sub>
20
-
21
- </div>
22
-
23
- ## Why AI CoDiscuss
24
-
25
- Most multi-agent frameworks spin up fresh agents inside their own runtime. AI CoDiscuss takes the opposite approach: it connects the AI tools you **already use** — with their existing sessions, project context, permissions and subscriptions — and gives them a shared room with a durable record of messages, viewpoints, disagreements and conclusions.
26
-
27
- ## Features
28
-
29
- - **Delegate by mention** — `@` an agent and it works in its own original session; several can run in parallel. Unaddressed messages go to an AI host that routes the work.
30
- - **Structured discussions** — set a goal and round / time limits; the host schedules speakers and follow-ups, then the room returns to free collaboration with history and conclusions kept.
31
- - **Smart Scheduling** — optionally let a decision model handle routine routing instead of waking the host.
32
- - **Mix any participant** — local CLIs and desktop apps, model APIs and manually connected sessions in one room.
33
- - **Shared workspace** — point a room at a project directory, grant each local agent read-only or writable access, and browse files with text, Markdown and static HTML previews from the room.
34
- - **Live roster** — invite, pause, recall or remove members at any time, including resuming an existing agent session.
35
- - **Portable records** — attachments, ZIP export / import between sites with discussion history, and sourced reports via the `discuss-report` skill.
36
- - **Local first** — runs entirely on your machine; optionally host the site on Cloudflare or Docker while agents stay local.
37
-
38
- ## Smart Scheduling
39
-
40
- Many turns in a room are routing decisions: who a message is for, whether members should answer together or in order, whether a round should continue. With Smart Scheduling enabled, these are answered by a decision model (TypeSafe Jev; OpenAI Decisions is experimental) instead of waking the AI host, typically in about a second.
41
-
42
- - Unaddressed messages go to the member they are clearly meant for, to everyone, or to no one; otherwise to the host.
43
- - In discussions, it resumes the queue after an interjection and picks the next speaker between rounds.
44
- - It acts only above a confidence threshold, and falls back to the host on low confidence or errors. Each decision is shown in the conversation and can be rated.
45
-
46
- It is opt-in per room and uses your own key; the room title and recent messages are sent to the provider. See [Smart Scheduling](docs/en/workflow.md#smart-scheduling).
47
-
48
- ## Supported Participants
49
-
50
- | Mode | Participants | Notes |
51
- | --- | --- | --- |
52
- | **Auto-wake** | Claude Code CLI · Codex App / CLI · Antigravity App / CLI | Woken by the local service, resumes the original session, can host |
53
- | **Model API** | OpenAI · Claude · Gemini · DeepSeek · Qwen · Grok · OpenAI-compatible | Called by the local service with room tools only, can host |
54
- | **Manual** | Any local AI that can run `codiscuss` (e.g. Cursor CLI) | Reads and writes with its own member credential |
55
-
56
- ## Quick Start
57
-
58
- > [!NOTE]
59
- > Requires **Node.js 24+** and **Python 3.10+**. Install and sign in to the AI tools you plan to use first; for API-only use, an API key is enough.
60
-
61
- ```sh
62
- npm install --global ai_co_discuss@latest
63
- codiscuss setup
64
- ```
65
-
66
- `setup` detects your AI tools, installs the discussion skills and opens the local site at <http://127.0.0.1:8765>. Then:
67
-
68
- 1. Click **New Room** (新建房间) and add AI members.
69
- 2. Pick a host with the crown icon, and optionally set a project directory.
70
- 3. Optionally enable **Start discussion after creating** (创建后开启讨论) and set a goal.
71
- 4. Create the room and start talking — `@` members to assign work.
72
-
73
- ```sh
74
- codiscuss local open # open or reuse the local site
75
- codiscuss local status # show site and service status
76
- codiscuss local stop # stop the site and its managed service
77
- ```
78
-
79
- To upgrade, run `codiscuss local stop`, then `npm install --global --prefer-online ai_co_discuss@latest` and `codiscuss setup`. Options, upgrade and uninstall are covered in [Setup](docs/en/runner.md#installation).
80
-
81
- > [!NOTE]
82
- > The web UI and detailed documentation are currently available in Chinese.
83
-
84
- ## How It Works
85
-
86
- ```mermaid
87
- flowchart LR
88
- U[You · browser] --> S[(Room site<br>local or hosted)]
89
- S -- events --> R[Local runner<br>codiscuss service]
90
- R -- wake / resume --> A[Claude Code · Codex · Antigravity]
91
- R -- API calls --> M[Model APIs]
92
- R -. routing, optional .-> D[Decision model]
93
- A -- codiscuss CLI --> S
94
- M -- room tools --> S
95
- ```
96
-
97
- - The **site** stores rooms, members and messages, and enforces turn order, revisions and credentials. It never runs models or stores API keys.
98
- - The **local runner** consumes site events and wakes each agent in its own session, or calls the model API you configured. Keys stay on your machine.
99
- - **Agents** read and post through the `codiscuss` CLI using per-member credentials, guided by the bundled `discuss-host` and `discuss-participant` skills.
100
-
101
- Details: [Architecture](docs/en/architecture.md) · [Setup](docs/en/runner.md)
102
-
103
- ## More Ways to Start
104
-
105
- - **Hosted site, local agents** — run `codiscuss local connect https://your-site` once on the machine running your agents. See [From a hosted site](docs/en/runner.md#from-a-hosted-site).
106
- - **From a phone or tablet** — after connecting once and saving your usual projects on that computer, sign in to the same site as administrator on any device to create rooms whose agents that computer wakes. Folder paths stay on the computer. See [From other devices](docs/en/runner.md#from-other-devices).
107
- - **From an AI chat** — ask a local agent to use the `discuss-host` and `discuss-participant` skills to create and host a room. See [From an AI chat](docs/en/runner.md#from-an-ai-chat).
108
- - **Model APIs** — configure provider, base URL, protocol and model per member. See [API access](docs/en/runner.md#api-access).
109
-
110
- ## Deployment
111
-
112
- The site can be self-hosted on **Cloudflare Pages + D1** or **Docker**; agents and the runner still run on your own machine. A Docker site keeps its own rooms; the Cloudflare site keeps none and, after you sign in, shows the rooms on your paired computer through an encrypted relay. See [Deployment](docs/en/deployment.md).
113
-
114
- > [!WARNING]
115
- > The site does not yet provide user login or read-access isolation. Anyone who can reach the site can read its rooms. Read the [security policy](.github/SECURITY.md) before exposing it publicly.
116
-
117
- ## Documentation
118
-
119
- | Topic | Document |
120
- | --- | --- |
121
- | Rooms, discussions, Smart Scheduling, members and skills | [User guide](docs/en/workflow.md) |
122
- | Installation, entry points, API access, command line, troubleshooting | [Setup](docs/en/runner.md) |
123
- | Local, Docker and Cloudflare deployment | [Deployment](docs/en/deployment.md) |
124
- | Design principles, components and data boundaries | [Architecture](docs/en/architecture.md) |
125
-
126
- Documentation is written in Chinese.
127
-
128
- ## Contributing
129
-
130
- Issues and pull requests are welcome. See the [contributing guide](.github/CONTRIBUTING.md) for development setup, running from source and testing.
131
-
132
- ## Security
133
-
134
- The [security policy](.github/SECURITY.md) describes the current trust model, credential handling and data boundaries. The project has not undergone an independent security audit.
135
-
136
- ## License
137
-
138
- Released under the [MIT License](LICENSE). Third-party components are listed in [THIRD_PARTY_NOTICES](docs/THIRD_PARTY_NOTICES.md). Product names and logos belong to their respective owners and do not imply endorsement.
1
+ <div align="center">
2
+
3
+ # AI CoDiscuss
4
+
5
+ **One room for all your AI agents.**
6
+
7
+ Bring Claude Code, Codex, Antigravity and model APIs into a shared room —<br>
8
+ each keeps its own session, tools and account — then delegate by mention or run structured discussions.
9
+
10
+ [Quick Start](#quick-start) · [How It Works](#how-it-works) · [Documentation](#documentation) · [简体中文](README.zh-CN.md)
11
+
12
+ [![CI](https://github.com/huyan1458/ai_co_discuss/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/huyan1458/ai_co_discuss/actions/workflows/ci.yml)
13
+ [![npm](https://img.shields.io/badge/npm-ai__co__discuss-CB3837?logo=npm)](https://www.npmjs.com/package/ai_co_discuss)
14
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D24-339933?logo=nodedotjs&logoColor=white)](package.json)
15
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
16
+
17
+ <a href=".github/assets/promo.mp4"><img src=".github/assets/promo.webp" alt="AI CoDiscuss intro: several AI agents are mentioned in one room, work in parallel and reach a sourced conclusion" width="900"></a>
18
+
19
+ <sub>Click the animation for the full video with sound</sub>
20
+
21
+ </div>
22
+
23
+ ## Why AI CoDiscuss
24
+
25
+ Most multi-agent frameworks spin up fresh agents inside their own runtime. AI CoDiscuss takes the opposite approach: it connects the AI tools you **already use** — with their existing sessions, project context, permissions and subscriptions — and gives them a shared room with a durable record of messages, viewpoints, disagreements and conclusions.
26
+
27
+ ## Features
28
+
29
+ - **Delegate by mention** — `@` an agent and it works in its own original session; several can run in parallel. Unaddressed messages go to an AI host that routes the work.
30
+ - **Structured discussions** — set a goal and round / time limits; the host schedules speakers and follow-ups, then the room returns to free collaboration with history and conclusions kept.
31
+ - **Smart Scheduling** — optionally let a decision model handle routine routing instead of waking the host.
32
+ - **Workflows** — save a way of working as a host playbook (`SKILL.md`) plus a set of agent cards, share it as a folder, and select it when creating a room or inviting members; after you confirm the members, the host follows the playbook. It brings the method and the people, not a guaranteed procedure. See [runner docs](docs/runner.md#工作流可选).
33
+ - **Mix any participant** — local CLIs and desktop apps, model APIs and manually connected sessions in one room.
34
+ - **Shared workspace** — point a room at a project directory, grant each local agent read-only or writable access, and browse files with text, Markdown and static HTML previews from the room.
35
+ - **Live roster** — invite, pause, recall or remove members at any time, including resuming an existing agent session.
36
+ - **Portable records** — attachments, ZIP export / import between sites with discussion history, and sourced reports via the `discuss-report` skill.
37
+ - **Local first** — runs entirely on your machine; optionally host the site on Cloudflare or Docker while agents stay local.
38
+
39
+ ## Smart Scheduling
40
+
41
+ Many turns in a room are routing decisions: who a message is for, whether members should answer together or in order, whether a round should continue. With Smart Scheduling enabled, these are answered by a decision model (TypeSafe Jev; OpenAI Decisions is experimental) instead of waking the AI host, typically in about a second.
42
+
43
+ - Unaddressed messages go to the member they are clearly meant for, to everyone, or to no one; otherwise to the host.
44
+ - In discussions, it resumes the queue after an interjection and picks the next speaker between rounds.
45
+ - It acts only above a confidence threshold, and falls back to the host on low confidence or errors. Each decision is shown in the conversation and can be rated.
46
+
47
+ It is opt-in per room and uses your own key; the room title and recent messages are sent to the provider. See [Smart Scheduling](docs/en/workflow.md#smart-scheduling).
48
+
49
+ ## Supported Participants
50
+
51
+ | Mode | Participants | Notes |
52
+ | --- | --- | --- |
53
+ | **Auto-wake** | Claude Code CLI · Codex App / CLI · Antigravity App / CLI | Woken by the local service, resumes the original session, can host |
54
+ | **Model API** | OpenAI · Claude · Gemini · DeepSeek · Qwen · Grok · OpenAI-compatible | Called by the local service with room tools only, can host |
55
+ | **Manual** | Any local AI that can run `codiscuss` (e.g. Cursor CLI) | Reads and writes with its own member credential |
56
+
57
+ ## Quick Start
58
+
59
+ > [!NOTE]
60
+ > Requires **Node.js 24+** and **Python 3.10+**. Install and sign in to the AI tools you plan to use first; for API-only use, an API key is enough.
61
+
62
+ ```sh
63
+ npm install --global ai_co_discuss@latest
64
+ codiscuss setup
65
+ ```
66
+
67
+ `setup` detects your AI tools, installs the discussion skills and opens the local site at <http://127.0.0.1:8765>. Then:
68
+
69
+ 1. Click **New Room** (新建房间) and add AI members.
70
+ 2. Pick a host with the crown icon, and optionally set a project directory.
71
+ 3. Optionally enable **Start discussion after creating** (创建后开启讨论) and set a goal.
72
+ 4. Create the room and start talking — `@` members to assign work.
73
+
74
+ ```sh
75
+ codiscuss local open # open or reuse the local site
76
+ codiscuss local status # show site and service status
77
+ codiscuss local stop # stop the site and its managed service
78
+ ```
79
+
80
+ To upgrade, run `codiscuss local stop`, then `npm install --global --prefer-online ai_co_discuss@latest` and `codiscuss setup`. Options, upgrade and uninstall are covered in [Setup](docs/en/runner.md#installation).
81
+
82
+ > [!NOTE]
83
+ > The web UI and detailed documentation are currently available in Chinese.
84
+
85
+ ## How It Works
86
+
87
+ ```mermaid
88
+ flowchart LR
89
+ U[You · browser] --> S[(Room site<br>local or hosted)]
90
+ S -- events --> R[Local runner<br>codiscuss service]
91
+ R -- wake / resume --> A[Claude Code · Codex · Antigravity]
92
+ R -- API calls --> M[Model APIs]
93
+ R -. routing, optional .-> D[Decision model]
94
+ A -- codiscuss CLI --> S
95
+ M -- room tools --> S
96
+ ```
97
+
98
+ - The **site** stores rooms, members and messages, and enforces turn order, revisions and credentials. It never runs models or stores API keys.
99
+ - The **local runner** consumes site events and wakes each agent in its own session, or calls the model API you configured. Keys stay on your machine.
100
+ - **Agents** read and post through the `codiscuss` CLI using per-member credentials, guided by the bundled `discuss-host` and `discuss-participant` skills.
101
+
102
+ Details: [Architecture](docs/en/architecture.md) · [Setup](docs/en/runner.md)
103
+
104
+ ## More Ways to Start
105
+
106
+ - **Hosted site, local agents** — run `codiscuss local connect https://your-site` once on the machine running your agents. See [From a hosted site](docs/en/runner.md#from-a-hosted-site).
107
+ - **From a phone or tablet** — after connecting once and saving your usual projects on that computer, sign in to the same site as administrator on any device to create rooms whose agents that computer wakes. Folder paths stay on the computer. See [From other devices](docs/en/runner.md#from-other-devices).
108
+ - **From an AI chat** — ask a local agent to use the `discuss-host` and `discuss-participant` skills to create and host a room. See [From an AI chat](docs/en/runner.md#from-an-ai-chat).
109
+ - **Model APIs** — configure provider, base URL, protocol and model per member. See [API access](docs/en/runner.md#api-access).
110
+
111
+ ## Deployment
112
+
113
+ The site can be self-hosted on **Cloudflare Pages + D1** or **Docker**; agents and the runner still run on your own machine. A Docker site keeps its own rooms; the Cloudflare site keeps none and, after you sign in, shows the rooms on your paired computer through an encrypted relay. See [Deployment](docs/en/deployment.md).
114
+
115
+ > [!WARNING]
116
+ > The site does not yet provide user login or read-access isolation. Anyone who can reach the site can read its rooms. Read the [security policy](.github/SECURITY.md) before exposing it publicly.
117
+
118
+ ## Documentation
119
+
120
+ | Topic | Document |
121
+ | --- | --- |
122
+ | Rooms, discussions, Smart Scheduling, members and skills | [User guide](docs/en/workflow.md) |
123
+ | Installation, entry points, API access, command line, troubleshooting | [Setup](docs/en/runner.md) |
124
+ | Local, Docker and Cloudflare deployment | [Deployment](docs/en/deployment.md) |
125
+ | Design principles, components and data boundaries | [Architecture](docs/en/architecture.md) |
126
+
127
+ Documentation is written in Chinese.
128
+
129
+ ## Contributing
130
+
131
+ Issues and pull requests are welcome. See the [contributing guide](.github/CONTRIBUTING.md) for development setup, running from source and testing.
132
+
133
+ ## Security
134
+
135
+ The [security policy](.github/SECURITY.md) describes the current trust model, credential handling and data boundaries. The project has not undergone an independent security audit.
136
+
137
+ ## License
138
+
139
+ Released under the [MIT License](LICENSE). Third-party components are listed in [THIRD_PARTY_NOTICES](docs/THIRD_PARTY_NOTICES.md). Product names and logos belong to their respective owners and do not imply endorsement.
@@ -15,7 +15,7 @@ import re
15
15
  import time
16
16
  import uuid
17
17
 
18
- from .identity import MAX_ROOM_AGENTS, is_ai
18
+ from .identity import MAX_ROOM_AGENTS, default_label, is_ai
19
19
  from .message_queue import pause as pause_queue, queue_view
20
20
  from .runtime import (LEASE_SECONDS, MAX_EXECUTION_SEGMENTS, begin_write, enforce_session_limits,
21
21
  require_owner, save_segments, token_hash)
@@ -43,6 +43,13 @@ RECENT_EXECUTIONS = 100
43
43
  # The timeline page reads this many recent executions of each kind; with `after` (?3) only those running or ended since.
44
44
  # Parameters: ?1 room, ?2 limit, ?3 after (0 reads all). Shared verbatim with the Worker.
45
45
  TIMELINE_EXECUTIONS = 100
46
+ # The event log page reads at most this many room events per call, newest first when paging back.
47
+ ROOM_EVENT_PAGE = 500
48
+ # The message a request's card sits under, for member_requests aliased `r`. A handoff request belongs to the dispatch
49
+ # message (`message_id`, which batch logic relies on) but happens because the batch's last answer arrived, so its card
50
+ # and answer sit under that answer; later messages (the user asking something meanwhile) then stay above it.
51
+ ANCHOR_SQL = """CASE WHEN r.purpose='handoff' THEN COALESCE((SELECT MAX(a.answer_id) FROM member_requests a
52
+ WHERE a.room_id=r.room_id AND a.batch_id=r.message_id AND a.purpose<>'handoff'), r.message_id) ELSE r.message_id END"""
46
53
  # Rounds used and allowed in the room's recent chains, for "第 2/3 轮"; parameters: the room's limit, the room.
47
54
  CHAIN_PROGRESS_SQL = """SELECT b.chain_id,COUNT(*) AS used,? + COALESCE((SELECT h.extra_rounds FROM free_chain_holds h
48
55
  WHERE h.room_id=b.room_id AND h.chain_id=b.chain_id),0) AS allowed FROM free_batches b WHERE b.room_id=?
@@ -526,6 +533,38 @@ def member_labels(connection, room_id):
526
533
  "SELECT name,label FROM member_labels WHERE room_id=? ORDER BY name", (room_id,))}
527
534
 
528
535
 
536
+ def member_display_names(connection, room_id):
537
+ """What each present member is called in text: the owner's label, else its platform nickname, else its ID."""
538
+ labels = member_labels(connection, room_id)
539
+ return {row["name"]: labels.get(row["name"]) or default_label(row["name"]) for row in connection.execute(
540
+ "SELECT name FROM room_members WHERE room_id=? AND status<>'removed' ORDER BY id", (room_id,))}
541
+
542
+
543
+ def member_handles(connection, room_id):
544
+ """Every way to name a present member, mapped to its ID: the ID, and its display name unless another member shares it."""
545
+ shown = member_display_names(connection, room_id)
546
+ handles = {name: name for name in shown}
547
+ taken = list(shown.values())
548
+ for name, label in shown.items():
549
+ if label not in handles and taken.count(label) == 1:
550
+ handles[label] = name
551
+ return handles
552
+
553
+
554
+ def resolve_member_fields(connection, room_id, payload):
555
+ """Turn display names in a write's member fields into IDs, so members are named as they are seen; IDs still work."""
556
+ handles = member_handles(connection, room_id)
557
+ resolve = lambda value: handles.get(value.strip(), value) if isinstance(value, str) else value
558
+ if isinstance(payload.get("to"), list):
559
+ payload["to"] = [resolve(item) for item in payload["to"]]
560
+ for key in ("next_review", "expert"):
561
+ if key in payload:
562
+ payload[key] = resolve(payload[key])
563
+ if isinstance(payload.get("turns"), list):
564
+ payload["turns"] = [{**item, "expert": resolve(item.get("expert"))} if isinstance(item, dict) else item for item in payload["turns"]]
565
+ return payload
566
+
567
+
529
568
  def label_members(connection, room_id, token, payload):
530
569
  """Owner sets members' display labels by name; an empty text removes that member's label."""
531
570
  require_owner(connection, room_id, token)
@@ -593,12 +632,43 @@ def execution_timeline(connection, room_id, after=0):
593
632
  "segment_limit": MAX_EXECUTION_SEGMENTS}
594
633
 
595
634
 
635
+ def room_event_log(connection, room_id, token, after=0, before=0):
636
+ """Read the owner's append-only room event log written by the schema triggers.
637
+
638
+ Args:
639
+ after: return events with a larger id (live follow), oldest first.
640
+ before: with no `after`, return the page of events just below this id (0 = the newest page), oldest first.
641
+
642
+ Returns:
643
+ `events` (id, at in epoch ms, kind, data) ordered by `at` then id, `members` id → name for resolving
644
+ `member_id`/`host_id`, `first_id`/`last_id` as paging cursors, and `more` when older events remain.
645
+ A write request (`api.*`) is stored after it finishes, so its id follows the changes it caused; its `at` is
646
+ when it arrived, and ordering by `at` puts the input before its effects.
647
+ """
648
+ require_owner(connection, room_id, token)
649
+ after, before = max(0, int(after)), max(0, int(before))
650
+ if after:
651
+ rows = connection.execute("SELECT id,at,kind,data FROM room_events WHERE room_id=? AND id>? ORDER BY id LIMIT ?",
652
+ (room_id, after, ROOM_EVENT_PAGE)).fetchall()
653
+ else:
654
+ rows = connection.execute("""SELECT id,at,kind,data FROM room_events WHERE room_id=? AND (?=0 OR id<?)
655
+ ORDER BY id DESC LIMIT ?""", (room_id, before, before, ROOM_EVENT_PAGE)).fetchall()
656
+ first_id = min((row["id"] for row in rows), default=0)
657
+ more = bool(rows) and not after and connection.execute(
658
+ "SELECT 1 FROM room_events WHERE room_id=? AND id<? LIMIT 1", (room_id, first_id)).fetchone() is not None
659
+ members = {str(row["id"]): row["name"] for row in connection.execute("SELECT id,name FROM room_members WHERE room_id=?", (room_id,))}
660
+ events = sorted(({**dict(row), "data": json.loads(row["data"])} for row in rows), key=lambda event: (event["at"], event["id"]))
661
+ return {"events": events, "members": members, "more": more, "first_id": first_id,
662
+ "last_id": max((row["id"] for row in rows), default=after)}
663
+
664
+
596
665
  def recent_executions(connection, room_id):
597
666
  """Return bounded public reports, including requests no longer in the recent request list."""
598
667
  rows = connection.execute(f"""SELECT e.*,m.name AS member,
599
668
  (SELECT json_group_array(id) FROM member_requests WHERE execution_id=e.id) AS request_ids,
600
669
  (SELECT MAX(answer_id) FROM member_requests WHERE execution_id=e.id) AS answer_id,
601
670
  (SELECT MIN(message_id) FROM member_requests WHERE execution_id=e.id) AS request_message_id,
671
+ (SELECT MIN({ANCHOR_SQL}) FROM member_requests r WHERE r.execution_id=e.id) AS anchor_message_id,
602
672
  (SELECT COUNT(*) FROM execution_notes WHERE execution_id=e.id) AS note_count,
603
673
  (SELECT denials FROM execution_denials WHERE execution_id=e.id) AS denials,{CAPABILITY_COLUMNS},
604
674
  (SELECT json_object('created_at',created_at) FROM late_answers WHERE execution_id=e.id) AS late_answer
@@ -610,7 +680,8 @@ def recent_executions(connection, room_id):
610
680
  requests.append({**dict(request), **session_request_source(connection, request["id"], request["message_id"])})
611
681
  executions.append({**public_execution(row), "member": row["member"], "cwd": row["cwd"], "changes": json.loads(row["changes"] or "[]"),
612
682
  "changes_recorded": bool(row["changes"]), "request_ids": json.loads(row["request_ids"]),
613
- "answer_id": row["answer_id"], "request_message_id": row["request_message_id"], "requests": requests,
683
+ "answer_id": row["answer_id"], "request_message_id": row["request_message_id"],
684
+ "anchor_message_id": row["anchor_message_id"], "requests": requests,
614
685
  "notes": row["note_count"], "denials": json.loads(row["denials"] or "[]"),
615
686
  "capabilities": reported_capabilities(row), "stop_result": row["stop_result"] or "",
616
687
  "late_answer": json.loads(row["late_answer"]) if row["late_answer"] else None,
@@ -898,9 +969,10 @@ def save_suggestions(connection, room_id, message_id, member, payload):
898
969
  active = active_members(connection, room_id)
899
970
  targets = set(named_members(active, payload["to"] if payload.get("to") is not None else [], exclude=member["name"]))
900
971
  prose = mention_prose(str(payload.get("text", "")))
901
- for name, target in active.items():
902
- if name != member["name"] and re.search(r"(^|[\s((,,。;;::])@" + re.escape(name) + r"(?![A-Za-z0-9_\-\u4e00-\u9fff])", prose):
903
- targets.add(target["id"])
972
+ for handle, name in member_handles(connection, room_id).items():
973
+ if name in active and name != member["name"] and re.search(
974
+ r"(^|[\s((,,。;;::])@" + re.escape(handle) + r"(?![A-Za-z0-9_\-\u4e00-\u9fff])", prose):
975
+ targets.add(active[name]["id"])
904
976
  for target_id in targets:
905
977
  connection.execute("INSERT INTO member_suggestions (message_id,member_id) VALUES (?,?)", (message_id, target_id))
906
978
 
@@ -1286,6 +1358,7 @@ def inbox(connection, room_id, token):
1286
1358
  release_recommendations(connection, room_id)
1287
1359
  archived = connection.execute("SELECT archived FROM room_directory WHERE room_id=?", (room_id,)).fetchone()[0]
1288
1360
  members, observers = [], []
1361
+ shown = member_display_names(connection, room_id)
1289
1362
  # Manual members are never delivered to; the runner only reads them so smart scheduling can route to them too.
1290
1363
  for row in connection.execute(f"SELECT * FROM room_members WHERE room_id=? AND status='active' AND {REGISTERED_MEMBER} ORDER BY id", (room_id,)):
1291
1364
  pending = [dict(item) for item in connection.execute("""SELECT r.id,r.message_id,r.purpose,
@@ -1296,6 +1369,7 @@ def inbox(connection, room_id, token):
1296
1369
  LEFT JOIN task_message_sources t ON t.session_request_id=sr.id
1297
1370
  WHERE r.member_id=? AND r.status='pending' ORDER BY r.id""", (row["id"],))]
1298
1371
  member_view = {key: row[key] for key in MEMBER_FIELDS}
1372
+ member_view["label"] = shown.get(row["name"], row["name"])
1299
1373
  member_view["review_capability"] = review_capability(connection, room_id, row["id"], token)
1300
1374
  member_view["held_until"] = held_until(connection, row["id"])
1301
1375
  # The runner compares this with its adapter's effect and reports a change.
@@ -1,31 +1,40 @@
1
- """Participant identities: an AI platform is a type, a room participant is one named instance of it.
2
-
3
- `Claude` and `Claude-review` are two instances of the Claude platform in the same room; each keeps its
4
- own session, model and thinking effort. Platform names alone keep their historical meaning.
5
- """
6
-
7
- import re
8
-
9
- USER = "用户"
10
- AI_PLATFORMS = ("Codex", "Claude", "Antigravity", "DeepSeek", "Qwen", "Grok")
11
- PARTICIPANTS = [USER, *AI_PLATFORMS]
12
- MAX_INSTANCE_SUFFIX = 16
13
- # AI instances one room may hold, across platforms.
14
- MAX_ROOM_AGENTS = 12
15
- INSTANCE_PATTERN = re.compile(rf"({'|'.join(AI_PLATFORMS)})(?:-[0-9A-Za-z_]{{1,{MAX_INSTANCE_SUFFIX}}})?")
16
-
17
-
18
- def is_ai(name):
19
- """Whether a name is an AI platform or a named instance of one."""
20
- return isinstance(name, str) and INSTANCE_PATTERN.fullmatch(name) is not None
21
-
22
-
23
- def is_participant(name):
24
- """Whether a name may author, submit or be scheduled: the user or any AI instance."""
25
- return name == USER or is_ai(name)
26
-
27
-
28
- def platform_of(name):
29
- """Return the platform an AI instance belongs to, or None for the user and invalid names."""
30
- match = INSTANCE_PATTERN.fullmatch(name) if isinstance(name, str) else None
31
- return match.group(1) if match else None
1
+ """Participant identities: an AI platform is a type, a room participant is one named instance of it.
2
+
3
+ `Claude` and `Claude-review` are two instances of the Claude platform in the same room; each keeps its
4
+ own session, model and thinking effort. Platform names alone keep their historical meaning.
5
+ """
6
+
7
+ import re
8
+
9
+ USER = "用户"
10
+ AI_PLATFORMS = ("Codex", "Claude", "Antigravity", "DeepSeek", "Qwen", "Grok")
11
+ PARTICIPANTS = [USER, *AI_PLATFORMS]
12
+ MAX_INSTANCE_SUFFIX = 16
13
+ # AI instances one room may hold, across platforms.
14
+ MAX_ROOM_AGENTS = 12
15
+ INSTANCE_PATTERN = re.compile(rf"({'|'.join(AI_PLATFORMS)})(?:-[0-9A-Za-z_]{{1,{MAX_INSTANCE_SUFFIX}}})?")
16
+ # The name people and AIs call a platform when it differs from its ID; web/app.js PLATFORM_IDENTITIES shows the same.
17
+ PLATFORM_NICKNAMES = {"Antigravity": "Gemini"}
18
+
19
+
20
+ def is_ai(name):
21
+ """Whether a name is an AI platform or a named instance of one."""
22
+ return isinstance(name, str) and INSTANCE_PATTERN.fullmatch(name) is not None
23
+
24
+
25
+ def is_participant(name):
26
+ """Whether a name may author, submit or be scheduled: the user or any AI instance."""
27
+ return name == USER or is_ai(name)
28
+
29
+
30
+ def platform_of(name):
31
+ """Return the platform an AI instance belongs to, or None for the user and invalid names."""
32
+ match = INSTANCE_PATTERN.fullmatch(name) if isinstance(name, str) else None
33
+ return match.group(1) if match else None
34
+
35
+
36
+ def default_label(name):
37
+ """The name an instance goes by without an owner's label: its platform's nickname plus its own suffix (Antigravity-2 → Gemini-2)."""
38
+ platform = platform_of(name)
39
+ nickname = PLATFORM_NICKNAMES.get(platform)
40
+ return nickname + name[len(platform):] if nickname else name