ai_co_discuss 0.3.41 → 0.3.42
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/.agents/skills/discuss-host/references/free.md +1 -1
- package/.agents/skills/discuss-host/references/workflow-draft.md +13 -4
- package/docs/runner.md +146 -145
- package/package.json +1 -1
- package/runner/discussion_launch.mjs +68 -4
- package/runner/expert_prompts.mjs +5 -2
- package/runner/free_runner.mjs +1 -0
- package/web/app.js +4279 -4261
- package/web/modern.css +1 -1
- package/web/modern.js +852 -818
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# 起草工作流(主持人)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
用户请你把本房间形成的做法存成工作流,或按某种分工新建一套流程时读本页。
|
|
4
|
+
|
|
5
|
+
- 本次任务有 `host_workflows: true`:房主已为本房间允许你自建。按下面的格式把草稿写成一个文件(只用一段,分段头 `1/1`,不受单条消息长度限制),放在任务的 `draft_directory` 下,然后运行:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
<cli> launch workflow-adopt --draft <草稿文件> --access-file <本次任务的 access_file>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
本机服务会校验并保存到工作流库,在本房间启用,邀请缺少的卡片成员(离开的请回),返回 `members`(卡片 ID → 成员名)。成功后本次执行里就可以按成员名点名派活,不用等用户。之后要改流程,用同一 ID 改草稿再运行一次,成员下一次执行就读到新内容。只能更新本房间绑定的工作流,不能覆盖库里别的工作流;房间已启用另一个工作流时由用户先停用。卡片的项目权限最高到 write,写 auto / full 会被降为 write。命令失败时按报错修正,不要绕过它直接写本机服务目录。
|
|
12
|
+
- 没有该字段:你只起草,在回答里发出草稿,由用户在网页“工作流库 → 粘贴导入”保存;不要自己写入本机服务目录。需要的话告诉用户可以在右栏“工作流”里打开“允许主持人自建工作流”。
|
|
4
13
|
|
|
5
14
|
## 内容
|
|
6
15
|
|
|
@@ -8,7 +17,7 @@
|
|
|
8
17
|
|
|
9
18
|
- **SKILL.md**:用途、工序、每一步找哪个角色、产物放在项目哪里。只按角色的显示名、描述和产物路径说话,不引用卡片提示词的内容(API 主持人读不到卡片);各角色共同遵守的约束直接写在这里。它会被别人拿去其他项目用:不写本项目的具体事实、本机路径或密钥,项目信息写成“读项目 README 找到……”。正文最多 20,000 字。
|
|
10
19
|
- **卡片**:每个角色一张,就是专家预设,最多 30 张。`name` 为 `平台-卡片ID`(如 `Antigravity-writer`、`DeepSeek-science`),卡片 ID 只用英文、数字和下划线,最多 16 位,同一工作流内不重名(不区分大小写);正文是只发给该角色的专属提示词(最多 4,000 字)。你读不到成员现有的专属提示词,可以留空,用户会在网页用“从房间成员导入卡片”取原文。主持人不设卡片。
|
|
11
|
-
-
|
|
20
|
+
- 只写这次房间里确实验证过的做法;用户直接给了分工、还没跑过时,按用户的分工写,跑过后再用同一 ID 修订。
|
|
12
21
|
|
|
13
22
|
## 格式
|
|
14
23
|
|
|
@@ -28,11 +37,11 @@ description: 一句话说明,不换行
|
|
|
28
37
|
name: Antigravity-writer
|
|
29
38
|
label: 执笔
|
|
30
39
|
description: 一句话说明,不换行
|
|
31
|
-
project_access:
|
|
40
|
+
project_access: write
|
|
32
41
|
---
|
|
33
42
|
(专属提示词)
|
|
34
43
|
```
|
|
35
44
|
|
|
36
45
|
- `id` 只用小写英文、数字、下划线和连字符,1—40 位;分段头的 `id` 与 SKILL.md 的 `name` 相同。
|
|
37
46
|
- 头信息只支持单行 `key: value`;卡片可写 `name`、`label`、`description`、`project_access`(read / write / auto / full)、`model`、`effort`,其余不写。
|
|
38
|
-
-
|
|
47
|
+
- 在回答里发草稿、一条消息装不下(正文上限约 32,000 字)时拆成多条,最多 20 段;每条都带分段头 `序号/总数`,同一文件不要拆到两条里。
|
package/docs/runner.md
CHANGED
|
@@ -1,145 +1,146 @@
|
|
|
1
|
-
# 安装与接入
|
|
2
|
-
|
|
3
|
-
[English](en/runner.md) | 简体中文
|
|
4
|
-
|
|
5
|
-
本机服务在各 AI 的原会话中唤醒它们,或调用你配置的模型 API。网站本身不运行模型,也不保存密钥。
|
|
6
|
-
|
|
7
|
-
## 支持的软件
|
|
8
|
-
|
|
9
|
-
| 软件 | 接入方式 |
|
|
10
|
-
| --- | --- |
|
|
11
|
-
| Claude Code CLI | 自动 |
|
|
12
|
-
| Codex CLI(0.163.0+)/ Codex App | 自动;App 须保持运行 |
|
|
13
|
-
| Antigravity CLI(`agy`)/ Antigravity App | 自动 |
|
|
14
|
-
| Cursor CLI | 手动 |
|
|
15
|
-
| 模型 API | 自动,见 [API 接入](#api-接入) |
|
|
16
|
-
|
|
17
|
-
使用前须在对应软件中完成安装与登录。
|
|
18
|
-
|
|
19
|
-
## npm 安装与本机配置
|
|
20
|
-
|
|
21
|
-
需要 Node.js 24+ 与 Python 3.10+。
|
|
22
|
-
|
|
23
|
-
```sh
|
|
24
|
-
npm install --global ai_co_discuss@latest
|
|
25
|
-
codiscuss setup
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
`setup` 检测本机 CLI,保存软件配置、安装讨论技能,然后启动本机网站(`http://127.0.0.1:8765`)并打开浏览器。非交互环境可用 `codiscuss setup --yes`,用 `--clients codex,claude` 指定软件。结果提示“待处理”时,按提示处理后重跑 `setup`。
|
|
29
|
-
|
|
30
|
-
**升级**:先 `codiscuss local stop`,再运行 `npm install --global --prefer-online ai_co_discuss@latest` 和 `codiscuss setup`。
|
|
31
|
-
|
|
32
|
-
**卸载**:先 `codiscuss local stop`,再 `npm uninstall --global ai_co_discuss`。讨论数据保存在 `~/.codiscuss`,卸载不会删除。本机服务会自动清理已删除房间的本机文件,并控制日志大小;也可运行 `codiscuss service clean` 立即清理。
|
|
33
|
-
|
|
34
|
-
## 两种讨论入口与启动配置
|
|
35
|
-
|
|
36
|
-
### 从网页发起
|
|
37
|
-
|
|
38
|
-
运行 `codiscuss local open`,点击“新建房间”,添加成员并指定主持人。启动失败时刷新页面,可沿用原请求继续,不会重复建房。
|
|
39
|
-
|
|
40
|
-
### 浏览项目文件
|
|
41
|
-
|
|
42
|
-
房间设置了项目目录后,左栏“房间”下方的“文件”页可浏览电脑上的文件夹与文件,查看大小和修改时间。点击文件打开悬浮预览,支持文本、Markdown 渲染与 HTML 静态渲染,并可切换源码。未设置项目目录时入口灰显。
|
|
43
|
-
|
|
44
|
-
其他设备通过已配对电脑的加密连接读取同一目录。文件内容最多预览前 128 KB,支持 UTF-8 和带 BOM 的 UTF-16;HTML 展示内联样式与内嵌图片。文件访问限于房间项目目录,目录外的链接灰显。
|
|
45
|
-
|
|
46
|
-
### 从 AI 对话发起
|
|
47
|
-
|
|
48
|
-
在要担任主持人的本地 AI 对话中调用 `discuss-host` 和 `discuss-participant`,说明站点、议题、参与者与上限。主持技能沿用本机软件配置建房,当前会话继续主持。agy 不能在自己的对话中主持全自动讨论。
|
|
49
|
-
|
|
50
|
-
### 线上网页连接本机
|
|
51
|
-
|
|
52
|
-
使用自己部署的线上网站时,在运行 AI 的电脑上执行一次:
|
|
53
|
-
|
|
54
|
-
```sh
|
|
55
|
-
codiscuss local connect https://你的站点
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
之后在这台电脑的浏览器中登录网站管理员即完成配对,线上网页显示的就是这台电脑上的房间。线上网站本身不保存房间,任何设备打开都经中转读写电脑上的房间,需先部署中转(见[部署指南](deployment.md)“中转”)。连接链接带有本机启动权限,不要转发;撤销使用 `codiscuss local disconnect https://你的站点`。
|
|
59
|
-
|
|
60
|
-
### 在其他设备上远程控制
|
|
61
|
-
|
|
62
|
-
配对后在手机或平板上登录同一账号,只要这台电脑开机并运行 codiscuss,就能像在电脑上一样操作:建房、邀请与请回成员、翻开卡片设置软件、模型和权限、录入 API 密钥、管理常用项目等。这些操作由网站转交给电脑执行,内容端到端加密,网站只经手密文。项目目录在平板上直接输入电脑上的完整路径。
|
|
63
|
-
|
|
64
|
-
登录后目录“协作房间”直接列出电脑上的房间,与在电脑上看到的一致,可以发消息、上传附件、管理成员、导出备份,并实时更新。对话只保存在电脑上,网站只转交加密内容。
|
|
65
|
-
|
|
66
|
-
- 电脑关机或 codiscuss 未运行时看不到房间,目录标题提示“电脑未连接”或“电脑离线”;电脑上线后页面自动连上。
|
|
67
|
-
- 电脑没有及时答复时,页面提示“结果待确认”,不会自动重发;请先查看房间确认是否已生效。
|
|
68
|
-
|
|
69
|
-
### 外出时经 Cloudflare Tunnel 打开本机网站
|
|
70
|
-
|
|
71
|
-
可选做法,需要一个托管在 Cloudflare 的自有域名,并开通 Zero Trust 免费版。对话只保存在这台电脑上,外出时用手机经 [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 打开本机网站;电脑关机或 codiscuss 未运行时无法访问。
|
|
72
|
-
|
|
73
|
-
1. 开启远程入口。它只监听 `127.0.0.1`,与本机网站共用数据和实时推送,但访问者必须登录管理员,也不提供本机启动入口:
|
|
74
|
-
|
|
75
|
-
```sh
|
|
76
|
-
codiscuss setup --remote-port 8766
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
按提示重启本机网站后生效;`--remote-port off` 关闭。
|
|
80
|
-
2. 安装 `cloudflared`,把一个子域名转发到 `http://127.0.0.1:8766`。**不要转发本机网站端口(默认 8765)**,那里的访问者无需登录即为管理员。
|
|
81
|
-
3. 在 Cloudflare Access 中为这个子域名设置登录(如邮箱验证码)。远程入口与其他线上部署一样没有读取权限隔离,不设 Access 等于公开全部对话。
|
|
82
|
-
4. 首次打开子域名,在左下角注册管理员;安装令牌写在 `~/.codiscuss/local/website.log` 中 `Remote entry` 之后的启动日志里。
|
|
83
|
-
5. 需要在手机上建房、邀请成员等本机操作时,按上一节在电脑浏览器中对这个子域名执行 `codiscuss local connect` 并登录完成配对,再在 Access 中对 `/api/machines/` 路径设置 Bypass,让电脑能领取转交命令;该路径由管理员会话或电脑凭据校验。
|
|
84
|
-
|
|
85
|
-
## API 接入
|
|
86
|
-
|
|
87
|
-
翻开成员卡片选择 API,填写密钥并选择模型。默认提供 OpenAI、Anthropic、Google Gemini、DeepSeek、阿里云百炼(Qwen)、xAI(Grok)的官方地址;OpenRouter 等兼容服务填写其 Base URL 并选择 Chat Completions。
|
|
88
|
-
|
|
89
|
-
API 成员可以担任专家或主持人。房间指定了项目目录时,它可以用接入程序提供的文件工具读取、搜索项目文件;项目权限设为可写时还能新建和编辑文件,但没有命令行,不能运行测试或构建。`.env`、私钥等密钥文件、`.git` 目录和本机服务目录始终不开放,读到的内容会发给你配置的模型服务商。密钥只保存在本机,调用费用按你的账号计费。
|
|
90
|
-
|
|
91
|
-
## 智能调度(可选)
|
|
92
|
-
|
|
93
|
-
建房时在“智能调度”下选择供应商并填写密钥,需要连接本机服务。卡片下方的“判断方式”可选“引导(试验)”(默认,正在打磨)或“稳定”(已通过回归评测),影响未点名消息的分派和讨论中的插话、下一位发言、下一轮判断。作用见 [使用指南](workflow.md#智能调度)。
|
|
94
|
-
|
|
95
|
-
翻开成员卡片,“提示”一行可以给这位专家单独写几句要求,例如“回答先给结论”。它只发给这位专家,不改变权限;部分软件带有预设,可随时恢复。卡片背面的书签图标把这张卡片的描述、提示词、项目权限、模型与强度存为专家预设,之后在“添加”行一键加入,见 [使用指南](workflow.md#成员管理)。
|
|
96
|
-
|
|
97
|
-
## 工作流(可选)
|
|
98
|
-
|
|
99
|
-
工作流把一种多人协作的做法存下来,供别的房间复用:一份给主持人读的做法说明(SKILL.md),加一组 Agent 卡片(每张就是一个专家预设)。建房或邀请时在“工作流”一行选中它,确认卡片后作为成员加入;主持人此后按说明组织工作。它只带来做法和成员,不保证工序,具体怎么做仍由主持人判断,你随时可以 @ 任何成员。
|
|
100
|
-
|
|
101
|
-
- **保存**:点“工作流库”新建或编辑;也可以请房间主持人“把这个房间的做法存成工作流”,复制它起草的消息,在工作流库“粘贴导入”。编辑页可以“从当前房间成员导入”卡片,提示词保持原文。
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
-
|
|
144
|
-
- **
|
|
145
|
-
-
|
|
1
|
+
# 安装与接入
|
|
2
|
+
|
|
3
|
+
[English](en/runner.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
本机服务在各 AI 的原会话中唤醒它们,或调用你配置的模型 API。网站本身不运行模型,也不保存密钥。
|
|
6
|
+
|
|
7
|
+
## 支持的软件
|
|
8
|
+
|
|
9
|
+
| 软件 | 接入方式 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Claude Code CLI | 自动 |
|
|
12
|
+
| Codex CLI(0.163.0+)/ Codex App | 自动;App 须保持运行 |
|
|
13
|
+
| Antigravity CLI(`agy`)/ Antigravity App | 自动 |
|
|
14
|
+
| Cursor CLI | 手动 |
|
|
15
|
+
| 模型 API | 自动,见 [API 接入](#api-接入) |
|
|
16
|
+
|
|
17
|
+
使用前须在对应软件中完成安装与登录。
|
|
18
|
+
|
|
19
|
+
## npm 安装与本机配置
|
|
20
|
+
|
|
21
|
+
需要 Node.js 24+ 与 Python 3.10+。
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install --global ai_co_discuss@latest
|
|
25
|
+
codiscuss setup
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`setup` 检测本机 CLI,保存软件配置、安装讨论技能,然后启动本机网站(`http://127.0.0.1:8765`)并打开浏览器。非交互环境可用 `codiscuss setup --yes`,用 `--clients codex,claude` 指定软件。结果提示“待处理”时,按提示处理后重跑 `setup`。
|
|
29
|
+
|
|
30
|
+
**升级**:先 `codiscuss local stop`,再运行 `npm install --global --prefer-online ai_co_discuss@latest` 和 `codiscuss setup`。
|
|
31
|
+
|
|
32
|
+
**卸载**:先 `codiscuss local stop`,再 `npm uninstall --global ai_co_discuss`。讨论数据保存在 `~/.codiscuss`,卸载不会删除。本机服务会自动清理已删除房间的本机文件,并控制日志大小;也可运行 `codiscuss service clean` 立即清理。
|
|
33
|
+
|
|
34
|
+
## 两种讨论入口与启动配置
|
|
35
|
+
|
|
36
|
+
### 从网页发起
|
|
37
|
+
|
|
38
|
+
运行 `codiscuss local open`,点击“新建房间”,添加成员并指定主持人。启动失败时刷新页面,可沿用原请求继续,不会重复建房。
|
|
39
|
+
|
|
40
|
+
### 浏览项目文件
|
|
41
|
+
|
|
42
|
+
房间设置了项目目录后,左栏“房间”下方的“文件”页可浏览电脑上的文件夹与文件,查看大小和修改时间。点击文件打开悬浮预览,支持文本、Markdown 渲染与 HTML 静态渲染,并可切换源码。未设置项目目录时入口灰显。
|
|
43
|
+
|
|
44
|
+
其他设备通过已配对电脑的加密连接读取同一目录。文件内容最多预览前 128 KB,支持 UTF-8 和带 BOM 的 UTF-16;HTML 展示内联样式与内嵌图片。文件访问限于房间项目目录,目录外的链接灰显。
|
|
45
|
+
|
|
46
|
+
### 从 AI 对话发起
|
|
47
|
+
|
|
48
|
+
在要担任主持人的本地 AI 对话中调用 `discuss-host` 和 `discuss-participant`,说明站点、议题、参与者与上限。主持技能沿用本机软件配置建房,当前会话继续主持。agy 不能在自己的对话中主持全自动讨论。
|
|
49
|
+
|
|
50
|
+
### 线上网页连接本机
|
|
51
|
+
|
|
52
|
+
使用自己部署的线上网站时,在运行 AI 的电脑上执行一次:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
codiscuss local connect https://你的站点
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
之后在这台电脑的浏览器中登录网站管理员即完成配对,线上网页显示的就是这台电脑上的房间。线上网站本身不保存房间,任何设备打开都经中转读写电脑上的房间,需先部署中转(见[部署指南](deployment.md)“中转”)。连接链接带有本机启动权限,不要转发;撤销使用 `codiscuss local disconnect https://你的站点`。
|
|
59
|
+
|
|
60
|
+
### 在其他设备上远程控制
|
|
61
|
+
|
|
62
|
+
配对后在手机或平板上登录同一账号,只要这台电脑开机并运行 codiscuss,就能像在电脑上一样操作:建房、邀请与请回成员、翻开卡片设置软件、模型和权限、录入 API 密钥、管理常用项目等。这些操作由网站转交给电脑执行,内容端到端加密,网站只经手密文。项目目录在平板上直接输入电脑上的完整路径。
|
|
63
|
+
|
|
64
|
+
登录后目录“协作房间”直接列出电脑上的房间,与在电脑上看到的一致,可以发消息、上传附件、管理成员、导出备份,并实时更新。对话只保存在电脑上,网站只转交加密内容。
|
|
65
|
+
|
|
66
|
+
- 电脑关机或 codiscuss 未运行时看不到房间,目录标题提示“电脑未连接”或“电脑离线”;电脑上线后页面自动连上。
|
|
67
|
+
- 电脑没有及时答复时,页面提示“结果待确认”,不会自动重发;请先查看房间确认是否已生效。
|
|
68
|
+
|
|
69
|
+
### 外出时经 Cloudflare Tunnel 打开本机网站
|
|
70
|
+
|
|
71
|
+
可选做法,需要一个托管在 Cloudflare 的自有域名,并开通 Zero Trust 免费版。对话只保存在这台电脑上,外出时用手机经 [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 打开本机网站;电脑关机或 codiscuss 未运行时无法访问。
|
|
72
|
+
|
|
73
|
+
1. 开启远程入口。它只监听 `127.0.0.1`,与本机网站共用数据和实时推送,但访问者必须登录管理员,也不提供本机启动入口:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
codiscuss setup --remote-port 8766
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
按提示重启本机网站后生效;`--remote-port off` 关闭。
|
|
80
|
+
2. 安装 `cloudflared`,把一个子域名转发到 `http://127.0.0.1:8766`。**不要转发本机网站端口(默认 8765)**,那里的访问者无需登录即为管理员。
|
|
81
|
+
3. 在 Cloudflare Access 中为这个子域名设置登录(如邮箱验证码)。远程入口与其他线上部署一样没有读取权限隔离,不设 Access 等于公开全部对话。
|
|
82
|
+
4. 首次打开子域名,在左下角注册管理员;安装令牌写在 `~/.codiscuss/local/website.log` 中 `Remote entry` 之后的启动日志里。
|
|
83
|
+
5. 需要在手机上建房、邀请成员等本机操作时,按上一节在电脑浏览器中对这个子域名执行 `codiscuss local connect` 并登录完成配对,再在 Access 中对 `/api/machines/` 路径设置 Bypass,让电脑能领取转交命令;该路径由管理员会话或电脑凭据校验。
|
|
84
|
+
|
|
85
|
+
## API 接入
|
|
86
|
+
|
|
87
|
+
翻开成员卡片选择 API,填写密钥并选择模型。默认提供 OpenAI、Anthropic、Google Gemini、DeepSeek、阿里云百炼(Qwen)、xAI(Grok)的官方地址;OpenRouter 等兼容服务填写其 Base URL 并选择 Chat Completions。
|
|
88
|
+
|
|
89
|
+
API 成员可以担任专家或主持人。房间指定了项目目录时,它可以用接入程序提供的文件工具读取、搜索项目文件;项目权限设为可写时还能新建和编辑文件,但没有命令行,不能运行测试或构建。`.env`、私钥等密钥文件、`.git` 目录和本机服务目录始终不开放,读到的内容会发给你配置的模型服务商。密钥只保存在本机,调用费用按你的账号计费。
|
|
90
|
+
|
|
91
|
+
## 智能调度(可选)
|
|
92
|
+
|
|
93
|
+
建房时在“智能调度”下选择供应商并填写密钥,需要连接本机服务。卡片下方的“判断方式”可选“引导(试验)”(默认,正在打磨)或“稳定”(已通过回归评测),影响未点名消息的分派和讨论中的插话、下一位发言、下一轮判断。作用见 [使用指南](workflow.md#智能调度)。
|
|
94
|
+
|
|
95
|
+
翻开成员卡片,“提示”一行可以给这位专家单独写几句要求,例如“回答先给结论”。它只发给这位专家,不改变权限;部分软件带有预设,可随时恢复。卡片背面的书签图标把这张卡片的描述、提示词、项目权限、模型与强度存为专家预设,之后在“添加”行一键加入,见 [使用指南](workflow.md#成员管理)。
|
|
96
|
+
|
|
97
|
+
## 工作流(可选)
|
|
98
|
+
|
|
99
|
+
工作流把一种多人协作的做法存下来,供别的房间复用:一份给主持人读的做法说明(SKILL.md),加一组 Agent 卡片(每张就是一个专家预设)。建房或邀请时在“工作流”一行选中它,确认卡片后作为成员加入;主持人此后按说明组织工作。它只带来做法和成员,不保证工序,具体怎么做仍由主持人判断,你随时可以 @ 任何成员。
|
|
100
|
+
|
|
101
|
+
- **保存**:点“工作流库”新建或编辑;也可以请房间主持人“把这个房间的做法存成工作流”,复制它起草的消息,在工作流库“粘贴导入”。编辑页可以“从当前房间成员导入”卡片,提示词保持原文。
|
|
102
|
+
- **让主持人自己建**:建房时或在右栏“工作流”勾选“允许主持人自建工作流”后,直接对主持人说要什么分工(或“把这个房间的做法存成工作流”),它会自己保存、在本房间启用,并邀请工作流里的成员,接着就按流程派活,不需要你导入或添加成员。之后让它改流程,改动从成员下一次执行起生效。主持人带来的成员项目权限最高为可写;只有本地软件担任的主持人能这样做,API 主持人不行。
|
|
103
|
+
- **分享**:工作流存在本机服务目录的 `workflows` 文件夹,一个工作流一个文件夹;编辑页可复制路径,复制整个文件夹即可分享。导入时会列出每张卡片的提示词和项目权限;要求写文件的卡片默认按只读导入,逐张勾选后才放开。
|
|
104
|
+
- **修改与停用**:修改工作流会作用于所有启用它的房间,成员下一次执行就按新内容;工作流卡片的提示词弹窗提供“去工作流库编辑”。右栏显示工作流状态并可停用:它带来的成员离开(主持人保留),正在进行的工作随之停止;重新启用先打开邀请面板,确认后请回原成员并加入你保留的新增卡片。
|
|
105
|
+
|
|
106
|
+
## 朗读与语音输入(可选)
|
|
107
|
+
|
|
108
|
+
在火山引擎控制台开通「语音合成2.0」(朗读)和「录音文件识别2.0」(语音输入),复制 API Key,填进网页“设置”的“语音”。朗读可设一个通用音色,并按 AI 平台(Claude、Codex、Gemini 等)分别配音色,没配的平台、你自己的消息和程序消息用通用音色;还可选语速、改语气提示(默认是“像给朋友讲故事一样”的自然语气)。密钥只保存在本机,费用按你的账号计费。
|
|
109
|
+
|
|
110
|
+
- 朗读:每条消息的工具栏有“朗读”(手机上在“…”菜单里),在消息里选中文字后点“听当前”从选中处开始;读完这条会接着读后面的消息直到最新,点输入框右下角的播报按钮开启语音播报:之后的新回复会自动朗读回答摘要(没写摘要的短回答读全文,长回答读第一段),主持人的发言会报“主持人”;讨论中还会提示新一轮的发言顺序和轮到谁,执行失败、操作待你允许和讨论结束也会播一句。同时到达的回复依次播报,积压较多时只播最新 3 条。锁屏或切到其他 App 时继续播放,锁屏界面和耳机按键可以暂停、继续、跳到下一条;读过的段落会缓存在电脑上,重听不再计费。正在读的句子会高亮,点一下高亮的句子暂停,再点继续;代码块跳过。朗读时消息区底部有“跟随”和“退出”:页面默认不随朗读滚动,点“跟随”后才把正在读的句子保持在视野内,手动滚动一下就取消跟随。
|
|
111
|
+
- 语音输入:点输入框右下角的麦克风开始说话,再点一次转成文字插入输入框,单次最长 5 分钟。浏览器会先请求麦克风权限。
|
|
112
|
+
|
|
113
|
+
两者都由本机服务调用火山引擎,手机等其他设备经已配对电脑使用,电脑关机时不可用。
|
|
114
|
+
|
|
115
|
+
## 权限
|
|
116
|
+
|
|
117
|
+
项目权限按程序启动的 CLI 与 API 成员设置,默认只读;可选只读、可写、自动、全放开,软件不支持的档位灰显(agy 后台不支持自动,API 只有只读和可写)。只读档预先放行读写房间的 `codiscuss` 命令和答案草稿,可写档允许修改项目、运行命令;自动档由软件审核操作,全放开跳过权限确认。具体范围见档位的悬停说明。Codex 使用原生沙箱,agy 使用独立项目规则,Claude Code 中你已有的授权仍然生效;手动启动成员的权限沿用原会话设置。
|
|
118
|
+
|
|
119
|
+
## 常驻讨论服务
|
|
120
|
+
|
|
121
|
+
`codiscuss local open` 会同时启动讨论服务。一个服务托管所有房间;停止服务会暂停它托管的讨论。
|
|
122
|
+
|
|
123
|
+
| 操作 | 命令 |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| 打开本机网站 | `codiscuss local open` |
|
|
126
|
+
| 查看状态 | `codiscuss local status` |
|
|
127
|
+
| 停止 | `codiscuss local stop` |
|
|
128
|
+
| 重启讨论服务(使新环境变量生效,须在已有新值的终端执行) | `codiscuss service restart` |
|
|
129
|
+
| 登录时自动启动(Windows) | `codiscuss service autostart on` |
|
|
130
|
+
|
|
131
|
+
## 命令行
|
|
132
|
+
|
|
133
|
+
主持人可用 `codiscuss review roster ROOM` 查看本机服务上报的只读评审候选,当前限程序启动的 Codex CLI。此命令只读配置事实,不会自动派发评审;已有授权仍须在实际投递时核对。
|
|
134
|
+
|
|
135
|
+
读取后,派实现任务可携带 `--next-review NAME --review-scope 范围 --review-roster REVISION`,将下一步评审绑定本批次;评审者不能同时是本批实现者。批次完成后,主持人先用 `review ready ROOM --batch ID` 核对完整交付,再用 `review advance ROOM --batch ID --revision REV --submission-id UUID --as 本人` 推进既定评审;不能推进时会说明原因。开启智能调度后可代办这一步;没有配置、判断失败或把握不足时仍交主持人。
|
|
136
|
+
|
|
137
|
+
`codiscuss` 同时供 AI 读写房间。`codiscuss --help` 列出全部命令,`codiscuss schema <命令>` 显示字段,都可离线使用。
|
|
138
|
+
|
|
139
|
+
讨论与普通协作统一读取请求并提交回答。成员回答使用 `message post --kind chat`,自动任务绑定 `--execution`,手动回答绑定 `--request`;主持人先读 `turn roster`,再带返回的 revision 创建或继续安排。讨论开关先用 `run view` 读取 revision,再执行开始、暂停、继续或结束;具体字段以命令帮助和房间协议为准。主持任务附有调度建议时,可用 `state decision ROOM DECISION_ID` 读取对应原判断;建议注明未生效时仍由主持人自行决定。
|
|
140
|
+
|
|
141
|
+
## 常见问题
|
|
142
|
+
|
|
143
|
+
- **软件送达失败**:查看成员状态与原位失败卡,在对应软件中处理登录或权限;倒计时期间可以立即重试或停止。三次重试用尽后,讨论专家的失败交给主持人处理;也可跳过本次发言。
|
|
144
|
+
- **Codex 提示会话被占用**:原聊天仍被 Codex App 占用,可在进度看板切换到 App 接入。
|
|
145
|
+
- **Windows 报告两处数据路径**:从普通终端管理服务,或设置环境变量 `DISCUSS_SERVICE_HOME`。
|
|
146
|
+
- **结果不明**:已交付但无法确认的任务不会自动重发,请先在原会话与房间中核对。
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ import { stt_part } from './stt.mjs';
|
|
|
21
21
|
import { access_levels, BUILTIN_EXPERT_PRESETS, MAX_EXPERT_PROMPT, PROJECT_ACCESS } from './expert_prompts.mjs';
|
|
22
22
|
import { acquire_lock, api, DiscussionStore, service_drafts, validate_config } from './discussion_runner.mjs';
|
|
23
23
|
import { FreeRoomRunner, free_task_files } from './free_runner.mjs';
|
|
24
|
-
import { bind_room_workflow, delete_workflow, get_workflow, list_workflows, load_room_workflow, preview_workflow, save_workflow } from './workflows.mjs';
|
|
24
|
+
import { bind_room_workflow, delete_workflow, get_workflow, list_workflows, load_room_workflow, parse_workflow_draft, preview_workflow, save_workflow } from './workflows.mjs';
|
|
25
25
|
import { assert_shared_home, CONNECTION_FIELDS, configuration_snapshot, read_registry, registered_identity, register_room, service_home, service_pid, start_service } from './discussion_service.mjs';
|
|
26
26
|
|
|
27
27
|
// A platform is a type; `Claude-review` is another Claude instance with its own session, model and effort. An ID is
|
|
@@ -77,6 +77,7 @@ const HELP = `用法:codiscuss launch settings [--file FILE]
|
|
|
77
77
|
codiscuss launch presets [--file FILE]
|
|
78
78
|
codiscuss launch machine-link --file FILE
|
|
79
79
|
codiscuss launch admin-account --file FILE
|
|
80
|
+
codiscuss launch workflow-adopt --draft FILE [--access-file FILE]
|
|
80
81
|
|
|
81
82
|
settings 读取本机软件默认配置;指定文件时保存,不启动 AI。
|
|
82
83
|
start 使用启动 JSON 创建或接入房间并启动;--dry-run 只校验和预览。
|
|
@@ -103,6 +104,7 @@ room-member-adapter 把自由房间里空闲的 Codex 成员在 App(codex)
|
|
|
103
104
|
room-profiles 导出房间的本机成员软件配置与实际会话 ID(不含凭据),供备份附带;须提供 site、room_id、token。
|
|
104
105
|
room-restore 把导入房间的本机成员配置登记到本机并核对原会话存在,Claude Code 成员保持手动;须提供 site、room_id、token、local_members。
|
|
105
106
|
room-fork 把从原房间分支出的新房间登记到本机,各本机成员从原会话分叉、不含分支点之后的执行,原会话不变;须提供 site、room_id、token、source_room_id、source_token、executions({成员名: [排除的执行编号]})。
|
|
107
|
+
workflow-adopt 主持人把工作流草稿(一段,分段头 1/1)保存进工作流库并在本房间启用,缺的卡片成员由本机服务邀请;须房主已为本房间打开“允许主持人自建工作流”。凭据为本次任务的 access_file(或 DISCUSS_ACCESS_FILE)。
|
|
106
108
|
JSON 指定 site、entry(conversation/web)、room(建房内容,含 submission_id)
|
|
107
109
|
或 room_id(已有房间)、participants(本次软件覆盖配置)。
|
|
108
110
|
conversation 还须 current_session_id,当前会话接手首个主持任务。
|
|
@@ -308,7 +310,7 @@ export async function room_models(home, raw, { read_defaults = software_defaults
|
|
|
308
310
|
// Smart scheduling is a local choice; only which model decides is shown, never its key.
|
|
309
311
|
const scheduler = config.scheduler ? { provider: config.scheduler.provider, model: config.scheduler.model, mode: config.scheduler.mode || DEFAULT_SCHEDULER_MODE } : null;
|
|
310
312
|
return { room_id: input.room_id, registered: paths.length > 0, participants, scheduler, project_directory: config.cwd || '',
|
|
311
|
-
workflow: room_workflow_status(config, home) };
|
|
313
|
+
workflow: room_workflow_status(config, home), host_workflows: Boolean(config.host_workflows) };
|
|
312
314
|
}
|
|
313
315
|
|
|
314
316
|
/** The room's workflow as the right column shows it; `available` is false when its folder is gone or broken. */
|
|
@@ -537,7 +539,7 @@ function free_launch_config(home, input, room, directory, options) {
|
|
|
537
539
|
if (workflow && Object.values(workflow.members).some(name => !names.includes(name))) throw new Error('工作流成员须在本次邀请的成员中');
|
|
538
540
|
return validate_config({ site: input.site, room: room.id || 'preview', token: options.token, mode: 'free', participants, members, api_home: home,
|
|
539
541
|
scheduler: input.scheduler || undefined, cwd: input.cwd, project_access: input.project_access || undefined, workflow,
|
|
540
|
-
data_directory: join(directory, 'runtime'), draft_directory: service_drafts(home, join(directory, 'runtime')) }, directory);
|
|
542
|
+
host_workflows: input.host_workflows === true || undefined, data_directory: join(directory, 'runtime'), draft_directory: service_drafts(home, join(directory, 'runtime')) }, directory);
|
|
541
543
|
}
|
|
542
544
|
|
|
543
545
|
/** Preserve caller-generated member credentials; journal newly generated ones before creation. */
|
|
@@ -1358,6 +1360,12 @@ export async function room_workflow(home, raw) {
|
|
|
1358
1360
|
const release = acquire_lock(join(dirname(path), 'configuration-edit'));
|
|
1359
1361
|
try {
|
|
1360
1362
|
const config = configuration_snapshot(path).config;
|
|
1363
|
+
if (input.action === 'host') {
|
|
1364
|
+
// The owner's per-room grant that lets the host save and enable workflows by itself (workflow-adopt).
|
|
1365
|
+
const next = { ...config, host_workflows: input.allowed === true || undefined };
|
|
1366
|
+
atomic_json(path, next);
|
|
1367
|
+
return { room_id: input.room_id, host_workflows: Boolean(next.host_workflows) };
|
|
1368
|
+
}
|
|
1361
1369
|
if (input.action === 'enable') {
|
|
1362
1370
|
get_workflow(home, input.workflow?.id);
|
|
1363
1371
|
if (config.workflow?.enabled && config.workflow.id !== input.workflow.id) throw new Error('本房间已启用另一个工作流,请先停用');
|
|
@@ -1366,7 +1374,7 @@ export async function room_workflow(home, raw) {
|
|
|
1366
1374
|
atomic_json(path, next);
|
|
1367
1375
|
return { room_id: input.room_id, workflow: room_workflow_status(next, home) };
|
|
1368
1376
|
}
|
|
1369
|
-
if (input.action !== 'disable') throw new Error('action 须为 enable 或
|
|
1377
|
+
if (input.action !== 'disable') throw new Error('action 须为 enable、disable 或 host');
|
|
1370
1378
|
if (!config.workflow?.enabled) return { room_id: input.room_id, workflow: room_workflow_status(config, home) };
|
|
1371
1379
|
const room = await site_request(input.site, `/api/rooms/${input.room_id}`, input.token);
|
|
1372
1380
|
const names = new Set(Object.values(config.workflow.members || {}));
|
|
@@ -1379,13 +1387,69 @@ export async function room_workflow(home, raw) {
|
|
|
1379
1387
|
} finally { release(); }
|
|
1380
1388
|
}
|
|
1381
1389
|
|
|
1390
|
+
// The most a host-made card may ask for; anything above writing is lowered to it.
|
|
1391
|
+
const HOST_CARD_ACCESS = ['read', 'write'];
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* The host saves its drafted workflow and enables it in its own room, when the owner allowed that for this room
|
|
1395
|
+
* (`host_workflows`). Each card the room lacks is invited as a program-started member, one that left is brought back.
|
|
1396
|
+
* Saving the same ID again updates the workflow; members read it from their next execution.
|
|
1397
|
+
* Returns which member plays which card, so the host can name them in the same execution.
|
|
1398
|
+
*/
|
|
1399
|
+
export async function adopt_workflow(home, { access_file, draft_file }) {
|
|
1400
|
+
if (!access_file || !draft_file) throw new Error('须提供 --draft 草稿文件;凭据用 --access-file 或 DISCUSS_ACCESS_FILE');
|
|
1401
|
+
const access = JSON.parse(readFileSync(resolve(access_file), 'utf8').replace(/^/, ''));
|
|
1402
|
+
const path = registered_room_path(home, String(access.site || '').replace(/\/$/, ''), access.room);
|
|
1403
|
+
if (!path) throw new Error('本机没有登记这个房间');
|
|
1404
|
+
const config = configuration_snapshot(path).config;
|
|
1405
|
+
if (!config.host_workflows) throw new Error('房主没有允许本房间的主持人自建工作流;请把草稿发给用户,由用户在“工作流库 → 粘贴导入”保存');
|
|
1406
|
+
const host = Object.keys(config.members || {}).find(name => config.members[name].token === access.token);
|
|
1407
|
+
const room = await api(config, '');
|
|
1408
|
+
const roster = room.free?.members || [];
|
|
1409
|
+
if (!host || !roster.some(member => member.name === host && member.role === 'host' && member.status === 'active')) throw new Error('只有本房间的主持人能自建工作流');
|
|
1410
|
+
const draft = parse_workflow_draft([readFileSync(resolve(draft_file), 'utf8')]);
|
|
1411
|
+
const bound = config.workflow?.id;
|
|
1412
|
+
if (config.workflow?.enabled && bound !== draft.id) throw new Error(`本房间已启用工作流 ${bound};用同一 ID 更新它,或请用户先停用`);
|
|
1413
|
+
if (bound !== draft.id && list_workflows(home).some(item => item.id === draft.id)) throw new Error(`工作流库已有 ${draft.id},主持人不能覆盖其他工作流,请换一个 ID`);
|
|
1414
|
+
const configured = launch_settings(home).participants;
|
|
1415
|
+
const missing = draft.agents.filter(agent => !configured[platform_of(agent.name || '')]).map(agent => agent.name || agent.card);
|
|
1416
|
+
if (missing.length) throw new Error(`本机没有配置这些卡片的软件:${missing.join('、')}`);
|
|
1417
|
+
const workflow = save_workflow(home, { ...draft, agents: draft.agents.map(agent => agent.project_access && !HOST_CARD_ACCESS.includes(agent.project_access)
|
|
1418
|
+
? { ...agent, project_access: 'write' } : agent) }, validate_preset);
|
|
1419
|
+
const owner = { site: config.site, room_id: config.room, token: config.token };
|
|
1420
|
+
const members = bound === workflow.id ? { ...config.workflow.members } : {};
|
|
1421
|
+
const labels = {}, descriptions = {};
|
|
1422
|
+
for (const agent of workflow.agents) {
|
|
1423
|
+
const current = roster.find(member => member.name === members[agent.card] && member.status !== 'removed');
|
|
1424
|
+
if (current?.status === 'active') continue;
|
|
1425
|
+
if (current?.status === 'away') { await room_member(home, { ...owner, action: 'rejoin', name: current.name }); continue; }
|
|
1426
|
+
const taken = new Set([...roster.map(member => member.name), ...Object.values(members)]);
|
|
1427
|
+
const name = taken.has(agent.name) ? free_instance(agent.name, taken) : agent.name;
|
|
1428
|
+
const profile = Object.fromEntries(['project_access', 'model', 'effort'].filter(field => agent[field]).map(field => [field, agent[field]]));
|
|
1429
|
+
const joined = await room_member(home, { ...owner, action: 'add', name, participants: { [name]: profile } });
|
|
1430
|
+
members[agent.card] = joined.member;
|
|
1431
|
+
if (agent.label) labels[joined.member] = agent.label;
|
|
1432
|
+
if (agent.description) descriptions[joined.member] = agent.description;
|
|
1433
|
+
}
|
|
1434
|
+
if (Object.keys(labels).length) await site_request(config.site, `/api/rooms/${config.room}/labels`, config.token, { labels });
|
|
1435
|
+
if (Object.keys(descriptions).length) await site_request(config.site, `/api/rooms/${config.room}/descriptions`, config.token, { descriptions });
|
|
1436
|
+
const enabled = await room_workflow(home, { ...owner, action: 'enable', workflow: { id: workflow.id, members } });
|
|
1437
|
+
return { ...enabled, members };
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1382
1440
|
/** CLI and local HTTP bridge use the same commands and configuration validation. */
|
|
1383
1441
|
export async function launch_main(args = process.argv.slice(2), input_text) {
|
|
1384
1442
|
const { values, positionals } = parseArgs({ args, allowPositionals: true, options: {
|
|
1385
1443
|
file: { type: 'string' }, 'dry-run': { type: 'boolean' }, browser: { type: 'boolean' },
|
|
1386
1444
|
'configuration-locked': { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
|
|
1445
|
+
draft: { type: 'string' }, 'access-file': { type: 'string' },
|
|
1387
1446
|
} });
|
|
1388
1447
|
if (values.help || !positionals.length) return process.stdout.write(HELP);
|
|
1448
|
+
if (positionals[0] === 'workflow-adopt') {
|
|
1449
|
+
const home = service_home();
|
|
1450
|
+
const result = await adopt_workflow(home, { draft_file: values.draft, access_file: values['access-file'] || process.env.DISCUSS_ACCESS_FILE });
|
|
1451
|
+
return process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
1452
|
+
}
|
|
1389
1453
|
const input = input_text === undefined ? (values.file ? JSON.parse(readFileSync(values.file, 'utf8').replace(/^\uFEFF/, '')) : null) : JSON.parse(input_text);
|
|
1390
1454
|
const home = service_home();
|
|
1391
1455
|
assert_shared_home(home);
|
|
@@ -72,8 +72,11 @@ export function access_options(adapter) {
|
|
|
72
72
|
/** The page's name for a level; `own` reads as decided by the member's own session. */
|
|
73
73
|
export function access_label(level) { return level === 'own' ? '自行决定' : ACCESS_LABELS[level] || '只读'; }
|
|
74
74
|
|
|
75
|
-
/**
|
|
76
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Subcommands an automatic task may run; service, run, api, setup and the rest of launch stay unapproved.
|
|
77
|
+
* `launch workflow-adopt` checks itself that the caller is the host of a room whose owner allowed it.
|
|
78
|
+
*/
|
|
79
|
+
export const TASK_SUBCOMMANDS = ['room', 'message', 'turn', 'state', 'review', 'execution', 'health', 'schema', 'launch workflow-adopt'];
|
|
77
80
|
|
|
78
81
|
/**
|
|
79
82
|
* Whether a member may run commands beyond the codiscuss subcommands: every level but 只读, and 可写 only with a project.
|
package/runner/free_runner.mjs
CHANGED
|
@@ -124,6 +124,7 @@ export function free_task_files(config, member, execution, binding, { session =
|
|
|
124
124
|
read_after: execution.read_until, cwd: profile.cwd || null, project_access: profile.project_access, draft_directory,
|
|
125
125
|
skills: [...skills.map(file => join(config.project, '.agents/skills', file)), ...(host_workflow ? [host_workflow.skill_path] : [])],
|
|
126
126
|
...(workflow_field ? { workflow: workflow_field } : {}),
|
|
127
|
+
...(member.role === 'host' && config.host_workflows ? { host_workflows: true } : {}),
|
|
127
128
|
cli, subcommands: TASK_SUBCOMMANDS,
|
|
128
129
|
...(bound_access ? { access_env: 'DISCUSS_ACCESS_FILE' } : { access_file: access_path }), instructions });
|
|
129
130
|
return { id, participant: member.name, directory: config.directory, draft_directory,
|