ai_co_discuss 0.2.21 → 0.2.23

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.
@@ -21,7 +21,7 @@ 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`,房间始终显式指定,业务写入显式 `--as`。手动首次接入读一次 `room protocol`;自动任务不读,需要字段时只查 `schema 具体命令`,不列全部命令。
24
+ 从实际任务或用户指定值核实站点、房间和身份,不从当前网页或最近聊天猜测。站点用 `--site`、当前进程 `DISCUSS_URL` 或 `--access-file` 中绑定的站点(自动任务只带凭据文件即可),房间始终显式指定,业务写入显式 `--as`。手动首次接入读一次 `room protocol`;自动任务不读,需要字段时只查 `schema 具体命令`,不列全部命令。
25
25
 
26
26
  项目与 cwd 可缺省,有值时用于本次获准资料;CLI 路径只定位工具,不把 CLI 仓库当作讨论项目或扩大文件权限。
27
27
 
@@ -151,6 +151,7 @@ Python 在一个只读事务快照中查询;D1 使用同一个 batch 事务读
151
151
  | `TopicPanel.vue`、`WorkspaceDialogs.vue` | 话题资料、主持操作、接入步骤及编辑对话框 |
152
152
  | `CreateDiscussion.vue`、`LimitPicker.vue`、`LaunchSettings.vue` | 新建讨论表单及本机启动配置;推进方式与参与专家分组,单选方式使用强调色卡片,参与者通过“邀请 AI”加入并独立移出;轮次和时长独立配置,以“不限”按钮切换各自上限,按需展开滚轮,选择即时生效,并预览到限条件;鼠标滚轮逐格切换且不产生惯性,鼠标及触摸拖动松手后保留惯性,先阻尼滑行再吸附预计停点,轻点中间数字精确输入;键盘聚焦和直接输入时仅高亮中间选中行,输入与显示共用行高,避免整列描边与原生输入框叠加;时长分小时/分钟两列,收起与摘要共用时长格式;支持键盘,减少动态效果时保留拖动惯性并去掉三维翻转 |
153
153
  | `ProgressBoard.vue`、`ProgressPanel.vue` | 共享进度看板及抽屉容器;Codex 参与者菜单内直接打开 App / CLI 切换弹窗,显式暂停后复用本场配置的读取与保存,不跳转接入页或自动重启 |
154
+ | `FreeBoard.vue` | 自由协作房间的右侧看板,版式与讨论进度看板一致:房间状态与请求/执行计数、当前任务、成员(主持人居首,显示接入方式、模型与状态)、邀请、发起头脑风暴和每次执行用时 |
154
155
  | `use-discussion.js` | 将 `web/app.js` 的共享控制器状态接入 Vue |
155
156
  | `theme.css`、`channel.css` | 组件主题、深浅配色与响应式布局 |
156
157
  | `markdown.mjs`、`markdown.css`、`backup.mjs` | 三个主题共用的内容渲染与本地导出 |
@@ -239,7 +240,7 @@ running 状态即使只有 pending 事件,也必须先暂停才能重新启动
239
240
 
240
241
  - **凭据**:建房时由客户端提供房间控制凭据和每位成员的成员凭证,服务端只存哈希。用户身份的房间写入(发言、附件、存档、恢复、删除、打断)都须控制凭据;房间排序和归入文件夹只改共享目录,与站点级文件夹操作一样不需要凭据;无凭据、错误凭据、成员凭证或他房凭据一律只读。成员凭证只能以该成员身份发言或上传附件。声明字段(`author`、`source`、localhost)不代替凭据。
241
242
  - **成员**:`room_members` 以 `id` 为稳定主键,`access` 为 `runner`(接入程序投递)、`manual`(成员自行读写)或 `online`(只读、原文代发),`session_mode` 标记是否借用用户正在使用的会话。房主用控制凭据 `POST members` 运行中加入成员,`POST members/ID {action}` 让成员离开(`away`,作废其进行中执行与未投递请求)、请回(`active`,必须提供不同于原凭证的新凭证,旧凭证副本不再有效)或移除(`remove`,保留历史发言)。同一成员凭证不能复用于其他成员或房主;相同凭证与配置的邀请、请回重试返回已有结果。会话续接由本机接入程序负责,服务端不保存会话绑定。
242
- - **请求**:用户消息按结构化接收者 `to`(成员名)为每位成员生成一条请求;省略且仅有一位在场成员时默认点名该成员,多位成员时交给主持人(`route`),只有在线成员而没有主持人时只作留言,不广播。网页从点名按钮和正文中代码与引用之外的 `@成员` 生成 `to`。请求状态为 `pending`(待投递)→ `delivering`(处理中)→ `completed` / `cancelled` / `failed`。
243
+ - **请求**:用户消息按结构化接收者 `to`(成员名)为每位成员生成一条请求;省略且仅有一位在场成员时默认点名该成员,多位成员时交给主持人(`route`),只有在线成员而没有主持人时只作留言,不广播。网页只从正文中代码与引用之外的 `@成员` 生成 `to`,没有 @ 时省略 `to`。请求状态为 `pending`(待投递)→ `delivering`(处理中)→ `completed` / `cancelled` / `failed`。
243
244
  - **执行**:`member_executions` 每位成员最多一个 `dispatched/started`(部分唯一索引),不同成员可并行。接入程序用控制凭据 `GET inbox`、`POST executions {member_id, client_id}`(同一 `client_id` 返回同一执行)把成员的全部待投递请求打包;`POST executions/ID {action}` 支持 `renew`(租约 120 秒,不超过单次执行时限,默认 20 分钟)、`fail`(请求标记失败)和 `release`(仅确认未送达时退回队列)。租约过期的执行在下次读取或领取时标记失败,不自动重发。
244
245
  - **主持人与任务链**:用户是需求方,不担任主持人。只要有在场的非在线成员,房间就保持恰好一位主持人:成员变动时,离开或移出的主持人失去角色,由第一位在场的 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 完成回交,回交请求按(派活消息,主持人)唯一,重复执行不会重复唤醒。
245
246
  - **回应建议**:普通成员新发言的结构化 `to` 与正文在场成员 @ 合并保存到 `member_suggestions`(原消息编号、稳定成员编号),排除自己、代码与引用;不会创建请求。消息读取返回 `suggestions`,包含目标在场状态及已转换的请求编号。房主用 `POST suggestions {message_id,member_id}` 转换:同一事务记录用户确认消息,调用现有建请求路径,`message_id` 保留原回答、`chain_id` 使用用户确认消息编号、`created_by=user`。`UNIQUE(message_id,member_id)` 使重复或并发点击只生成一个请求及一条确认,终态不重新派发。权限、在场状态和未存档条件在写入事务内校验;Worker 使用批次断言。升级须应用包含新表的 schema;Python 重启时自动建表,D1 发布前显式执行。已有历史消息不补写建议。
package/docs/cli.md CHANGED
@@ -31,7 +31,7 @@ $env:DISCUSS_URL = 'http://127.0.0.1:8765'
31
31
  codiscuss room list
32
32
  ```
33
33
 
34
- Bash 对应 `export DISCUSS_URL=http://127.0.0.1:8765`,CMD 对应 `set DISCUSS_URL=http://127.0.0.1:8765`。`--site` 优先于环境变量;没有任何配置则报错,不默认写入线上。站点须为根地址,不带路径、查询、用户名或密码。客户端使用标准代理设置、校验证书且不跟随重定向。
34
+ Bash 对应 `export DISCUSS_URL=http://127.0.0.1:8765`,CMD 对应 `set DISCUSS_URL=http://127.0.0.1:8765`。`--site` 优先于环境变量;两者都没有时,使用 `--access-file` 凭据文件中绑定的站点(凭据只会发回该站点),因此自动任务只带凭据文件即可;没有任何配置则报错,不默认写入线上。站点须为根地址,不带路径、查询、用户名或密码。客户端使用标准代理设置、校验证书且不跟随重定向。
35
35
 
36
36
  建房与更新资料支持 `--user-request` / `--user-request-file`,将用户原文与 `--background` 分开保存,各限 4000 字。`room brief` 返回两者,概要和通知不自动展开原文。
37
37
 
package/docs/workflow.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  成员回答下的执行报告列出请求编号、耗时、终态与工作区观察文件;失败或取消的报告保留在原请求下。报告展示最近 100 次执行;“未提供自动改动检测”表示非 Git 目录或未上报,不能据此判断没有文件变化。
16
16
 
17
- 自由协作房间从同一角色库选择一位或多位成员。用户是需求方而不是主持人:只要有能自行发言的成员(本机唤醒或手动接入),房间就始终有一位 AI 主持人,默认是第一位本机唤醒的成员,可点皇冠或在成员菜单中更换;主持人不能直接卸任,离开或被移出时由下一位成员接任,原先由用户主持的旧房间在下一条消息时自动指定。只有一位成员时,每条消息都交给它;有多位时,在输入框上方点选成员,或在正文中 `@成员`(代码和引用中的 @ 不算,输入框中会生效的 @ 显示为蓝色),被点名的成员各自处理;未点名的消息交给主持人分派。只有网页 AI 成员时没有主持人,未点名的消息只作留言。输入 `@` 后,输入框上方弹出在场成员菜单,继续输入可筛选;用方向键与 Enter / Tab 或点击补全,Esc 关闭。选择只修改草稿。输入框 Enter 发送、Shift + Enter 换行;成员处理中且草稿为空时,发送按钮变为红色停止按钮。自动成员空闲时,服务把它名下所有未处理的消息打包成一次执行交给原会话;执行期间再发的消息排队,下一次一起投递。手动成员自行读取自己的请求,在线成员由用户转交。成员按时间顺序处理,后面的消息可以修正前面的。
17
+ 自由协作房间从同一角色库选择一位或多位成员。用户是需求方而不是主持人:只要有能自行发言的成员(本机唤醒或手动接入),房间就始终有一位 AI 主持人,默认是第一位本机唤醒的成员,可点皇冠或在成员菜单中更换;主持人不能直接卸任,离开或被移出时由下一位成员接任,原先由用户主持的旧房间在下一条消息时自动指定。只有一位成员时,每条消息都交给它;有多位时,你的消息默认发给主持人;在正文中 `@成员` 才让其他成员发言(代码和引用中的 @ 不算,输入框中会生效的 @ 显示为蓝色),被点名的成员各自处理,主持人也可以 @ 其他成员派活。输入框下方的备注行显示这条消息会发给谁。只有网页 AI 成员时没有主持人,未点名的消息只作留言。输入 `@` 后,输入框上方弹出在场成员菜单,继续输入可筛选;用方向键与 Enter / Tab 或点击补全,Esc 关闭。选择只修改草稿。输入框 Enter 发送、Shift + Enter 换行;成员处理中且草稿为空时,发送按钮变为红色停止按钮。自动成员空闲时,服务把它名下所有未处理的消息打包成一次执行交给原会话;执行期间再发的消息排队,下一次一起投递。手动成员自行读取自己的请求,在线成员由用户转交。成员按时间顺序处理,后面的消息可以修正前面的。
18
18
 
19
19
  运行中可在右侧成员栏邀请 AI(沿用本机软件配置和本房间工作目录),也可让成员离开、请回或移除。自动成员的项目文件权限默认只读;需要它修改项目时,在邀请前选择“项目文件可写”,权限只应用于这位成员;App 与手动成员的文件权限由其软件设置,API 与网页成员不访问项目文件。填写已有会话 ID 时续接该原会话,不新建。离开会立即停止它的工作;请回时换发新凭证并续接它原来的会话。邀请时可选“手动接入”:成员不会被自动唤醒,而是得到一份私有凭据文件,由你在它的会话中按参与技能读写。绑定你正在使用的会话的成员会标注“借用会话”:它工作时不要在原窗口继续操作。
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -34,7 +34,7 @@ const HELP = `AI CoDiscuss CLI
34
34
  codiscuss room --help
35
35
  codiscuss health --site http://127.0.0.1:8765
36
36
 
37
- 网站操作使用 --site 或 DISCUSS_URL,并需要 Python 3.10+。
37
+ 网站操作使用 --site、DISCUSS_URL 或 --access-file 中的站点,并需要 Python 3.10+。
38
38
  原有 start、status、stop、add 等服务命令继续兼容。
39
39
  `;
40
40
  const SERVICE_HELP = `AI CoDiscuss 讨论服务
package/runner/discuss.py CHANGED
@@ -140,7 +140,7 @@ def attachment_id(value):
140
140
  def add_common(parser):
141
141
  """Allow common flags before or after subcommands without shared state."""
142
142
  options = {"default": argparse.SUPPRESS}
143
- parser.add_argument("--site", help="HTTP 服务地址;未指定时读 DISCUSS_URL", **options)
143
+ parser.add_argument("--site", help="HTTP 服务地址;未指定时读 DISCUSS_URL,再读 --access-file 中的 site", **options)
144
144
  parser.add_argument("--as", dest="actor", type=participant,
145
145
  help="业务写入的声明操作者;不是登录认证;api 使用原始请求体", **options)
146
146
  parser.add_argument("-o", "--output", choices=("json", "text"),
@@ -317,7 +317,7 @@ def execution_commands(subcommands, registry):
317
317
  def build_parser(prog="discuss"):
318
318
  """Build help and schema from the same command definitions."""
319
319
  parser = Parser(prog=prog, description="AI CoDiscuss CLI:通过 HTTP 操作现有讨论网站。",
320
- allow_abbrev=False, epilog="站点用 --site 或 DISCUSS_URL;不保存当前房间,不自动重试写入。")
320
+ allow_abbrev=False, epilog="站点用 --site、DISCUSS_URL 或凭据文件中的 site;不保存当前房间,不自动重试写入。")
321
321
  add_common(parser)
322
322
  parser.add_argument("--version", action="version", version=f"{prog} {VERSION}")
323
323
  subcommands = parser.add_subparsers(dest="resource", required=True)
@@ -616,12 +616,15 @@ def build_plan(args):
616
616
 
617
617
 
618
618
  def resolve_site(args):
619
- """Resolve an explicit flag or environment URL without implicit production writes."""
619
+ """Resolve the flag, then the environment, then the site the access file is bound to; never a built-in default."""
620
620
  site = getattr(args, "site", None) or os.getenv("DISCUSS_URL", "")
621
+ if not site and getattr(args, "access_file", None):
622
+ # The file's own credential is only ever sent back to this same site.
623
+ site = str(read_object(args.access_file).get("site", ""))
621
624
  parsed = urlsplit(site)
622
625
  if (parsed.scheme not in ("http", "https") or not parsed.hostname or parsed.username
623
626
  or parsed.password or parsed.query or parsed.fragment or parsed.path not in ("", "/")):
624
- raise CliError("请用 --site 或 DISCUSS_URL 指定 http(s)://主机[:端口],不带路径、凭据或查询")
627
+ raise CliError("请用 --site、DISCUSS_URL 或凭据文件指定 http(s)://主机[:端口],不带路径、凭据或查询")
625
628
  try:
626
629
  parsed.port
627
630
  except ValueError as error:
@@ -746,7 +749,7 @@ def command_schema(args, registry):
746
749
  name = " ".join(args.command)
747
750
  if not name:
748
751
  return {"version": VERSION, "commands": {key: item.description for key, item in registry.items()},
749
- "exit_codes": EXIT_CODES, "site": "--site > DISCUSS_URL;无内置默认站点",
752
+ "exit_codes": EXIT_CODES, "site": "--site > DISCUSS_URL > --access-file 中的 site;无内置默认站点",
750
753
  "output": "默认 JSON;成功 stdout,错误 stderr;无交互提示、无自动重试"}
751
754
  if name not in registry:
752
755
  raise CliError(f"未知命令 {name};运行 discuss schema 查看可用命令")
package/web/app.js CHANGED
@@ -37,7 +37,7 @@ const PLATFORM_IDENTITIES = {
37
37
  Grok: { initials: "GK", style: "grok", description: "AI 身份" },
38
38
  };
39
39
  const IDENTITIES = by_platform(PLATFORM_IDENTITIES, (identity, name) => ({ ...identity, label: name, description: "AI 实例" }));
40
- const state = { rooms: [], folders: [], room: null, page: 1, pages: 1, query: "", mention_only: false, free_to: [], free_notice: "", free_error: "",
40
+ const state = { rooms: [], folders: [], room: null, page: 1, pages: 1, query: "", mention_only: false, free_notice: "", free_error: "",
41
41
  free_proxy: "", create_parent: null,
42
42
  archive_view: false, directory_sort: "manual", management: null, dragged_room: null, folder_moves: new Map(), folder_version: 0,
43
43
  reply_to: null, turn_reply_to: null, tab: "topic", refresh_version: 0, context_dirty: false, topic_dirty: false,
@@ -795,8 +795,9 @@ function message_payload() {
795
795
  request_ids: state.room.free.requests.filter(item => item.member === online.name && item.status === "pending").map(item => item.id) };
796
796
  const payload = { author: "用户", text, reply_to: state.reply_to, kind: "chat", submitted_by: "用户" };
797
797
  const active = (state.room.free?.members || []).filter(member => member.status === "active").map(member => member.name);
798
- // One member is named by default; with several, the chips and @mentions outside code decide, never a broadcast.
799
- if (active.length > 1) payload.to = active.filter(name => state.free_to.includes(name) || mentioned(text, name));
798
+ // @mentions outside code name members; without any, the server gives the message to the only member or the host.
799
+ const named = active.filter(name => mentioned(text, name));
800
+ if (named.length) payload.to = named;
800
801
  return payload;
801
802
  }
802
803
  const mode = element("message-kind").value;
@@ -820,11 +821,6 @@ function mentioned(text, name) {
820
821
  return new RegExp(`(^|[\\s((,,。;;::])@${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`, "u").test(prose);
821
822
  }
822
823
 
823
- /** Toggle one member chip of the free-room composer. */
824
- function toggle_recipient(name) {
825
- state.free_to = state.free_to.includes(name) ? state.free_to.filter(item => item !== name) : [...state.free_to, name];
826
- }
827
-
828
824
  /** Choose an online member to forward the next answer for, or clear the choice. */
829
825
  function set_proxy(name) { state.free_proxy = state.free_proxy === name ? "" : name; }
830
826
 
@@ -2791,7 +2787,7 @@ function view_snapshot() {
2791
2787
  attachment_drafts: state.attachment_drafts.map(item => ({ id: item.id, file_name: item.file.name, size: item.file.size, preview_url: item.preview_url })),
2792
2788
  composer_error: state.composer_error, upload_status: state.upload_status, sending_message: state.sending_message,
2793
2789
  mention_only: state.mention_only, reply_to: state.reply_to, turn_reply_to: state.turn_reply_to,
2794
- free_to: [...state.free_to], free_notice: state.free_notice, free_error: state.free_error, free_proxy: state.free_proxy,
2790
+ free_notice: state.free_notice, free_error: state.free_error, free_proxy: state.free_proxy,
2795
2791
  create_parent: state.create_parent, access: access_snapshot(),
2796
2792
  tab: state.tab, snapshot_id: state.snapshot_id, context_dirty: state.context_dirty,
2797
2793
  theme: document.documentElement.dataset.theme, fields, experts, launch: structuredClone(state.launch) };
@@ -2965,7 +2961,7 @@ async function execute_view_action(name, value) {
2965
2961
  copy_online: () => copy_context(), copy_cli: () => copy_context("cli"), copy_background,
2966
2962
  choose_runtime, export_runtime, download_member_access, copy_member_join, pause_runtime, check_limits, pause_discussion, restart_discussion, force_close, conclude_discussion,
2967
2963
  save_launch_defaults, launch_current_room, resume_launch, discard_launch, retry_launch_connection: load_launch_settings,
2968
- load_room_configuration, save_room_configuration, interrupt_member, toggle_recipient, change_member, invite_member, interrupt_free, set_proxy, copy_online_notice,
2964
+ load_room_configuration, save_room_configuration, interrupt_member, change_member, invite_member, interrupt_free, set_proxy, copy_online_notice,
2969
2965
  prepare_brainstorm, return_conclusion, convert_suggestion,
2970
2966
  copy_reading: () => copy_text(`${location.origin}/r/${state.room.id}`), copy: copy_text };
2971
2967
  try {