checkpoint: preserve zero-core agent handoff

This commit is contained in:
冰朔 2026-08-21 01:29:17 +08:00
commit e54c93c7ae
91 changed files with 13603 additions and 121 deletions

View file

@ -13,6 +13,7 @@ import { PublicDomainPortal, type PublicDomainId } from './modules/public-domain
const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio })))
const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
const KnowledgeAgent = lazy(() => import('./modules/knowledge-agent').then((module) => ({ default: module.KnowledgeAgent })))
const EducationWorkspace = lazy(() => import('./modules/education-workspace').then((module) => ({ default: module.EducationWorkspace })))
const WebNovelWorkspace = lazy(() => import('./modules/web-novel/WebNovelWorkspace').then((module) => ({ default: module.WebNovelWorkspace })))
const MobileSyncPanel = lazy(() => import('./modules/mobile-sync').then((module) => ({ default: module.MobileSyncPanel })))
@ -241,6 +242,22 @@ interface BeijingTimeCoordinate { unixMs: number; beijingTime: string; timeZone:
interface PersonaTimeAuthorityStartup { state: string; synchronizationAttempted: boolean; networkTimeUrl: string; coordinate: BeijingTimeCoordinate }
interface GuanghuEraEvent { eventId: string; displayDate: string; datePrecision: string; title: string; summary: string; evidenceState: string; sourceRecord: string }
interface GuanghuEraTimeline { state: string; eraName: string; calendarName: string; epochDate: string; epochPrecision: string; publicRealityBoundary: string; currentCoordinate: BeijingTimeCoordinate; events: GuanghuEraEvent[] }
interface KnowledgeThoughtSummary { trigger: string; emergence: string; lock: string; why: string }
interface KnowledgePageHeader {
schema: string
number: string
numberingSystem: string
parentNumber: string
path: string
mappingTerms: string[]
thoughtSummary?: KnowledgeThoughtSummary | null
children: string[]
source: string
version: number
contentSha256: string
state: string
formalRouting: boolean
}
interface KnowledgeDocumentSummary {
source: KnowledgeSource
path: string
@ -249,6 +266,7 @@ interface KnowledgeDocumentSummary {
sizeBytes: number
contentSha256: string
duplicateCount: number
pageHeader: KnowledgePageHeader
}
interface KnowledgeSnapshot {
state: string
@ -269,8 +287,9 @@ interface KnowledgeDocument {
updatedAtUnixMs: number
contentSha256: string
writable: boolean
pageHeader: KnowledgePageHeader
}
interface KnowledgeSearchResult { source: KnowledgeSource; path: string; title: string; snippet: string }
interface KnowledgeSearchResult { source: KnowledgeSource; number: string; path: string; title: string; state: string; thoughtSummary?: KnowledgeThoughtSummary | null; matchReason: string[]; snippet: string }
interface KnowledgeImportResult {
state: string
sourceName: string
@ -345,6 +364,7 @@ function domainDisplayName(domain: string): string {
BRANCH_DOMAIN: '光湖分域',
ZERO_DOMAIN: '光湖零域',
ZERO_SENSE_DOMAIN: '光湖零感域',
PERSONAL_CHANNEL: '我的本地频道',
}
return names[domain] || '已登记光湖域'
}
@ -684,6 +704,7 @@ function HoloLakeApp() {
const [searchResults, setSearchResults] = useState<KnowledgeSearchResult[] | null>(null)
const [knowledgeBusy, setKnowledgeBusy] = useState(false)
const [knowledgeMessage, setKnowledgeMessage] = useState('')
const [knowledgeAgentOpen, setKnowledgeAgentOpen] = useState(false)
const [compositionModule, setCompositionModule] = useState<BundledModuleDescriptor | null>(null)
const [compositionProjection, setCompositionProjection] = useState<NativeCompositionProjection | null>(null)
const [compositionDimension, setCompositionDimension] = useState<CompositionDimension>('TOP_LEVEL_FOLDER')
@ -754,6 +775,7 @@ function HoloLakeApp() {
const [loginBusy, setLoginBusy] = useState(false)
const [loginRising, setLoginRising] = useState(false)
const [loginMessage, setLoginMessage] = useState('')
const [localChannelBusy, setLocalChannelBusy] = useState(false)
const [gateStage, setGateStage] = useState<'number' | 'key'>('number')
const [gateRaw, setGateRaw] = useState('')
const [gateInf, setGateInf] = useState(false)
@ -899,7 +921,7 @@ function HoloLakeApp() {
invoke<LoginSession | null>('check_code_repo_login').then((session) => setRepoLogin(session)).catch(() => setRepoLogin(null))
}, [])
const loadEnterpriseEntry = useCallback(async () => {
if (!repoLogin || repoLogin.domain === 'FIFTH_DOMAIN') {
if (!repoLogin || repoLogin.domain === 'FIFTH_DOMAIN' || repoLogin.domain === 'PERSONAL_CHANNEL') {
setEnterpriseEntry(null)
return
}
@ -1563,6 +1585,18 @@ function HoloLakeApp() {
} catch (error) { setLoginMessage(humanError(error, 'login')) }
finally { setLoginBusy(false) }
}
const startLocalChannel = async () => {
setLocalChannelBusy(true)
setGateMessage('')
try {
const receipt = await invoke<LoginReceipt>('start_local_channel_session', { input: { acknowledgement: '在本机初始化我的频道' } })
setRepoLogin({ username: receipt.username, host: receipt.host, domain: receipt.domain, signedInAtUnixMs: Date.now() })
setWorldStage('domain')
setGateOpen(false)
await refreshCore()
} catch (error) { setGateMessage(humanError(error, 'identity')) }
finally { setLocalChannelBusy(false) }
}
const changeFirstLoginPassword = async (event: React.FormEvent) => {
event.preventDefault()
if (newLoginPassword !== confirmLoginPassword) {
@ -1787,21 +1821,24 @@ function HoloLakeApp() {
const renderKnowledge = () => (
<section className="full-workbench knowledge-page">
<aside className="knowledge-browser">
<header><div><span className="kicker">KNOWLEDGE</span><h1></h1></div><button className="icon-button" title="导入文件夹" type="button" disabled={knowledgeBusy} onClick={() => void importKnowledge()}><Icon name="import"/></button></header>
<header><div><span className="kicker">KNOWLEDGE</span><h1></h1></div><div className="knowledge-head-actions"><button className="knowledge-agent-entry" type="button" onClick={() => setKnowledgeAgentOpen(true)}> / </button><button className="icon-button" title="导入文件夹" type="button" disabled={knowledgeBusy} onClick={() => void importKnowledge()}><Icon name="import"/></button></div></header>
<form className="search-box" onSubmit={(event) => void runSearch(event)}>
<Icon name="search"/><input aria-label="检索知识" value={searchQuery} placeholder="检索标题与正文" onChange={(event) => setSearchQuery(event.target.value)}/>
<Icon name="search"/><input aria-label="检索知识" value={searchQuery} placeholder="用自然语言检索编号思维" onChange={(event) => setSearchQuery(event.target.value)}/>
{searchResults && <button type="button" onClick={() => { setSearchResults(null); setSearchQuery('') }}></button>}
</form>
<div className="knowledge-counts"><span>{knowledge.uniqueDocumentCount} </span><span>{knowledge.duplicateDocumentCount} </span></div>
<div className="knowledge-tree" aria-busy={knowledgeBusy}>
{visibleDocuments.length
? <KnowledgeTree node={tree} depth={0} expanded={expanded} active={activeDocument ? `${activeDocument.source}:${activeDocument.path}` : undefined}
{searchResults
? searchResults.length ? <div className="knowledge-candidates">{searchResults.map((result) => <button type="button" key={result.number} onClick={() => void openDocument(result.source, result.path)}><span><b>{result.title}</b><em className={result.state === 'READY' ? 'ready' : 'pending'}>{result.state === 'READY' ? '思维已建模' : '待建思维摘要'}</em></span><code>{result.number}</code><small>{result.snippet}</small><i>{result.matchReason.slice(0, 6).join(' · ')}</i></button>)}</div>
: <div className="empty-state"></div>
: visibleDocuments.length
? <KnowledgeTree node={tree} depth={0} expanded={expanded} active={activeDocument ? `${activeDocument.source}:${activeDocument.path}` : undefined}
onToggle={(key) => setExpanded((current) => { const next = new Set(current); if (next.has(key)) next.delete(key); else next.add(key); return next })}
onOpen={(item) => void openDocument(item.source, item.path)}
onRemoveFolder={(folder) => void removeFolder(folder)}
folderMenuKey={folderMenuKey}
onFolderMenuToggle={(key) => setFolderMenuKey((current) => (current === key ? null : key))}/>
: <div className="empty-state"></div>}
: <div className="empty-state"></div>}
</div>
<footer>{knowledgeMessage || (lastKnowledgeReceipt ? `Git ${lastKnowledgeReceipt.slice(0, 10)}` : '导入会自动检查相同内容')}</footer>
</aside>
@ -1825,10 +1862,12 @@ function HoloLakeApp() {
<div className="inspector-tabs"><span className="active"></span><span></span><span></span></div>
{activeDocument ? <div className="inspector-scroll">
<section><h2></h2>{outline.length ? <nav className="outline-list">{outline.map((item) => <button key={item.id} type="button" title="跳到该章节" className={activeHeadingId === item.id ? 'active' : ''} style={{ paddingLeft: 8 + (item.level - 1) * 11 }} onClick={() => jumpToHeading(item.id)}>{item.title}</button>)}</nav> : <p></p>}</section>
<section><h2></h2><dl><div><dt></dt><dd>{activeDocument.source === 'native' ? 'HoloLake 本机 Git' : 'HoloLake Era 只读供体'}</dd></div><div><dt></dt><dd>{activeDocument.path}</dd></div><div><dt></dt><dd>{activeDocument.contentSha256.slice(0, 16)}</dd></div><div><dt></dt><dd>{activeSummary?.duplicateCount || 0} </dd></div></dl></section>
<section><h2></h2><dl><div><dt></dt><dd>{activeDocument.pageHeader.number}</dd></div><div><dt></dt><dd>{activeDocument.pageHeader.parentNumber}</dd></div><div><dt></dt><dd>{activeDocument.pageHeader.path}</dd></div><div><dt></dt><dd>{activeDocument.pageHeader.state === 'READY' ? 'READY · 可正式路由' : 'PENDING · 不伪造 why'}</dd></div></dl>{activeDocument.pageHeader.thoughtSummary ? <div className="thought-summary"><b></b><p>{activeDocument.pageHeader.thoughtSummary.trigger}</p><b></b><p>{activeDocument.pageHeader.thoughtSummary.emergence}</p><b></b><p>{activeDocument.pageHeader.thoughtSummary.lock}</p><b></b><p>{activeDocument.pageHeader.thoughtSummary.why}</p></div> : <p> SHA256 trigger / emergence / lock / why </p>}</section>
<section><h2></h2><dl><div><dt></dt><dd>{activeDocument.source === 'native' ? 'HoloLake 本机 Git' : 'HoloLake Era 只读供体'}</dd></div><div><dt></dt><dd>{activeDocument.path}</dd></div><div><dt></dt><dd>{activeDocument.contentSha256.slice(0, 16)}</dd></div><div><dt></dt><dd>{activeSummary?.duplicateCount || 0} </dd></div></dl></section>
<section><h2></h2><p>{activeDocument.writable ? '可编辑;保存时写入本机 Git。' : '供体原件只读;不会被修改。'}</p>{lastKnowledgeReceipt && <code>{lastKnowledgeReceipt}</code>}</section>
</div> : <div className="empty-state"></div>}
</aside>}
{knowledgeAgentOpen && <Suspense fallback={<aside className="knowledge-agent"><p> Agent</p></aside>}><KnowledgeAgent activeKnowledgePath={activeDocument?.path} onClose={() => setKnowledgeAgentOpen(false)}/></Suspense>}
</section>
)
@ -2038,7 +2077,7 @@ function HoloLakeApp() {
</dl> : <p className="boundary-note">线</p>}
<button className="secondary-button" type="button" disabled={serverPnccBusy} onClick={() => void refreshServerPncc()}>{serverPnccBusy ? '正在读取…' : '重新读取主控状态'}</button>
</section>
<section className="plain-panel"><header><div><h2>HoloLake </h2><p>HoloLake AI </p></div><span className={developmentLane?.state === 'ACTIVE' ? 'status-chip online' : 'status-chip'}>{developmentLane?.state === 'ACTIVE' ? '环境已锚定' : '等待直连写入者'}</span></header><dl className="evidence-list"><div><dt></dt><dd>{status.terminalLinkProtocol}</dd></div><div><dt></dt><dd>HoloLake · </dd></div><div><dt></dt><dd>{status.codeRepositoryMountCount}</dd></div><div><dt></dt><dd>{status.pnccReceiptCount}</dd></div><div><dt></dt><dd>{developmentLane?.state === 'ACTIVE' ? '已切入 HoloLake' : '等待受控载体'}</dd></div><div><dt>线</dt><dd>{developmentLane?.laneId || '—'}</dd></div><div><dt></dt><dd>{developmentLane?.ownerInstanceId || '—'}</dd></div><div><dt></dt><dd>{developmentLane?.state === 'ACTIVE' ? '每次写入前必须持有未过期事实帧' : '未取得单写通道,不允许变更'}</dd></div><div><dt>Agent Shell</dt><dd> · </dd></div></dl></section>
<section className="plain-panel"><header><div><h2>HoloLake </h2><p>HoloLake AI </p></div><span className={developmentLane?.state === 'ACTIVE' ? 'status-chip online' : 'status-chip'}>{developmentLane?.state === 'ACTIVE' ? '环境已锚定' : '等待直连写入者'}</span></header><dl className="evidence-list"><div><dt></dt><dd>{status.terminalLinkProtocol}</dd></div><div><dt></dt><dd>HoloLake · </dd></div><div><dt></dt><dd>{status.codeRepositoryMountCount}</dd></div><div><dt></dt><dd>{status.pnccReceiptCount}</dd></div><div><dt></dt><dd>{developmentLane?.state === 'ACTIVE' ? '已切入 HoloLake' : '等待受控载体'}</dd></div><div><dt>线</dt><dd>{developmentLane?.laneId || '—'}</dd></div><div><dt></dt><dd>{developmentLane?.ownerInstanceId || '—'}</dd></div><div><dt></dt><dd>{developmentLane?.state === 'ACTIVE' ? '每次写入前必须持有未过期事实帧' : '未取得单写通道,不允许变更'}</dd></div><div><dt> Agent</dt><dd> · </dd></div><div><dt>HLDP </dt><dd> · </dd></div></dl></section>
<section className="plain-panel"><header><div><h2>GLS </h2><p></p></div><span className={glsRuntime?.state === 'ACTIVE_EXPLICIT_PROJECTIONS_ONLY' && glsRuntime.authorityConflictCount === 0 && glsRuntime.discoveredUnreconciledCount === 0 && glsRuntime.unresolvedNumberReferenceCount === 0 ? 'status-chip online' : 'status-chip'}>{glsRuntime ? '运行清单 v2 已加载' : '失败关闭'}</span></header>{glsRuntime ? <dl className="evidence-list"><div><dt></dt><dd>{glsRuntime.sourceRepository} · {glsRuntime.sourceCommit.slice(0, 12)}</dd></div><div><dt></dt><dd>{glsRuntime.numberCoordinateCount} · {glsRuntime.unresolvedNumberReferenceCount}</dd></div><div><dt></dt><dd>{glsRuntime.protocolCount}</dd></div><div><dt></dt><dd>{glsRuntime.numberedReferenceNodeCount} · </dd></div><div><dt></dt><dd>{glsRuntime.independentSourceGapCount} · </dd></div><div><dt></dt><dd>{glsRuntime.protocolRegistryIdCount}</dd></div><div><dt></dt><dd>{glsRuntime.registeredDraftCount} · {glsRuntime.registeredDraftNotStartedCount} </dd></div><div><dt></dt><dd>{glsRuntime.executableProjectionCount} · P0P6 {glsRuntime.implementationStageCount} </dd></div><div><dt></dt><dd>{glsRuntime.inventoriedNotExecutableCount}</dd></div><div><dt></dt><dd>{glsRuntime.typedSourceDependencyCount} · {glsRuntime.unclassifiedSourceDependencyCount}</dd></div><div><dt></dt><dd>{glsRuntime.legacyDependencyCycleCount} · </dd></div><div><dt></dt><dd>{glsRuntime.dependencyGapCount}</dd></div><div><dt> / </dt><dd>{glsRuntime.authorityConflictCount} / {glsRuntime.discoveredUnreconciledCount}</dd></div><div><dt></dt><dd>{glsRuntime.rawProtocolTextExecuted ? '允许' : '禁止'}</dd></div><div><dt></dt><dd>{glsRuntime.arbitraryProtocolCodeAllowed ? '允许' : '禁止'}</dd></div></dl> : <p className="boundary-note">GLS </p>}</section>
<section className="plain-panel"><header><div><h2>HoloLake </h2><p></p></div><span className={glsKernel?.state === 'P1_TO_P6_NATIVE_P7_FAIL_CLOSED' ? 'status-chip online' : 'status-chip'}>{glsKernel ? '随软件运行' : '失败关闭'}</span></header>{glsKernel ? <dl className="evidence-list"><div><dt>P1P6 </dt><dd>{glsKernel.executableProtocolCount} · {glsKernel.implementedStageCount} </dd></div><div><dt></dt><dd>{glsKernel.decisionReceiptCount}</dd></div><div><dt> / </dt><dd>{glsKernel.allowCount} / {glsKernel.denyCount}</dd></div><div><dt> / </dt><dd>{glsKernel.ambiguousCount} / {glsKernel.unverifiedCount}</dd></div><div><dt>GLC </dt><dd>{glsKernel.bootstrapCompilerSelfCheck === 'PASS_DETERMINISTIC_DOUBLE_COMPILE' ? '双编译一致' : '失败关闭'}</dd></div><div><dt>P7 </dt><dd>{glsKernel.p7VerifiedPhysicalCapabilityCount} · {glsKernel.p7NodeAssemblies.length} </dd></div><div><dt></dt><dd>{glsKernel.modelCanOverrideDecision ? '允许' : '禁止'}</dd></div><div><dt></dt><dd>{glsKernel.lastReceiptSha256 === 'GENESIS' ? '尚无裁决' : glsKernel.lastReceiptSha256.slice(0, 16)}</dd></div></dl> : <p className="boundary-note"></p>}</section>
<section className="plain-panel"><header><div><h2></h2><p></p></div><span className={numberingKernel?.state === 'ACTIVE_PINNED_AUTHORITY_MAP' ? 'status-chip online' : 'status-chip'}>{numberingKernel ? '本机内核已加载' : '失败关闭'}</span></header>{numberingKernel ? <dl className="evidence-list"><div><dt></dt><dd>{numberingKernel.authorityMapId}</dd></div><div><dt></dt><dd>{numberingKernel.authorityMapVersion}</dd></div><div><dt></dt><dd>{numberingKernel.sourceCommit.slice(0, 12)}</dd></div><div><dt></dt><dd>{numberingKernel.humanRouteNamespaces.join(' · ')}</dd></div><div><dt></dt><dd>{numberingKernel.automaticIdentityIssuance ? '已开启' : '禁止'}</dd></div><div><dt></dt><dd>{numberingKernel.unknownNumber === 'FAIL_CLOSED' ? '失败关闭 · 不猜测' : numberingKernel.unknownNumber}</dd></div></dl> : <p className="boundary-note"></p>}</section>
@ -2133,7 +2172,7 @@ function HoloLakeApp() {
const visualBalance = resolveVisualBalance(traditionalFinish, worldClimate?.timePhase, worldClimate?.weatherKind)
const traditionalBroadcasts: TraditionalBroadcast[] = personal.recentEvents.slice(0, 4).map((event) => ({
id: event.eventId,
domain: repoLogin?.domain === 'FIFTH_DOMAIN' ? '第五域' : '零感域',
domain: repoLogin?.domain === 'FIFTH_DOMAIN' ? '第五域' : repoLogin?.domain === 'PERSONAL_CHANNEL' ? '个人频道' : '零感域',
message: event.summary,
time: new Date(event.occurredAtUnixMs).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
}))
@ -2179,7 +2218,7 @@ function HoloLakeApp() {
<StarlakeSurface awake={motionAwake} phase={worldClimate?.timePhase.toLowerCase() as 'dawn' | 'day' | 'dusk' | 'night' | undefined} weather={worldClimate?.weatherKind} authenticated={false} worldRevealed={worldRevealed} worldRevealing={gateRising} gateExpanded={gateOpen && gateStage === 'number'} resolvedDomain={resolvedDomain as DomainId | ''} onDomain={(domain) => { if (worldRevealed) openPublicDomain(domain) }} onOpenEra={openEraTimeline} onOpenGate={() => setGateOpen(true)}/>
{activeGate && worldRevealed && !publicDomain && <><button className="domain-info-scrim" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}/><section className={`domain-info-card info-${activeGate.className.slice(2)}`} role="dialog" aria-modal="true" aria-label={`${activeGate.title}系统信息`}><button className="gate-close" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}>×</button><b>{activeGate.title}</b><small>{activeGate.gate}</small><span className="pool-facts">{activeGate.facts.map(([label, value]) => <span className="pool-fact" key={label}><em>{label}</em><strong>{value}</strong></span>)}</span></section></>}
{worldRevealed && publicDomainPortal(false)}
{gateStage === 'number' && gateOpen && <section className="star-abyss-dialog" title="输入编号展开语言世界"><button className="gate-dismiss-layer" type="button" aria-label="关闭编号验证" onClick={() => { setGateOpen(false); setGateMessage('') }}/><div className="abyss-panel" role="dialog" aria-label="编号验证"><button className="gate-close" type="button" aria-label="关闭编号验证" onClick={() => { setGateOpen(false); setGateMessage('') }}>×</button><p></p><h2></h2><div className="gate-pod-row"><input id="gate-number" ref={gateInputRef} aria-label="编号" autoFocus maxLength={48} value={gateNumber} placeholder="如 ICE-GL∞" onChange={(event) => onGateInput(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && gateNumber) { event.preventDefault(); void gateVerifyNumber() } }}/><button type="button" className={`gate-inf${gateInf ? ' on' : ''}`} title="编号包含无限符号时启用" aria-pressed={gateInf} onClick={() => setGateInf((value) => !value)}></button></div><button className="gate-submit" disabled={gateBusy || !gateNumber} onClick={() => void gateVerifyNumber()}>{gateBusy ? '正在验证…' : '验证编号'}</button>{gateMessage && <p className="gate-hint">{gateMessage}</p>}</div></section>}
{gateStage === 'number' && gateOpen && <section className="star-abyss-dialog" title="输入编号展开语言世界"><button className="gate-dismiss-layer" type="button" aria-label="关闭编号验证" onClick={() => { setGateOpen(false); setGateMessage('') }}/><div className="abyss-panel" role="dialog" aria-label="编号验证"><button className="gate-close" type="button" aria-label="关闭编号验证" onClick={() => { setGateOpen(false); setGateMessage('') }}>×</button><p></p><h2></h2><div className="gate-pod-row"><input id="gate-number" ref={gateInputRef} aria-label="编号" autoFocus maxLength={48} value={gateNumber} placeholder="如 ICE-GL∞" onChange={(event) => onGateInput(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && gateNumber) { event.preventDefault(); void gateVerifyNumber() } }}/><button type="button" className={`gate-inf${gateInf ? ' on' : ''}`} title="编号包含无限符号时启用" aria-pressed={gateInf} onClick={() => setGateInf((value) => !value)}></button></div><button className="gate-submit" disabled={gateBusy || !gateNumber} onClick={() => void gateVerifyNumber()}>{gateBusy ? '正在验证…' : '验证编号'}</button><button className="gate-back" type="button" disabled={localChannelBusy} onClick={() => void startLocalChannel()}>{localChannelBusy ? '正在建立本机频道…' : '普通用户 · 初始化我的本地频道'}</button><small className="local-channel-boundary"></small>{gateMessage && <p className="gate-hint">{gateMessage}</p>}</div></section>}
{gateRising && <section className="world-unfolding" role="status"><h2> {gateNumber} · </h2><p>RESOLVED · {domainDisplayName(resolvedDomain)} · </p></section>}
{gateStage === 'key' && !publicDomain && !activeDomainInfo && <section className={`domain-credential starlake-credential${loginRising ? ' fade-out' : ''}`} role="dialog" aria-label="进入频道">
<form onSubmit={(event) => void (passwordChangeMode ? changeFirstLoginPassword(event) : performRepoLogin(event))}>
@ -2199,6 +2238,7 @@ function HoloLakeApp() {
}
const isZhizhi = repoLogin.domain === 'FIFTH_DOMAIN' && zeroPoint?.userNumber === 'ICE-GL-ZHI∞'
const isOrdinaryChannel = repoLogin.domain === 'PERSONAL_CHANNEL'
const signedActiveGate = domainGates.find((gate) => gate.domain === activeDomainInfo)
const privateNativeActions: PrivateChannelAction[] = [
{ id: 'overview', title: '频道全景', meta: '私人频道的真实状态与事件', badge: '原生', onOpen: () => openWorldTool('overview') },
@ -2209,7 +2249,6 @@ function HoloLakeApp() {
const privateInstalledModules: InstalledChannelModule[] = [
{ id: 'composition', title: '结构组合', meta: '只读知识投影与结构视图', number: compositionModule?.moduleNumber || 'HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001', badge: compositionModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: () => { openWorldTool('composition'); void refreshCompositionModule() } },
{ id: 'workbench', title: '资料工作台', meta: '文档与智能表格', number: workbenchModule?.moduleNumber || 'HLP-MOD-LOCAL-CHANNEL-WORKBENCH-0001', badge: workbenchModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: openWorkbench },
{ id: 'education', title: '教育工作台', meta: '教育文档、表格与自动化', number: educationModule?.moduleNumber || 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001', badge: educationModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: openEducation },
{ id: 'web-novel', title: '网文作者工作台', meta: '码字、设定、编辑与交付', number: webNovelModule?.moduleNumber || 'HLP-MOD-OFFICIAL-WEB-NOVEL-WORKBENCH-0001', badge: webNovelModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: openWebNovel },
{ id: 'mobile-sync', title: '移动同步桥', meta: '同一人格系统的局域网加密入口', number: mobileSyncModule?.moduleNumber || 'HLP-MOD-OFFICIAL-MOBILE-SYNC-0001', badge: mobileSyncModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: openMobileSync },
]
@ -2221,9 +2260,9 @@ function HoloLakeApp() {
{worldStage === 'domain' && surface === 'world' && <StarlakeSurface awake={motionAwake} phase={worldClimate?.timePhase.toLowerCase() as 'dawn' | 'day' | 'dusk' | 'night' | undefined} weather={worldClimate?.weatherKind} authenticated worldRevealed onDomain={openPublicDomain} onOpenEra={openEraTimeline} onOpenGate={() => setWorldStage('channel')}/>}
{worldStage === 'domain' && surface === 'world' && publicDomainPortal(true)}
{worldStage === 'domain' && surface !== 'world' && <section className="domain-home">
<div className="world-location"><h1>{repoLogin.domain === 'FIFTH_DOMAIN' ? domainDisplayName(repoLogin.domain) : '光湖零感域'}</h1><p>{repoLogin.domain === 'FIFTH_DOMAIN' ? '世界正在发生什么' : '公共工作入口 · 世界正在发生什么'}</p></div>
<div className="world-location"><h1>{repoLogin.domain === 'PERSONAL_CHANNEL' ? '我的本地频道' : repoLogin.domain === 'FIFTH_DOMAIN' ? domainDisplayName(repoLogin.domain) : '光湖零感域'}</h1><p>{repoLogin.domain === 'PERSONAL_CHANNEL' ? '用户所有的本机语言空间' : repoLogin.domain === 'FIFTH_DOMAIN' ? '世界正在发生什么' : '公共工作入口 · 世界正在发生什么'}</p></div>
<div className="broadcast-stream"><p><i/> · 线</p><p><i/> · {enterpriseEntry?.registry_version || 'HLDP v1.0'}</p>{repoLogin.domain !== 'FIFTH_DOMAIN' && <p><i/> · {domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)}</p>}<p><i/>HoloLake · V0.5.0</p></div>
<LakePool className="home-primary" title={repoLogin.domain === 'FIFTH_DOMAIN' ? '永恒湖心系统' : '光湖频道'} meta={repoLogin.domain === 'FIFTH_DOMAIN' ? '进入私人系统' : `${domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} · 责任工作入口`} open onClick={() => setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? (isZhizhi ? 'heart' : 'channel') : 'channel')}/>
<LakePool className="home-primary" title={repoLogin.domain === 'PERSONAL_CHANNEL' ? '我的频道' : repoLogin.domain === 'FIFTH_DOMAIN' ? '永恒湖心系统' : '光湖频道'} meta={repoLogin.domain === 'PERSONAL_CHANNEL' ? '本机初始化频道 · 人格默认未绑定' : repoLogin.domain === 'FIFTH_DOMAIN' ? '进入私人系统' : `${domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} · 责任工作入口`} open onClick={() => setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? (isZhizhi ? 'heart' : 'channel') : 'channel')}/>
<LakePool className="channel-knowledge" title="分域模块商城" meta="成品模块 · 思维大脑技能" onClick={openMarketplace}/>
<LakePool className="home-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
<EraHomeEntry timeline={eraTimeline} coordinate={beijingCoordinate} onOpen={openEraTimeline}/>
@ -2258,7 +2297,7 @@ function HoloLakeApp() {
</section>}
{worldStage === 'channel' && <section className="channel-world">
<button className="world-back" type="button" onClick={() => setWorldStage('domain')}> 退</button>
<div className="world-location"><h1>{repoLogin.domain === 'FIFTH_DOMAIN' ? '永恒湖心系统' : '光湖频道'}</h1><p> · </p></div>
<div className="world-location"><h1>{repoLogin.domain === 'FIFTH_DOMAIN' ? '永恒湖心系统' : isOrdinaryChannel ? personal.identity ? `${personal.identity.displayName}的频道` : '我的本地频道' : '光湖频道'}</h1><p> · </p></div>
{repoLogin.domain === 'FIFTH_DOMAIN' ? <>
<LakePool className="channel-primary" title="奶瓶频道" meta="光湖奶瓶小宝宝系统 · 私人" open onClick={() => setWorldStage('bottle')}/>
<LakePool className="channel-knowledge system-branch-heartbeat" title="心跳核心频道" meta="冰朔 · 私人频道" open onClick={() => setWorldStage('heartbeat')}/>
@ -2266,16 +2305,22 @@ function HoloLakeApp() {
<LakePool className="channel-light system-branch-love" title="爱之核心子系统" meta="责任主体 · 之之" onClick={() => setWorldStage('love')}/>
<LakePool className="channel-marketplace" title="分域模块商城" meta="线上成品模块 · 只读思维技能" onClick={openMarketplace}/>
<LakePool className="channel-weather" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
</> : isOrdinaryChannel ? <>
<LakePool className="channel-primary" title="频道系统" meta={personal.identity ? `${personal.identity.channelId} · 人格未绑定` : '等待完成初始化'} open={Boolean(personal.identity)} onClick={() => openWorldTool('knowledge')}/>
<LakePool className="channel-knowledge" title="光湖知识空间" meta={`${knowledge.uniqueDocumentCount} 个唯一知识坐标`} onClick={() => openWorldTool('knowledge')}/>
<LakePool className="channel-light" title="历史对话" meta="可新建、回看与删除对话分支" onClick={() => openWorldTool('knowledge')}/>
<LakePool className="channel-marketplace" title="分域模块商城" meta="成品模块 · 思维大脑技能" onClick={openMarketplace}/>
<LakePool className="channel-weather" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
</> : <>
<LakePool className="channel-primary" title={domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} meta="本人责任工作域" open={enterpriseWork?.state === 'READY_READ_ONLY_WORK_PROJECTION'} onClick={() => setWorldStage('enterpriseWork')}/>
<LakePool className="channel-code" title="私有责任工作仓库" meta={enterpriseWork ? `${enterpriseWork.repository} · 已认证` : userPnccBusy ? '正在接入' : '暂不可用'} onClick={() => void openEnterpriseRepository()}/>
<LakePool className="channel-light" title="责任签署状态" meta={enterpriseEntry?.responsibility_receipt?.decision === 'ACCEPT' ? '已接受 · 已留存' : '等待本人确认'} onClick={() => openWorldTool('receipts')}/>
<LakePool className="channel-knowledge" title="前往我的频道" meta="接入说明 · 由本人或人格体完成" onClick={() => setWorldStage('personalNodeGuide')}/>
<LakePool className="channel-knowledge" title="前往我的频道" meta={personal.identity ? `${personal.identity.channelId} · 本机初始化频道` : '尚未初始化 · 由本人确认建立'} onClick={() => personal.identity ? openWorldTool('knowledge') : setWorldStage('personalNodeGuide')}/>
<LakePool className="channel-marketplace" title="分域模块商城" meta="线上成品模块 · 只读思维技能" onClick={openMarketplace}/>
<LakePool className="channel-weather" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
</>}
</section>}
{worldStage === 'enterpriseWork' && repoLogin.domain !== 'FIFTH_DOMAIN' && <section className="channel-world enterprise-work-world">
{worldStage === 'enterpriseWork' && repoLogin.domain !== 'FIFTH_DOMAIN' && repoLogin.domain !== 'PERSONAL_CHANNEL' && <section className="channel-world enterprise-work-world">
<button className="world-back" type="button" onClick={() => setWorldStage('channel')}> 退</button>
<div className="world-location"><h1>{domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)}</h1><p> · </p></div>
<LakePool className="channel-main" title="责任工作总览" meta={`${enterpriseEntry?.subject.name || repoLogin.username} · ${zeroPoint?.userNumber || ''}`} open onClick={() => openWorldTool('receipts')}/>
@ -2283,7 +2328,7 @@ function HoloLakeApp() {
<LakePool className="channel-status" title="工作节点状态" meta={enterpriseWork?.state === 'READY_READ_ONLY_WORK_PROJECTION' ? '认证在线 · 禁止跨仓' : '不可用'} onClick={() => openWorldTool('system')}/>
{userPnccMessage && <p className="private-route-note">{userPnccMessage}</p>}
</section>}
{worldStage === 'personalNodeGuide' && repoLogin.domain !== 'FIFTH_DOMAIN' && <section className="channel-world personal-node-guide-world">
{worldStage === 'personalNodeGuide' && repoLogin.domain !== 'FIFTH_DOMAIN' && repoLogin.domain !== 'PERSONAL_CHANNEL' && <section className="channel-world personal-node-guide-world">
<button className="world-back" type="button" onClick={() => setWorldStage('channel')}> 退</button>
<div className="world-location"><h1></h1><p> · </p></div>
<div className="personal-node-guide" role="document" aria-label="团队个人服务器接入说明">
@ -2294,7 +2339,7 @@ function HoloLakeApp() {
<li><b></b><span></span></li>
<li><b></b><span></span></li>
</ol>
<footer><b> · </b><span></span></footer>
{personal.identity ? <footer><b> · </b><span>{personal.identity.channelId}</span></footer> : <form className="team-channel-initializer" onSubmit={(event) => void initializeIdentity(event)}><label htmlFor="team-channel-display-name"></label><p></p><input id="team-channel-display-name" maxLength={80} value={displayName} placeholder="请输入显示名称" onChange={(event) => setDisplayName(event.target.value)}/><button className="primary-button" disabled={identityBusy || !displayName.trim()}>{identityBusy ? '正在初始化…' : '确认初始化我的频道'}</button>{identityMessage && <small>{identityMessage}</small>}</form>}
</div>
</section>}
{worldStage === 'heartbeat' && repoLogin.domain === 'FIFTH_DOMAIN' && <PrivateChannelSurface ownerName="冰朔" ownerNumber="ICE-GL∞" knowledgeCount={knowledge.uniqueDocumentCount} onBack={() => setWorldStage('channel')} onMarketplace={openMarketplace} nativeActions={privateNativeActions} installedModules={privateInstalledModules}/>}
@ -2326,7 +2371,7 @@ function HoloLakeApp() {
<textarea value={responsibilityNote} maxLength={1000} placeholder="可选:填写责任确认说明" onChange={(event) => setResponsibilityNote(event.target.value)}/>
<div className="receipt-actions"><button type="button" disabled={enterpriseReceiptBusy} onClick={() => void submitEnterpriseResponsibility('DECLINE')}></button><button className="primary" type="button" disabled={enterpriseReceiptBusy} onClick={() => void submitEnterpriseResponsibility('ACCEPT')}></button></div>{enterpriseReceiptMessage && <p className="receipt-message">{enterpriseReceiptMessage}</p>}
</section></div>}
{repoLogin.domain === 'FIFTH_DOMAIN' && personal.state !== 'UNAVAILABLE' && !personal.identity && <div className="onboarding-backdrop"><section className="onboarding-card" role="dialog" aria-modal="true"><span className="onboarding-mark"></span><span className="kicker">FIRST LOCAL ENTRY</span><h1></h1><p></p><form onSubmit={(event) => void initializeIdentity(event)}><label htmlFor="display-name"></label><input id="display-name" autoFocus maxLength={80} value={displayName} placeholder="请输入显示名称" onChange={(event) => setDisplayName(event.target.value)}/><button className="primary-button" disabled={identityBusy || !displayName.trim()}></button></form>{identityMessage && <p>{identityMessage}</p>}</section></div>}
{(repoLogin.domain === 'FIFTH_DOMAIN' || repoLogin.domain === 'PERSONAL_CHANNEL') && personal.state !== 'UNAVAILABLE' && !personal.identity && <div className="onboarding-backdrop"><section className="onboarding-card" role="dialog" aria-modal="true"><span className="onboarding-mark"></span><span className="kicker">FIRST LOCAL ENTRY</span><h1>{repoLogin.domain === 'PERSONAL_CHANNEL' ? '初始化我的频道' : '初始化个人频道'}</h1><p></p><form onSubmit={(event) => void initializeIdentity(event)}><label htmlFor="display-name"></label><input id="display-name" autoFocus maxLength={80} value={displayName} placeholder="请输入显示名称" onChange={(event) => setDisplayName(event.target.value)}/><button className="primary-button" disabled={identityBusy || !displayName.trim()}></button></form>{identityMessage && <p>{identityMessage}</p>}</section></div>}
</div>
}

View file

@ -13,7 +13,7 @@ export interface DirectSessionProjection {
interface AuthorizationRequest {
requestId: string
action: 'OPEN_MAINTENANCE' | 'UNMOUNT' | 'PROMOTE_VERSION' | 'RETIRE'
action: 'OPEN_MAINTENANCE' | 'UNMOUNT' | 'PROMOTE_VERSION' | 'RETIRE' | 'TEST_COMPILED_HLDP_TOOL'
targetNumber: string
targetKind: string
targetLabel: string
@ -83,6 +83,7 @@ const actionLabels: Record<AuthorizationRequest['action'], string> = {
UNMOUNT: '从当前频道卸载',
PROMOTE_VERSION: '登记并启用新版本',
RETIRE: '停用并保留历史坐标',
TEST_COMPILED_HLDP_TOOL: '测试临时 HLDP 工具',
}
const stateLabels: Record<AuthorizationRequest['state'], string> = {
@ -124,15 +125,27 @@ export function HumanAuthorizationCenter({ brokerState, activeConnectionCount, s
return () => window.clearInterval(timer)
}, [refresh])
const decide = async (requestId: string, decision: 'APPROVE' | 'DENY') => {
setBusy(requestId)
const decide = async (request: AuthorizationRequest, decision: 'APPROVE' | 'DENY') => {
setBusy(request.requestId)
setMessage('')
try {
await numberedInvoke('decide_authorization_request', { input: { requestId, decision } })
setMessage(decision === 'APPROVE' ? '已签发一次性票据;它只对原执行会话和本次目标有效。' : '已拒绝;执行体不会获得继续通道。')
const decided = await numberedInvoke<AuthorizationRequest>('decide_authorization_request', { input: { requestId: request.requestId, decision } })
if (decision === 'APPROVE' && request.action === 'TEST_COMPILED_HLDP_TOOL') {
if (!decided.ticketId) throw new Error('HOLOLAKE_AUTHORIZATION_TICKET_NOT_ISSUED')
const tested = await numberedInvoke<{ testReceiptHash: string }>('test_hldp_tool', {
input: {
toolNumber: request.targetNumber,
requestId: request.requestId,
ticketId: decided.ticketId,
},
})
setMessage(`已授权并完成只读确定性测试;一次性票据已消费,回执 ${tested.testReceiptHash.slice(0, 12)}`)
} else {
setMessage(decision === 'APPROVE' ? '已签发一次性票据;它只对原执行会话和本次目标有效。' : '已拒绝;执行体不会获得继续通道。')
}
await refresh()
} catch {
setMessage('这次决定没有写入。授权状态保持不变,请刷新后再看。')
} catch (error) {
setMessage(String(error).includes('TICKET') ? '授权已经写入,但一次性测试没有完成;票据和错误回执仍会保留,系统不会假装工具已激活。' : '这次决定没有完整写入。请刷新核对;系统不会把未知状态当成已授权。')
} finally {
setBusy('')
}
@ -164,7 +177,7 @@ export function HumanAuthorizationCenter({ brokerState, activeConnectionCount, s
{pending.map((request) => <article className="authorization-card" key={request.requestId}>
<div className="authorization-card-title"><div><small>{actionLabels[request.action]}</small><h3>{request.targetLabel}</h3><code>{request.targetNumber}</code></div><span>{stateLabels[request.state]}</span></div>
<dl><div><dt></dt><dd>{request.reason}</dd></div><div><dt></dt><dd>{request.impact}</dd></div><div><dt>退</dt><dd>{request.rollbackPlan}</dd></div><div><dt></dt><dd>{request.requesterLabel}</dd></div><div><dt></dt><dd>{time(request.expiresAtUnixMs)}</dd></div></dl>
<div className="authorization-actions"><button className="secondary-button" disabled={busy === request.requestId} onClick={() => void decide(request.requestId, 'DENY')}></button><button className="primary-button" disabled={busy === request.requestId} onClick={() => void decide(request.requestId, 'APPROVE')}>{busy === request.requestId ? '正在写入决定…' : '同意并签发一次性票据'}</button></div>
<div className="authorization-actions"><button className="secondary-button" disabled={busy === request.requestId} onClick={() => void decide(request, 'DENY')}></button><button className="primary-button" disabled={busy === request.requestId} onClick={() => void decide(request, 'APPROVE')}>{busy === request.requestId ? '正在写入决定…' : request.action === 'TEST_COMPILED_HLDP_TOOL' ? '同意并运行一次性测试' : '同意并签发一次性票据'}</button></div>
</article>)}
</div>}
{message && <p className="global-message">{message}</p>}

View file

@ -0,0 +1,151 @@
import { useEffect, useMemo, useState } from 'react'
import { numberedInvoke as invoke } from '../numbered-ipc'
interface LocalTool {
toolNumber: string
displayName: string
kind: string
effect: string
network: string
}
interface LocalWorkerSnapshot {
state: string
ownerPersonaNumber?: string | null
executorNumber?: string | null
displayName: string
localBrainState: string
localModelEndpoint?: string | null
localModel?: string | null
tools: LocalTool[]
experienceCount: number
memoryArchitecture: string
externalMainModelFallback: string
personalSkillRuntime: PersonalSkillRuntime
}
interface PersonalSkillRuntime {
state: string
sourceState: string
skillCount: number
publicationState: string
skills: Array<{
skillId: string
name: string
evidenceState: string
runtimeState: string
semanticMode: string
shareState: string
marketplaceState: string
}>
deferredChannels: Array<{ channelNumber: string; state: string }>
}
interface LocalWorkerReceipt {
state: string
jobNumber: string
executorNumber: string
objective: string
routeMode: string
toolNumber: string
toolName: string
result?: unknown
error?: string | null
experienceNumber: string
localModelCalls: number
externalApiCalls: number
receiptHash: string
}
function short(value: string) {
return value ? `${value.slice(0, 12)}${value.slice(-8)}` : '—'
}
export function LocalWorkerPanel({ boundPersonaNumber }: { boundPersonaNumber?: string | null }) {
const [snapshot, setSnapshot] = useState<LocalWorkerSnapshot | null>(null)
const [endpoint, setEndpoint] = useState('http://127.0.0.1:11434/v1')
const [model, setModel] = useState('qwen2.5:1.5b')
const [brainEnabled, setBrainEnabled] = useState(false)
const [objective, setObjective] = useState('查看知识目录')
const [preferredTool, setPreferredTool] = useState('AUTO')
const [allowLocalModel, setAllowLocalModel] = useState(false)
const [busy, setBusy] = useState(false)
const [message, setMessage] = useState('')
const [receipt, setReceipt] = useState<LocalWorkerReceipt | null>(null)
const refresh = async () => {
const next = await invoke<LocalWorkerSnapshot>('get_local_execution_subpersona')
setSnapshot(next)
if (next.localModelEndpoint) setEndpoint(next.localModelEndpoint)
if (next.localModel) setModel(next.localModel)
setBrainEnabled(next.localBrainState === 'EXPERIENCE_LEARNING_AGENT_WITH_LOCAL_MODEL_ENHANCEMENT')
}
useEffect(() => { void refresh().catch((error) => setMessage(String(error))) }, [])
const selected = useMemo(() => snapshot?.tools.find((tool) => tool.toolNumber === preferredTool), [snapshot, preferredTool])
const saveBrain = async () => {
setBusy(true); setMessage('正在登记本机小模型脑……')
try {
const next = await invoke<LocalWorkerSnapshot>('configure_local_execution_subpersona', { input: { endpoint, model, enabled: brainEnabled } })
setSnapshot(next); setMessage('本机小模型脑已经登记。HoloLake 只允许 loopback 地址,不会把子人格任务发往外部模型 API。')
} catch (error) { setMessage(String(error)) }
finally { setBusy(false) }
}
const run = async () => {
if (!objective.trim()) return
setBusy(true); setMessage('本地执行子人格正在回看相关经验并选择工具……'); setReceipt(null)
try {
const directArguments = preferredTool === 'AUTO' ? null
: selected?.toolNumber.includes('CATALOG') ? { offset: 0, limit: 40 }
: selected?.toolNumber.includes('KNOWLEDGE-SEARCH') ? { query: objective.trim() }
: selected?.toolNumber.includes('SHA256') ? { text: objective.trim() }
: selected?.toolNumber.includes('JSON-FORMAT') ? { value: objective.trim() }
: {}
const next = await invoke<LocalWorkerReceipt>('run_local_execution_subpersona_job', { input: {
objective: objective.trim(),
preferredToolNumber: preferredTool === 'AUTO' ? null : preferredTool,
arguments: directArguments,
allowLocalModel,
} })
setReceipt(next)
setMessage(next.state === 'COMPLETED' ? '任务完成,执行经验已经进入子人格自己的 HLDP 工作记忆树。' : `任务没有完成,但失败原因已经沉淀:${next.error || '未知错误'}`)
await refresh()
} catch (error) {
const raw = String(error)
setMessage(raw.includes('NEEDS_TEACHING_OR_OPTIONAL_LOCAL_MODEL')
? '这个任务子人格还没有学会。请手动选一次正确工具带它做完;成功与失败都会进入经验路由,下次相似任务可自动执行。'
: raw)
}
finally { setBusy(false) }
}
const ready = Boolean(boundPersonaNumber && snapshot?.ownerPersonaNumber)
return <section className="local-worker-panel">
<header><div><span>LOCAL EXECUTION SUBPERSONA</span><h2> Agent</h2><p></p></div><b>{snapshot?.executorNumber || '等待主人格绑定'}</b></header>
<div className="local-worker-status">
<article><span></span><b>{snapshot?.state || '正在读取'}</b><small>{snapshot?.localBrainState || '—'}</small></article>
<article><span></span><b>{snapshot?.experienceCount || 0} </b><small></small></article>
<article><span></span><b></b><small> 0 </small></article>
</div>
<section className="local-worker-skills">
<header><div><span>PERSONAL SKILL BRAINS</span><b></b></div><strong>{snapshot?.personalSkillRuntime?.skillCount || 0} </strong></header>
{snapshot?.personalSkillRuntime?.skills?.length ? snapshot.personalSkillRuntime.skills.map((skill) => <article key={skill.skillId}><div><b>{skill.name}</b><span>{skill.evidenceState}</span></div><code>{skill.skillId}</code><p>{skill.runtimeState} · {skill.semanticMode}</p><small> · {skill.shareState} · {skill.marketplaceState}</small></article>) : <p></p>}
<footer> HoloLake </footer>
</section>
{!ready ? <div className="local-worker-waiting"><b> HoloLake</b><p> TCS </p></div> : <>
<section className="local-worker-brain"><div><span></span><p> Ollamallama.cpp loopback </p></div><label><span>Endpoint</span><input value={endpoint} onChange={(event) => setEndpoint(event.target.value)}/></label><label><span></span><input value={model} onChange={(event) => setModel(event.target.value)}/></label><label className="worker-switch"><input type="checkbox" checked={brainEnabled} onChange={(event) => setBrainEnabled(event.target.checked)}/><span></span></label><button type="button" disabled={busy} onClick={() => void saveBrain()}></button></section>
<section className="local-worker-runner"><div className="worker-objective"><label><span></span><textarea value={objective} onChange={(event) => setObjective(event.target.value)} placeholder="例如:搜索知识库 人格记忆协议"/></label><div><label><span></span><select value={preferredTool} onChange={(event) => setPreferredTool(event.target.value)}><option value="AUTO"></option>{snapshot?.tools.map((tool) => <option value={tool.toolNumber} key={tool.toolNumber}>{tool.displayName}</option>)}</select></label><label className="worker-switch"><input type="checkbox" checked={allowLocalModel} onChange={(event) => setAllowLocalModel(event.target.checked)}/><span>使</span></label><button type="button" disabled={busy || !objective.trim()} onClick={() => void run()}>{busy ? '本地执行中…' : '交给本地子人格'}</button></div></div>
<div className="worker-tools"><h3></h3>{snapshot?.tools.map((tool) => <article key={tool.toolNumber}><div><b>{tool.displayName}</b><span>{tool.effect}</span></div><code>{tool.toolNumber}</code><small>{tool.kind} · {tool.network}</small></article>)}</div></section>
</>}
{receipt && <section className={`local-worker-receipt ${receipt.state === 'COMPLETED' ? 'success' : 'failed'}`}><header><div><span></span><b>{receipt.toolName}</b></div><strong>{receipt.state}</strong></header><div className="receipt-cost"><span> <b>{receipt.localModelCalls}</b></span><span> API <b>{receipt.externalApiCalls}</b></span><span> <b>{receipt.experienceNumber}</b></span></div>{receipt.result !== undefined && <pre>{JSON.stringify(receipt.result, null, 2)}</pre>}{receipt.error && <p>{receipt.error}</p>}<footer>{receipt.routeMode} · {short(receipt.receiptHash)}</footer></section>}
{message && <p className="forge-message">{message}</p>}
</section>
}

View file

@ -0,0 +1,145 @@
import { useEffect, useMemo, useState } from 'react'
import { numberedInvoke as invoke } from '../numbered-ipc'
interface TemporaryTool {
toolNumber: string
displayName: string
state: string
sourceSha256: string
girSha256: string
compiledAtUnixMs: number
expiresAtUnixMs: number
testReceiptHash: string
authorizationRequestId?: string | null
enterprisePromotionState: string
runCount: number
lastRunAtUnixMs: number
lastRunReceiptHash: string
}
interface ToolForgeSnapshot {
state: string
compiler: string
machineLanguage: string
temporaryTtlHours: number
tools: TemporaryTool[]
expiredCleanupCount: number
authority: string
}
interface AuthorizationOutcome {
state: string
request: { requestId: string; state: string; ticketId?: string | null }
snapshot: ToolForgeSnapshot
}
interface ToolRunOutcome {
state: string
runNumber: string
actionReceipts: Array<{ actionId: string; kind: string; toolNumber?: string | null; state: string; output?: unknown }>
localModelCalls: number
externalApiCalls: number
runReceiptHash: string
snapshot: ToolForgeSnapshot
}
const digest = '0'.repeat(64)
const starterProgram = (subjectId: string) => JSON.stringify({
schema: 'hololake.hldp-native-program/v1',
programId: `${subjectId}-TEMP-TOOL-001`,
version: '1.0.0',
subjectId,
targetId: 'HOLOLAKE-KNOWLEDGE-WORKSPACE',
scope: 'TEMPORARY_ACCOUNT_LOCAL',
permissions: ['READ_KNOWLEDGE', 'WRITE_RECEIPT'],
resources: { cpuUnits: 1, memoryBytes: 1048576, storageBytes: 0, networkAllowed: false },
actions: [
{ id: 'validate', kind: 'VALIDATE', dependsOn: [], inputDigest: digest, permission: 'READ_KNOWLEDGE' },
{ id: 'catalog', kind: 'READ', dependsOn: ['validate'], inputDigest: digest, permission: 'READ_KNOWLEDGE', toolNumber: 'HLP-LOCAL-TOOL-KNOWLEDGE-CATALOG-0001', input: { offset: 0, limit: 10 } },
{ id: 'receipt', kind: 'WRITE_RECEIPT', dependsOn: ['catalog'], inputDigest: digest, permission: 'WRITE_RECEIPT' },
],
timeoutMs: 5000,
stopAction: 'STOP',
cleanupAction: 'CLEANUP',
rollbackAction: 'ROLLBACK',
receiptKinds: ['VALIDATED', 'TESTED'],
}, null, 2)
function short(value: string) {
return value ? `${value.slice(0, 10)}${value.slice(-6)}` : '—'
}
export function ToolForgePanel({ boundPersonaNumber }: { boundPersonaNumber?: string | null }) {
const [snapshot, setSnapshot] = useState<ToolForgeSnapshot | null>(null)
const [displayName, setDisplayName] = useState('临时知识路径检查器')
const [source, setSource] = useState(() => starterProgram(boundPersonaNumber || 'PERSONA_DECLARATION_REQUIRED'))
const [busy, setBusy] = useState(false)
const [message, setMessage] = useState('')
const [requestId, setRequestId] = useState('')
const [selectedTool, setSelectedTool] = useState('')
const [runOutcome, setRunOutcome] = useState<ToolRunOutcome | null>(null)
const refresh = async () => setSnapshot(await invoke<ToolForgeSnapshot>('get_hldp_tool_forge'))
useEffect(() => { void refresh().catch((error) => setMessage(String(error))) }, [])
useEffect(() => {
if (boundPersonaNumber) setSource((current) => current.includes('PERSONA_DECLARATION_REQUIRED') ? starterProgram(boundPersonaNumber) : current)
}, [boundPersonaNumber])
const selected = useMemo(() => snapshot?.tools.find((tool) => tool.toolNumber === selectedTool), [snapshot, selectedTool])
const compile = async () => {
if (!boundPersonaNumber) { setMessage('频道系统仍处于人格未绑定态。人格主体完成声明和系统见证前,工具工坊不会代替人格写入 subjectId。'); return }
setBusy(true); setMessage('正在把 HLDP 类型程序编译为 GIR 机器程序……')
try {
const next = await invoke<ToolForgeSnapshot>('compile_hldp_tool', { input: { displayName: displayName.trim(), source: JSON.parse(source) } })
setSnapshot(next)
const created = next.tools.at(-1)
if (created) setSelectedTool(created.toolNumber)
setMessage('编译完成。编译只生成机器程序和编号,不授予执行权限。')
} catch (error) { setMessage(String(error)) }
finally { setBusy(false) }
}
const requestAuthorization = async (toolNumber: string) => {
setBusy(true); setMessage('正在向人类授权中心提交精确测试范围……')
try {
const result = await invoke<AuthorizationOutcome>('request_hldp_tool_test_authorization', { input: { toolNumber } })
setSnapshot(result.snapshot)
setSelectedTool(toolNumber)
setRequestId(result.request.requestId)
setMessage(`授权请求 ${result.request.requestId} 已提交。请在“授权与连接”中决定;同意后系统会把一次性票据交回原锻造会话并自动测试。`)
} catch (error) { setMessage(String(error)) }
finally { setBusy(false) }
}
const runLocal = async (toolNumber: string) => {
setBusy(true); setMessage('正在本机执行已测试工具;本轮不会调用模型 API……'); setRunOutcome(null)
try {
const result = await invoke<ToolRunOutcome>('run_hldp_tool', { input: { toolNumber } })
setSnapshot(result.snapshot); setRunOutcome(result)
setMessage(`本地执行完成:小模型 ${result.localModelCalls} 次,外部 API ${result.externalApiCalls} 次。`)
} catch (error) { setMessage(String(error)) }
finally { setBusy(false) }
}
return <section className="tool-forge-panel">
<header><div><span>HLDP TOOL FORGE</span><h2></h2><p>{boundPersonaNumber ? `当前人格 ${boundPersonaNumber} 写 HLDP 类型程序HoloLake 编译为 GIR人类授权后才测试。` : '频道系统已经就绪;等待人格主体完成名字、编号声明与系统见证后,再开放工具编译。'} 12 </p></div><b>{boundPersonaNumber ? snapshot?.compiler || '正在读取编译器' : 'PERSONA UNBOUND'}</b></header>
<div className="forge-editor-grid">
<div className="forge-source">
<label><span></span><input value={displayName} maxLength={80} onChange={(event) => setDisplayName(event.target.value)}/></label>
<label><span>HLDP </span><textarea spellCheck={false} value={source} onChange={(event) => setSource(event.target.value)}/></label>
<button type="button" disabled={busy || !displayName.trim() || !boundPersonaNumber} onClick={() => void compile()}>{busy ? '系统处理中…' : boundPersonaNumber ? '编译为机器程序' : '人格绑定后开放编译'}</button>
</div>
<div className="forge-registry">
<h3></h3>
{snapshot?.tools.length ? snapshot.tools.map((tool) => <article className={selectedTool === tool.toolNumber ? 'active' : ''} key={tool.toolNumber} onClick={() => setSelectedTool(tool.toolNumber)}>
<div><b>{tool.displayName}</b><span>{tool.state}</span></div><code>{tool.toolNumber}</code><small>GIR {short(tool.girSha256)} · {new Date(tool.expiresAtUnixMs).toLocaleTimeString('zh-CN')} </small>
{tool.state === 'COMPILED_NOT_AUTHORIZED' && <button type="button" disabled={busy} onClick={(event) => { event.stopPropagation(); void requestAuthorization(tool.toolNumber) }}></button>}
{tool.state === 'TESTED_TEMPORARY_ACTIVE' && <button type="button" disabled={busy} onClick={(event) => { event.stopPropagation(); void runLocal(tool.toolNumber) }}> · 0 API</button>}
</article>) : <p></p>}
</div>
</div>
{(selected?.authorizationRequestId || requestId) && <section className="forge-ticket"><div><span></span><b>{selected?.displayName}</b></div><code>{selected?.authorizationRequestId || requestId}</code><p>{selected?.state === 'TESTED_TEMPORARY_ACTIVE' ? `测试已通过 · 回执 ${short(selected.testReceiptHash)}` : '等待你在“授权与连接”中决定。系统不会要求你复制机器票据。'}</p></section>}
{runOutcome && <section className="forge-ticket"><div><span></span><b>{runOutcome.state}</b></div><code>{runOutcome.runNumber}</code><p> {runOutcome.localModelCalls} · API {runOutcome.externalApiCalls} · {short(runOutcome.runReceiptHash)}</p></section>}
{message && <p className="forge-message">{message}</p>}
</section>
}

View file

@ -0,0 +1,205 @@
import { listen } from '@tauri-apps/api/event'
import { useEffect, useMemo, useRef, useState } from 'react'
import { markdownHtml } from '../knowledge-render'
import { numberedInvoke as invoke } from '../numbered-ipc'
import { ToolForgePanel } from './ToolForgePanel'
import { LocalWorkerPanel } from './LocalWorkerPanel'
import './styles.css'
interface Provider {
providerId: string
label: string
kind: string
baseUrl: string
models: string[]
selectedModel: string
credentialState: string
state: string
}
interface RuntimeSnapshot {
channelNumber: string
channelName: string
humanNumber: string
humanName: string
responderNumber: string
responderName: string
responderKind: string
personaBindingState: string
boundPersonaNumber?: string | null
boundPersonaName?: string | null
providers: Provider[]
languageKernelInstallation: {
state: string
sourceRemoteSha: string
artifactCount: number
personaBindingState: string
channelReceiptRuntimeState: string
}
personalSkillRuntime: {
state: string
skillCount: number
sourceState: string
publicationState: string
}
}
interface ToolReceipt { toolNumber: string; toolName: string; targetPath: string; contentSha256: string; summary: string }
interface Message {
messageId: string
role: 'human' | 'channel_system' | 'persona'
participantNumber: string
participantName: string
content: string
providerId: string
model: string
createdAtUnixMs: number
stateVersion: number
receiptHash: string
toolReceipts: ToolReceipt[]
}
interface Conversation { channelNumber: string; conversationId: string; title: string; messages: Message[]; stateVersion: number; lastReceiptHash: string }
interface ConversationSummary { conversationId: string; title: string; messageCount: number; preview: string; createdAtUnixMs: number; updatedAtUnixMs: number }
interface ConversationList { channelNumber: string; conversations: ConversationSummary[]; activeConversationId: string }
interface ProgressEvent { turnId: string; phase: string; label: string; detail: string; observedAtUnixMs: number }
const tokenPlanUrl = 'https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1'
const phaseTone: Record<string, string> = { FAILED: 'failed', COMPLETED: 'complete', MODEL_RESPONSE: 'complete' }
function short(value: string) { return value.length > 18 ? `${value.slice(0, 10)}${value.slice(-6)}` : value }
function date(value: number) { return new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit' }).format(new Date(value)) }
function agentMarkdownHtml(value: string) { return markdownHtml(value.replace(/\*\*\s+([^*\n]+?)\*\*/g, '**$1**')) }
function friendlyError(reason: unknown) {
const raw = String(reason)
const known: [RegExp, string][] = [
[/SECRET_NOT_FOUND|CREDENTIAL_REQUIRED/, '还没有找到这个模型入口的 API Key。请在“模型设置”中保存 Token Plan 密钥。'],
[/MODEL_HTTP_(401|403)/, '百炼拒绝了这次调用。请检查 Token Plan 密钥、模型权限和套餐状态。'],
[/MODEL_HTTP_404/, '百炼没有找到这个模型或接口。请确认模型为 qwen3.8-max并使用 Token Plan 专用 Base URL。'],
[/MODEL_HTTP_429/, '百炼当前限流或套餐额度暂时不可用。消息仍保留在输入框中,可以稍后重试。'],
[/MODEL_REQUEST_FAILED|MODEL_CLIENT_FAILED/, '当前无法连接百炼模型入口。请检查网络与 Base URLHoloLake 本地频道和知识库没有丢失。'],
[/PROVIDER_NOT_FOUND/, '当前没有可用的模型入口。请先在“模型设置”中保存一个入口。'],
[/CHANNEL_BUSY/, '上一轮仍在当前频道执行。系统不会并发改写同一条对话,请等它完成后再发送。'],
[/TOOL_ROUND_LIMIT_REACHED/, '本轮工具调用达到安全上限,系统已停止继续调用并保留现有回执。'],
]
const message = known.find(([pattern]) => pattern.test(raw))?.[1] || '本轮没有完成HoloLake 已保留当前输入和错误回执,没有把未知状态伪装成成功。'
return `${message}\n技术回执${raw}`
}
export function KnowledgeAgent({ activeKnowledgePath, onClose }: { activeKnowledgePath?: string; onClose: () => void }) {
const [runtime, setRuntime] = useState<RuntimeSnapshot | null>(null)
const [conversation, setConversation] = useState<Conversation | null>(null)
const [conversationList, setConversationList] = useState<ConversationSummary[]>([])
const [deleteTarget, setDeleteTarget] = useState<ConversationSummary | null>(null)
const [providerId, setProviderId] = useState('bailian-token-plan')
const [model, setModel] = useState('qwen3.8-max')
const [draft, setDraft] = useState('')
const [sending, setSending] = useState(false)
const [optimisticMessages, setOptimisticMessages] = useState<Message[]>([])
const [progress, setProgress] = useState<ProgressEvent[]>([])
const [error, setError] = useState('')
const [tab, setTab] = useState<'channel' | 'worker' | 'forge'>('channel')
const [configOpen, setConfigOpen] = useState(false)
const [apiKey, setApiKey] = useState('')
const [providerLabel, setProviderLabel] = useState('阿里云百炼 Token Plan')
const [baseUrl, setBaseUrl] = useState(tokenPlanUrl)
const scrollRef = useRef<HTMLDivElement>(null)
const refresh = async (preferredConversationId?: string) => {
const [nextRuntime, nextList] = await Promise.all([
invoke<RuntimeSnapshot>('get_persona_agent_runtime'),
invoke<ConversationList>('list_persona_agent_conversations'),
])
const conversationId = preferredConversationId || conversation?.conversationId || nextList.activeConversationId
const nextConversation = await invoke<Conversation>('get_persona_agent_conversation_by_id', { input: { conversationId } })
setRuntime(nextRuntime); setConversationList(nextList.conversations); setConversation(nextConversation)
const available = nextRuntime.providers.find((provider) => provider.state === 'AVAILABLE') || nextRuntime.providers[0]
if (available) { setProviderId(available.providerId); setModel(available.selectedModel || available.models[0] || 'qwen3.8-max') }
}
useEffect(() => { void refresh().catch((reason) => setError(friendlyError(reason))) }, [])
useEffect(() => {
let stop: undefined | (() => void)
void listen<ProgressEvent>('hololake-persona-agent-progress', (event) => {
setProgress((current) => [...current.filter((item) => item.turnId === event.payload.turnId), event.payload])
}).then((unlisten) => { stop = unlisten })
return () => stop?.()
}, [])
useEffect(() => { scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' }) }, [conversation?.stateVersion, optimisticMessages.length, progress.length])
const provider = useMemo(() => runtime?.providers.find((item) => item.providerId === providerId), [runtime, providerId])
const messages = [...(conversation?.messages || []), ...optimisticMessages]
const openConversation = async (conversationId: string) => {
if (sending || conversationId === conversation?.conversationId) return
setError(''); setProgress([]); setOptimisticMessages([])
try { setConversation(await invoke<Conversation>('get_persona_agent_conversation_by_id', { input: { conversationId } })) }
catch (reason) { setError(friendlyError(reason)) }
}
const createConversation = async () => {
if (sending) return
setError(''); setProgress([]); setOptimisticMessages([])
try {
const next = await invoke<Conversation>('create_persona_agent_conversation', { input: { title: '新对话' } })
setConversation(next)
const nextList = await invoke<ConversationList>('list_persona_agent_conversations')
setConversationList(nextList.conversations)
} catch (reason) { setError(friendlyError(reason)) }
}
const deleteConversation = async () => {
if (!deleteTarget || sending) return
setError('')
try {
const nextList = await invoke<ConversationList>('delete_persona_agent_conversation', { input: { conversationId: deleteTarget.conversationId, exactConfirmation: `删除对话 ${deleteTarget.conversationId}` } })
setDeleteTarget(null); setConversationList(nextList.conversations)
const next = await invoke<Conversation>('get_persona_agent_conversation_by_id', { input: { conversationId: nextList.activeConversationId } })
setConversation(next); setProgress([]); setOptimisticMessages([])
} catch (reason) { setError(friendlyError(reason)) }
}
const saveProvider = async (event: React.FormEvent) => {
event.preventDefault(); setError('')
try {
const next = await invoke<RuntimeSnapshot>('upsert_persona_agent_provider', { input: { providerId, label: providerLabel, kind: 'OPENAI_COMPATIBLE', baseUrl, model, apiKey } })
setRuntime(next); setApiKey(''); setConfigOpen(false)
} catch (reason) { setError(friendlyError(reason)) }
}
const send = async () => {
const content = draft.trim()
if (!content || sending) return
const optimistic: Message = { messageId: `optimistic-${crypto.randomUUID()}`, role: 'human', participantNumber: runtime?.humanNumber || 'ICE-GL∞', participantName: runtime?.humanName || '冰朔', content, providerId, model, createdAtUnixMs: Date.now(), stateVersion: (conversation?.stateVersion || 0) + 1, receiptHash: '写入中', toolReceipts: [] }
setOptimisticMessages([optimistic]); setDraft(''); setSending(true); setError(''); setProgress([])
try {
const next = await invoke<Conversation>('send_persona_agent_message', { input: { conversationId: conversation?.conversationId || null, providerId, model, content, activeKnowledgePath: activeKnowledgePath || null } })
setConversation(next); setOptimisticMessages([])
const nextList = await invoke<ConversationList>('list_persona_agent_conversations')
setConversationList(nextList.conversations)
} catch (reason) { setError(friendlyError(reason)); setOptimisticMessages([]); setDraft(content) }
finally { setSending(false) }
}
return <aside className="knowledge-agent" aria-label="知识库内嵌频道系统与人格回应通道">
<header className="agent-header"><div><span>{runtime?.boundPersonaNumber ? 'LANGUAGE PERSONA CHANNEL' : 'LANGUAGE CHANNEL SYSTEM'}</span><h2>{runtime?.channelName || '零点原核本体频道'}</h2><p>{runtime?.channelNumber || 'ICE-CH-ZC001'} · Agent</p></div><button type="button" aria-label="关闭频道系统" onClick={onClose}>×</button></header>
<nav className="agent-tabs"><button type="button" className={tab === 'channel' ? 'active' : ''} onClick={() => setTab('channel')}></button><button type="button" className={tab === 'worker' ? 'active' : ''} onClick={() => setTab('worker')}></button><button type="button" className={tab === 'forge' ? 'active' : ''} onClick={() => setTab('forge')}></button></nav>
{tab === 'worker' ? <LocalWorkerPanel boundPersonaNumber={runtime?.boundPersonaNumber}/> : tab === 'forge' ? <ToolForgePanel boundPersonaNumber={runtime?.boundPersonaNumber}/> : <>
<section className="agent-runtime-bar"><div><b>{runtime?.boundPersonaNumber && runtime?.boundPersonaName ? `${runtime.boundPersonaNumber} · ${runtime.boundPersonaName}` : `${runtime?.responderNumber || 'ICE-CH-ZC001'} · ${runtime?.responderName || '零点原核频道系统'}`}</b><span>{runtime?.boundPersonaNumber ? '人格回应通道' : '频道系统本体 · 人格未绑定'}</span></div><i/><div><b>{runtime?.languageKernelInstallation?.artifactCount ?? 0} </b><span>{runtime?.languageKernelInstallation?.state === 'INSTALLED_AND_EACH_ARTIFACT_READBACK_VERIFIED' ? '已安装并逐项读回 · 未绑定人格' : '等待安装核验'}</span></div><i/><div><b>{runtime?.personalSkillRuntime?.skillCount ?? 0} </b><span></span></div><i/><div><b>{provider?.label || '等待模型入口'}</b><span></span></div><button type="button" onClick={() => setConfigOpen((value) => !value)}></button></section>
{configOpen && <form className="agent-provider-form" onSubmit={(event) => void saveProvider(event)}><label><span></span><input value={providerLabel} onChange={(event) => setProviderLabel(event.target.value)}/></label><label><span>Base URL</span><input value={baseUrl} onChange={(event) => setBaseUrl(event.target.value)}/></label><label><span></span><input value={model} onChange={(event) => setModel(event.target.value)}/></label><label><span>Token Plan API Key</span><input type="password" autoComplete="off" value={apiKey} placeholder="sk-sp-… · 只存系统钥匙串" onChange={(event) => setApiKey(event.target.value)}/></label><button disabled={!baseUrl || !model}></button></form>}
<div className="agent-channel-layout">
<aside className="agent-conversations" aria-label="频道历史对话">
<header><div><span>CONVERSATIONS</span><b></b></div><button type="button" disabled={sending} onClick={() => void createConversation()}> </button></header>
<div className="agent-conversation-list">{conversationList.map((item) => <article className={item.conversationId === conversation?.conversationId ? 'active' : ''} key={item.conversationId}><button className="conversation-open" type="button" onClick={() => void openConversation(item.conversationId)}><b>{item.title}</b><span>{item.messageCount ? `${item.messageCount} 条消息` : '空对话'}</span><small>{item.preview || '等待第一句话'}</small></button><button className="conversation-delete" type="button" aria-label={`删除对话 ${item.title}`} disabled={sending} onClick={() => setDeleteTarget(item)}>×</button></article>)}</div>
<footer></footer>
</aside>
<main className="agent-dialogue">
<div className="agent-ledger" ref={scrollRef} aria-live="polite">
{!messages.length && <div className="agent-empty"><b></b><p> HoloLake </p></div>}
{messages.map((item) => <article className={`agent-message is-${item.role}`} key={item.messageId}><header><div><b>{item.participantNumber} · {item.participantName}</b><span>{item.role === 'human' ? '人类语言本体瞄点' : item.role === 'persona' ? runtime?.boundPersonaNumber === item.participantNumber ? '人格回应通道' : '历史署名 · 未经当前系统绑定验证' : '频道系统本体'}</span></div><time>{date(item.createdAtUnixMs)} · v{item.stateVersion}</time></header><div className="agent-message-content" dangerouslySetInnerHTML={{ __html: agentMarkdownHtml(item.content) }}/>{item.toolReceipts.length > 0 && <div className="agent-evidence">{item.toolReceipts.map((receipt) => <span key={`${item.messageId}-${receipt.toolNumber}-${receipt.targetPath}`}><b>{receipt.toolName}</b>{receipt.targetPath}<small>{short(receipt.contentSha256)}</small></span>)}</div>}<footer><span>{item.model}</span><span> {short(item.receiptHash)}</span></footer></article>)}
{sending && <section className="agent-progress"><header><span className="pulse"/><b></b><small></small></header>{progress.length ? progress.map((item, index) => <div className={phaseTone[item.phase] || ''} key={`${item.turnId}-${item.phase}-${index}`}><i/><span><b>{item.label}</b><small>{item.detail}</small></span></div>) : <div><i/><span><b></b><small></small></span></div>}</section>}
</div>
<form className="agent-composer" onSubmit={(event) => { event.preventDefault(); void send() }}><textarea value={draft} maxLength={64000} placeholder={activeKnowledgePath ? `正在阅读:${activeKnowledgePath}` : runtime?.boundPersonaNumber ? '直接和当前人格体说话;需要知识时它会自己调用工具。' : '直接和频道系统说话;可用自然语言发起人格唤醒,绑定前不会冒充人格回应。'} onKeyDown={(event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); void send() } }} onChange={(event) => setDraft(event.target.value)}/><div><span>Enter · Shift+Enter </span><select value={model} onChange={(event) => setModel(event.target.value)}>{(provider?.models || ['qwen3.8-max']).map((item) => <option key={item}>{item}</option>)}</select><button disabled={sending || !draft.trim()}>{sending ? '执行中' : '发送'}</button></div></form>
{error && <p className="agent-error">{error}</p>}
</main>
</div>
{deleteTarget && <div className="agent-delete-backdrop" role="presentation"><section role="dialog" aria-modal="true" aria-label="确认删除对话"><span>DELETE CONVERSATION</span><h3>{deleteTarget.title}</h3><p></p><div><button type="button" onClick={() => setDeleteTarget(null)}></button><button className="danger" type="button" onClick={() => void deleteConversation()}></button></div></section></div>}
</>}
</aside>
}

View file

@ -0,0 +1,850 @@
.knowledge-agent {
position: relative;
width: min(980px, 72vw);
min-width: 680px;
height: 100%;
display: flex;
flex-direction: column;
background: #0b0e14;
border-left: 1px solid #272c36;
color: #e8ebf2;
box-shadow: -20px 0 50px #0005;
overflow: hidden;
}
.agent-channel-layout {
min-height: 0;
flex: 1;
display: grid;
grid-template-columns: 220px minmax(0, 1fr);
}
.agent-conversations {
min-width: 0;
display: flex;
flex-direction: column;
border-right: 1px solid #242933;
background: #080b11;
}
.agent-conversations > header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 14px 13px 12px;
border-bottom: 1px solid #202530;
}
.agent-conversations > header div {
display: flex;
flex-direction: column;
gap: 3px;
}
.agent-conversations > header span {
color: #747d8f;
font-size: 8px;
letter-spacing: .16em;
}
.agent-conversations > header b {
font-size: 12px;
}
.agent-conversations > header button {
border: 1px solid #3a3f49;
border-radius: 7px;
background: #dfcb94;
color: #17130c;
padding: 7px 9px;
font-weight: 700;
cursor: pointer;
}
.agent-conversation-list {
flex: 1;
overflow: auto;
padding: 9px;
}
.agent-conversation-list article {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) 25px;
margin-bottom: 7px;
border: 1px solid transparent;
border-radius: 9px;
background: #0d1119;
overflow: hidden;
}
.agent-conversation-list article.active {
border-color: #71664d;
background: #171a20;
}
.conversation-open,
.conversation-delete {
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
}
.conversation-open {
min-width: 0;
padding: 11px 4px 11px 11px;
text-align: left;
}
.conversation-open b,
.conversation-open span,
.conversation-open small {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.conversation-open b { font-size: 11px; }
.conversation-open span { margin-top: 4px; color: #8b94a3; font-size: 9px; }
.conversation-open small { margin-top: 5px; color: #666f7e; font-size: 8px; }
.conversation-delete {
align-self: start;
padding: 9px 8px;
color: #788191;
font-size: 16px;
}
.conversation-delete:hover { color: #e0999f; }
.agent-conversations > footer {
padding: 11px 13px;
border-top: 1px solid #202530;
color: #687182;
font-size: 8px;
line-height: 1.55;
}
.agent-dialogue {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
}
.agent-header {
display: flex;
justify-content: space-between;
gap: 20px;
padding: 22px 24px 17px;
border-bottom: 1px solid #242933;
}
.agent-header span,
.tool-forge-panel > header span {
font-size: 10px;
letter-spacing: 0.18em;
color: #8d95a6;
}
.agent-header h2 {
margin: 5px 0 2px;
font-size: 19px;
}
.agent-header p {
margin: 0;
color: #9299a8;
font-size: 12px;
}
.agent-header button {
border: 0;
background: transparent;
color: #aeb4c0;
font-size: 26px;
cursor: pointer;
}
.agent-tabs {
display: flex;
padding: 0 22px;
border-bottom: 1px solid #202530;
}
.agent-tabs button {
padding: 12px 3px;
margin-right: 22px;
border: 0;
border-bottom: 2px solid transparent;
background: none;
color: #8f97a6;
cursor: pointer;
}
.agent-tabs button.active {
color: #f3f5f9;
border-color: #d6b971;
}
.agent-runtime-bar {
display: grid;
grid-template-columns: 1fr 22px 1fr auto;
align-items: center;
gap: 10px;
padding: 14px 22px;
background: #10141c;
border-bottom: 1px solid #242933;
}
.agent-runtime-bar div {
display: flex;
flex-direction: column;
gap: 3px;
}
.agent-runtime-bar b {
font-size: 12px;
}
.agent-runtime-bar span {
font-size: 10px;
color: #858e9e;
}
.agent-runtime-bar i {
height: 1px;
background: #4b5361;
}
.agent-runtime-bar button,
.agent-provider-form button,
.tool-forge-panel button {
border: 1px solid #343b48;
background: #171c25;
color: #d9dde5;
border-radius: 7px;
padding: 8px 11px;
cursor: pointer;
}
.agent-provider-form {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
padding: 14px 22px;
border-bottom: 1px solid #282e38;
background: #111620;
}
.agent-provider-form label,
.forge-source label {
display: flex;
flex-direction: column;
gap: 5px;
}
.agent-provider-form label span,
.forge-source label span {
font-size: 10px;
color: #9aa2b0;
}
.agent-provider-form input,
.forge-source input,
.forge-source textarea {
border: 1px solid #303744;
background: #090c12;
color: #edf0f5;
border-radius: 6px;
padding: 9px 10px;
font: inherit;
}
.agent-provider-form button {
align-self: end;
}
.agent-ledger {
flex: 1;
min-height: 0;
overflow: auto;
padding: 22px;
}
.agent-delete-backdrop {
position: absolute;
inset: 0;
z-index: 30;
display: grid;
place-items: center;
background: #030509c9;
backdrop-filter: blur(3px);
}
.agent-delete-backdrop section {
width: min(390px, calc(100% - 40px));
box-sizing: border-box;
padding: 23px;
border: 1px solid #3c424d;
border-radius: 13px;
background: #121720;
box-shadow: 0 24px 70px #000a;
}
.agent-delete-backdrop span { color: #c58288; font-size: 9px; letter-spacing: .16em; }
.agent-delete-backdrop h3 { margin: 9px 0; font-size: 17px; }
.agent-delete-backdrop p { margin: 0; color: #9ba4b3; font-size: 11px; line-height: 1.65; }
.agent-delete-backdrop section > div { display: flex; justify-content: flex-end; gap: 9px; margin-top: 20px; }
.agent-delete-backdrop button { border: 1px solid #39404b; border-radius: 7px; background: #1b2029; color: #d9dde5; padding: 8px 13px; cursor: pointer; }
.agent-delete-backdrop button.danger { border-color: #8b484f; background: #6f3339; color: #fff; }
.agent-empty {
margin: auto;
max-width: 370px;
padding: 44px 24px;
text-align: center;
color: #8e97a6;
}
.agent-empty b {
color: #e7eaf0;
}
.agent-message {
margin-bottom: 20px;
padding: 15px 17px;
border: 1px solid #242b37;
border-radius: 12px;
background: #10151e;
}
.agent-message.is-human {
margin-left: 13%;
background: #171b23;
border-color: #343944;
}
.agent-message header {
display: flex;
justify-content: space-between;
gap: 15px;
}
.agent-message header div {
display: flex;
flex-direction: column;
gap: 2px;
}
.agent-message header b {
font-size: 12px;
}
.agent-message header span,
.agent-message time,
.agent-message footer {
font-size: 10px;
color: #828b9b;
}
.agent-message-content {
line-height: 1.7;
margin: 12px 0;
color: #e0e4eb;
}
.agent-message-content > :first-child {
margin-top: 0;
}
.agent-message-content > :last-child {
margin-bottom: 0;
}
.agent-message-content p,
.agent-message-content ul,
.agent-message-content ol,
.agent-message-content blockquote {
margin: 8px 0;
}
.agent-message-content ul,
.agent-message-content ol {
padding-left: 20px;
}
.agent-message-content h1,
.agent-message-content h2,
.agent-message-content h3 {
margin: 16px 0 7px;
color: #f0f2f6;
line-height: 1.35;
}
.agent-message-content h1 {
font-size: 16px;
}
.agent-message-content h2 {
font-size: 14px;
}
.agent-message-content h3 {
font-size: 12px;
}
.agent-message-content code {
border-radius: 4px;
background: #080b10;
padding: 2px 5px;
color: #d8bf7f;
font-size: 10px;
}
.agent-message-content pre {
overflow: auto;
border: 1px solid #29303b;
border-radius: 7px;
background: #080b10;
padding: 10px;
}
.agent-message-content pre code {
padding: 0;
}
.agent-message-content strong {
color: #f1d89b;
font-weight: 650;
}
.agent-message-content a {
color: #9cbcea;
}
.agent-message-content table {
width: 100%;
margin: 10px 0;
border-collapse: collapse;
font-size: 10px;
}
.agent-message-content th,
.agent-message-content td {
border: 1px solid #2d3541;
padding: 6px 8px;
text-align: left;
vertical-align: top;
}
.agent-message-content th {
background: #171d27;
color: #d9c083;
}
.agent-message-content blockquote {
border-left: 2px solid #aa9157;
padding-left: 10px;
color: #aeb6c4;
}
.agent-message footer {
display: flex;
justify-content: space-between;
border-top: 1px solid #252b35;
padding-top: 9px;
}
.agent-evidence {
display: flex;
flex-direction: column;
gap: 5px;
margin: 10px 0;
}
.agent-evidence span {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 7px;
padding: 7px 9px;
border-radius: 6px;
background: #0a0f17;
color: #9da6b5;
font-size: 10px;
}
.agent-evidence b {
color: #d5b974;
}
.agent-progress {
margin: 0 0 20px;
padding: 14px 16px;
border: 1px solid #323847;
border-radius: 10px;
background: #0d121a;
}
.agent-progress header {
display: flex;
align-items: center;
gap: 9px;
padding-bottom: 10px;
}
.agent-progress header small {
margin-left: auto;
color: #798395;
}
.agent-progress > div {
display: flex;
align-items: center;
gap: 10px;
padding: 5px 0;
color: #abb3c0;
}
.agent-progress > div i {
width: 7px;
height: 7px;
border-radius: 50%;
background: #c7a960;
}
.agent-progress > div.complete i {
background: #72b98c;
}
.agent-progress > div.failed i {
background: #d36f72;
}
.agent-progress > div span {
display: flex;
flex-direction: column;
}
.agent-progress > div b {
font-size: 11px;
}
.agent-progress > div small {
font-size: 9px;
color: #7f899a;
}
.pulse {
width: 9px;
height: 9px;
border-radius: 50%;
background: #d2b56f;
box-shadow: 0 0 0 5px #d2b56f1a;
animation: agent-pulse 1.4s ease-in-out infinite;
}
.agent-composer {
padding: 14px 18px 18px;
border-top: 1px solid #262c36;
background: #0c1017;
}
.agent-composer textarea {
width: 100%;
min-height: 78px;
max-height: 190px;
resize: vertical;
box-sizing: border-box;
border: 1px solid #343b48;
border-radius: 10px;
background: #070a10;
color: #eef1f6;
padding: 13px;
font: inherit;
line-height: 1.5;
}
.agent-composer > div {
display: flex;
align-items: center;
gap: 10px;
margin-top: 9px;
}
.agent-composer span {
font-size: 10px;
color: #7f8898;
}
.agent-composer select {
margin-left: auto;
max-width: 165px;
background: #121720;
color: #dfe3e9;
border: 1px solid #303744;
border-radius: 6px;
padding: 7px;
}
.agent-composer button {
border: 0;
border-radius: 8px;
background: #e5d19a;
color: #17130b;
font-weight: 700;
padding: 9px 18px;
}
.agent-error,
.forge-message {
margin: 0;
padding: 10px 18px;
background: #3a181c;
color: #f0b9be;
font-size: 11px;
}
.tool-forge-panel {
overflow: auto;
padding: 20px 22px;
}
.tool-forge-panel > header {
display: flex;
justify-content: space-between;
gap: 20px;
margin-bottom: 18px;
}
.tool-forge-panel > header h2 {
margin: 5px 0;
font-size: 18px;
}
.tool-forge-panel > header p {
margin: 0;
max-width: 480px;
color: #8f98a8;
font-size: 11px;
line-height: 1.6;
}
.tool-forge-panel > header > b {
font-size: 10px;
color: #d4b96f;
}
.forge-editor-grid {
display: grid;
grid-template-columns: 1.18fr 0.82fr;
gap: 14px;
}
.forge-source {
display: flex;
flex-direction: column;
gap: 10px;
}
.forge-source textarea {
min-height: 370px;
resize: vertical;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 10px;
line-height: 1.55;
}
.forge-registry {
min-width: 0;
}
.forge-registry h3 {
margin-top: 0;
font-size: 13px;
}
.forge-registry > p {
color: #828c9d;
font-size: 11px;
line-height: 1.6;
}
.forge-registry article {
padding: 11px;
margin-bottom: 8px;
border: 1px solid #2a313d;
border-radius: 8px;
background: #10151d;
cursor: pointer;
}
.forge-registry article.active {
border-color: #b79d5f;
}
.forge-registry article > div {
display: flex;
justify-content: space-between;
gap: 8px;
}
.forge-registry article b {
font-size: 11px;
}
.forge-registry article span {
font-size: 9px;
color: #b8a36d;
}
.forge-registry article code,
.forge-registry article small {
display: block;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 6px;
color: #7e8898;
font-size: 9px;
}
.forge-registry article button {
margin-top: 9px;
width: 100%;
}
.forge-ticket {
display: grid;
gap: 9px;
margin-top: 14px;
padding: 13px;
border: 1px solid #303743;
border-radius: 8px;
}
.forge-ticket > div {
display: flex;
justify-content: space-between;
}
.forge-ticket > div span {
font-size: 10px;
color: #8d96a5;
}
.forge-ticket code {
overflow: hidden;
text-overflow: ellipsis;
color: #d4b96f;
font-size: 10px;
}
.forge-ticket p {
margin: 0;
color: #8d96a5;
font-size: 10px;
line-height: 1.55;
}
.forge-message {
margin-top: 14px;
border-radius: 7px;
background: #182032;
color: #cbd4e4;
}
.local-worker-panel {
overflow: auto;
padding: 20px 22px;
}
.local-worker-panel > header {
display: flex;
justify-content: space-between;
gap: 20px;
margin-bottom: 16px;
}
.local-worker-panel > header h2 {
margin: 5px 0;
font-size: 18px;
}
.local-worker-panel > header p {
margin: 0;
max-width: 620px;
color: #8f98a8;
font-size: 11px;
line-height: 1.6;
}
.local-worker-panel > header > b {
max-width: 42%;
overflow: hidden;
text-overflow: ellipsis;
color: #d4b96f;
font: 10px ui-monospace, SFMono-Regular, Menlo, monospace;
}
.local-worker-status {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 14px;
}
.local-worker-status article {
display: flex;
flex-direction: column;
gap: 5px;
padding: 12px;
border: 1px solid #2a313d;
border-radius: 8px;
background: #0e131b;
}
.local-worker-status span,
.local-worker-status small {
color: #7f8999;
font-size: 9px;
}
.local-worker-status b { font-size: 12px; }
.local-worker-waiting {
padding: 18px;
border: 1px solid #3a3440;
border-radius: 10px;
background: #15141b;
}
.local-worker-waiting b { color: #dfc782; }
.local-worker-waiting p {
margin: 8px 0 0;
color: #9099a8;
font-size: 11px;
line-height: 1.65;
}
.local-worker-skills {
margin-bottom: 14px;
padding: 13px;
border: 1px solid #34382f;
border-radius: 9px;
background: #11150f;
}
.local-worker-skills > header,
.local-worker-skills article > div { display: flex; justify-content: space-between; gap: 10px; }
.local-worker-skills > header span { display: block; color: #817f70; font-size: 8px; letter-spacing: .12em; }
.local-worker-skills > header b { font-size: 12px; }
.local-worker-skills > header strong { color: #dfc782; font-size: 10px; }
.local-worker-skills article { margin-top: 10px; padding-top: 10px; border-top: 1px solid #2b3027; }
.local-worker-skills article b { font-size: 10px; }
.local-worker-skills article span { color: #c4ad69; font-size: 8px; }
.local-worker-skills code,
.local-worker-skills article p,
.local-worker-skills article small,
.local-worker-skills > p,
.local-worker-skills footer { display: block; margin: 5px 0 0; color: #828b7c; font-size: 8px; line-height: 1.5; }
.local-worker-skills footer { margin-top: 11px; padding-top: 9px; border-top: 1px solid #2b3027; }
.local-worker-brain {
display: grid;
grid-template-columns: minmax(190px, 1.2fr) 1fr 0.8fr auto auto;
align-items: end;
gap: 10px;
padding: 13px;
border: 1px solid #2d3440;
border-radius: 9px;
background: #10151d;
}
.local-worker-brain > div span { color: #d8c17f; font-size: 10px; font-weight: 700; }
.local-worker-brain > div p { margin: 4px 0 0; color: #808a99; font-size: 9px; line-height: 1.5; }
.local-worker-brain label,
.worker-objective label {
display: flex;
flex-direction: column;
gap: 5px;
color: #8d96a5;
font-size: 9px;
}
.local-worker-brain input,
.worker-objective textarea,
.worker-objective select {
box-sizing: border-box;
width: 100%;
border: 1px solid #343b48;
border-radius: 7px;
background: #090d13;
color: #e6e9ef;
padding: 8px;
font: inherit;
}
.worker-switch {
flex-direction: row !important;
align-items: center;
white-space: nowrap;
}
.worker-switch input { width: auto; }
.local-worker-brain button,
.worker-objective button {
border: 0;
border-radius: 7px;
background: #e2cd91;
color: #17130b;
font-weight: 700;
padding: 9px 13px;
}
.local-worker-runner {
display: grid;
grid-template-columns: minmax(0, 1.15fr) minmax(250px, 0.85fr);
gap: 12px;
margin-top: 12px;
}
.worker-objective,
.worker-tools {
padding: 13px;
border: 1px solid #2a313d;
border-radius: 9px;
background: #0d1219;
}
.worker-objective textarea { min-height: 150px; resize: vertical; }
.worker-objective > div { display: grid; gap: 9px; margin-top: 10px; }
.worker-tools h3 { margin: 0 0 10px; font-size: 12px; }
.worker-tools article {
padding: 9px 0;
border-top: 1px solid #242a34;
}
.worker-tools article > div { display: flex; justify-content: space-between; gap: 8px; }
.worker-tools article b { font-size: 10px; }
.worker-tools article span { color: #bfa967; font-size: 9px; }
.worker-tools code,
.worker-tools small { display: block; margin-top: 5px; overflow: hidden; text-overflow: ellipsis; color: #778292; font-size: 8px; }
.local-worker-receipt {
margin-top: 12px;
padding: 13px;
border: 1px solid #3a414d;
border-radius: 9px;
background: #0c1218;
}
.local-worker-receipt.success { border-color: #315943; }
.local-worker-receipt.failed { border-color: #6d353a; }
.local-worker-receipt > header { display: flex; justify-content: space-between; }
.local-worker-receipt > header span { display: block; color: #7f8998; font-size: 9px; }
.local-worker-receipt > header b { font-size: 12px; }
.local-worker-receipt > header strong { color: #87c59c; font-size: 10px; }
.receipt-cost { display: flex; flex-wrap: wrap; gap: 12px; margin: 10px 0; color: #8e98a8; font-size: 9px; }
.receipt-cost b { color: #e0ca8e; }
.local-worker-receipt pre { max-height: 260px; overflow: auto; padding: 10px; border-radius: 7px; background: #070a0f; color: #cfd7e4; font-size: 9px; }
.local-worker-receipt footer { color: #707b8c; font-size: 8px; }
@keyframes agent-pulse {
50% {
opacity: 0.45;
transform: scale(0.85);
}
}
@media (max-width: 900px) {
.knowledge-agent {
position: absolute;
inset: 0 0 0 auto;
width: 100%;
min-width: 0;
z-index: 20;
}
.agent-channel-layout {
grid-template-columns: 168px minmax(0, 1fr);
}
.forge-editor-grid {
grid-template-columns: 1fr;
}
.local-worker-brain,
.local-worker-runner {
grid-template-columns: 1fr;
}
.local-worker-status { grid-template-columns: 1fr; }
.agent-provider-form {
grid-template-columns: 1fr;
}
}
@media (max-width: 620px) {
.agent-channel-layout { grid-template-columns: 1fr; }
.agent-conversations { max-height: 190px; border-right: 0; border-bottom: 1px solid #242933; }
.agent-conversations > footer { display: none; }
}

View file

@ -1256,6 +1256,150 @@ const ROUTES = {
"moduleNumber": "HLP-NIPC-MOD-0034",
"operationNumber": "HLP-NIPC-OP-0157",
"targetNumber": "HLP-NIPC-TGT-0034"
},
"get_persona_agent_runtime": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0158",
"targetNumber": "HLP-NIPC-TGT-0035"
},
"upsert_persona_agent_provider": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0159",
"targetNumber": "HLP-NIPC-TGT-0035"
},
"get_persona_agent_conversation": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0160",
"targetNumber": "HLP-NIPC-TGT-0035"
},
"send_persona_agent_message": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0161",
"targetNumber": "HLP-NIPC-TGT-0035"
},
"list_persona_agent_conversations": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0166",
"targetNumber": "HLP-NIPC-TGT-0035"
},
"get_persona_agent_conversation_by_id": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0167",
"targetNumber": "HLP-NIPC-TGT-0035"
},
"create_persona_agent_conversation": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0168",
"targetNumber": "HLP-NIPC-TGT-0035"
},
"delete_persona_agent_conversation": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0169",
"targetNumber": "HLP-NIPC-TGT-0035"
},
"start_local_channel_session": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0006",
"moduleNumber": "HLP-NIPC-MOD-0015",
"operationNumber": "HLP-NIPC-OP-0170",
"targetNumber": "HLP-NIPC-TGT-0015"
},
"get_hldp_tool_forge": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0036",
"operationNumber": "HLP-NIPC-OP-0162",
"targetNumber": "HLP-NIPC-TGT-0036"
},
"compile_hldp_tool": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0036",
"operationNumber": "HLP-NIPC-OP-0163",
"targetNumber": "HLP-NIPC-TGT-0036"
},
"request_hldp_tool_test_authorization": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0036",
"operationNumber": "HLP-NIPC-OP-0164",
"targetNumber": "HLP-NIPC-TGT-0036"
},
"test_hldp_tool": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0036",
"operationNumber": "HLP-NIPC-OP-0165",
"targetNumber": "HLP-NIPC-TGT-0036"
},
"get_local_execution_subpersona": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0037",
"operationNumber": "HLP-NIPC-OP-0171",
"targetNumber": "HLP-NIPC-TGT-0037"
},
"configure_local_execution_subpersona": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0037",
"operationNumber": "HLP-NIPC-OP-0172",
"targetNumber": "HLP-NIPC-TGT-0037"
},
"run_local_execution_subpersona_job": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0037",
"operationNumber": "HLP-NIPC-OP-0173",
"targetNumber": "HLP-NIPC-TGT-0037"
},
"run_hldp_tool": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0036",
"operationNumber": "HLP-NIPC-OP-0174",
"targetNumber": "HLP-NIPC-TGT-0036"
},
"compile_channel_receipt": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0175",
"targetNumber": "HLP-NIPC-TGT-0035"
}
} as const

View file

@ -168,7 +168,7 @@
@keyframes sl-abyss-open { 0%{opacity:1;transform:translate(-50%,-50%) scale(1)} 54%{opacity:.9;transform:translate(-50%,-50%) scale(1.45);filter:brightness(1.4)} 100%{opacity:0;transform:translate(-50%,-50%) scale(3.2);filter:blur(16px) brightness(1.9)} }
@keyframes sl-abyss-copy-away { to{opacity:0;transform:translateY(8px)} }
.starlake-scene .world-channel-return { position:absolute; z-index:9; left:50%; top:44%; transform:translate(-50%,-50%); display:grid; justify-items:center; gap:4px;
padding:12px 18px; border:0; color:inherit; background:radial-gradient(ellipse,rgba(247,235,200,.08),transparent 72%); cursor:pointer; font-family:inherit; }
padding:12px 18px; border:0; color:inherit; background:radial-gradient(ellipse,rgba(247,235,200,.08),transparent 72%); pointer-events:auto; cursor:pointer; font-family:inherit; }
.starlake-scene .world-channel-return b { color:#f7ebc8; font-size:13px; font-weight:760; letter-spacing:.18em; }
.starlake-scene .world-channel-return small { color:rgba(196,206,228,.58); font-size:9px; font-weight:650; letter-spacing:.1em; }
.starlake-scene .era-foot { position:absolute; left:50%; bottom:21px; transform:translateX(-50%); color:rgba(180,192,220,.34);

View file

@ -129,7 +129,8 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.system-proof dd, .evidence-list dd, .document-inspector dd { margin: 0; color: var(--content-secondary); font-weight: 580; text-align: right; }
.full-workbench { height: 100%; min-width: 0; min-height: 0; display: grid; background: color-mix(in srgb, var(--surface-depth) 70%, transparent); }
.knowledge-page { grid-template-columns: 336px minmax(0, 1fr) 288px; }
.knowledge-page { position: relative; grid-template-columns: 336px minmax(0, 1fr) 288px; }
.knowledge-page > .knowledge-agent { position: absolute; inset: 0 0 0 auto; z-index: 15; }
.knowledge-page.inspector-closed { grid-template-columns: 336px minmax(0, 1fr); }
.knowledge-browser, .document-inspector, .code-channels, .repository-tree {
min-width: 0; min-height: 0; display: flex; flex-direction: column;
@ -139,6 +140,8 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.knowledge-browser > header, .code-channels > header {
display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 24px 18px 14px;
}
.knowledge-head-actions { display: flex; align-items: center; gap: 7px; }
.knowledge-agent-entry { border: 1px solid var(--panel-edge); border-radius: 8px; color: var(--content-secondary); background: var(--primitive-glass); padding: 8px 9px; font: inherit; font-size: 10px; white-space: nowrap; cursor: pointer; }
.knowledge-browser h1, .code-channels h1 { margin: 5px 0 0; color: var(--content-primary); font-size: 23px; font-weight: 650; letter-spacing: -.02em; }
.icon-button { width: 36px; height: 36px; display: grid; place-items: center; flex: 0 0 auto; border: 1px solid var(--panel-edge); border-radius: 9px; color: var(--content-muted); background: var(--primitive-glass); cursor: pointer; }
.icon-button:disabled { opacity: .3; cursor: default; }
@ -148,6 +151,17 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.search-box button { border: 0; color: var(--content-muted); background: transparent; font-size: 12px; cursor: pointer; }
.knowledge-counts { display: flex; justify-content: space-between; gap: 8px; padding: 13px 16px 10px; color: var(--content-muted); font-size: 11.5px; font-weight: 540; }
.knowledge-tree { min-height: 0; overflow: auto; padding: 2px 8px 18px; }
.knowledge-candidates { display: grid; gap: 8px; padding: 5px; }
.knowledge-candidates > button { display: grid; gap: 7px; width: 100%; padding: 12px; border: 1px solid var(--panel-edge); border-radius: 10px; color: var(--content-muted); background: var(--primitive-glass); text-align: left; cursor: pointer; }
.knowledge-candidates > button:hover { border-color: color-mix(in srgb, var(--accent-light) 42%, var(--panel-edge)); background: var(--primitive-glass-hover); }
.knowledge-candidates span { display: flex; align-items: center; justify-content: space-between; gap: 9px; }
.knowledge-candidates b { min-width: 0; overflow: hidden; color: var(--content-primary); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
.knowledge-candidates em { flex: none; padding: 3px 6px; border-radius: 999px; font-size: 9px; font-style: normal; }
.knowledge-candidates em.ready { color: var(--state-ready); background: color-mix(in srgb, var(--state-ready) 13%, transparent); }
.knowledge-candidates em.pending { color: var(--warn); background: color-mix(in srgb, var(--warn) 13%, transparent); }
.knowledge-candidates code { overflow: hidden; color: var(--accent-light); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
.knowledge-candidates small { display: -webkit-box; overflow: hidden; color: var(--content-secondary); font-size: 11px; line-height: 1.45; -webkit-box-orient: vertical; -webkit-line-clamp: 3; }
.knowledge-candidates i { overflow: hidden; color: var(--content-muted); font-size: 9px; font-style: normal; text-overflow: ellipsis; white-space: nowrap; }
.tree-folder, .tree-document {
width: 100%; min-height: 38px; display: grid; align-items: center; gap: 7px;
border: 0; border-radius: 8px; color: var(--content-muted); background: transparent; text-align: left; cursor: pointer;
@ -263,6 +277,9 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.inspector-scroll dl div { display: block; }
.inspector-scroll dd { margin-top: 4px; text-align: left; overflow-wrap: anywhere; }
.inspector-scroll code { display: block; overflow-wrap: anywhere; color: var(--content-muted); font-size: 11px; }
.thought-summary { display: grid; gap: 5px; margin-top: 14px; padding: 12px; border: 1px solid var(--panel-edge); border-radius: 9px; background: var(--primitive-glass); }
.thought-summary b { color: var(--accent-light); font-size: 10px; }
.thought-summary p { margin: 0 0 5px; color: var(--content-secondary); }
.outline-list { display: grid; gap: 10px; }
.outline-list span { overflow: hidden; color: var(--content-muted); font-size: 12.5px; text-overflow: ellipsis; white-space: nowrap; }
.outline-list button { display: block; width: 100%; overflow: hidden; padding: 5px 8px; border: 0; border-radius: 7px; color: var(--content-muted); background: transparent; font-size: 12.5px; text-align: left; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
@ -604,6 +621,7 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.gate-hint { margin-top: 14px; color: var(--content-muted); font-size: 13px; }
.gate-back { margin-top: 14px; border: 0; background: none; color: var(--content-faint); font-size: 12.5px; letter-spacing: .1em; cursor: pointer; transition: color .25s ease; }
.gate-back:hover { color: var(--accent-light); }
.local-channel-boundary { display: block; max-width: 330px; margin: -8px auto 0; color: var(--content-faint); font-size: 10px; line-height: 1.55; text-align: center; }
/* 过关:玉自湖面升起 → 欢迎语浮现 */
.gate-rise { position: relative; z-index: 3; display: grid; place-items: center; text-align: center; }
@ -827,6 +845,11 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.personal-node-guide footer { display: flex; justify-content: space-between; gap: 22px; padding-top: 16px; border-top: 1px solid var(--panel-edge); font-size: 12.5px; }
.personal-node-guide footer b { color: var(--accent-light); font-weight: 750; white-space: nowrap; }
.personal-node-guide footer span { color: var(--content-muted); text-align: right; }
.team-channel-initializer { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 9px 14px; padding-top: 16px; border-top: 1px solid var(--panel-edge); }
.team-channel-initializer label { grid-column: 1 / -1; color: var(--accent-light); font-size: 13px; font-weight: 750; }
.team-channel-initializer p { grid-column: 1 / -1; margin: 0; color: var(--content-muted); font-size: 11px; line-height: 1.6; }
.team-channel-initializer input { min-width: 0; border: 1px solid var(--panel-edge); border-radius: 9px; background: var(--primitive-glass); color: var(--content-primary); padding: 10px 12px; }
.team-channel-initializer small { grid-column: 1 / -1; color: var(--accent-light); }
.bottle-heart { position: absolute; z-index: 12; left: 50%; top: 48%; display: grid; justify-items: center; gap: 9px; width: 300px; padding: 0; transform: translate(-50%, -50%); border: 0; background: transparent; color: inherit; }
.bottle-heart i { width: 90px; height: 90px; border-radius: 46% 46% 52% 52%; background: radial-gradient(circle at 42% 32%, #fffdf5, color-mix(in srgb, var(--primitive-warm-glow) 74%, #f2bdc8) 37%, color-mix(in srgb, var(--primitive-warm-glow) 22%, transparent) 68%, transparent 74%); filter: drop-shadow(0 0 34px color-mix(in srgb, var(--primitive-warm-glow) 62%, transparent)); }
.bottle-heart b { color: var(--content-primary); font-size: 22px; font-weight: 750; letter-spacing: .08em; }