feat: add tiered channel agent and mobile Codex bridge

This commit is contained in:
冰朔 2026-08-21 08:21:03 +08:00
commit 71db11e7e8
35 changed files with 2242 additions and 76 deletions

View file

@ -181,7 +181,7 @@ export function KnowledgeAgent({ activeKnowledgePath, onClose }: { activeKnowled
<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')}> Agent</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>
<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>{runtime?.boundPersonaNumber ? '频道按任务选择小模型或旗舰模型' : '频道认知与架构对话使用旗舰模型'}</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="频道历史对话">
@ -191,9 +191,9 @@ export function KnowledgeAgent({ activeKnowledgePath, onClose }: { activeKnowled
</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.length && <div className="agent-empty"><b></b><p>使</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>}
{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>}

View file

@ -56,6 +56,15 @@ interface MobileSyncSnapshot {
sensitiveValuesIncluded: boolean
}
recentCaptures: MobileCapture[]
recentAgentExchanges: Array<{
requestId: string
conversationId: string
reply: string
responderName: string
personaBindingState: string
toolReceipts: Array<{ toolNumber: string; summary: string }>
}>
codexTarget?: CodexThread
boundary: {
mobileRole: string
remoteDesktopClone: boolean
@ -65,6 +74,21 @@ interface MobileSyncSnapshot {
}
}
interface CodexThread {
threadId: string
name: string
preview: string
cwd: string
updatedAtUnixSeconds: number
status: string
}
interface CodexThreadListSnapshot {
state: string
cliVersion: string
threads: CodexThread[]
}
function formatTime(value?: number) {
return value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '尚未连接'
}
@ -74,6 +98,7 @@ export function MobileSyncPanel({ onBack }: { onBack: () => void }) {
const [snapshot, setSnapshot] = useState<MobileSyncSnapshot | null>(null)
const [message, setMessage] = useState('手机是当前个人节点的远程入口,不是第二套人格系统。')
const [pending, setPending] = useState(false)
const [codexThreads, setCodexThreads] = useState<CodexThread[]>([])
const refresh = useCallback(async () => {
const next = await invoke<MobileSyncStatus>('get_mobile_sync_status')
@ -157,6 +182,31 @@ export function MobileSyncPanel({ onBack }: { onBack: () => void }) {
<header><span>MOBILE CAPTURES</span><h2></h2></header>
{snapshot?.recentCaptures.length ? snapshot.recentCaptures.map((capture) => <article key={capture.captureId}><div><b>{capture.title || '未命名记录'}</b><time>{formatTime(capture.createdAtUnixMs)}</time></div><p>{capture.body}</p></article>) : <p></p>}
</section>
<section className="mobile-sync-captures">
<header><span>CODEX APP SERVER</span><h2></h2></header>
{snapshot?.codexTarget ? <article>
<div><b>{snapshot.codexTarget.name}</b><span></span></div>
<p>{snapshot.codexTarget.cwd}</p>
</article> : <p> Codex </p>}
<button type="button" disabled={pending} onClick={() => void run(async () => {
const result = await invoke<CodexThreadListSnapshot>('list_mobile_codex_threads')
setCodexThreads(result.threads)
}, '已从本机 Codex App Server 读取任务列表。')}> Codex </button>
{codexThreads.map((thread) => <article key={thread.threadId}>
<div><b>{thread.name}</b><button type="button" disabled={pending} onClick={() => void run(() => invoke('select_mobile_codex_thread', { input: { threadId: thread.threadId } }), '已绑定手机远程开发任务。')}></button></div>
<p>{thread.cwd}</p>
</article>)}
</section>
<section className="mobile-sync-captures">
<header><span>REMOTE CONVERSATION</span><h2></h2></header>
{snapshot?.recentAgentExchanges.length ? snapshot.recentAgentExchanges.map((exchange) => <article key={exchange.requestId}>
<div><b>{exchange.responderName}</b><span>{exchange.personaBindingState === 'BOUND_VERIFY_PASS' ? '人格已绑定' : '系统频道回复'}</span></div>
<p>{exchange.reply}</p>
{exchange.toolReceipts.map((receipt) => <small key={`${exchange.requestId}-${receipt.toolNumber}`}> {receipt.summary}</small>)}
</article>) : <p> HoloLake </p>}
</section>
</div>}
<footer className="mobile-sync-footer"><span>{message}</span><div><button type="button" disabled={pending} onClick={() => void run(refresh, '状态已从本机根节点重新读取。')}></button>{status?.running && <button type="button" disabled={pending} onClick={() => void run(() => invoke('stop_mobile_sync'), '同步入口已关闭;已配对设备登记仍保留,可单独撤销。')}></button>}</div></footer>

View file

@ -1400,6 +1400,22 @@ const ROUTES = {
"moduleNumber": "HLP-NIPC-MOD-0035",
"operationNumber": "HLP-NIPC-OP-0175",
"targetNumber": "HLP-NIPC-TGT-0035"
},
"list_mobile_codex_threads": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0030",
"operationNumber": "HLP-NIPC-OP-0176",
"targetNumber": "HLP-NIPC-TGT-0030"
},
"select_mobile_codex_thread": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0030",
"operationNumber": "HLP-NIPC-OP-0177",
"targetNumber": "HLP-NIPC-TGT-0030"
}
} as const