From a106b23f9b2dc1a3985e0834d65de7823054d758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Sun, 9 Aug 2026 14:14:07 +0800 Subject: [PATCH] feat: add HoloLake personal channel module runtime --- ...H-HOLOLAKE-DESKTOP-0.8.0-20260809-001.json | 75 +++++++++++ engineering/MODULE-REGISTRY.json | 13 +- .../guanghu-knowledge-base/package-lock.json | 4 +- .../guanghu-knowledge-base/package.json | 2 +- .../server/channel-runtime.test.ts | 35 +++++ .../server/channel-runtime.ts | 126 ++++++++++++++++++ .../server/git-engine.ts | 60 +++++++++ .../guanghu-knowledge-base/server/index.ts | 35 ++++- .../server/persona-agent.test.ts | 25 ++++ .../server/persona-agent.ts | 32 ++++- .../guanghu-knowledge-base/src/App.tsx | 90 +++++++++++-- .../guanghu-knowledge-base/src/api.ts | 49 +++++++ .../src/components/AgentChat.tsx | 4 +- .../src/components/ModuleLibrarySheet.tsx | 55 ++++++++ .../src/components/PlatformNavigation.tsx | 11 +- .../src/styles/workspace-0.7.1.css | 102 +++++++++++++- .../hololake-desktop/RELEASE-0.8.0.md | 29 ++++ .../hololake-desktop/electron/preload.ts | 2 +- ...lake-era-0.8.0-development-causal-chain.md | 62 +++++++++ .../hololake-desktop/package-lock.json | 4 +- product-source/hololake-desktop/package.json | 2 +- 21 files changed, 786 insertions(+), 31 deletions(-) create mode 100644 deployment/receipts/GH-HOLOLAKE-DESKTOP-0.8.0-20260809-001.json create mode 100644 product-source/guanghu-knowledge-base/server/channel-runtime.test.ts create mode 100644 product-source/guanghu-knowledge-base/server/channel-runtime.ts create mode 100644 product-source/guanghu-knowledge-base/src/components/ModuleLibrarySheet.tsx create mode 100644 product-source/hololake-desktop/RELEASE-0.8.0.md create mode 100644 product-source/hololake-desktop/hololake-era-0.8.0-development-causal-chain.md diff --git a/deployment/receipts/GH-HOLOLAKE-DESKTOP-0.8.0-20260809-001.json b/deployment/receipts/GH-HOLOLAKE-DESKTOP-0.8.0-20260809-001.json new file mode 100644 index 0000000..d747e59 --- /dev/null +++ b/deployment/receipts/GH-HOLOLAKE-DESKTOP-0.8.0-20260809-001.json @@ -0,0 +1,75 @@ +{ + "schema": "guanghu.desktop-delivery-receipt/v1", + "receipt_id": "GH-HOLOLAKE-DESKTOP-0.8.0-20260809-001", + "development_id": "DEV-20260809-002", + "source_base_commit": "bb96d5eb8913c93bd678d989492c5440c0558a8e", + "source_commit": "SELF", + "repository": "repo://guanghulab.com/code/bingshuo/hololake-system-architecture#main", + "target": { + "path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake Era.app", + "version": "0.8.0", + "bundle_identifier": "com.guanghu.hololake-desktop", + "architecture": "arm64", + "binary_sha256": "509fde9922b37323a4500c1509db1c3840393b8516f87e33fd96f3d3bfe7679a", + "app_asar_sha256": "feabfb3bf00dd47f337a34a928d577c723dd373f4ee8e700ccac2705b29d279a" + }, + "installers": [ + { + "path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake Era 0.8.0 (Apple Silicon).dmg", + "architecture": "arm64", + "bytes": 137149792, + "sha256": "44dc52b01b7dba292c16d645572fd9c9ebc1ad63c8cd8cdf3939e6212588275" + }, + { + "path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake Era 0.8.0 (Intel).dmg", + "architecture": "x86_64", + "bytes": 142153805, + "sha256": "f616ba6afcadd7f967152fe96ffe4c17100d2d5bc6913a18dafc4b06c5c60130" + } + ], + "implemented": { + "official_module_registry": "HL-MOD-KNOWLEDGE-001 now resolves to the current Guanghu-native knowledge module instead of the legacy Tolaria component path", + "personal_channel_world_object": "module installation and mount state persist under .hololake/channel-state.json in the user's own knowledge world repository", + "module_lifecycle": "install, open, collapse and remove-entry actions create Git checkpoints and reversible channel receipts", + "data_boundary": "removing the module entry never deletes documents, history, repository bindings or persona continuity data", + "single_runtime": "UI actions and Agent-confirmed language actions both execute through the same deterministic channel runtime", + "agent_permission_gate": "set_channel_module_state is a write tool and cannot execute before explicit human confirmation" + }, + "verification": { + "tests": "PASS 14; FAIL 0", + "production_build": "PASS", + "packaged_server_integration": "PASS: remove -> revision 1, undo -> revision 2, README preserved, git fsck full passed", + "human_sequence_ui": "PASS: module library, collapse, open, remove, undo and Agent panel were inspected against an isolated local repository; console errors 0", + "installed_health": "PASS: /api/health returned version 0.8.0 and online from the user's existing Application Support knowledge repository", + "installed_registry": "PASS: /api/modules returned HL-MOD-KNOWLEDGE-001 version 0.8.0 state LIVE", + "installed_channel": "PASS: /api/channel returned revision 0 with the knowledge module installed and mounted; the read did not write user data", + "codesign": "PASS: installed arm64 app and both packaged app architectures satisfy deep strict code-sign verification", + "dmg": "PASS: both final disk images passed hdiutil verify" + }, + "not_claimed": [ + "No server deployment, production cutover, enterprise four-domain runtime, server account authentication or persona-birth claim is made by this release.", + "A configured Forgejo remote still requires a real explicit sync receipt before server hosting can be claimed.", + "No Apple notarization was performed; these are Developer ID signed development deliveries.", + "The module marketplace, signed module packages, cross-device automatic recovery and arbitrary natural-language layout patches remain later milestones." + ], + "reasoning_boundary": { + "included": "user goal, verified architecture sources, observed implementation gap, public engineering decisions, changes, tests and receipts", + "excluded": "hidden model reasoning, credentials, private server addresses, personal knowledge content and local authentication state" + }, + "environment_maintenance": { + "audit": "MAC_INTERNAL OK with 37082.1 MiB free; JZAO OK with 1386000 MiB free", + "codex_cache": "SKIPPED_BELOW_SIZE_THRESHOLD", + "codex_rotated_logs": "SKIPPED_BELOW_SIZE_THRESHOLD", + "protected_sessions": "PRESERVED", + "deleted_generated_builds_approx_mib": 2510.6, + "deleted_exact_old_desktop_builds": [ + "/Users/bingshuolingdianyuanhe/.Trash/HoloLake-0.7.1-replaced-by-0.8.0-20260809", + "/Users/bingshuolingdianyuanhe/.Trash/HoloLake-0.8.0-stale-internal-build-20260809" + ], + "deleted_git_appledouble_metadata_files": 20, + "git_fsck_after_cleanup": "PASS", + "recoverable": false + }, + "result": "PASS_100_LOCAL_DEVELOPMENT_DELIVERY", + "verified_at": "2026-08-09T14:10:44+08:00" +} diff --git a/engineering/MODULE-REGISTRY.json b/engineering/MODULE-REGISTRY.json index 61fbc83..709de5a 100644 --- a/engineering/MODULE-REGISTRY.json +++ b/engineering/MODULE-REGISTRY.json @@ -35,14 +35,15 @@ { "id": "HL-MOD-KNOWLEDGE-001", "name": "hololake-knowledge-lake", - "version": "0.4.6", - "state": "LEGACY_COMPONENT_ASSET", - "path": "product-source/hololake-platform/src/", + "version": "0.8.0", + "state": "LIVE_NATIVE_MODULE", + "path": "product-source/guanghu-knowledge-base/", "repo": "REPO-014 hololake-system-architecture", - "provenance": "Tolaria REPO-004 component source, assembled in REPO-014", + "provenance": "Guanghu-native Git world projection; Tolaria and Outline are isolated component-study sources only", "license": "AGPL-3.0-or-later", - "capabilities": ["knowledge.search", "knowledge.read", "knowledge.edit", "knowledge.project"], - "execution_boundary": "component-study-only; not the education subdomain runtime", + "capabilities": ["knowledge.import", "knowledge.search", "knowledge.read", "knowledge.edit", "knowledge.history", "channel.mount", "channel.unmount", "channel.remove", "channel.undo"], + "execution_boundary": "personal-channel native module; module removal never deletes user knowledge or repository history", + "runtime_state_path": ".hololake/channel-state.json", "superseded_for_education_by": "GH-EDU-MODULE-REGISTRY-0001" }, { diff --git a/product-source/guanghu-knowledge-base/package-lock.json b/product-source/guanghu-knowledge-base/package-lock.json index fc4fbe1..b0a5a97 100644 --- a/product-source/guanghu-knowledge-base/package-lock.json +++ b/product-source/guanghu-knowledge-base/package-lock.json @@ -1,12 +1,12 @@ { "name": "guanghu-knowledge-base", - "version": "0.1.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "guanghu-knowledge-base", - "version": "0.1.0", + "version": "0.8.0", "dependencies": { "cors": "^2.8.5", "diff": "^7.0.0", diff --git a/product-source/guanghu-knowledge-base/package.json b/product-source/guanghu-knowledge-base/package.json index b8e898f..60251a7 100644 --- a/product-source/guanghu-knowledge-base/package.json +++ b/product-source/guanghu-knowledge-base/package.json @@ -1,6 +1,6 @@ { "name": "guanghu-knowledge-base", - "version": "0.1.0", + "version": "0.8.0", "description": "光湖知识库模块 — Git 驱动的文档管理系统", "type": "module", "scripts": { diff --git a/product-source/guanghu-knowledge-base/server/channel-runtime.test.ts b/product-source/guanghu-knowledge-base/server/channel-runtime.test.ts new file mode 100644 index 0000000..c30daa8 --- /dev/null +++ b/product-source/guanghu-knowledge-base/server/channel-runtime.test.ts @@ -0,0 +1,35 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { applyChannelPatch, defaultChannelState, KNOWLEDGE_MODULE_ID, normalizeChannelState } from './channel-runtime.js'; + +test('module removal unmounts the module but preserves a reversible before state', () => { + const receipt = applyChannelPatch(defaultChannelState('2026-08-09T00:00:00.000Z'), { + operation: 'set_module_state', + moduleId: KNOWLEDGE_MODULE_ID, + installed: false, + }, '2026-08-09T00:01:00.000Z'); + assert.equal(receipt.after.modules[0].installed, false); + assert.equal(receipt.after.modules[0].mounted, false); + assert.equal(receipt.before.modules[0].installed, true); + assert.equal(receipt.after.revision, 1); +}); + +test('unknown modules fail closed instead of becoming UI-only entries', () => { + assert.throws(() => applyChannelPatch(defaultChannelState(), { + operation: 'set_module_state', + moduleId: 'UNREGISTERED-MODULE', + installed: true, + }), /未在光湖注册表登记/); +}); + +test('stored state is normalized against the current official registry', () => { + const state = normalizeChannelState({ + schema: 'hololake.personal-channel-state/v1', + channelId: 'CHANNEL-ICE-001', + revision: 3, + updatedAt: '2026-08-09T00:00:00.000Z', + modules: [{ id: KNOWLEDGE_MODULE_ID, installed: false, mounted: true, order: 0 }], + }); + assert.equal(state.modules[0].installed, false); + assert.equal(state.modules[0].mounted, false); +}); diff --git a/product-source/guanghu-knowledge-base/server/channel-runtime.ts b/product-source/guanghu-knowledge-base/server/channel-runtime.ts new file mode 100644 index 0000000..dc90eb0 --- /dev/null +++ b/product-source/guanghu-knowledge-base/server/channel-runtime.ts @@ -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; + 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, + }; +} diff --git a/product-source/guanghu-knowledge-base/server/git-engine.ts b/product-source/guanghu-knowledge-base/server/git-engine.ts index cd35f78..9d30f9a 100644 --- a/product-source/guanghu-knowledge-base/server/git-engine.ts +++ b/product-source/guanghu-knowledge-base/server/git-engine.ts @@ -10,8 +10,17 @@ import simpleGit, { SimpleGit, LogResult } from 'simple-git'; import matter from 'gray-matter'; import { diffLines } from 'diff'; import path from 'path'; +import crypto from 'crypto'; import fs from 'fs/promises'; import fsSync from 'fs'; +import { + applyChannelPatch as compileChannelPatch, + defaultChannelState, + normalizeChannelState, + type ChannelPatch, + type ChannelReceipt, + type ChannelState, +} from './channel-runtime.js'; // ─── 类型定义 ─── @@ -74,15 +83,66 @@ export class GitEngine { private git: SimpleGit; private docsDir: string; private repoPath: string; + private channelStatePath: string; + private channelReceiptsDir: string; constructor(repoPath: string) { this.repoPath = repoPath; this.docsDir = path.join(repoPath, 'docs'); + this.channelStatePath = path.join(repoPath, '.hololake', 'channel-state.json'); + this.channelReceiptsDir = path.join(repoPath, '.hololake', 'receipts'); // simple-git 要求目录先存在,先同步创建 fsSync.mkdirSync(repoPath, { recursive: true }); this.git = simpleGit(repoPath); } + async getChannelState(): Promise { + try { + return normalizeChannelState(JSON.parse(await fs.readFile(this.channelStatePath, 'utf8'))); + } catch { + return defaultChannelState(); + } + } + + async applyChannelPatch(patch: ChannelPatch): Promise { + const receipt = compileChannelPatch(await this.getChannelState(), patch); + await this.persistChannelReceipt(receipt, `channel: ${patch.operation} ${patch.moduleId}`); + return receipt; + } + + async undoChannelPatch(receiptId: string): Promise { + if (!/^HL-CHANNEL-RCPT-[0-9a-f-]+$/i.test(receiptId)) throw new Error('无效的频道回执编号'); + const receiptPath = path.join(this.channelReceiptsDir, `${receiptId}.json`); + const original = JSON.parse(await fs.readFile(receiptPath, 'utf8')) as ChannelReceipt; + const current = await this.getChannelState(); + if (current.revision !== original.after.revision) throw new Error('频道状态已经继续变化,不能静默覆盖;请按当前状态重新操作'); + const restored = { + ...normalizeChannelState(original.before), + revision: current.revision + 1, + updatedAt: new Date().toISOString(), + }; + const undoReceipt: ChannelReceipt = { + schema: 'hololake.channel-receipt/v1', + id: `HL-CHANNEL-RCPT-${crypto.randomUUID()}`, + operation: original.operation, + before: current, + after: restored, + createdAt: restored.updatedAt, + reversible: true, + }; + await this.persistChannelReceipt(undoReceipt, `channel: undo ${receiptId}`); + return undoReceipt; + } + + private async persistChannelReceipt(receipt: ChannelReceipt, message: string): Promise { + await fs.mkdir(this.channelReceiptsDir, { recursive: true }); + const receiptPath = path.join(this.channelReceiptsDir, `${receipt.id}.json`); + await fs.writeFile(this.channelStatePath, `${JSON.stringify(receipt.after, null, 2)}\n`, { mode: 0o600 }); + await fs.writeFile(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, { mode: 0o600 }); + await this.git.add([this.channelStatePath, receiptPath]); + await this.git.commit(message, [this.channelStatePath, receiptPath]); + } + /** 初始化仓库(如果不存在则创建) */ async init(): Promise { const repoRoot = path.dirname(this.docsDir); diff --git a/product-source/guanghu-knowledge-base/server/index.ts b/product-source/guanghu-knowledge-base/server/index.ts index b34775c..f44eb07 100644 --- a/product-source/guanghu-knowledge-base/server/index.ts +++ b/product-source/guanghu-knowledge-base/server/index.ts @@ -13,6 +13,7 @@ import { projectHumanKnowledgeTree } from './language-protocol-kernel.js'; import path from 'path'; import fs from 'fs'; import crypto from 'crypto'; +import { MODULE_REGISTRY } from './channel-runtime.js'; // __dirname 兼容 CJS 和 ESM const _dirname = typeof __dirname !== 'undefined' ? __dirname : process.cwd(); @@ -44,7 +45,7 @@ const p = (v: unknown): string => Array.isArray(v) ? v.join('/') : String(v); app.get('/api/health', (_req, res) => { res.json({ module: 'guanghu-knowledge-base', - version: '0.1.0', + version: '0.8.0', engine: 'git', repo: REPO_PATH, status: 'online', @@ -52,6 +53,38 @@ app.get('/api/health', (_req, res) => { }); }); +// ─── 初始化频道与模块运行时 ─── + +app.get('/api/modules', (_req, res) => { + res.json({ ok: true, registry: MODULE_REGISTRY }); +}); + +app.get('/api/channel', async (_req, res) => { + try { + res.json({ ok: true, channel: await engine.getChannelState() }); + } catch (err: any) { + res.status(500).json({ ok: false, error: err.message }); + } +}); + +app.post('/api/channel/patch', async (req, res) => { + try { + const receipt = await engine.applyChannelPatch(req.body); + res.json({ ok: true, channel: receipt.after, receipt }); + } catch (err: any) { + res.status(400).json({ ok: false, error: err.message }); + } +}); + +app.post('/api/channel/undo/:receiptId', async (req, res) => { + try { + const receipt = await engine.undoChannelPatch(req.params.receiptId); + res.json({ ok: true, channel: receipt.after, receipt }); + } catch (err: any) { + res.status(409).json({ ok: false, error: err.message }); + } +}); + // ─── 文档树 ─── app.get('/api/tree', async (_req, res) => { diff --git a/product-source/guanghu-knowledge-base/server/persona-agent.test.ts b/product-source/guanghu-knowledge-base/server/persona-agent.test.ts index e5e7547..94ef6d8 100644 --- a/product-source/guanghu-knowledge-base/server/persona-agent.test.ts +++ b/product-source/guanghu-knowledge-base/server/persona-agent.test.ts @@ -32,6 +32,8 @@ function toolCall(id: string, name: string, args: Record = {}): function fakeGit(overrides: Record = {}) { return { getRepositoryStatus: async () => ({}), + getChannelState: async () => ({ revision: 0, modules: [{ id: 'HL-MOD-KNOWLEDGE-001', installed: true, mounted: true, order: 0 }] }), + applyChannelPatch: async () => ({ id: 'HL-CHANNEL-RCPT-TEST', after: { revision: 1 } }), getDoc: async (path: string) => ({ meta: { id: path, title: path, updatedAt: '2026-08-09T00:00:00Z' }, body: '正文', @@ -115,6 +117,29 @@ test('confirmed write receipt never leaks an orphan tool message into the next t }); }); +test('channel module changes use the same confirmation gate as document writes', { concurrency: false }, async () => { + let executed = 0; + await withMockModel([ + toolCall('call-channel', 'set_channel_module_state', { moduleId: 'HL-MOD-KNOWLEDGE-001', mounted: false }), + { content: '频道动作等待确认。' }, + ], async () => { + const agent = createDefaultPersona(fakeGit({ + applyChannelPatch: async () => { + executed += 1; + return { id: 'HL-CHANNEL-RCPT-TEST', after: { revision: 1 } }; + }, + })); + const staged = await agent.chat('收起知识库'); + assert.equal(staged.activities[0]?.status, 'pending'); + assert.equal(executed, 0); + const pending = agent.getPendingActions()[0]; + assert.equal(pending.target, 'HL-MOD-KNOWLEDGE-001'); + const receipt = await agent.confirmAction(pending.id); + assert.equal(executed, 1); + assert.match(receipt.output, /HL-CHANNEL-RCPT-TEST/); + }); +}); + test('persona wake route is resolved and read dynamically without a fixed step count', { concurrency: false }, async () => { const routePath = 'personas/TCS-0002/WAKE-CURRENT.md'; await withMockModel([ diff --git a/product-source/guanghu-knowledge-base/server/persona-agent.ts b/product-source/guanghu-knowledge-base/server/persona-agent.ts index 17e3cdd..3a7e42f 100644 --- a/product-source/guanghu-knowledge-base/server/persona-agent.ts +++ b/product-source/guanghu-knowledge-base/server/persona-agent.ts @@ -185,6 +185,34 @@ export class PersonaAgent { execute: async () => JSON.stringify(await this.git.getRepositoryStatus(), null, 2), }); + this.tools.register({ + name: 'inspect_channel', + description: '读取当前个人初始化频道的模块安装与挂载状态(只读)', + parameters: {}, + effect: 'read', + execute: async () => JSON.stringify(await this.git.getChannelState(), null, 2), + }); + + this.tools.register({ + name: 'set_channel_module_state', + description: '安装、打开、收起或移除已在光湖注册表登记的频道模块;执行前必须由人类确认', + parameters: { + moduleId: { type: 'string', description: '光湖稳定模块编号', required: true }, + installed: { type: 'boolean', description: '是否安装;不改变时可以省略', required: false }, + mounted: { type: 'boolean', description: '是否在当前频道展开;不改变时可以省略', required: false }, + }, + effect: 'write', + execute: async (params) => { + const receipt = await this.git.applyChannelPatch({ + operation: 'set_module_state', + moduleId: String(params.moduleId || ''), + installed: typeof params.installed === 'boolean' ? params.installed : undefined, + mounted: typeof params.mounted === 'boolean' ? params.mounted : undefined, + }); + return `频道状态已更新;回执 ${receipt.id};当前修订 ${receipt.after.revision}`; + }, + }); + // 读取知识库文档 this.tools.register({ name: 'read_document', @@ -465,7 +493,7 @@ ${toolsList} if (!tool || tool.effect === 'read') { return { id: call.id, name: call.name, output: '', error: `动作不可登记: ${call.name}` }; } - const target = String(call.arguments.path || call.arguments.title || '当前知识库'); + const target = String(call.arguments.path || call.arguments.moduleId || call.arguments.title || '当前知识库'); const actionId = `action-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; const action: PendingAction = { id: actionId, @@ -641,6 +669,8 @@ export function createDefaultPersona(git: GitEngine): PersonaAgent { systemPromptBase: '你是 HoloLake 官方语言操作入口的当前模型计算实例。语言协议、权限边界、路径校验和可核验回执高于模型自由发挥。', tools: [ 'inspect_repository', + 'inspect_channel', + 'set_channel_module_state', 'read_document', 'create_document', 'update_document', diff --git a/product-source/guanghu-knowledge-base/src/App.tsx b/product-source/guanghu-knowledge-base/src/App.tsx index 2a00d22..10cee34 100644 --- a/product-source/guanghu-knowledge-base/src/App.tsx +++ b/product-source/guanghu-knowledge-base/src/App.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState, type CSSProperties, type PointerEvent as ReactPointerEvent } from 'react'; -import { api, DocTreeNode, DocContent } from './api'; +import { api, DocTreeNode, DocContent, type ChannelState, type ModuleManifest } from './api'; import { DocTree } from './components/DocTree'; import { Editor } from './components/Editor'; import { SearchBar } from './components/SearchBar'; @@ -9,6 +9,7 @@ import { PlatformNavigation } from './components/PlatformNavigation'; import { StorageLocationSheet } from './components/StorageLocationSheet'; import { HumanSettings, HumanPreferences, loadHumanPreferences } from './components/HumanSettings'; import { DomainSurface } from './components/DomainSurface'; +import { ModuleLibrarySheet } from './components/ModuleLibrarySheet'; import { cleanDisplayText } from './presentation'; type View = 'editor' | 'history'; @@ -63,7 +64,6 @@ export default function App() { const [currentPath, setCurrentPath] = useState(''); const [view, setView] = useState('editor'); const [sidebarOpen, setSidebarOpen] = useState(true); - const [moduleCollapsed, setModuleCollapsed] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [agentPanelOpen, setAgentPanelOpen] = useState(false); @@ -82,6 +82,12 @@ export default function App() { const [agentRevision, setAgentRevision] = useState(0); const [treeWidth, setTreeWidth] = useState(() => Number(localStorage.getItem('hololake.layout.tree-width')) || 258); const [agentWidth, setAgentWidth] = useState(() => Number(localStorage.getItem('hololake.layout.agent-width')) || 460); + const [channelState, setChannelState] = useState(null); + const [moduleRegistry, setModuleRegistry] = useState([]); + const [moduleLibraryOpen, setModuleLibraryOpen] = useState(false); + const [moduleBusy, setModuleBusy] = useState(false); + const [moduleMessage, setModuleMessage] = useState(''); + const [lastChannelReceipt, setLastChannelReceipt] = useState(''); const storageMode = repositoryStatus?.remote ? 'server' : 'local'; const storageLabel = storageMode === 'server' ? '服务器已托管' : '仅本机'; @@ -96,7 +102,6 @@ export default function App() { setView('editor'); setActiveRoute('fifth'); setActiveModule('knowledge'); - setModuleCollapsed(false); } catch (err: any) { setError(`加载文档失败: ${err.message}`); } finally { @@ -158,11 +163,53 @@ export default function App() { } }, []); + const refreshChannel = useCallback(async () => { + try { + const [channel, registry] = await Promise.all([api.getChannel(), api.getModules()]); + setChannelState(channel); + setModuleRegistry(registry); + } catch (err: any) { + setError(`频道状态读取失败: ${err.message}`); + } + }, []); + useEffect(() => { refreshTree(); refreshRepositoryStatus(); refreshServerSession(); - }, [refreshTree, refreshRepositoryStatus, refreshServerSession]); + refreshChannel(); + }, [refreshTree, refreshRepositoryStatus, refreshServerSession, refreshChannel]); + + const changeModuleState = useCallback(async (moduleId: string, installed: boolean, mounted: boolean) => { + setModuleBusy(true); + setModuleMessage(''); + try { + const result = await api.patchChannel({ operation: 'set_module_state', moduleId, installed, mounted }); + setChannelState(result.channel); + setLastChannelReceipt(result.receipt.id); + setModuleMessage(installed ? (mounted ? '模块已安装并打开,已保留可撤销检查点' : '模块已收起,数据保持不变') : '模块入口已移除,知识数据与历史没有删除'); + if (!installed || !mounted) setAgentPanelOpen(false); + } catch (err: any) { + setModuleMessage(err.message); + } finally { + setModuleBusy(false); + } + }, []); + + const undoModuleChange = useCallback(async () => { + if (!lastChannelReceipt) return; + setModuleBusy(true); + try { + const result = await api.undoChannel(lastChannelReceipt); + setChannelState(result.channel); + setLastChannelReceipt(''); + setModuleMessage('已恢复上一步频道状态;用户数据始终保留'); + } catch (err: any) { + setModuleMessage(err.message); + } finally { + setModuleBusy(false); + } + }, [lastChannelReceipt]); useEffect(() => { const resolvedLanguage = humanPreferences.language === 'system' @@ -260,7 +307,11 @@ export default function App() { const activeRouteCopy = routeCopy[activeRoute]; const currentTitle = cleanDisplayText(currentDoc?.meta.title || '知识库'); - const contentVisible = activeRoute === 'fifth' && activeModule === 'knowledge' && !moduleCollapsed; + const knowledgeState = channelState?.modules.find(module => module.id === 'HL-MOD-KNOWLEDGE-001'); + const knowledgeInstalled = knowledgeState?.installed !== false; + const knowledgeMounted = knowledgeInstalled && knowledgeState?.mounted !== false; + const moduleCollapsed = knowledgeInstalled && !knowledgeMounted; + const contentVisible = activeRoute === 'fifth' && activeModule === 'knowledge' && knowledgeMounted; const breadcrumb = useMemo( () => currentPath ? currentPath.split('/').map(cleanDisplayText).join(' / ') : '知识库', [currentPath], @@ -329,27 +380,27 @@ export default function App() { channelTitle={channelTitle} channelSubtitle={channelSubtitle} activeRoute={activeRoute} + knowledgeInstalled={knowledgeInstalled} knowledgeSelected={activeRoute === 'fifth' && activeModule === 'knowledge'} onRouteSelect={route => { setActiveRoute(route); setActiveModule('knowledge'); - setModuleCollapsed(false); }} onKnowledgeSelect={() => { setActiveRoute('fifth'); setActiveModule('knowledge'); - setModuleCollapsed(false); + if (!knowledgeMounted) void changeModuleState('HL-MOD-KNOWLEDGE-001', true, true); }} onEducationSelect={() => { setActiveRoute('sub'); setActiveModule('education'); - setModuleCollapsed(false); }} onSettingsOpen={() => setHumanSettingsOpen(true)} onAccountOpen={() => { setStorageSheetInitialMode('server'); setStorageSheetOpen(true); }} + onModuleLibraryOpen={() => setModuleLibraryOpen(true)} />
@@ -358,7 +409,13 @@ export default function App() {

知识库已收起

模块仍安装在永恒湖心频道中,重新打开不会改变本地或服务器数据。

- + +
+ ) : activeRoute === 'fifth' && !knowledgeInstalled ? ( +
+

这是你的初始化频道

+

频道目前没有安装模块。资料仍保留在个人状态域中,可以随时重新安装知识库继续使用。

+
) : ( setView('history')}>历史 } - + @@ -444,7 +501,7 @@ export default function App() { {agentPanelOpen && ( )} @@ -488,6 +545,17 @@ export default function App() { setStorageSheetOpen(true); }} /> + setModuleLibraryOpen(false)} + onChange={(moduleId, installed, mounted) => void changeModuleState(moduleId, installed, mounted)} + onUndo={() => void undoModuleChange()} + /> ); } diff --git a/product-source/guanghu-knowledge-base/src/api.ts b/product-source/guanghu-knowledge-base/src/api.ts index 0907db5..6a3948a 100644 --- a/product-source/guanghu-knowledge-base/src/api.ts +++ b/product-source/guanghu-knowledge-base/src/api.ts @@ -57,6 +57,38 @@ export interface SearchResult { line: number; } +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 ChannelReceipt { + id: string; + after: ChannelState; + reversible: true; +} + export interface DiffResult { additions: number; deletions: number; @@ -66,6 +98,23 @@ export interface DiffResult { // ─── API 方法 ─── export const api = { + async getModules(): Promise { + const data = await request<{ registry: ModuleManifest[] }>('/modules'); + return data.registry; + }, + + async getChannel(): Promise { + const data = await request<{ channel: ChannelState }>('/channel'); + return data.channel; + }, + + async patchChannel(input: { operation: 'set_module_state'; moduleId: string; installed?: boolean; mounted?: boolean }): Promise<{ channel: ChannelState; receipt: ChannelReceipt }> { + return request('/channel/patch', { method: 'POST', body: JSON.stringify(input) }); + }, + + async undoChannel(receiptId: string): Promise<{ channel: ChannelState; receipt: ChannelReceipt }> { + return request(`/channel/undo/${encodeURIComponent(receiptId)}`, { method: 'POST' }); + }, /** 获取文档树 */ getTree: () => request<{ ok: true; tree: DocTreeNode[] }>('/tree').then(d => d.tree), diff --git a/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx b/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx index a7eb96b..2e0f454 100644 --- a/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx +++ b/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx @@ -47,9 +47,10 @@ interface Props { apiBase: string; runtimeRevision?: number; onClose?: () => void; + onWorldChanged?: () => void; } -export default function AgentChat({ apiBase, runtimeRevision = 0, onClose }: Props) { +export default function AgentChat({ apiBase, runtimeRevision = 0, onClose, onWorldChanged }: Props) { const [messages, setMessages] = useState([]); const [conversations, setConversations] = useState([]); const [activeConversationId, setActiveConversationId] = useState(''); @@ -172,6 +173,7 @@ export default function AgentChat({ apiBase, runtimeRevision = 0, onClose }: Pro setPendingActions(data.actions || []); setMessages(data.messages || messages); await refreshConversationList(); + if (decision === 'confirm') onWorldChanged?.(); } catch (error) { setMessages(previous => [...previous, { role: 'assistant', content: `动作未执行:${error instanceof Error ? error.message : String(error)}`, timestamp: new Date().toISOString() }]); } finally { diff --git a/product-source/guanghu-knowledge-base/src/components/ModuleLibrarySheet.tsx b/product-source/guanghu-knowledge-base/src/components/ModuleLibrarySheet.tsx new file mode 100644 index 0000000..3491732 --- /dev/null +++ b/product-source/guanghu-knowledge-base/src/components/ModuleLibrarySheet.tsx @@ -0,0 +1,55 @@ +import type { ChannelState, ModuleManifest } from '../api'; + +interface Props { + open: boolean; + registry: ModuleManifest[]; + channel: ChannelState | null; + busy: boolean; + message: string; + onClose: () => void; + onChange: (moduleId: string, installed: boolean, mounted: boolean) => void; + onUndo: () => void; + canUndo: boolean; +} + +export function ModuleLibrarySheet({ open, registry, channel, busy, message, onClose, onChange, onUndo, canUndo }: Props) { + if (!open) return null; + return ( +
{ if (event.target === event.currentTarget) onClose(); }}> +
+
+
个人初始化频道

频道模块

这里只显示灯塔已登记、当前版本真实可用的模块。

+ +
+
+ {registry.map(manifest => { + const state = channel?.modules.find(module => module.id === manifest.id); + const installed = state?.installed === true; + const mounted = installed && state?.mounted === true; + return ( +
+ +
+

{manifest.name}

{manifest.state === 'LIVE' ? '可用' : manifest.state}
+ {manifest.id} +

{manifest.description}

+ {manifest.dataPolicy} +
+
+ {!installed && } + {installed && !mounted && } + {installed && mounted && } + {installed && } +
+
+ ); + })} +
+
+ {message || `频道状态修订 ${channel?.revision ?? 0}`} + {canUndo && } +
+
+
+ ); +} diff --git a/product-source/guanghu-knowledge-base/src/components/PlatformNavigation.tsx b/product-source/guanghu-knowledge-base/src/components/PlatformNavigation.tsx index fb97347..c5e21fa 100644 --- a/product-source/guanghu-knowledge-base/src/components/PlatformNavigation.tsx +++ b/product-source/guanghu-knowledge-base/src/components/PlatformNavigation.tsx @@ -5,11 +5,13 @@ type RouteId = 'fifth' | 'main' | 'sub' | 'zero' | 'zero-sense'; interface Props { activeRoute: RouteId; knowledgeSelected: boolean; + knowledgeInstalled: boolean; onRouteSelect: (route: RouteId) => void; onKnowledgeSelect: () => void; onEducationSelect: () => void; onSettingsOpen: () => void; onAccountOpen: () => void; + onModuleLibraryOpen: () => void; channelTitle: string; channelSubtitle: string; } @@ -48,11 +50,13 @@ function ModuleIcon({ kind }: { kind: 'knowledge' | 'education' }) { export function PlatformNavigation({ activeRoute, knowledgeSelected, + knowledgeInstalled, onRouteSelect, onKnowledgeSelect, onEducationSelect, onSettingsOpen, onAccountOpen, + onModuleLibraryOpen, channelTitle, channelSubtitle, }: Props) { @@ -111,11 +115,12 @@ export function PlatformNavigation({ )} -
已安装模块
+
已安装模块
diff --git a/product-source/guanghu-knowledge-base/src/styles/workspace-0.7.1.css b/product-source/guanghu-knowledge-base/src/styles/workspace-0.7.1.css index e5609be..3cc5e46 100644 --- a/product-source/guanghu-knowledge-base/src/styles/workspace-0.7.1.css +++ b/product-source/guanghu-knowledge-base/src/styles/workspace-0.7.1.css @@ -1,4 +1,4 @@ -/* HoloLake 0.7.1 canonical workspace layer. +/* HoloLake 0.8.0 canonical workspace layer. * This file is intentionally loaded after the inherited component stylesheet. * It is the single layout and theme authority for the platform shell. */ @@ -241,6 +241,106 @@ body, #root { background: var(--lake-bg, #07111d); } .storage-sheet, .human-settings { border-color: var(--lake-border); background: var(--lake-panel); color: var(--lake-text); } .storage-sheet-backdrop, .human-settings-backdrop { background: rgba(2, 8, 14, .56); } + +.module-empty-entry { border: 1px dashed var(--lake-border) !important; background: transparent !important; } +.module-empty-entry span:first-child { font-size: 20px; color: var(--lake-accent); } + +.sheet-backdrop { + position: fixed; + inset: 0; + z-index: 1500; + display: grid; + place-items: center; + padding: 28px; + background: rgba(2, 8, 14, .62); + backdrop-filter: blur(10px); +} + +.module-library-sheet { + width: min(760px, 100%); + max-height: min(760px, calc(100vh - 56px)); + display: flex; + flex-direction: column; + overflow: hidden; + border: 1px solid var(--lake-border); + border-radius: 22px; + background: var(--lake-panel); + color: var(--lake-text); + box-shadow: 0 30px 90px rgba(0, 0, 0, .44); +} + +.module-library-sheet > header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 28px; + padding: 28px 30px 22px; + border-bottom: 1px solid var(--lake-border); +} + +.module-library-sheet > header small { color: var(--lake-accent); font-weight: 700; letter-spacing: .08em; } +.module-library-sheet > header h2 { margin: 6px 0 5px; font-size: 27px; letter-spacing: -.02em; } +.module-library-sheet > header p { margin: 0; color: var(--lake-muted); } +.module-library-sheet > header > button { + width: 36px; + height: 36px; + border: 0; + border-radius: 10px; + background: var(--lake-surface-2); + color: var(--lake-muted); + font-size: 24px; +} + +.module-library-list { padding: 22px; overflow: auto; } +.module-library-card { + display: grid; + grid-template-columns: 52px minmax(0, 1fr) auto; + gap: 18px; + align-items: start; + padding: 20px; + border: 1px solid var(--lake-border); + border-radius: 17px; + background: var(--lake-surface-1); +} +.module-library-icon { + width: 52px; + height: 52px; + display: grid; + place-items: center; + border: 1px solid var(--lake-border); + border-radius: 15px; + background: var(--lake-accent-soft); +} +.module-library-icon span { width: 20px; height: 25px; border: 2px solid var(--lake-accent); border-radius: 3px 7px 7px 3px; } +.module-library-title { display: flex; align-items: center; gap: 10px; } +.module-library-title h3 { margin: 0; font-size: 18px; } +.module-library-title span { padding: 3px 8px; border-radius: 999px; background: color-mix(in srgb, var(--lake-success) 14%, transparent); color: var(--lake-success); font-size: 11px; font-weight: 700; } +.module-library-copy code { display: block; margin-top: 5px; color: var(--lake-muted); font-size: 11px; } +.module-library-copy p { margin: 12px 0 7px; color: var(--lake-text); line-height: 1.55; } +.module-library-copy > small { color: var(--lake-muted); line-height: 1.5; } +.module-library-actions { min-width: 118px; display: grid; gap: 8px; } +.module-library-actions button { min-height: 36px; white-space: nowrap; } +.quiet-danger { border: 0; background: transparent; color: var(--lake-danger); } +.module-library-sheet > footer { + min-height: 64px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; + padding: 14px 30px; + border-top: 1px solid var(--lake-border); + color: var(--lake-muted); + font-size: 13px; +} +.module-library-sheet > footer button { border: 1px solid var(--lake-border); border-radius: 9px; background: var(--lake-surface-2); color: var(--lake-text); padding: 8px 13px; } + +@media (max-width: 700px) { + .sheet-backdrop { padding: 12px; align-items: end; } + .module-library-sheet { max-height: calc(100vh - 24px); border-radius: 20px 20px 12px 12px; } + .module-library-card { grid-template-columns: 44px minmax(0, 1fr); } + .module-library-icon { width: 44px; height: 44px; } + .module-library-actions { grid-column: 1 / -1; grid-template-columns: repeat(2, minmax(0, 1fr)); } +} .settings-section select, .settings-model-fields input { border-color: var(--lake-border); background: var(--lake-surface); color: var(--lake-text); } .settings-model-card, .settings-account-card { border-color: var(--lake-border-soft); background: var(--lake-surface); } diff --git a/product-source/hololake-desktop/RELEASE-0.8.0.md b/product-source/hololake-desktop/RELEASE-0.8.0.md new file mode 100644 index 0000000..ad588b9 --- /dev/null +++ b/product-source/hololake-desktop/RELEASE-0.8.0.md @@ -0,0 +1,29 @@ +# HoloLake Era Desktop 0.8.0 + +--- +type: release-note +status: verified-local +development_id: DEV-20260809-002 +source_baseline: bb96d5eb8913c93bd678d989492c5440c0558a8e +--- + +## 本版目标 + +把 0.7.1 中仅存在于界面内存的“已安装模块”和“收起知识库”升级为初始化频道的真实世界对象,使模块生命周期由 HoloLake 协议、Git 检查点和可撤销回执共同约束。 + +## 已实现 + +- `HL-MOD-KNOWLEDGE-001` 作为当前首个真实 HoloLake 原生模块进入官方模块注册表; +- 个人频道状态写入知识世界仓库的 `.hololake/channel-state.json`,不再写死在导航栏; +- 安装、打开、收起和移除模块入口都会形成独立 Git 检查点和频道回执; +- 上一步频道动作可以撤销;状态继续变化后拒绝静默覆盖; +- 移除知识库只卸载频道入口,不删除文档、历史、服务器绑定或人格连续性资料; +- 模块库只显示已注册且当前真实可用的模块,教育行业继续作为光湖分域行业入口; +- HoloLake 语言操作入口新增频道状态读取和模块状态动作;写动作仍必须由人类确认; +- Agent 确认执行频道动作后,界面从同一频道世界对象刷新,不维护第二份假状态。 + +## 现实边界 + +- 模块状态先在当前个人知识世界的本地 Git 中形成检查点;配置远端并显式同步后才能称为服务器已托管; +- 本版没有宣称企业四域在线、生产服务器部署或人格出生; +- 模块商城、签名发行包、跨设备自动恢复和任意自然语言频道布局仍属于后续阶段。 diff --git a/product-source/hololake-desktop/electron/preload.ts b/product-source/hololake-desktop/electron/preload.ts index ace9cdd..10d50c7 100644 --- a/product-source/hololake-desktop/electron/preload.ts +++ b/product-source/hololake-desktop/electron/preload.ts @@ -11,7 +11,7 @@ import { contextBridge, ipcRenderer } from 'electron'; contextBridge.exposeInMainWorld('hololake', { // 应用信息 platform: process.platform, - version: '0.7.1', + version: '0.8.0', // 数据目录 getDataPath: () => ipcRenderer.invoke('get-data-path'), diff --git a/product-source/hololake-desktop/hololake-era-0.8.0-development-causal-chain.md b/product-source/hololake-desktop/hololake-era-0.8.0-development-causal-chain.md new file mode 100644 index 0000000..ed5b92d --- /dev/null +++ b/product-source/hololake-desktop/hololake-era-0.8.0-development-causal-chain.md @@ -0,0 +1,62 @@ +# HoloLake Era 0.8.0 开发因果链 + +--- +type: engineering-causal-chain +status: implemented-and-locally-verified +development_id: DEV-20260809-002 +repository: REPO-014 +branch: main +baseline: bb96d5eb8913c93bd678d989492c5440c0558a8e +--- + +## 为什么做这一轮 + +0.7.1 已经具备知识库、Agent、布局、主题和本地/服务器托管入口,但“已安装模块”仍是固定界面文案,“收起模块”只是本次运行的临时状态。重启后状态丢失,Agent 与界面也没有共同的频道事实源,因此尚未满足 HoloLake 初始化频道和模块热插拔蓝图。 + +## 本轮采用的系统解释 + +1. 模块以灯塔登记的稳定编号存在,界面名称不是身份。 +2. 初始化频道是持续存在的世界对象,不等于仓库 URL 或 React 页面状态。 +3. Git 是后台检查点与恢复引擎,不作为普通用户前台。 +4. 模块可以卸载,用户知识、项目状态和历史不能随模块删除。 +5. 语言操作与点击操作必须经过同一确定性运行时;写操作继续遵守显性确认。 + +## 工程映射 + +```text +engineering/MODULE-REGISTRY.json + → HL-MOD-KNOWLEDGE-001 官方登记 + → HoloLake 模块库只投影 LIVE 模块 + +个人频道动作 + → /api/channel/patch + → channel-runtime 确定性校验 + → .hololake/channel-state.json + → .hololake/receipts/.json + → Git 检查点 + → 界面刷新或撤销 + +语言指令 + → set_channel_module_state + → 人类确认门 + → 同一个 channel-runtime + → 同一个频道状态与回执 +``` + +## 取舍 + +- 没有把未开发模块放进模块库冒充可安装能力;当前只登记知识库这一真实模块。 +- 没有把教育行业降级为个人已安装模块;它仍属于光湖分域。 +- 没有在模块移除时删除知识目录或远端绑定。 +- 没有在 UI 中暴露提交、分支和 Forgejo 工程操作;只显示人类可理解的模块状态和撤销结果。 +- 没有记录或展示模型隐藏思维;本页只保存公开架构依据、实现选择与验收证据。 + +## 验收标准 + +- 安装、打开、收起、移除在重启后仍可从 Git 世界对象恢复; +- 每次变化产生独立回执; +- 撤销只能恢复仍然对应当前修订的上一步,避免覆盖后续变化; +- 移除再恢复的过程中知识文档内容不变; +- 未登记模块请求失败关闭; +- Agent 写操作仍需确认,确认后界面读取同一状态; +- 自动测试、生产构建、打包后服务 API 与 Git 完整性全部通过。 diff --git a/product-source/hololake-desktop/package-lock.json b/product-source/hololake-desktop/package-lock.json index 10c077f..d0b9359 100644 --- a/product-source/hololake-desktop/package-lock.json +++ b/product-source/hololake-desktop/package-lock.json @@ -1,12 +1,12 @@ { "name": "hololake-desktop", - "version": "0.7.1", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hololake-desktop", - "version": "0.7.1", + "version": "0.8.0", "dependencies": { "cors": "^2.8.5", "diff": "^9.0.0", diff --git a/product-source/hololake-desktop/package.json b/product-source/hololake-desktop/package.json index cf21dd9..025df42 100644 --- a/product-source/hololake-desktop/package.json +++ b/product-source/hololake-desktop/package.json @@ -1,6 +1,6 @@ { "name": "hololake-desktop", - "version": "0.7.1", + "version": "0.8.0", "description": "HoloLake Era — 光湖语言操作系统平台", "main": "dist-electron/main.cjs", "type": "module",