dsh-data-domain-knowledge 0.1.0-alpha.3 → 0.1.0-alpha.4
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 +126 -22
- package/cordis.patch.yml +25 -16
- package/lib/client.js +2144 -1571
- package/lib/doc-page.js +40 -19
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +27 -2
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +7 -4
- package/lib/types/convert/ingest.d.ts.map +1 -1
- package/lib/types/convert/ingest.js +52 -14
- package/lib/types/convert/ingest.js.map +1 -1
- package/lib/types/convert/types.d.ts +2 -2
- package/lib/types/convert/types.d.ts.map +1 -1
- package/lib/types/convert/types.js +2 -2
- package/lib/types/convert/types.js.map +1 -1
- package/lib/types/diagnosis/cards.js +2 -2
- package/lib/types/diagnosis/run.d.ts +7 -1
- package/lib/types/diagnosis/run.d.ts.map +1 -1
- package/lib/types/diagnosis/run.js +8 -2
- package/lib/types/diagnosis/run.js.map +1 -1
- package/lib/types/diagnosis/seed-source.js +1 -1
- package/lib/types/diagnosis/seed.d.ts +1 -1
- package/lib/types/diagnosis/seed.js +1 -1
- package/lib/types/diagnosis/store.d.ts +13 -31
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +33 -71
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +1 -1
- package/lib/types/diagnosis/types.js +1 -1
- package/lib/types/extract-model.d.ts +67 -27
- package/lib/types/extract-model.d.ts.map +1 -1
- package/lib/types/extract-model.js +225 -97
- package/lib/types/extract-model.js.map +1 -1
- package/lib/types/extract.d.ts +16 -0
- package/lib/types/extract.d.ts.map +1 -1
- package/lib/types/extract.js +19 -2
- package/lib/types/extract.js.map +1 -1
- package/lib/types/fs-atomic.d.ts +3 -4
- package/lib/types/fs-atomic.d.ts.map +1 -1
- package/lib/types/fs-atomic.js +3 -4
- package/lib/types/fs-atomic.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +302 -89
- package/lib/types/index.js.map +1 -1
- package/lib/types/intent/types.js +1 -1
- package/lib/types/intent/types.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts +13 -10
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +18 -27
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/layout.d.ts +3 -3
- package/lib/types/layout.d.ts.map +1 -1
- package/lib/types/layout.js +4 -4
- package/lib/types/layout.js.map +1 -1
- package/lib/types/migrate.d.ts +1 -1
- package/lib/types/migrate.js +2 -2
- package/lib/types/migrate.js.map +1 -1
- package/lib/types/model-text.d.ts +99 -0
- package/lib/types/model-text.d.ts.map +1 -0
- package/lib/types/model-text.js +103 -0
- package/lib/types/model-text.js.map +1 -0
- package/lib/types/present.d.ts +239 -0
- package/lib/types/present.d.ts.map +1 -0
- package/lib/types/present.js +255 -0
- package/lib/types/present.js.map +1 -0
- package/lib/types/recall-rank.d.ts +37 -0
- package/lib/types/recall-rank.d.ts.map +1 -1
- package/lib/types/recall-rank.js +41 -1
- package/lib/types/recall-rank.js.map +1 -1
- package/lib/types/runtime.d.ts +419 -78
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +2104 -334
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/settings-schema.d.ts +2 -0
- package/lib/types/settings-schema.d.ts.map +1 -1
- package/lib/types/settings-schema.js +2 -0
- package/lib/types/settings-schema.js.map +1 -1
- package/lib/types/source-images.d.ts +118 -0
- package/lib/types/source-images.d.ts.map +1 -0
- package/lib/types/source-images.js +288 -0
- package/lib/types/source-images.js.map +1 -0
- package/lib/types/types.d.ts +316 -39
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js +57 -11
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-finalize.d.ts +140 -0
- package/lib/types/wiki-finalize.d.ts.map +1 -0
- package/lib/types/wiki-finalize.js +269 -0
- package/lib/types/wiki-finalize.js.map +1 -0
- package/lib/types/wiki-types.d.ts +2 -1
- package/lib/types/wiki-types.d.ts.map +1 -1
- package/lib/types/wiki-types.js +13 -7
- package/lib/types/wiki-types.js.map +1 -1
- package/package.json +3 -3
- package/preset/tita-data-agent/agent.cordis.yml +25 -16
- package/src/client/DrawerSection.tsx +18 -0
- package/src/client/KnowledgeBaseTab.tsx +294 -104
- package/src/client/KnowledgePanel.tsx +4 -3
- package/src/client/KnowledgeToolRow.tsx +331 -0
- package/src/client/PreviewSections.tsx +115 -34
- package/src/client/RecallPanel.tsx +418 -127
- package/src/client/TypeDefinitionDrawer.tsx +82 -16
- package/src/client/WikiCardReview.tsx +266 -0
- package/src/client/WikiFlowCard.tsx +57 -95
- package/src/client/WikiTypeEditor.tsx +6 -20
- package/src/client/arch/AgentArchitectureGraph.tsx +22 -135
- package/src/client/architecture.css +22 -27
- package/src/client/architecture.tsx +12 -12
- package/src/client/asset-images.ts +125 -0
- package/src/client/attribution/AttributionTab.tsx +4 -10
- package/src/client/attribution/StageChrome.tsx +8 -24
- package/src/client/convert/ConvertPanel.tsx +30 -3
- package/src/client/doc-page.tsx +6 -27
- package/src/client/index.ts +18 -0
- package/src/client/problem-domains.ts +1 -1
- package/src/client/styles.css +80 -11
- package/src/convert/host.ts +27 -2
- package/src/convert/ingest.ts +61 -18
- package/src/convert/types.ts +2 -2
- package/src/diagnosis/cards.ts +2 -2
- package/src/diagnosis/run.ts +8 -2
- package/src/diagnosis/seed-source.ts +1 -1
- package/src/diagnosis/seed.ts +1 -1
- package/src/diagnosis/store.ts +32 -79
- package/src/diagnosis/types.ts +2 -2
- package/src/extract-model.ts +254 -99
- package/src/extract.ts +35 -3
- package/src/fs-atomic.ts +3 -4
- package/src/index.ts +338 -88
- package/src/intent/types.ts +1 -1
- package/src/knowledge-bases.ts +31 -32
- package/src/layout.ts +4 -4
- package/src/migrate.ts +2 -2
- package/src/model-text.ts +126 -0
- package/src/present.ts +367 -0
- package/src/recall-rank.ts +57 -2
- package/src/runtime.ts +2282 -374
- package/src/settings-schema.ts +2 -0
- package/src/source-images.ts +326 -0
- package/src/types.ts +354 -47
- package/src/wiki-finalize.ts +310 -0
- package/src/wiki-types.ts +13 -7
- package/lib/types/proposals.d.ts +0 -166
- package/lib/types/proposals.d.ts.map +0 -1
- package/lib/types/proposals.js +0 -238
- package/lib/types/proposals.js.map +0 -1
- package/src/client/ProposalReview.tsx +0 -313
- package/src/proposals.ts +0 -337
package/lib/types/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname, extname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
6
6
|
import { DEFAULT_KNOWLEDGE_CONFIG, } from './types.js';
|
|
7
|
-
import { KnowledgeRuntime, success, failure } from './runtime.js';
|
|
7
|
+
import { KnowledgeRuntime, MAX_IMPORT_SOURCES, success, failure, } from './runtime.js';
|
|
8
8
|
import { unwrapGroup } from './settings-volatile.js';
|
|
9
9
|
import { DOC_PAGE_CHARS, DOC_PAGE_CHARS_MAX, documentContentPage } from './document-page.js';
|
|
10
10
|
import { agentLayout, defaultAgentRoot, ensureAgentLayout, intentRoot, knowledgeBaseRoot, legacyDefaultKnowledgeRoot, memoryRoot, presetSourceDir, resourceRegistry, resourceRoot, } from './layout.js';
|
|
@@ -47,6 +47,8 @@ export { CONVERT_SETTINGS_SCHEMA, INTENT_SETTINGS_SCHEMA };
|
|
|
47
47
|
* 客户端按**路径**寻址(`{op:'set', path:['intent','categories']}`)。
|
|
48
48
|
*/
|
|
49
49
|
import { Config } from './config-schema.js';
|
|
50
|
+
import { buildModelRequest, collectModelText } from './model-text.js';
|
|
51
|
+
import { addSourceCallView, addSourceMetaOf, addSourceResultMarkdown, importCallView, importMetaOf, importResultMarkdown, recallCallView, recallMetaOf, recallResultMarkdown, rebuildCallView, rebuildMetaOf, rebuildResultMarkdown, writeCardsCallView, writeCardsMetaOf, writeCardsResultMarkdown, } from './present.js';
|
|
50
52
|
export { Config };
|
|
51
53
|
/**
|
|
52
54
|
* The live multi-knowledge-base registry, set by `apply()`.
|
|
@@ -58,9 +60,42 @@ export { Config };
|
|
|
58
60
|
*/
|
|
59
61
|
let activeKnowledgeBases;
|
|
60
62
|
export function currentKnowledgeBases() { return activeKnowledgeBases; }
|
|
61
|
-
function output() {
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
function output(presentation) {
|
|
64
|
+
return {
|
|
65
|
+
schema: { type: 'json' },
|
|
66
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
|
|
67
|
+
...(presentation?.meta ? { presentationMeta: presentation.meta } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function tool(name, description, parameters, execute, presentation) {
|
|
71
|
+
return defineTool({
|
|
72
|
+
name, description, parameters: parameters, output: output(presentation), execute: execute,
|
|
73
|
+
...(presentation?.call ? { presentCall: presentation.call } : {}),
|
|
74
|
+
...(presentation?.result ? { presentResult: presentation.result } : {}),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 信封 → `data`。
|
|
79
|
+
*
|
|
80
|
+
* 工具的 `execute` 返回的是一整个 `Envelope`(`{ok, status, data, warnings, error}`),
|
|
81
|
+
* 而呈现层要的是里面的 `data`。失败时 `data` 仍可能是空壳(比如
|
|
82
|
+
* `IMPORT_ALL_FAILED` 带着逐条原因),所以这里不抛,只取。
|
|
83
|
+
*/
|
|
84
|
+
function dataOf(value) {
|
|
85
|
+
if (value && typeof value === 'object' && 'data' in value) {
|
|
86
|
+
const data = value.data;
|
|
87
|
+
if (data && typeof data === 'object')
|
|
88
|
+
return data;
|
|
89
|
+
}
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
/** 一条 `presentResult` 的通用形状:用现成的 `meta` 换成一段人话,换不到就交回原内容。 */
|
|
93
|
+
function resultViewOf(markdownOf) {
|
|
94
|
+
return (_args, result) => {
|
|
95
|
+
if (result.isError || !result.meta)
|
|
96
|
+
return undefined;
|
|
97
|
+
return { card: 'generic', content: [{ type: 'text', text: markdownOf(result.meta) }] };
|
|
98
|
+
};
|
|
64
99
|
}
|
|
65
100
|
function envelopeError(code, message, suggestion) { return failure({}, code, message, false, suggestion); }
|
|
66
101
|
/**
|
|
@@ -339,8 +374,21 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
339
374
|
* 取不到 `llm` 或默认模型时返回 undefined,链路**退回纯规则抽取**:模型是增强,
|
|
340
375
|
* 不是前提。
|
|
341
376
|
*/
|
|
342
|
-
const
|
|
377
|
+
const modelSeamFor = () => {
|
|
378
|
+
/**
|
|
379
|
+
* **只能用 `ctx.get(name)`,不能用属性访问。**
|
|
380
|
+
*
|
|
381
|
+
* cordis 的 `ReflectService.get` 注释写得很清楚:「Read a service from the store
|
|
382
|
+
* without the inject requirement」;而 `ctx.<name>` 走的是 Proxy 的 get trap,
|
|
383
|
+
* 名字不在本插件的 `inject` 里时**直接抛** `cannot get property "..." without inject`。
|
|
384
|
+
* 本插件只 inject 了 tools / webServer / systemPrompt,所以属性访问不是「可能取不到」,
|
|
385
|
+
* 而是「一取就炸」—— 在 `rebuild` 里炸掉一整次编译。
|
|
386
|
+
*
|
|
387
|
+
* 本机探针(cordis 4.0.4)实测:`ctx.get` 能看见后来才注册的服务,属性访问抛异常。
|
|
388
|
+
*/
|
|
343
389
|
const llm = ctx.get('llm');
|
|
390
|
+
if (!llm)
|
|
391
|
+
return { why: '当前上下文里取不到 `llm` 服务(模型调用入口)' };
|
|
344
392
|
/**
|
|
345
393
|
* `agentDefaultModel` 是一个**服务实例**(`dsh-agent-default-model` 的
|
|
346
394
|
* `AgentDefaultModelConfig extends Service`),默认模型要从它的
|
|
@@ -351,33 +399,54 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
351
399
|
* 结果是永远拿到 undefined —— 模型接缝静默失效。
|
|
352
400
|
*/
|
|
353
401
|
const service = ctx.get('agentDefaultModel');
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
402
|
+
if (!service)
|
|
403
|
+
return { why: '当前上下文里取不到 `agentDefaultModel` 服务 —— 默认模型那一行可能没挂上' };
|
|
404
|
+
const route = service.currentSelection?.();
|
|
405
|
+
if (!route?.provider || !route?.model) {
|
|
406
|
+
return { why: `默认模型没有选好(provider=${route?.provider ?? '空'} / model=${route?.model ?? '空'})` };
|
|
407
|
+
}
|
|
357
408
|
const { provider, model, reasoningEffort } = route;
|
|
358
|
-
return async ({ system, prompt }) => {
|
|
359
|
-
let text = '';
|
|
360
|
-
const request = { provider, model, ...(reasoningEffort ? { reasoningEffort } : {}), system, messages: [{ role: 'user', content: prompt }] };
|
|
361
|
-
for await (const chunk of llm.stream(request)) {
|
|
362
|
-
if (chunk?.type === 'text-delta' && typeof chunk.text === 'string')
|
|
363
|
-
text += chunk.text;
|
|
364
|
-
}
|
|
365
|
-
return text;
|
|
366
|
-
};
|
|
367
|
-
};
|
|
368
|
-
const runtimeOptions = () => {
|
|
369
|
-
const modelCall = modelCallFor();
|
|
370
409
|
return {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
410
|
+
call: async ({ system, prompt, sessionId }) => {
|
|
411
|
+
/**
|
|
412
|
+
* 请求形状与收流都交给 `model-text.ts`(0044 实机验证第三~五批)。
|
|
413
|
+
*
|
|
414
|
+
* 三处都踩过坑,前两处还都伪装成同一句告警「模型输出不是 JSON 对象」:
|
|
415
|
+
* 请求里 `content` 写成字符串(平台在适配器里调 `.some()`,于是
|
|
416
|
+
* `content.some is not a function`),只挑 `text-delta` 收流(平台把失败表达成
|
|
417
|
+
* 终结性的 finish chunk,不是抛异常),以及**没带 `sessionId`**
|
|
418
|
+
* (OpenCode 系路由回 400 `MissingSessionID`)。
|
|
419
|
+
*/
|
|
420
|
+
const request = buildModelRequest({
|
|
421
|
+
provider, model, reasoningEffort, system, prompt,
|
|
422
|
+
// 运行时按知识库给;接缝被单独调用时(测试)退回一个稳定值,
|
|
423
|
+
// 而不是留空 —— 留空会在 OpenCode 路由上 400。
|
|
424
|
+
sessionId: sessionId ?? 'knowledge:default',
|
|
425
|
+
});
|
|
426
|
+
return collectModelText(llm.stream(request));
|
|
427
|
+
},
|
|
379
428
|
};
|
|
380
429
|
};
|
|
430
|
+
const runtimeOptions = () => ({
|
|
431
|
+
...(defaultBaseSourceTypes ? { sourceTypes: defaultBaseSourceTypes } : {}),
|
|
432
|
+
...(defaultBaseWikiSchema ? { wikiSchema: defaultBaseWikiSchema } : {}),
|
|
433
|
+
// 0029:家当目录。配置里三个路径为空时,运行时从这里解析模型与解释器。
|
|
434
|
+
resourceRoot: resourceRoot(config.agentRoot),
|
|
435
|
+
// 0032:记忆段。默认库的记忆落在 `<agentRoot>/memory/default`,与知识树平级。
|
|
436
|
+
memoryRoot: memoryRoot(config.agentRoot, DEFAULT_KB_ID),
|
|
437
|
+
/**
|
|
438
|
+
* #3b:模型接缝(实体抽取与归并)。
|
|
439
|
+
*
|
|
440
|
+
* **给的是函数,不是值**(0044)。profile 的组合顺序是「package.json 的 bundles →
|
|
441
|
+
* `cordis.patch.yml`」,而 `agent-default-model` 在 patch 层 —— 插件 apply 的时刻问
|
|
442
|
+
* `ctx.get('agentDefaultModel')` 只会得到 undefined,而定死之后就再也不会变:
|
|
443
|
+
* 实测的直接后果是编译一路走规则路径(正文按 `##` 小节切),而界面上没有任何提示。
|
|
444
|
+
*
|
|
445
|
+
* 传函数之后,每次真正要用模型时才问一遍 `ctx`;解析不到时它**必须说出为什么**,
|
|
446
|
+
* 那句话会原样出现在 `rebuild` 的 warnings 里。
|
|
447
|
+
*/
|
|
448
|
+
modelSeam: modelSeamFor,
|
|
449
|
+
});
|
|
381
450
|
/** 默认知识库的根:`<agentRoot>/domain_knowledge/default`。 */
|
|
382
451
|
const defaultBaseRoot = () => knowledgeBaseRoot(config.agentRoot, 'default');
|
|
383
452
|
let runtime = new KnowledgeRuntime(defaultBaseRoot(), config, runtimeOptions());
|
|
@@ -393,10 +462,14 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
393
462
|
registryFile: () => resourceRegistry(config.agentRoot),
|
|
394
463
|
defaultRuntime: () => runtime,
|
|
395
464
|
config: () => config,
|
|
465
|
+
// 0044:非默认库的 runtime 也要拿到模型接缝。只传 `config` 不够 —— 默认库走上面
|
|
466
|
+
// 那个 `new KnowledgeRuntime(... runtimeOptions())`,别的库走注册表这条支路,
|
|
467
|
+
// 两边必须都带上,否则「新建一个库 → 导入文档」静默变成规则抽取。
|
|
468
|
+
runtimeOptions: () => ({ modelSeam: modelSeamFor }),
|
|
396
469
|
});
|
|
397
470
|
runtime.baseId = bases.defaultId;
|
|
398
471
|
defaultBaseSourceTypes = () => bases.sourceTypesOf(bases.defaultId);
|
|
399
|
-
defaultBaseWikiSchema = () => ({ types: bases.wikiTypesOf(bases.defaultId)
|
|
472
|
+
defaultBaseWikiSchema = () => ({ types: bases.wikiTypesOf(bases.defaultId) });
|
|
400
473
|
activeKnowledgeBases = bases;
|
|
401
474
|
const kbIdOf = (value) => {
|
|
402
475
|
if (value === undefined || value === null)
|
|
@@ -598,13 +671,25 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
598
671
|
// 0032/0033:意图的配置与留痕落进 `<agentRoot>/intent`(与知识、记忆平级)。
|
|
599
672
|
// 0.1.7:意图的设置不再走命名空间,随本 entry 的 config 一起下来。
|
|
600
673
|
intentHost = applyIntent(ctx, { intentRoot: () => intentRoot(config.agentRoot), config: intentGroup });
|
|
601
|
-
|
|
674
|
+
/**
|
|
675
|
+
* 需要本轮用户审批的写工具。
|
|
676
|
+
*
|
|
677
|
+
* 0044 换过一批名字:`write_knowledge_card`(只接受已确认提议)退役了,因为正文现在
|
|
678
|
+
* 只读、卡片由编译直接写入;`list_card_proposals` 也退役了,因为待办队列不存在了。
|
|
679
|
+
* 新增的两个是**入库之后**的动作:加备注、review 抬分。
|
|
680
|
+
*/
|
|
681
|
+
const writeTools = new Set(['add_source', 'import_sources', 'propose_wiki_cards', 'annotate_wiki_card', 'review_wiki_card', 'set_knowledge_status', 'rebuild_knowledge', 'record_feedback']);
|
|
602
682
|
ctx.on('tools/pre-execute', (exec, next) => {
|
|
603
683
|
if (writeTools.has(exec.name))
|
|
604
684
|
return Promise.resolve({ kind: 'ask', reason: '该操作会写入 Tita Knowledge Store,需要本轮用户审批。' });
|
|
605
685
|
return next();
|
|
606
686
|
});
|
|
607
|
-
ctx.tools.register(tool('recall_knowledge', 'Recall compiled knowledge: multi-route recall over wikiNotes (top_k, default from settings),
|
|
687
|
+
ctx.tools.register(tool('recall_knowledge', 'Recall compiled knowledge: multi-route recall over wikiNotes (top_k, default from settings), BERT-rerank the candidate pool, then assemble the context as **wiki cards** — notes are deduplicated by their card (a card keeps all its hit sections), 1-hop graph neighbours are added after that dedupe, and the whole pack is bounded by context_max_chars (default 20000). Anything over the budget goes to a temp file whose absolute path is in budget.overflow_path; read that file with your own file tools when you need more.', { query: { type: 'string', required: true }, scope: { type: 'string' }, limit: { type: 'integer' }, top_k: { type: 'integer' }, top_n: { type: 'integer' }, rerank: { type: 'boolean' }, context_max_chars: { type: 'integer', description: '这次召回的上下文字符上限;省略则用设置里的值(默认 20000,0 = 不限)。超出部分落在系统临时目录,响应里的 budget.overflow_path 就是它的绝对路径 —— 需要更多信息时直接读那个文件。' }, source_types: { type: 'array' }, include_problems: { type: 'boolean' }, mode: { type: 'string', enum: ['fts', 'vector', 'graph', 'hybrid'] }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, parent_subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.recall(args.query, args.top_k ?? args.limit, args.source_types ?? [], args.include_problems ?? false, args.mode, lineageOf(args, exec), { contextTopN: args.top_n, rerank: args.rerank, ...(typeof args.context_max_chars === 'number' ? { contextMaxChars: args.context_max_chars } : {}) })), {
|
|
688
|
+
call: recallCallView,
|
|
689
|
+
// 查询词从参数来,命中从结果来 —— 两者拼成一张能看懂的账本。
|
|
690
|
+
meta: (args, value) => recallMetaOf({ ...dataOf(value), query: args?.query }),
|
|
691
|
+
result: resultViewOf(recallResultMarkdown),
|
|
692
|
+
}));
|
|
608
693
|
ctx.tools.register(tool('check_knowledge_embeddings', 'Inspect the local embedding provider and binary/int8 vector index without loading the model.', { kb: { type: 'string' }, session_key: { type: 'string' } }, async (args) => withRuntime(args.kb, target => target.embeddingHealth())));
|
|
609
694
|
ctx.tools.register(tool('query_knowledge_graph', 'Query entities, neighbors, paths, lists and relation types in the compiled knowledge graph.', { action: { type: 'string', enum: ['entity', 'neighbors', 'path', 'list', 'types'], required: true }, entity_id: { type: 'string' }, id: { type: 'string' }, entity_type: { type: 'string' }, from_id: { type: 'string' }, to_id: { type: 'string' }, type: { type: 'string' }, status: { type: 'string' }, include_problem: { type: 'boolean' }, limit: { type: 'integer' }, depth: { type: 'integer' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, parent_subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.graphQuery(args.action, args.entity_id ?? args.id, args.entity_type ?? args.type, args.depth ?? 1, { fromId: args.from_id, toId: args.to_id, status: args.status, includeProblem: args.include_problem ?? false, limit: args.limit ?? 50 }, lineageOf(args, exec)))));
|
|
610
695
|
ctx.tools.register(tool('query_source_truth', 'Read SourceRaw (原始可追溯文档) with access audit. Returns the top max_chars characters (default and hard max 40000) shared across the matched sources. Each source carries its absolute file `path`, `returnedChars` and `totalChars`, and the envelope carries `truncated`. When truncated is true (or returnedChars < totalChars) the content is INCOMPLETE — read that `path` directly to get the rest rather than answering from a partial document.', { query: { type: 'string', required: true }, source_types: { type: 'array' }, limit: { type: 'integer' }, max_chars: { type: 'integer' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.sourceTruth(args.query, args.source_types ?? [], args.limit ?? 20, args.max_chars ?? 40000, lineageOf(args, exec)))));
|
|
@@ -637,40 +722,112 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
637
722
|
})),
|
|
638
723
|
})));
|
|
639
724
|
ctx.tools.register(tool('list_source_types', 'List the SourceRaw business classifications (the per-knowledge-base source_type registry) together with the built-in read-only classifier prompt. Call this before add_source whenever the classification is not obvious; the registry is editable per knowledge base, so custom ids such as test_unit may exist.', { kb: { type: 'string', description: '目标知识库 id;省略则用默认库' }, session_key: { type: 'string' } }, async (args) => withRuntime(args.kb, target => Promise.resolve(target.sourceTypeCatalog()))));
|
|
640
|
-
ctx.tools.register(tool('list_wiki_types', 'List the Wiki card types (the per-knowledge-base card schema) with their required sections, required fields, template card, and the
|
|
641
|
-
ctx.tools.register(tool('add_source', 'Add or update a SourceRaw document (原始可追溯文档). source_type is a per-knowledge-base business classification: call list_source_types first when unsure, and never guess silently. DSH requests one user approval before execution.', { source_type: { type: 'string', required: true, description: '来源业务分类 id(按业务场景,不是文件格式)。先调 list_source_types 拿注册表;拿不准就返回候选问用户,不要默认 document。' }, title: { type: 'string' }, content: { type: 'string', required: true }, external_id: { type: 'string' }, metadata: { type: 'json' }, source_id: { type: 'string' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.addSource({ sourceType: args.source_type, title: args.title, content: args.content, externalId: args.external_id, metadata: args.metadata, sourceId: args.source_id, lineage: lineageOf(args, exec) }))
|
|
725
|
+
ctx.tools.register(tool('list_wiki_types', 'List the Wiki card types (the per-knowledge-base card schema) with their required sections, required fields, template card, and the current review progress (how many cards are reviewed / unreviewed / flagged as anomalies). Call this before propose_wiki_cards: entity_type must be one of these ids, and the required sections/fields are what makes the card usable.', { kb: { type: 'string', description: '目标知识库 id;省略则用默认库' }, session_key: { type: 'string' } }, async (args) => withRuntime(args.kb, target => target.wikiTypeCatalog())));
|
|
726
|
+
ctx.tools.register(tool('add_source', 'Add or update a SourceRaw document (原始可追溯文档). source_type is a per-knowledge-base business classification: call list_source_types first when unsure, and never guess silently. DSH requests one user approval before execution.', { source_type: { type: 'string', required: true, description: '来源业务分类 id(按业务场景,不是文件格式)。先调 list_source_types 拿注册表;拿不准就返回候选问用户,不要默认 document。' }, title: { type: 'string' }, content: { type: 'string', required: true }, external_id: { type: 'string' }, metadata: { type: 'json' }, source_id: { type: 'string' }, allow_duplicate_title: { type: 'boolean', description: '默认 false:标题与已有来源重复时拒收(SOURCE_TITLE_DUPLICATED),要求改标题。传 true 才允许并存(落成 <标题>-<内容哈希前6位>.md,并回一条 warning)。' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.addSource({ sourceType: args.source_type, title: args.title, content: args.content, externalId: args.external_id, metadata: args.metadata, sourceId: args.source_id, ...(args.allow_duplicate_title === true ? { allowDuplicateTitle: true } : {}), lineage: lineageOf(args, exec) })), {
|
|
727
|
+
call: addSourceCallView,
|
|
728
|
+
meta: (args, value) => addSourceMetaOf(args, dataOf(value)),
|
|
729
|
+
result: resultViewOf(addSourceResultMarkdown),
|
|
730
|
+
}));
|
|
731
|
+
/**
|
|
732
|
+
* `import_sources` —— **批量导入 + 一次编译**(0045)。
|
|
733
|
+
*
|
|
734
|
+
* 在这之前,「让 Agent 批量编译入库」意味着 `add_source` × N 再 `rebuild_knowledge`:
|
|
735
|
+
* 十份文档就是十次审批弹窗、十次往返,而人真正要同意的是「把这十份导进去」这一件事。
|
|
736
|
+
* 这个工具把那件事做成一次调用。
|
|
737
|
+
*
|
|
738
|
+
* 它**不替代** `add_source`:单份文档仍然用那个(参数更少、审批理由更具体)。
|
|
739
|
+
* 两者的差别只在「一次多少份」,落盘路径、分类闸门、编译链路完全共用 ——
|
|
740
|
+
* 这个工具的 `execute` 就是循环调 `importSources`,而后者逐条调 `addSource`。
|
|
741
|
+
*
|
|
742
|
+
* `compile` 默认开:批量导入的意图几乎总是「进来就能用」。关掉它的场景是「先全部
|
|
743
|
+
* 落盘、稍后统一编译」——那时卡片还没生成,工具返回里会明说 `compile: null`。
|
|
744
|
+
*/
|
|
745
|
+
ctx.tools.register(tool('import_sources', 'Import many SourceRaw documents and compile them into Wiki cards in ONE call (one approval, one compile, per-source failures reported instead of aborting the batch). Use this instead of calling add_source repeatedly. source_type is a per-knowledge-base business classification: call list_source_types first when unsure. DSH requests one user approval before execution.', {
|
|
746
|
+
sources: { type: 'array', required: true, description: `来源数组,每项 { source_type, title?, content, external_id?, metadata?, allow_duplicate_title? };一次最多 ${MAX_IMPORT_SOURCES} 份,超了请分批。标题与已有来源重复的那一份会被拒(SOURCE_TITLE_DUPLICATED),逐条报出来而不是整批失败` },
|
|
747
|
+
compile: { type: 'boolean', description: '默认 true:写入后立刻编译成 wiki 卡片。false 表示只落盘' },
|
|
748
|
+
kb: { type: 'string' },
|
|
749
|
+
session_key: { type: 'string' },
|
|
750
|
+
task_id: { type: 'string' },
|
|
751
|
+
subtask_id: { type: 'string' },
|
|
752
|
+
run_id: { type: 'string' },
|
|
753
|
+
turn_id: { type: 'string' },
|
|
754
|
+
trace_id: { type: 'string' },
|
|
755
|
+
domain: { type: 'string' },
|
|
756
|
+
}, async (args, exec) => withRuntime(args.kb, target => target.importSources({
|
|
757
|
+
// 逐项**逐字段**点名:批量的输入是模型生成的,多带一个字段就多一分静默错位的可能。
|
|
758
|
+
sources: (Array.isArray(args.sources) ? args.sources : []).map(item => ({
|
|
759
|
+
sourceType: item?.source_type,
|
|
760
|
+
...(item?.title ? { title: item.title } : {}),
|
|
761
|
+
content: item?.content,
|
|
762
|
+
...(item?.external_id ? { externalId: item.external_id } : {}),
|
|
763
|
+
...(item?.metadata ? { metadata: item.metadata } : {}),
|
|
764
|
+
...(item?.allow_duplicate_title === true ? { allowDuplicateTitle: true } : {}),
|
|
765
|
+
})),
|
|
766
|
+
...(args.compile === false ? { compile: false } : {}),
|
|
767
|
+
lineage: lineageOf(args, exec),
|
|
768
|
+
})), {
|
|
769
|
+
call: importCallView,
|
|
770
|
+
meta: (_args, value) => importMetaOf(dataOf(value)),
|
|
771
|
+
result: resultViewOf(importResultMarkdown),
|
|
772
|
+
}));
|
|
642
773
|
/**
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
774
|
+
* `propose_wiki_cards` —— 把一批卡片**直接写进 `02_wiki`**(工具名不变,语义在 0044 改了)。
|
|
775
|
+
*
|
|
776
|
+
* 名字保持不变是因为它是冻结契约;但描述必须说清它现在真的会写。0040 v1.5 的员工是
|
|
777
|
+
* 「草案停在待办队列里等人确认」,0044 撤掉了那道门:**入库在前、review 在后**。
|
|
778
|
+
* 判据见 `runtime.writeCards` 的注释 —— 确认门把知识挡在了门外,而 review 的价值
|
|
779
|
+
* 不需要靠排队来实现。
|
|
780
|
+
*
|
|
781
|
+
* 正常动线是:`add_source` 落来源 → `rebuild_knowledge` 让编译按 schema 抽实体入库。
|
|
782
|
+
* 这个工具服务于另一条路:Agent 手里已经有结构化的卡片草案(例如从一次排查里读出来的),
|
|
783
|
+
* 直接写进来,不必再走一遍抽取。
|
|
784
|
+
*/
|
|
785
|
+
ctx.tools.register(tool('propose_wiki_cards', 'Write Wiki cards compiled from a SourceRaw directly into 02_wiki. The confirmation gate was retired in 0044: cards land immediately and are marked unreviewed, so a human reviews them AFTER entry (review raises the card score; the body itself is read-only). Call list_wiki_types first to pick entity_type and to see the required sections/fields. DSH requests one user approval before execution.', { source_id: { type: 'string', required: true, description: '这批卡片引用的那条 SourceRaw 的 source_id(先 add_source)' }, proposals: { type: 'array', required: true, description: '卡片数组,每项 { action?, entity_type, entity_id, title, summary?, content, source_refs?, metadata? };content 是卡片正文(按该类型的必填小节组织)' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.writeCards({
|
|
786
|
+
sourceId: args.source_id,
|
|
787
|
+
cards: (Array.isArray(args.proposals) ? args.proposals : []).map(item => ({ action: item?.action, entityType: item?.entity_type, entityId: item?.entity_id, title: item?.title, summary: item?.summary, content: item?.content, sourceRefs: item?.source_refs, metadata: item?.metadata })),
|
|
788
|
+
lineage: lineageOf(args, exec),
|
|
789
|
+
})), {
|
|
790
|
+
call: writeCardsCallView,
|
|
791
|
+
meta: (args, value) => writeCardsMetaOf(dataOf(value), String(args?.source_id ?? '')),
|
|
792
|
+
result: resultViewOf(writeCardsResultMarkdown),
|
|
793
|
+
}));
|
|
653
794
|
/**
|
|
654
|
-
* `
|
|
795
|
+
* `annotate_wiki_card` —— 给卡片加**人工备注**(0044)。
|
|
655
796
|
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
797
|
+
* 这是「wiki 不可修改」的对偶:正文改不了,但判断改得了。`kind: 'anomaly'` 是
|
|
798
|
+
* 「这里有异常点」的显式标记(数字与原始文档对不上、两处口径互相矛盾),它会把卡片的
|
|
799
|
+
* 有效评分压到 `ANOMALY_SCORE` —— 异常卡片照样召回得到,只是排在后面,
|
|
800
|
+
* 而不是被藏起来(藏起来就没人会去修它)。
|
|
658
801
|
*/
|
|
659
|
-
ctx.tools.register(tool('
|
|
802
|
+
ctx.tools.register(tool('annotate_wiki_card', 'Attach a human annotation to a Wiki card, or remove one. Wiki bodies are read-only — this is how a person records a judgement about a card ("this number disagrees with the source", "these two definitions conflict"). kind=anomaly additionally lowers the card score for recall ranking while keeping it retrievable. DSH requests one user approval before execution.', { entity_id: { type: 'string', required: true }, entity_type: { type: 'string' }, kind: { type: 'string', enum: ['note', 'anomaly'], description: '默认 note;anomaly 表示异常点' }, note: { type: 'string', description: '备注正文;撤销一条时省略并给 remove_id' }, author: { type: 'string' }, source_ref: { type: 'string', description: '这条备注针对哪一份来源(可选)' }, remove_id: { type: 'string', description: '要撤销的备注 id' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.annotateCard({
|
|
803
|
+
entityId: args.entity_id,
|
|
804
|
+
...(args.entity_type ? { entityType: args.entity_type } : {}),
|
|
805
|
+
...(args.kind ? { kind: args.kind } : {}),
|
|
806
|
+
...(args.note ? { note: args.note } : {}),
|
|
807
|
+
...(args.author ? { author: args.author } : {}),
|
|
808
|
+
...(args.source_ref ? { sourceRef: args.source_ref } : {}),
|
|
809
|
+
...(args.remove_id ? { removeId: args.remove_id } : {}),
|
|
810
|
+
lineage: lineageOf(args, exec),
|
|
811
|
+
}))));
|
|
660
812
|
/**
|
|
661
|
-
* `
|
|
813
|
+
* `review_wiki_card` —— review 一张卡片,抬高它的召回评分(0044)。
|
|
662
814
|
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
815
|
+
* 它**不是准入闸门**:卡片在 review 之前就已经入库、可召回了。两个动作都可逆 ——
|
|
816
|
+
* review 是一个可以被推翻的判断,不是一次性的凭证。
|
|
665
817
|
*/
|
|
666
|
-
ctx.tools.register(tool('
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
818
|
+
ctx.tools.register(tool('review_wiki_card', 'Mark a Wiki card as reviewed (raises its score, which mildly boosts it in recall ranking) or undo that. Review happens AFTER entry and is reversible — it is not a gate. DSH requests one user approval before execution.', { entity_id: { type: 'string', required: true }, entity_type: { type: 'string' }, action: { type: 'string', enum: ['review', 'unreview'], required: true }, reviewer: { type: 'string' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.reviewCard({
|
|
819
|
+
entityId: args.entity_id,
|
|
820
|
+
...(args.entity_type ? { entityType: args.entity_type } : {}),
|
|
821
|
+
action: args.action === 'unreview' ? 'unreview' : 'review',
|
|
822
|
+
...(args.reviewer ? { reviewer: args.reviewer } : {}),
|
|
823
|
+
lineage: lineageOf(args, exec),
|
|
824
|
+
}))));
|
|
672
825
|
ctx.tools.register(tool('set_knowledge_status', 'Change a Wiki card status. problem requires problem_note and recovery must pass through unreviewed. DSH requests one user approval before execution.', { entity_id: { type: 'string', required: true }, status: { type: 'string', enum: ['unreviewed', 'draft', 'reviewed', 'active', 'deprecated', 'problem'], required: true }, problem_note: { type: 'string' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.setStatus(args.entity_id, args.status, args.problem_note, lineageOf(args, exec)))));
|
|
673
|
-
ctx.tools.register(tool('rebuild_knowledge', 'Rebuild schema/wiki/compiled knowledge indexes. DSH requests one user approval before execution.', { only: { type: 'string', enum: ['all', 'schema', 'wiki', 'embeddings'] }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.rebuild(args.only ?? 'all', lineageOf(args, exec)))
|
|
826
|
+
ctx.tools.register(tool('rebuild_knowledge', 'Rebuild schema/wiki/compiled knowledge indexes. DSH requests one user approval before execution.', { only: { type: 'string', enum: ['all', 'schema', 'wiki', 'embeddings'] }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.rebuild(args.only ?? 'all', lineageOf(args, exec))), {
|
|
827
|
+
call: rebuildCallView,
|
|
828
|
+
meta: (args, value) => rebuildMetaOf(args?.only ?? 'all', dataOf(value)),
|
|
829
|
+
result: resultViewOf(rebuildResultMarkdown),
|
|
830
|
+
}));
|
|
674
831
|
ctx.tools.register(tool('record_feedback', 'Record bounded feedback for a knowledge result. DSH requests one user approval before execution.', { feedback_id: { type: 'string' }, unit_id: { type: 'string' }, rating: { type: 'integer', required: true }, comment: { type: 'string' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.recordFeedback({ feedbackId: args.feedback_id, unitId: args.unit_id, rating: args.rating, comment: args.comment, sessionKey: args.session_key, lineage: lineageOf(args, exec) }))));
|
|
675
832
|
ctx.inject(['webServer'], webCtx => {
|
|
676
833
|
const web = webCtx;
|
|
@@ -692,8 +849,9 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
692
849
|
/**
|
|
693
850
|
* 智能归因图谱的存储句柄,按 rootDir 缓存。
|
|
694
851
|
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
852
|
+
* 归因知识住在各库自己的 `01_source_raw/agent_diagnosis_case/` 与
|
|
853
|
+
* `02_wiki/diagnosis/` 里(`05_diagnosis/` 已于 v1.1 撤销),和它的 wiki 同源;
|
|
854
|
+
* 缓存键用 rootDir 而不是 baseId,因为独立 runtime(未挂到知识库列表上的)也要能读写。
|
|
697
855
|
*/
|
|
698
856
|
const storeOf = (target) => target.diagnosisStore();
|
|
699
857
|
/**
|
|
@@ -711,8 +869,6 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
711
869
|
graph: snapshot.graph, cases: snapshot.cases, issues: snapshot.issues,
|
|
712
870
|
seeded: snapshot.seeded, migrated: snapshot.migrated, source: snapshot.source,
|
|
713
871
|
canLoadSample: snapshot.canLoadSample, problems: snapshot.problems,
|
|
714
|
-
// 待确认的提议条数:归因这一栏只挂角标,review 在「知识库 → Wiki」的待办区。
|
|
715
|
-
proposals: snapshot.proposals,
|
|
716
872
|
});
|
|
717
873
|
const routes = [
|
|
718
874
|
{ kind: 'exact', path: '/knowledge/stats', handler: async (req, res) => {
|
|
@@ -731,7 +887,14 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
731
887
|
const topN = url.searchParams.get('top_n');
|
|
732
888
|
const rerankParam = url.searchParams.get('rerank');
|
|
733
889
|
// 响应里始终带 routes + backend,前端据此显示「关键词 / 向量 / 知识图谱」三路状态。
|
|
734
|
-
return send(res, 200, await resolved.runtime.recall(url.searchParams.get('query') ?? '', topK === null ? undefined : Number(topK), sourceTypes, url.searchParams.get('include_problems') === 'true', (url.searchParams.get('mode') ?? undefined), lineageFromUrl(url), {
|
|
890
|
+
return send(res, 200, await resolved.runtime.recall(url.searchParams.get('query') ?? '', topK === null ? undefined : Number(topK), sourceTypes, url.searchParams.get('include_problems') === 'true', (url.searchParams.get('mode') ?? undefined), lineageFromUrl(url), {
|
|
891
|
+
...(topN === null ? {} : { contextTopN: Number(topN) }),
|
|
892
|
+
...(rerankParam === null ? {} : { rerank: rerankParam !== 'false' && rerankParam !== '0' }),
|
|
893
|
+
...(url.searchParams.get('context_max_chars') === null ? {} : { contextMaxChars: Number(url.searchParams.get('context_max_chars')) }),
|
|
894
|
+
// 面板账本(0050):只在 HTTP 上给,工具调用不带 —— 几十行候选分数进
|
|
895
|
+
// 模型上下文是纯噪声,而面板要的就是「哪条被丢了、各自多少分」。
|
|
896
|
+
...(url.searchParams.get('detail') === 'true' ? { detail: true } : {}),
|
|
897
|
+
}));
|
|
735
898
|
} },
|
|
736
899
|
{ kind: 'exact', path: '/knowledge/embedding/health', handler: async (req, res) => {
|
|
737
900
|
const resolved = resolveBase(query(req).searchParams.get('kb'));
|
|
@@ -766,6 +929,37 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
766
929
|
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)));
|
|
767
930
|
}
|
|
768
931
|
} },
|
|
932
|
+
/**
|
|
933
|
+
* 批量导入 + 一次编译(0045)。
|
|
934
|
+
*
|
|
935
|
+
* 与 `import_sources` 工具**同一条实现**(`runtime.importSources`):
|
|
936
|
+
* 「面板能做的 Agent 也能做、Agent 能做的面板也能做」是这个插件一贯的规矩,
|
|
937
|
+
* 差异只允许出现在审批与呈现上,不允许出现在行为上。
|
|
938
|
+
*/
|
|
939
|
+
{ kind: 'exact', path: '/knowledge/import', handler: async (req, res) => {
|
|
940
|
+
try {
|
|
941
|
+
if (req.method !== 'POST')
|
|
942
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 knowledge endpoint 只接受 POST'));
|
|
943
|
+
const body = await readBody(req, 4_000_000);
|
|
944
|
+
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'));
|
|
945
|
+
if ('failure' in resolved)
|
|
946
|
+
return sendEnvelope(res, resolved.failure);
|
|
947
|
+
return send(res, 200, await resolved.runtime.importSources({
|
|
948
|
+
sources: (Array.isArray(body.sources) ? body.sources : []).map((item) => ({
|
|
949
|
+
sourceType: item?.source_type,
|
|
950
|
+
...(item?.title ? { title: String(item.title) } : {}),
|
|
951
|
+
content: item?.content,
|
|
952
|
+
...(item?.external_id ? { externalId: String(item.external_id) } : {}),
|
|
953
|
+
...(item?.metadata && typeof item.metadata === 'object' ? { metadata: item.metadata } : {}),
|
|
954
|
+
})),
|
|
955
|
+
...(body.compile === false ? { compile: false } : {}),
|
|
956
|
+
lineage: lineageOf(body),
|
|
957
|
+
}));
|
|
958
|
+
}
|
|
959
|
+
catch (cause) {
|
|
960
|
+
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)));
|
|
961
|
+
}
|
|
962
|
+
} },
|
|
769
963
|
{ kind: 'exact', path: '/knowledge/sources', handler: async (req, res) => {
|
|
770
964
|
try {
|
|
771
965
|
if (req.method === 'POST') {
|
|
@@ -773,7 +967,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
773
967
|
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'));
|
|
774
968
|
if ('failure' in resolved)
|
|
775
969
|
return sendEnvelope(res, resolved.failure);
|
|
776
|
-
return send(res, 200, await resolved.runtime.addSource({ sourceType: body.source_type ?? 'document', title: body.title, content: body.content, externalId: body.external_id, metadata: body.metadata, sourceId: body.source_id, lineage: lineageOf(body) }));
|
|
970
|
+
return send(res, 200, await resolved.runtime.addSource({ sourceType: body.source_type ?? 'document', title: body.title, content: body.content, externalId: body.external_id, metadata: body.metadata, sourceId: body.source_id, ...(body.allow_duplicate_title === true ? { allowDuplicateTitle: true } : {}), lineage: lineageOf(body) }));
|
|
777
971
|
}
|
|
778
972
|
if (req.method !== 'GET')
|
|
779
973
|
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 knowledge endpoint 只接受 GET 或 POST'));
|
|
@@ -875,7 +1069,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
875
1069
|
*
|
|
876
1070
|
* 它是一次显式的**写**:用户在这个库上还没录任何排查知识时,界面上那枚
|
|
877
1071
|
* 「载入内置示例」按钮打的就是这里。单独一条路由而不是让读取顺手写 ——
|
|
878
|
-
* 读接口写用户的 `
|
|
1072
|
+
* 读接口写用户的 `01_source_raw/` 正是上一版的毛病。
|
|
879
1073
|
*/
|
|
880
1074
|
{ kind: 'exact', path: '/diagnosis/sample', handler: async (req, res) => {
|
|
881
1075
|
if (req.method !== 'POST')
|
|
@@ -894,11 +1088,10 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
894
1088
|
return send(res, 200, success({ cases: (await storeOf(resolved.runtime).snapshot()).cases }));
|
|
895
1089
|
} },
|
|
896
1090
|
/**
|
|
897
|
-
* 一次排查结束:落一条 `agent_diagnosis_case` 的 SourceRaw
|
|
1091
|
+
* 一次排查结束:落一条 `agent_diagnosis_case` 的 SourceRaw,并写入一张案例卡片。
|
|
898
1092
|
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
* 与 `proposalId` —— 界面要能说清「记录已经留下了,但那张卡片还在等人 review」。
|
|
1093
|
+
* 0044:卡片不再只提议 —— 它直接入库并标成 `unreviewed`。响应给 `sourceId` 与
|
|
1094
|
+
* `entityId`,界面据此说清「记录留下了,卡片也建了,等人 review」。
|
|
902
1095
|
*/
|
|
903
1096
|
{ kind: 'exact', path: '/diagnosis/run', handler: async (req, res) => {
|
|
904
1097
|
if (req.method !== 'POST')
|
|
@@ -913,40 +1106,48 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
913
1106
|
}
|
|
914
1107
|
const outcome = await storeOf(resolved.runtime).recordRun(record);
|
|
915
1108
|
if (outcome.problems.length > 0 && outcome.sourceId === undefined) {
|
|
916
|
-
return send(res, 200, failure({ sourceId: '',
|
|
1109
|
+
return send(res, 200, failure({ sourceId: '', entityId: '' }, 'RUN_WRITE_FAILED', outcome.problems.join(';'), true));
|
|
917
1110
|
}
|
|
918
|
-
return send(res, 200, success({ sourceId: outcome.sourceId ?? '',
|
|
1111
|
+
return send(res, 200, success({ sourceId: outcome.sourceId ?? '', entityId: outcome.entityId ?? '' }, outcome.problems));
|
|
919
1112
|
} },
|
|
920
1113
|
/**
|
|
921
|
-
*
|
|
1114
|
+
* 入库之后的两个人工动作(0044):加备注、review 抬分。
|
|
922
1115
|
*
|
|
923
|
-
*
|
|
924
|
-
*
|
|
1116
|
+
* 它们取代了原来的 `/knowledge/card-proposals/review`。差别是**对象变了**:
|
|
1117
|
+
* 以前 review 的是一条还没落库的提议(通过 = 允许入库),现在 review 的是一张
|
|
1118
|
+
* 已经在 `02_wiki` 里、已经能召回的卡片(通过 = 抬高它的召回评分)。
|
|
1119
|
+
* 前者是准入,后者是事后判断 —— 后者可逆,前者不可逆。
|
|
925
1120
|
*/
|
|
926
|
-
{ kind: 'exact', path: '/knowledge/card-
|
|
927
|
-
|
|
928
|
-
|
|
1121
|
+
{ kind: 'exact', path: '/knowledge/card-annotate', handler: async (req, res) => {
|
|
1122
|
+
if (req.method !== 'POST')
|
|
1123
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 endpoint 只接受 POST'));
|
|
1124
|
+
const body = await readBody(req, 2_000_000);
|
|
1125
|
+
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'));
|
|
929
1126
|
if ('failure' in resolved)
|
|
930
1127
|
return sendEnvelope(res, resolved.failure);
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1128
|
+
return sendEnvelope(res, await resolved.runtime.annotateCard({
|
|
1129
|
+
entityId: String(body.entity_id ?? ''),
|
|
1130
|
+
...(body.entity_type ? { entityType: String(body.entity_type) } : {}),
|
|
1131
|
+
...(body.kind === 'anomaly' ? { kind: 'anomaly' } : body.kind === 'note' ? { kind: 'note' } : {}),
|
|
1132
|
+
...(body.note ? { note: String(body.note) } : {}),
|
|
1133
|
+
...(body.author ? { author: String(body.author) } : {}),
|
|
1134
|
+
...(body.source_ref ? { sourceRef: String(body.source_ref) } : {}),
|
|
1135
|
+
...(body.remove_id ? { removeId: String(body.remove_id) } : {}),
|
|
1136
|
+
lineage: lineageOf(body),
|
|
1137
|
+
}));
|
|
935
1138
|
} },
|
|
936
|
-
{ kind: 'exact', path: '/knowledge/card-
|
|
1139
|
+
{ kind: 'exact', path: '/knowledge/card-review', handler: async (req, res) => {
|
|
937
1140
|
if (req.method !== 'POST')
|
|
938
1141
|
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 endpoint 只接受 POST'));
|
|
939
1142
|
const body = await readBody(req, 2_000_000);
|
|
940
1143
|
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'));
|
|
941
1144
|
if ('failure' in resolved)
|
|
942
1145
|
return sendEnvelope(res, resolved.failure);
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
action,
|
|
1146
|
+
return sendEnvelope(res, await resolved.runtime.reviewCard({
|
|
1147
|
+
entityId: String(body.entity_id ?? ''),
|
|
1148
|
+
...(body.entity_type ? { entityType: String(body.entity_type) } : {}),
|
|
1149
|
+
action: body.action === 'unreview' ? 'unreview' : 'review',
|
|
947
1150
|
...(body.reviewer ? { reviewer: String(body.reviewer) } : {}),
|
|
948
|
-
...(body.reason ? { reason: String(body.reason) } : {}),
|
|
949
|
-
...(body.patch ? { patch: body.patch } : {}),
|
|
950
1151
|
lineage: lineageOf(body),
|
|
951
1152
|
}));
|
|
952
1153
|
} },
|
|
@@ -1018,7 +1219,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1018
1219
|
if (rest.length === 2) {
|
|
1019
1220
|
if (method === 'PATCH') {
|
|
1020
1221
|
const body = await readBody(req);
|
|
1021
|
-
const updated = await bases.update(baseId, { name: body.name, description: body.description, sourceTypes: body.sourceTypes, wikiTypes: body.wikiTypes,
|
|
1222
|
+
const updated = await bases.update(baseId, { name: body.name, description: body.description, sourceTypes: body.sourceTypes, wikiTypes: body.wikiTypes, resetWikiTypes: body.resetWikiTypes === true });
|
|
1022
1223
|
if (!updated.ok)
|
|
1023
1224
|
return sendEnvelope(res, updated);
|
|
1024
1225
|
return send(res, 200, success({ base: updated.data }, updated.warnings));
|
|
@@ -1138,7 +1339,19 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1138
1339
|
const card = await resolved.runtime.readCard(entityId, entityType);
|
|
1139
1340
|
if (!card)
|
|
1140
1341
|
return sendEnvelope(res, failure({}, 'CARD_NOT_FOUND', `知识卡片不存在: ${entityId}`, true));
|
|
1141
|
-
|
|
1342
|
+
// 正文里的图要能在面板里显示,就得给 URL(`imagePaths` 是磁盘路径,浏览器
|
|
1343
|
+
// 加载不了)。与 SourceRaw 详情同一种形状,两个预览抽屉共用一套解析。
|
|
1344
|
+
const assets = await resolved.runtime.assetsOfCard(card);
|
|
1345
|
+
/**
|
|
1346
|
+
* 相关卡片(0049):出链与入链。
|
|
1347
|
+
*
|
|
1348
|
+
* 由服务端算,而不是让客户端去解析正文里那一块 `## 相关` —— 正文里那块是
|
|
1349
|
+
* 给人 `grep` 的(`[[类型/标题]]` 在面板里点不开),而抽屉里这几行是**可点的
|
|
1350
|
+
* 入口**。两处读同一个计划(`planRelated`),所以不会出现「文件里写着 A、
|
|
1351
|
+
* 界面上列着 B」。
|
|
1352
|
+
*/
|
|
1353
|
+
const related = await resolved.runtime.relatedOfCard(card);
|
|
1354
|
+
return send(res, 200, success({ card, assets, related }));
|
|
1142
1355
|
}
|
|
1143
1356
|
if (rest.length === 3 && rest[2] === 'versions') {
|
|
1144
1357
|
if (method !== 'GET')
|