@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 +12 -3
- package/lib/index.js +232 -63
- package/lib/types/plan.d.ts +46 -16
- package/lib/types/types.d.ts +11 -0
- package/lib/types/version.d.ts +3 -3
- package/package.json +40 -40
- package/release-notes/v1.0.0-rc.8.md +46 -0
package/README.md
CHANGED
|
@@ -62,10 +62,19 @@
|
|
|
62
62
|
| 能力 | 说明 |
|
|
63
63
|
| --- | --- |
|
|
64
64
|
| **开会前先确认设置** | 每次开会(哪怕只有一位专家)都会先弹一张**设置卡片**:专家名单 + 协作模式 + 轮数/Token 预算 + 知识库 + 选中 skill。主持人只出草案,**确认后才创建会议**。 |
|
|
65
|
-
| **卡片上即可初次修改** |
|
|
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` |
|
|
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.
|
|
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.
|
|
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
|
|
1144
|
-
if (line
|
|
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
|
|
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
|
-
/** 卡片上"
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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
|
-
*
|
|
1191
|
-
*
|
|
1192
|
-
*
|
|
1279
|
+
* 这里是"让用户看懂将要创建什么"的唯一位置:表 1 摊开会议参数,表 2 逐个
|
|
1280
|
+
* 列出专家的 key / 角色 / 模型 / 提示词摘要,表格下方是完整目标,并把
|
|
1281
|
+
* **当前可选**的 skill 清单也列出来(用户要在「仍需修改」的自由文本里增删
|
|
1282
|
+
* skill,就得先看得到名字)。禁止在其中塞伪交互说明。
|
|
1193
1283
|
*/
|
|
1194
|
-
function formatMeetingDraft(draft,
|
|
1195
|
-
const
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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
|
|
1307
|
+
`# 会议设置确认 · ${cellOr(draft.name, "(未命名)")}${revised ? "(已按你的意见更新)" : ""}`,
|
|
1308
|
+
"",
|
|
1309
|
+
revised ? formatChanges(options.changes ?? []) : "> 以下是主持人推导的会议草案。**确认后才会真正创建会议。**",
|
|
1205
1310
|
"",
|
|
1206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
1465
|
-
return formatMeetingDraft(draft,
|
|
1579
|
+
function renderPlanCard(draft, available, options) {
|
|
1580
|
+
return formatMeetingDraft(draft, {
|
|
1466
1581
|
revised: options.revised,
|
|
1467
|
-
|
|
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 (
|
|
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}
|
|
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
|
|
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: (
|
|
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
|
|
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
|
|
2973
|
+
content: line.content,
|
|
2805
2974
|
status: "pending",
|
|
2806
|
-
quote: line
|
|
2807
|
-
dimension: line
|
|
2808
|
-
evidence: line
|
|
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
|
|
3171
|
-
return line
|
|
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 : [];
|
package/lib/types/plan.d.ts
CHANGED
|
@@ -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
|
-
/** 卡片上"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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` 的入参一一对应,外加 `
|
|
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
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
94
|
+
* 这里是"让用户看懂将要创建什么"的唯一位置:表 1 摊开会议参数,表 2 逐个
|
|
95
|
+
* 列出专家的 key / 角色 / 模型 / 提示词摘要,表格下方是完整目标,并把
|
|
96
|
+
* **当前可选**的 skill 清单也列出来(用户要在「仍需修改」的自由文本里增删
|
|
97
|
+
* skill,就得先看得到名字)。禁止在其中塞伪交互说明。
|
|
66
98
|
*/
|
|
67
|
-
export declare function formatMeetingDraft(draft: MeetingDraft,
|
|
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
|
* 解析用户的卡片回答。
|
package/lib/types/types.d.ts
CHANGED
|
@@ -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 请求
|
package/lib/types/version.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
88
|
-
"@deepseek-ai/schemastery": "^3.
|
|
89
|
-
"@deepseek-ai/dsh-agent": "^0.2.
|
|
90
|
-
"@deepseek-ai/dsh-atomic-write": "^0.2.
|
|
91
|
-
"@deepseek-ai/dsh-client-connection": "^0.2.
|
|
92
|
-
"@deepseek-ai/dsh-client-locale": "^0.2.
|
|
93
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.2.
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.2.
|
|
95
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.2.
|
|
96
|
-
"@deepseek-ai/dsh-client-ui-tool": "^0.2.
|
|
97
|
-
"@deepseek-ai/dsh-host-webserver": "^0.2.
|
|
98
|
-
"@deepseek-ai/dsh-llm": "^0.2.
|
|
99
|
-
"@deepseek-ai/dsh-session": "^0.2.
|
|
100
|
-
"@deepseek-ai/dsh-skill": "^0.2.
|
|
101
|
-
"@deepseek-ai/dsh-subagent": "^0.2.
|
|
102
|
-
"@deepseek-ai/dsh-system-prompt": "^0.2.
|
|
103
|
-
"@deepseek-ai/dsh-tools": "^0.2.
|
|
104
|
-
"@deepseek-ai/dsh-user-questions": "^0.2.
|
|
105
|
-
"@deepseek-ai/dsh-workspace": "^0.2.
|
|
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
|
|
132
|
-
"@deepseek-ai/schemastery": "3.
|
|
133
|
-
"@deepseek-ai/dsh-agent": "0.2.
|
|
134
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.
|
|
135
|
-
"@deepseek-ai/dsh-client-connection": "0.2.
|
|
136
|
-
"@deepseek-ai/dsh-client-locale": "0.2.
|
|
137
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.2.
|
|
138
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.
|
|
139
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.
|
|
140
|
-
"@deepseek-ai/dsh-client-ui-tool": "0.2.
|
|
141
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.
|
|
142
|
-
"@deepseek-ai/dsh-llm": "0.2.
|
|
143
|
-
"@deepseek-ai/dsh-session": "0.2.
|
|
144
|
-
"@deepseek-ai/dsh-skill": "0.2.
|
|
145
|
-
"@deepseek-ai/dsh-subagent": "0.2.
|
|
146
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.
|
|
147
|
-
"@deepseek-ai/dsh-tools": "0.2.
|
|
148
|
-
"@deepseek-ai/dsh-user-questions": "0.2.
|
|
149
|
-
"@deepseek-ai/dsh-workspace": "0.2.
|
|
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`
|