fix: complete canonical language channel acceptance
This commit is contained in:
parent
b70e092e28
commit
e72ee85b89
7 changed files with 105 additions and 17 deletions
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"schema": "hololake.persona-language-channel-runtime-acceptance/v1",
|
||||
"receipt_id": "GH-HOLOLAKE-PERSONA-LANGUAGE-CHANNEL-20260820-001",
|
||||
"state": "PASS_100_LOCAL_CANONICAL_RUNTIME",
|
||||
"observed_at": "2026-08-20T05:30:00+08:00",
|
||||
"installed_application": {
|
||||
"path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake.app",
|
||||
"bundle_identifier": "world.guanghu.hololake",
|
||||
"version": "0.8.1",
|
||||
"executable_sha256": "478f3459a4fe1c8cc6c8e0f5efceaedf4d106a418c53c63768f7eea7a88f2bf4",
|
||||
"cdhash": "65cae97b594168067a6be45d6970d952d25e946a",
|
||||
"signature": "DEVELOPER_ID_APPLICATION",
|
||||
"developer_id_team": "825A9L3G7Q",
|
||||
"codesign_strict_verification": "PASS",
|
||||
"apple_notarized": false
|
||||
},
|
||||
"human_chat": {
|
||||
"first_class_light_lake_entry_visible": true,
|
||||
"knowledge_workspace_sidebar_visible_by_default": true,
|
||||
"configuration_is_secondary": true,
|
||||
"real_provider": "阿里云百炼 · Token Plan",
|
||||
"real_model": "qwen3.7-plus",
|
||||
"provider_selection_survived_restart": true,
|
||||
"provider_secret_storage": "OS_KEYCHAIN_ONLY",
|
||||
"post_restart_prompt": "重启后的正版验收:只回复“正版重启可用”。",
|
||||
"post_restart_response": "正版重启可用"
|
||||
},
|
||||
"knowledge_collaboration": {
|
||||
"active_title": "光湖纪元演化史",
|
||||
"active_source": "native",
|
||||
"active_path": "未命名页面.md",
|
||||
"active_content_sha256": "04905d23d1b47f46b2b482111fd6a3a0e7ee8e780ddd3fc589b6e2da22edaa31",
|
||||
"question": "这页开头“用途”下面写的数据来源是什么?只回复数据来源名称。",
|
||||
"answer": "📡 广播归档表(含现实保存时间)",
|
||||
"path_and_hash_receipt_visible": true,
|
||||
"conversation_and_receipt_survived_restart": true,
|
||||
"post_restart_live_message_repeated_same_knowledge_receipt": true
|
||||
},
|
||||
"automated_gates": {
|
||||
"node_tests": "121_PASS",
|
||||
"rust_tests": "141_PASS_2_IGNORED",
|
||||
"typescript_vite_build": "PASS",
|
||||
"cargo_fmt_check": "PASS",
|
||||
"secret_scan": "PASS_NO_EMBEDDED_API_SECRET",
|
||||
"release_bundle": "PASS"
|
||||
},
|
||||
"desktop_convergence": {
|
||||
"active_hololake_app_count": 1,
|
||||
"active_application": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake.app",
|
||||
"older_desktop_app_variants_moved_to_trash": 6,
|
||||
"cleanup_recoverable": true,
|
||||
"source_worktrees_deleted": false,
|
||||
"knowledge_data_deleted": false
|
||||
},
|
||||
"exclusions": [
|
||||
"APPLE_NOTARIZATION",
|
||||
"PUBLIC_RELEASE_ACTIVATION",
|
||||
"REMOTE_GIT_PUSH",
|
||||
"SERVER_DEPLOYMENT",
|
||||
"PUBLICATION"
|
||||
]
|
||||
}
|
||||
|
|
@ -71,7 +71,8 @@
|
|||
"exact_active_knowledge_page_context_implemented": true,
|
||||
"knowledge_path_and_content_hash_receipt_implemented": true,
|
||||
"model_is_persona_identity_or_authority": false,
|
||||
"installed_runtime_acceptance": false
|
||||
"installed_runtime_acceptance": true,
|
||||
"acceptance_receipt": "audit/persona-language-channel-runtime-acceptance-20260820.json"
|
||||
},
|
||||
"zero_point_nucleus_client_runtime": {
|
||||
"contract": "contracts/zero-point-nucleus-channel.json",
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@
|
|||
"pncc_jd_live_server_projection_repository_content_exposed": false,
|
||||
"pncc_jd_live_server_projection_write_authority": false,
|
||||
"pncc_jd_live_server_projection_carrier_state": "UNBOUND_EVIDENCE_REQUIRED",
|
||||
"installed_local_product_version": "0.4.1",
|
||||
"installed_local_product_version": "0.8.1",
|
||||
"pncc_authenticated_direct_broker_integration_implemented": true,
|
||||
"pncc_human_mount_registration_implemented": true,
|
||||
"pncc_human_mount_registration_gate": "SATISFIED_NATIVE_FILE_PICKER_EXACT_CONFIRMATION",
|
||||
|
|
@ -156,7 +156,8 @@
|
|||
"persona_language_channel_model_configuration_is_secondary": true,
|
||||
"persona_language_channel_api_secret_storage": "OS_KEYCHAIN_ONLY",
|
||||
"persona_language_channel_exact_knowledge_context_and_receipt_implemented": true,
|
||||
"persona_language_channel_installed_runtime_acceptance": false,
|
||||
"persona_language_channel_installed_runtime_acceptance": true,
|
||||
"persona_language_channel_acceptance_receipt": "audit/persona-language-channel-runtime-acceptance-20260820.json",
|
||||
"vendor_adapter_matrix_required": false,
|
||||
"first_public_product": "GH-AIOS_GENERAL_AI_OPERATING_PLATFORM",
|
||||
"visible_five_domain_navigation_in_stage_one": true,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ test('knowledge workspace keeps the chat visible and binds the exact active page
|
|||
assert.match(channel, /引用知识 · \{receipt\.path\} · \{shortHash\(receipt\.contentSha256\)\}/)
|
||||
assert.match(runtime, /collect_knowledge_context\([\s\S]*?input\.active_knowledge_source\.as_deref\(\)[\s\S]*?input\.active_knowledge_path\.as_deref\(\)/)
|
||||
assert.match(runtime, /append_knowledge_document\(&mut context, &mut seen, document\)/)
|
||||
assert.match(runtime, /let secret = read_secret_bounded\(app, provider_id\)\.await\.ok\(\)/)
|
||||
assert.doesNotMatch(runtime, /fn read_secret\(/)
|
||||
})
|
||||
|
||||
test('model configuration remains secondary to the conversation surface', () => {
|
||||
|
|
@ -31,3 +33,11 @@ test('model configuration remains secondary to the conversation surface', () =>
|
|||
assert.match(channel, /\{settingsOpen && <aside className="persona-chat-settings">/)
|
||||
assert.match(channel, /API 密钥 · 只进钥匙串/)
|
||||
})
|
||||
|
||||
test('the chosen model route survives restart and prefers explicit user configuration', () => {
|
||||
assert.match(channel, /const PROVIDER_SELECTION_KEY = 'hololake-persona-provider-id'/)
|
||||
assert.match(channel, /localStorage\.getItem\(PROVIDER_SELECTION_KEY\)/)
|
||||
assert.match(channel, /item\.source === 'EXPLICIT_USER_CONFIG'/)
|
||||
assert.match(channel, /rememberProvider\(nextProviderId\)/)
|
||||
assert.match(channel, /onChange=\{\(event\) => selectProvider\(event\.target\.value\)\}/)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ test('stage one exposes a language persona channel without turning configuration
|
|||
assert.ok(contract.stage_one_forbidden.includes('MODEL_IDENTITY_OR_AUTHORITY'))
|
||||
assert.equal(contract.persona_language_channel.human_chat_is_primary_surface, true)
|
||||
assert.equal(contract.persona_language_channel.model_configuration_is_secondary, true)
|
||||
assert.equal(contract.persona_language_channel.installed_runtime_acceptance, false)
|
||||
assert.equal(contract.persona_language_channel.installed_runtime_acceptance, true)
|
||||
assert.equal(contract.persona_language_channel.acceptance_receipt, 'audit/persona-language-channel-runtime-acceptance-20260820.json')
|
||||
assert.equal(foundation.persona_language_channel_installed_runtime_acceptance, true)
|
||||
assert.equal(foundation.stage_one_internal_ai_interaction, true)
|
||||
assert.equal(foundation.stage_one_model_api_configuration, true)
|
||||
assert.equal(foundation.persona_language_channel_api_secret_storage, 'OS_KEYCHAIN_ONLY')
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ async fn invoke_provider(
|
|||
.map(str::to_string)
|
||||
.or_else(|| (!config.model.is_empty()).then_some(config.model.clone()))
|
||||
.ok_or_else(|| "HOLOLAKE_MODEL_SELECTION_REQUIRED".to_string())?;
|
||||
let secret = read_secret(app, provider_id).ok();
|
||||
let secret = read_secret_bounded(app, provider_id).await.ok();
|
||||
match config.kind.as_str() {
|
||||
"OPENAI_COMPATIBLE" => invoke_openai_compatible(
|
||||
&config,
|
||||
|
|
@ -1015,13 +1015,6 @@ fn secret_account(app: &AppHandle, provider_id: &str) -> Result<String, String>
|
|||
Ok(format!("{scope}:{provider_id}"))
|
||||
}
|
||||
|
||||
fn read_secret(app: &AppHandle, provider_id: &str) -> Result<String, String> {
|
||||
Entry::new(SECRET_SERVICE, &secret_account(app, provider_id)?)
|
||||
.map_err(|error| format!("HOLOLAKE_MODEL_KEYCHAIN_UNAVAILABLE: {error}"))?
|
||||
.get_password()
|
||||
.map_err(|_| "HOLOLAKE_MODEL_SECRET_NOT_FOUND".to_string())
|
||||
}
|
||||
|
||||
pub(crate) async fn codex_execution_model_route(
|
||||
app: &AppHandle,
|
||||
) -> Result<Option<CodexExecutionModelRoute>, String> {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ interface ConversationSnapshot { personaId: string; messages: PersonaMessage[];
|
|||
interface LimbSnapshot { state: string; limbId: string; executableState: string; executableVersion?: string; trainingCycleCount: number; validatedMemoryCount: number; ledgerIntegrity: string; lastTraining?: { state: string; lessonId: string; summary: string; receiptHash: string } }
|
||||
interface DrillReceipt { state: string; summary: string; receiptHash: string; snapshot: LimbSnapshot }
|
||||
|
||||
const PROVIDER_SELECTION_KEY = 'hololake-persona-provider-id'
|
||||
|
||||
function errorText(error: unknown) {
|
||||
return String(error).replace(/^Error:\s*/, '').replace(/^HOLOLAKE_/, '')
|
||||
}
|
||||
|
|
@ -31,12 +33,17 @@ function localTime(value: number) {
|
|||
return new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit' }).format(new Date(value))
|
||||
}
|
||||
|
||||
function rememberProvider(providerId: string) {
|
||||
if (providerId) localStorage.setItem(PROVIDER_SELECTION_KEY, providerId)
|
||||
else localStorage.removeItem(PROVIDER_SELECTION_KEY)
|
||||
}
|
||||
|
||||
export function PersonaChannelBody({ onBack, knowledgeFocus, embedded = false }: { onBack?: () => void; knowledgeFocus?: KnowledgeFocus | null; embedded?: boolean }) {
|
||||
const [body, setBody] = useState<PersonaBodySnapshot | null>(null)
|
||||
const [discovery, setDiscovery] = useState<ProviderDiscovery | null>(null)
|
||||
const [conversation, setConversation] = useState<ConversationSnapshot | null>(null)
|
||||
const [personaId, setPersonaId] = useState('')
|
||||
const [providerId, setProviderId] = useState('')
|
||||
const [providerId, setProviderId] = useState(() => localStorage.getItem(PROVIDER_SELECTION_KEY) || '')
|
||||
const [model, setModel] = useState('')
|
||||
const [draft, setDraft] = useState('')
|
||||
const [notice, setNotice] = useState('')
|
||||
|
|
@ -59,11 +66,23 @@ export function PersonaChannelBody({ onBack, knowledgeFocus, embedded = false }:
|
|||
const next = await invoke<ProviderDiscovery>('discover_model_providers')
|
||||
setDiscovery(next)
|
||||
setProviderId((current) => {
|
||||
if (current && next.providers.some((item) => item.providerId === current && item.state === 'AVAILABLE')) return current
|
||||
return next.providers.find((item) => item.state === 'AVAILABLE')?.providerId || ''
|
||||
const available = next.providers.filter((item) => item.state === 'AVAILABLE')
|
||||
const stored = localStorage.getItem(PROVIDER_SELECTION_KEY)
|
||||
const selected = available.find((item) => item.providerId === current)
|
||||
|| available.find((item) => item.providerId === stored)
|
||||
|| available.find((item) => item.source === 'EXPLICIT_USER_CONFIG')
|
||||
|| available[0]
|
||||
const nextProviderId = selected?.providerId || ''
|
||||
rememberProvider(nextProviderId)
|
||||
return nextProviderId
|
||||
})
|
||||
}
|
||||
|
||||
const selectProvider = (nextProviderId: string) => {
|
||||
rememberProvider(nextProviderId)
|
||||
setProviderId(nextProviderId)
|
||||
}
|
||||
|
||||
const refreshBody = async () => {
|
||||
const next = await invoke<PersonaBodySnapshot>('get_persona_channel_body')
|
||||
setBody(next)
|
||||
|
|
@ -141,7 +160,7 @@ export function PersonaChannelBody({ onBack, knowledgeFocus, embedded = false }:
|
|||
} })
|
||||
setDiscovery(next)
|
||||
const created = next.providers.find((item) => item.label === configLabel.trim())
|
||||
setProviderId(created?.providerId || next.providers.find((item) => item.state === 'AVAILABLE')?.providerId || '')
|
||||
selectProvider(created?.providerId || next.providers.find((item) => item.state === 'AVAILABLE')?.providerId || '')
|
||||
setConfigKey('')
|
||||
setSettingsOpen(false)
|
||||
setNotice('模型入口已保存并验证;密钥只进入系统钥匙串。')
|
||||
|
|
@ -201,7 +220,7 @@ export function PersonaChannelBody({ onBack, knowledgeFocus, embedded = false }:
|
|||
<header><div><span>ADVANCED SETTINGS</span><h2>模型与执行证据</h2></div><button type="button" onClick={() => void refreshProviders()}>重新发现</button></header>
|
||||
<div className="persona-provider-selectors">
|
||||
<label><span>频道对象</span><select value={personaId} onChange={(event) => setPersonaId(event.target.value)}>{body?.personas.map((item) => <option value={item.personaId} key={item.personaId}>{item.displayName}</option>)}</select></label>
|
||||
<label><span>模型入口</span><select value={providerId} onChange={(event) => setProviderId(event.target.value)}><option value="">请选择</option>{discovery?.providers.map((item) => <option value={item.providerId} key={item.providerId} disabled={item.state !== 'AVAILABLE'}>{item.label} · {item.state}</option>)}</select></label>
|
||||
<label><span>模型入口</span><select value={providerId} onChange={(event) => selectProvider(event.target.value)}><option value="">请选择</option>{discovery?.providers.map((item) => <option value={item.providerId} key={item.providerId} disabled={item.state !== 'AVAILABLE'}>{item.label} · {item.state}</option>)}</select></label>
|
||||
<label><span>模型</span><input value={model} onChange={(event) => setModel(event.target.value)}/></label>
|
||||
</div>
|
||||
<form className="persona-provider-form" onSubmit={(event) => void saveProvider(event)}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue