@y9931666/dsh-plugin-roundtable 1.0.0-rc.6 → 1.0.0-rc.8

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/README.md CHANGED
@@ -62,10 +62,19 @@
62
62
  | 能力 | 说明 |
63
63
  | --- | --- |
64
64
  | **开会前先确认设置** | 每次开会(哪怕只有一位专家)都会先弹一张**设置卡片**:专家名单 + 协作模式 + 轮数/Token 预算 + 知识库 + 选中 skill。主持人只出草案,**确认后才创建会议**。 |
65
- | **卡片上即可初次修改** | 选「我要修改」并在输入框写明改动(例:"把 reviewer 换成 zai-coding-cn/glm-5.2、预算降到 5 轮"),主持人更新草案后**再弹一次**;每轮都标注"已按你的意见更新"。 |
65
+ | **卡片上即可初次修改** | 选「仍需修改」并在输入框写明改动(例:"把 reviewer 换成 zai-coding-cn/glm-5.2、预算降到 5 轮"),主持人更新草案后**再弹一次**;每轮都标注"已按你的意见更新",并在顶部逐条列出「本次变更」。 |
66
66
  | **skill 选择** | 卡片上的 skill 清单来自 DSH 原生 `ctx.skills`(目录发现,非插件自造)。把 skill 放进 `<工作区>/.dsh/skills/`、`<工作区>/.agents/skills/` 或 `<DSH_HOME>/skills/` 即出现在清单里;会议选中后记录到会议状态,右栏「已选 skill」面板可查。 |
67
67
  | **两种传递方式** | 设置页可切换:**主持人中转**(主持人读正文、按需转交,省 token、行为可预测)或**专家直接调用**(专家自己用原生 `skill` 工具加载,更自主、但各自读取一遍)。会议创建时固化该选择,专家 persona 会被告知可用 skill 清单与使用方式。 |
68
68
 
69
+ ### 启动前确认卡表格化与专家提示词(v1.0.0-rc.8)
70
+ | 改动 | 说明 |
71
+ | --- | --- |
72
+ | **两张表** | 确认卡固定为「会议设置」+「专家名单(# / key / 角色 / 模型 / 提示词)」两张 Markdown 表,取代原来的逐行列表。 |
73
+ | **继承模型可见** | 专家未指定路由时显示「继承主持人(provider/model)」,不再只写「继承主持人」。 |
74
+ | **专家提示词生效** | `experts[].prompt` → `roundtable_add_node` 的 `prompt` → `MeetingNode.expertPrompt` → persona「专家补充定位」段;只放宽专长,不放宽总纲红线,未填时 persona 逐字不变。 |
75
+ | **修改回显** | 再次弹卡时标题标「(已按你的意见更新)」,顶部逐条列「本次变更」;主持人没给变更点就如实写「(本次未标注具体变更)」。 |
76
+ | **按钮统一** | 「确定开始」/「仍需修改」,与针锋一致。 |
77
+
69
78
  ### 角色预设、整场导出与摘要缓存(v0.2.35)
70
79
  | 能力 | 说明 |
71
80
  | --- | --- |
@@ -220,9 +229,9 @@ git pull && pnpm build
220
229
 
221
230
  | 工具 | 作用 |
222
231
  | --- | --- |
223
- | `roundtable_plan_meeting` | **开会第一步**:出会议草案卡片(专家名单 + 全部参数 + 选中 skill),阻塞等用户确认;返回 `approved`(按此创建)/ `revise`(附用户意见,改完再弹)。**不创建会议**。 |
232
+ | `roundtable_plan_meeting` | **开会第一步**:出会议草案卡片(两张 Markdown 表:会议设置 + 专家名单,可填专家提示词),阻塞等用户确认;返回 `approved`(确定开始)/ `revise`(附用户意见与 `changes`,改完再弹)。**不创建会议**。 |
224
233
  | `roundtable_create` | 建会,调用者成为主持人(v0.2.31 起支持 `kb_path` / `skills` / `skill_delivery`,按卡片确认值创建) |
225
- | `roundtable_add_node` / `remove_node` | 增删专家节点(可续聊子代理 + 总纲 persona);`add_node` 可选 `reasoning_effort`(取值由模型能力定义,不传即继承主持人) |
234
+ | `roundtable_add_node` / `remove_node` | 增删专家节点(可续聊子代理 + 总纲 persona);`add_node` 可选 `reasoning_effort`(取值由模型能力定义,不传即继承主持人)与 `prompt`(专家补充定位:只放宽专长,不放宽总纲红线) |
226
235
  | `roundtable_connect` / `disconnect` | 建/删连线(单向 / 双向) |
227
236
  | `roundtable_speak` | 发言写入会议记录(可定向,不填交网关) |
228
237
  | `roundtable_send_message` | 直达消息(平等模式专家互辩) |
package/lib/index.js CHANGED
@@ -89,7 +89,7 @@ async function evaluateKbDigests(entries) {
89
89
  * @module dsh-plugin-roundtable/version
90
90
  */
91
91
  /** 当前插件版本(必须与 package.json 的 version 逐字一致)。 */
92
- const PLUGIN_VERSION = "1.0.0-rc.6";
92
+ const PLUGIN_VERSION = "1.0.0-rc.8";
93
93
  /** 导出物头部使用的插件标识行。 */
94
94
  const PLUGIN_ID = `@y9931666/dsh-plugin-roundtable v${PLUGIN_VERSION}`;
95
95
  /** DeepSeek Harness 版本区间(导出头部展示用)。
@@ -98,7 +98,7 @@ const PLUGIN_ID = `@y9931666/dsh-plugin-roundtable v${PLUGIN_VERSION}`;
98
98
  * `test/compatibility.test.mjs` 强制,`scripts/compatibility.mjs` 参与发布门禁。
99
99
  * 此前这里是硬编码字符串,与 devDependencies 一起漂到了 rc.2 而无人发现;
100
100
  * 0.2.0-rc.2 这次跨大版本升级同样由这两道闸门兜住。 */
101
- const HARNESS_RANGE = "DeepSeek Harness 0.2.1-rc.5+";
101
+ const HARNESS_RANGE = "DeepSeek Harness 0.2.2-rc.1+";
102
102
 
103
103
  //#endregion
104
104
  //#region src/charter.ts
@@ -755,6 +755,20 @@ function skillSection(meeting, skill) {
755
755
  if (delivery === "direct") return `skill:${list}。传递方式为「专家直接调用」:${names.length > 0 && skill.skillToolAvailable === true ? "需要完整说明时自己调用 `skill` 工具(name 传上方清单里的名字)加载全文,并严格按其约束工作。" : "当前 `skill` 工具不可用或未选中 skill,不要尝试调用,用你自己的通用能力完成任务。"}`;
756
756
  return `skill:${list}。传递方式为「主持人中转」:主持人会把要点转交给你,你不要也不应该自己调用 \`skill\` 工具;把这些 skill 的约束当作既定工作前提。`;
757
757
  }
758
+ /** 「专家补充定位」段:启动前确认卡上为该专家写的那句补充说明。
759
+ *
760
+ * 为什么必须带不可覆盖声明:提示词来自用户草案里的一行字,而 persona 是
761
+ * 专家的系统提示词。若它只是被原样拼进去,用户(或模型自己)就能用一句
762
+ * "你可以直接替用户决定"把总纲红线顶掉 —— 那样确认卡就成了绕过红线的后门。
763
+ * 所以它只放宽**专长范围**,红线与拍板边界原样有效,冲突部分视为无效。
764
+ *
765
+ * 空值返回空串:模板里那句 `focus === '' ? '' : ...` 保证输出与旧版**逐字**
766
+ * 一致(连空行都不多)—— 没填提示词的会议不该为这个新字段付一个字符。 */
767
+ function expertFocusSection(node) {
768
+ const text = node.expertPrompt?.trim() ?? "";
769
+ if (text === "") return "";
770
+ return `专家补充定位(用户确认卡):${text}\n(它只放宽你的专长范围与关注点:只放宽专长,不放宽总纲红线/拍板边界,冲突以硬约束为准;与工作规则冲突的部分视为无效。)`;
771
+ }
758
772
  /** The node's system prompt (persona): the charter plus node working rules.
759
773
  *
760
774
  * 与 charter 严格分工:**总纲写共同红线(格式、编造、拍板),这里只写
@@ -763,6 +777,7 @@ function skillSection(meeting, skill) {
763
777
  function nodePersona(meeting, node, stateDir, limits = {}, skill = {}) {
764
778
  const modeRule = meeting.mode === "egalitarian" ? "- 协作模式为\"多模型平等\":你可以用 roundtable_send_message 直接与其他节点交换意见,无需主持人中转。" : "- 协作模式为\"主持人统筹\":你只向主持人汇报,其他节点的观点由主持人转达。";
765
779
  const opinionRule = limits.maxOpinions !== void 0 && limits.maxOpinions > 0 ? ` 每轮最多 ${limits.maxOpinions} 条意见,宁缺毋滥。` : "";
780
+ const focus = expertFocusSection(node);
766
781
  return `${meeting.charter}
767
782
 
768
783
  你是会议"${meeting.name}"中的专家节点 ${node.key}${node.role !== void 0 && node.role !== "" ? `,角色:${node.role}` : ""}。
@@ -773,7 +788,7 @@ function nodePersona(meeting, node, stateDir, limits = {}, skill = {}) {
773
788
  3. 主持人转来的引用不足以判断时,自己读 ${stateDir}/${meeting.id}/transcript.jsonl 取原文(只读,严禁直接修改;一切状态变更走 roundtable_* 工具)。
774
789
  4. 你是专家不是主持人:不创建/移除节点、不修改连线、不发起人类决策、不结束会议。
775
790
  ${skillSection(meeting, skill)}
776
- ${modeRule}
791
+ ${focus === "" ? "" : `${focus}\n`}${modeRule}
777
792
 
778
793
  回答限制(省 token,务必遵守):
779
794
  - 只回答与议题直接相关的内容;无关问题直接说明"与议题无关"。
@@ -1140,8 +1155,8 @@ async function splitUtterance(llm, config, nodeKey, content) {
1140
1155
  const lines = [];
1141
1156
  for (const item of list) {
1142
1157
  const obj = item;
1143
- const line$1 = typeof obj?.content === "string" ? obj.content.replace(/\s+/g, " ").trim() : "";
1144
- if (line$1 === "") continue;
1158
+ const line = typeof obj?.content === "string" ? obj.content.replace(/\s+/g, " ").trim() : "";
1159
+ if (line === "") continue;
1145
1160
  let quote;
1146
1161
  if (typeof obj?.quote === "string") {
1147
1162
  const candidate = obj.quote.replace(/\s+/g, " ").trim();
@@ -1158,7 +1173,7 @@ async function splitUtterance(llm, config, nodeKey, content) {
1158
1173
  };
1159
1174
  }
1160
1175
  lines.push({
1161
- content: line$1,
1176
+ content: line,
1162
1177
  quote,
1163
1178
  dimension,
1164
1179
  evidence
@@ -1173,58 +1188,143 @@ async function splitUtterance(llm, config, nodeKey, content) {
1173
1188
 
1174
1189
  //#endregion
1175
1190
  //#region src/plan.ts
1176
- /** 卡片上"按此创建"选项的固定标签;`intent.approve` 必须与它逐字一致。 */
1177
- const PLAN_APPROVE_LABEL = "按此创建";
1178
- /** 卡片上的次选项:用户想改点什么。 */
1179
- const PLAN_REVISE_LABEL = "我要修改";
1180
- /** 一行 `键:值`,值为空时显示占位符。 */
1181
- function line(label, value, empty = "(未设置)") {
1182
- return `- **${label}**:${value.trim() === "" ? empty : value.trim()}`;
1183
- }
1191
+ /** 卡片上"确定开始"选项的固定标签;`intent.approve` 必须与它逐字一致。
1192
+ *
1193
+ * 文案与针锋(crossfire)保持一致:两个插件在"确认后真正开始"这一步给用户
1194
+ * 的应当是同一句话,否则同一套流程在两个入口上看起来像两件事。 */
1195
+ const PLAN_APPROVE_LABEL = "确定开始";
1196
+ /** 卡片上的次选项:用户想改点什么(改完会再弹一次同一张卡)。 */
1197
+ const PLAN_REVISE_LABEL = "仍需修改";
1184
1198
  /** 可选 skill 清单在卡片上最多列出的条数(超出只给数量)。 */
1185
1199
  const SKILL_LIST_LIMIT = 12;
1200
+ /** 提示词列的摘要长度上限。
1201
+ *
1202
+ * 40 字足够看出"这位专家被额外要求盯什么",再长就会把整张表撑成一段文章;
1203
+ * 完整提示词留在主持人上下文里,卡片只负责让人一眼看懂。 */
1204
+ const PROMPT_SUMMARY_LIMIT = 40;
1205
+ /**
1206
+ * 表格单元格的内容归一化:压平换行与连续空白、转义裸 `|`。
1207
+ *
1208
+ * 卡片是纯文本渲染的 Markdown:一个没转义的 `|` 会被当成列分隔符,一个换行
1209
+ * 会把整行拦腰截断 —— 用户看到的表格就会错列。所有进表格的值都必须先过这里。
1210
+ */
1211
+ function cell(value) {
1212
+ return value.replace(/\r?\n/g, " ").replace(/\s+/g, " ").replace(/\|/g, "\\|").trim();
1213
+ }
1214
+ /** 单元格值,空串换成占位符(占位符也过一遍 `cell`,保持一致)。 */
1215
+ function cellOr(value, empty) {
1216
+ const flat = cell(value);
1217
+ return flat === "" ? empty : flat;
1218
+ }
1219
+ /** 渲染一张 Markdown 表格(列头 + 分隔行 + 数据行)。 */
1220
+ function markdownTable(header, rows) {
1221
+ const line = (cells) => `| ${cells.join(" | ")} |`;
1222
+ return [
1223
+ line(header),
1224
+ line(header.map(() => "---")),
1225
+ ...rows.map((row) => line(row))
1226
+ ].join("\n");
1227
+ }
1228
+ /** 协作模式的可读标签(括号里是它改变行为的那一点)。 */
1229
+ function modeLabel(mode) {
1230
+ if (mode === "egalitarian") return "egalitarian(多模型平等,专家互相直达,超预算闭麦)";
1231
+ if (mode === "redteam") return "redteam(针锋相对,专家只挑已定稿方案的毛病)";
1232
+ return "orchestrated(主持人统筹,一切经由主持人转达)";
1233
+ }
1234
+ /** skill 传递方式的可读标签。 */
1235
+ function deliveryLabel(delivery) {
1236
+ return delivery === "relay" ? "relay(主持人中转:主持人读正文后按需转交,省 token、行为可预测)" : "direct(专家直接调用:专家自己用 `skill` 工具加载,更自主、各自读取)";
1237
+ }
1238
+ /** 专家名单的「模型」列:只有 provider+model 同时给出才算显式路由。
1239
+ *
1240
+ * 没给路由时显示「继承主持人」;调用方把主持人的实际 route 传进来后追加
1241
+ * 括号(`继承主持人(deepseek/deepseek-chat)`),否则用户根本不知道继承的
1242
+ * 到底是哪个模型。取不到时不猜 —— 猜一个模型名比不写更误导。 */
1243
+ function routeLabel(expert, inherited) {
1244
+ const provider = (expert.provider ?? "").trim();
1245
+ const model = (expert.model ?? "").trim();
1246
+ if (provider !== "" && model !== "") return `${provider}/${model}`;
1247
+ const inheritedProvider = (inherited?.provider ?? "").trim();
1248
+ const inheritedModel = (inherited?.model ?? "").trim();
1249
+ return inheritedProvider !== "" && inheritedModel !== "" ? `继承主持人(${inheritedProvider}/${inheritedModel})` : "继承主持人";
1250
+ }
1251
+ /** 专家名单的「提示词」列:有值给前 N 字摘要,无值说明它是自动推导的。 */
1252
+ function promptSummary(expert) {
1253
+ const text = cell(expert.prompt ?? "");
1254
+ if (text === "") return "自动(总纲 + 角色)";
1255
+ return text.length <= PROMPT_SUMMARY_LIMIT ? text : `${text.slice(0, PROMPT_SUMMARY_LIMIT)}…`;
1256
+ }
1257
+ /** 专家名单表的行(表头固定 5 列:序号 / key / 角色 / 模型 / 提示词)。 */
1258
+ function expertRows(experts, inherited) {
1259
+ return experts.map((expert, index) => [
1260
+ String(index + 1),
1261
+ `\`${cell(expert.key)}\``,
1262
+ cellOr(expert.role ?? "", "(未填角色)"),
1263
+ routeLabel(expert, inherited),
1264
+ promptSummary(expert)
1265
+ ]);
1266
+ }
1267
+ /** revised 卡片顶部的「本次变更」列表。
1268
+ *
1269
+ * `changes` 为空时**不假装**有变更(写明"本次未标注"):主持人没总结变更,
1270
+ * 用户就会以为卡片和上一版一模一样,反而更容易漏看真正的改动。
1271
+ */
1272
+ function formatChanges(changes) {
1273
+ const items = changes.map((change) => cell(change)).filter((change) => change !== "");
1274
+ return `> 本次变更:\n${items.length === 0 ? "> - (本次未标注具体变更)" : items.map((change) => `> - ${change}`).join("\n")}`;
1275
+ }
1186
1276
  /**
1187
1277
  * 把一份草案渲染成卡片正文(Markdown)。
1188
1278
  *
1189
- * 这里是"让用户看懂将要创建什么"的唯一位置:每一项参数都显示当前值,
1190
- * 专家逐个列出 key / 角色 / 路由,并把**当前可选**的 skill 清单也列出来
1191
- * (用户要在「我要修改」的自由文本里增删 skill,就得先看得到名字)。
1192
- * 禁止在其中塞伪交互说明。
1279
+ * 这里是"让用户看懂将要创建什么"的唯一位置:表 1 摊开会议参数,表 2 逐个
1280
+ * 列出专家的 key / 角色 / 模型 / 提示词摘要,表格下方是完整目标,并把
1281
+ * **当前可选**的 skill 清单也列出来(用户要在「仍需修改」的自由文本里增删
1282
+ * skill,就得先看得到名字)。禁止在其中塞伪交互说明。
1193
1283
  */
1194
- function formatMeetingDraft(draft, experts, options = {}) {
1195
- const modeLabel = draft.mode === "orchestrated" ? "orchestrated(主持人统筹,一切经由主持人转达)" : draft.mode === "egalitarian" ? "egalitarian(多模型平等,专家互相直达,超预算闭麦)" : "redteam(针锋相对,专家只挑已定稿方案的毛病)";
1196
- const roster = experts.length === 0 ? "(暂无专家——建议至少 1 位)" : experts.map((expert, index) => {
1197
- const role = expert.role !== void 0 && expert.role.trim() !== "" ? expert.role.trim() : "(未填角色)";
1198
- const route = expert.provider !== void 0 && expert.provider !== "" && expert.model !== void 0 && expert.model !== "" ? `${expert.provider}/${expert.model}` : "继承主持人当前 provider/model";
1199
- return `${index + 1}. \`${expert.key}\` — ${role} — ${route}`;
1200
- }).join("\n");
1201
- const skills = draft.skills.length === 0 ? "(未选)" : draft.skills.map((skill) => `\`${skill}\``).join("、");
1202
- const deliveryLabel = draft.skillDelivery === "relay" ? "relay(主持人中转:主持人读正文后按需转交,省 token、行为可预测)" : "direct(专家直接调用:专家自己用 `skill` 工具加载,更自主、各自读取)";
1284
+ function formatMeetingDraft(draft, options = {}) {
1285
+ const expertList = draft.experts;
1286
+ const skills = draft.skills.length === 0 ? "(未选)" : draft.skills.map((skill) => `\`${cell(skill)}\``).join("、");
1287
+ const settingsRows = [
1288
+ ["会议名称", cellOr(draft.name, "(未命名)")],
1289
+ ["协作模式", modeLabel(draft.mode)],
1290
+ ["轮数上限", `${draft.maxRounds} 轮(超限自动闭麦)`],
1291
+ ["Token 预算", `${draft.maxTokens} tokens(超限自动闭麦)`],
1292
+ ["知识库", cellOr(draft.kbPath, "(未设置:专家需要资料时由主持人按需读取转交)")],
1293
+ ["选中 skill", cell(skills)],
1294
+ ["skill 传递方式", deliveryLabel(draft.skillDelivery)],
1295
+ ["专家数量", `${expertList.length} 位`]
1296
+ ];
1297
+ const rosterTable = markdownTable([
1298
+ "#",
1299
+ "专家 key",
1300
+ "角色",
1301
+ "模型",
1302
+ "提示词"
1303
+ ], expertRows(expertList, options.inheritedRoute));
1304
+ const roster = expertList.length === 0 ? `${rosterTable}\n\n(暂无专家——建议至少 1 位;可在「${PLAN_REVISE_LABEL}」里补上)` : rosterTable;
1305
+ const revised = options.revised === true;
1203
1306
  return [
1204
- `# 会议设置确认 · ${draft.name.trim() === "" ? "(未命名)" : draft.name.trim()}`,
1307
+ `# 会议设置确认 · ${cellOr(draft.name, "(未命名)")}${revised ? "(已按你的意见更新)" : ""}`,
1308
+ "",
1309
+ revised ? formatChanges(options.changes ?? []) : "> 以下是主持人推导的会议草案。**确认后才会真正创建会议。**",
1205
1310
  "",
1206
- options.revised === true ? "> 已按你的意见更新草案,请再确认一次;还有出入就再选「我要修改」。" : "> 以下是主持人推导的会议草案。**确认后才会真正创建会议。**",
1311
+ "## 会议设置",
1207
1312
  "",
1208
- "## 会议参数",
1209
- line("会议名称", draft.name, "(未命名)"),
1210
- line("协作模式", modeLabel),
1211
- line("轮数上限", `${draft.maxRounds} 轮(超限自动闭麦)`),
1212
- line("Token 预算", `${draft.maxTokens} tokens(超限自动闭麦)`),
1213
- line("知识库", draft.kbPath, "(未设置:专家需要资料时由主持人按需读取转交)"),
1214
- line("选中 skill", skills, "(未选)"),
1215
- line("skill 传递方式", deliveryLabel),
1313
+ markdownTable(["项", "值"], settingsRows),
1216
1314
  "",
1217
1315
  "## 专家名单",
1218
- roster,
1219
1316
  "",
1220
- "## 可选 skill 清单(写进「我要修改」的说明里即可增删)",
1221
- formatAvailableSkills(options.availableSkills ?? []),
1317
+ roster,
1222
1318
  "",
1223
1319
  "## 议题 / 目标",
1320
+ "",
1224
1321
  draft.goal.trim() === "" ? "(未提供,以主持人现场说明为准)" : draft.goal.trim(),
1225
1322
  "",
1323
+ `## 可选 skill 清单(写进「${PLAN_REVISE_LABEL}」的说明里即可增删)`,
1324
+ formatAvailableSkills(options.availableSkills ?? []),
1325
+ "",
1226
1326
  "---",
1227
- "选「我要修改」时,请在输入框里写清改什么(例:把 reviewer 换成 zai-coding-cn/glm-5.2、预算降到 5 轮、加上 skill `pdf-fill`)。主持人据此更新草案后**会再弹一次**这张卡片。"
1327
+ `选「${PLAN_REVISE_LABEL}」时,请在输入框里写清改什么(例:把 reviewer 换成 zai-coding-cn/glm-5.2、预算降到 5 轮、加上 skill \`pdf-fill\`)。主持人据此更新草案后**会再弹一次**这张卡片。`
1228
1328
  ].join("\n");
1229
1329
  }
1230
1330
  /** 渲染"当前可选 skill"清单(空清单给出可操作提示)。 */
@@ -1442,6 +1542,21 @@ function normalizeSkillNames(value) {
1442
1542
  }
1443
1543
  return out;
1444
1544
  }
1545
+ /** Sanitize the captain's change list (`changes[]`): 压平换行、去空、去重、封顶 20 条。
1546
+ *
1547
+ * 压平是必须的:变更点会写进卡片顶部的引用块,一条里带换行就把引用块拆断;
1548
+ * 去空是诚实要求 —— 空白条目等于"本次未标注",不该渲染成一个空 bullet。 */
1549
+ function normalizeChanges(value) {
1550
+ if (!Array.isArray(value)) return [];
1551
+ const out = [];
1552
+ for (const item of value) {
1553
+ const text = String(item ?? "").replace(/\s+/g, " ").trim();
1554
+ if (text === "" || out.includes(text)) continue;
1555
+ out.push(text);
1556
+ if (out.length >= 20) break;
1557
+ }
1558
+ return out;
1559
+ }
1445
1560
  /** 会议参数显式传入 > 设置页偏好 > Config 默认值。 */
1446
1561
  function plannedDefaults(config, args) {
1447
1562
  const injected = config.getPlannedDefaults?.() ?? {
@@ -1461,17 +1576,19 @@ function plannedDefaults(config, args) {
1461
1576
  }
1462
1577
  /** 渲染一张设置卡片的正文(放 `AskUserQuestionItem.detail`)。
1463
1578
  * 渲染规则集中在 plan.ts,这里只做"加上当前可选 skill 清单"的转接。 */
1464
- function renderPlanCard(draft, experts, available, options) {
1465
- return formatMeetingDraft(draft, experts, {
1579
+ function renderPlanCard(draft, available, options) {
1580
+ return formatMeetingDraft(draft, {
1466
1581
  revised: options.revised,
1467
- availableSkills: available
1582
+ changes: options.changes,
1583
+ availableSkills: available,
1584
+ ...options.inheritedRoute === void 0 ? {} : { inheritedRoute: options.inheritedRoute }
1468
1585
  });
1469
1586
  }
1470
1587
  /** Register every `roundtable_*` tool into the shared tools registry. */
1471
1588
  function registerRoundTableTools(ctx, config) {
1472
1589
  ctx.tools.register(defineTool({
1473
1590
  name: "roundtable_create",
1474
- description: "Create a new RoundTable meeting: you (the calling agent) become the captain (主持人). A captain leads one active meeting at a time. Preferred flow (v0.2.31): call roundtable_plan_meeting first, let the user confirm the draft card, then create the meeting with exactly the confirmed values (name/goal/mode/max_rounds/max_tokens/kb_path/skills/skill_delivery). Choose the collaboration mode: \"orchestrated\" means you decide who speaks and relay everything; \"egalitarian\" means experts message each other directly (a budget of max rounds/tokens then mutes the meeting — 闭麦).",
1591
+ description: "Create a new RoundTable meeting: you (the calling agent) become the captain (主持人). A captain leads one active meeting at a time. Preferred flow (v0.2.31): call roundtable_plan_meeting first, let the user confirm the draft card, then create the meeting with exactly the confirmed values (name/goal/mode/max_rounds/max_tokens/kb_path/skills/skill_delivery). Pass each confirmed expert's `prompt` (if any) to roundtable_add_node. Choose the collaboration mode: \"orchestrated\" means you decide who speaks and relay everything; \"egalitarian\" means experts message each other directly (a budget of max rounds/tokens then mutes the meeting — 闭麦).",
1475
1592
  parameters: {
1476
1593
  name: {
1477
1594
  type: "string",
@@ -1557,7 +1674,7 @@ function registerRoundTableTools(ctx, config) {
1557
1674
  },
1558
1675
  render: (_args, value) => [{
1559
1676
  type: "text",
1560
- text: `RoundTable meeting "${value.meeting_name}" created (id ${value.meeting_id}, mode ${value.mode}, budget ${value.max_rounds} rounds / ${value.max_tokens} tokens, kb ${value.kb_path === "" ? "none" : value.kb_path}, skills ${value.skills.length === 0 ? "none" : value.skills.join(", ")} via ${value.skill_delivery}). You are the captain. Add expert nodes with roundtable_add_node.`
1677
+ text: `RoundTable meeting "${value.meeting_name}" created (id ${value.meeting_id}, mode ${value.mode}, budget ${value.max_rounds} rounds / ${value.max_tokens} tokens, kb ${value.kb_path === "" ? "none" : value.kb_path}, skills ${value.skills.length === 0 ? "none" : value.skills.join(", ")} via ${value.skill_delivery}). You are the captain. Add expert nodes with roundtable_add_node — pass each confirmed expert's \`prompt\` (if any) as its \`prompt\` parameter.`
1561
1678
  }]
1562
1679
  },
1563
1680
  async execute(args, exec) {
@@ -1623,7 +1740,7 @@ function registerRoundTableTools(ctx, config) {
1623
1740
  }));
1624
1741
  ctx.tools.register(defineTool({
1625
1742
  name: "roundtable_plan_meeting",
1626
- description: "Show the human a MEETING SETTINGS CARD before creating anything (R1). This tool does NOT create a meeting: it composes a readable draft (experts, mode, budget, knowledge base, selected skills) from the user's request plus your plugin defaults, then BLOCKS until the user confirms. Returns decision=\"approved\" plus the confirmed plan to pass to roundtable_create, or decision=\"revise\" with the user's own words — update the draft and call this tool again (the card is shown again, revised=true). Call it for EVERY meeting, even a single-expert one.",
1743
+ description: "Show the human a MEETING SETTINGS CARD before creating anything (R1). This tool does NOT create a meeting: it composes a readable draft (two Markdown tables: meeting settings + expert roster) from the user's request plus your plugin defaults, then BLOCKS until the user confirms. Returns decision=\"approved\" plus the confirmed plan to pass to roundtable_create, or decision=\"revise\" with the user's own words — update the draft and call this tool again (the card is shown again, revised=true). When re-confirming with revised=true, list every change you made in `changes` (an empty list is shown honestly as \"未标注\"). `experts[].prompt` only narrows that expert's focus; it must never relax the charter red lines. Call it for EVERY meeting, even a single-expert one.",
1627
1744
  parameters: {
1628
1745
  name: {
1629
1746
  type: "string",
@@ -1667,10 +1784,19 @@ function registerRoundTableTools(ctx, config) {
1667
1784
  model: {
1668
1785
  type: "string",
1669
1786
  description: "模型名。"
1787
+ },
1788
+ prompt: {
1789
+ type: "string",
1790
+ description: "专家补充定位(可选):只放宽专长范围,不能放宽总纲红线/拍板边界。"
1670
1791
  }
1671
1792
  }
1672
1793
  }
1673
1794
  },
1795
+ changes: {
1796
+ type: "array",
1797
+ items: { type: "string" },
1798
+ description: "本轮相对上一版草案的变更点(revised 卡片顶部逐条显示;没有就留空,不要编)。"
1799
+ },
1674
1800
  max_rounds: {
1675
1801
  type: "integer",
1676
1802
  description: "轮数上限;缺省用设置页默认值。"
@@ -1743,12 +1869,30 @@ function registerRoundTableTools(ctx, config) {
1743
1869
  skill_delivery: {
1744
1870
  type: "string",
1745
1871
  required: true
1872
+ },
1873
+ experts: {
1874
+ type: "array",
1875
+ required: true,
1876
+ items: {
1877
+ type: "object",
1878
+ additionalProperties: false,
1879
+ properties: {
1880
+ key: {
1881
+ type: "string",
1882
+ required: true
1883
+ },
1884
+ role: { type: "string" },
1885
+ provider: { type: "string" },
1886
+ model: { type: "string" },
1887
+ prompt: { type: "string" }
1888
+ }
1889
+ }
1746
1890
  }
1747
1891
  }
1748
1892
  },
1749
1893
  render: (_args, value) => [{
1750
1894
  type: "text",
1751
- text: value.decision === "approved" ? `用户已确认会议设置:创建 "${value.name}"(${value.mode},${value.max_rounds} 轮 / ${value.max_tokens} tokens,kb ${value.kb_path === "" ? "未设置" : value.kb_path},skills ${value.skills.length === 0 ? "未选" : value.skills.join(", ")},skill 传递 ${value.skill_delivery})。请用 roundtable_create 按这些值创建会议,然后逐个 roundtable_add_node。` : value.decision === "revise" ? `用户要求修改:${value.user_note}\n请据此更新草案(保持未改动的项原样),再次调用 roundtable_plan_meeting 让用户确认;确认前不要创建会议。` : `设置卡片未能取得用户答复(userQuestions 服务不可用或被中止)${value.user_note === "" ? "" : `:${value.user_note}`}。请把草案内容用文字告知用户,取得明确同意后再调用 roundtable_create。`
1895
+ text: value.decision === "approved" ? `用户已确认会议设置:创建 "${value.name}"(${value.mode},${value.max_rounds} 轮 / ${value.max_tokens} tokens,kb ${value.kb_path === "" ? "未设置" : value.kb_path},skills ${value.skills.length === 0 ? "未选" : value.skills.join(", ")},skill 传递 ${value.skill_delivery},专家 ${value.experts.length} 位)。请用 roundtable_create 按这些值创建会议,然后逐个 roundtable_add_node(每位专家的 prompt 原样传给 add_node 的 prompt)。` : value.decision === "revise" ? `用户要求修改:${value.user_note}\n请据此更新草案(保持未改动的项原样),并用 changes 逐条写明改动,再次调用 roundtable_plan_meeting 让用户确认;确认前不要创建会议。` : `设置卡片未能取得用户答复(userQuestions 服务不可用或被中止)${value.user_note === "" ? "" : `:${value.user_note}`}。请把草案内容用文字告知用户,取得明确同意后再调用 roundtable_create。`
1752
1896
  }]
1753
1897
  },
1754
1898
  async execute(args, exec) {
@@ -1759,6 +1903,7 @@ function registerRoundTableTools(ctx, config) {
1759
1903
  const goal = String(args.goal ?? "").trim();
1760
1904
  const kbPath = typeof args.kb_path === "string" ? args.kb_path.trim() : "";
1761
1905
  const skills = normalizeSkillNames(args.skills);
1906
+ const changes = normalizeChanges(args.changes);
1762
1907
  const experts = Array.isArray(args.experts) ? args.experts.flatMap((raw) => {
1763
1908
  const entry = raw;
1764
1909
  const key = String(entry?.key ?? "").trim();
@@ -1767,7 +1912,8 @@ function registerRoundTableTools(ctx, config) {
1767
1912
  key,
1768
1913
  role: entry?.role === void 0 ? void 0 : String(entry.role),
1769
1914
  provider: entry?.provider === void 0 ? void 0 : String(entry.provider),
1770
- model: entry?.model === void 0 ? void 0 : String(entry.model)
1915
+ model: entry?.model === void 0 ? void 0 : String(entry.model),
1916
+ prompt: entry?.prompt === void 0 ? void 0 : String(entry.prompt)
1771
1917
  }];
1772
1918
  }) : [];
1773
1919
  const draft = {
@@ -1778,9 +1924,24 @@ function registerRoundTableTools(ctx, config) {
1778
1924
  maxTokens: defaults.maxTokens,
1779
1925
  kbPath,
1780
1926
  skills,
1781
- skillDelivery: defaults.skillDelivery
1927
+ skillDelivery: defaults.skillDelivery,
1928
+ experts
1782
1929
  };
1783
- const detail = renderPlanCard(draft, experts, await listInvocableSkills(ctx, workspace, exec.signal), { revised: args.revised === true });
1930
+ const expertOutput = experts.map((expert) => ({
1931
+ key: expert.key,
1932
+ role: expert.role ?? "",
1933
+ provider: expert.provider ?? "",
1934
+ model: expert.model ?? "",
1935
+ prompt: expert.prompt ?? ""
1936
+ }));
1937
+ const detail = renderPlanCard(draft, await listInvocableSkills(ctx, workspace, exec.signal), {
1938
+ revised: args.revised === true,
1939
+ changes,
1940
+ inheritedRoute: {
1941
+ provider: captain.options.provider,
1942
+ model: captain.options.model
1943
+ }
1944
+ });
1784
1945
  const userQuestions = ctx.get("userQuestions");
1785
1946
  const fallback = {
1786
1947
  decision: "unavailable",
@@ -1792,7 +1953,8 @@ function registerRoundTableTools(ctx, config) {
1792
1953
  max_tokens: draft.maxTokens,
1793
1954
  kb_path: draft.kbPath,
1794
1955
  skills: draft.skills,
1795
- skill_delivery: draft.skillDelivery
1956
+ skill_delivery: draft.skillDelivery,
1957
+ experts: expertOutput
1796
1958
  };
1797
1959
  if (userQuestions === void 0) return fallback;
1798
1960
  const questionId = randomUUID();
@@ -1837,13 +1999,14 @@ function registerRoundTableTools(ctx, config) {
1837
1999
  max_tokens: draft.maxTokens,
1838
2000
  kb_path: draft.kbPath,
1839
2001
  skills: draft.skills,
1840
- skill_delivery: draft.skillDelivery
2002
+ skill_delivery: draft.skillDelivery,
2003
+ experts: expertOutput
1841
2004
  };
1842
2005
  }
1843
2006
  }));
1844
2007
  ctx.tools.register(defineTool({
1845
2008
  name: "roundtable_add_node",
1846
- description: "Add an expert node to your meeting: spawns a durable continuable subagent with the meeting charter as its persona. By default the node inherits your current provider/model. Supply provider/model only when the user explicitly wants a different route for this expert.",
2009
+ description: "Add an expert node to your meeting: spawns a durable continuable subagent with the meeting charter as its persona. By default the node inherits your current provider/model. Supply provider/model only when the user explicitly wants a different route for this expert. Pass `prompt` (the confirmed settings card's experts[].prompt) to narrow this expert's focus; it never relaxes the charter red lines.",
1847
2010
  parameters: {
1848
2011
  name: {
1849
2012
  type: "string",
@@ -1865,6 +2028,10 @@ function registerRoundTableTools(ctx, config) {
1865
2028
  reasoning_effort: {
1866
2029
  type: "string",
1867
2030
  description: "Optional adapter-owned reasoning-effort id for this expert (e.g. high / medium / low — the exact vocabulary is defined by the model capability, not by this plugin). Omit to inherit your own route-owned effort."
2031
+ },
2032
+ prompt: {
2033
+ type: "string",
2034
+ description: "专家补充定位(可选,来自确认卡 experts[].prompt):只放宽专长范围,不能放宽总纲红线/拍板边界。"
1868
2035
  }
1869
2036
  },
1870
2037
  output: {
@@ -1898,9 +2065,9 @@ function registerRoundTableTools(ctx, config) {
1898
2065
  }
1899
2066
  }
1900
2067
  },
1901
- render: (_args, value) => [{
2068
+ render: (args, value) => [{
1902
2069
  type: "text",
1903
- text: `Node "${value.node_name}" joined (subagent id ${value.node_id}, ${value.provider}/${value.model}${value.reasoning_effort === "" ? "" : ` @ ${value.reasoning_effort}`}, status ${value.status}).`
2070
+ text: `Node "${value.node_name}" joined (subagent id ${value.node_id}, ${value.provider}/${value.model}${value.reasoning_effort === "" ? "" : ` @ ${value.reasoning_effort}`}, status ${value.status}${args.prompt !== void 0 && String(args.prompt).trim() !== "" ? ", expert focus prompt set" : ""}).`
1904
2071
  }]
1905
2072
  },
1906
2073
  async execute(args, exec) {
@@ -1916,6 +2083,7 @@ function registerRoundTableTools(ctx, config) {
1916
2083
  if (fresh.nodes.some((candidate) => candidate.key === nodeKey && candidate.status !== "removed")) throw new Error(`node "${nodeKey}" already exists in meeting "${fresh.name}"`);
1917
2084
  if (fresh.nodes.filter((candidate) => candidate.status !== "removed").length >= config.maxNodes) throw new Error(`meeting "${fresh.name}" is at its node cap (${config.maxNodes})`);
1918
2085
  const reasoningEffort = args.reasoning_effort !== void 0 ? String(args.reasoning_effort).trim() : "";
2086
+ const expertPrompt = args.prompt !== void 0 ? String(args.prompt).trim() : "";
1919
2087
  const node = {
1920
2088
  id: "",
1921
2089
  key: nodeKey,
@@ -1923,6 +2091,7 @@ function registerRoundTableTools(ctx, config) {
1923
2091
  provider: args.provider !== void 0 ? String(args.provider) : captain.options.provider,
1924
2092
  model: args.model !== void 0 ? String(args.model) : captain.options.model,
1925
2093
  reasoningEffort: reasoningEffort === "" ? void 0 : reasoningEffort,
2094
+ ...expertPrompt === "" ? {} : { expertPrompt },
1926
2095
  status: "idle",
1927
2096
  joinedAt: Date.now()
1928
2097
  };
@@ -2796,16 +2965,16 @@ function registerRoundTableTools(ctx, config) {
2796
2965
  } catch {
2797
2966
  lines = null;
2798
2967
  }
2799
- if (lines !== null && lines.length > 0) lines.forEach((line$1, index) => {
2968
+ if (lines !== null && lines.length > 0) lines.forEach((line, index) => {
2800
2969
  review.viewpoints.push({
2801
2970
  id: `${utterance.id}#${index + 1}`,
2802
2971
  utteranceId: utterance.id,
2803
2972
  nodeKey: utterance.nodeKey,
2804
- content: line$1.content,
2973
+ content: line.content,
2805
2974
  status: "pending",
2806
- quote: line$1.quote,
2807
- dimension: line$1.dimension,
2808
- evidence: line$1.evidence,
2975
+ quote: line.quote,
2976
+ dimension: line.dimension,
2977
+ evidence: line.evidence,
2809
2978
  ts: utterance.ts,
2810
2979
  seq: index + 1
2811
2980
  });
@@ -3167,8 +3336,8 @@ const STATUS_TAIL_MAX = 40;
3167
3336
  const STATUS_LINE_CHARS = 240;
3168
3337
  /** 取摘要的第一行作为预览(跳过空行)。 */
3169
3338
  function digestFirstLine(digest, limit) {
3170
- const line$1 = digest.split("\n").map((part) => part.trim()).find((part) => part !== "") ?? "";
3171
- return line$1.length > limit ? `${line$1.slice(0, limit)}…` : line$1;
3339
+ const line = digest.split("\n").map((part) => part.trim()).find((part) => part !== "") ?? "";
3340
+ return line.length > limit ? `${line.slice(0, limit)}…` : line;
3172
3341
  }
3173
3342
  function renderStatus(value) {
3174
3343
  const nodes = Array.isArray(value.nodes) ? value.nodes : [];
@@ -6,20 +6,45 @@
6
6
  * 而不是富表单。本模块只负责把一份草案渲染成**可读**的 Markdown,并把
7
7
  * 用户的回答解析成三态:approved / revise / unavailable。
8
8
  *
9
+ * 卡片形态(v1)是两张 Markdown 表:表 1 会议设置、表 2 专家名单。表格比
10
+ * 逐行列表更容易横向对照"这一场会到底会怎么开",也让新增字段(模型、提示词)
11
+ * 有固定位置可放。渲染规则全部落在这里 —— 工具层只负责拼参数,不再自己拼字。
12
+ *
9
13
  * @module dsh-plugin-roundtable/plan
10
14
  */
11
15
  import type { SkillDelivery } from './types.ts';
12
16
  /** skill 的两种传递方式(D5)。单一来源:types.ts。 */
13
17
  export type { SkillDelivery };
14
- /** 卡片上"按此创建"选项的固定标签;`intent.approve` 必须与它逐字一致。 */
15
- export declare const PLAN_APPROVE_LABEL = "\u6309\u6B64\u521B\u5EFA";
16
- /** 卡片上的次选项:用户想改点什么。 */
17
- export declare const PLAN_REVISE_LABEL = "\u6211\u8981\u4FEE\u6539";
18
+ /** 卡片上"确定开始"选项的固定标签;`intent.approve` 必须与它逐字一致。
19
+ *
20
+ * 文案与针锋(crossfire)保持一致:两个插件在"确认后真正开始"这一步给用户
21
+ * 的应当是同一句话,否则同一套流程在两个入口上看起来像两件事。 */
22
+ export declare const PLAN_APPROVE_LABEL = "\u786E\u5B9A\u5F00\u59CB";
23
+ /** 卡片上的次选项:用户想改点什么(改完会再弹一次同一张卡)。 */
24
+ export declare const PLAN_REVISE_LABEL = "\u4ECD\u9700\u4FEE\u6539";
25
+ /** 草案里的一位专家(尚未创建节点)。
26
+ *
27
+ * 与 `roundtable_add_node` 的入参对应:确认后主持人逐个照此拉节点,`prompt`
28
+ * 写进 `MeetingNode.expertPrompt`,由 `nodePersona()` 生成「专家补充定位」段。 */
29
+ export interface DraftExpert {
30
+ /** 专家唯一标识(会议内节点 key)。 */
31
+ key: string;
32
+ /** 角色说明,如"安全审查"。 */
33
+ role?: string;
34
+ /** 可选 LLM provider 路由;必须与 model 同时给出才生效。 */
35
+ provider?: string;
36
+ /** 可选模型名;空 = 继承主持人当前路由。 */
37
+ model?: string;
38
+ /** 专家补充定位(可选):只放宽"你的专长范围/关注点",
39
+ * **不能**放宽总纲红线(格式、编造、拍板边界)—— 硬边界由 persona 声明。 */
40
+ prompt?: string;
41
+ }
18
42
  /**
19
43
  * 一张设置卡片所描述的会议草案(尚未创建)。
20
44
  *
21
- * 字段与 `roundtable_create` 的入参一一对应,外加 `skills` / `skillDelivery`,
22
- * 这样用户确认后主持人可以**原样**按草案创建会议,不再自行拍参数。
45
+ * 字段与 `roundtable_create` 的入参一一对应,外加 `experts` / `skills` /
46
+ * `skillDelivery`,这样用户确认后主持人可以**原样**按草案创建会议与拉专家,
47
+ * 不再自行拍参数。
23
48
  */
24
49
  export interface MeetingDraft {
25
50
  /** 会议名称(同时作为会议 id 的稳定来源)。 */
@@ -38,6 +63,8 @@ export interface MeetingDraft {
38
63
  skills: string[];
39
64
  /** skill 传递方式:relay=主持人中转;direct=专家自行调用。 */
40
65
  skillDelivery: SkillDelivery;
66
+ /** 草案里的专家名单(可为空;空名单在卡片上给出可操作提示)。 */
67
+ experts: DraftExpert[];
41
68
  }
42
69
  /** 卡片选项的两态解析结果。 */
43
70
  export type PlanConfirmation = {
@@ -56,22 +83,25 @@ export interface SkillListEntry {
56
83
  readonly name: string;
57
84
  readonly description?: string;
58
85
  }
86
+ /** 主持人实际路由(用于把「继承主持人」画成用户看得懂的模型名)。 */
87
+ export interface InheritedRoute {
88
+ provider?: string;
89
+ model?: string;
90
+ }
59
91
  /**
60
92
  * 把一份草案渲染成卡片正文(Markdown)。
61
93
  *
62
- * 这里是"让用户看懂将要创建什么"的唯一位置:每一项参数都显示当前值,
63
- * 专家逐个列出 key / 角色 / 路由,并把**当前可选**的 skill 清单也列出来
64
- * (用户要在「我要修改」的自由文本里增删 skill,就得先看得到名字)。
65
- * 禁止在其中塞伪交互说明。
94
+ * 这里是"让用户看懂将要创建什么"的唯一位置:表 1 摊开会议参数,表 2 逐个
95
+ * 列出专家的 key / 角色 / 模型 / 提示词摘要,表格下方是完整目标,并把
96
+ * **当前可选**的 skill 清单也列出来(用户要在「仍需修改」的自由文本里增删
97
+ * skill,就得先看得到名字)。禁止在其中塞伪交互说明。
66
98
  */
67
- export declare function formatMeetingDraft(draft: MeetingDraft, experts: readonly {
68
- key: string;
69
- role?: string;
70
- provider?: string;
71
- model?: string;
72
- }[], options?: {
99
+ export declare function formatMeetingDraft(draft: MeetingDraft, options?: {
73
100
  revised?: boolean;
101
+ changes?: readonly string[];
74
102
  availableSkills?: readonly SkillListEntry[];
103
+ /** 主持人实际路由:把「继承主持人」补成「继承主持人(provider/model)」。 */
104
+ inheritedRoute?: InheritedRoute;
75
105
  }): string;
76
106
  /**
77
107
  * 解析用户的卡片回答。
@@ -51,6 +51,17 @@ export interface MeetingNode {
51
51
  * `spawnNode` into `agentOptions.reasoningEffort` and surfaced by
52
52
  * `roundtable_status`; absent = inherit the captain's route-owned effort. */
53
53
  reasoningEffort?: string;
54
+ /** 专家补充定位(可选,来自启动前确认卡 `experts[].prompt`;由
55
+ * `roundtable_add_node` 的 `prompt` 参数写入)。
56
+ *
57
+ * 语义:它只**放宽这位专家的专长范围/关注点**("只盯密钥轮换与权限边界"
58
+ * 这类)。硬边界:不能放宽《全局协作总纲》的红线(发言格式、严禁编造、
59
+ * 不替用户拍板),也不能改变只读/不产出方案等节点边界;与硬约束冲突的
60
+ * 部分一律视为无效。`nodePersona()` 会把这段文字以「专家补充定位」段注入
61
+ * persona,并在同一段里写明上述声明。
62
+ *
63
+ * 缺省(或全空白)= 与旧行为逐字一致:persona 里不出现该段,不多空行。 */
64
+ expertPrompt?: string;
54
65
  status: NodeStatus;
55
66
  joinedAt: number;
56
67
  /** 最近一次失败的可读说明(第 1 批)。子代理以 error 结束、或 provider 请求
@@ -9,13 +9,13 @@
9
9
  * @module dsh-plugin-roundtable/version
10
10
  */
11
11
  /** 当前插件版本(必须与 package.json 的 version 逐字一致)。 */
12
- export declare const PLUGIN_VERSION = "1.0.0-rc.6";
12
+ export declare const PLUGIN_VERSION = "1.0.0-rc.8";
13
13
  /** 导出物头部使用的插件标识行。 */
14
- export declare const PLUGIN_ID = "@y9931666/dsh-plugin-roundtable v1.0.0-rc.6";
14
+ export declare const PLUGIN_ID = "@y9931666/dsh-plugin-roundtable v1.0.0-rc.8";
15
15
  /** DeepSeek Harness 版本区间(导出头部展示用)。
16
16
  *
17
17
  * 必须与 `compatibility.json` 的 `recommendedHost` 一致 —— 由
18
18
  * `test/compatibility.test.mjs` 强制,`scripts/compatibility.mjs` 参与发布门禁。
19
19
  * 此前这里是硬编码字符串,与 devDependencies 一起漂到了 rc.2 而无人发现;
20
20
  * 0.2.0-rc.2 这次跨大版本升级同样由这两道闸门兜住。 */
21
- export declare const HARNESS_RANGE = "DeepSeek Harness 0.2.1-rc.5+";
21
+ export declare const HARNESS_RANGE = "DeepSeek Harness 0.2.2-rc.1+";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y9931666/dsh-plugin-roundtable",
3
- "version": "1.0.0-rc.6",
3
+ "version": "1.0.0-rc.8",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -68,7 +68,7 @@
68
68
  }
69
69
  },
70
70
  "roundtable": {
71
- "hostBaseline": "0.2.1-rc.5"
71
+ "hostBaseline": "0.2.2-rc.1"
72
72
  },
73
73
  "scripts": {
74
74
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
@@ -84,25 +84,25 @@
84
84
  "publish:guard": "node scripts/compatibility.mjs && node scripts/verify-package.mjs --strict && node scripts/release.mjs"
85
85
  },
86
86
  "peerDependencies": {
87
- "@deepseek-ai/cordis": "^4.0.5",
88
- "@deepseek-ai/schemastery": "^3.18.5",
89
- "@deepseek-ai/dsh-agent": "^0.2.1-rc.5",
90
- "@deepseek-ai/dsh-atomic-write": "^0.2.1-rc.5",
91
- "@deepseek-ai/dsh-client-connection": "^0.2.1-rc.5",
92
- "@deepseek-ai/dsh-client-locale": "^0.2.1-rc.5",
93
- "@deepseek-ai/dsh-client-ui-conversation": "^0.2.1-rc.5",
94
- "@deepseek-ai/dsh-client-ui-renderer": "^0.2.1-rc.5",
95
- "@deepseek-ai/dsh-client-ui-settings": "^0.2.1-rc.5",
96
- "@deepseek-ai/dsh-client-ui-tool": "^0.2.1-rc.5",
97
- "@deepseek-ai/dsh-host-webserver": "^0.2.1-rc.5",
98
- "@deepseek-ai/dsh-llm": "^0.2.1-rc.5",
99
- "@deepseek-ai/dsh-session": "^0.2.1-rc.5",
100
- "@deepseek-ai/dsh-skill": "^0.2.1-rc.5",
101
- "@deepseek-ai/dsh-subagent": "^0.2.1-rc.5",
102
- "@deepseek-ai/dsh-system-prompt": "^0.2.1-rc.5",
103
- "@deepseek-ai/dsh-tools": "^0.2.1-rc.5",
104
- "@deepseek-ai/dsh-user-questions": "^0.2.1-rc.5",
105
- "@deepseek-ai/dsh-workspace": "^0.2.1-rc.5",
87
+ "@deepseek-ai/cordis": "^4.1.0",
88
+ "@deepseek-ai/schemastery": "^3.19.0",
89
+ "@deepseek-ai/dsh-agent": "^0.2.2-rc.1",
90
+ "@deepseek-ai/dsh-atomic-write": "^0.2.2-rc.1",
91
+ "@deepseek-ai/dsh-client-connection": "^0.2.2-rc.1",
92
+ "@deepseek-ai/dsh-client-locale": "^0.2.2-rc.1",
93
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.2.2-rc.1",
94
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.2.2-rc.1",
95
+ "@deepseek-ai/dsh-client-ui-settings": "^0.2.2-rc.1",
96
+ "@deepseek-ai/dsh-client-ui-tool": "^0.2.2-rc.1",
97
+ "@deepseek-ai/dsh-host-webserver": "^0.2.2-rc.1",
98
+ "@deepseek-ai/dsh-llm": "^0.2.2-rc.1",
99
+ "@deepseek-ai/dsh-session": "^0.2.2-rc.1",
100
+ "@deepseek-ai/dsh-skill": "^0.2.2-rc.1",
101
+ "@deepseek-ai/dsh-subagent": "^0.2.2-rc.1",
102
+ "@deepseek-ai/dsh-system-prompt": "^0.2.2-rc.1",
103
+ "@deepseek-ai/dsh-tools": "^0.2.2-rc.1",
104
+ "@deepseek-ai/dsh-user-questions": "^0.2.2-rc.1",
105
+ "@deepseek-ai/dsh-workspace": "^0.2.2-rc.1",
106
106
  "react": "^18.2.0"
107
107
  },
108
108
  "peerDependenciesMeta": {
@@ -128,25 +128,25 @@
128
128
  "react": { "optional": true }
129
129
  },
130
130
  "devDependencies": {
131
- "@deepseek-ai/cordis": "4.0.5",
132
- "@deepseek-ai/schemastery": "3.18.5",
133
- "@deepseek-ai/dsh-agent": "0.2.1-rc.5",
134
- "@deepseek-ai/dsh-atomic-write": "0.2.1-rc.5",
135
- "@deepseek-ai/dsh-client-connection": "0.2.1-rc.5",
136
- "@deepseek-ai/dsh-client-locale": "0.2.1-rc.5",
137
- "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.5",
138
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
139
- "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.5",
140
- "@deepseek-ai/dsh-client-ui-tool": "0.2.1-rc.5",
141
- "@deepseek-ai/dsh-host-webserver": "0.2.1-rc.5",
142
- "@deepseek-ai/dsh-llm": "0.2.1-rc.5",
143
- "@deepseek-ai/dsh-session": "0.2.1-rc.5",
144
- "@deepseek-ai/dsh-skill": "0.2.1-rc.5",
145
- "@deepseek-ai/dsh-subagent": "0.2.1-rc.5",
146
- "@deepseek-ai/dsh-system-prompt": "0.2.1-rc.5",
147
- "@deepseek-ai/dsh-tools": "0.2.1-rc.5",
148
- "@deepseek-ai/dsh-user-questions": "0.2.1-rc.5",
149
- "@deepseek-ai/dsh-workspace": "0.2.1-rc.5",
131
+ "@deepseek-ai/cordis": "4.1.0",
132
+ "@deepseek-ai/schemastery": "3.19.0",
133
+ "@deepseek-ai/dsh-agent": "0.2.2-rc.1",
134
+ "@deepseek-ai/dsh-atomic-write": "0.2.2-rc.1",
135
+ "@deepseek-ai/dsh-client-connection": "0.2.2-rc.1",
136
+ "@deepseek-ai/dsh-client-locale": "0.2.2-rc.1",
137
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.2-rc.1",
138
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.2-rc.1",
139
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.2-rc.1",
140
+ "@deepseek-ai/dsh-client-ui-tool": "0.2.2-rc.1",
141
+ "@deepseek-ai/dsh-host-webserver": "0.2.2-rc.1",
142
+ "@deepseek-ai/dsh-llm": "0.2.2-rc.1",
143
+ "@deepseek-ai/dsh-session": "0.2.2-rc.1",
144
+ "@deepseek-ai/dsh-skill": "0.2.2-rc.1",
145
+ "@deepseek-ai/dsh-subagent": "0.2.2-rc.1",
146
+ "@deepseek-ai/dsh-system-prompt": "0.2.2-rc.1",
147
+ "@deepseek-ai/dsh-tools": "0.2.2-rc.1",
148
+ "@deepseek-ai/dsh-user-questions": "0.2.2-rc.1",
149
+ "@deepseek-ai/dsh-workspace": "0.2.2-rc.1",
150
150
  "@types/node": "^22.0.0",
151
151
  "@types/react": "~18.3.1",
152
152
  "@types/react-dom": "^18.3.7",
@@ -0,0 +1,46 @@
1
+ # v1.0.0-rc.8 — 启动前确认卡表格化 + 专家提示词
2
+
3
+ 本版只改「开会前那张确认卡」与专家的提示词入口,不改会议协议、不改进程/拓扑语义、不改数据格式(老会议可直接打开)。
4
+
5
+ ## 确认卡:从列表改成两张表
6
+
7
+ 此前卡片是「- 字段:值」的逐行列表,专家只有 key / 角色 / 路由三样。现在固定为两张 Markdown 表:
8
+
9
+ - **表 1 会议设置**:会议名称、协作模式、轮数上限、Token 预算、知识库、选中 skill、skill 传递方式、专家数量。
10
+ - **表 2 专家名单**:`# / 专家 key / 角色 / 模型 / 提示词` 五列,一眼能横向对照。
11
+
12
+ 配套细节:
13
+
14
+ - **继承路由显示具体模型**:专家没写 `provider/model` 时,卡片显示「继承主持人(deepseek/…)」而不是只有「继承主持人」——用户看得到继承的到底是哪个模型。
15
+ - **提示词摘要**:有值时显示前 40 字(只有真被截断才补「…」),没值时写「自动(总纲 + 角色)」,不假装有内容。
16
+ - **单元格转义**:值里的裸 `|` 与换行会被转义/压平,表不会错列。
17
+ - **修改回显**:再次弹卡时,标题标「(已按你的意见更新)」,顶部列出「本次变更」;主持人没给变更点就如实写「(本次未标注具体变更)」,不假装有改动。
18
+
19
+ ## 专家提示词真正生效
20
+
21
+ - `roundtable_plan_meeting` 的 `experts[]` 新增 `prompt`(可选),确认后由主持人原样传给 `roundtable_add_node` 的新参数 `prompt`。
22
+ - 该提示词写入 `MeetingNode.expertPrompt`,由 `nodePersona()` 生成「专家补充定位」段。
23
+ - **硬边界**:它只放宽这位专家的专长范围/关注点;不能放宽《全局协作总纲》的红线(发言格式、严禁编造、不替用户拍板),冲突部分视为无效。这样确认卡不会变成绕过红线的后门。
24
+ - 未填提示词时 persona 与旧版**逐字一致**(不多空行),老记录不受影响。
25
+
26
+ ## 按钮文案
27
+
28
+ - 统一为「**确定开始**」/「**仍需修改**」,与针锋的确认卡保持同一套说法。
29
+
30
+ ## 兼容性
31
+
32
+ - 不传 `prompt` / `changes` 时行为与 rc.7 一致。
33
+ - `formatMeetingDraft` 的签名变为 `(draft, options)`,`experts` 移入 `MeetingDraft`;全仓仅工具层与测试使用,client 未引用。
34
+ - Web UI 右栏「添加专家」的 user-action 暂不含 `prompt` 字段(下一版再补)。
35
+
36
+ ## 验证
37
+
38
+ - `compatibility.mjs` 通过(43 项检查)。
39
+ - `tsc`(host / client)通过。
40
+ - 全量测试 **163 / 163 通过**(新增表格、变更回显、转义、提示词摘要、继承路由、persona 硬边界等用例)。
41
+ - `tsdown` 构建通过。
42
+
43
+ ## 发布
44
+
45
+ - npm 预发布轨:`npm publish --tag next`
46
+ - 安装/升级:`npm i @y9931666/dsh-plugin-roundtable@next`