@xia-sc/dsh-cc-studio 0.8.0 → 0.8.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.
- package/README.md +4 -3
- package/lib/agent.js +1 -1
- package/lib/client.js +37 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ DSH(DeepSeek Harness)插件:输入框上方的胶囊 → 全屏融合工
|
|
|
14
14
|
|
|
15
15
|
| 项目 | 值 |
|
|
16
16
|
| --- | --- |
|
|
17
|
-
| 包名 / 版本 | `@xia-sc/dsh-cc-studio` `0.8.
|
|
17
|
+
| 包名 / 版本 | `@xia-sc/dsh-cc-studio` `0.8.1` |
|
|
18
18
|
| 兼容 | DSH `>=0.1.5-rc.1`(含**桌面版**)、Node `>=22`、**零运行依赖**;逐版本实测见 [兼容与权限](#兼容与权限) |
|
|
19
19
|
| 落盘位置 | `<DSH_HOME>/cc-library/`(角色卡)、`<DSH_HOME>/cc-drafts/`(会话草稿);`DSH_HOME` 默认 `~/.dsh`,旧位置仍读得到 |
|
|
20
20
|
| 文档 | 用户文档=本文件;改代码前必读 [AGENTS.md](./AGENTS.md);完整历史 [CHANGELOG.md](./CHANGELOG.md) |
|
|
@@ -171,7 +171,7 @@ dsh plugin --profile web remove @xia-sc/dsh-cc-studio # 卸载(同时移
|
|
|
171
171
|
## 开发与测试
|
|
172
172
|
|
|
173
173
|
```bash
|
|
174
|
-
npm test # 12 个文件、
|
|
174
|
+
npm test # 12 个文件、512 项断言,全绿才算过
|
|
175
175
|
node tests/tools-contract.test.mjs # 只跑 CC 工具链契约(88 项)
|
|
176
176
|
```
|
|
177
177
|
|
|
@@ -182,7 +182,8 @@ node tests/tools-contract.test.mjs # 只跑 CC 工具链契约(88 项
|
|
|
182
182
|
|
|
183
183
|
完整历史见 [CHANGELOG.md](./CHANGELOG.md)。最近三版都是「另一个 LLM 以工具使用者身份跑回归」挖出来的问题:
|
|
184
184
|
|
|
185
|
-
- `0.8.0` **人物关系图谱与群聊分组**:CCv3 没有关系字段 → 插件层图谱(`<DSH_HOME>/cc-groups/`)做真源,三维(方向 × 类型 × 强度),节点分「有独立卡的角色」与「卡内 NPC」;`cc_render_relations` 把关系渲染进 `description` 的固定标记区让卡自包含(幂等、可 `dryRun`、空图谱 = 清空)。新增 4 个工具(19 → 23)与工坊侧的**分组选择器 + 关系视图**(`s.cur === 4`,不进左侧导航)。另修 `cc_save_to_library` 的 `action` 谎报(传库里不存在的 id 派生新卡时报 updated)与 `cc_render_relations` 漏赋值(`changed:true` 却没写进卡)。测试 464 → 509 项。**需重启 dsh(桌面版重启应用)**。- `0.
|
|
185
|
+
- `0.8.0` **人物关系图谱与群聊分组**:CCv3 没有关系字段 → 插件层图谱(`<DSH_HOME>/cc-groups/`)做真源,三维(方向 × 类型 × 强度),节点分「有独立卡的角色」与「卡内 NPC」;`cc_render_relations` 把关系渲染进 `description` 的固定标记区让卡自包含(幂等、可 `dryRun`、空图谱 = 清空)。新增 4 个工具(19 → 23)与工坊侧的**分组选择器 + 关系视图**(`s.cur === 4`,不进左侧导航)。另修 `cc_save_to_library` 的 `action` 谎报(传库里不存在的 id 派生新卡时报 updated)与 `cc_render_relations` 漏赋值(`changed:true` 却没写进卡)。测试 464 → 509 项。**需重启 dsh(桌面版重启应用)**。- `0.8.1` 修 0.8.0 关系/分组 UI 的三个「点了没反应」:**「+ 组」依赖 `window.prompt`**(内嵌 webview 拦掉原生对话框 → 静默返回)→ 改内联输入框,关系视图的「+ NPC」同改;**面板打开时从没拉过分组列表** → 卡片上的归组下拉只有「未分组」、新建的组不出现,现已补上;**「关系」按钮在「全部/未分组」下静默禁用** → 改为可点并有提示。另外宿主半没重启时会明确提示「请重启 dsh」。**客户端改动刷新页面即可;宿主端点需重启过 dsh**。
|
|
186
|
+
- `0.7.1` 修 **B1**:dsh 重启后第一次 `cc_get_card` 报 `value is not lossless JSON` —— DSH 对返回值做 lossless-JSON 校验,**任何嵌套 `undefined` 都会让整次调用失败**,而外部导入的卡(PNG / CHARX)可能整个缺 `spec_version` / `creation_date`。三层修法:加载即补齐(`normalizeCardShape`)、`creation_date` 兜底 `null`、返回前统一 `sanitizeJson()`。另加 `build` 版本标识与 `watchedRev`,`changed` 与 `rev` 改用同一套判据。
|
|
186
187
|
- `0.7.0` 修 **N2**:长五维 + autoLorebook 原文照搬会让**一句话**吃满整份世界书预算(实测 1139 token / 预算 1200),而调大 `token_budget` 还能把告警一并消掉 → `cc_lint_lorebook` 新增**最坏情况单次触发量**。另修:`creator` 此前**没有写入路径**(validate 却一直催)、主动重置不再被误报「内容可能已丢失」、不传 `expectedRev` 也会提示 `revDrift`、空改动不再消耗 `rev`。
|
|
187
188
|
- `0.6.0` 修 **P0-6**:草稿被外部(工坊 / host 恢复)换掉时模型完全无感知,实测因此误伤过另一张卡 → 每个返回都带 `cardName` / `cardId` / `rev`,`getDraft()` 以盘为准重载,写类工具支持 `expectedRev` 乐观锁。
|
|
188
189
|
- `0.5.0` 第一轮 LLM 测试的整轮修复:`loreKeys` 解决「自动条目是死条目」、世界书完成态改为「≥5 条且 ≥1 条共创」、`cc_validate` 有了终态、载入后保存不再产生重复卡,并新增 5 个工具(14 → 19)。
|
package/lib/agent.js
CHANGED
|
@@ -12,7 +12,7 @@ const name = "dsh-cc-studio-agent";
|
|
|
12
12
|
// 由来:第五轮实测里测试者第一遍跑出「四项全无变化」,其实只是**没重启 dsh** —— LLM 侧完全无法
|
|
13
13
|
// 分辨「代码没改」和「改了但没加载」。有了这个字段,任何一次调用都能先确认自己测的是哪一版。
|
|
14
14
|
// 发版时与 package.json 的 version 同步。
|
|
15
|
-
const BUILD = "0.8.
|
|
15
|
+
const BUILD = "0.8.1";
|
|
16
16
|
const inject = ["tools", "agents"];
|
|
17
17
|
|
|
18
18
|
function nowSec(){ return Math.floor(Date.now()/1000); }
|
package/lib/client.js
CHANGED
|
@@ -137,6 +137,7 @@ window.__ModuleLoader__.load({
|
|
|
137
137
|
"workshop.group.none":"未分组",
|
|
138
138
|
"workshop.group.new":"+ 组",
|
|
139
139
|
"workshop.group.summary":"{n} 卡 / {m} 关系",
|
|
140
|
+
"workshop.group.needRestart":"宿主半还是旧代码(这些按钮要重启 dsh 才生效):请退出并重新打开桌面应用 / 重启 dsh web,然后刷新页面",
|
|
140
141
|
"workshop.group.prompt":"新分组名称(同一世界观的角色放一组)",
|
|
141
142
|
"workshop.group.assign":"组",
|
|
142
143
|
"workshop.group.set":"已归组",
|
|
@@ -394,6 +395,7 @@ window.__ModuleLoader__.load({
|
|
|
394
395
|
"workshop.group.none":"Ungrouped",
|
|
395
396
|
"workshop.group.new":"+ Group",
|
|
396
397
|
"workshop.group.summary":"{n} cards / {m} relations",
|
|
398
|
+
"workshop.group.needRestart":"The host half is still running old code (these buttons need a dsh restart): quit and reopen the desktop app (or restart dsh web), then reload the page.",
|
|
397
399
|
"workshop.group.prompt":"New group name (characters of one world go together)",
|
|
398
400
|
"workshop.group.assign":"Group",
|
|
399
401
|
"workshop.group.set":"Grouped",
|
|
@@ -891,9 +893,21 @@ window.__ModuleLoader__.load({
|
|
|
891
893
|
async function refreshGroups(){
|
|
892
894
|
try{
|
|
893
895
|
var res = await rpc("cc_listGroups", {});
|
|
894
|
-
if(res && res.ok && res.value) set({groups: res.value.groups||[]});
|
|
896
|
+
if(res && res.ok && res.value){ set({groups: res.value.groups||[]}); return; }
|
|
897
|
+
// 宿主半还是旧代码时这里会拿到 unknown-endpoint —— 那是「只刷新了页面、没重启 dsh」的典型信号,
|
|
898
|
+
// 直接说人话,别让用户对着一堆没反应的按钮猜(0.8.1)。
|
|
899
|
+
var code = res && res.error && res.error.details && res.error.details.code;
|
|
900
|
+
if(code === "unknown-endpoint"){ set({lastMsg: t("workshop.group.needRestart")}); return; }
|
|
901
|
+
set({lastMsg: String((res && res.error && res.error.message) || "")});
|
|
895
902
|
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
896
903
|
}
|
|
904
|
+
/** 0.8.1:从侧栏进关系视图。未选具体分组时**给一句提示**,而不是把按钮静默禁用(用户会以为坏了)。 */
|
|
905
|
+
function openRelations(){
|
|
906
|
+
var gid = String(state.currentGroupId || "");
|
|
907
|
+
if(!gid || gid === "__none"){ set({lastMsg: t("workshop.rel.pickGroup")}); return; }
|
|
908
|
+
set({cur: 4});
|
|
909
|
+
loadGroup(gid);
|
|
910
|
+
}
|
|
897
911
|
async function loadGroup(id){
|
|
898
912
|
var gid = String(id || state.currentGroupId || "");
|
|
899
913
|
if(!gid){ set({relations:null}); return; }
|
|
@@ -905,9 +919,10 @@ window.__ModuleLoader__.load({
|
|
|
905
919
|
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
906
920
|
set({relBusy:null});
|
|
907
921
|
}
|
|
908
|
-
async function createGroup(){
|
|
909
|
-
|
|
910
|
-
|
|
922
|
+
async function createGroup(nameArg){
|
|
923
|
+
// 0.8.1:不再依赖 window.prompt(内嵌 webview 会拦掉原生对话框 → 用户看到的是「点了没反应」)。
|
|
924
|
+
// 名字由侧栏那个内联输入框传进来;没传就当取消。
|
|
925
|
+
var name = String(nameArg == null ? "" : nameArg).trim();
|
|
911
926
|
if(!name) return;
|
|
912
927
|
set({relBusy:"new"});
|
|
913
928
|
try{
|
|
@@ -1122,7 +1137,7 @@ window.__ModuleLoader__.load({
|
|
|
1122
1137
|
// 0.4.1:这里原本还有一个 exportLibraryEntry —— 直接下载某张已存角色的 JSON。
|
|
1123
1138
|
// 三个导出入口归一之后,卡片上的「导出」改成切到导出页,导出动作只剩导出页那一套实现,
|
|
1124
1139
|
// 这条旁路就没有调用者了(留着它等于「同一次导出有两套行为」),故删除。
|
|
1125
|
-
return { getSnapshot:getSnapshot, subscribe:subscribe, setCur:setCur, setPanel:setPanel, setSideExpanded:setSideExpanded, setIsCcMode:setIsCcMode, setDraftFromHost:setDraftFromHost, setSessionId:setSessionId, setSlotWarn:setSlotWarn, setAlternateSlots:setAlternateSlots, migrateSlot:migrateSlot, checkIsCcMode:checkIsCcMode, setPollEnabled:setPollEnabled, setPollInterval:setPollInterval, setAutoValidate:setAutoValidate, setAutoSaveOnImport:setAutoSaveOnImport, updateDraft:updateDraft, validate:validate, exportJSON:exportJSON, exportPNG:exportPNG, embedIntoPng:embedIntoPng, exportCharx:exportCharx, importJSON:importJSON, importAny:importAny, setWorldPatch:setWorldPatch, setEditingWorld:setEditingWorld, cancelEditingWorld:cancelEditingWorld, setEditingText:setEditingText, clearEditingText:clearEditingText, cancelEditingText:cancelEditingText, updateEditingText:updateEditingText, addEntry:addEntry, setLibraryQuery:setLibraryQuery, toggleLibraryCollapsed:toggleLibraryCollapsed, refreshLibrary:refreshLibrary, saveCurrentToLibrary:saveCurrentToLibrary, saveAsNewToLibrary:saveAsNewToLibrary, clearCurrentLibraryId:clearCurrentLibraryId, clearDraft:clearDraft, loadFromLibrary:loadFromLibrary, deleteFromLibrary:deleteFromLibrary, renameCurrentInLibrary:renameCurrentInLibrary, setCurrentGroupId:setCurrentGroupId, refreshGroups:refreshGroups, createGroup:createGroup, loadGroup:loadGroup, assignGroup:assignGroup, deleteGroup:deleteGroup, patchRelations:patchRelations, checkGroupNow:checkGroupNow, renderRelationsToCard:renderRelationsToCard };
|
|
1140
|
+
return { getSnapshot:getSnapshot, subscribe:subscribe, setCur:setCur, setPanel:setPanel, setSideExpanded:setSideExpanded, setIsCcMode:setIsCcMode, setDraftFromHost:setDraftFromHost, setSessionId:setSessionId, setSlotWarn:setSlotWarn, setAlternateSlots:setAlternateSlots, migrateSlot:migrateSlot, checkIsCcMode:checkIsCcMode, setPollEnabled:setPollEnabled, setPollInterval:setPollInterval, setAutoValidate:setAutoValidate, setAutoSaveOnImport:setAutoSaveOnImport, updateDraft:updateDraft, validate:validate, exportJSON:exportJSON, exportPNG:exportPNG, embedIntoPng:embedIntoPng, exportCharx:exportCharx, importJSON:importJSON, importAny:importAny, setWorldPatch:setWorldPatch, setEditingWorld:setEditingWorld, cancelEditingWorld:cancelEditingWorld, setEditingText:setEditingText, clearEditingText:clearEditingText, cancelEditingText:cancelEditingText, updateEditingText:updateEditingText, addEntry:addEntry, setLibraryQuery:setLibraryQuery, toggleLibraryCollapsed:toggleLibraryCollapsed, refreshLibrary:refreshLibrary, saveCurrentToLibrary:saveCurrentToLibrary, saveAsNewToLibrary:saveAsNewToLibrary, clearCurrentLibraryId:clearCurrentLibraryId, clearDraft:clearDraft, loadFromLibrary:loadFromLibrary, deleteFromLibrary:deleteFromLibrary, renameCurrentInLibrary:renameCurrentInLibrary, setCurrentGroupId:setCurrentGroupId, refreshGroups:refreshGroups, createGroup:createGroup, loadGroup:loadGroup, assignGroup:assignGroup, deleteGroup:deleteGroup, patchRelations:patchRelations, checkGroupNow:checkGroupNow, renderRelationsToCard:renderRelationsToCard, openRelations:openRelations };
|
|
1126
1141
|
}
|
|
1127
1142
|
|
|
1128
1143
|
var S={
|
|
@@ -1362,10 +1377,13 @@ window.__ModuleLoader__.load({
|
|
|
1362
1377
|
// 并在导出页把「导出的仍是工坊里这张、你点的那张还没载入」说清楚。清空后回到常态提示。
|
|
1363
1378
|
var jumpRef=React.useState(null); var jumpFrom=jumpRef[0], setJumpFrom=jumpRef[1];
|
|
1364
1379
|
// 0.8.0 关系视图的「添加关系」表单(局部状态,不进 store:它只是草稿输入)
|
|
1365
|
-
|
|
1380
|
+
var relFormRef=React.useState({from:"", to:"", type:"friend", strength:3, direction:"one-way", note:""}); var relForm=relFormRef[0], setRelForm=relFormRef[1];
|
|
1381
|
+
// 0.8.1:分组名 / NPC 名改用内联输入(不再用 window.prompt —— 内嵌 webview 会拦掉它)
|
|
1382
|
+
var groupDraftRef=React.useState(null); var groupDraft=groupDraftRef[0], setGroupDraft=groupDraftRef[1];
|
|
1383
|
+
var npcDraftRef=React.useState(""); var npcDraft=npcDraftRef[0], setNpcDraft=npcDraftRef[1];
|
|
1366
1384
|
React.useEffect(function(){ return store.subscribe(function(){ setS(store.getSnapshot()); }); }, [store]);
|
|
1367
1385
|
React.useEffect(function(){
|
|
1368
|
-
if(s.panelOpen) store.refreshLibrary();
|
|
1386
|
+
if(s.panelOpen){ store.refreshLibrary(); store.refreshGroups(); }
|
|
1369
1387
|
}, [s.panelOpen]);
|
|
1370
1388
|
if(!s.panelOpen) return null;
|
|
1371
1389
|
// 顶部胶囊与左侧导航都只有写作三步:导出不再是「第 4 步」(0.4.1 归一)——
|
|
@@ -1617,10 +1635,12 @@ window.__ModuleLoader__.load({
|
|
|
1617
1635
|
relMembers.length===0 ? h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.rel.noMember")) : null),
|
|
1618
1636
|
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6, marginBottom:14, alignItems:"center"}},
|
|
1619
1637
|
relNodes.filter(function(n){ return n.kind === "npc"; }).map(function(n){ return h("span",{key:n.id, style:S.badge}, n.name); }),
|
|
1638
|
+
h("input",{placeholder:t("workshop.rel.nodePrompt"), value:npcDraft, style:{width:150, background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"2px 6px", fontSize:11},
|
|
1639
|
+
onChange:function(ev){ setNpcDraft(ev.target.value); },
|
|
1640
|
+
onKeyDown:function(ev){ var v=String(npcDraft||"").trim(); if(ev.key==="Enter" && v){ store.patchRelations([{op:"addNode", kind:"npc", name:v}]); setNpcDraft(""); } }}),
|
|
1620
1641
|
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-secondary)", borderRadius:8, padding:"2px 8px", fontSize:11}), onClick:function(){
|
|
1621
|
-
var name = "";
|
|
1622
|
-
|
|
1623
|
-
if(name) store.patchRelations([{op:"addNode", kind:"npc", name:name}]);
|
|
1642
|
+
var name = String(npcDraft||"").trim();
|
|
1643
|
+
if(name){ store.patchRelations([{op:"addNode", kind:"npc", name:name}]); setNpcDraft(""); }
|
|
1624
1644
|
}}, t("workshop.rel.addNode"))),
|
|
1625
1645
|
h("div",{style:{fontSize:12, fontWeight:600, marginBottom:4}}, t("workshop.rel.edges") + " · " + relEdges.length),
|
|
1626
1646
|
relEdges.length===0 ? h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginBottom:10}}, t("workshop.rel.noEdge")) :
|
|
@@ -1736,8 +1756,13 @@ window.__ModuleLoader__.load({
|
|
|
1736
1756
|
h("option",{value:""}, t("workshop.group.all")),
|
|
1737
1757
|
h("option",{value:"__none"}, t("workshop.group.none")),
|
|
1738
1758
|
(s.groups||[]).map(function(g){ return h("option",{key:g.id, value:g.id}, g.name + " · " + t("workshop.group.summary",{n:String(g.memberCount), m:String(g.edgeCount)})); })),
|
|
1739
|
-
|
|
1740
|
-
|
|
1759
|
+
(groupDraft === null
|
|
1760
|
+
? h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-secondary)", borderRadius:8, padding:"4px 8px", fontSize:11, flexShrink:0}), onClick:function(){ setGroupDraft(""); }}, t("workshop.group.new"))
|
|
1761
|
+
: h("input",{autoFocus:true, value:groupDraft, placeholder:t("workshop.group.prompt"), style:{width:104, flexShrink:0, background:"var(--dsw-alias-bg-base)", border:"1px solid #7c5cff", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"4px 6px", fontSize:11},
|
|
1762
|
+
onChange:function(ev){ setGroupDraft(ev.target.value); },
|
|
1763
|
+
onKeyDown:function(ev){ if(ev.key==="Enter"){ store.createGroup(ev.target.value); setGroupDraft(null); } if(ev.key==="Escape"){ setGroupDraft(null); } },
|
|
1764
|
+
onBlur:function(ev){ var v=String(ev.target.value||"").trim(); if(v) store.createGroup(v); setGroupDraft(null); }})),
|
|
1765
|
+
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid #7c5cff", background:"transparent", color:"#7c5cff", borderRadius:8, padding:"4px 8px", fontSize:11, flexShrink:0}), onClick:function(){ store.openRelations(); }}, t("workshop.rel.open"))),
|
|
1741
1766
|
s.lastMsg ? h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis"}}, s.lastMsg) : null
|
|
1742
1767
|
),
|
|
1743
1768
|
h("div",{style:S.libraryList},
|