checkpoint: preserve zero-core agent handoff
This commit is contained in:
parent
f8c8db4d48
commit
e54c93c7ae
91 changed files with 13603 additions and 121 deletions
|
|
@ -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} · P0–P6 共 {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>P1–P6 原生器官</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>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>}
|
||||
|
|
|
|||
|
|
@ -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>经验学习逻辑脑默认已经运行,不依赖模型。只有本机条件允许时,才可接 Ollama、llama.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>
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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 URL;HoloLake 本地频道和知识库没有丢失。'],
|
||||
[/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>
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue