@roaming-ai/dsh-group-chat 0.3.0 → 0.3.1

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.
Files changed (66) hide show
  1. package/README.md +89 -26
  2. package/lib/client.js +289 -321
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +124 -132
  5. package/lib/types/client/components/AsidePanel.d.ts +3 -19
  6. package/lib/types/client/components/NavPanel.d.ts +9 -11
  7. package/lib/types/client/components/RoleDrawer.d.ts +3 -2
  8. package/lib/types/client/components/ToolRow.d.ts +2 -2
  9. package/lib/types/client/hooks/useGroupChatState.d.ts +0 -16
  10. package/lib/types/client/lib/model.d.ts +1 -7
  11. package/lib/types/client/utils/utils.d.ts +4 -4
  12. package/lib/types/core/json.d.ts +6 -0
  13. package/lib/types/core/types.d.ts +57 -63
  14. package/lib/types/host/engine/defaults.d.ts +13 -0
  15. package/lib/types/shared/file-mention-grammar.d.ts +3 -12
  16. package/package.json +1 -1
  17. package/src/client/GroupChatPanel.tsx +7 -56
  18. package/src/client/components/AsidePanel.tsx +40 -15
  19. package/src/client/components/ConstraintList.tsx +1 -6
  20. package/src/client/components/MessageFlow.tsx +2 -1
  21. package/src/client/components/NavPanel.tsx +96 -78
  22. package/src/client/components/RoleDrawer.tsx +29 -7
  23. package/src/client/components/ToolRow.tsx +2 -2
  24. package/src/client/hooks/useComposer.ts +14 -53
  25. package/src/client/hooks/useGroupChatState.ts +2 -13
  26. package/src/client/lib/model.ts +1 -14
  27. package/src/client/utils/utils.ts +6 -6
  28. package/src/core/constraints.ts +6 -12
  29. package/src/core/errors.ts +2 -12
  30. package/src/core/json.ts +17 -0
  31. package/src/core/types.ts +27 -19
  32. package/src/host/api/actions.ts +14 -13
  33. package/src/host/engine/conversation.ts +49 -50
  34. package/src/host/engine/defaults.ts +28 -0
  35. package/src/host/engine/fold.ts +2 -17
  36. package/src/host/engine/retitle.ts +9 -31
  37. package/src/host/materials/materials.ts +2 -0
  38. package/src/host/tools/tools.ts +2 -2
  39. package/src/shared/file-mention-grammar.test.ts +1 -38
  40. package/src/shared/file-mention-grammar.ts +7 -33
  41. package/lib/types/client/Bubble.d.ts +0 -10
  42. package/lib/types/client/Glyph.d.ts +0 -12
  43. package/lib/types/client/GroupChatPanel.old.d.ts +0 -10
  44. package/lib/types/client/GroupChatSettingsSection.d.ts +0 -15
  45. package/lib/types/client/PermissionSelect.d.ts +0 -13
  46. package/lib/types/client/RoleDrawer.d.ts +0 -17
  47. package/lib/types/client/ThinkRow.d.ts +0 -9
  48. package/lib/types/client/ToolRow.d.ts +0 -9
  49. package/lib/types/client/api.d.ts +0 -8
  50. package/lib/types/client/components.d.ts +0 -22
  51. package/lib/types/client/drawer.d.ts +0 -17
  52. package/lib/types/client/glyph.d.ts +0 -12
  53. package/lib/types/client/model.d.ts +0 -78
  54. package/lib/types/client/panel.d.ts +0 -10
  55. package/lib/types/client/settings.d.ts +0 -15
  56. package/lib/types/client/styles.d.ts +0 -10
  57. package/lib/types/client/ui.d.ts +0 -17
  58. package/lib/types/client/utils.d.ts +0 -22
  59. package/lib/types/host/actions.d.ts +0 -34
  60. package/lib/types/host/conversation.d.ts +0 -25
  61. package/lib/types/host/http.d.ts +0 -16
  62. package/lib/types/host/materials.d.ts +0 -32
  63. package/lib/types/host/persistence.d.ts +0 -31
  64. package/lib/types/host/routes.d.ts +0 -11
  65. package/lib/types/host/store.d.ts +0 -65
  66. package/lib/types/host/tools.d.ts +0 -28
package/lib/index.js CHANGED
@@ -46,6 +46,59 @@ function mountOnce(packageName, fn) {
46
46
  });
47
47
  }
48
48
  //#endregion
49
+ //#region src/core/json.ts
50
+ /** 消息 → 落盘 JSON(可选字段无则省略;兼容保留 error)。 */
51
+ function messageJson(m) {
52
+ if (!m || typeof m.id !== "string") return null;
53
+ const o = {
54
+ id: m.id,
55
+ speaker: m.speaker,
56
+ text: String(m.text || ""),
57
+ ts: typeof m.ts === "number" ? m.ts : Date.now(),
58
+ seq: typeof m.seq === "number" ? m.seq : 0
59
+ };
60
+ if (m.model !== void 0) o.model = m.model;
61
+ if (m.reasoning !== void 0) o.reasoning = m.reasoning;
62
+ if (m.reasoningFull !== void 0) o.reasoningFull = m.reasoningFull;
63
+ if (m.thinkingSummary !== void 0) o.thinkingSummary = m.thinkingSummary;
64
+ if (m.error !== void 0) o.error = m.error;
65
+ if (typeof m.failedRoleId === "string" && m.failedRoleId) o.failedRoleId = m.failedRoleId;
66
+ if (Array.isArray(m.toolCalls) && m.toolCalls.length > 0) o.toolCalls = m.toolCalls;
67
+ return o;
68
+ }
69
+ /** 角色 → 落盘 JSON(groupId 由目录归属,不再写入)。 */
70
+ function roleJson(r) {
71
+ const o = {
72
+ id: r.id,
73
+ name: String(r.name || "成员"),
74
+ persona: String(r.persona || ""),
75
+ provider: String(r.provider || ""),
76
+ model: String(r.model || ""),
77
+ enabled: r.enabled !== false,
78
+ thinking: r.thinking === true
79
+ };
80
+ if (r.color) o.color = r.color;
81
+ if (typeof r.temperature === "number" && !Number.isNaN(r.temperature)) o.temperature = r.temperature;
82
+ if (typeof r.reasoningEffort === "string" && r.reasoningEffort && r.reasoningEffort !== "default") o.reasoningEffort = r.reasoningEffort;
83
+ return o;
84
+ }
85
+ /**
86
+ * 宽容 JSON 提取(模型输出/供应商错误原文的统一解析入口):
87
+ * 取首个 { 至末个 } 的片段解析,仅接受普通对象;失败返回 null。
88
+ * 代码围栏剥离由调用方按需先行处理(供应商错误原文不剥)。
89
+ */
90
+ function looseJson(raw) {
91
+ const start = raw.indexOf("{");
92
+ const end = raw.lastIndexOf("}");
93
+ if (start < 0 || end <= start) return null;
94
+ try {
95
+ const parsed = JSON.parse(raw.slice(start, end + 1));
96
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+ //#endregion
49
102
  //#region src/core/errors.ts
50
103
  const PREFIX = "模型输出异常终止: ";
51
104
  const LEGACY_ROLE = /^角色「([^」]+)」发言失败[::]\s*/;
@@ -226,7 +279,7 @@ function createActions(core, deps) {
226
279
  const { llm, groups, sessions, roles, messages, run } = core;
227
280
  const { touch, snapshot, schedulePersist, dropDirty, appendMessage, runLoop, wakeConfirm, killChild, browse, fileSearch, disposeFileSearch } = deps;
228
281
  const mutate = (args) => {
229
- const op = args && args.op;
282
+ const op = args.op;
230
283
  if (op === "createGroup") {
231
284
  const g = {
232
285
  id: core.nid("grp"),
@@ -1117,18 +1170,11 @@ function sanitizeConstraints(raw) {
1117
1170
  * 解析折叠模型输出。null = 解析失败(水位不推);[] = 无新结论(水位推、备忘不动)。
1118
1171
  */
1119
1172
  function parseConstraints(raw) {
1120
- const body = raw.replace(/```(?:json)?/g, "");
1121
- const l = body.indexOf("{");
1122
- const r = body.lastIndexOf("}");
1123
- if (l < 0 || r <= l) return null;
1124
- try {
1125
- const o = JSON.parse(body.slice(l, r + 1));
1126
- if (!Object.prototype.hasOwnProperty.call(o, "constraints")) return null;
1127
- if (!Array.isArray(o.constraints)) return null;
1128
- return sanitizeConstraints(o.constraints);
1129
- } catch {
1130
- return null;
1131
- }
1173
+ const o = looseJson(raw.replace(/```(?:json)?/g, ""));
1174
+ if (o === null) return null;
1175
+ if (!Object.prototype.hasOwnProperty.call(o, "constraints")) return null;
1176
+ if (!Array.isArray(o.constraints)) return null;
1177
+ return sanitizeConstraints(o.constraints);
1132
1178
  }
1133
1179
  /** 本批无用户消息时,新的已定/否决降为未决。 */
1134
1180
  function downgradeWithoutUser(list, hasUser) {
@@ -1144,9 +1190,13 @@ function constraintBlock(list) {
1144
1190
  return "\n# 已确认约束\n" + list.map((c) => "- " + KIND_LABEL[c.kind] + ":" + c.text).join("\n");
1145
1191
  }
1146
1192
  //#endregion
1147
- //#region src/host/engine/fold.ts
1148
- /** DSH 默认模型(与 retitle 同一读取面;缺位返回 null)。 */
1149
- const defaultModel$1 = (core) => {
1193
+ //#region src/host/engine/defaults.ts
1194
+ /**
1195
+ * 引擎共享小件:DSH 默认模型读取 + 发言人展示名(retitle / fold / conversation 共用)。
1196
+ * @module dsh-group-chat/host/engine/defaults
1197
+ */
1198
+ /** DSH 默认模型(agentDefaultModel 服务缺位或未配置时返回 null,调用方静默跳过)。 */
1199
+ const defaultModel = (core) => {
1150
1200
  try {
1151
1201
  const svc = core.ctx.reflect.get("agentDefaultModel");
1152
1202
  const sel = svc ? svc.currentSelection() : null;
@@ -1158,6 +1208,10 @@ const defaultModel$1 = (core) => {
1158
1208
  return null;
1159
1209
  }
1160
1210
  };
1211
+ /** 发言人展示名(roles 表内查角色名;user/system 固定文案,见 speakerLabel)。 */
1212
+ const speakerNameOf = (roles, speaker) => speakerLabel(speaker, roles.get(speaker)?.name);
1213
+ //#endregion
1214
+ //#region src/host/engine/fold.ts
1161
1215
  /**
1162
1216
  * 每轮 send 结束后折叠窗口外约束:fire-and-forget、不产生消息、静默失败。
1163
1217
  * 同会话去重;会话已清空则 abort。内存 {constraints, constraintsUpToSeq} 一次挂上。
@@ -1166,12 +1220,11 @@ function createFold(core, deps) {
1166
1220
  const { llm, messages, roles } = core;
1167
1221
  const { touch, schedulePersist } = deps;
1168
1222
  const folding = /* @__PURE__ */ new Set();
1169
- const nameOf = (speaker) => speakerLabel(speaker, (roles.get(speaker) || { name: void 0 }).name);
1170
1223
  return async (sess) => {
1171
1224
  if (folding.has(sess.id)) return;
1172
1225
  const squeezed = squeezedMessages(messages, sess);
1173
1226
  if (!squeezed.length) return;
1174
- const input = takeFoldBatch(squeezed, (m) => nameOf(m.speaker));
1227
+ const input = takeFoldBatch(squeezed, (m) => speakerNameOf(roles, m.speaker));
1175
1228
  const watermark = squeezedMaxSeq(input.consumed);
1176
1229
  if (watermark <= 0) return;
1177
1230
  const commit = (next) => {
@@ -1186,7 +1239,7 @@ function createFold(core, deps) {
1186
1239
  commit(void 0);
1187
1240
  return;
1188
1241
  }
1189
- const dm = defaultModel$1(core);
1242
+ const dm = defaultModel(core);
1190
1243
  if (!dm) return;
1191
1244
  folding.add(sess.id);
1192
1245
  try {
@@ -1240,47 +1293,26 @@ function createFold(core, deps) {
1240
1293
  //#region src/host/engine/retitle.ts
1241
1294
  /** 仍是新建占位名(含改名之前的「会话 N」存量),自动标题尚未落地。 */
1242
1295
  const isPlaceholderName = (name) => name === "新会话" || /^会话 \d+$/.test(name);
1243
- /** DSH 默认模型(agentDefaultModel 服务缺位或未配置时返回 null,调用方静默跳过)。 */
1244
- const defaultModel = (core) => {
1245
- try {
1246
- const svc = core.ctx.reflect.get("agentDefaultModel");
1247
- const sel = svc ? svc.currentSelection() : null;
1248
- return sel && typeof sel.provider === "string" && typeof sel.model === "string" && sel.provider && sel.model ? {
1249
- provider: sel.provider,
1250
- model: sel.model
1251
- } : null;
1252
- } catch {
1253
- return null;
1254
- }
1255
- };
1256
1296
  /** 命名输入:最近 40 条非系统消息的紧凑转写(每条 500 字符封顶,命名不需要全文)。 */
1257
1297
  const titleTranscript = (core, sess) => {
1258
1298
  const out = [];
1259
1299
  for (const mid of sess.messageIds.slice(-40)) {
1260
1300
  const m = core.messages.get(mid);
1261
1301
  if (!m || m.speaker === "system" || m.error || !m.text) continue;
1262
- const name = m.speaker === "user" ? "用户" : (core.roles.get(m.speaker) || { name: void 0 }).name || "成员";
1263
- out.push("【" + name + "】" + m.text.replace(/\s+/g, " ").slice(0, 500));
1302
+ out.push("" + speakerNameOf(core.roles, m.speaker) + "】" + m.text.replace(/\s+/g, " ").slice(0, 500));
1264
1303
  }
1265
1304
  return out.join("\n");
1266
1305
  };
1267
- /** 宽容解析模型输出:剥代码围栏 → 取首个 { 至末个 } 的 JSON → 校验并封顶字段。 */
1306
+ /** 宽容解析模型输出:剥代码围栏 → looseJson → 校验并封顶字段。 */
1268
1307
  const parseRetitle = (raw) => {
1269
- const body = raw.replace(/```(?:json)?/g, "");
1270
- const l = body.indexOf("{");
1271
- const r = body.lastIndexOf("}");
1272
- if (l < 0 || r <= l) return {};
1273
- try {
1274
- const o = JSON.parse(body.slice(l, r + 1));
1275
- const name = typeof o.name === "string" ? o.name.trim() : "";
1276
- const topic = typeof o.topic === "string" ? o.topic.trim() : "";
1277
- return {
1278
- name: name ? name.slice(0, 24) : void 0,
1279
- topic: topic ? topic.slice(0, 120) : void 0
1280
- };
1281
- } catch {
1282
- return {};
1283
- }
1308
+ const o = looseJson(raw.replace(/```(?:json)?/g, ""));
1309
+ if (!o) return {};
1310
+ const name = typeof o.name === "string" ? o.name.trim() : "";
1311
+ const topic = typeof o.topic === "string" ? o.topic.trim() : "";
1312
+ return {
1313
+ name: name ? name.slice(0, 24) : void 0,
1314
+ topic: topic ? topic.slice(0, 120) : void 0
1315
+ };
1284
1316
  };
1285
1317
  /**
1286
1318
  * 每轮结束后根据聊天内容整理会话名称与主题:后台 fire-and-forget、不产生
@@ -1382,7 +1414,6 @@ function createConversation(core, deps) {
1382
1414
  touch,
1383
1415
  schedulePersist
1384
1416
  });
1385
- const nameOf = (speaker) => speakerLabel(speaker, (roles.get(speaker) || { name: void 0 }).name);
1386
1417
  const appendMessage = (sess, speaker, text, extra) => {
1387
1418
  const msg = {
1388
1419
  id: core.nid("msg"),
@@ -1410,60 +1441,56 @@ function createConversation(core, deps) {
1410
1441
  schedulePersist({ session: sess.id });
1411
1442
  return msg;
1412
1443
  };
1444
+ /** 原地覆盖既有消息(重试槽位);目标不存在或不属于本会话时返回 false,由调用方走追加。 */
1445
+ const overwriteMessage = (sess, replaceId, patch) => {
1446
+ if (!replaceId) return false;
1447
+ const existing = messages.get(replaceId);
1448
+ if (!existing || existing.sessionId !== sess.id) return false;
1449
+ Object.assign(existing, patch, { ts: Date.now() });
1450
+ touch();
1451
+ schedulePersist({ session: sess.id });
1452
+ return true;
1453
+ };
1413
1454
  /** 把失败回合写成该角色的消息(speaker = 角色 id),不再用系统胶囊顶替。 */
1414
1455
  const writeFailure = (sess, role, err, replaceId) => {
1415
1456
  const raw = unwrapSpeakFailure(String(err && err.message || err));
1457
+ const model = role.provider + " / " + role.model;
1416
1458
  const extra = {
1417
1459
  error: true,
1418
1460
  failedRoleId: role.id,
1419
- model: role.provider + " / " + role.model
1461
+ model
1420
1462
  };
1421
- if (replaceId) {
1422
- const existing = messages.get(replaceId);
1423
- if (existing && existing.sessionId === sess.id) {
1424
- existing.speaker = role.id;
1425
- existing.text = raw;
1426
- existing.error = true;
1427
- existing.failedRoleId = role.id;
1428
- existing.model = extra.model;
1429
- existing.reasoning = void 0;
1430
- existing.reasoningFull = void 0;
1431
- existing.thinkingSummary = void 0;
1432
- existing.toolCalls = void 0;
1433
- existing.ts = Date.now();
1434
- touch();
1435
- schedulePersist({ session: sess.id });
1436
- return;
1437
- }
1438
- }
1463
+ if (overwriteMessage(sess, replaceId, {
1464
+ speaker: role.id,
1465
+ text: raw,
1466
+ ...extra,
1467
+ reasoning: void 0,
1468
+ reasoningFull: void 0,
1469
+ thinkingSummary: void 0,
1470
+ toolCalls: void 0
1471
+ })) return;
1439
1472
  appendMessage(sess, role.id, raw, extra);
1440
1473
  };
1441
1474
  /** 成功发言写入:replaceId 存在则原地覆盖失败卡。 */
1442
1475
  const writeSuccess = (sess, role, out, replaceId) => {
1443
- const extra = {
1444
- model: role.provider + " / " + role.model,
1476
+ const model = role.provider + " / " + role.model;
1477
+ const toolCalls = Array.isArray(out.toolCalls) && out.toolCalls.length > 0 ? out.toolCalls : void 0;
1478
+ if (overwriteMessage(sess, replaceId, {
1479
+ speaker: role.id,
1480
+ text: out.text,
1481
+ error: void 0,
1482
+ failedRoleId: void 0,
1483
+ model,
1484
+ reasoning: out.reasoning,
1485
+ reasoningFull: void 0,
1486
+ thinkingSummary: void 0,
1487
+ toolCalls
1488
+ })) return;
1489
+ appendMessage(sess, role.id, out.text, {
1490
+ model,
1445
1491
  reasoning: out.reasoning,
1446
1492
  toolCalls: out.toolCalls
1447
- };
1448
- if (replaceId) {
1449
- const existing = messages.get(replaceId);
1450
- if (existing && existing.sessionId === sess.id) {
1451
- existing.speaker = role.id;
1452
- existing.text = out.text;
1453
- existing.error = void 0;
1454
- existing.failedRoleId = void 0;
1455
- existing.model = extra.model;
1456
- existing.reasoning = out.reasoning;
1457
- existing.reasoningFull = void 0;
1458
- existing.thinkingSummary = void 0;
1459
- existing.toolCalls = Array.isArray(out.toolCalls) && out.toolCalls.length > 0 ? out.toolCalls : void 0;
1460
- existing.ts = Date.now();
1461
- touch();
1462
- schedulePersist({ session: sess.id });
1463
- return;
1464
- }
1465
- }
1466
- appendMessage(sess, role.id, out.text, extra);
1493
+ });
1467
1494
  };
1468
1495
  /** 群聊记录 → 角色上下文块(最近 40 条 + 未折入临时原文)。失败卡不进上下文。重试截到该条之前。 */
1469
1496
  const transcriptBlock = (sess, skipId) => {
@@ -1471,10 +1498,10 @@ function createConversation(core, deps) {
1471
1498
  for (const mid of prefixIds(sess.messageIds, skipId).slice(-40)) {
1472
1499
  const m = messages.get(mid);
1473
1500
  if (!m || m.id === skipId || m.error) continue;
1474
- out.push(formatTranscriptLine(m, nameOf(m.speaker)));
1501
+ out.push(formatTranscriptLine(m, speakerNameOf(roles, m.speaker)));
1475
1502
  }
1476
1503
  const live = out.join("\n\n");
1477
- const temp = tempTranscript(squeezedMessages(messages, sess, skipId), (m) => nameOf(m.speaker));
1504
+ const temp = tempTranscript(squeezedMessages(messages, sess, skipId), (m) => speakerNameOf(roles, m.speaker));
1478
1505
  if (!temp) return live;
1479
1506
  if (!live) return temp;
1480
1507
  return temp + "\n\n" + live;
@@ -1671,6 +1698,7 @@ function createConversation(core, deps) {
1671
1698
  });
1672
1699
  for (const tc of round.tcs) {
1673
1700
  if (run.stopping) break;
1701
+ if (wsRoot === null) break;
1674
1702
  const res = await tools.executeTool(g, wsRoot, tc);
1675
1703
  const output = String(res.output || "");
1676
1704
  toolCalls.push({
@@ -1718,12 +1746,13 @@ function createConversation(core, deps) {
1718
1746
  };
1719
1747
  };
1720
1748
  const runLoop = async (sess, opts) => {
1721
- const g = groups.get(sess.groupId);
1749
+ const g = groups.get(sess.groupId) ?? null;
1722
1750
  const startCount = sess.messageIds.length;
1723
1751
  let failed = false;
1724
1752
  let replaceId = opts && opts.replaceMessageId;
1725
1753
  run.replaceMessageId = replaceId || null;
1726
1754
  try {
1755
+ if (!g) return;
1727
1756
  while (run.queue.length > 0 && !run.stopping) {
1728
1757
  const roleId = run.queue.shift();
1729
1758
  const role = roles.get(roleId);
@@ -2049,43 +2078,6 @@ function createMaterials(core) {
2049
2078
  };
2050
2079
  }
2051
2080
  //#endregion
2052
- //#region src/core/json.ts
2053
- /** 消息 → 落盘 JSON(可选字段无则省略;兼容保留 error)。 */
2054
- function messageJson(m) {
2055
- if (!m || typeof m.id !== "string") return null;
2056
- const o = {
2057
- id: m.id,
2058
- speaker: m.speaker,
2059
- text: String(m.text || ""),
2060
- ts: typeof m.ts === "number" ? m.ts : Date.now(),
2061
- seq: typeof m.seq === "number" ? m.seq : 0
2062
- };
2063
- if (m.model !== void 0) o.model = m.model;
2064
- if (m.reasoning !== void 0) o.reasoning = m.reasoning;
2065
- if (m.reasoningFull !== void 0) o.reasoningFull = m.reasoningFull;
2066
- if (m.thinkingSummary !== void 0) o.thinkingSummary = m.thinkingSummary;
2067
- if (m.error !== void 0) o.error = m.error;
2068
- if (typeof m.failedRoleId === "string" && m.failedRoleId) o.failedRoleId = m.failedRoleId;
2069
- if (Array.isArray(m.toolCalls) && m.toolCalls.length > 0) o.toolCalls = m.toolCalls;
2070
- return o;
2071
- }
2072
- /** 角色 → 落盘 JSON(groupId 由目录归属,不再写入)。 */
2073
- function roleJson(r) {
2074
- const o = {
2075
- id: r.id,
2076
- name: String(r.name || "成员"),
2077
- persona: String(r.persona || ""),
2078
- provider: String(r.provider || ""),
2079
- model: String(r.model || ""),
2080
- enabled: r.enabled !== false,
2081
- thinking: r.thinking === true
2082
- };
2083
- if (r.color) o.color = r.color;
2084
- if (typeof r.temperature === "number" && !Number.isNaN(r.temperature)) o.temperature = r.temperature;
2085
- if (typeof r.reasoningEffort === "string" && r.reasoningEffort && r.reasoningEffort !== "default") o.reasoningEffort = r.reasoningEffort;
2086
- return o;
2087
- }
2088
- //#endregion
2089
2081
  //#region src/host/persistence/store.ts
2090
2082
  /**
2091
2083
  * 持久化(PERSISTENCE.md v2.1:会话级文件隔离)。
@@ -1,31 +1,15 @@
1
1
  /**
2
- * 右侧成员与工作区栏组件
2
+ * 右侧成员与工作区栏组件。工作区目录编辑态(草稿、文件浏览器)为栏内自有状态。
3
3
  * @module dsh-group-chat/client/components
4
4
  */
5
- import type { ReactNode } from 'react';
5
+ import { type ReactNode } from 'react';
6
6
  import { type ClientSnapshot, type SnapshotRole } from '../lib/model.ts';
7
7
  interface AsidePanelProps {
8
8
  snap: ClientSnapshot;
9
9
  group: ClientSnapshot['groups'][number];
10
10
  asideOpen: boolean;
11
- wsDraft: string | null;
12
- setWsDraft: (val: string | null) => void;
13
- fileBrowser: {
14
- open: boolean;
15
- loading: boolean;
16
- list: import('../../core/types.ts').BrowseResult | null;
17
- error: string;
18
- } | null;
19
- setFileBrowser: (val: {
20
- open: boolean;
21
- loading: boolean;
22
- list: import('../../core/types.ts').BrowseResult | null;
23
- error: string;
24
- } | null) => void;
25
11
  mutate: (args: Record<string, unknown>) => Promise<unknown>;
26
- openRoleEditor: (role: SnapshotRole | null) => Promise<void>;
27
- openBrowser: (path: string | undefined) => Promise<void>;
28
- selectCurrentDir: () => void;
12
+ openRoleEditor: (role: SnapshotRole | null) => void;
29
13
  }
30
14
  export declare function AsidePanel(props: AsidePanelProps): ReactNode;
31
15
  export {};
@@ -1,9 +1,15 @@
1
1
  /**
2
- * 左侧导航栏组件
2
+ * 左侧导航栏组件(群组 → 会话目录树;群组行与会话行共用重命名/删除三件套)。
3
3
  * @module dsh-group-chat/client/components
4
4
  */
5
5
  import type { ReactNode } from 'react';
6
6
  import { type ClientSnapshot } from '../lib/model.ts';
7
+ /** 目录树节点编辑态形状(群组/会话共用)。 */
8
+ type NodeEdit = {
9
+ kind: 'group' | 'session';
10
+ id: string;
11
+ value: string;
12
+ };
7
13
  interface NavPanelProps {
8
14
  snap: ClientSnapshot;
9
15
  search: string;
@@ -15,16 +21,8 @@ interface NavPanelProps {
15
21
  setGid: (val: string) => void;
16
22
  setSid: (val: string) => void;
17
23
  setPartsSel: (val: string[] | null) => void;
18
- renameDraft: {
19
- kind: 'group' | 'session';
20
- id: string;
21
- value: string;
22
- } | null;
23
- setRenameDraft: (val: {
24
- kind: 'group' | 'session';
25
- id: string;
26
- value: string;
27
- } | null) => void;
24
+ renameDraft: NodeEdit | null;
25
+ setRenameDraft: (val: NodeEdit | null) => void;
28
26
  confirmDel: {
29
27
  kind: 'group' | 'session';
30
28
  id: string;
@@ -7,11 +7,12 @@ import { type ModelsResponse, type RoleDraft } from '../lib/model.ts';
7
7
  export interface RoleDrawerProps {
8
8
  draft: RoleDraft;
9
9
  set: (draft: RoleDraft) => void;
10
+ /** 保存目标群组(upsertRole 落库)。 */
11
+ groupId: string;
10
12
  models: ModelsResponse | null;
11
13
  modelsError: string | null;
12
14
  onRetryModels: () => void;
13
- onSave: () => void;
15
+ mutate: (args: Record<string, unknown>) => Promise<unknown>;
14
16
  onCancel: () => void;
15
- formError: string;
16
17
  }
17
18
  export declare function RoleDrawer(props: RoleDrawerProps): ReactNode;
@@ -3,7 +3,7 @@
3
3
  * @module dsh-group-chat/client/ToolRow
4
4
  */
5
5
  import { type ReactNode } from 'react';
6
- import type { ToolCallView } from '../lib/model.ts';
6
+ import type { ToolCallRecord } from '../../core/types.ts';
7
7
  export declare function ToolRow(props: {
8
- c: ToolCallView;
8
+ c: ToolCallRecord;
9
9
  }): ReactNode;
@@ -47,26 +47,10 @@ export declare function useGroupChatState(): {
47
47
  setConfirmClear: import("react").Dispatch<import("react").SetStateAction<boolean>>;
48
48
  roleDraft: RoleDraft | null;
49
49
  setRoleDraft: import("react").Dispatch<import("react").SetStateAction<RoleDraft | null>>;
50
- roleFormError: string;
51
- setRoleFormError: import("react").Dispatch<import("react").SetStateAction<string>>;
52
50
  models: ModelsResponse | null;
53
51
  setModels: import("react").Dispatch<import("react").SetStateAction<ModelsResponse | null>>;
54
52
  modelsError: string | null;
55
53
  setModelsError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
56
- fileBrowser: {
57
- open: boolean;
58
- loading: boolean;
59
- list: import("../../core/types.ts").BrowseResult | null;
60
- error: string;
61
- } | null;
62
- setFileBrowser: import("react").Dispatch<import("react").SetStateAction<{
63
- open: boolean;
64
- loading: boolean;
65
- list: import("../../core/types.ts").BrowseResult | null;
66
- error: string;
67
- } | null>>;
68
- wsDraft: string | null;
69
- setWsDraft: import("react").Dispatch<import("react").SetStateAction<string | null>>;
70
54
  partsSel: string[] | null;
71
55
  setPartsSel: import("react").Dispatch<import("react").SetStateAction<string[] | null>>;
72
56
  rounds: number;
@@ -2,7 +2,7 @@
2
2
  * 浏览器半共享常量与小工具(纯函数,无 React 依赖)。
3
3
  * @module dsh-group-chat/client/model
4
4
  */
5
- import type { RoleRecord, Snapshot, ToolCallRecord } from '../../core/types.ts';
5
+ import type { Snapshot } from '../../core/types.ts';
6
6
  /** 会话列表状态派生与文案(core 纯函数,供 NavPanel 渲染)。 */
7
7
  export { sessStatus, SESS_STATUS_LABEL, type SessStatus } from '../../core/status.ts';
8
8
  /** 角色标识色调色板(与宿主半一致)。 */
@@ -72,9 +72,3 @@ export interface RoleDraft {
72
72
  }
73
73
  export declare function draftFromRole(role: SnapshotRole): RoleDraft;
74
74
  export declare function blankDraft(): RoleDraft;
75
- /** 工具调用行的展示参数。 */
76
- export type ToolCallView = ToolCallRecord;
77
- /** 草稿是否可作为角色落库(宿主侧再校验一次)。 */
78
- export declare function draftMissing(draft: RoleDraft): string;
79
- /** RoleRecord 兼容视图(快照角色行即其展示子集)。 */
80
- export type RoleView = RoleRecord;
@@ -13,14 +13,14 @@ export declare function execCommand(commandId: 'delete' | 'insertHTML' | 'insert
13
13
  * DIV/P 块前补换行(防粘贴残留的块级包裹)。
14
14
  */
15
15
  export declare function serializeInput(root: HTMLElement): string;
16
- /** @角色芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
16
+ /** @角色芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删;fresh=插入后需营救选区,加 data-new 标记)。 */
17
17
  export declare function chipHtml(role: {
18
18
  id: string;
19
19
  name: string;
20
20
  color?: string;
21
- }): string;
22
- /** @文件芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
23
- export declare function fileChipHtml(path: string, kind: 'file' | 'directory'): string;
21
+ }, fresh?: boolean): string;
22
+ /** @文件芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删;fresh 同 chipHtml)。 */
23
+ export declare function fileChipHtml(path: string, kind: 'file' | 'directory', fresh?: boolean): string;
24
24
  /**
25
25
  * 光标前的活跃 @token(角色或文件弹层触发判定)。
26
26
  *
@@ -7,3 +7,9 @@ import type { MessageRecord, RoleRecord } from './types.ts';
7
7
  export declare function messageJson(m: Partial<MessageRecord> | undefined): Record<string, unknown> | null;
8
8
  /** 角色 → 落盘 JSON(groupId 由目录归属,不再写入)。 */
9
9
  export declare function roleJson(r: Partial<RoleRecord>): Record<string, unknown>;
10
+ /**
11
+ * 宽容 JSON 提取(模型输出/供应商错误原文的统一解析入口):
12
+ * 取首个 { 至末个 } 的片段解析,仅接受普通对象;失败返回 null。
13
+ * 代码围栏剥离由调用方按需先行处理(供应商错误原文不剥)。
14
+ */
15
+ export declare function looseJson(raw: string): Record<string, unknown> | null;