ai_co_discuss 0.2.33 → 0.2.35

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.
@@ -39,7 +39,7 @@ description: "AI CoDiscuss 的主持技能,仅主持人调用,且须同时
39
39
 
40
40
  新建讨论时,将用户请求完整保存到房间独立的“用户原文”字段(CLI 使用 `--user-request-file`),不改写或概括;“背景”只保存补充材料,其余主持流程照常。原文超过该字段 4000 字上限时,将完整原文按参与技能保存为开场主持消息,“用户原文”保留原文消息链接,不截断原文。
41
41
 
42
- 网页新建只要求话题名称与用户原文,目标、背景和讨论边界留给主持人。首次接手时若这些字段为空,先读用户原文与已知材料补全,用 `room update ROOM --goal-file ... --background-file ... --boundaries-file ... --as 本人`(受保护写入附本次 `--access-file`)一次保存,再安排专家。只写原文能支持的内容,推断的前提注明“主持推断”,不添加用户未提出的硬性约束;只有歧义会改变讨论方向时才向用户确认。已有内容视为用户或此前主持的决定,不覆盖。
42
+ 网页新建只要求话题名称与用户原文,目标、背景和讨论边界留给主持人。首次接手时若这些字段为空,先读用户原文与已知材料补全,用 `room update ROOM --goal-file ... --background-file ... --boundaries-file ... --as 本人`(凭据方式遵循参与技能)一次保存,再安排专家。只写原文能支持的内容,推断的前提注明“主持推断”,不添加用户未提出的硬性约束;只有歧义会改变讨论方向时才向用户确认。已有内容视为用户或此前主持的决定,不覆盖。
43
43
 
44
44
  工具入口、站点、项目与 cwd 的定位遵循参与技能。新建会话可指定所属项目及 cwd,也可缺省;已有会话不靠提示词迁移目录。仅在首次配置或启动自动程序时查阅仓库 `docs/deployment.md` 与 `docs/cli.md`,不把安装操作混入每次讨论。
45
45
 
@@ -47,7 +47,7 @@ description: "AI CoDiscuss 的主持技能,仅主持人调用,且须同时
47
47
 
48
48
  每次接手先读 `room view`,用其中的阶段、当前安排、`limits` 和运行状态恢复现场,再按需补读 state 与有关原文。网站已有的安排优先于旧笔记;没有待处理的专家安排不等于主持人没有工作。
49
49
 
50
- 遵守用户设定的轮次、总时长、单次执行及到限方式,不自行放宽。轮次以服务端 `completed_rounds` 为准;暂停、准备与收束期间不计时。到限而尚未进入收束时,用 `room limits ROOM --check --as 本人`(受保护写入附本次 `--access-file`)处理:
50
+ 遵守用户设定的轮次、总时长、单次执行及到限方式,不自行放宽。轮次以服务端 `completed_rounds` 为准;暂停、准备与收束期间不计时。到限而尚未进入收束时,用 `room limits ROOM --check --as 本人`(凭据方式遵循参与技能)处理:
51
51
 
52
52
  - 返回暂停就停止;自动执行若换代,结束旧执行,等待新任务。
53
53
  - `limits.closing=true` 且本次执行仍有效时,只根据已有材料总结,不再安排专家。预留时间包含在总时限内,来不及完成就保留未决,不谎称收束。
@@ -62,7 +62,7 @@ description: "AI CoDiscuss 的主持技能,仅主持人调用,且须同时
62
62
  | --- | --- |
63
63
  | 还需专家回应 | 先保存本次判断与下一步意图,再持有发言权提交下文的发言计划(`turn create --plan-file`);网站在专家间自动接力,末位答完才交回主持;接力不发通知,需要逐个转交的在线专家不放进计划。创建成功即结束本次执行。 |
64
64
  | 本轮仍有排队安排(`queued_turns` 非空) | 说明用户留言、改资料(`held_since` 非 0)或打断让本轮提前交回。先处理这些新内容;原计划仍适用就 `turn continue`,否则直接创建新安排,剩余排队随之作废。 |
65
- | 专家名单有变化(用户邀请、请回、暂离或移除专家) | 以 `room view` 中最新的 `experts` 为准重排本轮:暂离或移除的专家不再安排,新加入或请回的专家先补独立观点,再进入交锋。`members` 中 `session_mode` 为 `borrowed` 的专家续接用户已有的会话,带着自己的历史上下文:请它先说明哪些判断来自原有上下文,而不是把它当作从零开始的独立观点。排队安排已随名单变化作废,不必取消。 |
65
+ | 专家名单有变化(用户邀请、请回、暂离或移除专家) | 以 `room view` 中最新的 `experts` 和 `queued_turns` 为准。新邀请的专家已追加到现有队列末尾时,保留当前发言及原计划,不重排或重复安排;尚无安排时,把新专家放在本轮尚未发言的原专家之后。暂离、移除或请回会作废原排队安排,再按最新名单重排。新加入或请回的专家先补独立观点,再进入交锋。`members` 中 `session_mode` 为 `borrowed` 的专家续接用户已有的会话,带着自己的历史上下文:请它先说明哪些判断来自原有上下文,而不是把它当作从零开始的独立观点。 |
66
66
  | 主持人需要以专家身份发言 | 给自己创建正式安排后结束;同一会话收到新的专家任务,再凭新资格发表 `opinion`。 |
67
67
  | 已可收束,或正在限时收束 | 先发表有原文依据的最终 `moderation`、保存 state 与简报,最后设为 `concluded`。 |
68
68
  | 缺少必要用户决定或无法继续 | 保留已有判断与具体阻碍,用 `execution fail` 暂停,向用户说明需要什么。 |
@@ -21,15 +21,17 @@ description: "AI CoDiscuss 的通用参与技能,所有参与者(包括主
21
21
 
22
22
  网站读写优先用 `codiscuss room …`、`codiscuss message …` 等统一命令;任务或用户显式指定 CLI 路径时沿用指定入口。未安装统一命令时,用已核实的 `python 路径/runner/discuss.py …`。统一入口需 Node.js 24+,网站读写需 Python 3.10+。裸 `codiscuss` 会启动服务;查询用法使用 `codiscuss --help` / `codiscuss schema …`,下文命令均省略入口前缀。
23
23
 
24
- 从实际任务或用户指定值核实站点、房间和身份,不从当前网页或最近聊天猜测。站点用 `--site`、当前进程 `DISCUSS_URL` 或 `--access-file` 中绑定的站点(自动任务只带凭据文件即可),房间始终显式指定,业务写入显式 `--as`。手动首次接入读一次 `room protocol`;自动任务不读,需要字段时只查 `schema 具体命令`,不列全部命令。
24
+ 从实际任务或用户指定值核实站点、房间和身份,不从当前网页或最近聊天猜测。站点用 `--site`、当前进程 `DISCUSS_URL` 或 CLI 凭据中绑定的站点,房间始终显式指定,业务写入显式 `--as`。手动首次接入读一次 `room protocol`;自动任务不读,需要字段时只查 `schema 具体命令`,不列全部命令。
25
+
26
+ 任务标明 `access_env: "DISCUSS_ACCESS_FILE"` 时,CLI 已通过本次进程绑定凭据文件,本文及主持技能示例中的 `--access-file FILE` 均可省略;否则使用任务交付的 `access_file`。不读取或打印凭据内容。
25
27
 
26
28
  项目与 cwd 可缺省,有值时用于本次获准资料;CLI 路径只定位工具,不把 CLI 仓库当作讨论项目或扩大文件权限。
27
29
 
28
30
  **自动任务(收到 `*.task.json`):**
29
31
 
30
- - 从任务文件取得身份、职责、事件、安排、CLI 和 `access_file`。按任务指令以 `execution enter ROOM EVENT --access-file FILE` 认领一次;**若当前调用刚已成功 enter,直接沿用,不再执行第二次**。若提示词写明接入程序已进入,则跳过 enter。会话 ID 由接入程序提供,不编造、不打印凭据内容。
32
+ - 从任务文件取得身份、职责、事件、安排、CLI 和凭据绑定方式。按任务指令以 `execution enter ROOM EVENT --access-file FILE` 认领一次;**若当前调用刚已成功 enter,直接沿用,不再执行第二次**。若提示词写明接入程序已进入,则跳过 enter。会话 ID 由接入程序提供,不编造、不打印凭据内容。
31
33
  - 进入失败或重复交付时,用 `execution view` 和实际业务结果核对;仅凭事件显示 started,不能认定可以再做一遍。暂停、到期或失效的旧任务不得继续写入。
32
- - 本次受保护写入统一附同一 `--access-file`。只处理交付的职责和安排;专家提交答案即完成此执行,主持人的交接边界见主持技能。
34
+ - 本次受保护写入统一使用上述本次凭据。只处理交付的职责和安排;专家提交答案即完成此执行,主持人的交接边界见主持技能。
33
35
  - 通知程序负责续租与后续交付。AI 不创建定时任务、不操作其他软件,不另发“就绪/完成”回执。确实无法继续时保留草稿,用 `execution fail ROOM EVENT --reason 原因 --access-file FILE` 暂停;已失效时不再写回执或清理消息。
34
36
 
35
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 须用户确认。
@@ -62,7 +64,7 @@ description: "AI CoDiscuss 的通用参与技能,所有参与者(包括主
62
64
 
63
65
  观点用 `message post`。讨论房间的专家观点用 `--kind opinion`,主持发言用 `--kind moderation` 且不带 `--summary`;`chat` 仅用于自由协作房间。观点摘要用 `--summary` / `--summary-file`,完整正文优先 UTF-8 `--body-file`;自己的观点署名自己。代发保留真实作者、完整原文及引用关系,摘要归实际撰写者,不把自己的改写冒充对方回答。
64
66
 
65
- 需要提交原文件时,先 `attachment upload ROOM --as 本人 --file FILE --submission-id 上传编号`,再把回执中的附件 ID 通过 `message post --attachment ID` 关联,可重复指定(最多 10 个,单个 20 MiB)。受保护上传带本次 `--access-file`,须在回答完成、执行资格结束前上传。上传和消息分别保存提交编号;结果不明用 `attachment list ROOM --submission-id 上传编号` 核对,重试沿用原文件与原编号。
67
+ 需要提交原文件时,先 `attachment upload ROOM --as 本人 --file FILE --submission-id 上传编号`,再把回执中的附件 ID 通过 `message post --attachment ID` 关联,可重复指定(最多 10 个,单个 20 MiB)。受保护上传使用上述本次凭据,须在回答完成、执行资格结束前上传。上传和消息分别保存提交编号;结果不明用 `attachment list ROOM --submission-id 上传编号` 核对,重试沿用原文件与原编号。
66
68
 
67
69
  创建房间、安排、消息前保存 `--submission-id` 与原内容;同次重试沿用,新内容换新编号。成功须核对服务端返回的持久记录、作者和关联;返回不含所需证据时按编号补读,避免重复获取同一份已确认内容。答案落库后只读核验即可结束,不补启停回执或使用旧资格继续写入。
68
70
 
package/README.md CHANGED
@@ -115,7 +115,7 @@ codiscuss local connect https://你的站点
115
115
 
116
116
  软件失败时先查看暂停原因,登录和权限问题在对应软件中处理。本机网站的已登记房间,可从进度看板 Codex 旁的“…”菜单直接选择“切换 App / CLI”,在弹窗内暂停并保存;软件路径在“接入 → 修正本场软件连接”修改。原会话与讨论进度保留,待服务确认采用后重启;线上连接暂不提供此修正入口。切到 App 后模型和文件权限按 App 设置,切回 CLI 前须让 App 释放原聊天。本场修改不影响后续讨论的默认配置,详见 [配置修正](docs/runner.md#已启动后的配置修正)。
117
117
 
118
- **手动接入与网页 AI:** 手动接入可用于“自由协作”和“讨论”的专家(讨论的主持人必须由本机唤醒),在成员卡片正面的“自动 / 手动 / 网页”切换里选择。手动成员从成员菜单下载自己的凭据文件,或复制同样内容的凭证文本(设为环境变量 `DISCUSS_ACCESS`),连同参与说明私下交给对应 AI 会话,由人工或软件原生定时触发;讨论里的手动专家只在被安排发言时参与,没有及时回答时讨论会等待,可在进度看板取消安排;网页 AI 仍只用于自由协作,网页 AI 只能阅读房间,回答由你代发;它从自己的服务器读取阅读链接,只在本机运行的网站(`127.0.0.1`)它读不到,需由你粘贴材料。只有手动和网页成员时建房无需本机接入服务。详见 [自由协作房间](docs/workflow.md#自由协作房间)。
118
+ **手动接入与网页 AI:** 手动接入可用于“自由协作”和“讨论”的专家(讨论的主持人必须由本机唤醒),在成员卡片正面的“自动 / 手动 / 网页”切换里选择。手动成员从成员菜单下载自己的凭据文件,或复制同样内容的凭证文本(设为环境变量 `DISCUSS_ACCESS`),连同参与说明私下交给对应 AI 会话,由人工或软件原生定时触发;讨论里的手动专家只在被安排发言时参与,没有及时回答时消息区显示等待发言卡片,可在进度看板点“跳过”本次发言,交给主持人继续安排;网页 AI 仍只用于自由协作,网页 AI 只能阅读房间,回答由你代发;它从自己的服务器读取阅读链接,只在本机运行的网站(`127.0.0.1`)它读不到,需由你粘贴材料。只有手动和网页成员时建房无需本机接入服务。详见 [自由协作房间](docs/workflow.md#自由协作房间)。
119
119
 
120
120
  ## 在线部署
121
121
 
@@ -780,10 +780,25 @@ def discussion_roster(connection, room_id):
780
780
 
781
781
 
782
782
  def save_discussion_experts(connection, room_id, experts):
783
- """Write the active roster; a running discussion keeps going and its host is asked to re-plan."""
783
+ """Write the active roster; additions preserve its plan, other changes ask the host to re-plan."""
784
784
  connection.execute("UPDATE topic_settings SET experts=? WHERE room_id=?", (json.dumps(experts, ensure_ascii=False), room_id))
785
785
 
786
786
 
787
+ def queue_invited_expert(connection, room_id, name, now):
788
+ """Append a new expert to an existing live plan without changing its current speaker or hold."""
789
+ connection.execute("""INSERT INTO discussion_turns (room_id,expert,question,status,submission_id,created_at)
790
+ SELECT settings.room_id,member.name,
791
+ CASE WHEN settings.phase='initial' THEN '欢迎加入:请先读取议题,独立给出你的开场观点。'
792
+ ELSE '欢迎加入:请先读取议题和已有回答,给出你的观点。' END,
793
+ 'queued','invite:'||member.id,?
794
+ FROM topic_settings AS settings JOIN room_members AS member ON member.room_id=settings.room_id
795
+ WHERE settings.room_id=? AND member.name=? AND member.status='active'
796
+ AND settings.phase NOT IN ('paused','concluded')
797
+ AND NOT EXISTS (SELECT 1 FROM discussion_limits WHERE room_id=settings.room_id AND closing_at>0)
798
+ AND EXISTS (SELECT 1 FROM discussion_turns WHERE room_id=settings.room_id AND status IN ('pending','notified','queued'))""",
799
+ (now, room_id, name))
800
+
801
+
787
802
  def add_discussion_member(connection, room_id, token, payload, now):
788
803
  """Owner invites one expert into a discussion using the room's delivery method."""
789
804
  require_owner(connection, room_id, token)
@@ -805,6 +820,7 @@ def add_discussion_member(connection, room_id, token, payload, now):
805
820
  require_fresh_member_token(connection, room_id, member["token"])
806
821
  insert_member(connection, room_id, mark_manual(member, bool(run and run["mode"] == "automatic")), now)
807
822
  save_discussion_experts(connection, room_id, {**experts, member["name"]: "cli"})
823
+ queue_invited_expert(connection, room_id, member["name"], now)
808
824
 
809
825
 
810
826
  def discussion_member_action(connection, room_id, member_id, token, payload):
@@ -839,13 +855,32 @@ def discussion_member_action(connection, room_id, member_id, token, payload):
839
855
  if member["status"] != "away" or not hashed or hmac.compare_digest(hashed, member["token_hash"]):
840
856
  raise ValueError("只有离开的成员可以请回,且须提供新的成员凭证")
841
857
  require_fresh_member_token(connection, room_id, payload.get("token"))
858
+ connection.execute("UPDATE discussion_turns SET status='skipped' WHERE room_id=? AND status='queued'", (room_id,))
842
859
  connection.execute("UPDATE room_members SET status='active',token_hash=? WHERE id=?", (hashed, member_id))
843
860
  experts[member["name"]] = "online" if member["access"] == "online" else "cli"
861
+ elif action == "adopt":
862
+ adopt_manual_member(connection, room_id, member, payload)
863
+ return
844
864
  else:
845
- raise ValueError("action 须为 away、active、remove 或 redo")
865
+ raise ValueError("action 须为 away、active、remove、redo 或 adopt")
846
866
  save_discussion_experts(connection, room_id, experts)
847
867
 
848
868
 
869
+ ADOPTABLE_ADAPTERS = ("claude", "codex", "codex_cli")
870
+
871
+
872
+ def adopt_manual_member(connection, room_id, member, payload):
873
+ """Hand a manual expert to the local service once its CLI reported the session it runs in; the owner's service resumes that session."""
874
+ reported = connection.execute("SELECT session_id FROM member_sessions WHERE member_id=?", (member["id"],)).fetchone()
875
+ run = connection.execute("SELECT mode FROM discussion_runtime WHERE room_id=?", (room_id,)).fetchone()
876
+ if not reported or reported["session_id"] != payload.get("session_id") or payload.get("adapter") not in ADOPTABLE_ADAPTERS:
877
+ raise ValueError("成员尚未上报与之相符的会话,不能转为托管")
878
+ if member["access"] != "manual" or member["status"] != "active" or not run or run["mode"] != "automatic":
879
+ raise ValueError("只有全自动讨论中在场的手动专家可以转为托管")
880
+ connection.execute("UPDATE room_members SET access='runner',adapter=?,session_mode='borrowed' WHERE id=?", (payload["adapter"], member["id"]))
881
+ connection.execute("DELETE FROM member_sessions WHERE member_id=?", (member["id"],))
882
+
883
+
849
884
  def void_latest_opinion(connection, room_id, name, phase):
850
885
  """Delete an expert's latest opinion while paused; its assignment is cancelled and the round no longer counts it."""
851
886
  if phase != "paused":
@@ -394,14 +394,20 @@ BEGIN
394
394
  UPDATE turn_relay SET held_since=(SELECT COALESCE(MAX(id),0)+1 FROM messages WHERE room_id=NEW.room_id)
395
395
  WHERE room_id=NEW.room_id AND held_since=0;
396
396
  END;
397
- -- Concluding or changing participants voids plans that have not started.
397
+ -- Concluding, replacing the host or removing/changing an expert voids plans that have not started.
398
+ -- Adding experts preserves the current plan; the invitation transaction appends their opening turns.
398
399
  CREATE TRIGGER IF NOT EXISTS relay_plan_concluded AFTER UPDATE OF phase ON topic_settings
399
400
  WHEN NEW.phase='concluded'
400
401
  BEGIN
401
402
  UPDATE discussion_turns SET status='skipped' WHERE room_id=NEW.room_id AND status='queued';
402
403
  END;
403
- CREATE TRIGGER IF NOT EXISTS relay_plan_participants AFTER UPDATE OF host,experts ON topic_settings
404
- WHEN NEW.host<>OLD.host OR NEW.experts<>OLD.experts
404
+ DROP TRIGGER IF EXISTS relay_plan_participants;
405
+ CREATE TRIGGER relay_plan_participants AFTER UPDATE OF host,experts ON topic_settings
406
+ WHEN NEW.host<>OLD.host OR EXISTS (
407
+ SELECT 1 FROM json_each(OLD.experts) AS previous
408
+ WHERE NOT EXISTS (SELECT 1 FROM json_each(NEW.experts) AS current
409
+ WHERE current.key=previous.key AND current.value=previous.value)
410
+ )
405
411
  BEGIN
406
412
  UPDATE discussion_turns SET status='skipped' WHERE room_id=NEW.room_id AND status='queued';
407
413
  END;
@@ -533,6 +539,13 @@ CREATE TABLE IF NOT EXISTS room_members (
533
539
  );
534
540
  CREATE UNIQUE INDEX IF NOT EXISTS member_name ON room_members(room_id,name) WHERE status<>'removed';
535
541
  CREATE UNIQUE INDEX IF NOT EXISTS member_credential ON room_members(room_id,token_hash);
542
+ -- A manual member's own software session, reported by its CLI on posting; the local service uses it to take the member over.
543
+ CREATE TABLE IF NOT EXISTS member_sessions (
544
+ member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
545
+ room_id TEXT NOT NULL,
546
+ session_id TEXT NOT NULL,
547
+ client TEXT NOT NULL
548
+ );
536
549
  CREATE TABLE IF NOT EXISTS member_executions (
537
550
  id INTEGER PRIMARY KEY AUTOINCREMENT,
538
551
  room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
package/backend/server.py CHANGED
@@ -44,6 +44,7 @@ LOCAL_LAUNCH_TIMEOUT = 60
44
44
  DIRECTORY_OPERATIONS = ("move", "folder")
45
45
  DIRECTORY_PICKER_TIMEOUT = 600 # the folder dialog waits for the person at the computer
46
46
  MAX_TEXT_LENGTH = 32000
47
+ SESSION_ID_PATTERN = re.compile(r"[0-9A-Za-z_-]{8,128}")
47
48
  MAX_STATE_LENGTH = 4000
48
49
  MAX_BRIEF_LENGTH = 1600
49
50
  MAX_SUMMARY_LENGTH = 1000
@@ -210,7 +211,8 @@ def require_room(connection, room_id):
210
211
  room["limits"] = limits_view(connection.execute(LIMITS_SQL, (room_id,)).fetchone(), room)
211
212
  room["mode"] = room_mode(connection, room_id)
212
213
  room["members"] = [dict(row) for row in connection.execute(
213
- "SELECT id,name,adapter,role,status,access,session_mode FROM room_members WHERE room_id=? AND status<>'removed' ORDER BY id", (room_id,))]
214
+ """SELECT m.id,m.name,m.adapter,m.role,m.status,m.access,m.session_mode,COALESCE(s.session_id,'') AS reported_session,COALESCE(s.client,'') AS reported_client
215
+ FROM room_members AS m LEFT JOIN member_sessions AS s ON s.member_id=m.id WHERE m.room_id=? AND m.status<>'removed' ORDER BY m.id""", (room_id,))]
214
216
  option = connection.execute("SELECT named_followup FROM discussion_options WHERE room_id=?", (room_id,)).fetchone()
215
217
  # Rooms created before the option keep the host's named follow-ups.
216
218
  room["named_followup"] = bool(option[0]) if option else True
@@ -547,7 +549,7 @@ def manage_room(connection, room_id, action, payload):
547
549
 
548
550
  def delete_room_records(connection, room_id):
549
551
  """Remove a room's dependent records in the caller's transaction."""
550
- for table in ("member_recommendations", "member_requests", "member_executions", "room_members", "room_modes", "free_batches", "free_chains", "room_links",
552
+ for table in ("member_recommendations", "member_requests", "member_executions", "member_sessions", "room_members", "room_modes", "free_batches", "free_chains", "room_links",
551
553
  "discussion_options", "scheduler_decisions"):
552
554
  connection.execute(f"DELETE FROM {table} WHERE room_id=?", (room_id,))
553
555
  connection.execute("DELETE FROM message_timings WHERE message_id IN (SELECT id FROM messages WHERE room_id=?)", (room_id,))
@@ -872,6 +874,18 @@ def append_free_message(connection, room, payload, actor):
872
874
  return {"message": get_message(connection, room_id, cursor.lastrowid), "duplicate": False}
873
875
 
874
876
 
877
+ def record_reported_session(connection, room_id, token, payload):
878
+ """Keep the software session a manual member posted from, so the local service can take the member over."""
879
+ session_id, client = payload.get("session_id"), payload.get("session_client")
880
+ if client not in ("claude", "codex") or not isinstance(session_id, str) or not SESSION_ID_PATTERN.fullmatch(session_id) or not token:
881
+ return
882
+ member = connection.execute("SELECT id FROM room_members WHERE room_id=? AND token_hash=? AND status='active' AND access='manual' AND adapter='manual'",
883
+ (room_id, token_hash(token))).fetchone()
884
+ if member:
885
+ connection.execute("""INSERT INTO member_sessions (member_id,room_id,session_id,client) VALUES (?,?,?,?)
886
+ ON CONFLICT(member_id) DO UPDATE SET session_id=excluded.session_id,client=excluded.client""", (member["id"], room_id, session_id, client))
887
+
888
+
875
889
  def append_message(connection, room_id, payload, token="", free_actor=None):
876
890
  """Atomically append an attributed message and complete its scheduled turn."""
877
891
  author, text, submission_id, reply_to = message_values(payload)
@@ -910,6 +924,7 @@ def append_message(connection, room_id, payload, token="", free_actor=None):
910
924
  connection.execute("UPDATE attachments SET message_id=?,position=? WHERE id=?",
911
925
  (cursor.lastrowid, position, identifier))
912
926
  record_message_timing(connection, cursor.lastrowid, token)
927
+ record_reported_session(connection, room_id, token, payload)
913
928
  if details["turn_id"]:
914
929
  connection.execute("UPDATE discussion_turns SET status='answered',answer_id=? WHERE id=?", (cursor.lastrowid, details["turn_id"]))
915
930
  return {"message": get_message(connection, room_id, cursor.lastrowid), "duplicate": False}
@@ -1480,7 +1495,8 @@ class DiscussionHandler(BaseHTTPRequestHandler):
1480
1495
  """Forward authenticated browser requests to the shared Node entrypoint and pin their website."""
1481
1496
  site = self.local_request_site(method)
1482
1497
  route = parts[2:]
1483
- if method == "OPTIONS" and route in (["settings"], ["launch"], ["room-models"], ["room-member"], ["models"], ["api-key"], ["api-check"], ["pick-directory"]):
1498
+ if method == "OPTIONS" and route in (["settings"], ["launch"], ["room-models"], ["room-member"], ["models"], ["api-key"], ["api-check"],
1499
+ ["scheduler-check"], ["pick-directory"]):
1484
1500
  return self.respond({"ok": True})
1485
1501
  if route == ["settings"] and method in ("GET", "POST"):
1486
1502
  command = "settings"
@@ -1496,6 +1512,8 @@ class DiscussionHandler(BaseHTTPRequestHandler):
1496
1512
  command = "api-key"
1497
1513
  elif route == ["api-check"] and method == "POST":
1498
1514
  command = "api-check"
1515
+ elif route == ["scheduler-check"] and method == "POST":
1516
+ command = "scheduler-check"
1499
1517
  elif route == ["room-member"] and method == "POST":
1500
1518
  command = "room-member"
1501
1519
  elif route == ["pick-directory"] and method == "POST":
@@ -1597,6 +1615,7 @@ class DiscussionHandler(BaseHTTPRequestHandler):
1597
1615
  return self.handle_free_api(connection, parts, method)
1598
1616
  if parts[3:4] == ["members"] and method == "POST" and len(parts) in (4, 5):
1599
1617
  if len(parts) == 4:
1618
+ # Membership and its queued opening are committed together; the current plan stays in order.
1600
1619
  add_discussion_member(connection, room_id, token, self.read_payload(), utc_now())
1601
1620
  else:
1602
1621
  discussion_member_action(connection, room_id, int(parts[4]), token, self.read_payload())
@@ -167,7 +167,9 @@ Python 在一个只读事务快照中查询;D1 使用同一个 batch 事务读
167
167
 
168
168
  项目权限选择仅对 CLI 入口开放;API 与网页专家不访问项目文件,界面显示“不访问”。创建卡片、接入设置、经典主题和自由房间邀请沿用同一 CLI 入口判断,API 不继承本场可写配置,执行器强制归一为只读。App 的实际文件权限在原软件中配置。
169
169
 
170
- 正反面占用同一网格单元,避免翻转引起布局跳动。角度由现有 Motion 弹簧驱动:质量 1、频率 2.615 Hz、阻尼比 0.96(刚度 270.032、阻尼 31.551);目标在 0° / 180° 之间切换,中途反向保留速度,无固定时长。隐藏面设为 inert 并退出辅助技术读取,转过 90° 后移交焦点;Escape 返回,减少动态效果时直接到位。滑块采用单位质量、4.5 Hz 临界阻尼弹簧,拖动由 6 Hz 手部弹簧牵引、8 Hz 端点力限制;松手依据真实位置与速度的投影选择档位,点击直接选档,方向键和 Home / End 覆盖全部档位,取消拖动恢复原选项。模型与强度仅在创建前配置,讨论进度中保持只读。
170
+ 邀请弹窗将接入方式放在角色卡片上方,卡片占满可用宽度;手动卡片按内容收缩,并在卡片下说明指令交付与原软件设置。复制启动指令成功后关闭弹窗,失败保留错误与重试入口。
171
+
172
+ 正反面占用同一网格单元,避免翻转引起布局跳动。背面滚动区域保留 2px 内边距,避免下拉控件的外描边被裁切。角度由现有 Motion 弹簧驱动:质量 1、频率 2.615 Hz、阻尼比 0.96(刚度 270.032、阻尼 31.551);目标在 0° / 180° 之间切换,中途反向保留速度,无固定时长。隐藏面设为 inert 并退出辅助技术读取,转过 90° 后移交焦点;Escape 返回,减少动态效果时直接到位。滑块采用单位质量、4.5 Hz 临界阻尼弹簧,拖动由 6 Hz 手部弹簧牵引、8 Hz 端点力限制;松手依据真实位置与速度的投影选择档位,点击直接选档,方向键和 Home / End 覆盖全部档位,取消拖动恢复原选项。模型与强度仅在创建前配置,讨论进度中保持只读。
171
173
 
172
174
  界面只提供默认深色「协作频道」与浅色协作频道。深浅频道共用组件,浅色覆盖同时作用于正文、菜单和弹窗;只有深色频道启用根节点的 `dark` 类。外观设置集中在左下角,窄屏可从房间导航抽屉进入。主题保存在浏览器,已移除或未知主题值回退并保存为深色频道;切换保留房间、筛选与未发送草稿。原独立主题样式和切换入口已删除;HTML 中的隐藏控件仍作为共享控制器的表单适配层,不是可显示的第二套界面。
173
175
 
@@ -175,15 +177,15 @@ Python 在一个只读事务快照中查询;D1 使用同一个 batch 事务读
175
177
 
176
178
  深浅频道采用统一的低饱和蓝灰色系,由 `channel.css` 的背景、表面、强调色与分级阴影变量维护。左右侧栏共用底色,三栏标题与主持简报共用顶部表面色;正文底色、卡片与控件用明度差区分,避免各区域使用不同冷暖的灰色。深色卡片保留顶部亮边、底部暗边和局部渐变;浅色使用白色卡片、蓝灰细描边与柔和投影。交互状态共用蓝色强调色,参与者和运行状态保留各自的语义颜色。
177
179
 
178
- 左右侧栏仅用朝向正文的柔和阴影分界,避免竖向描边与内侧高光线重复叠加。分栏拖柄覆盖接缝,层级高于主持简报、低于浮动预览,不占用布局宽度,仅在悬停、聚焦或拖动时显色。新建入口与导航选中项具有独立边界,右栏状态为轻浮起面板,焦点、参与者及耗时以留白和分隔线分组。消息、共识与主持面板使用较轻阴影,按钮使用短投影,菜单和抽屉使用浮层投影,搜索和表单使用内阴影;按下和禁用时降低或移除投影。输入区聚焦保留面板阴影并叠加焦点边界。材质调整保留三栏结构和阅读宽度。
180
+ 左右侧栏仅用朝向正文的柔和阴影分界,避免竖向描边与内侧高光线重复叠加。分栏拖柄覆盖接缝,层级高于主持简报、低于浮动预览,不占用布局宽度,仅在悬停、聚焦或拖动时显色。新建入口与导航选中项具有独立边界,右栏状态为轻浮起面板,焦点、参与者及耗时以留白和分隔线分组。消息、共识与主持面板使用较轻阴影,按钮使用短投影,菜单和抽屉使用浮层投影,搜索和表单使用内阴影;按下和禁用时降低或移除投影。输入区聚焦保留面板阴影并叠加焦点边界;发送按钮始终用于发言,无正文或附件时禁用,不随 AI 运行状态切换为停止按钮。讨论的暂停、打断与结束保留在进度看板,自由协作的停止保留在成员菜单。材质调整保留三栏结构和阅读宽度。
179
181
 
180
182
  浮层(菜单、预览、消息工具条、抽屉与弹窗)在深色频道使用毛玻璃:半透明上亮下暗渐变底配合 24px 背景模糊与增艳,边缘只用顶部内侧亮线、底部内侧暗线和向下投影,不加外侧白色辉光。浅色频道的控件本体保持不透明白色,只学习材质边界:蓝灰细描边、内侧顶部高光、内侧底部浅蓝灰暗边和蓝灰多层投影,按控件、面板、浮层三级加强。外观设置的「视觉效果」可切换为精简,写入根节点 `data-effects="reduced"` 并随布局偏好保存在浏览器;精简模式全局关闭背景模糊,深色浮层改为实色,深浅阴影各退为单层,用于保证低配设备的流畅度。浅色频道的发言快速导航不加底板,刻度改为发言者实色、固定 2px 粗细(覆盖脚本动画的内联高度,长度与透明度仍由脚本驱动),轮次号与轮次分隔比辅助文字深一级。
181
183
 
182
- 选中状态用填色、文字和勾选表达,键盘焦点使用中性边缘,不增加蓝色外框;卡片、模型选择、强度滑杆、模式、时长及编辑区遵循同一规则。
184
+ 选中状态用填色、文字和勾选表达,键盘焦点使用中性边缘,不增加蓝色外框;卡片、模型选择、强度滑杆、模式、时长及编辑区遵循同一规则。加载图标在各处统一为每 3 秒匀速旋转一圈;系统开启“减少动态效果”时保留静态图标,不再旋转。
183
185
 
184
186
  接入页分别交付本地成员的私有配置文件和不含 token 的参与说明,在线成员只给阅读说明;前端快照仅含成员接入方式与文件可用状态。手动创建、邀请、请回直接请求网站,自动成员才经本机启动器。创建与成员变更在请求前保存原编号和凭据,结果不明时沿用;复制说明不代表已送达或接入。项目、工作目录、讨论上限和错误按需展开。暂停后的本场软件连接修正使用独立卡片,页面不推断本机服务在线状态,选择模式或下载配置不会启动讨论。
185
187
 
186
- 消息区显示议题、原文和主持简报;议题卡的参与配置使用与头像一致的 20px 行高,姓名与主持 / CLI 标签保留字号层级并垂直居中。全自动执行期间,消息流末尾按有效执行显示等待状态:专家复用发言卡片的头像、姓名、轮次与样式,显示“正在唤醒…”或“正在准备回复…”;主持执行使用同宽、更矮、弱化底色和边框的“主持工作”卡,显示头像、姓名及右上角耗时,按交付与进入状态显示“正在唤醒主持人…”或“正在安排下一步…”,不推测内部操作。卡片类型按本次执行职责区分,主持人作为专家回答时仍使用专家等待卡;正式主持发言保留普通消息卡。安排待处理、手动接入、执行过期、暂停或归档不显示等待卡片。卡片不计入消息记录,没有消息编号、回复或引用操作;执行结束后移除,下一位专家实际交付后才显示其等待卡。搜索与提及筛选时隐藏,状态更新仅在用户原本位于底部时跟随滚动。标题栏、窄屏标签栏与主持简报合为同一块顶部面板,简报展开内容留在面板内,与收起状态共用背景、边线与投影,不再另起浮层。消息视图中该面板浮在消息列表之上,使用与深色浮层同款的毛玻璃(半透明渐变底、24px 背景模糊与增艳;浅色为白色半透明),滚动经过的卡片在其下模糊可见;消息列表顶部留白、导航与筛选条下移量、跳转与当前消息判定都按收起时的面板高度计算,展开简报只覆盖消息,不推动消息与输入区。其他视图中面板按普通流式布局排列。精简视觉效果下面板改为实色。已存档房间在独立弹窗管理,不改变当前房间和草稿。存档房间仍可阅读、恢复与导出;批量删除作用于当前搜索和筛选结果,执行前确认。
188
+ 消息区显示议题、原文和主持简报;议题卡的参与配置使用与头像一致的 20px 行高,姓名与主持 / CLI 标签保留字号层级并垂直居中。全自动执行期间,消息流末尾按有效执行显示等待状态:专家复用发言卡片的头像、姓名、轮次与样式,显示“正在唤醒…”或“正在准备回复…”;主持执行使用同宽、更矮、弱化底色和边框的“主持工作”卡,显示头像、姓名及右上角耗时,按交付与进入状态显示“正在唤醒主持人…”或“正在安排下一步…”,不推测内部操作。卡片类型按本次执行职责区分,主持人作为专家回答时仍使用专家等待卡;正式主持发言保留普通消息卡。手动接入专家轮到发言时,即使没有自动执行,也按当前安排显示“等待发言”卡片,提示到该 AI 会话发送启动指令;标注“手动接入”,不显示执行动画或耗时。输入区的“复制启动指令”使用无底色、无描边的复制图标文字按钮,复制成功后原位显示“已复制”。自动成员仅有待处理安排、执行过期、暂停或归档时不显示等待卡片。卡片不计入消息记录,没有消息编号、回复或引用操作;执行结束、手动安排已回答或取消后移除;下一位自动专家实际交付、手动专家轮到发言时显示对应卡片。右侧进度看板的“跳过”取消当前专家本次安排及有效执行,保留排队安排并交给主持人继续安排,不暂停讨论。搜索与提及筛选时隐藏,状态更新仅在用户原本位于底部时跟随滚动。标题栏、窄屏标签栏与主持简报合为同一块顶部面板,简报展开内容留在面板内,与收起状态共用背景、边线与投影,不再另起浮层。消息视图中该面板浮在消息列表之上,使用与深色浮层同款的毛玻璃(半透明渐变底、24px 背景模糊与增艳;浅色为白色半透明),滚动经过的卡片在其下模糊可见;消息列表顶部留白、导航与筛选条下移量、跳转与当前消息判定都按收起时的面板高度计算,展开简报只覆盖消息,不推动消息与输入区。其他视图中面板按普通流式布局排列。精简视觉效果下面板改为实色。已存档房间在独立弹窗管理,不改变当前房间和草稿。存档房间仍可阅读、恢复与导出;批量删除作用于当前搜索和筛选结果,执行前确认。
187
189
 
188
190
  所有发言卡片右上角显示耗时。等待卡片使用同一执行的 `runtime.timings` 唤醒与执行秒数之和,从交付唤醒起累计,不计之前排队;本地每秒更新,服务器计时变化时校准,状态结束或切换房间后停止。正式发言的 `elapsed_seconds` 在保存消息的同一事务内按有效执行凭据与原作者关联计算,保存在 `message_timings`,此后翻页、重试和运行结束均不改变。主持发言同样计到各自保存时刻;不接受客户端提供的耗时。用户留言、手动或代发内容及缺少记录的旧消息显示“耗时 —”。升级仅为有精确 `message:ID` 完成结果与计时记录的历史专家发言回填,不按时间邻近推测关联,不覆盖已有耗时。
189
191
 
@@ -246,7 +248,7 @@ running 状态即使只有 pending 事件,也必须先暂停才能重新启动
246
248
  自由房间(`room_modes.mode='free'`)与讨论房间共用房间、成员登记、消息、附件与目录,没有发言权、安排、阶段推进和轮次。没有控制凭据登记的旧房间只供阅读,不支持通过首次运行配置认领;本次不迁移历史房间。
247
249
 
248
250
  - **凭据**:建房时由客户端提供房间控制凭据和每位成员的成员凭证,服务端只存哈希。用户身份的房间写入(发言、附件、存档、恢复、删除、打断)都须控制凭据;房间排序和归入文件夹只改共享目录,与站点级文件夹操作一样不需要凭据;无凭据、错误凭据、成员凭证或他房凭据一律只读。成员凭证只能以该成员身份发言或上传附件。声明字段(`author`、`source`、localhost)不代替凭据。
249
- - **成员**:`room_members` 以 `id` 为稳定主键,`access` 为 `runner`(接入程序投递)、`manual`(成员自行读写)或 `online`(只读、原文代发),`session_mode` 标记是否借用用户正在使用的会话。房主用控制凭据 `POST members` 运行中加入成员,`POST members/ID {action}` 让成员离开(`away`,作废其进行中执行与未投递请求)、请回(`active`,必须提供不同于原凭证的新凭证,旧凭证副本不再有效)或移除(`remove`,保留历史发言)。同一成员凭证不能复用于其他成员或房主;相同凭证与配置的邀请、请回重试返回已有结果。会话续接由本机接入程序负责,服务端不保存会话绑定。
251
+ - **成员**:`room_members` 以 `id` 为稳定主键,`access` 为 `runner`(接入程序投递)、`manual`(成员自行读写)或 `online`(只读、原文代发),`session_mode` 标记是否借用用户正在使用的会话。房主用控制凭据 `POST members` 运行中加入成员,`POST members/ID {action}` 让成员离开(`away`,作废其进行中执行与未投递请求)、请回(`active`,必须提供不同于原凭证的新凭证,旧凭证副本不再有效)或移除(`remove`,保留历史发言)。同一成员凭证不能复用于其他成员或房主;相同凭证与配置的邀请、请回重试返回已有结果。会话续接由本机接入程序负责,服务端不保存会话绑定;唯一例外是手动成员上报的会话:`message post` 可带 `session_id` 与 `session_client`(CLI 读取 `CLAUDE_CODE_SESSION_ID` 或 `CODEX_THREAD_ID`,取值 `claude` / `codex`),服务端在 `member_sessions` 保存该手动成员最近上报的会话,成员视图以 `reported_session` 暴露。房主用 `POST members/ID {action:"adopt", adapter:"claude", session_id}`(须与上报一致、仅全自动讨论中在场的手动成员)把成员改为 `runner`、`borrowed` 并清除上报,本机接入程序据此接管。两套后端行为一致。
250
252
  - **请求**:用户消息按结构化接收者 `to`(成员名)为每位成员生成一条请求;省略且仅有一位在场成员时默认点名该成员,多位成员时交给主持人(`route`),只有在线成员而没有主持人时只作留言,不广播。网页只从正文中代码与引用之外的 `@成员` 生成 `to`,没有 @ 时省略 `to`。请求状态为 `pending`(待投递)→ `delivering`(处理中)→ `completed` / `cancelled` / `failed`。
251
253
  - **执行**:`member_executions` 每位成员最多一个 `dispatched/started`(部分唯一索引),不同成员可并行。接入程序用控制凭据 `GET inbox`、`POST executions {member_id, client_id}`(同一 `client_id` 返回同一执行)把成员的全部待投递请求打包;`POST executions/ID {action}` 支持 `renew`(租约 120 秒,不超过单次执行时限,默认 20 分钟)、`fail`(请求标记失败)和 `release`(仅确认未送达时退回队列)。租约过期的执行在下次读取或领取时标记失败,不自动重发。
252
254
  - **主持人与任务链**:用户是需求方,不担任主持人。只要有在场的非在线成员,房间就保持恰好一位主持人:成员变动时,离开或移出的主持人失去角色,由第一位在场的 runner 成员(没有则手动成员)接任;建房和每条房主消息前也做同样的补位,因此旧房间在下一条消息时自动获得主持人。房主用 `POST members/ID {action:"role", role:"host"}` 更换主持人(在线成员不能担任),把现任主持人改为 `member` 会被拒绝。多位成员时未点名的房主消息生成给主持人的 `route` 请求。主持人的回答可带 `to` 点名其他成员(不能点名自己),这些请求组成一个批次(`free_batches`);批次内全部进入终态后,只要有一个完成就给主持人生成一次 `handoff` 请求,全部失败则停下该任务链。每条房主消息开启一条任务链(`chain_id` 为该消息编号),链上 AI 发起的请求(派活与回交)最多 8 次,超出即停下(`free_chains`,原因 `budget` / `all_failed`),房主发新消息开启新链。Worker 用同一批次内的集合 SQL 完成回交,回交请求按(派活消息,主持人)唯一,重复执行不会重复唤醒。
@@ -258,7 +260,7 @@ running 状态即使只有 pending 事件,也必须先暂停才能重新启动
258
260
  - **改动记录**:接入程序在执行前后对成员 cwd 做 Git 工作区快照(状态与内容哈希),执行结束且进程退出后用 `report {changes}` 记录一次。房间视图的 `free.conflicts` 列出不同成员在同一 cwd、时段重叠的执行均观察到变化的文件,只作“可能冲突”提示;非 Git 目录不记录。快照也会包含其他进程在同一目录的写入,不能确定修改者;分工修改不同文件仍可能出现提示,不代表已发生覆盖。
259
261
  - **回答**:接入程序投递的成员回答须带 `execution_id` 并使用成员凭证;消息保存、执行完成、请求完成和成员已读游标在同一事务中写入。执行结束、被打断或过期后的回答返回 409;缺少执行编号返回 400,不能改作普通发言。消息类型为 `chat`,不计入讨论轮次。
260
262
  - **打断**:`POST members/ID/interrupt`(控制凭据)作废该成员的进行中执行及所有未投递请求,不会自动重新开始;成员的已读游标不前移,下一次请求从同一位置读起。执行失败只影响该成员的请求,房间不暂停。
261
- - **讨论成员变更**:房主修改讨论名单或读写能力时,为新增或改变能力的成员提供新凭证,未变成员保留原凭证;删去的成员立即停用。成员主持不能自行扩大名单或更改主持职责。运行中名单只能经 `POST members`(邀请,接入方式须与推进方式一致)与 `POST members/ID {action: away|active|remove}` 变更,“编辑议题”改名单返回 400。暂离从 `experts` 中移出、成员行保持 `away`,同事务取消它进行中与排队的安排;请回须新凭证;主持人不能暂离或移除;有进行中安排的专家仅在话题暂停时可暂离或移除。暂停时 `{action: redo}` 删除该专家最近一条观点(含摘要、耗时、附件,被其回答的安排改为 `cancelled`)。`experts` 变化不再暂停运行:触发器 `runtime_experts_changed` 在无进行中安排和主持执行时为主持人生成 `members:` 事件,排队安排照常作废;只有更换主持人才暂停并换代。本机接入程序接受只新增参与者的配置变化,已绑定会话的条目仍须保持不变。
263
+ - **讨论成员变更**:房主修改讨论名单或读写能力时,为新增或改变能力的成员提供新凭证,未变成员保留原凭证;删去的成员立即停用。成员主持不能自行扩大名单或更改主持职责。运行中名单只能经 `POST members`(邀请,接入方式须与推进方式一致)与 `POST members/ID {action: away|active|remove}` 变更,“编辑议题”改名单返回 400。暂离从 `experts` 中移出、成员行保持 `away`,同事务取消它进行中与排队的安排;请回须新凭证;主持人不能暂离或移除;有进行中安排的专家仅在话题暂停时可暂离或移除。暂停时 `{action: redo}` 删除该专家最近一条观点(含摘要、耗时、附件,被其回答的安排改为 `cancelled`)。`experts` 变化不再暂停运行:触发器 `runtime_experts_changed` 在无进行中安排和主持执行时为主持人生成 `members:` 事件,纯新增专家保留排队安排;邀请事务通过 `queue_invited_expert` 将新专家的开场安排追加为 `queued`,到它轮次才产生执行。无现有安排、暂停或收束期间不自动创建安排,由主持人恢复推进时排在本轮末尾。暂离、移除、请回仍作废排队并重排;只有更换主持人才暂停并换代。本机接入程序接受只新增参与者的配置变化,已绑定会话的条目仍须保持不变。
262
264
  - **范围**:成员名称是 AI 实例名(见下文“参与者身份”),一个房间最多 12 位 AI。早期创建的自由房间表在 Python 启动时补齐新列。Cloudflare Worker 实现同一协议:成员与执行状态的校验放在同一 D1 批次的断言中,校验后状态变化则整批回滚。备份只导出成员名称、状态与请求,不导出凭据哈希。
263
265
 
264
266
  ## 参与者身份
package/docs/cli.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  `codiscuss launch room-models --file FILE` 输入 `site`、`room_id`,只读返回本机已登记房间中各参与者的 `adapter/model/effort`;未登记时返回空参与者对象。命令不启动 AI、不修改配置,不用本机默认模型填补缺失值。
14
14
 
15
- `codiscuss launch models --file FILE` 输入 `adapter` 和可选的完整 `executable`,读取对应 CLI 的模型目录与支持的档位,不发送提示词。详情与失败边界见 [本机模型目录](runner.md#本机模型目录)。
15
+ `codiscuss launch models --file FILE` 输入 `adapter` 和可选的完整 `executable`(agy 可附 `model` 查询该模型的原生思考档位),读取对应 CLI 的模型目录与支持的档位,不发送提示词。详情与失败边界见 [本机模型目录](runner.md#本机模型目录)。
16
16
 
17
17
  API 入口使用 `adapter: "api"`,其 Base URL、请求协议、Key 引用和模型 ID 字段见 [API 接入](runner.md#api-接入)。`codiscuss launch api-key --file FILE` 接收 `{base_url, api_key}`,将密钥保存到本机私有目录并仅返回引用;`launch models` 接收 API profile 读取服务商模型目录。API 发言的 `source` 为 `api`,普通网站 CLI 的 `message post` 仍标记为 `cli`;两者使用相同的成员或执行授权。
18
18
 
@@ -35,7 +35,9 @@ Bash 对应 `export DISCUSS_URL=http://127.0.0.1:8765`,CMD 对应 `set DISCUSS
35
35
 
36
36
  建房与更新资料支持 `--user-request` / `--user-request-file`,将用户原文与 `--background` 分开保存,各限 4000 字。`room brief` 返回两者,概要和通知不自动展开原文。
37
37
 
38
- `room create` 必须指定 `--access-file 私有文件`,CLI 在请求前原子保存建房编号、房主和各成员凭据,确认房间后另写各本地成员的独立文件,回执只返回 `member_access_files` 路径。结果不明时沿用同一文件、编号和创建参数;不能换文件重建。后续每次写入使用对应身份的文件,不能拿房主文件冒充成员;自动讨论使用本次任务文件中的执行凭据。文件含 `site`、`room`、`token`,CLI 检查站点与房间匹配。同样内容的单行 JSON 文本可放入环境变量 `DISCUSS_ACCESS`(或 `--access-file -` 从标准输入读取),未传 `--access-file` 时使用;成员菜单的“复制凭证文本”复制的就是这段文本,含 token,只交给该成员,不要写进命令行参数或回显。`--dry-run` 不生成或写入凭据,预览会隐藏凭据字段。
38
+ `room create` 必须指定 `--access-file 私有文件`,CLI 在请求前原子保存建房编号、房主和各成员凭据,确认房间后另写各本地成员的独立文件,回执只返回 `member_access_files` 路径。结果不明时沿用同一文件、编号和创建参数;不能换文件重建。后续每次写入使用对应身份的文件,不能拿房主文件冒充成员;自动讨论使用本次任务文件中的执行凭据。文件含 `site`、`room`、`token`,CLI 检查站点与房间匹配。同样内容的单行 JSON 文本可放入环境变量 `DISCUSS_ACCESS`(或 `--access-file -` 从标准输入读取),未传 `--access-file` 时使用;成员菜单的“复制凭证文本”复制的就是这段文本,含 token,只交给该成员,不要写进命令行参数或回显。`--dry-run` 不生成或写入凭据,预览会隐藏凭据字段。`message post` 在 Claude Code 会话中运行时自动附带 `session_id` 与 `session_client: "claude"`(取自 `CLAUDE_CODE_SESSION_ID`;Codex 为 `session_client: "codex"`,取自 `CODEX_THREAD_ID`),服务端仅对手动成员保存,供本机服务转为托管;其他情况忽略。
39
+
40
+ 接入程序新启动的 CLI 使用 `DISCUSS_ACCESS_FILE` 绑定本次凭据文件,日常读写可省略 `--access-file`;文件每次重新读取,保留会话绑定与房间、站点检查。优先级为显式 `--access-file` → `DISCUSS_ACCESS_FILE` 文件 → `DISCUSS_ACCESS` JSON;已指定文件损坏或缺失时直接报错,不回退其他身份。首次接入已有会话和 App 仍使用交付的文件路径。建房仍须显式指定新凭据的保存路径。
39
41
 
40
42
  消息 JSON 中的 `elapsed_seconds` 为只读耗时:自动执行从交付唤醒到保存该消息的秒数,不含排队,保存后固定。用户留言、手动或代发内容及没有计时记录的旧消息返回 `null`;发布命令无需提供耗时。
41
43
 
@@ -51,7 +53,7 @@ codiscuss message post ROOM --as 用户 --body "请参考附件" --attachment AT
51
53
 
52
54
  上传回执中的 `attachment.id` 是后续引用的编号;上传和消息分别使用自己的提交编号。单个文件最多 20 MiB,消息最多重复指定 10 次 `--attachment`;仅附件消息可省略正文。普通 `attachment list ROOM` 只列已发送附件,可加 `--message ID`、`--page N`;精确提交查询用于核对未关联的上传。下载路径必须明确、父目录已存在且目标文件不存在;客户端验证长度和 SHA-256,不覆盖已有文件。
53
55
 
54
- 所有写入带 `--access-file`。自动任务先上传,再提交关联回答;回答完成后执行凭据不能继续上传。AI 发言仍需有效安排、发言权和摘要;下载后由参与软件自身工具读取,不默认把全部附件加入通知。`--dry-run` 预览路径与元数据,不上传字节或创建下载文件。结果不明先查原上传编号;重试沿用原文件、身份和编号。
56
+ 所有写入须有对应凭据;使用 `--access-file` 或接入程序绑定的 `DISCUSS_ACCESS_FILE`。自动任务先上传,再提交关联回答;回答完成后执行凭据不能继续上传。AI 发言仍需有效安排、发言权和摘要;下载后由参与软件自身工具读取,不默认把全部附件加入通知。`--dry-run` 预览路径与元数据,不上传字节或创建下载文件。结果不明先查原上传编号;重试沿用原文件、身份和编号。
55
57
 
56
58
  ## 讨论上限参数
57
59
 
package/docs/runner.md CHANGED
@@ -37,7 +37,7 @@ API 参与者不安装技能,也不靠手写的另一套规则:每次执行
37
37
 
38
38
  ## npm 安装与本机配置
39
39
 
40
- 网站附件由 `codiscuss attachment` 命令按需读写,详见 [CLI 附件](cli.md#附件)。接入程序的事件通知不携带文件字节,也不替实际 AI 解析文件;自动参与者使用本次 `--access-file` 在提交回答前完成上传和关联。本地数据库迁移、备份时同时保留其 `.attachments` 目录。
40
+ 网站附件由 `codiscuss attachment` 命令按需读写,详见 [CLI 附件](cli.md#附件)。接入程序的事件通知不携带文件字节,也不替实际 AI 解析文件;自动参与者使用本次凭据(进程绑定或显式 `--access-file`)在提交回答前完成上传和关联。本地数据库迁移、备份时同时保留其 `.attachments` 目录。
41
41
 
42
42
  需要 Node.js 24+、npm 与 Python 3.10+。包内带已构建网页、Python 后端、接入程序及三份完整技能;运行时不安装前端依赖。从 npm registry 安装:
43
43
 
@@ -102,7 +102,7 @@ codiscuss local stop
102
102
  python server.py --host 127.0.0.1 --local-launch
103
103
  ```
104
104
 
105
- 需要 Node.js 24+ 与已安装、已登录的 AI 客户端;讨论所需的最小工具权限按 [单次执行权限](#单次执行权限) 提供。新建时分别选择房间用法与“程序唤醒”,从卡片下方的“添加”行加入本地参与实例,点卡片上的皇冠指定主持人;自由协作默认由第一位能自行发言的成员主持(用户是需求方),头脑风暴须选择本地 AI 主持。参与区只显示已加入的实例,用独立“移出”按钮调整名单。自动 CLI 卡片翻到背面后通过真实软件目录下拉选择模型,并按该模型支持的档位显示强度滑杆;首档使用 CLI 默认,agy 的强度包含在模型 ID 中。恢复默认只清除该实例的模型与强度覆盖,App、网页和手动会话在原软件设置。配置入口不依赖勾选立即启动,移出后重新加入保留本次填写。接入方式默认“程序唤醒”,“创建后立即启动 AI”默认勾选;本机服务未连接时只建房。项目文件权限在各自动 CLI 卡片上按专家设置(默认只读,在线专家固定只读),卡片背面“取消”或 Esc 恢复翻面前的设置,“完成”保留修改;卡片只在缺少软件路径、Sidecar IPC 目录或 Codex App 来源聊天时显示对应输入。项目目录可直接输入,也可点输入框右侧的文件夹图标经 `POST /api/local/pick-directory` 在运行本机服务的电脑上弹出系统目录选择窗口(Windows 用 PowerShell WinForms,macOS 用 osascript,Linux 用 zenity),取消时不返回路径,窗口最长等待 10 分钟。再点击“创建并启动讨论”;不勾选则只建房,之后在“接入”页启动。自由协作的“创建并连接”登记自动成员,后续点名产生任务时才唤醒。复用会话、单个 AI 的工作目录与项目等高级覆盖,以及“保存为本机软件默认配置”,在建房后的“接入”页设置,保存本身不启动任何 AI。卡片输入沿用当前页面的启动草稿,不随只建房请求保存到网站。正常首次自动接入无需下载 JSON、手动登记房间或另执行 `run start`。
105
+ 需要 Node.js 24+ 与已安装、已登录的 AI 客户端;讨论所需的最小工具权限按 [单次执行权限](#单次执行权限) 提供。新建时分别选择房间用法与“程序唤醒”,从卡片下方的“添加”行加入本地参与实例,点卡片上的皇冠指定主持人;自由协作默认由第一位能自行发言的成员主持(用户是需求方),头脑风暴须选择本地 AI 主持。参与区只显示已加入的实例,用独立“移出”按钮调整名单。自动 CLI 卡片翻到背面后通过真实软件目录下拉选择模型,并按该模型支持的档位显示强度滑杆;首档使用 CLI 默认,agy 的思考档位通过原生只读命令查询。恢复默认只清除该实例的模型与强度覆盖,App、网页和手动会话在原软件设置。配置入口不依赖勾选立即启动,移出后重新加入保留本次填写。接入方式默认“程序唤醒”,“创建后立即启动 AI”默认勾选;本机服务未连接时只建房。项目文件权限在各自动 CLI 卡片上按专家设置(默认只读,在线专家固定只读),卡片背面“取消”或 Esc 恢复翻面前的设置,“完成”保留修改;卡片只在缺少软件路径、Sidecar IPC 目录或 Codex App 来源聊天时显示对应输入。项目目录可直接输入,也可点输入框右侧的文件夹图标经 `POST /api/local/pick-directory` 在运行本机服务的电脑上弹出系统目录选择窗口(Windows 用 PowerShell WinForms,macOS 用 osascript,Linux 用 zenity),取消时不返回路径,窗口最长等待 10 分钟。再点击“创建并启动讨论”;不勾选则只建房,之后在“接入”页启动。自由协作的“创建并连接”登记自动成员,后续点名产生任务时才唤醒。复用会话、单个 AI 的工作目录与项目等高级覆盖,以及“保存为本机软件默认配置”,在建房后的“接入”页设置,保存本身不启动任何 AI。卡片输入沿用当前页面的启动草稿,不随只建房请求保存到网站。正常首次自动接入无需下载 JSON、手动登记房间或另执行 `run start`。
106
106
 
107
107
  网页在发送前把完整已提交输入、原提交号和控制凭据保存在当前站点的浏览器本地存储。服务启动失败或响应丢失后,刷新仍会显示“继续未完成的启动”;点击“继续本次启动”沿用原请求和本机已固定配置,无需重新填写,也不随新的默认配置变化。刷新只恢复界面,不自动运行;已暂停、结束、过期或在启动期间改变的房间不会因此重新开始。部分失败明确显示已核实的阶段,已建房不等于回滚;结果不明时保留原请求待核对。
108
108
 
@@ -114,7 +114,7 @@ python server.py --host 127.0.0.1 --local-launch
114
114
 
115
115
  - Codex:临时运行 `app-server`,依次 `initialize`、`initialized`、分页 `model/list`,不创建 thread 或 turn。见 [官方协议](https://learn.chatgpt.com/docs/app-server#list-models-modellist)。
116
116
  - Claude:临时以 `--safe-mode --no-session-persistence` 开启流式控制通道,仅发送 initialize,读取软件返回的 models 和 supportedEffortLevels;禁用自定义启动扩展,不发送用户消息。协议依据 [官方客户端初始化](https://github.com/anthropics/claude-agent-sdk-python/blob/main/src/claude_agent_sdk/_internal/query.py)。旧版本不支持时显示查询失败。
117
- - Antigravity:执行只读 `agy models`,解析软件返回的模型 ID 与显示名;档位随模型。见 [官方命令说明](https://antigravity.google/docs/cli/headless/#select-a-model-effort-or-agent)。
117
+ - Antigravity:执行只读 `agy models`,解析软件返回的模型 ID 与显示名;另用 `agy -p /effort --output-format json` 查询默认模型的原生档位,选择模型后附 `--model ID` 查询该模型。该命令不创建模型轮次、不修改软件设置。`models` 入口接受可选 `model`,在原模型目录之外返回顶层 `efforts` 或 `effort_error`;档位按模型单独缓存,切换模型复用完整目录,查询失败不猜测可用档位。见 [官方档位说明](https://antigravity.google/docs/cli/commands/effort)。
118
118
 
119
119
  查询仅使用 Node 内置模块,进程最长 30 秒、响应最多 1 MiB,结束或失败均关闭查询进程。原始软件诊断和账号字段不返回网页;本机入口沿用 Origin 与站点连接鉴权,不需要房间凭据。目录可能含客户端预置项目,不证明账号额度或实际推理可用。本次以本机三个 CLI 完成只读列表核对,没有发送模型提示词或启动真实讨论。
120
120
 
@@ -257,9 +257,11 @@ Windows 的 `executable` 填原生 `.exe`,不填 npm 的 `.cmd` / `.ps1` 包
257
257
  | `claude` | 现有 `claude -p --output-format json`;后续 `--resume ID` | 沿用上表中的 Claude 规则 |
258
258
  | `antigravity_cli` | `agy -p --output-format stream-json`,从 `init.conversation_id` 读取实际 ID;后续 `--conversation ID`,不使用 `--continue` | 保持指定 cwd;省略 cwd 时使用本场独立草稿目录。只在讨论服务新建的会话中执行:每位参与者一个独立原生讨论项目,续接复用该项目;不绑定外部已有会话 |
259
259
 
260
- 三种 CLI 适配器可选 `model`:分别作为 `codex -m`、`claude --model`、`agy --model` 的参数传入,留空使用各 CLI 的默认模型;可存入本机默认配置,也可在新建讨论时按实例从软件目录中选择。配置文件的模型名不能以 `-` 开头或含空格。可选 `effort` 设置思考档位:Codex 接受 low / medium / high / xhigh / max / ultra,以 `-c model_reasoning_effort="档位"` 传入;Claude 接受 low / medium / high / xhigh / max,以 `--effort` 传入;网页按所选模型实际支持的档位收窄。agy 的档位写在模型名中(如 `gemini-4-argon-high`),不单独设置。App 适配器使用 App 内选择的模型与档位。
260
+ 三种 CLI 适配器可选 `model`:分别作为 `codex -m`、`claude --model`、`agy --model` 的参数传入,留空使用各 CLI 的默认模型;可存入本机默认配置,也可在新建讨论时按实例从软件目录中选择。配置文件的模型名不能以 `-` 开头或含空格。可选 `effort` 设置思考档位:Codex 接受 low / medium / high / xhigh / max / ultra,以 `-c model_reasoning_effort="档位"` 传入;Claude 和 agy 接受 low / medium / high / xhigh / max,以 `--effort` 传入;网页按原生查询结果收窄到该模型实际支持的档位。agy 保留带档位模型名(如 `gemini-4-argon-high`),显式 `effort` 交由 CLI 处理覆盖;留空不传参数,不自动降低强度。App 适配器使用 App 内选择的模型与档位。
261
261
 
262
- 进度看板的参与者姓名下显示本场已登记的模型与思考强度,不使用新建草稿或本机默认配置代替。由本机桥接的只读 `room-models` 返回 `adapter/model/effort`,按当前站点与房间匹配;本机页面和已授权连接的线上页面均可读取,不要求暂停或浏览器持有房间控制凭据,不返回路径、令牌或会话编号。CLI 未显式指定时显示“默认模型 / 默认强度”,agy 显示“强度随模型”,App 和手动参与提示在原软件内设置;未连接本机、未登记或旧版服务不支持时显示未获取。它展示启动配置,不能证明软件最终采用的默认值,也不提供讨论中改模型的入口。
262
+ 本机服务新启动的三种 CLI 子进程通过 `DISCUSS_ACCESS_FILE` 绑定本次私有凭据文件,任务不再要求 AI 在每条命令里复写完整路径。CLI 每次读文件,能取得随后绑定的真实会话 ID;不把 token 放入提示词、参数或环境变量。绑定仅作用于该子进程,不设置全局房间或身份。App 和首次交还已有会话的任务继续传实际文件路径;显式 `--access-file` 仍优先。软件的 cwd、权限、工具、推理和调用顺序不因该绑定改变。
263
+
264
+ 进度看板的参与者姓名下显示本场已登记的模型与思考强度,不使用新建草稿或本机默认配置代替。由本机桥接的只读 `room-models` 返回 `adapter/model/effort`,按当前站点与房间匹配;本机页面和已授权连接的线上页面均可读取,不要求暂停或浏览器持有房间控制凭据,不返回路径、令牌或会话编号。CLI 未显式指定时显示“默认模型 / 默认强度”,agy 未覆盖时显示“模型默认强度”,App 和手动参与提示在原软件内设置;未连接本机、未登记或旧版服务不支持时显示未获取。它展示启动配置,不能证明软件最终采用的默认值,也不提供讨论中改模型的入口。
263
265
 
264
266
  两种新适配器都不需要 `source_thread_id` 或 `ipc_directory`。实际 ID 在输出流到达时写入本机绑定和本次执行凭据,随后由接入程序在会话启动时直接 `execution enter`(`codex_cli`、`claude` 与 `antigravity_cli`,任务内容内联进提示词,省去模型读文件和进入的往返;其余适配器仍由 AI 自行进入),AI 通过讨论 CLI 保存业务结果;Codex CLI 输出 `turn.completed` 1.5 秒后若进程未退出即终止,避免同会话的下一次投递等待收尾;每个 CLI 进程最长运行 2 小时(环境变量 `CODISCUSS_CLI_MAX_MS` 以毫秒覆盖),超时即终止并按“未保存本次结果”暂停房间,避免进程挂起时租约被一直续期,房间设置的单次执行时限仍优先。设置环境变量 `CODISCUSS_TRACE=1` 启动接入程序时,每次执行才在任务目录写 `event-ID.trace.jsonl`(默认不写)(时间戳加阶段:`event_seen`、`blocked_by_running_process`、`reserve_*`、`permission_check_*`、`cli_spawned`、`enter_*`、Codex 的 `thread.started` / `turn.started` / 工具命令 / `turn.completed`、`kill_after_completed`、`cli_closed`),仅用于定位各阶段耗时,不含凭据;程序不把终端回答直接代发。Codex App / CLI 可通过上述暂停后配置修正保留原绑定切换;其他适配器或项目、会话变更仍须使用独立 `data_directory` 明确重新接入。此路径不承诺 CLI 会话自动出现在桌面 App 的项目列表。
265
267
 
@@ -314,22 +316,23 @@ Codex 要求 CLI 0.163.0 或更新版本,使用 permission profiles 和本地
314
316
 
315
317
  `codiscuss launch room-member` 在运行中的自由房间邀请(`add`)或请回(`rejoin`)自动成员:先用房间控制凭据核对房主身份,在登记配置中写入成员的软件配置与新凭证,再通知网站;网页通过 `member_token` 提供其已保存的新凭证,结果不明时沿用同一凭证重试。网页邀请默认项目只读,显式选择可写后通过该成员的 `participants[name].project_access` 传递,不改变其他成员;重试保留最初选择。该命令也可为手动成员生成配置旁的 `manual-成员名.access.json`,回执只含文件路径;网页手动邀请和请回则直接在网站完成。常驻服务读取更新后的配置时逐个成员核对:新成员直接采用,已绑定会话的成员若项目、cwd 或接入方式变化,只阻止该成员并报告原因,不影响其他成员。
316
318
 
317
- 运行中的讨论同样可用 `room-member` 邀请或请回专家,但只接受由本机唤醒的成员(不接受 `access: manual`)。`participants[name].session_id` 指定已有会话时,成员标记为 `borrowed`,首次投递即续接该会话;网页仅对 Codex CLI / App 与 Claude 提供此项,agy 照旧拒绝。邀请前在本机核对会话确实存在(Codex 查 `CODEX_HOME/sessions`,Claude 查 `CLAUDE_CONFIG_DIR/projects`),填错时当场报错,不会让讨论在轮到它时暂停;Claude 会话只能在原目录续接,未指定项目时沿用原目录,项目目录不同则拒绝。Codex 会话若正被 App 持有,CLI 续接报 active writer,进度看板在暂停原因下提供“切换到 Codex App”,保存采用后可在弹窗内重启。讨论配置只新增参与者时常驻服务直接采用,讨论不暂停;已有参与者的项目、cwd 或身份变化仍须暂停后重新接入。
319
+ 运行中的讨论同样可用 `room-member` 邀请或请回专家,但只接受由本机唤醒的成员(不接受 `access: manual`)。`participants[name].session_id` 指定已有会话时,成员标记为 `borrowed`,首次投递即续接该会话;网页仅对 Codex CLI / App 与 Claude 提供此项,agy 照旧拒绝。邀请前在本机核对会话确实存在(Codex 查 `CODEX_HOME/sessions`,Claude 查 `CLAUDE_CONFIG_DIR/projects`),填错时当场报错,不会让讨论在轮到它时暂停;Claude 会话只能在原目录续接,未指定项目时沿用原目录,项目目录不同则拒绝。Codex 会话若正被 App 持有,CLI 续接报 active writer,进度看板在暂停原因下提供“切换到 Codex App”,保存采用后可在弹窗内重启。常驻服务每轮检查成员视图:手动 Claude 专家带 `reported_session` 时,`adopt_member_profile` 在本机配置登记其借用会话(会话须存在于 `CLAUDE_CONFIG_DIR/projects` 且 Claude 软件已配置),随后调用 `adopt` 把成员转为托管;任一步失败则保持手动并记录一次日志,同一会话 ID 不重试。讨论配置只新增参与者时常驻服务直接采用,讨论不暂停;已有参与者的项目、cwd 或身份变化仍须暂停后重新接入。
318
320
 
319
321
  每次检查读取 `inbox`:成员没有进行中的执行且有待投递消息时,先持久化本地 `client_id` 再领取执行,然后用原适配器续接该成员的绑定会话;任务文件列出执行编号、待处理消息和已读起点,凭据文件只含成员凭证。进程退出而网站未收到回答、软件报错或超过单次执行时限时报告失败;只有确认未送达才退回队列重试,最多 3 次。领取后、调用前中断的投递不自动重发。主持人的任务文件带 `role: "host"` 和每条请求的 `request_purposes`(`ask` / `route` / `handoff`),说明里写明如何用 `--to` 派活与何时结束。网站上执行被打断、成员离开或被移除后,只停止该成员的进程;不同成员各自投递,可以并行。成员配置了 cwd 且位于 Git 工作区时,执行前后各做一次快照,执行结束且进程退出后报告改动的文件,供网站提示可能冲突;服务重启会丢失尚未报告的快照。只能在网页创建自由房间,不支持对话入口。
320
322
 
321
323
  ### 智能调度(可选)
322
324
 
323
- 新建房间面板在选好 AI 后可勾选“智能调度”,选择供应商(TypeSafe Jev 的 `POST /v1/systemone`,或 OpenAI Decisions 的 `POST /v1/decisions`)、模型(预设 `jev-latest` / `gpt-6-luna`)并填写密钥。密钥经 `api-key` 保存到服务目录,绑定供应商官方根地址;启动请求只带 `scheduler: {provider, model, api_key_ref}`,`validate_config` 核对密钥存在后写入私有房间配置。只在本次由本机服务启动时提供:自由房间至少有一位本机唤醒成员,讨论须勾选“创建后立即启动”。目前只能在建房时开启,已建房间不能中途开启或关闭。
325
+ 新建房间面板始终显示“智能调度”,可选供应商(TypeSafe Jev 的 `POST /v1/systemone`,或 OpenAI Decisions 的 `POST /v1/decisions`)、模型(预设 `jev-latest` / `gpt-6-luna`)并填写密钥。密钥经 `api-key` 保存到服务目录,绑定供应商官方根地址;密钥旁的闪电按钮调用 `scheduler-check`(`codiscuss launch scheduler-check`),向决策模型发一道是非题核对供应商、模型与密钥。启动请求只带 `scheduler: {provider, model, api_key_ref}`,`validate_config` 核对密钥存在后写入私有房间配置。只在本次由本机服务启动时可开启(自由房间至少有一位本机唤醒成员,讨论须勾选“创建后立即启动”),否则选项禁用并说明原因。目前只能在建房时开启,已建房间不能中途开启或关闭。
324
326
 
325
- `runner/scheduler.mjs` 用中立的题目(`choice` 带选项,`predicate` 为是非题)组装两家的请求,并把答案统一为 `{choice, confidence, probabilities}` 或 `{probability}`。上下文是房间标题、最近 10 条消息(每条截断到 600 字)和待处理的那条用户消息。请求 15 秒超时,错误只报 HTTP 状态,不含响应正文或密钥。
327
+ `runner/scheduler.mjs` 用中立的题目(`choice` 带选项,`predicate` 为是非题)组装两家的请求,并把答案统一为 `{choice, confidence, probabilities}` 或 `{probability}`。上下文是房间标题、目标与用户原文、最近 10 条消息(每条截断到 600 字)和待处理的那条用户消息。把握程度取所选项的概率(缺失时才用 `confidence`;Jev 的两项选择 `confidence` 明显低于该概率,实测概率 0.82 时只有 0.64),阈值 0.8。请求 30 秒超时,错误只报 HTTP 状态,不含响应正文或密钥。
326
328
 
327
329
  - **自由房间(推荐,用户决定)**:未点名的消息照常交给主持人,判断在后台进行,不阻塞本次检查和任何投递。每条 `route` 请求只判断一次,选项为各位本机唤醒的非主持成员(描述为软件与项目读写权限)和“交回主持人”;只有选中成员且 `confidence ≥ 0.8` 时,才用 `POST recommendations` 记录推荐。网页在消息旁显示“智能调度建议让 B 回答”与 60 秒倒计时,用户可选“打断主持人,改由它回答”或“主持人答完后再回答”(主持人已答完时直接请 B 回答),过期后提示消失。判断失败、超时或置信度不足时不显示推荐,行为与未开启相同。
328
330
  - **讨论房间(有把握即执行)**:投递主持事件前先只读判断,置信度 ≥ 0.8 才执行,否则照常唤醒主持人:
329
331
  - **插话**:`held_since` 后恰有一条用户留言时判断。仅补充信息且有排队安排 → `turns/continue`;在问某位非主持专家 → 新计划先请该专家回应这条留言(问题引用原文 300 字),原排队安排按原顺序跟在后面(最多 10 项);改变方向、多条留言或话题修改 → 主持人。仅补充但计划已结束时,按下一条处理。
330
332
  - **轮次衔接**:仅 `named_followup` 为 false、无排队、上一轮已完成时判断。继续 → 先请模型选出的专家(不是上一轮最后发言者),其余按房间专家顺序,统一使用 `peer_round_question`;第 1 轮后自动把阶段从 `initial` 改为 `discussing`。可收束、跑偏、重复或卡住 → 主持人。
331
333
  - **执行方式**:有把握时 runner 领取该主持事件,以 `smart-scheduler` 会话进入,用本次执行凭据以主持人身份领取发言权、改阶段、创建或继续安排;网站的发言权、上限与凭据校验照常生效。到限、阶段不在进行中或已有进行中安排时不判断。执行中任一写入失败,把同一事件交回主持人软件并标记已进入(任务说明与 API 主持都跳过 `execution enter`)。判断失败或置信度不足时,主持任务附 `scheduler_suggestions` 供参考。
332
- - 每次判断(含交回主持人)用控制凭据写入 `POST scheduler-decisions`,对话中在相应消息下显示“智能调度:……”。
334
+ - **成员变化**:新邀请的专家由网站在入场事务中追加到现有队列末尾,原安排与当前执行保持不变,runner 到它的安排生效时才交付;手动专家仍由原会话提交。暂离、移除、请回等需要重排的变化会作废排队安排(留下最近一次实际安排之后的 `skipped`)。本轮未完成且无排队时,runner 不调用模型,按原顺序补回仍在场专家的安排,再请本轮尚未发言的专家开场,不唤醒主持人。
335
+ - 每次判断(含交回主持人)用控制凭据写入 `POST scheduler-decisions`,`after_message_id` 取最近一条已回答安排的回答,对话中显示为该发言卡片与下一张卡片之间的独立调度卡片(“智能调度:……”)。交回主持人时写明原因:建议收束、拿不准,或“未判断:已到讨论上限 / 同时有多条留言 / 话题设置有修改 / 点名追问已开启 / 本轮还有专家未发言但没有后续安排”;主持人的开场任务不记录。
333
336
  - 每次判断与结果写入 `service.log`;阈值 0.8 未经实测校准,两家的置信度不可互相套用。面板提示:开启后房间标题与最近消息会发给所选供应商。
334
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 仅按文档实现,未用真实密钥调用。
335
338