diff --git a/src/app/background/index.ts b/src/app/background/index.ts index 995eaa5..6cb75c9 100644 --- a/src/app/background/index.ts +++ b/src/app/background/index.ts @@ -409,7 +409,10 @@ async function handleRequest(request: ExtensionRequest, sender: Browser.runtime. } case 'agent.actions.clear': return ok(await clearAgentActions()); case 'policy.status': return ok(await getEnterprisePolicy()); - case 'diagnostics.export': return ok(await createDiagnosticsBundle(engineBridge.getStatus())); + case 'diagnostics.export': return ok(await createDiagnosticsBundle( + engineBridge.getStatus(), + engineBridge.getPairingStatus(), + )); case 'metrics.get': return ok(await getRuntimeMetrics()); case 'metrics.reset': return ok(await resetRuntimeMetrics()); case 'bridge.config.save': { @@ -451,9 +454,25 @@ async function handleRequest(request: ExtensionRequest, sender: Browser.runtime. return ok(engineBridge.getStatus()); } case 'bridge.pair': { - const status = await engineBridge.startPairing(); - void appendAuditEvent({ category: 'bridge', action: 'bridge.pair', outcome: 'success' }); - return ok(status); + const startedAt = Date.now(); + try { + const status = await engineBridge.startPairing(); + void appendAuditEvent({ + category: 'bridge', action: 'bridge.pair', + outcome: status.state === 'rejected' + ? 'denied' + : status.state === 'pending' || status.state === 'approved' ? 'success' : 'error', + durationMs: Date.now() - startedAt, summary: `${status.state}: ${status.message}`, + }); + return ok(status); + } catch (error) { + void appendAuditEvent({ + category: 'bridge', action: 'bridge.pair', outcome: 'error', + durationMs: Date.now() - startedAt, errorCode: errorCode(error), + summary: (error instanceof Error ? error.message : String(error)).slice(0, 512), + }); + throw error; + } } case 'bridge.pair.cancel': return ok(engineBridge.cancelPairing()); case 'bridge.pair.status': return ok(engineBridge.getPairingStatus()); diff --git a/src/entrypoints/options/App.css b/src/entrypoints/options/App.css index 5354422..5afd668 100644 --- a/src/entrypoints/options/App.css +++ b/src/entrypoints/options/App.css @@ -716,6 +716,10 @@ input[type='checkbox'] { width: 15px; height: 15px; flex: 0 0 auto; padding: 0; .pairing-code span { color: var(--muted); font-size: var(--text-xs); font-weight: 650; letter-spacing: .05em; text-transform: uppercase; } .pairing-code strong { font-family: var(--font-mono); font-size: 30px; font-weight: 700; letter-spacing: .12em; } .pairing-code small { color: var(--muted); font-size: var(--text-sm); } +.pairing-diagnostics { padding: 10px 12px; display: grid; grid-template-columns: minmax(90px, .35fr) minmax(0, 1fr) auto; gap: 12px; align-items: center; border-radius: var(--radius-md); background: var(--surface-subtle); text-align: left; } +.pairing-diagnostics > div { min-width: 0; } +.pairing-diagnostics span { display: block; color: var(--muted); font-size: var(--text-xs); } +.pairing-diagnostics code { display: block; margin-top: 2px; overflow: hidden; font-size: var(--text-sm); white-space: nowrap; text-overflow: ellipsis; } .paired-engine-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .paired-engine-meta > div { min-width: 0; } .paired-engine-meta span { color: var(--muted); font-size: var(--text-xs); font-weight: 650; letter-spacing: .04em; text-transform: uppercase; } @@ -795,6 +799,8 @@ input[type='checkbox'] { width: 15px; height: 15px; flex: 0 0 auto; padding: 0; .ua-assignment-list code { display: none; } .task-command-bar, .agent-runtime-summary { flex-direction: column; display: flex; align-items: stretch; } .task-status-grid, .context-session-strip, .diff-summary, .context-inventory-grid, .grant-options, .panel-policy-grid, .form-grid, .paired-engine-meta { grid-template-columns: minmax(0, 1fr); } + .pairing-diagnostics { grid-template-columns: minmax(0, 1fr); } + .pairing-diagnostics .ui-button { justify-self: start; } .agent-action-row { grid-template-columns: 12px 76px minmax(0, 1fr) 76px; } .agent-action-row span:nth-child(4), .agent-action-row span:last-child { display: none; } .activity-table__head, .activity-table__row { grid-template-columns: 120px minmax(0, 1fr) 80px; } diff --git a/src/entrypoints/options/App.tsx b/src/entrypoints/options/App.tsx index ccdd3e3..4954f72 100644 --- a/src/entrypoints/options/App.tsx +++ b/src/entrypoints/options/App.tsx @@ -838,6 +838,28 @@ function EngineSettings({ state, setState, bridge, setBridge, tabs, run, busy }: setState(next); setPanelDraft(next.floatingPanel); }, '悬浮面板策略已保存'); + const pairingEndpoint = (() => { + try { + const value = new URL(state.bridge.endpoint); + value.pathname = '/pairing'; + value.search = ''; + value.hash = ''; + return value.toString(); + } catch { + return state.bridge.endpoint; + } + })(); + const copyPairingDiagnostics = () => run(async () => { + const { code, ...safePairing } = pairing; + await navigator.clipboard.writeText(JSON.stringify({ + extensionVersion: browser.runtime.getManifest().version, + pairing: { ...safePairing, hasVerificationCode: Boolean(code) }, + bridge, + transport: state.bridge.transport, + endpoint: pairingEndpoint, + managedInstance: state.bridge.managedInstance, + }, null, 2)); + }, '配对排查信息已复制'); return
扩展主动连接本机 Bridge,网页无法直接访问此通道。
{state.bridge.pairedEngine ? '设备身份已锁定到首次批准的 Yak 引擎。' : pairing.message}
{state.bridge.pairedEngine.engineIdentityId.slice(0, 24)}{state.bridge.pairedEngine.deviceId.slice(0, 24)}{pairing.state}{pairingEndpoint}