feat: add HoloLake personal channel module runtime
This commit is contained in:
parent
bb96d5eb89
commit
a106b23f9b
21 changed files with 786 additions and 31 deletions
126
product-source/guanghu-knowledge-base/server/channel-runtime.ts
Normal file
126
product-source/guanghu-knowledge-base/server/channel-runtime.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import crypto from 'crypto';
|
||||
|
||||
export const KNOWLEDGE_MODULE_ID = 'HL-MOD-KNOWLEDGE-001';
|
||||
|
||||
export interface ModuleManifest {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
kind: 'native-application';
|
||||
state: 'LIVE';
|
||||
description: string;
|
||||
capabilities: string[];
|
||||
dataPolicy: string;
|
||||
}
|
||||
|
||||
export interface ChannelModuleState {
|
||||
id: string;
|
||||
installed: boolean;
|
||||
mounted: boolean;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface ChannelState {
|
||||
schema: 'hololake.personal-channel-state/v1';
|
||||
channelId: string;
|
||||
revision: number;
|
||||
updatedAt: string;
|
||||
modules: ChannelModuleState[];
|
||||
}
|
||||
|
||||
export interface ChannelPatch {
|
||||
operation: 'set_module_state';
|
||||
moduleId: string;
|
||||
installed?: boolean;
|
||||
mounted?: boolean;
|
||||
}
|
||||
|
||||
export interface ChannelReceipt {
|
||||
schema: 'hololake.channel-receipt/v1';
|
||||
id: string;
|
||||
operation: ChannelPatch;
|
||||
before: ChannelState;
|
||||
after: ChannelState;
|
||||
createdAt: string;
|
||||
reversible: true;
|
||||
}
|
||||
|
||||
export const MODULE_REGISTRY: ModuleManifest[] = [{
|
||||
id: KNOWLEDGE_MODULE_ID,
|
||||
name: '知识库',
|
||||
version: '0.8.0',
|
||||
kind: 'native-application',
|
||||
state: 'LIVE',
|
||||
description: 'Git 驱动的个人知识空间,支持导入、编辑、检索、历史与 HoloLake 语言操作。',
|
||||
capabilities: [
|
||||
'knowledge.import',
|
||||
'knowledge.read',
|
||||
'knowledge.search',
|
||||
'knowledge.edit',
|
||||
'knowledge.history',
|
||||
],
|
||||
dataPolicy: '移除模块只卸载频道入口,不删除用户文档、历史或服务器绑定。',
|
||||
}];
|
||||
|
||||
export function defaultChannelState(now = new Date().toISOString()): ChannelState {
|
||||
return {
|
||||
schema: 'hololake.personal-channel-state/v1',
|
||||
channelId: 'CHANNEL-PERSONAL-DEFAULT',
|
||||
revision: 0,
|
||||
updatedAt: now,
|
||||
modules: [{ id: KNOWLEDGE_MODULE_ID, installed: true, mounted: true, order: 0 }],
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeChannelState(input: unknown): ChannelState {
|
||||
const fallback = defaultChannelState();
|
||||
if (!input || typeof input !== 'object') return fallback;
|
||||
const candidate = input as Partial<ChannelState>;
|
||||
const known = new Set(MODULE_REGISTRY.map(module => module.id));
|
||||
const modules = Array.isArray(candidate.modules)
|
||||
? candidate.modules
|
||||
.filter(module => module && known.has(module.id))
|
||||
.map((module, index) => ({
|
||||
id: module.id,
|
||||
installed: module.installed !== false,
|
||||
mounted: module.installed !== false && module.mounted !== false,
|
||||
order: Number.isFinite(module.order) ? Number(module.order) : index,
|
||||
}))
|
||||
: fallback.modules;
|
||||
for (const manifest of MODULE_REGISTRY) {
|
||||
if (!modules.some(module => module.id === manifest.id)) {
|
||||
modules.push({ id: manifest.id, installed: false, mounted: false, order: modules.length });
|
||||
}
|
||||
}
|
||||
return {
|
||||
schema: 'hololake.personal-channel-state/v1',
|
||||
channelId: typeof candidate.channelId === 'string' && candidate.channelId ? candidate.channelId : fallback.channelId,
|
||||
revision: Number.isInteger(candidate.revision) && Number(candidate.revision) >= 0 ? Number(candidate.revision) : 0,
|
||||
updatedAt: typeof candidate.updatedAt === 'string' ? candidate.updatedAt : fallback.updatedAt,
|
||||
modules: modules.sort((a, b) => a.order - b.order),
|
||||
};
|
||||
}
|
||||
|
||||
export function applyChannelPatch(currentInput: ChannelState, patch: ChannelPatch, now = new Date().toISOString()): ChannelReceipt {
|
||||
const before = normalizeChannelState(currentInput);
|
||||
if (patch.operation !== 'set_module_state') throw new Error('不支持的频道动作');
|
||||
if (!MODULE_REGISTRY.some(module => module.id === patch.moduleId)) throw new Error('模块未在光湖注册表登记');
|
||||
if (typeof patch.installed !== 'boolean' && typeof patch.mounted !== 'boolean') throw new Error('频道动作没有状态变化');
|
||||
|
||||
const modules = before.modules.map(module => {
|
||||
if (module.id !== patch.moduleId) return module;
|
||||
const installed = typeof patch.installed === 'boolean' ? patch.installed : module.installed;
|
||||
const mounted = installed && (typeof patch.mounted === 'boolean' ? patch.mounted : module.mounted);
|
||||
return { ...module, installed, mounted };
|
||||
});
|
||||
const after = { ...before, revision: before.revision + 1, updatedAt: now, modules };
|
||||
return {
|
||||
schema: 'hololake.channel-receipt/v1',
|
||||
id: `HL-CHANNEL-RCPT-${crypto.randomUUID()}`,
|
||||
operation: patch,
|
||||
before,
|
||||
after,
|
||||
createdAt: now,
|
||||
reversible: true,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue