@dsh-libing/knowledge-base 0.1.3 → 0.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4
4
+
5
+ - 支持 Solo Team 长期 Agent 的受管资产选择;在原 owner 检查作用域,不复制存储。普通会话保持现有行为。
6
+
3
7
  ## 0.1.3
4
8
 
5
9
  - 增加有容量上限的 manifest / 完整正文检索缓存,保持正文子串匹配与评分;每次检查文件变化,返回证据仍复读原文核验哈希。
package/README.md CHANGED
@@ -84,3 +84,7 @@ DOCX 压缩包、上传流、图片和 PDF 渲染均有限制;PDF 在可取消
84
84
  图片预处理由独立 worker 执行,同一进程全部知识库与插件实例共用一个名额,最多 32 个等待者;等待和运行均可取消。每张图最多 32 MiB / 5000 万像素,单张输出最多 8 MiB;总览长边与裁片边长最多 4096,裁片最多 64(默认 12)。`imageTimeoutMs` 默认 60 秒、最多 120 秒,超时或卸载会终止 worker 后释放名额。worker 逐张输出并等待官方附件保存完成,裁片仅复制所需区域。该限制约束图片预处理的位图并发,不能作为整个宿主进程的 RSS 上限;PDF 引擎和模型调用仍有各自的生命周期与限制。
85
85
 
86
86
  检索保留原有完整正文的子串匹配与评分,包括只在正文出现的词。每个 runtime 使用 LRU 缓存(最多 256 个文件、估算载荷 32 MiB),复用 manifest 与完整正文的小写检索视图;超大文件不驻留,超出容量按需重新读盘。每次仍验证知识库可访问性、根路径及文件身份/大小/纳秒修改和变更时间;首次读正文核验哈希,返回候选证据时另外复读原文核验。解析后的 manifest 冻结,构建继承文档使用独立副本。当前 revision 指针不缓存,发布、回收、恢复、彻底删除和关闭会清空缓存。冷检索仍扫描所有片段,缓存载荷预算不是宿主进程总内存限制,也不替代可信本机权限边界。
87
+
88
+ ## 受管 Agent 的知识选择(0.1.4)
89
+
90
+ 公开可选 `libingKnowledgeCatalog` 提供库 ID/名称,不读取其他插件私有存储。Solo Team 0.2.0 提供 `libingKnowledgeAccess` 的实际 Session 绑定;本 owner 在每次调用检查库 ID(含唯一名称解析),过滤清单,拒绝未选库和模型建库/任务管理。已发布版本及原文证据规则保持。失去受管选择服务时不静默开放所有库;普通会话沿用原功能。此处不是 OS 文件 ACL。
@@ -0,0 +1,23 @@
1
+ /** Optional managed-Agent selection is enforced by the knowledge owner on every read. */
2
+ import { type Context } from '@deepseek-ai/cordis';
3
+ import type { Agent } from '@deepseek-ai/dsh-agent';
4
+ import type { Runtime } from './runtime.ts';
5
+ declare module '@deepseek-ai/cordis' {
6
+ interface Context {
7
+ libingKnowledgeAccess: {
8
+ resolveSession(sessionId: string): readonly string[] | undefined;
9
+ };
10
+ libingKnowledgeCatalog: {
11
+ readonly version: 1;
12
+ list(): Promise<{
13
+ kbId: string;
14
+ name: string;
15
+ }[]>;
16
+ };
17
+ }
18
+ }
19
+ export declare function registerKnowledgeCatalog(ctx: Context, runtime: Runtime): void;
20
+ export declare function selectedKnowledge(runtime: Runtime, agent: Agent | undefined, tool: string, raw: Record<string, unknown>): Promise<{
21
+ allowed?: readonly string[];
22
+ kbId?: string;
23
+ }>;
@@ -0,0 +1,28 @@
1
+ /** Optional managed-Agent selection is enforced by the knowledge owner on every read. */
2
+ import { Service } from '@deepseek-ai/cordis';
3
+ export function registerKnowledgeCatalog(ctx, runtime) {
4
+ class Catalog extends Service {
5
+ version = 1;
6
+ constructor() { super(ctx, 'libingKnowledgeCatalog'); }
7
+ async list() { return (await runtime.store.list()).map(item => ({ kbId: item.kbId, name: item.name })); }
8
+ }
9
+ new Catalog();
10
+ }
11
+ export async function selectedKnowledge(runtime, agent, tool, raw) {
12
+ const allowed = agent ? runtime.models.ctx.get('libingKnowledgeAccess')?.resolveSession(agent.id) : undefined;
13
+ if (allowed === undefined) {
14
+ if (agent && /^solo:(?:chat|standing-run):/u.test(agent.session.header.agentPreset ?? ''))
15
+ throw new Error('Managed knowledge selection is unavailable');
16
+ return {};
17
+ }
18
+ if (!['kb_catalog', 'kb_search', 'kb_read', 'kb_read_image'].includes(tool))
19
+ throw new Error('This Agent cannot administer knowledge libraries');
20
+ if (raw.kbId === undefined && tool === 'kb_catalog')
21
+ return { allowed };
22
+ if (typeof raw.kbId !== 'string')
23
+ throw new Error('A bound library is required');
24
+ const kb = await runtime.store.kb(raw.kbId);
25
+ if (!allowed.includes(kb.kbId))
26
+ throw new Error('Knowledge library is not bound to this Agent');
27
+ return { allowed, kbId: kb.kbId };
28
+ }
package/lib/chunks.js CHANGED
@@ -78,7 +78,8 @@ function metadata(chunk) {
78
78
  }
79
79
  /** Split enriched, source-preserving blocks; each fragment retains its original block identity. */
80
80
  export function createChunks(document, docInfo, config) {
81
- const nodes = [{ nodeId: 'root', title: docInfo.title, level: 0, parentId: null, headingPath: [], children: [], blocks: [] }], stack = [nodes[0]];
81
+ const nodes = [{ nodeId: 'root', title: docInfo.title, level: 0, parentId: null, headingPath: [], children: [], blocks: [] }];
82
+ const stack = [nodes[0]];
82
83
  const ignored = new Set(document.blocks.filter(b => b.type === 'figure' && b.figure?.status === 'ignored').map(b => b.occurrenceId));
83
84
  let number = 0;
84
85
  for (const block of document.blocks) {
@@ -88,7 +89,8 @@ export function createChunks(document, docInfo, config) {
88
89
  if (block.type === 'heading') {
89
90
  while (stack.length > 1 && required(stack.at(-1), '章节').level >= block.level)
90
91
  stack.pop();
91
- const parent = required(stack.at(-1), '父章节'), node = { nodeId: `s-${String(++number).padStart(4, '0')}`, title: block.text, level: block.level, parentId: parent.nodeId, headingPath: [...parent.headingPath, block.text], children: [], blocks: [], anchor: block.anchor };
92
+ const parent = required(stack.at(-1), '父章节');
93
+ const node = { nodeId: `s-${String(++number).padStart(4, '0')}`, title: block.text, level: block.level, parentId: parent.nodeId, headingPath: [...parent.headingPath, block.text], children: [], blocks: [], anchor: block.anchor };
92
94
  parent.children.push(node.nodeId);
93
95
  nodes.push(node);
94
96
  stack.push(node);
@@ -102,7 +104,18 @@ export function createChunks(document, docInfo, config) {
102
104
  for (let i = 0; i < node.blocks.length; i++) {
103
105
  const block = required(node.blocks[i], '正文块');
104
106
  const budget = Math.max(128, config.chunkTargetChars - chars(metadata({ docId: docInfo.docId, headingPath: node.headingPath })) - 400);
105
- const parts = block.type === 'table' ? splitTable(block, budget) : ['paragraph', 'list', 'note'].includes(block.type) ? splitParagraph(block, budget) : [block];
107
+ let parts;
108
+ if (block.type === 'table') {
109
+ parts = splitTable(block, budget);
110
+ }
111
+ else {
112
+ if (['paragraph', 'list', 'note'].includes(block.type)) {
113
+ parts = splitParagraph(block, budget);
114
+ }
115
+ else {
116
+ parts = [block];
117
+ }
118
+ }
106
119
  for (const part of parts) {
107
120
  const unit = { blocks: [part] };
108
121
  if (part.type === 'figure' && node.blocks[i + 1]?.figureRef === part.occurrenceId) {
@@ -131,9 +144,18 @@ export function createChunks(document, docInfo, config) {
131
144
  if (!group.length)
132
145
  return;
133
146
  const chunkId = `c-${docInfo.docId}-${String(chunks.length + 1).padStart(5, '0')}`;
134
- const blocks = group.flatMap(u => u.blocks), body = heading + group.map(render).join('\n\n') + '\n';
147
+ const blocks = group.flatMap(u => u.blocks);
148
+ const body = heading + group.map(render).join('\n\n') + '\n';
135
149
  const figureResults = blocks.filter(b => b.type === 'figure').map(b => required(b.figure, '图片处理结果'));
136
- const value = { content: '', fullChars: 0, bodyChars: 0, contentHash: '', summary: '', keywords: [], chunkId, docId: docInfo.docId, sourceHash: docInfo.sourceHash, sourceVersion: docInfo.sourceVersion || '', title: node.title, nodeId: node.nodeId, headingPath: node.headingPath, blockRange: [required(blocks[0], '起始块').blockId, required(blocks.at(-1), '终止块').blockId], assetRefs: [...new Set(blocks.flatMap(b => [b.assetId, b.sourcePreviewAssetId, ...b.sourcePreviewAssetIds || []].filter((x) => typeof x === 'string' && Boolean(x))))], tableRefs: [...new Set(blocks.filter(b => b.type === 'table').map(b => b.blockId))], relatedChunks: [], reviewStatus: blocks.some(b => b.needsReview) || figureResults.some(f => !f || ['failed', 'skipped'].includes(f.status) || f.needsReview && !f.humanReviewed) ? 'needs-review' : figureResults.some(f => !f.humanReviewed) ? 'generated' : 'source', path: posix.join(dir, chunkId + '.md'), body, segments: group.map(u => ({ text: render(u), blockIds: u.blocks.map(b => b.blockId), type: required(u.blocks[0], '语义块').type, ...document.sourceFormat === 'pdf' ? { sourceLocations: sourceLocations(u.blocks) } : {} })), anchors: blocks.flatMap(b => b.anchor ? [b.anchor] : []) };
150
+ const value = { content: '', fullChars: 0, bodyChars: 0, contentHash: '', summary: '', keywords: [], chunkId, docId: docInfo.docId, sourceHash: docInfo.sourceHash, sourceVersion: docInfo.sourceVersion || '', title: node.title, nodeId: node.nodeId, headingPath: node.headingPath, blockRange: [required(blocks[0], '起始块').blockId, required(blocks.at(-1), '终止块').blockId], assetRefs: [...new Set(blocks.flatMap(b => [b.assetId, b.sourcePreviewAssetId, ...b.sourcePreviewAssetIds || []].filter((x) => typeof x === 'string' && Boolean(x))))], tableRefs: [...new Set(blocks.filter(b => b.type === 'table').map(b => b.blockId))], relatedChunks: [], reviewStatus: (() => {
151
+ if (blocks.some(b => b.needsReview) || figureResults.some(f => !f || ['failed', 'skipped'].includes(f.status) || f.needsReview && !f.humanReviewed)) {
152
+ return 'needs-review';
153
+ }
154
+ if (figureResults.some(f => !f.humanReviewed)) {
155
+ return 'generated';
156
+ }
157
+ return 'source';
158
+ })(), path: posix.join(dir, chunkId + '.md'), body, segments: group.map(u => ({ text: render(u), blockIds: u.blocks.map(b => b.blockId), type: required(u.blocks[0], '语义块').type, ...document.sourceFormat === 'pdf' ? { sourceLocations: sourceLocations(u.blocks) } : {} })), anchors: blocks.flatMap(b => b.anchor ? [b.anchor] : []) };
137
159
  if (document.sourceFormat === 'pdf') {
138
160
  value.sourceFormat = 'pdf';
139
161
  value.sourceLocations = sourceLocations(blocks);