@lemoncat7/dsh-knowledge 0.6.0-alpha.7 → 0.6.0-alpha.9

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.
@@ -0,0 +1,133 @@
1
+ const MAX_REQUEST_BYTES = 1_048_576;
2
+ const MAX_RESPONSE_BYTES = 10_485_760;
3
+ export function registerRemoteManagementProxy(ctx, prefix, current) {
4
+ const webServer = ctx.webServer ?? ctx.get('webServer');
5
+ if (webServer === undefined)
6
+ throw new Error('remote knowledge management requires the DSH webServer service');
7
+ return webServer.register({
8
+ kind: 'prefix',
9
+ path: prefix,
10
+ handler: async (req, res) => {
11
+ try {
12
+ await dispatch(prefix, current(), req, res);
13
+ }
14
+ catch (error) {
15
+ sendError(res, error);
16
+ }
17
+ },
18
+ });
19
+ }
20
+ async function dispatch(prefix, settings, req, res) {
21
+ assertManagementRequest(req);
22
+ if (settings.backend !== 'remote' || settings.remoteUrl === undefined || settings.remoteToken === undefined) {
23
+ throw httpError(409, '当前没有可用的远程知识库连接。');
24
+ }
25
+ const incoming = new URL(req.url ?? '/', 'http://knowledge.local');
26
+ const relative = incoming.pathname.slice(prefix.length).replace(/^\/+/, '');
27
+ const method = req.method ?? 'GET';
28
+ if (relative === 'service') {
29
+ if (method !== 'GET')
30
+ throw httpError(409, '请在中央 DSH 的知识库管理台中修改远程 API 状态。');
31
+ return sendJson(res, 200, {
32
+ publicApiEnabled: true,
33
+ publicApiPrefix: settings.remoteUrl,
34
+ remote: true,
35
+ });
36
+ }
37
+ if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method))
38
+ throw httpError(405, '不支持此远程管理请求方法。');
39
+ const base = settings.remoteUrl.endsWith('/') ? settings.remoteUrl : `${settings.remoteUrl}/`;
40
+ const target = new URL(`${relative}${incoming.search}`, base);
41
+ const body = method === 'GET' ? undefined : await readBody(req);
42
+ const controller = new AbortController();
43
+ const abort = () => { controller.abort(); };
44
+ req.once('aborted', abort);
45
+ try {
46
+ const response = await fetch(target, {
47
+ method,
48
+ headers: {
49
+ accept: 'application/json',
50
+ authorization: `Bearer ${settings.remoteToken}`,
51
+ ...body === undefined ? {} : { 'content-type': 'application/json' },
52
+ },
53
+ ...body === undefined ? {} : { body },
54
+ signal: controller.signal,
55
+ redirect: 'manual',
56
+ });
57
+ const declared = Number(response.headers.get('content-length') ?? 0);
58
+ if (Number.isFinite(declared) && declared > MAX_RESPONSE_BYTES)
59
+ throw httpError(502, '中央知识库响应过大。');
60
+ const payload = Buffer.from(await response.arrayBuffer());
61
+ if (payload.byteLength > MAX_RESPONSE_BYTES)
62
+ throw httpError(502, '中央知识库响应过大。');
63
+ res.writeHead(response.status, {
64
+ 'content-type': response.headers.get('content-type') ?? 'application/json; charset=utf-8',
65
+ 'content-length': payload.byteLength,
66
+ 'cache-control': 'no-store',
67
+ 'x-content-type-options': 'nosniff',
68
+ });
69
+ res.end(payload);
70
+ }
71
+ finally {
72
+ req.off('aborted', abort);
73
+ }
74
+ }
75
+ function assertManagementRequest(req) {
76
+ if (req.headers['x-dsh-knowledge-client'] !== 'management-web')
77
+ throw httpError(401, '缺少知识库管理客户端标识。');
78
+ const fetchSite = req.headers['sec-fetch-site'];
79
+ if (fetchSite !== undefined && fetchSite !== 'same-origin' && fetchSite !== 'none')
80
+ throw httpError(403, '已拒绝跨站知识库管理请求。');
81
+ const origin = req.headers.origin;
82
+ if (origin === undefined)
83
+ return;
84
+ let originHost;
85
+ try {
86
+ originHost = new URL(origin).host;
87
+ }
88
+ catch {
89
+ throw httpError(403, '知识库管理请求来源无效。');
90
+ }
91
+ const expectedHost = firstHeader(req.headers['x-forwarded-host']) ?? req.headers.host;
92
+ if (expectedHost === undefined || originHost.toLowerCase() !== expectedHost.toLowerCase())
93
+ throw httpError(403, '已拒绝跨域知识库管理请求。');
94
+ }
95
+ async function readBody(req) {
96
+ const declared = Number(req.headers['content-length'] ?? 0);
97
+ if (Number.isFinite(declared) && declared > MAX_REQUEST_BYTES)
98
+ throw httpError(413, '远程管理请求内容过大。');
99
+ const chunks = [];
100
+ let size = 0;
101
+ for await (const chunk of req) {
102
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
103
+ size += buffer.length;
104
+ if (size > MAX_REQUEST_BYTES)
105
+ throw httpError(413, '远程管理请求内容过大。');
106
+ chunks.push(buffer);
107
+ }
108
+ return size === 0 ? undefined : Buffer.concat(chunks).toString('utf8');
109
+ }
110
+ function firstHeader(value) {
111
+ const raw = Array.isArray(value) ? value[0] : value;
112
+ return raw?.split(',')[0]?.trim();
113
+ }
114
+ function sendError(res, error) {
115
+ const status = error !== null && typeof error === 'object' && typeof error.status === 'number'
116
+ ? error.status : 502;
117
+ const message = status >= 500 ? '无法连接中央知识库,请检查网络和服务状态。' : error instanceof Error ? error.message : '远程管理请求失败。';
118
+ sendJson(res, status, { error: message, code: `HTTP_${status}` });
119
+ }
120
+ function sendJson(res, status, body) {
121
+ const payload = Buffer.from(JSON.stringify(body));
122
+ res.writeHead(status, {
123
+ 'content-type': 'application/json; charset=utf-8',
124
+ 'content-length': payload.byteLength,
125
+ 'cache-control': 'no-store',
126
+ 'x-content-type-options': 'nosniff',
127
+ });
128
+ res.end(payload);
129
+ }
130
+ function httpError(status, message) {
131
+ return Object.assign(new Error(message), { status });
132
+ }
133
+ //# sourceMappingURL=management-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"management-proxy.js","sourceRoot":"","sources":["../src/management-proxy.ts"],"names":[],"mappings":"AAIA,MAAM,iBAAiB,GAAG,SAAS,CAAA;AACnC,MAAM,kBAAkB,GAAG,UAAU,CAAA;AAErC,MAAM,UAAU,6BAA6B,CAC3C,GAAuB,EACvB,MAAc,EACd,OAA0C;IAE1C,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,CAAC,WAAW,CAAoC,CAAA;IAC1F,IAAI,SAAS,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAA;IAC9G,OAAO,SAAS,CAAC,QAAQ,CAAC;QACxB,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;YAC1B,IAAI,CAAC;gBAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;YAAC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YAAC,CAAC;QAC7F,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,MAAc,EACd,QAAqC,EACrC,GAAoB,EACpB,GAAmB;IAEnB,uBAAuB,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,QAAQ,CAAC,OAAO,KAAK,QAAQ,IAAI,QAAQ,CAAC,SAAS,KAAK,SAAS,IAAI,QAAQ,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QAC5G,MAAM,SAAS,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAA;IACzC,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,wBAAwB,CAAC,CAAA;IAClE,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC3E,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAA;IAClC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,MAAM,KAAK,KAAK;YAAE,MAAM,SAAS,CAAC,GAAG,EAAE,+BAA+B,CAAC,CAAA;QAC3E,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE;YACxB,gBAAgB,EAAE,IAAI;YACtB,eAAe,EAAE,QAAQ,CAAC,SAAS;YACnC,MAAM,EAAE,IAAI;SACb,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,MAAM,SAAS,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;IACtG,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,SAAS,GAAG,CAAA;IAC7F,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAA;IAC7D,MAAM,IAAI,GAAG,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAA;IAC/D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;IACxC,MAAM,KAAK,GAAG,GAAG,EAAE,GAAG,UAAU,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC,CAAA;IAC1C,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAC1B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;YACnC,MAAM;YACN,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,aAAa,EAAE,UAAU,QAAQ,CAAC,WAAW,EAAE;gBAC/C,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE;aACpE;YACD,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;YACrC,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAA;QACF,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAA;QACpE,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,kBAAkB;YAAE,MAAM,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QAClG,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;QACzD,IAAI,OAAO,CAAC,UAAU,GAAG,kBAAkB;YAAE,MAAM,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QAC/E,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE;YAC7B,cAAc,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,iCAAiC;YACzF,gBAAgB,EAAE,OAAO,CAAC,UAAU;YACpC,eAAe,EAAE,UAAU;YAC3B,wBAAwB,EAAE,SAAS;SACpC,CAAC,CAAA;QACF,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IAClB,CAAC;YAAS,CAAC;QACT,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAC3B,CAAC;AACH,CAAC;AAED,SAAS,uBAAuB,CAAC,GAAoB;IACnD,IAAI,GAAG,CAAC,OAAO,CAAC,wBAAwB,CAAC,KAAK,gBAAgB;QAAE,MAAM,SAAS,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;IACrG,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC/C,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,aAAa,IAAI,SAAS,KAAK,MAAM;QAAE,MAAM,SAAS,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;IACzH,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAA;IACjC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAM;IAChC,IAAI,UAAkB,CAAA;IACtB,IAAI,CAAC;QAAC,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,MAAM,SAAS,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;IAAC,CAAC;IACxF,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAA;IACrF,IAAI,YAAY,KAAK,SAAS,IAAI,UAAU,CAAC,WAAW,EAAE,KAAK,YAAY,CAAC,WAAW,EAAE;QAAE,MAAM,SAAS,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;AAClI,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,GAAoB;IAC1C,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAA;IAC3D,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,iBAAiB;QAAE,MAAM,SAAS,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;IAClG,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClE,IAAI,IAAI,MAAM,CAAC,MAAM,CAAA;QACrB,IAAI,IAAI,GAAG,iBAAiB;YAAE,MAAM,SAAS,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;QACjE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACrB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AACxE,CAAC;AAED,SAAS,WAAW,CAAC,KAAoC;IACvD,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACnD,OAAO,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAA;AACnC,CAAC;AAED,SAAS,SAAS,CAAC,GAAmB,EAAE,KAAc;IACpD,MAAM,MAAM,GAAG,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAQ,KAA8B,CAAC,MAAM,KAAK,QAAQ;QACtH,CAAC,CAAE,KAA4B,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAA;IAC9C,MAAM,OAAO,GAAG,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAA;IAC9G,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,MAAM,EAAE,EAAE,CAAC,CAAA;AACnE,CAAC;AAED,SAAS,QAAQ,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa;IAClE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;IACjD,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE;QACpB,cAAc,EAAE,iCAAiC;QACjD,gBAAgB,EAAE,OAAO,CAAC,UAAU;QACpC,eAAe,EAAE,UAAU;QAC3B,wBAAwB,EAAE,SAAS;KACpC,CAAC,CAAA;IACF,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;AAClB,CAAC;AAED,SAAS,SAAS,CAAC,MAAc,EAAE,OAAe;IAChD,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;AACtD,CAAC"}
@@ -0,0 +1,7 @@
1
+ export interface KnowledgeServiceSettings {
2
+ publicApiEnabled: boolean;
3
+ }
4
+ export declare function serviceSettingsPath(connectionPath: string | undefined): string | undefined;
5
+ export declare function loadServiceSettings(path: string | undefined): KnowledgeServiceSettings | undefined;
6
+ export declare function storeServiceSettings(path: string, settings: KnowledgeServiceSettings): Promise<void>;
7
+ //# sourceMappingURL=service-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-settings.d.ts","sourceRoot":"","sources":["../src/service-settings.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,wBAAwB;IACvC,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAE1F;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,wBAAwB,GAAG,SAAS,CAalG;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAS1G"}
@@ -0,0 +1,39 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
4
+ import { dirname } from 'node:path';
5
+ export function serviceSettingsPath(connectionPath) {
6
+ return connectionPath === undefined ? undefined : `${connectionPath}.service.json`;
7
+ }
8
+ export function loadServiceSettings(path) {
9
+ if (path === undefined)
10
+ return undefined;
11
+ let value;
12
+ try {
13
+ value = JSON.parse(readFileSync(path, 'utf8'));
14
+ }
15
+ catch (error) {
16
+ if (error.code === 'ENOENT')
17
+ return undefined;
18
+ throw new Error(`failed to read knowledge service settings: ${error instanceof Error ? error.message : String(error)}`);
19
+ }
20
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
21
+ throw new Error('stored knowledge service settings must be a JSON object');
22
+ }
23
+ const enabled = value.publicApiEnabled;
24
+ if (typeof enabled !== 'boolean')
25
+ throw new Error('stored public API setting must be a boolean');
26
+ return { publicApiEnabled: enabled };
27
+ }
28
+ export async function storeServiceSettings(path, settings) {
29
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
30
+ const temporary = `${path}.${randomUUID()}.tmp`;
31
+ try {
32
+ await writeFile(temporary, `${JSON.stringify(settings, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
33
+ await rename(temporary, path);
34
+ }
35
+ finally {
36
+ await rm(temporary, { force: true }).catch(() => { });
37
+ }
38
+ }
39
+ //# sourceMappingURL=service-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-settings.js","sourceRoot":"","sources":["../src/service-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAMnC,MAAM,UAAU,mBAAmB,CAAC,cAAkC;IACpE,OAAO,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,cAAc,eAAe,CAAA;AACpF,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAwB;IAC1D,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACxC,IAAI,KAAc,CAAA;IAClB,IAAI,CAAC;QAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAA;IAAC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAC/E,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAA;QACxE,MAAM,IAAI,KAAK,CAAC,8CAA8C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACzH,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,OAAO,GAAI,KAAwC,CAAC,gBAAgB,CAAA;IAC1E,IAAI,OAAO,OAAO,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChG,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAA;AACtC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,IAAY,EAAE,QAAkC;IACzF,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC5D,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,UAAU,EAAE,MAAM,CAAA;IAC/C,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QACnH,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC/B,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IACtD,CAAC;AACH,CAAC"}
package/lib/web.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import type { RuntimeContextLike } from './runtime.js';
2
- export declare function registerKnowledgeWeb(ctx: RuntimeContextLike, webPath: string, apiPrefix: string): () => void;
2
+ export declare function registerKnowledgeWeb(ctx: RuntimeContextLike, webPath: string, apiPrefix: string, authMode?: 'bearer' | 'same-origin'): () => void;
3
3
  //# sourceMappingURL=web.d.ts.map
package/lib/web.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAiBtD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,kBAAkB,EACvB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,MAAM,IAAI,CAYZ"}
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAiBtD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,kBAAkB,EACvB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,QAAQ,GAAG,aAAwB,GAC5C,MAAM,IAAI,CAaZ"}
package/lib/web.js CHANGED
@@ -9,12 +9,13 @@ const ASSET_VERSION = createHash('sha256')
9
9
  .digest('hex')
10
10
  .slice(0, 12);
11
11
  const INDEX_TEMPLATE = readFileSync(new URL('../web/index.html', import.meta.url), 'utf8');
12
- export function registerKnowledgeWeb(ctx, webPath, apiPrefix) {
12
+ export function registerKnowledgeWeb(ctx, webPath, apiPrefix, authMode = 'bearer') {
13
13
  const webServer = ctx.webServer ?? ctx.get('webServer');
14
14
  if (webServer === undefined)
15
15
  throw new Error('exposeWeb requires the DSH webServer service');
16
16
  const index = Buffer.from(INDEX_TEMPLATE
17
17
  .replaceAll('__DSH_KNOWLEDGE_API_PREFIX__', escapeHtmlAttribute(apiPrefix))
18
+ .replaceAll('__DSH_KNOWLEDGE_AUTH_MODE__', escapeHtmlAttribute(authMode))
18
19
  .replaceAll('__DSH_KNOWLEDGE_WEB_PATH__', escapeHtmlAttribute(webPath))
19
20
  .replaceAll('__DSH_KNOWLEDGE_ASSET_VERSION__', ASSET_VERSION));
20
21
  return webServer.register({
package/lib/web.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lemoncat7/dsh-knowledge",
3
- "version": "0.6.0-alpha.7",
3
+ "version": "0.6.0-alpha.9",
4
4
  "description": "Installable DeepSeek Harness knowledge base plugin",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/web/app.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const API_BASE = document.querySelector('meta[name="dsh-knowledge-api"]')?.content || '/knowledge-api/v1'
2
+ const AUTH_MODE = document.querySelector('meta[name="dsh-knowledge-auth-mode"]')?.content || 'bearer'
2
3
  const TOKEN_KEY = 'dsh-knowledge.session-token'
3
4
  const TYPES = ['preference', 'fact', 'decision', 'procedure', 'lesson']
4
5
  const TYPE_LABELS = { preference: '偏好', fact: '事实', decision: '决策', procedure: '流程', lesson: '经验' }
@@ -51,6 +52,7 @@ const state = {
51
52
  candidates: [],
52
53
  candidateStatus: 'pending',
53
54
  tokens: [],
55
+ service: { publicApiEnabled: false, publicApiPrefix: '/knowledge-api/v1', remote: false },
54
56
  loading: false,
55
57
  error: '',
56
58
  }
@@ -132,6 +134,7 @@ function showToast(message, kind = '') {
132
134
 
133
135
  async function api(path, options = {}) {
134
136
  const headers = { accept: 'application/json', ...(options.body === undefined ? {} : { 'content-type': 'application/json' }) }
137
+ if (AUTH_MODE === 'same-origin') headers['x-dsh-knowledge-client'] = 'management-web'
135
138
  if (state.token) headers.authorization = `Bearer ${state.token}`
136
139
  const response = await fetch(`${API_BASE}/${path.replace(/^\/+/, '')}`, {
137
140
  method: options.method || 'GET',
@@ -153,6 +156,12 @@ async function api(path, options = {}) {
153
156
  }
154
157
 
155
158
  async function boot() {
159
+ if (AUTH_MODE === 'same-origin') {
160
+ state.token = ''
161
+ try { state.service = await api('service') } catch {}
162
+ await navigate('overview')
163
+ return
164
+ }
156
165
  if (!state.token) {
157
166
  renderLogin()
158
167
  return
@@ -213,7 +222,8 @@ function renderLogin(message = '') {
213
222
  function signOut() {
214
223
  sessionStorage.removeItem(TOKEN_KEY)
215
224
  Object.assign(state, { token: '', stats: null, overview: null, knowledgeBases: [], mounts: [], resolvedMounts: [], entries: [], documents: [], candidates: [], tokens: [] })
216
- renderLogin()
225
+ if (AUTH_MODE === 'same-origin') void boot()
226
+ else renderLogin()
217
227
  }
218
228
 
219
229
  async function navigate(view) {
@@ -229,7 +239,7 @@ async function navigate(view) {
229
239
  if (view === 'candidates') await loadCandidates()
230
240
  if (view === 'tokens') await loadTokens()
231
241
  } catch (error) {
232
- if (error.status === 401) return signOut()
242
+ if (error.status === 401 && AUTH_MODE === 'bearer') return signOut()
233
243
  state.error = friendlyError(error)
234
244
  } finally {
235
245
  state.loading = false
@@ -329,7 +339,9 @@ async function loadCandidates() {
329
339
  }
330
340
 
331
341
  async function loadTokens() {
332
- state.tokens = await api('tokens')
342
+ const [tokens, service] = await Promise.all([api('tokens'), api('service')])
343
+ state.tokens = tokens
344
+ state.service = service
333
345
  if (!state.stats) await refreshStats()
334
346
  }
335
347
 
@@ -435,7 +447,7 @@ function renderSidebar() {
435
447
  const pending = state.stats?.candidates.pending
436
448
  const navItems = [
437
449
  ['overview', '概览', '◫'], ['bases', '知识库', '▦'], ['entries', '文档', '◇'], ['candidates', '审核', '✓'], ['tokens', '访问管理', '⌁'],
438
- ]
450
+ ].filter(([id]) => id !== 'tokens' || !state.service.remote)
439
451
  return element('aside', { class: 'sidebar', 'aria-label': '知识库导航' },
440
452
  element('div', { class: 'brand' },
441
453
  element('div', { class: 'brand-mark', 'aria-hidden': 'true' }, 'K'),
@@ -447,8 +459,8 @@ function renderSidebar() {
447
459
  }, element('span', { class: 'nav-icon', 'aria-hidden': 'true' }, icon), label,
448
460
  id === 'candidates' && pending ? element('span', { class: 'nav-count', 'aria-label': `${pending} 条待审核` }, pending) : null))),
449
461
  element('div', { class: 'sidebar-footer' },
450
- element('div', { class: 'connection' }, element('span', { class: 'status-dot', 'aria-hidden': 'true' }), '知识库已连接'),
451
- actionButton('退出当前会话', signOut, 'ghost small'),
462
+ element('div', { class: 'connection' }, element('span', { class: 'status-dot', 'aria-hidden': 'true' }), state.service.remote ? '中央知识库已连接' : '本地知识库已连接'),
463
+ AUTH_MODE === 'bearer' ? actionButton('退出当前会话', signOut, 'ghost small') : null,
452
464
  ),
453
465
  )
454
466
  }
@@ -1145,7 +1157,25 @@ function renderCandidateCard(candidate) {
1145
1157
  }
1146
1158
 
1147
1159
  function renderTokens() {
1160
+ const apiAddress = new URL(state.service.publicApiPrefix, location.origin).href.replace(/\/$/, '')
1148
1161
  return element('section', { 'aria-labelledby': 'tokens-heading' },
1162
+ element('div', { class: 'api-access-card' },
1163
+ element('div', { class: 'api-access-heading' },
1164
+ element('div', {}, element('strong', {}, '远程知识库 API'), element('p', {}, state.service.publicApiEnabled
1165
+ ? '其他 DSH 客户端可以使用下面的地址和客户端令牌连接。'
1166
+ : '当前仅能在本机管理;开启后其他 DSH 客户端才能连接。')),
1167
+ badge(state.service.publicApiEnabled ? '已开放' : '未开放', state.service.publicApiEnabled ? 'success' : 'warning'),
1168
+ ),
1169
+ element('label', { class: 'api-address-label', for: 'knowledge-public-api-address' }, '客户端填写的服务器地址'),
1170
+ element('div', { class: 'api-address-row' },
1171
+ Object.assign(element('input', { id: 'knowledge-public-api-address', class: 'input', readonly: true, value: apiAddress }), { value: apiAddress }),
1172
+ actionButton('复制地址', () => copyText(apiAddress, 'API 地址已复制。'), 'small'),
1173
+ ),
1174
+ element('div', { class: 'api-access-actions' },
1175
+ element('small', {}, state.service.publicApiEnabled ? '关闭后已有客户端会立即无法连接,本地管理不受影响。' : '开启远程 API 后,请为每台客户端创建独立令牌。'),
1176
+ actionButton(state.service.publicApiEnabled ? '关闭远程 API' : '开启远程 API', togglePublicApi, state.service.publicApiEnabled ? 'danger small' : 'primary small'),
1177
+ ),
1178
+ ),
1149
1179
  element('div', { class: 'section-heading' },
1150
1180
  element('div', {}, element('h2', { id: 'tokens-heading' }, '客户端访问令牌'), element('p', {}, '给其他 DSH 客户端分配最小必要权限;原始令牌只显示一次。')),
1151
1181
  actionButton('+ 创建令牌', openTokenCreator, 'primary'),
@@ -1164,10 +1194,32 @@ function renderTokenRow(token) {
1164
1194
  element('div', { class: 'permissions' }, token.permissions.map(permission => badge(permission))),
1165
1195
  element('small', {}, `创建于 ${formatDate(token.createdAt)}${token.lastUsedAt ? ` · 最近使用 ${formatDate(token.lastUsedAt)}` : ' · 尚未使用'}`),
1166
1196
  ),
1167
- revoked ? null : actionButton('撤销', () => confirmRevokeToken(token), 'danger small'),
1197
+ revoked
1198
+ ? actionButton('永久删除', () => confirmDeleteToken(token), 'danger small')
1199
+ : actionButton('撤销', () => confirmRevokeToken(token), 'danger small'),
1168
1200
  )
1169
1201
  }
1170
1202
 
1203
+ async function copyText(value, successMessage) {
1204
+ try { await navigator.clipboard.writeText(value); showToast(successMessage) } catch { showToast('复制失败,请手动选择内容。', 'error') }
1205
+ }
1206
+
1207
+ function togglePublicApi() {
1208
+ const enabling = !state.service.publicApiEnabled
1209
+ openConfirm({
1210
+ title: enabling ? '开启远程知识库 API?' : '关闭远程知识库 API?',
1211
+ message: enabling
1212
+ ? '开启后,持有有效令牌的其他 DSH 客户端可以连接这台中央知识库。'
1213
+ : '关闭后,所有远程客户端会立即断开;本地知识库和管理台不受影响。',
1214
+ confirmLabel: enabling ? '确认开启' : '确认关闭', danger: !enabling,
1215
+ onConfirm: async () => {
1216
+ state.service = await api('service', { method: 'PUT', body: { publicApiEnabled: enabling } })
1217
+ showToast(enabling ? '远程 API 已开启。' : '远程 API 已关闭。')
1218
+ renderShell()
1219
+ },
1220
+ })
1221
+ }
1222
+
1171
1223
  function emptyState(title, description, actionLabel, action) {
1172
1224
  return element('div', { class: 'empty-state' }, element('div', {}, element('strong', {}, title), element('p', {}, description), actionLabel ? actionButton(actionLabel, action, 'primary') : null))
1173
1225
  }
@@ -1605,6 +1657,15 @@ function confirmRevokeToken(token) {
1605
1657
  } })
1606
1658
  }
1607
1659
 
1660
+ function confirmDeleteToken(token) {
1661
+ openConfirm({ title: `永久删除“${token.name}”?`, message: '这条已撤销令牌记录会被永久删除,此操作无法撤销。', confirmLabel: '永久删除', danger: true, onConfirm: async () => {
1662
+ await api(`tokens/${encodeURIComponent(token.id)}`, { method: 'DELETE' })
1663
+ showToast('已撤销令牌已永久删除。')
1664
+ await loadTokens()
1665
+ renderShell()
1666
+ } })
1667
+ }
1668
+
1608
1669
  function openConfirm({ title, message, confirmLabel, danger, onConfirm }) {
1609
1670
  return openModal({ title, body: element('p', {}, message), primaryLabel: confirmLabel, primaryVariant: danger ? 'danger' : 'primary', onPrimary: async () => { await onConfirm(); return true } })
1610
1671
  }
package/web/index.html CHANGED
@@ -6,6 +6,7 @@
6
6
  <meta name="color-scheme" content="light dark">
7
7
  <meta name="theme-color" content="#f4f5f7">
8
8
  <meta name="dsh-knowledge-api" content="__DSH_KNOWLEDGE_API_PREFIX__">
9
+ <meta name="dsh-knowledge-auth-mode" content="__DSH_KNOWLEDGE_AUTH_MODE__">
9
10
  <meta name="dsh-knowledge-web" content="__DSH_KNOWLEDGE_WEB_PATH__">
10
11
  <title>DSH 知识库</title>
11
12
  <link rel="stylesheet" href="__DSH_KNOWLEDGE_WEB_PATH__/styles.css?v=__DSH_KNOWLEDGE_ASSET_VERSION__">
package/web/styles.css CHANGED
@@ -628,6 +628,15 @@ button:disabled { cursor: not-allowed; opacity: .56; }
628
628
  .token-row strong { display: block; }
629
629
  .token-row small { color: var(--text-tertiary); }
630
630
  .permissions { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
631
+ .api-access-card { display: grid; gap: 14px; margin-bottom: 20px; border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; background: var(--surface); }
632
+ .api-access-heading, .api-access-actions { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
633
+ .api-access-heading strong { font-size: 15px; }
634
+ .api-access-heading p { margin: 4px 0 0; color: var(--text-tertiary); }
635
+ .api-address-label { color: var(--text-secondary); font-size: 12px; font-weight: 600; }
636
+ .api-address-row { display: flex; align-items: center; gap: 8px; }
637
+ .api-address-row .input { min-width: 0; flex: 1; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
638
+ .api-access-actions { align-items: center; border-top: 1px solid var(--border); padding-top: 13px; }
639
+ .api-access-actions small { color: var(--text-tertiary); }
631
640
 
632
641
  .dialog-backdrop { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 20px; background: rgb(8 10 15 / 52%); backdrop-filter: blur(4px); }
633
642
  .dialog {
@@ -755,6 +764,7 @@ button:disabled { cursor: not-allowed; opacity: .56; }
755
764
  }
756
765
 
757
766
  @media (max-width: 520px) {
767
+ .api-access-heading, .api-access-actions, .api-address-row { align-items: stretch; flex-direction: column; }
758
768
  body { font-size: 13px; }
759
769
  .login-page { padding: 16px; }
760
770
  .login-card { padding: 24px; }