dsh-data-domain-knowledge 0.1.0-alpha.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 (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +360 -0
  3. package/cordis.patch.yml +410 -0
  4. package/eval/README.md +76 -0
  5. package/eval/evaluate-retrieval.mjs +121 -0
  6. package/eval/queries.example.jsonl +2 -0
  7. package/lib/client.js +111415 -0
  8. package/lib/types/config-schema.d.ts +35 -0
  9. package/lib/types/config-schema.d.ts.map +1 -0
  10. package/lib/types/config-schema.js +34 -0
  11. package/lib/types/config-schema.js.map +1 -0
  12. package/lib/types/convert/core-loader.d.ts +32 -0
  13. package/lib/types/convert/core-loader.d.ts.map +1 -0
  14. package/lib/types/convert/core-loader.js +60 -0
  15. package/lib/types/convert/core-loader.js.map +1 -0
  16. package/lib/types/convert/host.d.ts +88 -0
  17. package/lib/types/convert/host.d.ts.map +1 -0
  18. package/lib/types/convert/host.js +479 -0
  19. package/lib/types/convert/host.js.map +1 -0
  20. package/lib/types/convert/ingest.d.ts +125 -0
  21. package/lib/types/convert/ingest.d.ts.map +1 -0
  22. package/lib/types/convert/ingest.js +228 -0
  23. package/lib/types/convert/ingest.js.map +1 -0
  24. package/lib/types/convert/settings-schema.d.ts +18 -0
  25. package/lib/types/convert/settings-schema.d.ts.map +1 -0
  26. package/lib/types/convert/settings-schema.js +26 -0
  27. package/lib/types/convert/settings-schema.js.map +1 -0
  28. package/lib/types/convert/types.d.ts +46 -0
  29. package/lib/types/convert/types.d.ts.map +1 -0
  30. package/lib/types/convert/types.js +41 -0
  31. package/lib/types/convert/types.js.map +1 -0
  32. package/lib/types/diagnosis/cards.d.ts +71 -0
  33. package/lib/types/diagnosis/cards.d.ts.map +1 -0
  34. package/lib/types/diagnosis/cards.js +482 -0
  35. package/lib/types/diagnosis/cards.js.map +1 -0
  36. package/lib/types/diagnosis/problem-classes.d.ts +51 -0
  37. package/lib/types/diagnosis/problem-classes.d.ts.map +1 -0
  38. package/lib/types/diagnosis/problem-classes.js +66 -0
  39. package/lib/types/diagnosis/problem-classes.js.map +1 -0
  40. package/lib/types/diagnosis/schema.d.ts +65 -0
  41. package/lib/types/diagnosis/schema.d.ts.map +1 -0
  42. package/lib/types/diagnosis/schema.js +208 -0
  43. package/lib/types/diagnosis/schema.js.map +1 -0
  44. package/lib/types/diagnosis/seed-source.d.ts +20 -0
  45. package/lib/types/diagnosis/seed-source.d.ts.map +1 -0
  46. package/lib/types/diagnosis/seed-source.js +96 -0
  47. package/lib/types/diagnosis/seed-source.js.map +1 -0
  48. package/lib/types/diagnosis/seed.d.ts +50 -0
  49. package/lib/types/diagnosis/seed.d.ts.map +1 -0
  50. package/lib/types/diagnosis/seed.js +242 -0
  51. package/lib/types/diagnosis/seed.js.map +1 -0
  52. package/lib/types/diagnosis/store.d.ts +81 -0
  53. package/lib/types/diagnosis/store.d.ts.map +1 -0
  54. package/lib/types/diagnosis/store.js +409 -0
  55. package/lib/types/diagnosis/store.js.map +1 -0
  56. package/lib/types/diagnosis/types.d.ts +124 -0
  57. package/lib/types/diagnosis/types.d.ts.map +1 -0
  58. package/lib/types/diagnosis/types.js +13 -0
  59. package/lib/types/diagnosis/types.js.map +1 -0
  60. package/lib/types/directory-picker.d.ts +94 -0
  61. package/lib/types/directory-picker.d.ts.map +1 -0
  62. package/lib/types/directory-picker.js +110 -0
  63. package/lib/types/directory-picker.js.map +1 -0
  64. package/lib/types/extract-model.d.ts +76 -0
  65. package/lib/types/extract-model.d.ts.map +1 -0
  66. package/lib/types/extract-model.js +193 -0
  67. package/lib/types/extract-model.js.map +1 -0
  68. package/lib/types/extract.d.ts +121 -0
  69. package/lib/types/extract.d.ts.map +1 -0
  70. package/lib/types/extract.js +549 -0
  71. package/lib/types/extract.js.map +1 -0
  72. package/lib/types/fts.d.ts +96 -0
  73. package/lib/types/fts.d.ts.map +1 -0
  74. package/lib/types/fts.js +184 -0
  75. package/lib/types/fts.js.map +1 -0
  76. package/lib/types/index.d.ts +39 -0
  77. package/lib/types/index.d.ts.map +1 -0
  78. package/lib/types/index.js +1112 -0
  79. package/lib/types/index.js.map +1 -0
  80. package/lib/types/intent/engine.d.ts +108 -0
  81. package/lib/types/intent/engine.d.ts.map +1 -0
  82. package/lib/types/intent/engine.js +501 -0
  83. package/lib/types/intent/engine.js.map +1 -0
  84. package/lib/types/intent/host.d.ts +88 -0
  85. package/lib/types/intent/host.d.ts.map +1 -0
  86. package/lib/types/intent/host.js +389 -0
  87. package/lib/types/intent/host.js.map +1 -0
  88. package/lib/types/intent/preset-tools.d.ts +37 -0
  89. package/lib/types/intent/preset-tools.d.ts.map +1 -0
  90. package/lib/types/intent/preset-tools.js +45 -0
  91. package/lib/types/intent/preset-tools.js.map +1 -0
  92. package/lib/types/intent/settings-schema.d.ts +59 -0
  93. package/lib/types/intent/settings-schema.d.ts.map +1 -0
  94. package/lib/types/intent/settings-schema.js +57 -0
  95. package/lib/types/intent/settings-schema.js.map +1 -0
  96. package/lib/types/intent/store.d.ts +75 -0
  97. package/lib/types/intent/store.d.ts.map +1 -0
  98. package/lib/types/intent/store.js +139 -0
  99. package/lib/types/intent/store.js.map +1 -0
  100. package/lib/types/intent/tools.d.ts +63 -0
  101. package/lib/types/intent/tools.d.ts.map +1 -0
  102. package/lib/types/intent/tools.js +108 -0
  103. package/lib/types/intent/tools.js.map +1 -0
  104. package/lib/types/intent/types.d.ts +189 -0
  105. package/lib/types/intent/types.d.ts.map +1 -0
  106. package/lib/types/intent/types.js +62 -0
  107. package/lib/types/intent/types.js.map +1 -0
  108. package/lib/types/knowledge-bases.d.ts +266 -0
  109. package/lib/types/knowledge-bases.d.ts.map +1 -0
  110. package/lib/types/knowledge-bases.js +1107 -0
  111. package/lib/types/knowledge-bases.js.map +1 -0
  112. package/lib/types/layout.d.ts +262 -0
  113. package/lib/types/layout.d.ts.map +1 -0
  114. package/lib/types/layout.js +312 -0
  115. package/lib/types/layout.js.map +1 -0
  116. package/lib/types/migrate.d.ts +110 -0
  117. package/lib/types/migrate.d.ts.map +1 -0
  118. package/lib/types/migrate.js +280 -0
  119. package/lib/types/migrate.js.map +1 -0
  120. package/lib/types/recall-rank.d.ts +158 -0
  121. package/lib/types/recall-rank.d.ts.map +1 -0
  122. package/lib/types/recall-rank.js +261 -0
  123. package/lib/types/recall-rank.js.map +1 -0
  124. package/lib/types/runtime.d.ts +568 -0
  125. package/lib/types/runtime.d.ts.map +1 -0
  126. package/lib/types/runtime.js +3045 -0
  127. package/lib/types/runtime.js.map +1 -0
  128. package/lib/types/settings-schema.d.ts +117 -0
  129. package/lib/types/settings-schema.d.ts.map +1 -0
  130. package/lib/types/settings-schema.js +92 -0
  131. package/lib/types/settings-schema.js.map +1 -0
  132. package/lib/types/settings-volatile.d.ts +102 -0
  133. package/lib/types/settings-volatile.d.ts.map +1 -0
  134. package/lib/types/settings-volatile.js +114 -0
  135. package/lib/types/settings-volatile.js.map +1 -0
  136. package/lib/types/types.d.ts +799 -0
  137. package/lib/types/types.d.ts.map +1 -0
  138. package/lib/types/types.js +387 -0
  139. package/lib/types/types.js.map +1 -0
  140. package/lib/types/wiki-types.d.ts +68 -0
  141. package/lib/types/wiki-types.d.ts.map +1 -0
  142. package/lib/types/wiki-types.js +322 -0
  143. package/lib/types/wiki-types.js.map +1 -0
  144. package/package.json +101 -0
  145. package/preset/tita-data-agent/agent.cordis.yml +376 -0
  146. package/preset/tita-data-agent/preset.yml +3 -0
  147. package/python/requirements.txt +8 -0
  148. package/scripts/doctor.mjs +70 -0
  149. package/scripts/e2e-pipeline.mjs +355 -0
  150. package/scripts/install-agent-preset.mjs +92 -0
  151. package/scripts/setup-embedding.sh +34 -0
  152. package/scripts/sync-agent-preset.mjs +188 -0
  153. package/src/client/AgentPresetCard.tsx +247 -0
  154. package/src/client/DirectoryPicker.tsx +357 -0
  155. package/src/client/InfoHint.tsx +65 -0
  156. package/src/client/IntentLogicTab.tsx +185 -0
  157. package/src/client/IntentTab.tsx +715 -0
  158. package/src/client/KnowledgeBaseTab.tsx +1454 -0
  159. package/src/client/KnowledgeGraphView.tsx +306 -0
  160. package/src/client/KnowledgePanel.tsx +381 -0
  161. package/src/client/KnowledgeStepRail.tsx +60 -0
  162. package/src/client/NodeSettings.tsx +392 -0
  163. package/src/client/PreviewSections.tsx +116 -0
  164. package/src/client/RecallPanel.tsx +399 -0
  165. package/src/client/TypeDefinitionDrawer.tsx +215 -0
  166. package/src/client/WikiFlowCard.tsx +226 -0
  167. package/src/client/WikiTypeEditor.tsx +332 -0
  168. package/src/client/agent-preset.ts +121 -0
  169. package/src/client/arch/AgentArchitectureGraph.tsx +219 -0
  170. package/src/client/arch/NodeLogicPanel.tsx +133 -0
  171. package/src/client/arch/ProblemDetailPanel.tsx +88 -0
  172. package/src/client/arch/model.ts +691 -0
  173. package/src/client/architecture.css +191 -0
  174. package/src/client/architecture.tsx +530 -0
  175. package/src/client/attribution/AttributionGraph.tsx +104 -0
  176. package/src/client/attribution/AttributionTab.tsx +332 -0
  177. package/src/client/attribution/CaseView.tsx +102 -0
  178. package/src/client/attribution/GraphStage.tsx +87 -0
  179. package/src/client/attribution/RunView.tsx +184 -0
  180. package/src/client/attribution/StageChrome.tsx +403 -0
  181. package/src/client/attribution/ViewSwitch.tsx +107 -0
  182. package/src/client/attribution/graph-options.ts +381 -0
  183. package/src/client/attribution/model.ts +462 -0
  184. package/src/client/config-scope.ts +119 -0
  185. package/src/client/convert/ConvertPanel.tsx +594 -0
  186. package/src/client/convert/styles.css +138 -0
  187. package/src/client/convert/styles.ts +49 -0
  188. package/src/client/format.ts +39 -0
  189. package/src/client/graph/layout.ts +181 -0
  190. package/src/client/graph.css +113 -0
  191. package/src/client/index.ts +98 -0
  192. package/src/client/problem-domains.ts +114 -0
  193. package/src/client/styles.css +940 -0
  194. package/src/client/styles.ts +11 -0
  195. package/src/client/useFitScale.ts +68 -0
  196. package/src/config-schema.ts +45 -0
  197. package/src/convert/core-loader.ts +68 -0
  198. package/src/convert/host.ts +579 -0
  199. package/src/convert/ingest.ts +367 -0
  200. package/src/convert/settings-schema.ts +27 -0
  201. package/src/convert/types.ts +65 -0
  202. package/src/diagnosis/cards.ts +498 -0
  203. package/src/diagnosis/problem-classes.ts +117 -0
  204. package/src/diagnosis/schema.ts +260 -0
  205. package/src/diagnosis/seed-source.ts +107 -0
  206. package/src/diagnosis/seed.ts +291 -0
  207. package/src/diagnosis/store.ts +432 -0
  208. package/src/diagnosis/types.ts +152 -0
  209. package/src/directory-picker.ts +164 -0
  210. package/src/extract-model.ts +227 -0
  211. package/src/extract.ts +632 -0
  212. package/src/fts.ts +203 -0
  213. package/src/index.ts +1091 -0
  214. package/src/intent/README.md +63 -0
  215. package/src/intent/engine.ts +527 -0
  216. package/src/intent/host.ts +462 -0
  217. package/src/intent/preset-tools.ts +48 -0
  218. package/src/intent/settings-schema.ts +63 -0
  219. package/src/intent/store.ts +143 -0
  220. package/src/intent/tools.ts +139 -0
  221. package/src/intent/types.ts +245 -0
  222. package/src/knowledge-bases.ts +1160 -0
  223. package/src/layout.ts +350 -0
  224. package/src/migrate.ts +262 -0
  225. package/src/python/embedding_worker.py +471 -0
  226. package/src/recall-rank.ts +299 -0
  227. package/src/runtime.ts +3069 -0
  228. package/src/settings-schema.ts +94 -0
  229. package/src/settings-volatile.ts +122 -0
  230. package/src/types.ts +1140 -0
  231. package/src/wiki-types.ts +357 -0
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 把 `preset/tita-data-agent/` 编译成 `cordis.patch.yml` 里的一条
4
+ * `@deepseek-ai/dsh-agent-preset` 声明行。
5
+ *
6
+ * ## 为什么需要一步「编译」,而不是直接把组合写进 patch
7
+ *
8
+ * preset 的真源要满足两件事,而它们指向不同的文件形态:
9
+ *
10
+ * 1. **它要能被人读、被 review、被测试。** `preset/tita-data-agent/agent.cordis.yml`
11
+ * 是一份完整组合,改动是「改提示词」这种语义级的事;`agent-preset.ts` 的
12
+ * `readPersonaPrompt` 也已经按这份文件的 `persona` 行来解析。
13
+ * 2. **DSH 只认 `cordis.patch.yml` 里的声明行。** 见下面「0.1.7 换了寻址」。
14
+ *
15
+ * 所以两者之间需要一次投影,而不是让同一份组合在仓库里存两份、各自漂移。这个
16
+ * 脚本就是那次投影:真源唯一,生成物有标记,`--check` 让 doctor 能在 CI 与
17
+ * 本地都问出「改了真源但忘了编译」。
18
+ *
19
+ * ## 0.1.7 换了寻址:目录没了
20
+ *
21
+ * 0.1.5 时用户 preset 是 `$DSH_HOME/.agent-presets/<id>/`,里面放 `preset.yml`
22
+ * (展示名)与 `agent.cordis.yml`(组合)。**0.1.7 不再读那个目录**:registry
23
+ * 只接受 `agentPresets.register(config)`,没有任何文件系统扫描。preset 现在的
24
+ * 唯一形态是 profile 组合里的一条 `@deepseek-ai/dsh-agent-preset` 声明行,而
25
+ * 随插件一起分发的唯一办法就是让插件自己的 bundle patch 去 `insert` 它。
26
+ *
27
+ * 好消息是**子行的解析基准是 profile 根**(所有 patch 层由 `composeEntries`
28
+ * 合并进同一个 root include,`mountRootInclude` 把 baseUrl 定在 profile 目录),
29
+ * 而运行时的解析表同时收 installation scope(`@deepseek-ai/dsh-persona` 等)
30
+ * 与 profile scope(`dsh-data-domain-knowledge/intent-tools`)。所以这份组合里两种包
31
+ * 都能解析 —— 不需要给插件补一堆 peer。
32
+ *
33
+ * 用法:
34
+ * node scripts/sync-agent-preset.mjs # 编译并写回 cordis.patch.yml
35
+ * node scripts/sync-agent-preset.mjs --check # 只校验是否同步(不同步则退出 1)
36
+ * node scripts/sync-agent-preset.mjs --dry-run # 打印会写入的内容,不动文件
37
+ */
38
+ import { readFile, writeFile } from 'node:fs/promises'
39
+ import { dirname, join, resolve } from 'node:path'
40
+ import { fileURLToPath } from 'node:url'
41
+
42
+ /** 与 `src/client/agent-preset.ts` 的 `TITA_PRESET_ID`、组合文件里的引用一致。 */
43
+ const PRESET_ID = 'tita-data-agent'
44
+
45
+ /** patch 行 id 的约定形态:`preset-<preset id>`。 */
46
+ const ROW_ID = `preset-${PRESET_ID}`
47
+
48
+ /** 标记区。生成物只活在两者之间,区外的一切由人维护。 */
49
+ const BEGIN = '# >>> agent preset (generated) >>>'
50
+ const END = '# <<< agent preset (generated) <<<'
51
+
52
+ /** 声明行在 YAML 里的缩进:`plugins:` 落在 8,列表项落在 10。 */
53
+ const PLUGINS_INDENT = 10
54
+
55
+ const pluginRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
56
+ const sourceDir = join(pluginRoot, 'preset', PRESET_ID)
57
+ const patchPath = join(pluginRoot, 'cordis.patch.yml')
58
+
59
+ const check = process.argv.includes('--check')
60
+ const dryRun = process.argv.includes('--dry-run')
61
+
62
+ /**
63
+ * 读 `preset.yml` 的三个标量。
64
+ *
65
+ * **这不是一个 YAML 解析器**,和 `readPersonaPrompt` 同一个理由:这里要读的只有
66
+ * `name` / `description` / `order` 三个顶层标量,为它们拉一个依赖不值得。代价是
67
+ * 它只认「一行一个 `key: value`」,多行标量会读成第一行 —— 对这三个字段来说那是
68
+ * 一个显式的坏输入,而不是一个静默的错误答案。
69
+ */
70
+ function readPresetMeta(content) {
71
+ const meta = {}
72
+ for (const line of content.split('\n')) {
73
+ const match = /^([A-Za-z_][\w-]*):\s*(.*)$/.exec(line)
74
+ if (!match) continue
75
+ const [, key, raw] = match
76
+ // 去掉一层成对的引号(单引号里 `''` 是转义的 `'`)。
77
+ let value = raw.trim()
78
+ if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
79
+ value = value.slice(1, -1).replaceAll("''", "'")
80
+ } else if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
81
+ value = value.slice(1, -1)
82
+ }
83
+ meta[key] = value
84
+ }
85
+ return meta
86
+ }
87
+
88
+ /** YAML 单引号标量:内部 `'` 写成 `''`。 */
89
+ function quote(value) {
90
+ return `'${String(value).replaceAll("'", "''")}'`
91
+ }
92
+
93
+ /** 把组合正文整体缩进到 `plugins:` 之下;空行保持空,免得留下尾随空格。 */
94
+ function indentPlugins(content) {
95
+ const pad = ' '.repeat(PLUGINS_INDENT)
96
+ return content
97
+ .replace(/\s+$/, '')
98
+ .split('\n')
99
+ .map(line => (line.trim() === '' ? '' : pad + line))
100
+ .join('\n')
101
+ }
102
+
103
+ /** 渲染标记区之间的完整内容。 */
104
+ function renderBlock(meta, agentComposition) {
105
+ const header = [
106
+ '# Tita Data Agent 的 agent preset 声明。',
107
+ '#',
108
+ '# 这一段由 `scripts/sync-agent-preset.mjs` 从 `preset/tita-data-agent/` 生成 ——',
109
+ '# **不要手改**:改真源再跑 `pnpm sync:preset`。`pnpm doctor` 会校验它与真源一致。',
110
+ '#',
111
+ '# 为什么 preset 必须长在插件的 patch 里:0.1.7 起 DSH 不再扫描',
112
+ '# `$DSH_HOME/.agent-presets/`,preset 的唯一形态就是这条 insert。详见脚本头注释。',
113
+ ]
114
+ const lines = [
115
+ ...header,
116
+ '- insert:',
117
+ ` - id: ${ROW_ID}`,
118
+ " name: '@deepseek-ai/dsh-agent-preset'",
119
+ ' config:',
120
+ ` id: ${PRESET_ID}`,
121
+ ]
122
+ if (meta.name !== undefined && meta.name !== '') lines.push(` name: ${quote(meta.name)}`)
123
+ if (meta.description !== undefined && meta.description !== '') lines.push(` description: ${quote(meta.description)}`)
124
+ if (meta.order !== undefined && meta.order !== '') lines.push(` order: ${meta.order}`)
125
+ lines.push(' plugins:')
126
+ lines.push(indentPlugins(agentComposition))
127
+ return lines.join('\n')
128
+ }
129
+
130
+ /**
131
+ * 把 `block` 放进 `BEGIN`/`END` 之间。
132
+ *
133
+ * 标记不存在时**追加**到文件末尾:patch 层的顺序是「bundle 数组顺序」,这条声明
134
+ * 只要在 registry 那条 entry 之后即可,而 registry 属于 `dsh-web-app`,排在所有
135
+ * 本地插件之前 —— 所以追加是安全的,不需要插到文件中间。
136
+ */
137
+ function spliceBlock(patchContent, block) {
138
+ const beginAt = patchContent.indexOf(BEGIN)
139
+ const endAt = patchContent.indexOf(END)
140
+ const wrapped = `${BEGIN}\n${block}\n${END}`
141
+ if (beginAt !== -1 && endAt !== -1 && endAt > beginAt) {
142
+ const before = patchContent.slice(0, beginAt)
143
+ const after = patchContent.slice(endAt + END.length)
144
+ return { content: `${before}${wrapped}${after}`, existed: true }
145
+ }
146
+ const base = patchContent.replace(/\s+$/, '')
147
+ return { content: `${base}\n\n${wrapped}\n`, existed: false }
148
+ }
149
+
150
+ async function main() {
151
+ const [metaSource, agentComposition, patchSource] = await Promise.all([
152
+ readFile(join(sourceDir, 'preset.yml'), 'utf8'),
153
+ readFile(join(sourceDir, 'agent.cordis.yml'), 'utf8'),
154
+ readFile(patchPath, 'utf8'),
155
+ ])
156
+
157
+ const block = renderBlock(readPresetMeta(metaSource), agentComposition)
158
+ const { content, existed } = spliceBlock(patchSource, block)
159
+
160
+ if (check) {
161
+ if (content === patchSource) {
162
+ console.log(`preset 已同步:${ROW_ID}(标记区${existed ? '' : '缺失'})`)
163
+ return
164
+ }
165
+ console.error(`preset 与真源不同步:${patchPath}`)
166
+ console.error(`跑 \`pnpm sync:preset\` 重新生成(真源:preset/${PRESET_ID}/)。`)
167
+ process.exitCode = 1
168
+ return
169
+ }
170
+
171
+ if (dryRun) {
172
+ console.log(block)
173
+ return
174
+ }
175
+
176
+ if (content === patchSource) {
177
+ console.log(`无变化:${ROW_ID} 已是最新。`)
178
+ return
179
+ }
180
+ await writeFile(patchPath, content)
181
+ console.log(`已写入 ${ROW_ID} → ${patchPath}`)
182
+ console.log(`重启 DSH 后生效;preset 在**会话创建时**固定,正在跑的会话不受影响。`)
183
+ }
184
+
185
+ main().catch(error => {
186
+ console.error(`同步失败:${error instanceof Error ? error.message : String(error)}`)
187
+ process.exitCode = 1
188
+ })
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Agent 提示词(0029 / 0030 / 0036)—— 控制台里对 **agent preset** 的只读视图,
3
+ * 外加一个「去真源目录改它」的动作。
4
+ *
5
+ * ## 为什么这里没有「默认 Agent preset」这一项
6
+ *
7
+ * 它曾经有:一个下拉,写 `agent-preset-registry` 那条 profile entry 的 `default`。
8
+ * 那是个**重复**——DSH 设置里本来就有同一项,同一个字段、同一个真源。在控制台里再
9
+ * 摆一个,用户就要回答「这两处哪个算数」(答案是都算数,因为写的是同一个字段,但
10
+ * 光看界面看不出来)。所以它撤了:**默认 preset 归 DSH 设置**,这里只在它不是默认
11
+ * 时把这件事说出来,并指明去哪儿改。
12
+ *
13
+ * ## 保留的是「提示词正文」和「怎么改它」
14
+ *
15
+ * 正文只读,因为它的真源是一份**组合文件**(`agent.cordis.yml` 的 `persona` 行),
16
+ * 不是一段配置值:它决定这个 Agent 挂哪些工具、有哪些提示词段。从浏览器里改它,等于
17
+ * 让一个表单去写一份组合 —— 而 DSH 对 preset 的授权模型刻意是「声明式创建、之后一切
18
+ * 发生在声明它的那份文件里」。
19
+ *
20
+ * 所以这一块做三件事:把正文读出来给你看、把**真源目录**告诉你并给你一个按钮打开它、
21
+ * 在空态与错误态里说清问题与恢复动作。
22
+ *
23
+ * ## 打开目录走的是 `session` 命名空间,不是 `settings`
24
+ *
25
+ * 0.1.7 起 DSH 那边的 preset **不是一个目录**,是组合里的一条声明行(见 docs/0036),
26
+ * 所以 `settings.openAgentPresetDirectory` 那种能力不存在了。但「提示词写在哪」仍然
27
+ * 是一个目录 —— 本插件随包分发的 `preset/tita-data-agent/`。打开它用的是
28
+ * `session.openWorkspacePath`(文件预览的「在访达中显示」用的也是它),路径由宿主
29
+ * 算好随布局一起给出来。
30
+ *
31
+ * ## 为什么名单走 Remote 而不是自己读磁盘
32
+ *
33
+ * roster 的发现(有哪些 preset、哪个是默认、哪个组合不出来)是
34
+ * `dsh-agent-preset-registry` 的领域知识。控制台自己扫磁盘既得不到准确名单,也会在
35
+ * DSH 改发现规则时悄悄漂移。所以名单与组合正文都从宿主问:
36
+ * `agentPresets/list` + `agentPresets/read`。
37
+ *
38
+ * @module
39
+ */
40
+
41
+ import { useCallback, useEffect, useState, type ReactNode } from 'react'
42
+ import { Button } from '@tita-data-agent/ui/components'
43
+ import { AlertCircle, FolderOpen, LoaderCircle } from '@tita-data-agent/ui/icons'
44
+ import { INTENT_TOOL_NAMES, TITA_PRESET_ID, readPersonaPrompt, type AgentPresetRemote, type PresetRow } from './agent-preset.ts'
45
+
46
+ // 这几个名字是对外契约(组合文件、安装脚本、测试都引用),从纯数据模块转发出去。
47
+ export { INTENT_TOOL_NAMES, TITA_PRESET_ID, readPersonaPrompt }
48
+ export type { AgentPresetRemote, PresetRow }
49
+
50
+ type Load =
51
+ | { status: 'loading' }
52
+ | { status: 'unavailable'; message: string }
53
+ | { status: 'ready'; row: PresetRow | null; rows: PresetRow[]; prompt: string | null; defaultRow: PresetRow | null }
54
+
55
+ /** 「打开真源目录」这个动作的三种结局。`failed` 带一句给用户看的话。 */
56
+ type Open =
57
+ | { status: 'idle' }
58
+ | { status: 'opening' }
59
+ | { status: 'failed'; message: string }
60
+
61
+ /**
62
+ * Agent 提示词卡片。挂在设置抽屉的最后一块。
63
+ *
64
+ * 它回答三个问题,顺序就是用户会问的顺序:我们那个 preset 在不在、它的提示词写了
65
+ * 什么、我要改去哪儿改。
66
+ */
67
+ export function AgentPresetCard({ remote, presetDir, presetDirExists = false }: {
68
+ remote?: AgentPresetRemote
69
+ /** 提示词真源目录的绝对路径,由 `/knowledge/layout` 算好给出。 */
70
+ presetDir?: string
71
+ /** 那个目录在不在。不在时按钮禁用并如实说明。 */
72
+ presetDirExists?: boolean
73
+ }): ReactNode {
74
+ const [load, setLoad] = useState<Load>({ status: 'loading' })
75
+ const [open, setOpen] = useState<Open>({ status: 'idle' })
76
+
77
+ const refresh = useCallback((): void => {
78
+ /**
79
+ * 取命名空间这一步本身要兜住。
80
+ *
81
+ * cordis 的 remote 代理对**未声明 inject** 的属性直接抛(实测:
82
+ * `cannot get property "remote.agentPresets" without inject`),而它一旦抛在
83
+ * `useEffect` 里,React 会把整个面板子树卸掉 —— 打开设置抽屉就会让面板变空白。
84
+ * `inject` 里已经声明了,这里再兜一层:将来命名空间被摘掉时,坏掉的是一张卡片
85
+ * 的一句提示,不是整块面板。
86
+ */
87
+ let api: AgentPresetRemote['agentPresets']
88
+ try {
89
+ api = remote?.agentPresets
90
+ } catch (cause) {
91
+ setLoad({ status: 'unavailable', message: cause instanceof Error ? cause.message : String(cause) })
92
+ return
93
+ }
94
+ if (!api) {
95
+ setLoad({ status: 'unavailable', message: '宿主没有提供 agent preset 名单(dsh-agent-preset-registry 未挂载)。' })
96
+ return
97
+ }
98
+ setLoad({ status: 'loading' })
99
+ void (async () => {
100
+ try {
101
+ const listed = await api.list()
102
+ if (!listed.ok) {
103
+ setLoad({ status: 'unavailable', message: listed.message ?? '读取 preset 名单失败' })
104
+ return
105
+ }
106
+ const rows = listed.value.presets ?? []
107
+ const row = rows.find(item => item.id === TITA_PRESET_ID) ?? null
108
+ const fallback = rows.find(item => item.isDefault) ?? null
109
+ let prompt: string | null = null
110
+ // 我们那个 preset 不在时,预览**默认 preset** 的提示词:至少让用户看见
111
+ // 「现在这个 Agent 被交代了什么」,而不是一片空白。
112
+ const target = row ?? fallback
113
+ if (target) {
114
+ const read = await api.read(target.id)
115
+ if (read.ok) prompt = readPersonaPrompt(read.value.content)
116
+ }
117
+ setLoad({ status: 'ready', row, rows, prompt, defaultRow: fallback })
118
+ } catch (cause) {
119
+ setLoad({ status: 'unavailable', message: cause instanceof Error ? cause.message : String(cause) })
120
+ }
121
+ })()
122
+ }, [remote])
123
+
124
+ useEffect(() => { refresh() }, [refresh])
125
+
126
+ /**
127
+ * 在系统文件管理器里打开 preset 的真源目录。
128
+ *
129
+ * 宿主会先验证这个路径确实映射到它自己的文件系统上(`verifyDesktopPath`),验证
130
+ * 不过就拒 —— 那种情况下这里不装作成功:按钮旁边那句话会给出路径,用户仍能自己
131
+ * 复制过去打开。所以「打开失败」不是一个死胡同。
132
+ */
133
+ const openPresetDir = async (): Promise<void> => {
134
+ if (!presetDir) return
135
+ let api: AgentPresetRemote['session']
136
+ try {
137
+ api = remote?.session
138
+ } catch (cause) {
139
+ setOpen({ status: 'failed', message: cause instanceof Error ? cause.message : String(cause) })
140
+ return
141
+ }
142
+ if (!api || typeof api.openWorkspacePath !== 'function') {
143
+ setOpen({ status: 'failed', message: '宿主没有提供在系统文件管理器里打开路径的能力(session 命名空间未挂载),请手动打开下面这个路径。' })
144
+ return
145
+ }
146
+ setOpen({ status: 'opening' })
147
+ try {
148
+ const result = await api.openWorkspacePath({ path: presetDir, action: 'reveal' })
149
+ setOpen(result.ok
150
+ ? { status: 'idle' }
151
+ : { status: 'failed', message: result.message ?? '宿主拒绝了这个路径,请手动打开下面这个路径。' })
152
+ } catch (cause) {
153
+ setOpen({ status: 'failed', message: `${cause instanceof Error ? cause.message : String(cause)} 请手动打开下面这个路径。` })
154
+ }
155
+ }
156
+
157
+ const meta = load.status === 'loading'
158
+ ? '正在读取…'
159
+ : load.status === 'unavailable'
160
+ ? '不可用'
161
+ : load.row ? '已生效' : '不在名单里'
162
+
163
+ return <section className="tita-settingsSection">
164
+ <div className="tita-settingsSectionHeader">
165
+ <div>
166
+ <h3>Agent 提示词</h3>
167
+ <p>这个 Agent 被交代了什么。真源是随插件分发的 preset 目录,改那里,不在这里。</p>
168
+ </div>
169
+ <div className="tita-settingMeta" role="status" aria-live="polite">
170
+ {load.status === 'loading' && <LoaderCircle size={11} className="tita-spin" />}
171
+ {meta}
172
+ </div>
173
+ </div>
174
+
175
+ {load.status === 'unavailable' && <div className="tita-settingsError" role="alert"><AlertCircle size={13} />{load.message}</div>}
176
+
177
+ {/* 主操作放在 roster 之外:名单读不到时提示词照样要能改 —— 那两件事互不依赖。 */}
178
+ <div className="tita-presetActions">
179
+ <Button
180
+ variant="outline"
181
+ size="sm"
182
+ onClick={() => void openPresetDir()}
183
+ disabled={!presetDir || !presetDirExists || open.status === 'opening'}
184
+ >
185
+ {open.status === 'opening' ? <LoaderCircle size={12} className="tita-spin" /> : <FolderOpen size={12} />}
186
+ 打开 preset 目录
187
+ </Button>
188
+ {presetDir && <code className="tita-presetPath" title={presetDir}>{presetDir}</code>}
189
+ </div>
190
+ <small className="tita-settingHint tita-presetHint">
191
+ 改 <code>agent.cordis.yml</code> 的 <code>persona</code> 行 → 跑 <code>pnpm sync:preset</code> 编译进
192
+ <code>cordis.patch.yml</code> → 重启 DSH → <strong>新建一个会话</strong>(preset 在会话创建时固定)。
193
+ </small>
194
+
195
+ {open.status === 'failed' && <div className="tita-settingsError" role="alert"><AlertCircle size={13} />{open.message}</div>}
196
+
197
+ {presetDir && !presetDirExists && <p className="tita-presetNote tita-presetNoteBad">
198
+ 这个安装里没有 <code>preset/</code> 真源目录。它随插件分发,所以只可能是插件装漏了这一段 ——
199
+ 重装插件即可,这里没有可以打开的东西。
200
+ </p>}
201
+
202
+ {load.status === 'ready' && <>
203
+ <dl className="tita-presetFacts">
204
+ <div>
205
+ <dt>{TITA_PRESET_ID}</dt>
206
+ <dd>
207
+ {load.row
208
+ ? <>{load.row.name ?? load.row.id}<span className="tita-presetTrust">随包</span></>
209
+ : <span className="tita-presetMissing">不在名单里</span>}
210
+ </dd>
211
+ </div>
212
+ </dl>
213
+
214
+ {!load.row && <p className="tita-presetNote">
215
+ 名单里没有 <code>{TITA_PRESET_ID}</code>。它随插件分发 —— <code>cordis.patch.yml</code> 里那条
216
+ <code>@deepseek-ai/dsh-agent-preset</code> 声明就是它,所以只有两种可能:声明与
217
+ <code>preset/{TITA_PRESET_ID}/</code> 真源不同步(跑 <code>pnpm sync:preset</code>),或者 DSH 还没重启。
218
+ </p>}
219
+
220
+ {load.row?.broken && <p className="tita-presetNote tita-presetNoteBad">
221
+ 它现在组合不出会话,挂载停在 <code>{load.row.broken}</code>。
222
+ 冒号前是没挂上的那一行(只说明组合走到哪里),冒号后才是它抛出的错误原文 —— 按那句话改对应的代码或配置。
223
+ 改的是 <code>preset/{TITA_PRESET_ID}/agent.cordis.yml</code> 就先跑 <code>pnpm sync:preset</code>,然后重启 DSH。
224
+ </p>}
225
+
226
+ {/* 「哪个 preset 是新会话的默认」是 DSH 设置里的一项。这里不重复那个控件,只在
227
+ 它不是默认时把事实说出来并指明去哪儿改 —— 否则用户会以为这个部署已经换成它了。 */}
228
+ {load.row && !load.row.isDefault && <p className="tita-presetNote">
229
+ 它还不是默认 —— 新建的会话仍在跑「{load.defaultRow?.name ?? load.defaultRow?.id ?? '宿主组合'}」。
230
+ 默认 preset 是 DSH 设置里的一项,在那儿改。
231
+ </p>}
232
+
233
+ {load.prompt
234
+ ? <div className="tita-promptPreview">
235
+ <div className="tita-promptPreviewHead">
236
+ <span>提示词正文</span>
237
+ <small>persona 行的 prefix · {load.prompt.length} 字符</small>
238
+ </div>
239
+ {/* 只读的等宽块,不是输入框:正文的真源是那份 YAML,这里给它一个可选中复制的视图。 */}
240
+ <pre className="tita-promptBody" tabIndex={0} aria-label="Agent 提示词正文">{load.prompt}</pre>
241
+ </div>
242
+ : <p className="tita-presetNote">
243
+ 这个 preset 的组合里没有 <code>persona</code> 提示词行,所以它用的是部署级的默认人格。
244
+ </p>}
245
+ </>}
246
+ </section>
247
+ }