ai_co_discuss 0.3.29 → 0.3.31

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.
@@ -49,11 +49,12 @@ description: "AI CoDiscuss 的通用参与技能,所有参与者(包括主
49
49
 
50
50
  可以坚持有依据的少数意见,也可以因新证据修改判断并解释变化。主动检查关键反例与遗漏,但不为反对而反对,不以赞同人数代替证据;材料不足时说明什么证据会改变结论。
51
51
 
52
- 篇幅和结构由问题决定。保留关键数字、条件与失败情形;回应追问只补新论证或观点变化,旧内容用原文编号引用。摘要(`--summary`)按需填写:正文较长、读者需要先看结论时,用一两句写主要判断或变化及关键条件;简短回答不写,不把正文复述一遍。完整论证与出处放正文。
52
+ 篇幅和结构由问题决定。保留关键数字、条件与失败情形;回应追问只补新论证或观点变化,旧内容用原文编号引用。摘要(`--summary`)给先看结论或只听语音播报的人,约 15 秒能念完(不超过约 80 字):正文约 300 字以上或分多段时必写,简短回答不写。观点说立场或变化及最关键的理由;交付(写好的章节、文档、代码)说做了什么、最重要的变化。用能直接念出口的话,不用列表和原文编号。完整论证与出处放正文。
53
53
 
54
54
  示例(只示意密度与结构,内容与篇幅按问题而定):
55
55
 
56
- > 摘要:倾向方案 B——迁移成本一次性,A 的维护成本随规模线性增长。
56
+ > 观点 `--summary`:我倾向方案 B——迁移成本只付一次,A 的维护成本会随规模一直涨;团队五人以内时 A 仍更省。
57
+ > 交付 `--summary`:我按大家的意见重写了第一章:主角换成留校的北方研二学生林深,时代感升到 2032 年,全文约六千字。
57
58
  > 正文:结论先行;依据 1(出处/数据)、依据 2;**适用条件**:团队 ≤5 人时 A 仍更省;**不确定性**:未测量实际迁移耗时,超过 2 周则结论反转;对 #167 的分歧在于成本口径,而非事实。
58
59
 
59
60
  ## 保存与核验
@@ -521,7 +521,7 @@ def describe_members(connection, room_id, token, payload):
521
521
 
522
522
 
523
523
  def member_labels(connection, room_id):
524
- """The owner's display labels of members by name; only screens use them, never addressing or credentials."""
524
+ """The owner's display labels of members by name; screens show them and the page turns a user's @label into the member name, never credentials."""
525
525
  return {row["name"]: row["label"] for row in connection.execute(
526
526
  "SELECT name,label FROM member_labels WHERE room_id=? ORDER BY name", (room_id,))}
527
527
 
package/docs/workflow.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  ## 自由协作房间
17
17
 
18
- - **点名**:`@成员` 后,它在自己的原会话中处理并回复,多位可并行。
18
+ - **点名**:`@成员` 后,它在自己的原会话中处理并回复,多位可并行。用界面上显示的昵称点名即可(如 `@Gemini`),输入 `@` 后从列表选择最方便。
19
19
  - **未点名的消息**:交给主持人分派;只有一位成员时直接交给它。
20
20
  - **主持人派活**:主持人点名一位成员时,该成员直接回复你;主持人需要接着安排(如做完再交评审)时会要求它做完交回,点名多位时都完成后主持人再汇总。你的一条消息最多让主持人推进 3 轮(主持人派活、成员回答、交回汇总为一轮),建房时在“推进方式”里或建房后在房间页的“协作设置”里调整(1–20 轮);到上限会停下等你,右栏点“继续”即接着推进。
21
21
  - **全体回答**:`@全体成员` 请所有成员各自回答,`@全体依次` 按发言顺序逐个回答。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.3.29",
3
+ "version": "0.3.31",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
package/web/app.js CHANGED
@@ -1036,10 +1036,13 @@ function message_payload() {
1036
1036
  const members = state.room.free?.members || [];
1037
1037
  const payload = { author: "用户", text, reply_to: state.reply_to, kind: "chat", submitted_by: "用户" };
1038
1038
  const active = members.filter(member => member.status === "active" && (member.access !== "manual" || member.registered_at)).map(member => member.name);
1039
+ // Members are named by the nickname shown on screen (or their name); the request carries the member name.
1040
+ const named_here = mentioned_members(text, active);
1041
+ const mentioned_member = name => named_here.has(name);
1039
1042
  const session = state.room.session;
1040
1043
  const session_live = session && ["active", "paused"].includes(session.status);
1041
1044
  if (session_live) {
1042
- const direct = active.filter(name => mentioned(text, name));
1045
+ const direct = active.filter(mentioned_member);
1043
1046
  if (direct.length) payload.to = direct;
1044
1047
  return payload;
1045
1048
  }
@@ -1047,7 +1050,7 @@ function message_payload() {
1047
1050
  // @全体依次 or @all-seq has them answer one after another, the host first); without any, the server gives the message to the only member or the host.
1048
1051
  const sequential = mentioned(text, "全体依次") || mentioned(text, "all-seq");
1049
1052
  const everyone = !sequential && (mentioned(text, "全体成员") || mentioned(text, "all"));
1050
- const named = sequential || everyone ? active : active.filter(name => mentioned(text, name));
1053
+ const named = sequential || everyone ? active : active.filter(mentioned_member);
1051
1054
  if (named.length) payload.to = named;
1052
1055
  if (sequential && named.length) payload.sequential = true;
1053
1056
  if (everyone && named.length) payload.order = "auto";
@@ -1056,6 +1059,32 @@ function message_payload() {
1056
1059
  return payload;
1057
1060
  }
1058
1061
 
1062
+ /**
1063
+ * How a member may be @named: by the nickname shown on screen, and always by its member name. A nickname shared with
1064
+ * another member (or equal to another member's name) is ambiguous and is not used, so the member name stays the only way.
1065
+ */
1066
+ function mention_handles(name, names) {
1067
+ const nickname = display_name(name);
1068
+ const taken = names.filter(other => other !== name).flatMap(other => [other, display_name(other)]);
1069
+ return nickname && nickname !== name && !taken.includes(nickname) ? [nickname, name] : [name];
1070
+ }
1071
+
1072
+ /**
1073
+ * The members a text @names, longest handle first: once "@Claude评审" names one member, the shorter "@Claude" inside it
1074
+ * does not also name another (Chinese nicknames have no space after them).
1075
+ */
1076
+ function mentioned_members(text, names) {
1077
+ let prose = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "").replace(/^>.*$/gm, "");
1078
+ const handles = names.flatMap(name => mention_handles(name, names).map(handle => ({ name, handle })))
1079
+ .sort((left, right) => right.handle.length - left.handle.length);
1080
+ const found = new Set();
1081
+ for (const { name, handle } of handles) {
1082
+ const pattern = new RegExp(`(^|[\\s((,,。;;::])@${handle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`, "gu");
1083
+ prose = prose.replace(pattern, (match, lead) => { found.add(name); return lead + " ".repeat(match.length - lead.length); });
1084
+ }
1085
+ return found;
1086
+ }
1087
+
1059
1088
  /** Whether plain text (not code or quotes) names a member with @, so a typed mention selects it. */
1060
1089
  function mentioned(text, name) {
1061
1090
  const prose = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "").replace(/^>.*$/gm, "");
@@ -4111,7 +4140,7 @@ window.discuss_view = { snapshot: view_snapshot, write_field: write_view_field,
4111
4140
  write_expert: write_view_expert, add_instance: add_view_instance, add_preset: add_preset_instance, platform_of, execute: execute_view_action, theme: change_view_theme,
4112
4141
  admin: { register: register_admin, login: login_admin, logout: logout_admin,
4113
4142
  update_profile: update_admin_profile, avatar_from_file: admin_avatar_from_file, validate: validate_admin_form },
4114
- request: request_json, rewind_plan, identities: IDENTITIES, display_name, sources: SOURCE_LABELS,
4143
+ request: request_json, rewind_plan, identities: IDENTITIES, display_name, mention_handles, mentioned_members, sources: SOURCE_LABELS,
4115
4144
  set_member_access, set_member_model, allow_denials, set_member_adapter, reload_room_models, limits_label, stop_reason_label, participant_model, participant_files, participant_session_hint, participant_access,
4116
4145
  turn_label, turn_guidance, round_groups, limit_rounds, progress_speaker, brief_meta, max_message_length: MAX_MESSAGE_LENGTH, provenance: message_provenance, format_time };
4117
4146