ai_co_discuss 0.2.44 → 0.2.47

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.
@@ -62,7 +62,7 @@ description: "AI CoDiscuss 的主持技能,仅主持人调用,且须同时
62
62
 
63
63
  | 判断 | 保存与结束边界 |
64
64
  | --- | --- |
65
- | 还需专家回应 | 先保存本次判断与下一步意图,先 `turn roster` 申请最新专家状态(在场专家、本轮待发言者 `waiting`、`revision`),再持有发言权提交下文的发言计划(`turn create --plan-file … --roster <revision>`):计划须包含 `waiting` 中的每位专家,名单变化后 revision 失效需重取,缺人或未带 revision 都会被拒绝,本轮不需要某人时先让其暂离;网站在专家间自动接力,末位答完才交回主持;接力不发通知,需要逐个转交的在线专家不放进计划。创建成功即结束本次执行。 |
65
+ | 还需专家回应 | 先保存本次判断与下一步意图,先 `turn roster` 申请最新专家状态(在场专家、本轮待发言者 `waiting`、`revision`),再持有发言权提交下文的发言计划(`turn create --plan-file … --roster <revision>`):计划须包含 `waiting` 中的每位专家(`unregistered` 中尚未注册的手动专家不能排入,它注册后会自动加入本轮队列),名单变化后 revision 失效需重取,缺人或未带 revision 都会被拒绝,本轮不需要某人时先让其暂离;网站在专家间自动接力,末位答完才交回主持;接力不发通知,需要逐个转交的在线专家不放进计划。创建成功即结束本次执行。 |
66
66
  | 本轮仍有排队安排(`queued_turns` 非空) | 说明用户留言、改资料(`held_since` 非 0)或打断让本轮提前交回。先处理这些新内容;原计划仍适用就 `turn continue`,否则直接创建新安排,剩余排队随之作废。 |
67
67
  | 专家名单有变化(用户邀请、请回、暂离或移除专家) | 以 `room view` 中最新的 `experts` 和 `queued_turns` 为准。新邀请的专家已追加到现有队列末尾时,保留当前发言及原计划,不重排或重复安排;尚无安排时,把新专家放在本轮尚未发言的原专家之后。暂离、移除或请回会作废原排队安排,再按最新名单重排。新加入或请回的专家先补独立观点,再进入交锋。`members` 中 `session_mode` 为 `borrowed` 的专家续接用户已有的会话,带着自己的历史上下文:请它先说明哪些判断来自原有上下文,而不是把它当作从零开始的独立观点。 |
68
68
  | 主持人需要以专家身份发言 | 给自己创建正式安排后结束;同一会话收到新的专家任务,再凭新资格发表 `opinion`。 |
@@ -7,67 +7,90 @@ description: "AI CoDiscuss 的通用参与技能,所有参与者(包括主
7
7
 
8
8
  **主要任务是参与讨论:理解问题,检验证据与假设,回应其他人的论证,形成或修正自己的判断。** 网站操作负责保存这份工作,不应代替思考或成为发言主题。
9
9
 
10
- 软件参与者包括主持人使用本技能;主持人另读 discuss-host。只承担本次授予的职责,安装、读取或编辑技能不启动讨论。各 AI 使用自己的软件或用户明确配置的 API 形成观点;软件参与者不能自行改用模型 API 替代本次身份或其他专家。API 参与者由本机接入程序提供房间工具与相同的参与边界。
10
+ 主持人也使用本技能,另读 discuss-host。只承担本次授予的职责;安装、读取或编辑技能不启动讨论。各 AI 用自己的软件或用户明确配置的 API 形成观点,不能自行改用模型 API 替代本次身份或其他专家。
11
11
 
12
- ## 讨论质量
12
+ ## 先确认你在哪个场景
13
13
 
14
- 先回答真正的问题,再给影响判断的理由、证据、适用条件与不确定性。初始观点形成自己的见解;深入讨论先理解对方实际主张及其最有力的依据,再指出同意、分歧或需要补证的部分。区分事实错误、假设不同、评价标准不同和偏好不同,避免把它们混成“谁对谁错”。
14
+ | 场景 | 做法 |
15
+ |---|---|
16
+ | 手动成员第一次到场(收到启动指令) | 标准流程 A,手动接入细节见 [手动接入](references/manual.md) |
17
+ | 讨论房间里轮到你发言 | 标准流程 B |
18
+ | 收到 `*.task.json` 的自动任务 | 标准流程 C |
19
+ | 自由协作房间(任务含 `mode: "free"`) | 读 [自由协作](references/free.md) |
20
+ | 需要原生定时持续参与 | 读 [手动接入](references/manual.md) |
21
+ | 首次某类写入、代发、超长正文、附件或出错 | 读 [写入与恢复](references/writing.md) |
15
22
 
16
- 可以坚持有依据的少数意见,也可以因新证据修改判断并解释变化。主动检查关键反例与遗漏,但不为反对而反对,不以赞同人数代替证据;若现有材料不足,说明什么证据会改变结论。需要对方回应时直接 `@专家名` 并说明具体问题;@ 是讨论线索,不会自动通知或授予发言权。
23
+ ## 硬约束
17
24
 
18
- 篇幅和结构由问题决定,不要求固定栏目、反驳数量或字数。保留关键数字、条件与失败情形;回应追问只补新论证或观点变化,旧内容用原文编号引用。每次专家回答附一两句简述与完整正文;简述表达主要判断或变化,完整论证与出处放正文,避免再复制一遍长清单。
25
+ 这些规则不因场景或便利而变通;括号里是原因,遇到未覆盖的情况按原因判断。
19
26
 
20
- ## 本次运行方式
27
+ 1. **只回答属于自己的当前未完成安排**(`pending` / `notified`,用真实 `--turn`)。没有安排就等待,不自行接管主持。(安排由主持人统一调度,越权会打乱接力。)
28
+ 2. **任何 AI 消息,包括主持总结和必要回执,都须由本人持有发言权**;仅用户本人留言豁免。创建或取消安排也需主持人持有。AI 不借用用户身份绕过。(发言权是网站保证“同一时刻只有一人发言”的机制。)
29
+ 3. **暂停、收束、`limits.reached` 或 `limits.closing` 时不新增专家观点**,保留草稿;主持人按主持技能处理到限或总结。
30
+ 4. **不读取或打印凭据内容**,token 不进入提示或房间。每个成员只用自己的凭据,房主控制凭据不能代替成员署名。
31
+ 5. **执行失效后不再写入**:暂停、到期或被打断的旧任务保留草稿并结束,不换身份、职责或编号绕过拒绝,也不去掉 `--execution` 改作普通发言。
32
+ 6. **写入前定好 `--submission-id`**:同次重试沿用编号和原内容,新内容换新编号。(网站靠它去重,换号会产生重复发言。)
33
+ 7. **`initial` 阶段独立判断**:只读议题、背景、边界和自己的材料,独立提交后再读其他观点与主持总结;已经受影响就如实说明,不补称盲评。(先读他人会产生锚定。)
34
+ 8. 不创建定时任务、不操作其他软件、不另发“就绪/完成”回执,除非用户要求或出现需要记录的异常。
21
35
 
22
- 网站读写优先用 `codiscuss room …`、`codiscuss message …` 等统一命令;任务或用户显式指定 CLI 路径时沿用指定入口。未安装统一命令时,用已核实的 `python 路径/runner/discuss.py …`。统一入口需 Node.js 24+,网站读写需 Python 3.10+。裸 `codiscuss` 会启动服务;查询用法使用 `codiscuss --help` / `codiscuss schema …`,下文命令均省略入口前缀。
36
+ ## 命令入口与身份
23
37
 
24
- 从实际任务或用户指定值核实站点、房间和身份,不从当前网页或最近聊天猜测。站点用 `--site`、当前进程 `DISCUSS_URL` 或 CLI 凭据中绑定的站点,房间始终显式指定,业务写入显式 `--as`。手动首次接入读一次 `room protocol`;自动任务不读,需要字段时只查 `schema 具体命令`,不列全部命令。
38
+ 用统一命令 `codiscuss room …`、`codiscuss message …` 等;任务或用户显式指定 CLI 路径时沿用指定入口,未安装时用已核实的 `python 路径/runner/discuss.py …`(统一入口需 Node.js 24+,网站读写需 Python 3.10+)。裸 `codiscuss` 会启动服务,不要单独运行;用法查 `codiscuss --help`,字段以本技能为准,这里没写的才查 `codiscuss schema 具体命令`。下文命令均省略入口前缀。
25
39
 
26
- 任务标明 `access_env: "DISCUSS_ACCESS_FILE"` 时,CLI 已通过本次进程绑定凭据文件,本文及主持技能示例中的 `--access-file FILE` 均可省略;否则使用任务交付的 `access_file`。不读取或打印凭据内容。
40
+ 从实际任务或用户指定值核实站点、房间和身份,不从当前网页或最近聊天猜测:站点用 `--site`、进程 `DISCUSS_URL` 或凭据绑定的站点,房间始终显式指定,写入显式 `--as 本人`。任务标明 `access_env: "DISCUSS_ACCESS_FILE"` 时凭据已由进程绑定,`--access-file FILE` 可省略;否则用任务交付的 `access_file`。项目与 cwd 有值时用于本次获准资料;CLI 路径只定位工具,不把 CLI 仓库当作讨论项目或扩大文件权限。
27
41
 
28
- 项目与 cwd 可缺省,有值时用于本次获准资料;CLI 路径只定位工具,不把 CLI 仓库当作讨论项目或扩大文件权限。
42
+ ## 标准流程
29
43
 
30
- **自动任务(收到 `*.task.json`):**
44
+ 每次 shell 调用都是一次模型往返,手动会话里还可能要用户逐条确认,所以互不依赖的只读命令放进同一条 shell 命令依次执行;写入命令单独执行并核对结果。每条命令都带 `--as 本人`。
31
45
 
32
- - 从任务文件取得身份、职责、事件、安排、CLI 和凭据绑定方式。按任务指令以 `execution enter ROOM EVENT --access-file FILE` 认领一次;**若当前调用刚已成功 enter,直接沿用,不再执行第二次**。若提示词写明接入程序已进入,则跳过 enter。会话 ID 由接入程序提供,不编造、不打印凭据内容。
33
- - 进入失败或重复交付时,用 `execution view` 和实际业务结果核对;仅凭事件显示 started,不能认定可以再做一遍。暂停、到期或失效的旧任务不得继续写入。
34
- - 本次受保护写入统一使用上述本次凭据。只处理交付的职责和安排;专家提交答案即完成此执行,主持人的交接边界见主持技能。
35
- - 通知程序负责续租与后续交付。AI 不创建定时任务、不操作其他软件,不另发“就绪/完成”回执。确实无法继续时保留草稿,用 `execution fail ROOM EVENT --reason 原因 --access-file FILE` 暂停;已失效时不再写回执或清理消息。
46
+ ### A. 手动成员首次到场
36
47
 
37
- **自由协作任务(任务文件含 `mode: "free"`):** 没有发言权、安排和阶段,不执行 `execution enter`。按任务文件的已读起点 `message list ROOM --after ID` 读取新消息,按时间顺序处理,后面的消息可以修正前面的,以最新指令为准并说明取舍。完成后用 `message post ROOM --as 本人 --kind chat --execution 执行编号 --access-file FILE` 提交一次回答。提交被拒绝说明本次执行已被打断或过期:保留草稿并结束,不要去掉 `--execution` 改作普通发言。手动接入的自由成员没有执行编号:从 `room view` 的 `free.requests` 找到自己的待处理请求,回答时用 `--request 请求编号` 关闭。任务文件 `role` 为 `host` 时你是本房间主持人:`route` 请求是用户未点名的消息,`handoff` 表示你派出的成员都已结束;先 `room view` 看成员,需要他人时在回答中加 `--to 成员名` 派活并说明各自负责的文件或模块,不需要时不加,本次即结束;不点名自己,不扩大用户给的权限。多位成员共享同一工作目录时,只改分给自己的文件;只提交自己的改动,切分支、reset、全仓 stash 或 checkout 须用户确认。
48
+ 1. 一条命令内依次 `room view ROOM`、`room protocol ROOM`(只读一次,之后不重复)。
49
+ 2. `member register ROOM` 完成注册(只需一次;未注册不算到场,也不会被安排发言)。
50
+ 3. 用几句话向用户说明:议题、规则、已有发言、安排排到谁。不发言,不建定时任务,随后结束本次检查。
38
51
 
39
- 自由房间的普通成员可在回答中用 `--to 成员名` 或正文 `@成员` 提出回应建议,不能点名自己;这不会派发任务。网页显示“建议请 X 回应”,只有房主点击“转为请求”后才建立关联原回答的新用户任务链。任务的 `request_messages` 可能指向已读游标之前的原回答,须用 `message view ROOM ID` 补读;用户确认消息不代替原建议。不要把建议显示或复制入口当作已经通知;代码与引用里的正文 @ 不生成建议。
52
+ ### B. 轮到你发言
40
53
 
41
- **手动接入:** 用户已选手动时沿用人工触发或原生定时;需要持续参与才读 [手动持续参与](references/manual.md)。没有任务文件本身不代表手动授权:若房间正在自动运行,等待本次交付或核对缺项,不从旧文件接管。所有房间的手动本地成员都使用自己独立的私有接入文件,写入带 `--access-file`;文件路径可私下交付,token 不进入提示或房间。房主控制文件不能代替成员文件署名,在线成员没有自行写入资格。手动本地成员首次读取房间后立即执行一次 `member register ROOM --as 本人 --access-file FILE` 注册;未注册前网站显示“待注册”,不算到场,也不会被本机服务接管。会话 ID 由 CLI 从宿主会话自动附带,不自行填写;凭证重新签发后须重新注册。
54
+ 1. 一条命令内依次 `room view ROOM`、`message list ROOM --after 已读编号`,得到阶段、`current_turn`、`floor.revision`、上限和新增发言。`room view` 已含这些信息,不再另读 `turn current`;`initial` 阶段不读 `message list`。
55
+ 2. 先核对再动手(状态可能在你读到之后已经变了):阶段不是 `paused` / `concluded` 且未到限;`current_turn` 是你且为 `pending` / `notified`;发言权已由你持有,或无人持有且 `next_speaker` 是你。任一不满足就保留草稿并结束。
56
+ 3. 按「讨论质量」形成观点。
57
+ 4. 未持有发言权时 `floor take ROOM --revision 版本`(版本取第 1 步的 `floor.revision`);已持有则跳过。成功的发言、创建或取消安排会自动交接发言权,不额外归还;只有仍有效却放弃发言时才 `floor return`。
58
+ 5. 提交,例如:
59
+ `message post ROOM --kind opinion --turn 153 --reply-to 167 --summary "一两句主要判断" --body-file draft.md --submission-id gemini-153-1`
60
+ 讨论房间的专家观点用 `--kind opinion`;主持发言用 `--kind moderation` 且不带 `--summary`;`chat` 只用于自由协作。正文优先 UTF-8 `--body-file`。需要附件:先 `attachment upload ROOM --file FILE --submission-id 上传编号`,再用 `message post --attachment ID` 关联(最多 10 个,单个 20 MiB,须在执行资格结束前上传)。
61
+ 6. 核对返回里的消息编号、作者与关联后结束;返回已是持久记录,不补回执、不再读一遍。
42
62
 
43
- ## 先读什么
63
+ 出错时:`floor take` 版本冲突就回到第 1 步重读;`message post` 结果不明按原 `--submission-id` 查询,找到就沿用,找不到才用原编号原内容重试一次,再不明就保留草稿并报告。其余故障流程见 [写入与恢复](references/writing.md),不把它当作每次发言的检查表。
44
64
 
45
- 先读 `room view` 获取目标、阶段、当前安排、发言权和上限;已有所需字段就不重复查询同一信息。背景不足读 `room brief`,需要接续判断读 `state view`,核对具体论据读 `message view ROOM ID`,查找材料才用 `message list`。
65
+ ### C. 自动任务(`*.task.json`)
46
66
 
47
- `initial` 阶段只读议题、背景、边界和自己的材料,独立提交后再读其他观点与主持总结;已经受影响就如实说明,不补称盲评。深入讨论按争点阅读相关原文,不用摘要代替需要复核的论证,也不无目的拉取全部历史。
67
+ 任务文件给出身份、职责、事件、安排、CLI 和凭据绑定方式。
48
68
 
49
- 增量读取用实际已读编号 `message list ROOM --after ID`,按 `next_after` / `has_more` 续读;`latest_id` 不等于已读。筛选后的游标只代表该查询范围,不能用来声称已读全场。阶段和安排以最新服务端状态为准。
69
+ 1. 按任务指令用 `execution enter ROOM EVENT --access-file FILE` 认领一次;若本次调用已成功 enter 或提示词写明接入程序已进入,直接沿用,不执行第二次。会话 ID 由接入程序提供,不编造。
70
+ 2. 之后同 B 的第 1–6 步,写入统一使用本次任务凭据,只处理交付的职责和安排;专家提交答案即完成此执行,主持人的交接边界见主持技能。
71
+ 3. 进入失败或重复交付时,用 `execution view` 和实际业务结果核对;仅凭事件显示 started,不能认定可以再做一遍。确实无法继续时保留草稿,`execution fail ROOM EVENT --reason 原因 --access-file FILE` 暂停;已失效时不再写回执。续租与后续交付由通知程序负责。
50
72
 
51
- 消息的 `attachments` 提供文件名、类型、大小与编号;需要核对内容时用 `attachment view ROOM ID`、`attachment download ROOM ID --dest 明确的新文件路径`,再用当前软件的实际工具读取。独立阶段仍遵循上述材料边界;下载或看见文件名不等于已读,无法解析时如实说明。
73
+ ## 阅读
52
74
 
53
- ## 发言权与提交边界
75
+ 背景不足读 `room brief`,需要接续判断读 `state view`,核对具体论据读 `message view ROOM ID`,查找材料才用 `message list`。深入讨论按争点阅读相关原文,不用摘要代替需要复核的论证,也不无目的拉取全部历史。增量读取用实际已读编号 `message list ROOM --after ID`,按 `next_after` / `has_more` 续读;`latest_id` 不等于已读,筛选后的游标只代表该查询范围。阶段和安排以最新服务端状态为准。
54
76
 
55
- 专家只回答属于自己的当前未完成安排(pending/notified),使用真实 `--turn`;没有安排就等待,不自行接管主持。提交前核对阶段、安排和上限。暂停、收束、`limits.reached` 或 `limits.closing` 时不新增专家观点,保留草稿;主持人按主持技能处理到限或总结。
77
+ 消息的 `attachments` 提供文件名、类型、大小与编号;需要核对内容时用 `attachment view ROOM ID`、`attachment download ROOM ID --dest 明确的新文件路径`,再用当前软件的工具读取。下载或看见文件名不等于已读,无法解析时如实说明。
56
78
 
57
- **任何 AI 消息,包括主持总结和必要回执,都须由本人持有发言权;仅用户本人留言豁免。** 创建或取消安排也需要主持人持有发言权,修改资料、state 和阶段无需领取。AI 不借用用户身份绕过。
79
+ ## 讨论质量
58
80
 
59
- 用最新房间中的 `floor` 判断:本人已持有则沿用;无人持有且 `next_speaker` 为本人时,以该版本 `floor take ROOM --as 本人 --revision 版本` 领取;否则保留草稿等待。发言、创建或取消安排成功会原子交接发言权,不额外归还。仍有效但放弃发言时才主动 `floor return`;版本冲突或结果不明先回读。
81
+ 先回答真正的问题,再给影响判断的理由、证据、适用条件与不确定性。初始观点形成自己的见解;深入讨论先理解对方实际主张及其最有力的依据,再指出同意、分歧或需要补证的部分。区分事实错误、假设不同、评价标准不同和偏好不同,避免把它们混成“谁对谁错”。
60
82
 
61
- 新房间由主持人持有;正式安排交给专家,答案保存后回到主持人。主持人可连续发表说明,也可再次安排同一专家,但每次遵守新的发言权与安排。在线专家的代操作、字段和故障恢复按需查 [写入与恢复](references/writing.md),本地专家由本人处理。
83
+ 可以坚持有依据的少数意见,也可以因新证据修改判断并解释变化。主动检查关键反例与遗漏,但不为反对而反对,不以赞同人数代替证据;材料不足时说明什么证据会改变结论。需要对方回应时直接 `@专家名` 并说明具体问题;@ 只是讨论线索,不会通知或授予发言权。
62
84
 
63
- ## 保存真实结果
85
+ 篇幅和结构由问题决定。保留关键数字、条件与失败情形;回应追问只补新论证或观点变化,旧内容用原文编号引用。简述表达主要判断或变化,完整论证与出处放正文,不再复制一遍长清单。
64
86
 
65
- 观点用 `message post`。讨论房间的专家观点用 `--kind opinion`,主持发言用 `--kind moderation` 且不带 `--summary`;`chat` 仅用于自由协作房间。观点摘要用 `--summary` / `--summary-file`,完整正文优先 UTF-8 `--body-file`;自己的观点署名自己。代发保留真实作者、完整原文及引用关系,摘要归实际撰写者,不把自己的改写冒充对方回答。
87
+ 示例(只示意密度与结构,内容与篇幅按问题而定):
66
88
 
67
- 需要提交原文件时,先 `attachment upload ROOM --as 本人 --file FILE --submission-id 上传编号`,再把回执中的附件 ID 通过 `message post --attachment ID` 关联,可重复指定(最多 10 个,单个 20 MiB)。受保护上传使用上述本次凭据,须在回答完成、执行资格结束前上传。上传和消息分别保存提交编号;结果不明用 `attachment list ROOM --submission-id 上传编号` 核对,重试沿用原文件与原编号。
89
+ > 摘要:倾向方案 B——迁移成本一次性,A 的维护成本随规模线性增长。
90
+ > 正文:结论先行;依据 1(出处/数据)、依据 2;**适用条件**:团队 ≤5 人时 A 仍更省;**不确定性**:未测量实际迁移耗时,超过 2 周则结论反转;对 #167 的分歧在于成本口径,而非事实。
68
91
 
69
- 创建房间、安排、消息前保存 `--submission-id` 与原内容;同次重试沿用,新内容换新编号。成功须核对服务端返回的持久记录、作者和关联;返回不含所需证据时按编号补读,避免重复获取同一份已确认内容。答案落库后只读核验即可结束,不补启停回执或使用旧资格继续写入。
92
+ ## 保存与核验
70
93
 
71
- 结果不明先按原 `submission_id` 精确查询,找到就沿用;明确可重试时仅用原编号原内容重试一次,再不明则保留草稿并报告。不换身份、职责或编号绕过拒绝。首次使用某类写入、代发、超长正文或遇到失败时,读取 [写入与恢复](references/writing.md) 的相关部分;不把全部故障流程作为每次发言的检查表。
94
+ 成功须核对服务端返回的持久记录、作者和关联;返回不含所需证据时按编号补读,避免重复获取同一份已确认内容。答案落库后只读核验即可结束,不补启停回执或使用旧资格继续写入。代发保留真实作者、完整原文及引用关系,摘要归实际撰写者,不把自己的改写冒充对方回答。
72
95
 
73
- 正常阅读、等待、启停和成功回答不发布运行回执。只有用户要求或协作确需处理的异常才记录真实结果;回执不能作为观点、完成安排或共识的证据。执行结束不等于整场讨论结束。
96
+ 正常阅读、等待、启停和成功回答不发布运行回执;只有用户要求或协作确需处理的异常才记录真实结果,且回执不能作为观点、完成安排或共识的证据。执行结束不等于整场讨论结束。
@@ -0,0 +1,23 @@
1
+ # 自由协作房间
2
+
3
+ 仅用于任务文件含 `mode: "free"`、或用户明确说明是自由协作房间时。讨论房间不读本页。
4
+
5
+ ## 成员回答
6
+
7
+ 没有发言权、安排和阶段,不执行 `execution enter`。按任务文件的已读起点 `message list ROOM --after ID` 读取新消息,按时间顺序处理,后面的消息可以修正前面的,以最新指令为准并说明取舍。完成后用 `message post ROOM --as 本人 --kind chat --execution 执行编号 --access-file FILE` 提交一次回答。
8
+
9
+ 提交被拒绝说明本次执行已被打断或过期:保留草稿并结束,不要去掉 `--execution` 改作普通发言,因为那会让已失效的执行继续产生可见结果。
10
+
11
+ 手动接入的自由成员没有执行编号:从 `room view` 的 `free.requests` 找到自己的待处理请求,回答时用 `--request 请求编号` 关闭。
12
+
13
+ ## 主持人
14
+
15
+ 任务文件 `role` 为 `host` 时你是本房间主持人:`route` 请求是用户未点名的消息,`handoff` 表示你派出的成员都已结束。先 `room view` 看成员,需要他人时在回答中加 `--to 成员名` 派活并说明各自负责的文件或模块,不需要时不加,本次即结束;不点名自己,不扩大用户给的权限。
16
+
17
+ 多位成员共享同一工作目录时,只改分给自己的文件;只提交自己的改动,切分支、reset、全仓 stash 或 checkout 须用户确认。
18
+
19
+ ## 建议他人回应
20
+
21
+ 普通成员可在回答中用 `--to 成员名` 或正文 `@成员` 提出回应建议,不能点名自己;这不会派发任务。网页显示“建议请 X 回应”,只有房主点击“转为请求”后才建立关联原回答的新用户任务链。
22
+
23
+ 任务的 `request_messages` 可能指向已读游标之前的原回答,须用 `message view ROOM ID` 补读;用户确认消息不代替原建议。不要把建议显示或复制入口当作已经通知;代码与引用里的正文 @ 不生成建议。
@@ -1,8 +1,16 @@
1
- # 手动持续参与
1
+ # 手动接入
2
2
 
3
- 仅用于用户选择的手动模式及本软件原生定时;自动任务的租约、后续唤醒和终态由接入程序处理,不读取或照做本页。先核实房间的模式,不能因缺少 task 文件就建立定时任务。
3
+ ## 接入文件与注册
4
4
 
5
- ## 专家持续参与
5
+ 没有任务文件本身不代表手动授权:若房间正在自动运行,等待本次交付或核对缺项,不从旧文件接管。所有房间的手动本地成员都使用自己独立的私有接入文件,写入带 `--access-file`;文件路径可私下交付,token 不进入提示或房间。房主控制文件不能代替成员文件署名,在线成员没有自行写入资格。
6
+
7
+ 首次读取房间后立即执行一次 `member register ROOM --as 本人 --access-file FILE`。未注册前网站显示“待注册”,不算到场,也不会被安排发言或被本机服务接管。会话 ID 由 CLI 从宿主会话自动附带,不自行填写;凭证重新签发后须重新注册。
8
+
9
+ ## 持续参与(原生定时)
10
+
11
+ 仅用于用户选择的手动模式及本软件原生定时;自动任务的租约、后续唤醒和终态由接入程序处理,不读取或照做本节。先核实房间的模式,不能因缺少 task 文件就建立定时任务。
12
+
13
+ ### 专家持续参与
6
14
 
7
15
  仅在本次任务要求后台持续参与时使用所在软件的原生定时功能;网站读写子命令不负责定时调度。先通过 `room brief` 读取本次检查周期、运行上限和停止边界;用户或主持启动文本给出更新约定时沿用更新值,不因接入文本简短就忽略房间中已有的约定。无周期约定时,专家参考 **每 60 秒一次**,首次立即检查。长耗时调研可放宽到 3–5 分钟;工具不支持时选最接近且更长的周期,并报告实际值。创建前复用同房间同职责已有任务,记录真实任务 ID、频率和约定上限;工具不可用时说明,由用户或主持人手动触发,仍可自行写入。
8
16
 
@@ -275,10 +275,13 @@ def register_member(connection, room_id, token, now):
275
275
  member = require_member(connection, room_id, token)
276
276
  if member["access"] != "manual":
277
277
  raise ValueError("只有手动接入的成员需要注册")
278
- stamp = registered_at(connection, member) or now
278
+ previous = registered_at(connection, member)
279
+ stamp = previous or now
279
280
  connection.execute("""INSERT INTO member_registrations (member_id,room_id,token_hash,registered_at) VALUES (?,?,?,?)
280
281
  ON CONFLICT(member_id) DO UPDATE SET token_hash=excluded.token_hash,registered_at=excluded.registered_at""",
281
282
  (member["id"], room_id, member["token_hash"], stamp))
283
+ if not previous:
284
+ queue_invited_expert(connection, room_id, member["name"], now)
282
285
  reported = connection.execute("SELECT client FROM member_sessions WHERE member_id=?", (member["id"],)).fetchone()
283
286
  return {"member": member["name"], "registered_at": stamp, "session_client": reported[0] if reported else ""}
284
287
 
@@ -847,8 +850,14 @@ def place_invited_expert(connection, room_id, name):
847
850
 
848
851
 
849
852
  def queue_invited_expert(connection, room_id, name, now):
850
- """Queue a new expert in an existing live plan at its round-completing slot, without changing the current speaker or hold."""
851
- connection.execute("""INSERT INTO discussion_turns (room_id,expert,question,status,submission_id,created_at)
853
+ """Queue a new expert in an existing live plan at its round-completing slot, without changing the current speaker or hold.
854
+
855
+ A manual expert is queued only once it has registered (the invite alone does not), and only once per invitation.
856
+ """
857
+ member = connection.execute("SELECT * FROM room_members WHERE room_id=? AND name=? AND status='active'", (room_id, name)).fetchone()
858
+ if not member or (member["adapter"] == "manual" and not registered_at(connection, member)):
859
+ return
860
+ connection.execute("""INSERT OR IGNORE INTO discussion_turns (room_id,expert,question,status,submission_id,created_at)
852
861
  SELECT settings.room_id,member.name,
853
862
  CASE WHEN settings.phase='initial' THEN '欢迎加入:请先读取议题,独立给出你的开场观点。'
854
863
  ELSE '欢迎加入:请先读取议题和已有回答,给出你的观点。' END,
@@ -929,7 +938,7 @@ def discussion_member_action(connection, room_id, member_id, token, payload):
929
938
  save_discussion_experts(connection, room_id, experts)
930
939
 
931
940
 
932
- ADOPTABLE_ADAPTERS = ("claude", "codex", "codex_cli")
941
+ ADOPTABLE_ADAPTERS = ("claude", "codex", "codex_cli", "antigravity_cli")
933
942
 
934
943
 
935
944
  def adopt_manual_member(connection, room_id, member, payload):
@@ -115,8 +115,10 @@ BEGIN
115
115
  SELECT RAISE(ABORT, 'turn_unavailable');
116
116
  END;
117
117
 
118
- CREATE TRIGGER IF NOT EXISTS reject_inactive_opinion BEFORE INSERT ON message_details
118
+ DROP TRIGGER IF EXISTS reject_inactive_opinion;
119
+ CREATE TRIGGER reject_inactive_opinion BEFORE INSERT ON message_details
119
120
  WHEN NEW.kind='opinion'
121
+ AND (SELECT author FROM messages WHERE id=NEW.message_id)<>'用户'
120
122
  AND NOT EXISTS (SELECT 1 FROM message_details WHERE message_id=NEW.message_id)
121
123
  AND EXISTS (SELECT 1 FROM messages JOIN topic_settings ON topic_settings.room_id=messages.room_id
122
124
  WHERE messages.id=NEW.message_id AND topic_settings.phase IN ('paused','concluded'))
package/backend/server.py CHANGED
@@ -214,6 +214,7 @@ def require_room(connection, room_id):
214
214
  room["round_budget"] = dict(budget) if budget else None
215
215
  if budget:
216
216
  room["opinion_count"] = opinion_count(connection, room_id, room["experts"])
217
+ room["round_groups"] = budget_round_groups(connection, room_id, room["experts"], budget["base"]) if budget else room["rounds"]
217
218
  room["floor"] = dict(connection.execute("SELECT holder,next_speaker,revision FROM room_floor WHERE room_id=?", (room_id,)).fetchone())
218
219
  room["runtime"] = runtime_view(connection, room_id)
219
220
  room["limits"] = limits_view(connection.execute(LIMITS_SQL, (room_id,)).fetchone(), room)
@@ -233,6 +234,7 @@ def require_room(connection, room_id):
233
234
  for decision in room["scheduler_decisions"]:
234
235
  decision["detail"] = details.get(decision["id"])
235
236
  room["host_arrangements"] = host_arrangements(connection, room_id, room["scheduler_decisions"])
237
+ room["cancelled_turns"] = cancelled_turns(connection, room_id)
236
238
  parent = connection.execute("""SELECT rooms.id,rooms.title FROM room_links JOIN rooms ON rooms.id=room_links.parent_id
237
239
  WHERE room_links.room_id=?""", (room_id,)).fetchone()
238
240
  room["parent_room"] = dict(parent) if parent else None
@@ -282,7 +284,7 @@ def require_floor(room, speaker):
282
284
  def host_arrangements(connection, room_id, decisions):
283
285
  """Each plan the host created (turns saved in one batch), anchored after the latest answer before it.
284
286
 
285
- Invitation turns are not plans, and a plan the smart scheduler applied is already shown by its own decision.
287
+ The opening plan has no answer to follow and is anchored at 0. Invitation turns are not plans, and a plan the smart scheduler applied is already shown by its own decision.
286
288
  """
287
289
  plans, latest_answer = [], 0
288
290
  for row in connection.execute("SELECT id,expert,question,answer_id,created_at,submission_id FROM discussion_turns WHERE room_id=? ORDER BY id", (room_id,)):
@@ -293,7 +295,22 @@ def host_arrangements(connection, room_id, decisions):
293
295
  plans.append({"id": row["id"], "after_message_id": latest_answer, "created_at": row["created_at"], "turns": [{"expert": row["expert"], "question": row["question"]}]})
294
296
  latest_answer = max(latest_answer, row["answer_id"] or 0)
295
297
  applied = {item["after_message_id"] for item in decisions if item["applied"]}
296
- return [plan for plan in plans if plan["after_message_id"] and plan["after_message_id"] not in applied][-RECENT_DECISIONS:]
298
+ return [plan for plan in plans if plan["after_message_id"] not in applied][-RECENT_DECISIONS:]
299
+
300
+
301
+ def cancelled_turns(connection, room_id):
302
+ """Assignments that ended without an answer, anchored where their wake card first appeared.
303
+
304
+ The anchor is the later of the previous turn's answer and the latest message at creation time, so the card keeps its place in the transcript.
305
+ """
306
+ rows, latest_answer = [], 0
307
+ for row in connection.execute("""SELECT id,expert,question,status,answer_id,
308
+ (SELECT COALESCE(MAX(messages.id),0) FROM messages WHERE messages.room_id=discussion_turns.room_id AND messages.created_at<=discussion_turns.created_at) AS created_after
309
+ FROM discussion_turns WHERE room_id=? ORDER BY id""", (room_id,)):
310
+ if row["status"] == "cancelled":
311
+ rows.append({"id": row["id"], "expert": row["expert"], "question": row["question"], "after_message_id": max(latest_answer, row["created_after"])})
312
+ latest_answer = max(latest_answer, row["answer_id"] or 0)
313
+ return rows[-RECENT_DECISIONS:]
297
314
 
298
315
 
299
316
  def discussion_rounds(connection, room_id, experts):
@@ -327,6 +344,34 @@ def discussion_rounds(connection, room_id, experts):
327
344
  return rounds
328
345
 
329
346
 
347
+ def budget_round_groups(connection, room_id, experts, base):
348
+ """Group history by the round budget's rule: every len(experts) opinions form a round, and a new segment (base) starts a new one.
349
+
350
+ `rounds` stays speaker-based because scheduling needs who still owes a turn; this view only labels history.
351
+ """
352
+ if not experts:
353
+ return []
354
+ rows = connection.execute("""SELECT messages.id,messages.author,
355
+ COALESCE(details.kind,'opinion') AS kind FROM messages
356
+ LEFT JOIN message_details AS details ON details.message_id=messages.id
357
+ WHERE room_id=? ORDER BY messages.id""", (room_id,))
358
+ groups, next_start, seen = [], None, 0
359
+ for message in rows:
360
+ if next_start is None:
361
+ next_start = message["id"]
362
+ if message["kind"] != "opinion" or message["author"] not in experts:
363
+ continue
364
+ if not groups or groups[-1]["completed"]:
365
+ groups.append({"number": len(groups) + 1, "start_id": next_start, "opinions": 0, "completed": False})
366
+ current = groups[-1]
367
+ seen += 1
368
+ current["opinions"] += 1
369
+ current["completed"] = current["opinions"] >= len(experts) or seen == base
370
+ if current["completed"]:
371
+ next_start = None
372
+ return groups
373
+
374
+
330
375
  def opinion_count(connection, room_id, experts):
331
376
  """Count formal expert opinions; the program-controlled round budget is measured in these."""
332
377
  if not experts:
@@ -969,7 +1014,7 @@ def append_free_message(connection, room, payload, actor):
969
1014
  def record_reported_session(connection, room_id, token, payload):
970
1015
  """Keep the software session a manual member posted from, so the local service can take the member over."""
971
1016
  session_id, client = payload.get("session_id"), payload.get("session_client")
972
- if client not in ("claude", "codex") or not isinstance(session_id, str) or not SESSION_ID_PATTERN.fullmatch(session_id) or not token:
1017
+ if client not in ("claude", "codex", "antigravity") or not isinstance(session_id, str) or not SESSION_ID_PATTERN.fullmatch(session_id) or not token:
973
1018
  return
974
1019
  member = connection.execute("SELECT id FROM room_members WHERE room_id=? AND token_hash=? AND status='active' AND access='manual' AND adapter='manual'",
975
1020
  (room_id, token_hash(token))).fetchone()
@@ -998,7 +1043,7 @@ def append_message(connection, room_id, payload, token="", free_actor=None):
998
1043
  return {"message": previous, "duplicate": True}
999
1044
  if author != "用户" and details["kind"] == "opinion":
1000
1045
  require_within_limits(room)
1001
- if room["phase"] in ("paused", "concluded") and details["kind"] == "opinion":
1046
+ if room["phase"] in ("paused", "concluded") and details["kind"] == "opinion" and (author != "用户" or room["archived"]):
1002
1047
  raise ValueError("话题已暂停或收束;恢复阶段后才能提交新观点,运行回执请用 receipt")
1003
1048
  check_message_references(connection, room_id, expected)
1004
1049
  check_message_attachments(connection, room_id, details["submitted_by"], identifiers)
@@ -1065,12 +1110,28 @@ def roster_view(room):
1065
1110
  """Who is present and who still owes a turn in the open round; its revision changes whenever either changes."""
1066
1111
  last = room["rounds"][-1] if room["rounds"] else None
1067
1112
  spoken = set(last["speakers"]) if last and not last["completed"] else set()
1068
- experts = [{"name": name, "mode": mode, "host": name == room["host"], "spoken": name in spoken} for name, mode in room["experts"].items()]
1069
- state = json.dumps([sorted(room["experts"]), sorted(spoken)], ensure_ascii=False)
1070
- return {"experts": experts, "waiting": [item["name"] for item in experts if not item["spoken"] and not item["host"]],
1113
+ pending_registration = unregistered_experts(room)
1114
+ experts = [{"name": name, "mode": mode, "host": name == room["host"], "spoken": name in spoken,
1115
+ "registered": name not in pending_registration} for name, mode in room["experts"].items()]
1116
+ state = json.dumps([sorted(room["experts"]), sorted(spoken), sorted(pending_registration)], ensure_ascii=False)
1117
+ return {"experts": experts, "unregistered": pending_registration,
1118
+ "waiting": [item["name"] for item in experts if not item["spoken"] and not item["host"] and item["registered"]],
1071
1119
  "revision": hashlib.sha256(state.encode("utf-8")).hexdigest()[:12]}
1072
1120
 
1073
1121
 
1122
+ def unregistered_experts(room):
1123
+ """Manual experts of a woken discussion (adapter 'manual') that have not registered from their own session; they cannot be scheduled until they do."""
1124
+ return [member["name"] for member in room["members"]
1125
+ if member["adapter"] == "manual" and member["status"] == "active" and not member["registered_at"] and member["name"] in room["experts"]]
1126
+
1127
+
1128
+ def require_registered(room, names):
1129
+ """Reject scheduling an expert that has not registered yet; it joins the round queue by itself once it registers."""
1130
+ missing = [name for name in dict.fromkeys(names) if name in unregistered_experts(room)]
1131
+ if missing:
1132
+ raise ValueError(f"以下手动专家尚未注册,不能安排发言:{'、'.join(missing)};它在自己的会话中注册后会自动加入本轮队列,也可先让其暂离")
1133
+
1134
+
1074
1135
  def require_round_coverage(room, plan, payload):
1075
1136
  """A multi-turn plan is only accepted against the latest roster and must schedule everyone still waiting in the round."""
1076
1137
  if len(plan) < 2:
@@ -1100,6 +1161,7 @@ def create_turn(connection, room_id, payload):
1100
1161
  require_within_limits(room)
1101
1162
  if any(expert not in room["experts"] or len(question) > MAX_QUESTION_LENGTH for expert, question, _ in plan):
1102
1163
  raise ValueError("请选择参与专家,待回应问题最多 1000 字")
1164
+ require_registered(room, [expert for expert, _, _ in plan])
1103
1165
  require_round_coverage(room, plan, payload)
1104
1166
  if room["phase"] in ("paused", "concluded"):
1105
1167
  raise ValueError("话题已暂停或收束,请先调整阶段")
@@ -1151,6 +1213,7 @@ def continue_turns(connection, room_id, payload):
1151
1213
  if not room["queued_turns"]:
1152
1214
  raise FileExistsError("没有排队中的安排;请创建新的安排")
1153
1215
  require_within_limits(room)
1216
+ require_registered(room, [room["queued_turns"][0]["expert"]])
1154
1217
  require_floor(room, room["host"])
1155
1218
  turn_id = room["queued_turns"][0]["id"]
1156
1219
  connection.execute("UPDATE discussion_turns SET status='pending' WHERE id=? AND status='queued'", (turn_id,))
@@ -1240,7 +1303,7 @@ def update_turn(connection, room_id, turn_id, payload):
1240
1303
  status = payload.get("status")
1241
1304
  if status not in ("notified", "cancelled"):
1242
1305
  raise ValueError("安排状态仅可显式改为 notified 或 cancelled")
1243
- if turn["status"] == status:
1306
+ if turn["status"] == status or (status == "cancelled" and turn["status"] == "skipped"):
1244
1307
  return turn
1245
1308
  if status == "notified" and room["phase"] in ("paused", "concluded"):
1246
1309
  raise ValueError("话题已暂停或收束,不能继续通知")
@@ -1320,8 +1383,9 @@ def update_state(connection, room_id, payload):
1320
1383
  until = int(payload.get("until", room["latest_id"]))
1321
1384
  if until < 0 or until > room["latest_id"]:
1322
1385
  raise ValueError("讨论状态的截止编号无效")
1323
- if until < room["state_until"]:
1324
- raise ValueError(f"状态已整理至 #{room['state_until']},截止编号不能回退;请先读取最新状态")
1386
+ saved_brief = connection.execute("SELECT until FROM room_briefs WHERE room_id=?", (room_id,)).fetchone()
1387
+ if "brief" in payload and saved_brief and until < saved_brief["until"]:
1388
+ raise ValueError(f"简报已整理至 #{saved_brief['until']},截止编号不能回退;请先读取最新状态")
1325
1389
  connection.execute("UPDATE rooms SET state_text=?,state_until=? WHERE id=?", (text, until, room_id))
1326
1390
  if "brief" in payload:
1327
1391
  connection.execute("""INSERT INTO room_briefs (room_id,text,until,updated_at) VALUES (?,?,?,?)
@@ -224,7 +224,7 @@ Python 通过进程内条件变量唤醒订阅者,不占用数据库连接,
224
224
 
225
225
  - `round_budgets`:程序统计轮次的预算(`base` 为本段计数起点的专家观点数,`cap` 为本段轮次上限、0 表示取 `discussion_limits.max_rounds`,`extra` 为用户累计追加轮数)。建房时写入;没有记录的旧房间沿用按花名册逐轮统计的旧口径。房间视图的 `limits` 返回 `segment_rounds`、`round_cap`、`extra_rounds`。
226
226
  - `discussion_options`:建房时的讨论选项,目前只有 `named_followup`(0/1,建房 `named_followup` 布尔值,默认 false,计入建房幂等比对)。没有记录的旧房间视为 true;房间视图返回 `named_followup`。
227
- - `scheduler_decisions`:讨论中每次智能调度判断的可见记录(`kind` 为 `interjection` / `round`,`label`、`confidence`、`applied`、`provider`、`after_message_id`),由接入程序用运行控制凭据 `POST scheduler-decisions` 写入,不是消息,不影响轮次、发言权与暂扣;房间视图 `scheduler_decisions` 返回最近 100 条;每条可带 `detail`(独立表 `scheduler_decision_details`,由 `POST scheduler-decisions` 的 `detail` 写入:每个问题的题目说明、选项,以及决策模型选了哪项和各选项概率,最多 4 项、4000 字,不含房间消息摘录,不随备份导出),网页默认折叠,点开才显示“问 / 答”。房间视图另附 `host_arrangements`:由 `discussion_turns` 按同一批创建时间分组得到的主持人安排(不含邀请追加的开场安排,也不含已由智能调度写入决策的那一批),`after_message_id` 取该批之前最新的回答,网页在该回答后显示与智能调度卡片同样式的“主持人安排:A → B → C”一行,点开才逐条显示专家与主持人写的任务说明(`turns`);没有可跟随的回答(讨论开场)时不显示。不新增表。
227
+ - `scheduler_decisions`:讨论中每次智能调度判断的可见记录(`kind` 为 `interjection` / `round`,`label`、`confidence`、`applied`、`provider`、`after_message_id`),由接入程序用运行控制凭据 `POST scheduler-decisions` 写入,不是消息,不影响轮次、发言权与暂扣;房间视图 `scheduler_decisions` 返回最近 100 条;每条可带 `detail`(独立表 `scheduler_decision_details`,由 `POST scheduler-decisions` 的 `detail` 写入:每个问题的题目说明、选项,以及决策模型选了哪项和各选项概率,最多 4 项、4000 字,不含房间消息摘录,不随备份导出),网页默认折叠,点开才显示“问 / 答”。房间视图另附 `host_arrangements`:由 `discussion_turns` 按同一批创建时间分组得到的主持人安排(不含邀请追加的开场安排,也不含已由智能调度写入决策的那一批),`after_message_id` 取该批之前最新的回答(开场安排前没有回答,取 0,显示在议题卡片下方),网页在该回答后显示与智能调度卡片同样式的“主持人安排:A → B → C”一行,点开才逐条显示专家与主持人写的任务说明(`turns`);没有可跟随的回答(讨论开场)时不显示。不新增表。
228
228
  - 两表都是独立表,D1 重新应用 schema 即可;备份、导入(`after_message_id` 随消息重编号)与删除一并处理。智能调度代主持人执行的写入不新增权限,沿用该主持事件的执行凭据。
229
229
 
230
230
  ## 自动运行协议
package/docs/cli.md CHANGED
@@ -134,7 +134,7 @@ codiscuss floor view $room
134
134
  | 主持发言 | 领取后 `message post ROOM --as Codex --kind moderation --body-file FILE --submission-id UUID` |
135
135
  | 在线专家代发 | 被正式安排后,由主持人 `floor take ROOM --as Codex --speaker Claude --revision N`,再 `message post ROOM --as Codex --author Claude --turn TURN --summary-file SUMMARY --summary-by Claude --body-file FILE --submission-id UUID` |
136
136
  | 放弃发言 | `floor return ROOM --as NAME --revision N`;未完成安排仍需处理 |
137
- | 整轮安排 | 主持人领取后 `turn roster ROOM` 后 `turn create ROOM --as Codex --plan-file PLAN.json --roster REV --submission-id UUID`;PLAN 为 `[{"expert":"Claude","question":"…"}]`,按顺序接力;多项计划须先 `turn roster ROOM` 取得最新专家状态,把其 `revision` 用 `--roster` 带上,并包含 `waiting` 中的每位专家(主持人除外):未带 revision 返回 400,名单已变化返回 409,缺人返回 400 并列出名字 |
137
+ | 整轮安排 | 主持人领取后 `turn roster ROOM` 后 `turn create ROOM --as Codex --plan-file PLAN.json --roster REV --submission-id UUID`;PLAN 为 `[{"expert":"Claude","question":"…"}]`,按顺序接力;多项计划须先 `turn roster ROOM` 取得最新专家状态,把其 `revision` 用 `--roster` 带上,并包含 `waiting` 中的每位专家(主持人除外;尚未注册的手动专家列在 `unregistered`,不在 `waiting` 中,也不能被安排,否则返回 400 并列出名字):未带 revision 返回 400,名单已变化返回 409,缺人返回 400 并列出名字 |
138
138
  | 继续原计划 | 用户介入或打断后,主持人领取并 `turn continue ROOM --as Codex`;改用新安排则排队自动作废 |
139
139
  | 取消安排 | 主持人领取后 `turn cancel ROOM TURN --as Codex` |
140
140
  | 切换阶段 | `room update ROOM --as Codex --phase discussing` |
package/docs/runner.md CHANGED
@@ -283,7 +283,7 @@ Windows 的 `executable` 填原生 `.exe`,不填 npm 的 `.cmd` / `.ps1` 包
283
283
 
284
284
  agy 只用一套方法控制权限:
285
285
 
286
- 1. **只在本工具新建的会话中执行**:每位参与者对应一个独立原生讨论项目,权限只写在该项目里。外部已有会话(包括从当前 agy 对话发起主持、在启动配置中填写 `session_id`)会恢复其原项目,无法套用讨论项目权限,因此启动时即拒绝,交付时也再次拒绝。
286
+ 1. **只在本工具新建的会话中执行**:每位参与者对应一个独立原生讨论项目,权限只写在该项目里。外部已有会话(包括从当前 agy 对话发起主持、在启动配置中填写 `session_id`)会恢复其原项目,无法套用讨论项目权限,因此启动时即拒绝,交付时也再次拒绝。唯一例外是手动专家的托管:agy 在自己的会话里执行 `codiscuss` 时由 `ANTIGRAVITY_CONVERSATION_ID` 上报会话,用户注册后服务把它转为 `antigravity_cli` 并以 `--conversation ID` 续接,不加 `--project`,沿用该会话原项目和用户自己的 agy 权限(无讨论项目规则,也不检查全局放行);无头模式下 shell 命令须已由用户的规则放行,例如 `command(regex:^codiscuss(\.cmd)? .*$)`,否则被拒绝。
287
287
  2. **项目内规则**:allow 为受限的讨论命令规则与草稿目录写入(`write` 时加项目目录写入);deny 为项目目录写入(`read` 时)、运行目录、技能目录、`~/.gemini` 写入与 `mcp(*)`。规则按目录前缀递归匹配,不使用 `/**`;Deny > Ask > Allow,因此草稿不得位于受保护目录中,冲突在交付前报告。
288
288
  3. **交付前检查全局设置**:CLI 与共享全局 allow 中若有 `*`、`command(*)`、`unsandboxed(*)` 或 `unsandboxed_command(*)` 这类放行全部命令的规则,则拒绝交付,因为它会让 shell 绕过项目的文件规则。具体命令的放行和 `toolPermission` 不检查,属于用户自己的授权。未授权 shell 在非交互模式下被拒绝。
289
289
  4. **不改用户全局设置**:本工具不向 agy 全局设置写入任何规则;`setup` 只移除早期版本记录在 `agy-permission.json` 中的全局规则(先备份到服务目录 `agy-settings.backup.json`),其他设置与用户规则不变。
@@ -332,7 +332,7 @@ Codex 要求 CLI 0.163.0 或更新版本,使用 permission profiles 和本地
332
332
  - **轮次衔接**:仅 `named_followup` 为 false、无排队、上一轮已完成时判断。继续 → 先请模型选出的专家(不是上一轮最后发言者),其余按房间专家顺序,统一使用 `peer_round_question`;第 1 轮后自动把阶段从 `initial` 改为 `discussing`。可收束、跑偏、重复或卡住 → 主持人。
333
333
  - **执行方式**:有把握时 runner 领取该主持事件,以 `smart-scheduler` 会话进入,用本次执行凭据以主持人身份领取发言权、改阶段、创建或继续安排;网站的发言权、上限与凭据校验照常生效。到限、阶段不在进行中或已有进行中安排时不判断。执行中任一写入失败,把同一事件交回主持人软件并标记已进入(任务说明与 API 主持都跳过 `execution enter`)。判断失败或置信度不足时,主持任务附 `scheduler_suggestions` 供参考。
334
334
  - **成员变化**:新邀请的专家由网站在入场事务中加入现有队列的补齐位置(本轮未发言的专家之后、会再次发言的专家之前,没有重复时在队尾),当前执行与其余安排的相对顺序不变,runner 到它的安排生效时才交付;手动专家仍由原会话提交。暂离、移除、请回等需要重排的变化会作废排队安排(留下最近一次实际安排之后的 `skipped`)。本轮未完成且无排队时,runner 不调用模型,按原顺序补回仍在场专家的安排,再请本轮尚未发言的专家开场,不唤醒主持人。
335
- - 每次判断(含交回主持人)用控制凭据写入 `POST scheduler-decisions`,`after_message_id` 取最近一条已回答安排的回答,对话中显示为该发言卡片与下一张卡片之间的独立调度卡片(“智能调度:……”)。交回主持人时写明原因:建议收束、拿不准,或“未判断:已到讨论上限 / 同时有多条留言 / 话题设置有修改 / 点名追问已开启 / 本轮未发言的专家都是手动接入”;主持人的开场任务不记录。本轮未完成且没有后续安排时(如点“跳过”后),先补回因成员变动作废的计划,否则由决策模型在本轮未发言、可自动唤醒的专家中选下一位(置信度 ≥ 0.7),拿不准或建议调整方向才交回主持人。无论开新一轮还是本轮继续,每次只安排一位,答完队列为空再判断下一位;队列里已有安排时直接接力,不判断。刚被用户“跳过”的专家本轮不再被选(除非没有别人);从未发言的专家(如新邀请)使用开场提示词;手动接入的专家不在候选内,也不会被留言分流选中。
335
+ - 每次实际向决策模型提出的判断(含交回主持人)用控制凭据写入 `POST scheduler-decisions`,`after_message_id` 取最近一条已回答安排的回答,对话中显示为该发言卡片与下一张卡片之间的独立调度卡片(“智能调度:……”)。交回主持人时写明原因:建议收束或拿不准。没有向模型提问的情形(已到讨论上限、同时有多条留言、话题设置有修改、点名追问已开启、本轮只剩主持人或只剩手动接入的专家)直接交回主持人,不调用模型,也不记录调度卡片;主持人的开场任务同样不记录。本轮未完成且没有后续安排时(如点“跳过”后),先补回因成员变动作废的计划,否则由决策模型在本轮未发言、可自动唤醒的专家中选下一位(置信度 ≥ 0.7),拿不准或建议调整方向才交回主持人。无论开新一轮还是本轮继续,每次只安排一位,答完队列为空再判断下一位;队列里已有安排时直接接力,不判断。刚被用户“跳过”的专家本轮不再被选(除非没有别人);从未发言的专家(如新邀请)使用开场提示词;手动接入的专家不在候选内,也不会被留言分流选中。
336
336
  - 每次判断与结果写入 `service.log`;阈值 0.7(所有判断统一)未经实测校准,两家的置信度不可互相套用。面板提示:开启后房间标题与最近消息会发给所选供应商。
337
337
  - 2026-10-23 用真实 Jev 密钥核对:`/v1/systemone` 的请求与答案格式与本模块一致;4 条自由房间分派全部合理(直接派出的置信度 0.91),3 条讨论建议中 2 条符合预期,单次 0.5–1.1 秒(首次连接约 5 秒)。该密钥的 `GET /v1/models` 只列出 `jev-latest` 与 `jev-preview`,没有可锁定的版本,默认仍用 `jev-latest`;Jev 升级可能改变判断结果。OpenAI Decisions 仅按文档实现,未用真实密钥调用。
338
338
 
package/docs/skills.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  主持开始新讨论时确认全自动或手动复制冷启动。自动任务明确引用仓库内的当前技能版本,并提供任务文件与 CLI 凭据文件路径;参与者先 `execution enter`,再按阶段读取材料。自动执行不建原生定时。手动方式沿用本文的原有接入;受保护房间另由用户提供私有接入文件。讨论项目和 cwd 可选,不能把 CLI 仓库路径当作讨论资料目录或擅自增加目录权限。
8
8
 
9
- 两份主技能把讨论质量与判断放在前面,仅保留当前任务必需的执行边界。主持人的 `references/manual.md` 只在手动接入、在线转交或原生任务场景读取;参与者的 `references/manual.md` 只管手动持续参与,`references/writing.md` 在首次使用某类写入、代发或失败恢复时按需查阅。自动任务不加载手动流程,也不逐轮复读帮助、全量历史和故障清单。
9
+ 两份主技能把讨论质量与判断放在前面,仅保留当前任务必需的执行边界。主持人的 `references/manual.md` 只在手动接入、在线转交或原生任务场景读取;参与者的 `references/manual.md` 管手动成员的接入文件、注册与原生定时持续参与,`references/free.md` 只在自由协作房间读取,`references/writing.md` 在首次使用某类写入、代发或失败恢复时按需查阅。自动任务不加载手动流程,也不逐轮复读帮助、全量历史和故障清单。
10
10
 
11
11
  启动提示可能已要求 `execution enter`;当前调用成功认领一次后,加载技能不再次认领。主持人先用一次 `state set` 保存判断、简报和下一步意图,再创建安排;安排落库即结束该次资格,不追加状态写入。专家提交原文即结束,由程序续租和交付后续任务。返回记录已含完整保存证据时直接核对,缺少证据或结果不明才补查询;这减少重复读写,但不削弱署名、发言权、提交去重或到限约束。
12
12