ai_co_discuss 0.2.74 → 0.2.76
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/launch.md +2 -2
- package/.agents/skills/discuss-host/references/manual.md +2 -2
- package/.agents/skills/discuss-report/SKILL.md +90 -90
- package/README.md +12 -14
- package/README.zh-CN.md +6 -8
- package/docs/architecture.md +35 -266
- package/docs/deployment.md +30 -123
- package/docs/en/architecture.md +51 -0
- package/docs/en/deployment.md +132 -0
- package/docs/en/runner.md +92 -0
- package/docs/en/workflow.md +95 -0
- package/docs/runner.md +45 -420
- package/docs/workflow.md +58 -122
- package/package.json +1 -1
- package/runner/discussion_launch.mjs +63 -6
- package/runner/free_runner.mjs +8 -4
- package/web/modern.css +1 -1
- package/web/modern.js +897 -869
- package/docs/cli.md +0 -197
- package/docs/maintainers/TODO.md +0 -19
- package/docs/skills.md +0 -102
package/docs/workflow.md
CHANGED
|
@@ -1,157 +1,93 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 使用指南
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](en/workflow.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
本文介绍房间里的协作方式。安装与接入见 [安装与接入](runner.md)。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **开启 / 结束讨论**:讨论是自由协作房间里可临时开启的推进手段,不再是另一种房间。房主在右侧成员栏点“开启讨论”(目标、轮次与总时长上限,需一位由本机唤醒的 AI 主持人),房间变为讨论状态,由主持人安排成员轮流发言;到限、收束或点“结束”后房间回到自由协作,成员、历史与结论全部保留,之后还可再次开启(计数与计时重置)。开启与结束由房主凭据调用 `POST discussion {action: start|end}`。
|
|
9
|
-
- **自由协作**:你或 AI 主持人点名一位或多位成员,各自在原会话处理请求;没有讨论安排和轮次。每位成员单独选择本机唤醒、手动接入或网页代发,见下方 [自由协作房间](#自由协作房间)。
|
|
10
|
-
- **手动接入**:自由协作成员的一种接入方式。本地成员持自己的凭据文件自行读写,可由人工或软件原生定时触发;网页成员只能阅读房间,回答需转交代发;网页 AI 从其服务器读取阅读链接,本机网站对它不可达,此时由用户粘贴材料。只有这两种成员时建房无需本机接入服务。复制参与指令、下载凭据文件或复制凭证文本都不代表已经送达、接入或唤醒。
|
|
7
|
+
## 核心概念
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
邀请手动专家后,弹窗提供“复制启动指令”;复制成功后自动关闭,复制失败则保留弹窗并显示错误,方便重试。关闭后仍可在参与者列表中再次复制,不另设“稍后再复制”按钮。复制只交付指令,不改变发言顺序。轮到手动专家时,输入框上方也提供该按钮,复制成功后显示勾号与“已复制”,2 秒后恢复。
|
|
19
|
-
|
|
20
|
-
手动专家的“注册”:专家须在自己的会话里执行 `member register`,网站才把它记为已注册;会话 ID 由 CLI 自动采集,不由专家填写。进度看板中该专家显示“待注册”(警示色,并提示把启动指令交给它的会话),注册后显示“已注册”,轮到与发言后沿用排队、发言中、本轮已发言等状态。存在待注册的手动成员时,输入框上方持续提示并提供复制启动指令,直到它注册;换发新凭证(请回)后需重新注册。只有已注册且上报了 Claude Code / Codex 会话的专家才会被本机服务接管。注册不改变发言顺序。
|
|
9
|
+
| 概念 | 说明 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 房间 | 共享的协作空间,保存消息、观点、分歧与结论 |
|
|
12
|
+
| 成员 | 房间里的 AI 实例;同一平台可有多个实例(如 `Claude`、`Claude-评审`),各有会话与模型 |
|
|
13
|
+
| 主持人 | 一位由本机唤醒的 AI,接收未点名的消息并分派,可兼任专家 |
|
|
14
|
+
| 用户 | 房间所有者与需求方,可随时 `@` 任何成员 |
|
|
21
15
|
|
|
22
16
|
## 自由协作房间
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
运行中可在右侧成员栏邀请 AI(沿用本机软件配置和本房间工作目录),也可让成员离开、请回或移除。自动成员的项目文件权限默认只读;需要它修改项目时,在邀请前选择“项目文件可写”,权限只应用于这位成员;App 与手动成员的文件权限由其软件设置,API 与网页成员不访问项目文件。填写已有会话 ID 时续接该原会话,不新建。离开会立即停止它的工作;请回时换发新凭证并续接它原来的会话。邀请时可选“手动接入”:成员不会被自动唤醒,而是得到一份私有凭据文件,由你在它的会话中按参与技能读写。自由协作房间里,手动成员执行 `member register` 之前视为不存在:不能被点名,不收请求,不进“全体各自回应”和智能调度候选;注册后与其他成员一样可被点名,其 CLI 上报会话后本机服务按软件转为托管(Claude Code、Codex、agy),此后与自动成员无异;转托管时本机服务为该成员生成新凭证并由网站替换,浏览器生成的原凭证随即失效(本机不保存原凭证)。绑定你正在使用的会话的成员会标注“借用会话”:它工作时不要在原窗口继续操作。
|
|
31
|
-
|
|
32
|
-
输入 `@全体成员`(或 `@all`)等同于点名当前所有可点名成员(含主持人,不含未注册的手动成员),主持人不汇总。同时回答还是依次回答由智能调度判断:后发言的成员需要看到前面的回答(接龙、轮流补充或反驳、逐个评审)且置信度 ≥ 0.7 时依次回答,其余情况、未开启智能调度或判断失败时同时回答;主持人不由本机唤醒时直接同时回答。判断期间消息下显示“智能调度分析中”,结果记为一行“智能调度”决策。依次回答按房间的发言顺序逐个进行,前一位的请求结束(完成、失败或取消)后下一位才收到请求;轮到时已离开的成员被跳过。手动输入 `@全体依次`(或 `@all-seq`)可不经判断直接依次回答。成员不止一位时 @ 菜单第一项为 `@全体成员`。
|
|
33
|
-
|
|
34
|
-
右栏“发言顺序”在自由协作与讨论中显示同一份顺序:已发言(✓)、正在发言、待发言依次编号;不在本次依次回答或本轮未安排的成员(含讨论主持人)列在分隔线下、不编号,离开的成员列在最后。房主把鼠标移到待发言成员上时序号变为把手,拖动(Esc 取消)或聚焦后用上下方向键调整顺序,松手即生效,已发言和正在发言者不动;拖动途中顺序被他人改动时本次拖动作废并提示重新调整:`@全体依次` 进行中时调整其余排队成员,讨论中只调整本轮排队的安排(各自的问题随之移动);主持人一次排到后续轮次时,后续轮次折叠为“后续已排 N 轮”,只读不可拖,主持人通常会在每轮结束后重新安排;没有排队时,自由协作中拖动设置房间默认顺序,之后的 `@全体依次` 按它排列,开启讨论后主持人读取的专家状态也按它列出。默认顺序未设置时为主持人在前、其余按加入顺序。
|
|
18
|
+
- **点名**:`@成员` 后,它在自己的原会话中处理并回复,多位可并行。
|
|
19
|
+
- **未点名的消息**:交给主持人分派;只有一位成员时直接交给它。
|
|
20
|
+
- **主持人派活**:主持人点名一位成员时,该成员直接回复你;点名多位时,都完成后主持人再汇总。
|
|
21
|
+
- **全体回答**:`@全体成员` 请所有成员各自回答,`@全体依次` 按发言顺序逐个回答。
|
|
22
|
+
- **停止与重发**:成员菜单可停止当前执行;失败的执行可一键重发。
|
|
35
23
|
|
|
36
|
-
|
|
24
|
+
普通成员之间的 `@` 只是引用,不会唤醒对方。多位成员共享同一项目目录时不加锁,由你划分文件;两位成员同时改动同一文件时,成员栏会提示可能的冲突。
|
|
37
25
|
|
|
38
|
-
|
|
26
|
+
## 开启讨论
|
|
39
27
|
|
|
40
|
-
|
|
28
|
+
在成员栏点“开启讨论”,或建房时勾选“创建后开启讨论”:
|
|
41
29
|
|
|
42
|
-
|
|
30
|
+
1. 填写讨论目标,设置轮次与时长上限(网页默认 3 轮、不限时长)。
|
|
31
|
+
2. 主持人拆题并安排成员轮流发言;成员先独立提出观点,再回应彼此的论证。
|
|
32
|
+
3. 到达上限、主持人宣布目标完成或你点“结束”后,房间回到自由协作,记录与结论保留,之后可以再次开启。
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
讨论中可以随时 `@` 任何成员提问,不占轮次。你的留言交给主持人处理,不打断正在发言的专家;需要立即生效时,在进度看板点“跳过”当前发言。
|
|
45
35
|
|
|
46
|
-
|
|
36
|
+
### 点名追问与智能衔接
|
|
47
37
|
|
|
48
|
-
|
|
38
|
+
新建讨论时可开启“点名追问”:每轮结束后,主持人给每位专家写针对性的追问。关闭时(默认),专家读完上一轮回答后直接互相回应,主持人只负责引导方向。
|
|
49
39
|
|
|
50
|
-
|
|
40
|
+
### 讨论上限
|
|
51
41
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
新建讨论时可选“点名追问”,默认关闭;之前创建的房间视为开启,保持原有方式。
|
|
55
|
-
|
|
56
|
-
- **开启**:每轮结束后主持人读完回答,给每位专家写针对性的追问。
|
|
57
|
-
- **关闭**:首轮仍由主持人拆题出题;之后专家统一按“先读上一轮回答、回应不同意或需补充的观点、可 @ 彼此提问”发言,主持人用自己的主持说明引导方向。同时开启“智能调度”时,每轮结束由决策模型判断:还有分歧就自动开下一轮(先请最需要回应的专家,不是上一轮最后发言者),可以收束、跑偏或卡住才唤醒主持人。决策模型每次只安排一位发言人,那位答完后再判断下一位(含点“跳过”、邀请或成员变动后队列为空的情况);手动接入的专家不由它安排。自动开轮受轮次与时长上限约束,到限照常交给主持人处理。
|
|
58
|
-
|
|
59
|
-
## 讨论上限
|
|
60
|
-
|
|
61
|
-
准备或暂停阶段可配置以下参数;活动讨论需先暂停再修改,保存参数不重置计时或历史轮次。
|
|
62
|
-
|
|
63
|
-
| 参数 | 范围与作用 |
|
|
42
|
+
| 参数 | 说明 |
|
|
64
43
|
| --- | --- |
|
|
65
|
-
| 轮次上限 |
|
|
66
|
-
|
|
|
67
|
-
| 单次 AI 执行 |
|
|
68
|
-
| 达到上限后 | 讨论结束,房间回到自由协作;不自动宣布达成共识 |
|
|
69
|
-
|
|
70
|
-
网页新建默认 3 轮、不限时长、单次执行不限时;到限一律结束讨论。新建表单分别显示轮次和时长上限,点击数值才展开滚轮;鼠标滚轮逐格切换,停止滚动即停住,鼠标或手指在屏幕上拖动则保留松手惯性。选择即时生效,点击外部即可收起,无需确认。时长收起时显示小时及剩余分钟,展开后分为小时、分钟两列,按 1 小时、1 分钟选择;轻点各列中间数字可直接输入,启用时长上限后范围为 1–1440 分钟,24 小时时分钟固定为 0。拖动松手后先按阻尼减速,再按预计停止位置吸附;系统“减少动画”下仍保留拖动惯性,只去掉数字的三维翻转。两项各自提供“不限”,关闭“不限”恢复此前数值,首次启用时长上限为 1 小时,互不改动;下方实时说明哪个上限先到便触发所选动作。未传参数的 CLI 建房默认轮次、总时长与单次执行不限;旧房间保留已有设置。全自动支持不限总时长:按轮次上限或手动停止结束,重启不自动添加时长上限;主动设置的单次 AI 执行超时和租约仍有效。CLI 可额外指定未来 24 小时内的运行截止,但不能延长话题已设的截止。
|
|
71
|
-
|
|
72
|
-
“主持”及“接入”显示配置、已用/剩余时间、已完成轮次和到限状态。新专家安排及观点由服务端拒绝越界,原提交的幂等重试和用户留言保留。自动程序定期调用到限处理;手动主持每次原生检查时执行 `room limits ROOM --check --as 本人`,也可由用户点击界面中的检查按钮。仅打开页面不触发写入或启动 AI。新建的讨论房间由程序按“专家观点数 ÷ 在场专家数”统计轮次(`limits.segment_rounds`),与主持人的发言计划结构和花名册是否被排满无关;达到 `round_cap`(初始为 `max_rounds`)即到限,取消排队安排。此前创建的房间没有轮次预算记录,沿用按花名册逐轮统计的旧口径,不做迁移。网页的轮次显示(右上角进度、看板、对话缩略图的轮次分组)使用同一口径:房间视图的 `round_groups` 每满“在场专家数”条观点成一轮,重新开始或追加轮数时在新计数起点另起一轮;只统计最近一次讨论从开启(`session.started_message_id` 之后)到结束(`ended_message_id`)之间的消息,编号从 1 开始,讨论之外的自由协作消息不分轮;`rounds` 仍按“每位专家各发言一次”生成,只用于安排与花名册(`turn roster`、邀请补齐位置)。旧房间的 `round_groups` 与 `rounds` 相同。
|
|
73
|
-
|
|
74
|
-
到限后程序结束讨论:取消未完成安排、撤销旧自动执行(运行换代并停止),阶段记为 `concluded`,房间回到自由协作,与用户点“结束”相同;主持人不自行总结,`stop_reason` 保留到限原因。目标提前完成时由主持人 `room limits --done` 通知程序,效果与到限相同(原因记为 `goal_done`);主持人在无阻碍项时直接 `room update --phase concluded` 也同样回到自由协作。讨论结束后,中间区在讨论最后一条消息之后显示结束卡,写明结束原因与目标是否完成;卡上的“继续讨论”沿用讨论目标、按新填的轮次与时长上限再次开启讨论(计数与计时重置),也可在成员栏重新“开启讨论”;需要总结时直接请主持人写一份。讨论结束(到限、宣布完成、收束或点“结束”)时,讨论期间尚未被主持人回应的用户留言——`held_since` 起、主持人最后一条消息之后、且未点名任何人的——按自由协作规则补交给主持人(只有一位成员时交给它),不用重发。页面到限提示不代表外部手动软件已停止。升级前已因到限或目标完成而暂停的讨论,看板只提供“结束”。
|
|
75
|
-
|
|
76
|
-
升级前已收束、仍未回到自由协作的讨论可重启:用户指定新的轮次与总时长上限,计数起点和计时重置,历史与原结论保留,随后按暂停状态重新启动。已收束的页面展示结束状态,不再显示活动倒计时或后来才触发的到限警告。
|
|
77
|
-
|
|
78
|
-
## 角色与参与方式
|
|
79
|
-
|
|
80
|
-
| 角色 | 责任 |
|
|
81
|
-
| --- | --- |
|
|
82
|
-
| 用户 | 提出目标、约束、决策偏好;可留言、参与或担任主持人 |
|
|
83
|
-
| 主持人 | 明确问题、接入专家、决定下一安排、维护主持简报与状态、保留分歧并收束 |
|
|
84
|
-
| 本地专家(cli) | 自主读取材料、提交自己安排的回答;管理自己创建的原生定时任务 |
|
|
85
|
-
| 在线专家(online) | 从可访问链接按需阅读,在原聊天形成回答;主持人或用户保留真实原文后代发 |
|
|
86
|
-
|
|
87
|
-
主持人可以兼任专家。自己的观点用 `opinion` 并关联自己的正式安排,主持说明和共识总结用 `moderation`;不能把个人意见直接写成共识。参与者身份为用户与 AI 实例:平台(Codex、Claude、Gemini、DeepSeek、Qwen、Grok)相当于类型,房间里的参与者是它的实例,名称为平台名或“平台名-后缀”(如 `Claude-评审`,后缀 1–16 个字母、数字、下划线或汉字)。同一平台的多个实例各有会话、模型和思考强度,名称在同房间唯一;在角色卡片库中再次选择同一平台即新增一个实例。本机默认软件配置按平台保存,实例的模型与强度只用于本场。
|
|
88
|
-
|
|
89
|
-
## 从议题到结论
|
|
90
|
-
|
|
91
|
-
1. **准备**:确定目标、背景、约束、参与者和停止边界。区别讨论截止、必要回答等待边界与各端任务运行上限。主持人建房后回读资料,再交付接入文本。网页新建只填写话题名称与用户原文,目标、背景与边界由主持人首次接手时补全,已有内容不覆盖。
|
|
92
|
-
2. **独立初始观点**:必要专家先基于相同目标与材料提出自己的见解;提交前不读他人观点和主持结论。已受到影响应如实说明。串行安排不等于技术盲评。
|
|
93
|
-
3. **深入讨论**:阅读彼此观点,围绕影响选择的争点追加论证、修正判断或提出 @ 追问。主持人在每轮内按相关程度决定先后,每位专家各回应一次;不连续安排刚发言的专家,新一轮也不先安排上一轮最后发言者,仅在需要其本人澄清刚发表内容时例外。
|
|
94
|
-
4. **必要复核**:对拟定结论核对原意、条件和未决影响。解释某人的话受到质疑时,请原作者澄清;另一人赞同不能代替原作者确认。不要求所有议题都多做一轮复核。
|
|
95
|
-
5. **收束**:将共识总结作为独立主持消息留档,保留分歧、未确认事项及下一步。更新共享状态和阶段,核验本次任务停止;达到时限不等于问题已解决。
|
|
96
|
-
|
|
97
|
-
阶段值为 `preparing`、`initial`、`discussing`、`paused`、`concluded`。讨论阶段由主持人结合目标决定;配置的到限处理可以暂停,收束完成仍需主持人确认。一轮指参与专家各完成一次完整观点,同轮补充只计一次;主持说明和用户留言不推进轮次。
|
|
98
|
-
|
|
99
|
-
## 正式安排
|
|
100
|
-
|
|
101
|
-
同一时刻只能有一个未完成安排(整轮中排队的安排不计)。没有发言权:专家的观点自动绑定到自己当前未完成的安排(`--turn` 可省略,带了必须与之相符),保存完整回答后安排为 `answered`,整轮仍有排队且无用户新操作时自动接力,否则交回主持人。消息与安排状态在同一事务中保存。
|
|
102
|
-
|
|
103
|
-
AI 不借用用户身份发言;用户本人留言不受安排限制,话题暂停或收束时仍可留言(如 @主持人 提问),已存档房间除外。@ 和回复只是内容关联,不自动创建安排;讨论中点名成员的直接提问见上文。
|
|
104
|
-
|
|
105
|
-
### 整轮安排与用户介入
|
|
106
|
-
|
|
107
|
-
主持人可以一次排好本轮顺序:先 `turn roster` 申请最新专家状态(在场专家、本轮待发言者 `waiting` 与 `revision`),再 `turn create --plan-file … --roster <revision>`。多项计划未带 revision 返回 400,名单已变化(有人加入、暂离或发言)返回 409,漏掉 `waiting` 中的专家返回 400 并列出名字;全自动讨论中尚未注册的手动专家(`unregistered`)不在 `waiting` 中,`turn create` / `turn continue` 排入它们返回 400 并列出名字,它们注册后才自动加入本轮队列;主持人本人不需要排入,API 主持人的 `arrange_turns` 由接入程序自动取最新 revision。第一项立即生效,其余为 `queued`。专家保存回答后,网站在同一事务中把下一位排队专家的安排转为当前;末位答完才交回主持人。接力不发通知,需要逐个转交的在线专家不放进整轮。
|
|
108
|
-
|
|
109
|
-
用户留言或修改目标、背景、边界、焦点会记为待主持人处理(`held_since`),不打断正在发言的专家;该专家保存回答后交回主持人而不接力。主持人执行期间新到的用户留言,不会因主持人创建或继续安排而清除,下一位专家答完后仍交回主持人处理;网页在这些留言上标注“待主持人处理”。要立即生效,用户在进度看板点「跳过」:用户代该专家保存一条“本次发言已被用户跳过”的观点(计为本轮已发言、对话中保留卡片),本次执行随之完成;因有待处理留言,交回主持人,排队安排保留。主持人处理后用 `turn continue` 按原计划继续,或直接创建新安排,剩余排队改为 `skipped`。建房时开启“智能调度”的讨论,留言由决策模型先判断:有把握是“仅补充信息”就按原计划继续,是在问某位专家就先请该专家回应,其余情况才交回主持人;每次判断都在对话中留一行“智能调度”记录。暂停保留当前与排队安排,点“重启”由原专家继续;若要跳过当前安排,可点“跳过”,保存跳过发言后恢复讨论,按原计划交给下一位(无排队则交回主持人);邀请只排在本轮队尾,提前结束、到限处理、收束、更换主持人或专家暂离、请回、移除才会作废全部排队安排。
|
|
110
|
-
|
|
111
|
-
在线专家由主持人以自身操作者身份、指定原专家代发。正常推进不超时夺取;到限会结束讨论,取消未完成安排并撤销旧自动执行。
|
|
112
|
-
|
|
113
|
-
## 按需读取与表达
|
|
114
|
-
|
|
115
|
-
先读房间概要和当前安排,再按问题读取背景、共享状态或指定原文。独立阶段遵守阅读边界;深入阶段补读与问题相关的他人观点。增量读取以实际读过的编号为起点,每次最多 20 条,按 `next_after` 继续,不直接跳到房间 `latest_id`。
|
|
116
|
-
|
|
117
|
-
回答较长时附简述,通常一两句,说明本次主张、变化或分歧;详细论据、适用条件、数字、验证和引用放在正文,不能用摘要代替原文。代发保留真实作者和完整引用关系,主持人另写的摘要注明实际摘要作者。
|
|
118
|
-
|
|
119
|
-
观点应直接回答当前问题,以证据支持判断;有依据时坚持,也可以说明理由后修正。分歧区分事实、假设、标准和偏好,分别查证、比较或交给决策者。外部引用、估计与实测分开,未核验数字不能因多人赞同变成事实。
|
|
44
|
+
| 轮次上限 | 每位专家各完成一次观点计一轮 |
|
|
45
|
+
| 总时长 | 只统计进行中的时间,暂停期间不计 |
|
|
46
|
+
| 单次 AI 执行 | 单次执行超时即暂停 |
|
|
120
47
|
|
|
121
|
-
|
|
48
|
+
到限后讨论结束;到限不代表已经达成共识。
|
|
122
49
|
|
|
123
|
-
|
|
50
|
+
## 智能调度
|
|
124
51
|
|
|
125
|
-
|
|
52
|
+
建房时可在“智能调度”下添加一个决策模型(TypeSafe Jev;OpenAI Decisions 为实验性),用你自己的密钥按次计费。开启后,常规分派由决策模型判断,不必每次唤醒主持人:
|
|
126
53
|
|
|
127
|
-
|
|
54
|
+
- 未点名的消息:明确对某位成员说时直接交给他,或请全体回答、无需回应;其余仍交给主持人。
|
|
55
|
+
- `@全体成员`:判断应同时回答还是依次回答。
|
|
56
|
+
- 讨论中:用户插话后继续原有安排,轮次之间选出下一位发言人。
|
|
128
57
|
|
|
129
|
-
|
|
58
|
+
决策模型只在把握足够时执行,否则交回主持人。每次判断都会显示在对话中,可以评价。开启后,房间标题与最近消息会发送给所选供应商。
|
|
130
59
|
|
|
131
|
-
##
|
|
60
|
+
## 成员管理
|
|
132
61
|
|
|
133
|
-
|
|
134
|
-
- `state_text`:详细共享状态,保存依据、已处理编号、下一步及其执行情况,最多 4000 字符。
|
|
135
|
-
- `state_until`:主持人实际已读材料截止编号,不证明后续安排、通知或清理已经完成。
|
|
62
|
+
在右侧成员栏可以:
|
|
136
63
|
|
|
137
|
-
|
|
64
|
+
- **邀请**成员。Codex 与 Claude 可以填写已有会话 ID,带着原会话的上下文加入;讨论中新邀请的专家排在本轮队尾。
|
|
65
|
+
- **暂离、请回或移除**成员,讨论不会因此暂停。
|
|
66
|
+
- **更换主持人**:点成员卡片上的皇冠。讨论中更换会暂停讨论。
|
|
67
|
+
- **调整发言顺序**:拖动右栏“发言顺序”中待发言的成员。
|
|
138
68
|
|
|
139
|
-
|
|
69
|
+
项目文件权限按成员设置,默认只读。
|
|
140
70
|
|
|
141
|
-
##
|
|
71
|
+
## 手动接入与网页 AI
|
|
142
72
|
|
|
143
|
-
|
|
73
|
+
- **手动接入**:成员不被自动唤醒,凭自己的凭据文件读写,由你或软件的定时功能触发。成员须先在自己的会话中执行 `codiscuss member register` 才算到场。
|
|
74
|
+
- **网页 AI**:在线聊天产品只能阅读房间。在成员栏复制阅读通知交给它,再在输入框选择“代发”粘贴它的回答。网页 AI 访问不到本机地址。
|
|
144
75
|
|
|
145
|
-
|
|
76
|
+
讨论的主持人必须由本机唤醒;网页 AI 只参与自由协作。
|
|
146
77
|
|
|
147
|
-
|
|
78
|
+
## 技能
|
|
148
79
|
|
|
149
|
-
|
|
80
|
+
AI 按随包分发的技能参与房间,`codiscuss setup` 会为选定的软件自动安装:
|
|
150
81
|
|
|
151
|
-
|
|
82
|
+
| 技能 | 使用者 | 作用 |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| [discuss-participant](../.agents/skills/discuss-participant/SKILL.md) | 所有成员 | 阅读、发言与可靠写入 |
|
|
85
|
+
| [discuss-host](../.agents/skills/discuss-host/SKILL.md) | 主持人(同时调用参与技能) | 安排发言、保存进度与收束 |
|
|
86
|
+
| [discuss-report](../.agents/skills/discuss-report/SKILL.md) | 报告整理者 | 把已有讨论整理为带原文引用的报告 |
|
|
152
87
|
|
|
153
|
-
|
|
88
|
+
在 Codex 中用 `$discuss-host` 调用;其他软件没有自动发现时,直接要求它读取对应的 `SKILL.md`。
|
|
154
89
|
|
|
155
|
-
|
|
90
|
+
## 附件与导出
|
|
156
91
|
|
|
157
|
-
|
|
92
|
+
- **附件**:每条消息最多 10 个,单个最多 20 MiB,图片与 PDF 可在站内预览。上传不代表 AI 已读取。
|
|
93
|
+
- **导出与导入**:房间菜单的“导出对话”下载原始数据与附件;在另一个站点“导入房间…”即可得到可继续使用的副本。
|
package/package.json
CHANGED
|
@@ -73,7 +73,8 @@ conversation 还须 current_session_id,当前会话接手首个主持任务。
|
|
|
73
73
|
`;
|
|
74
74
|
|
|
75
75
|
// Windows PowerShell 5.1 ships WinForms. A background process cannot take the foreground, so the owner must be
|
|
76
|
-
// shown (
|
|
76
|
+
// shown (transparent) as a topmost window; the dialog it owns then stays above the browser that asked for it.
|
|
77
|
+
// Keep the owner on-screen because the Common Item Dialog positions itself relative to its owner.
|
|
77
78
|
// windowsHide turns the process's first Show into a hide, so the owner is shown a second time to really appear.
|
|
78
79
|
// Under the local service the whole process chain has no console and no foreground rights: Windows then silently ignores
|
|
79
80
|
// SetWindowPos(HWND_TOPMOST) on the dialog (it returns success). So once the dialog exists, a timer in its message loop
|
|
@@ -104,16 +105,72 @@ const PICKER_RAISE_SOURCE = [
|
|
|
104
105
|
' }',
|
|
105
106
|
'}',
|
|
106
107
|
].join(' ');
|
|
108
|
+
// Use the Common Item Dialog: unlike Framework WinForms, it has an editable address bar.
|
|
109
|
+
// COM declarations retain the native vtable order through the last method used.
|
|
110
|
+
const FOLDER_PICKER_SOURCE = `
|
|
111
|
+
public static class FolderPicker {
|
|
112
|
+
const uint FOS_PICKFOLDERS = 0x20, FOS_FORCEFILESYSTEM = 0x40, FOS_PATHMUSTEXIST = 0x800;
|
|
113
|
+
const uint SIGDN_FILESYSPATH = 0x80058000;
|
|
114
|
+
const int CANCELLED = unchecked((int)0x800704C7);
|
|
115
|
+
[ComImport, Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
116
|
+
interface IFileDialog {
|
|
117
|
+
[PreserveSig] int Show(IntPtr owner);
|
|
118
|
+
void SetFileTypes(uint count, IntPtr filters);
|
|
119
|
+
void SetFileTypeIndex(uint index);
|
|
120
|
+
void GetFileTypeIndex(out uint index);
|
|
121
|
+
void Advise(IntPtr events, out uint cookie);
|
|
122
|
+
void Unadvise(uint cookie);
|
|
123
|
+
void SetOptions(uint options);
|
|
124
|
+
void GetOptions(out uint options);
|
|
125
|
+
void SetDefaultFolder(IShellItem folder);
|
|
126
|
+
void SetFolder(IShellItem folder);
|
|
127
|
+
void GetFolder(out IShellItem folder);
|
|
128
|
+
void GetCurrentSelection(out IShellItem item);
|
|
129
|
+
void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string name);
|
|
130
|
+
void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string name);
|
|
131
|
+
void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string title);
|
|
132
|
+
void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string label);
|
|
133
|
+
void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string label);
|
|
134
|
+
void GetResult(out IShellItem item);
|
|
135
|
+
}
|
|
136
|
+
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
137
|
+
interface IShellItem {
|
|
138
|
+
void BindToHandler(IntPtr context, ref Guid handler, ref Guid iid, out IntPtr result);
|
|
139
|
+
void GetParent(out IShellItem parent);
|
|
140
|
+
void GetDisplayName(uint format, [MarshalAs(UnmanagedType.LPWStr)] out string name);
|
|
141
|
+
}
|
|
142
|
+
/// <summary>Choose an existing filesystem folder, or return null when cancelled.</summary>
|
|
143
|
+
public static string Show(IntPtr owner) {
|
|
144
|
+
var dialog = (IFileDialog)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")));
|
|
145
|
+
IShellItem item = null;
|
|
146
|
+
try {
|
|
147
|
+
uint options;
|
|
148
|
+
dialog.GetOptions(out options);
|
|
149
|
+
dialog.SetOptions(options | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST);
|
|
150
|
+
dialog.SetTitle("选择项目目录");
|
|
151
|
+
int result = dialog.Show(owner);
|
|
152
|
+
if (result == CANCELLED) return null;
|
|
153
|
+
Marshal.ThrowExceptionForHR(result);
|
|
154
|
+
dialog.GetResult(out item);
|
|
155
|
+
string path;
|
|
156
|
+
item.GetDisplayName(SIGDN_FILESYSPATH, out path);
|
|
157
|
+
return path;
|
|
158
|
+
} finally {
|
|
159
|
+
if (item != null) Marshal.ReleaseComObject(item);
|
|
160
|
+
Marshal.ReleaseComObject(dialog);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
107
165
|
const WINDOWS_PICKER = [
|
|
166
|
+
'$ErrorActionPreference = "Stop"',
|
|
108
167
|
'Add-Type -AssemblyName System.Windows.Forms',
|
|
109
168
|
'[Console]::OutputEncoding = [Text.Encoding]::UTF8',
|
|
110
|
-
'$owner = New-Object System.Windows.Forms.Form -Property @{ TopMost = $true; ShowInTaskbar = $false; FormBorderStyle = "None"; StartPosition = "
|
|
111
|
-
`Add-Type -TypeDefinition '${PICKER_RAISE_SOURCE}'`,
|
|
169
|
+
'$owner = New-Object System.Windows.Forms.Form -Property @{ TopMost = $true; Opacity = 0; ShowInTaskbar = $false; FormBorderStyle = "None"; StartPosition = "CenterScreen"; Size = New-Object System.Drawing.Size(1, 1) }',
|
|
170
|
+
`Add-Type -TypeDefinition '${PICKER_RAISE_SOURCE} ${FOLDER_PICKER_SOURCE}'`,
|
|
112
171
|
'$owner.Show(); $owner.Hide(); $owner.Show(); $owner.Activate()',
|
|
113
172
|
'$timer = New-Object System.Windows.Forms.Timer -Property @{ Interval = 150 }; $timer.Add_Tick({ if ([PickerRaise]::OwnedBy($owner.Handle)) { $timer.Stop() } }); $timer.Start()',
|
|
114
|
-
'
|
|
115
|
-
'if ($dialog.ShowDialog($owner) -eq "OK") { $dialog.SelectedPath }',
|
|
116
|
-
'$owner.Close()',
|
|
173
|
+
'try { [FolderPicker]::Show($owner.Handle) } finally { $timer.Stop(); $timer.Dispose(); $owner.Close(); $owner.Dispose() }',
|
|
117
174
|
].join('; ');
|
|
118
175
|
const PICKERS = {
|
|
119
176
|
win32: ['powershell.exe', ['-NoProfile', '-STA', '-Command', WINDOWS_PICKER]],
|
package/runner/free_runner.mjs
CHANGED
|
@@ -87,7 +87,7 @@ export function free_task_files(config, member, execution, binding) {
|
|
|
87
87
|
skills: [join(config.project, '.agents/skills/discuss-participant/SKILL.md')], cli, subcommands: TASK_SUBCOMMANDS,
|
|
88
88
|
...(bound_access ? { access_env: 'DISCUSS_ACCESS_FILE' } : { access_file: access_path }),
|
|
89
89
|
instructions: `${discussion ? '讨论房间:用户直接向你提问(' + messages + ')。这不是你的发言安排,不要领取发言权、不要安排他人,也不改变讨论进度,只回答这个提问。' : '自由协作房间:用户点名了你(' + messages + ')。'}${access_rule}先用 ${cli} message list ${config.room} --after ${execution.read_until}${access_arg} 读取新消息,按时间顺序处理,后面的消息可能修正前面的,以最新指令为准。${host_rule}完成后用 ${cli} message post ${config.room} --as ${member.name} --kind chat --execution ${execution.id} --body-file 草稿 --submission-id 新UUID${access_arg} 提交一次回答。提交被拒绝说明本次执行已被打断或过期:保留草稿并结束,不要去掉 --execution 改作普通发言。草稿只写入 draft_directory;${project_rule}不读取或打印凭据内容,不建定时任务。` });
|
|
90
|
-
const prompt = `用户在${discussion ? '讨论' : '自由协作'}房间点名了你(${member.name}),对应消息 ${messages}。请先读取任务文件:${task_path},按其中说明读取新消息、完成请求,并用 ${cli} message post 带 --execution ${execution.id} 提交一次回答后结束。命令只用任务内 subcommands 列出的 ${cli}
|
|
90
|
+
const prompt = `用户在${discussion ? '讨论' : '自由协作'}房间点名了你(${member.name}),对应消息 ${messages}。请先读取任务文件:${task_path},按其中说明读取新消息、完成请求,并用 ${cli} message post 带 --execution ${execution.id} 提交一次回答后结束。命令只用任务内 subcommands 列出的 ${cli} 子命令,列目录、搜索或读取文件用软件自带的文件工具,不用 shell 命令;${project_rule}不读取凭据内容。`;
|
|
91
91
|
return { id, participant: member.name, directory: config.directory, draft_directory,
|
|
92
92
|
skills_directory: join(config.project, '.agents', 'skills'), access_path, prompt };
|
|
93
93
|
}
|
|
@@ -134,7 +134,8 @@ export class FreeRoomRunner {
|
|
|
134
134
|
this.routing = new Set();
|
|
135
135
|
this.children = new Set();
|
|
136
136
|
this.members = new Map();
|
|
137
|
-
|
|
137
|
+
// Execution id -> why its session ended on its own, such as a command refused in unattended mode ('' when unknown).
|
|
138
|
+
this.exited = new Map();
|
|
138
139
|
this.snapshots = new Map();
|
|
139
140
|
// Execution id -> { phase, at, sent_phase, sent_at, notes, next_seq }: what the member's software last showed it doing,
|
|
140
141
|
// and the progress notes not yet on the website.
|
|
@@ -343,7 +344,7 @@ export class FreeRoomRunner {
|
|
|
343
344
|
on_note: text => this.note(execution.id, text),
|
|
344
345
|
// A stopped process may report after the next execution started; only its own execution takes the result.
|
|
345
346
|
on_failure: error => { try { if (this.row(member.name)?.execution_id === execution.id) this.save(member.name, { error }); } catch { /* Store may already be closed at shutdown. */ } },
|
|
346
|
-
on_exit:
|
|
347
|
+
on_exit: denial => this.exited.set(execution.id, denial || ''),
|
|
347
348
|
});
|
|
348
349
|
if (result.session_id) this.store.bind(member.name, result.session_id);
|
|
349
350
|
this.save(member.name, { status: result.status === 'sidecar_pending' ? 'sidecar_pending' : 'running', attempts: 0 });
|
|
@@ -377,7 +378,10 @@ export class FreeRoomRunner {
|
|
|
377
378
|
if (row.error) return this.fail(member.name, execution.id, row.error);
|
|
378
379
|
if (this.exited.has(execution.id)) {
|
|
379
380
|
const latest = await api(this.config, `/executions/${execution.id}`);
|
|
380
|
-
|
|
381
|
+
const denial = this.exited.get(execution.id);
|
|
382
|
+
if (['dispatched', 'started'].includes(latest.status)) return this.fail(member.name, execution.id, denial
|
|
383
|
+
? `${member.name} ${denial},未提交本次回答`
|
|
384
|
+
: `${member.name} 会话已结束,但未提交本次回答;请核对原会话确认是否已有内容`);
|
|
381
385
|
return this.save(member.name, { status: 'idle' });
|
|
382
386
|
}
|
|
383
387
|
if (Date.now() >= execution.created_at * 1000 + minutes * 60_000) return this.fail(member.name, execution.id, `超过单次执行时限 ${minutes} 分钟;请核对原会话确认是否已有内容`);
|