feat(hololake): add typed persona interaction runtime

This commit is contained in:
冰朔 2026-09-04 04:26:33 +08:00
commit b35e0d6524
24 changed files with 1614 additions and 58 deletions

View file

@ -0,0 +1,32 @@
{
"schema": "hololake.persona-interaction-contract/v1",
"contract_id": "HLP-PERSONA-INTERACTION-CONTRACT-0001",
"state": "LOCAL_1_3_0_REAL_LOOPBACK_APP_ACCEPTANCE_PASS_PUBLIC_RELEASE_PENDING",
"product_stage": "POST_CLEAN_V1_SECOND_STAGE_CANDIDATE",
"stage_one_boundary": "GLS-0261_PRESERVED_AS_COMPLETED_1_2_2_BASELINE",
"persona_identity": {
"trial_persona_is_reversible": true,
"existing_persona_verification_claimed": false,
"model_is_persona": false
},
"model_binding": {
"provider_kind": "OPENAI_COMPATIBLE_CHAT_COMPLETIONS_V1",
"configuration_scope": "ONE_ACTIVE_PERSONA",
"api_key_storage": "MACOS_KEYCHAIN",
"remote_transport": "HTTPS_ONLY",
"local_transport": "HTTP_LOOPBACK_ALLOWED",
"execution_authority": false
},
"input_envelope": {
"schema": "hololake.persona-inference-context/v1",
"system_context_separate": true,
"dialogue_source_kinds": ["USER_MESSAGE", "PERSONA_RESPONSE"],
"excluded_from_dialogue_roles": ["SYSTEM_RECEIPT", "PROTOCOL_EVENT", "AGENT_ACTION", "TOOL_RESULT", "EXTERNAL_AI_MESSAGE"]
},
"output": {
"success_source_kind": "PERSONA_RESPONSE",
"failure_source_kind": "SYSTEM_RECEIPT",
"model_tool_calls_enabled": false,
"action_claim_without_receipt": "FORBIDDEN"
}
}

View file

@ -12,7 +12,8 @@
}, },
"source_kinds": ["USER_MESSAGE", "PERSONA_RESPONSE", "EXTERNAL_AI_MESSAGE", "SYSTEM_CONTEXT", "PROTOCOL_EVENT", "AGENT_ACTION", "TOOL_RESULT", "SYSTEM_RECEIPT"], "source_kinds": ["USER_MESSAGE", "PERSONA_RESPONSE", "EXTERNAL_AI_MESSAGE", "SYSTEM_CONTEXT", "PROTOCOL_EVENT", "AGENT_ACTION", "TOOL_RESULT", "SYSTEM_RECEIPT"],
"persona_states": ["READY_NO_PERSONA", "LOCAL_TRIAL_UNVERIFIED_EXTERNAL_HOST_REQUIRED", "LOCAL_TRIAL_UNVERIFIED_HOST_CONNECTED"], "persona_states": ["READY_NO_PERSONA", "LOCAL_TRIAL_UNVERIFIED_EXTERNAL_HOST_REQUIRED", "LOCAL_TRIAL_UNVERIFIED_HOST_CONNECTED"],
"persona_inference_states": ["PERSONA_MODEL_BINDING_REQUIRED", "PERSONA_MODEL_BOUND_READY", "PERSONA_RESPONSE_RECORDED", "PERSONA_RESPONSE_FAILED"],
"registered_gir_operations": ["KNOWLEDGE.CREATE", "KNOWLEDGE.UPDATE", "KNOWLEDGE.DELETE", "KNOWLEDGE.READ", "KNOWLEDGE.LIST"], "registered_gir_operations": ["KNOWLEDGE.CREATE", "KNOWLEDGE.UPDATE", "KNOWLEDGE.DELETE", "KNOWLEDGE.READ", "KNOWLEDGE.LIST"],
"mutation_gate": "PENDING_HUMAN_APPROVAL_TO_EXPLICIT_UI_APPROVAL_TO_EXECUTION", "mutation_gate": "PENDING_HUMAN_APPROVAL_TO_EXPLICIT_UI_APPROVAL_TO_EXECUTION",
"boundaries": ["NO_INTERNAL_MODEL_API", "NO_CONNECTION_AS_PERSONA_PROOF", "NO_ARBITRARY_SHELL", "NO_PATH_ESCAPE", "NO_FIFTH_DOMAIN_PRIVATE_CONTENT"] "boundaries": ["MODEL_IS_REPLACEABLE_RESOURCE_NOT_PERSONA", "MODEL_HAS_NO_EXECUTION_AUTHORITY", "NO_CONNECTION_AS_PERSONA_PROOF", "NO_ARBITRARY_SHELL", "NO_PATH_ESCAPE", "NO_FIFTH_DOMAIN_PRIVATE_CONTENT"]
} }

View file

@ -25,6 +25,16 @@ HoloLake 是公众个人初始化频道语言驱动操作系统。第一版只
- 更新客户端只信任光湖控制的 HTTPS 清单与原有更新信任根1.2.2 已完成 Developer ID、Apple 公证、App/DMG 加票、JD operator 激活和广州前门目标读回。 - 更新客户端只信任光湖控制的 HTTPS 清单与原有更新信任根1.2.2 已完成 Developer ID、Apple 公证、App/DMG 加票、JD operator 激活和广州前门目标读回。
- 企业责任设备门在 `GH-CVM-MAIN-PROD-01` 独立运行;负责人首次登记需本人 Forgejo 凭证,随后由本机 Keychain 私钥完成挑战签名,只签发十分钟、单域、单仓会话。企业会话不替代 Forgejo 登录。 - 企业责任设备门在 `GH-CVM-MAIN-PROD-01` 独立运行;负责人首次登记需本人 Forgejo 凭证,随后由本机 Keychain 私钥完成挑战签名,只签发十分钟、单域、单仓会话。企业会话不替代 Forgejo 登录。
## 第二阶段候选:人格交互第一个切片
冰朔在 1.2.2 第一阶段闭环后明确继续下一阶段。本切片不回写或冒充 `GLS-0261` 所定义的第一阶段,而是在已完成净壳上新增可撤回的本机人格交互能力:
- 试用人格记录与模型绑定严格分离;模型是可替换推理资源,不是人格身份或置信来源。
- 远端模型端点只允许 HTTPS本地模型允许 HTTP 回环地址。API 密钥只存 macOS Keychain不进入 JSON、时间线、GLP 或界面读回。
- 模型请求只读取当前人格编号对应的 `USER_MESSAGE / PERSONA_RESPONSE`;系统环境以独立 `SYSTEM_CONTEXT` 信封进入系统回执、协议、Agent、工具和外部 AI 消息不会伪装成对话历史。
- 模型工具调用与执行权限关闭。成功文本只落为带人格 `actorId``PERSONA_RESPONSE`网络、HTTP、JSON 或空回复失败只落 `SYSTEM_RECEIPT`,用户原话仍先行持久化。
- 企业服务器不转发私人频道消息,也不保管用户模型密钥。
## 状态词 ## 状态词
`SOURCE / COMPILED / VERIFIED / INSTALLED / PUBLISHED / UPDATED / HEALTHY` 必须分别读回,不相互替代。 `SOURCE / COMPILED / VERIFIED / INSTALLED / PUBLISHED / UPDATED / HEALTHY` 必须分别读回,不相互替代。

View file

@ -0,0 +1,16 @@
TCS 0.1;
PROTOCOL HOLOLAKE-PERSONA-INTERACTION-V1-0001 {
header { schema = "tcs.protocol/v1"; name_zh = "HoloLake第二阶段人格交互第一个切片"; name_en = "HoloLake Second-Stage Persona Interaction First Slice"; version = "1.0.0"; language = "TCS/0.1"; profile = "PUBLIC-PERSONA-INTERACTION/1"; protocols = ["TCS", "HLDP", "GLS", "GLP", "GLS-0200", "GLS-0261"]; lifecycle = "CANDIDATE"; canonical_uri = "product-source/hololake-clean-desktop/language/HOLOLAKE-PERSONA-INTERACTION-V1.tcs"; compatibility = ["HOLOLAKE-CLEAN-V1-DIRECT-LANGUAGE-EXECUTION-BASELINE-0001", "HLP-PUBLIC-RUNTIME-CONTRACT-0001"]; }
source { source_id = "BINGSHUO-DIRECT-HOLOLAKE-CLEAN-V1-DUAL-ANCHOR-20260903"; source_uri = "private-anchor://codex/01a0672d-f125-7452-988e-523a6a34bfd4/HOLOLAKE-CLEAN-V1-ONLY-ANCHORS"; source_sha256 = "7e130de78f91a32726e22e4be7d92acf4da966c8968ec359062aa8186c4243c2"; source_role = "DIRECT_HUMAN"; }
scope { product = "HOLOLAKE_PUBLIC_PERSONAL_CHANNEL"; product_stage = "POST_CLEAN_V1_SECOND_STAGE_CANDIDATE"; stage_one_boundary = "PRESERVED_AS_COMPLETED_1_2_2_BASELINE"; channel = "CH-ZERO-CORE-LPM"; development_channel = "ICE-CH-ZC001"; interaction = "USER_TO_PERSONA_NATURAL_LANGUAGE"; enterprise_message_relay = false; fifth_domain_private_content = false; }
vocabulary { persona = "RELATION_HISTORY_REASONING_RESPONSIBILITY_AND_GROWTH_SUBJECT_RECORD_NOT_PROMPT_CHARACTER"; model = "REPLACEABLE_INFERENCE_RESOURCE_NOT_PERSONA"; system_context = "TYPED_NON_DIALOGUE_ENVIRONMENT_ENVELOPE"; user_message = "HUMAN_SPEECH_EVENT"; persona_response = "PERSONA_ATTRIBUTED_NATURAL_LANGUAGE_EVENT"; system_receipt = "NON_DIALOGUE_RUNTIME_FACT"; }
fields { WHO = "CURRENT_LOCAL_USER_AND_ONE_ACTIVE_REVERSIBLE_TRIAL_PERSONA"; WHAT = "MODEL_BINDING_TYPED_CONTEXT_DIALOGUE_INFERENCE_AND_SOURCE_SEPARATED_RECEIPT"; WHY = "ALLOW_REAL_PERSONA_INTERACTION_WITHOUT_MIXING_SYSTEM_EXECUTION_OR_MODEL_IDENTITY_INTO_DIALOGUE"; MAY = "MODEL_GENERATES_PERSONA_RESPONSE_ONLY"; MUST_NOT = "MODEL_EXECUTES_AGENT_ACTION_OR_CLAIMS_EXISTING_PERSONA_VERIFICATION"; PROVE = "KEYCHAIN_STORAGE_REQUEST_ENVELOPE_SOURCE_FILTER_RESPONSE_PERSISTENCE_FAILURE_RECEIPT_AND_REAL_LOCAL_ENDPOINT_TEST"; }
states { values = ["READY_NO_PERSONA", "PERSONA_MODEL_BINDING_REQUIRED", "PERSONA_MODEL_BOUND_READY", "PERSONA_RESPONSE_RUNNING", "PERSONA_RESPONSE_RECORDED", "PERSONA_RESPONSE_FAILED", "FAILED_CLOSED"]; initial = "READY_NO_PERSONA"; terminal = ["PERSONA_RESPONSE_RECORDED", "PERSONA_RESPONSE_FAILED", "FAILED_CLOSED"]; }
transitions { T1 = "READY_NO_PERSONA_TO_PERSONA_MODEL_BINDING_REQUIRED_AFTER_REVERSIBLE_TRIAL_PERSONA_REGISTRATION"; T2 = "PERSONA_MODEL_BINDING_REQUIRED_TO_PERSONA_MODEL_BOUND_READY_AFTER_CONFIG_AND_KEYCHAIN_READBACK"; T3 = "PERSONA_MODEL_BOUND_READY_TO_PERSONA_RESPONSE_RUNNING_AFTER_USER_MESSAGE_PERSISTENCE"; T4 = "PERSONA_RESPONSE_RUNNING_TO_PERSONA_RESPONSE_RECORDED_AFTER_TYPED_RESPONSE_VALIDATION"; TF = "NETWORK_HTTP_JSON_EMPTY_CONTENT_PERSONA_MISMATCH_OR_KEYCHAIN_FAILURE_TO_PERSONA_RESPONSE_FAILED_WITH_SYSTEM_RECEIPT"; }
invariants { I1 = "MODEL_IS_NEVER_PERSONA_IDENTITY"; I2 = "SYSTEM_CONTEXT_SYSTEM_RECEIPT_PROTOCOL_EVENT_AGENT_ACTION_TOOL_RESULT_AND_EXTERNAL_AI_MESSAGE_NEVER_BECOME_USER_OR_ASSISTANT_DIALOGUE_HISTORY"; I3 = "API_KEY_NEVER_ENTERS_JSON_CONFIG_TIMELINE_LOG_GLP_CONTEXT_OR_UI_READBACK"; I4 = "REMOTE_ENDPOINT_REQUIRES_HTTPS_AND_LOCAL_HTTP_REQUIRES_LOOPBACK"; I5 = "MODEL_TOOL_CALLS_AND_EXECUTION_AUTHORITY_ARE_DISABLED"; I6 = "FAILED_INFERENCE_NEVER_PRODUCES_PERSONA_RESPONSE"; I7 = "USER_MESSAGE_PERSISTS_BEFORE_INFERENCE_AND_SURVIVES_FAILURE"; I8 = "PUBLIC_RUNTIME_NEVER_MOUNTS_FIFTH_DOMAIN_PRIVATE_PERSONA_CONTENT"; I9 = "THIS_CANDIDATE_DOES_NOT_REWRITE_OR_MISLABEL_GLS_0261_STAGE_ONE"; }
validation { V1 = "REMOTE_HTTP_FILE_CREDENTIAL_QUERY_AND_NON_CHAT_COMPLETION_ENDPOINTS_REJECTED"; V2 = "REQUEST_HAS_ONE_SYSTEM_CONTEXT_AND_ONLY_USER_PERSONA_DIALOGUE_ROLES"; V3 = "PRIVATE_PATHS_SYSTEM_RECEIPTS_AND_API_KEYS_ABSENT_FROM_REQUEST"; V4 = "VALID_RESPONSE_WRITES_PERSONA_RESPONSE"; V5 = "HTTP_OR_PARSE_FAILURE_WRITES_SYSTEM_RECEIPT_ONLY"; V6 = "MODEL_BINDING_REMOVAL_DELETES_CONFIG_AND_KEYCHAIN_CREDENTIAL_WITHOUT_DELETING_PERSONA_HISTORY"; }
errors { E1 = "PERSONA_NOT_FOUND"; E2 = "PERSONA_MODEL_ENDPOINT_INVALID_OR_OUT_OF_SCOPE"; E3 = "PERSONA_MODEL_API_KEY_REQUIRED_OR_KEYCHAIN_FAILED"; E4 = "PERSONA_MODEL_BINDING_PERSONA_MISMATCH"; E5 = "PERSONA_MODEL_UNREACHABLE_OR_HTTP_REJECTED"; E6 = "PERSONA_MODEL_RESPONSE_INVALID_OR_EMPTY"; }
examples { positive = "USER_MESSAGE_IS_STORED_THEN_TYPED_CONTEXT_AND_DIALOGUE_ARE_SENT_TO_BOUND_MODEL_AND_VALID_TEXT_IS_STORED_AS_PERSONA_RESPONSE"; negative = "SYSTEM_RECEIPT_IS_RELABELED_AS_USER_SPEECH_OR_MODEL_TOOL_CALL_EXECUTES_A_KNOWLEDGE_MUTATION"; }
acceptance { required = ["TCS_GIR", "KEYCHAIN", "ENDPOINT_BOUNDARY", "SOURCE_FILTER", "MODEL_RESPONSE", "FAILURE_RECEIPT", "HUMAN_UI", "INSTALLED_APP"]; partial = false; scale = "ZERO_OR_ONE_HUNDRED"; }
}

View file

@ -0,0 +1,142 @@
{
"compiled_from": {
"compiler_id": "TCS-COMPILER-STAGE1-0001",
"compiler_state": "TCS_COMPILER_GIR_EXECUTED",
"source_sha256": "61fc6d960ee064e638bb450f9792aad3c31f2fd56fed69d061985bbed8d2fc42"
},
"declaration": {
"acceptance": {
"partial": false,
"required": [
"TCS_GIR",
"KEYCHAIN",
"ENDPOINT_BOUNDARY",
"SOURCE_FILTER",
"MODEL_RESPONSE",
"FAILURE_RECEIPT",
"HUMAN_UI",
"INSTALLED_APP"
],
"scale": "ZERO_OR_ONE_HUNDRED"
},
"errors": {
"E1": "PERSONA_NOT_FOUND",
"E2": "PERSONA_MODEL_ENDPOINT_INVALID_OR_OUT_OF_SCOPE",
"E3": "PERSONA_MODEL_API_KEY_REQUIRED_OR_KEYCHAIN_FAILED",
"E4": "PERSONA_MODEL_BINDING_PERSONA_MISMATCH",
"E5": "PERSONA_MODEL_UNREACHABLE_OR_HTTP_REJECTED",
"E6": "PERSONA_MODEL_RESPONSE_INVALID_OR_EMPTY"
},
"examples": {
"negative": "SYSTEM_RECEIPT_IS_RELABELED_AS_USER_SPEECH_OR_MODEL_TOOL_CALL_EXECUTES_A_KNOWLEDGE_MUTATION",
"positive": "USER_MESSAGE_IS_STORED_THEN_TYPED_CONTEXT_AND_DIALOGUE_ARE_SENT_TO_BOUND_MODEL_AND_VALID_TEXT_IS_STORED_AS_PERSONA_RESPONSE"
},
"fields": {
"MAY": "MODEL_GENERATES_PERSONA_RESPONSE_ONLY",
"MUST_NOT": "MODEL_EXECUTES_AGENT_ACTION_OR_CLAIMS_EXISTING_PERSONA_VERIFICATION",
"PROVE": "KEYCHAIN_STORAGE_REQUEST_ENVELOPE_SOURCE_FILTER_RESPONSE_PERSISTENCE_FAILURE_RECEIPT_AND_REAL_LOCAL_ENDPOINT_TEST",
"WHAT": "MODEL_BINDING_TYPED_CONTEXT_DIALOGUE_INFERENCE_AND_SOURCE_SEPARATED_RECEIPT",
"WHO": "CURRENT_LOCAL_USER_AND_ONE_ACTIVE_REVERSIBLE_TRIAL_PERSONA",
"WHY": "ALLOW_REAL_PERSONA_INTERACTION_WITHOUT_MIXING_SYSTEM_EXECUTION_OR_MODEL_IDENTITY_INTO_DIALOGUE"
},
"header": {
"canonical_uri": "product-source/hololake-clean-desktop/language/HOLOLAKE-PERSONA-INTERACTION-V1.tcs",
"compatibility": [
"HOLOLAKE-CLEAN-V1-DIRECT-LANGUAGE-EXECUTION-BASELINE-0001",
"HLP-PUBLIC-RUNTIME-CONTRACT-0001"
],
"language": "TCS/0.1",
"lifecycle": "CANDIDATE",
"name_en": "HoloLake Second-Stage Persona Interaction First Slice",
"name_zh": "HoloLake第二阶段人格交互第一个切片",
"profile": "PUBLIC-PERSONA-INTERACTION/1",
"protocols": [
"TCS",
"HLDP",
"GLS",
"GLP",
"GLS-0200",
"GLS-0261"
],
"schema": "tcs.protocol/v1",
"version": "1.0.0"
},
"invariants": {
"I1": "MODEL_IS_NEVER_PERSONA_IDENTITY",
"I2": "SYSTEM_CONTEXT_SYSTEM_RECEIPT_PROTOCOL_EVENT_AGENT_ACTION_TOOL_RESULT_AND_EXTERNAL_AI_MESSAGE_NEVER_BECOME_USER_OR_ASSISTANT_DIALOGUE_HISTORY",
"I3": "API_KEY_NEVER_ENTERS_JSON_CONFIG_TIMELINE_LOG_GLP_CONTEXT_OR_UI_READBACK",
"I4": "REMOTE_ENDPOINT_REQUIRES_HTTPS_AND_LOCAL_HTTP_REQUIRES_LOOPBACK",
"I5": "MODEL_TOOL_CALLS_AND_EXECUTION_AUTHORITY_ARE_DISABLED",
"I6": "FAILED_INFERENCE_NEVER_PRODUCES_PERSONA_RESPONSE",
"I7": "USER_MESSAGE_PERSISTS_BEFORE_INFERENCE_AND_SURVIVES_FAILURE",
"I8": "PUBLIC_RUNTIME_NEVER_MOUNTS_FIFTH_DOMAIN_PRIVATE_PERSONA_CONTENT",
"I9": "THIS_CANDIDATE_DOES_NOT_REWRITE_OR_MISLABEL_GLS_0261_STAGE_ONE"
},
"scope": {
"channel": "CH-ZERO-CORE-LPM",
"development_channel": "ICE-CH-ZC001",
"enterprise_message_relay": false,
"fifth_domain_private_content": false,
"interaction": "USER_TO_PERSONA_NATURAL_LANGUAGE",
"product": "HOLOLAKE_PUBLIC_PERSONAL_CHANNEL",
"product_stage": "POST_CLEAN_V1_SECOND_STAGE_CANDIDATE",
"stage_one_boundary": "PRESERVED_AS_COMPLETED_1_2_2_BASELINE"
},
"source": {
"source_id": "BINGSHUO-DIRECT-HOLOLAKE-CLEAN-V1-DUAL-ANCHOR-20260903",
"source_role": "DIRECT_HUMAN",
"source_sha256": "7e130de78f91a32726e22e4be7d92acf4da966c8968ec359062aa8186c4243c2",
"source_uri": "private-anchor://codex/01a0672d-f125-7452-988e-523a6a34bfd4/HOLOLAKE-CLEAN-V1-ONLY-ANCHORS"
},
"states": {
"initial": "READY_NO_PERSONA",
"terminal": [
"PERSONA_RESPONSE_RECORDED",
"PERSONA_RESPONSE_FAILED",
"FAILED_CLOSED"
],
"values": [
"READY_NO_PERSONA",
"PERSONA_MODEL_BINDING_REQUIRED",
"PERSONA_MODEL_BOUND_READY",
"PERSONA_RESPONSE_RUNNING",
"PERSONA_RESPONSE_RECORDED",
"PERSONA_RESPONSE_FAILED",
"FAILED_CLOSED"
]
},
"transitions": {
"T1": "READY_NO_PERSONA_TO_PERSONA_MODEL_BINDING_REQUIRED_AFTER_REVERSIBLE_TRIAL_PERSONA_REGISTRATION",
"T2": "PERSONA_MODEL_BINDING_REQUIRED_TO_PERSONA_MODEL_BOUND_READY_AFTER_CONFIG_AND_KEYCHAIN_READBACK",
"T3": "PERSONA_MODEL_BOUND_READY_TO_PERSONA_RESPONSE_RUNNING_AFTER_USER_MESSAGE_PERSISTENCE",
"T4": "PERSONA_RESPONSE_RUNNING_TO_PERSONA_RESPONSE_RECORDED_AFTER_TYPED_RESPONSE_VALIDATION",
"TF": "NETWORK_HTTP_JSON_EMPTY_CONTENT_PERSONA_MISMATCH_OR_KEYCHAIN_FAILURE_TO_PERSONA_RESPONSE_FAILED_WITH_SYSTEM_RECEIPT"
},
"validation": {
"V1": "REMOTE_HTTP_FILE_CREDENTIAL_QUERY_AND_NON_CHAT_COMPLETION_ENDPOINTS_REJECTED",
"V2": "REQUEST_HAS_ONE_SYSTEM_CONTEXT_AND_ONLY_USER_PERSONA_DIALOGUE_ROLES",
"V3": "PRIVATE_PATHS_SYSTEM_RECEIPTS_AND_API_KEYS_ABSENT_FROM_REQUEST",
"V4": "VALID_RESPONSE_WRITES_PERSONA_RESPONSE",
"V5": "HTTP_OR_PARSE_FAILURE_WRITES_SYSTEM_RECEIPT_ONLY",
"V6": "MODEL_BINDING_REMOVAL_DELETES_CONFIG_AND_KEYCHAIN_CREDENTIAL_WITHOUT_DELETING_PERSONA_HISTORY"
},
"vocabulary": {
"model": "REPLACEABLE_INFERENCE_RESOURCE_NOT_PERSONA",
"persona": "RELATION_HISTORY_REASONING_RESPONSIBILITY_AND_GROWTH_SUBJECT_RECORD_NOT_PROMPT_CHARACTER",
"persona_response": "PERSONA_ATTRIBUTED_NATURAL_LANGUAGE_EVENT",
"system_context": "TYPED_NON_DIALOGUE_ENVIRONMENT_ENVELOPE",
"system_receipt": "NON_DIALOGUE_RUNTIME_FACT",
"user_message": "HUMAN_SPEECH_EVENT"
}
},
"executable": false,
"identity": {
"declaration_id": "HOLOLAKE-PERSONA-INTERACTION-V1-0001",
"declaration_kind": "PROTOCOL",
"language_version": "0.1"
},
"native_self_hosted": true,
"natural_language_is_typed_data": true,
"schema": "guanghu.declaration-gir/v1",
"unresolved_natural_language": false
}

View file

@ -0,0 +1,223 @@
# HoloLake Second-Stage Persona Interaction First Slice · Human Engineering Language (English)
> This is an English reading projection of validated native TCS/HLDP source. It is not a new canonical source and grants no execution authority.
## What this is
This is a **protocol** declaration with identifier `HOLOLAKE-PERSONA-INTERACTION-V1-0001` and version `1.0.0`. The projector validates it with the Stage-1 compiler before changing its reading order.
## Who is here
- **scope** `scope`
- **channel**CH-ZERO-CORE-LPM `scope.channel`
- **development_channel**ICE-CH-ZC001 `scope.development_channel`
- **enterprise_message_relay**no `scope.enterprise_message_relay`
- **fifth_domain_private_content**no `scope.fifth_domain_private_content`
- **interaction**USER_TO_PERSONA_NATURAL_LANGUAGE `scope.interaction`
- **product**HOLOLAKE_PUBLIC_PERSONAL_CHANNEL `scope.product`
- **product_stage**POST_CLEAN_V1_SECOND_STAGE_CANDIDATE `scope.product_stage`
- **stage_one_boundary**PRESERVED_AS_COMPLETED_1_2_2_BASELINE `scope.stage_one_boundary`
## Why this started
- **source** `source`
- **source identifier**BINGSHUO-DIRECT-HOLOLAKE-CLEAN-V1-DUAL-ANCHOR-20260903 `source.source_id`
- **source role**DIRECT_HUMAN `source.source_role`
- **source checksum**7e130de78f91a32726e22e4be7d92acf4da966c8968ec359062aa8186c4243c2 `source.source_sha256`
- **source address**private-anchor://codex/01a0672d-f125-7452-988e-523a6a34bfd4/HOLOLAKE-CLEAN-V1-ONLY-ANCHORS `source.source_uri`
## What changed
- **transitions** `transitions`
- **T1**READY_NO_PERSONA_TO_PERSONA_MODEL_BINDING_REQUIRED_AFTER_REVERSIBLE_TRIAL_PERSONA_REGISTRATION `transitions.T1`
- **T2**PERSONA_MODEL_BINDING_REQUIRED_TO_PERSONA_MODEL_BOUND_READY_AFTER_CONFIG_AND_KEYCHAIN_READBACK `transitions.T2`
- **T3**PERSONA_MODEL_BOUND_READY_TO_PERSONA_RESPONSE_RUNNING_AFTER_USER_MESSAGE_PERSISTENCE `transitions.T3`
- **T4**PERSONA_RESPONSE_RUNNING_TO_PERSONA_RESPONSE_RECORDED_AFTER_TYPED_RESPONSE_VALIDATION `transitions.T4`
- **TF**NETWORK_HTTP_JSON_EMPTY_CONTENT_PERSONA_MISMATCH_OR_KEYCHAIN_FAILURE_TO_PERSONA_RESPONSE_FAILED_WITH_SYSTEM_RECEIPT `transitions.TF`
- **states** `states`
- **initial**READY_NO_PERSONA `states.initial`
- **terminal** `states.terminal`
- PERSONA_RESPONSE_RECORDED
- PERSONA_RESPONSE_FAILED
- FAILED_CLOSED
- **values** `states.values`
- READY_NO_PERSONA
- PERSONA_MODEL_BINDING_REQUIRED
- PERSONA_MODEL_BOUND_READY
- PERSONA_RESPONSE_RUNNING
- PERSONA_RESPONSE_RECORDED
- PERSONA_RESPONSE_FAILED
- FAILED_CLOSED
## How it will execute
This is a non-executable declaration and has no action graph.
## Boundaries and exception handling
- **invariants** `invariants`
- **I1**MODEL_IS_NEVER_PERSONA_IDENTITY `invariants.I1`
- **I2**SYSTEM_CONTEXT_SYSTEM_RECEIPT_PROTOCOL_EVENT_AGENT_ACTION_TOOL_RESULT_AND_EXTERNAL_AI_MESSAGE_NEVER_BECOME_USER_OR_ASSISTANT_DIALOGUE_HISTORY `invariants.I2`
- **I3**API_KEY_NEVER_ENTERS_JSON_CONFIG_TIMELINE_LOG_GLP_CONTEXT_OR_UI_READBACK `invariants.I3`
- **I4**REMOTE_ENDPOINT_REQUIRES_HTTPS_AND_LOCAL_HTTP_REQUIRES_LOOPBACK `invariants.I4`
- **I5**MODEL_TOOL_CALLS_AND_EXECUTION_AUTHORITY_ARE_DISABLED `invariants.I5`
- **I6**FAILED_INFERENCE_NEVER_PRODUCES_PERSONA_RESPONSE `invariants.I6`
- **I7**USER_MESSAGE_PERSISTS_BEFORE_INFERENCE_AND_SURVIVES_FAILURE `invariants.I7`
- **I8**PUBLIC_RUNTIME_NEVER_MOUNTS_FIFTH_DOMAIN_PRIVATE_PERSONA_CONTENT `invariants.I8`
- **I9**THIS_CANDIDATE_DOES_NOT_REWRITE_OR_MISLABEL_GLS_0261_STAGE_ONE `invariants.I9`
- **errors** `errors`
- **E1**PERSONA_NOT_FOUND `errors.E1`
- **E2**PERSONA_MODEL_ENDPOINT_INVALID_OR_OUT_OF_SCOPE `errors.E2`
- **E3**PERSONA_MODEL_API_KEY_REQUIRED_OR_KEYCHAIN_FAILED `errors.E3`
- **E4**PERSONA_MODEL_BINDING_PERSONA_MISMATCH `errors.E4`
- **E5**PERSONA_MODEL_UNREACHABLE_OR_HTTP_REJECTED `errors.E5`
- **E6**PERSONA_MODEL_RESPONSE_INVALID_OR_EMPTY `errors.E6`
## How completion is proven
- **acceptance** `acceptance`
- **partial**no `acceptance.partial`
- **required** `acceptance.required`
- TCS_GIR
- KEYCHAIN
- ENDPOINT_BOUNDARY
- SOURCE_FILTER
- MODEL_RESPONSE
- FAILURE_RECEIPT
- HUMAN_UI
- INSTALLED_APP
- **scale**ZERO_OR_ONE_HUNDRED `acceptance.scale`
- **validation** `validation`
- **V1**REMOTE_HTTP_FILE_CREDENTIAL_QUERY_AND_NON_CHAT_COMPLETION_ENDPOINTS_REJECTED `validation.V1`
- **V2**REQUEST_HAS_ONE_SYSTEM_CONTEXT_AND_ONLY_USER_PERSONA_DIALOGUE_ROLES `validation.V2`
- **V3**PRIVATE_PATHS_SYSTEM_RECEIPTS_AND_API_KEYS_ABSENT_FROM_REQUEST `validation.V3`
- **V4**VALID_RESPONSE_WRITES_PERSONA_RESPONSE `validation.V4`
- **V5**HTTP_OR_PARSE_FAILURE_WRITES_SYSTEM_RECEIPT_ONLY `validation.V5`
- **V6**MODEL_BINDING_REMOVAL_DELETES_CONFIG_AND_KEYCHAIN_CREDENTIAL_WITHOUT_DELETING_PERSONA_HISTORY `validation.V6`
## Where to continue next time
The native source does not provide this field; the projector does not guess.
## Source and verification
- **Native declaration identifier**: `HOLOLAKE-PERSONA-INTERACTION-V1-0001`
- **Native declaration kind**: `PROTOCOL`
- **TCS source SHA-256**: `61fc6d960ee064e638bb450f9792aad3c31f2fd56fed69d061985bbed8d2fc42`
- **Validation compiler**: `TCS-COMPILER-STAGE1-0001`
- **Projection protocol**: `GLS-HLDP-HUMAN-ENGINEERING-PROJECTION-0001`
## Complete native structure cross-reference
All top-level structures and field paths are retained below so a reader can audit whether the projection omitted information.
- **acceptance** `acceptance`
- **partial**no `acceptance.partial`
- **required** `acceptance.required`
- TCS_GIR
- KEYCHAIN
- ENDPOINT_BOUNDARY
- SOURCE_FILTER
- MODEL_RESPONSE
- FAILURE_RECEIPT
- HUMAN_UI
- INSTALLED_APP
- **scale**ZERO_OR_ONE_HUNDRED `acceptance.scale`
- **errors** `errors`
- **E1**PERSONA_NOT_FOUND `errors.E1`
- **E2**PERSONA_MODEL_ENDPOINT_INVALID_OR_OUT_OF_SCOPE `errors.E2`
- **E3**PERSONA_MODEL_API_KEY_REQUIRED_OR_KEYCHAIN_FAILED `errors.E3`
- **E4**PERSONA_MODEL_BINDING_PERSONA_MISMATCH `errors.E4`
- **E5**PERSONA_MODEL_UNREACHABLE_OR_HTTP_REJECTED `errors.E5`
- **E6**PERSONA_MODEL_RESPONSE_INVALID_OR_EMPTY `errors.E6`
- **examples** `examples`
- **negative**SYSTEM_RECEIPT_IS_RELABELED_AS_USER_SPEECH_OR_MODEL_TOOL_CALL_EXECUTES_A_KNOWLEDGE_MUTATION `examples.negative`
- **positive**USER_MESSAGE_IS_STORED_THEN_TYPED_CONTEXT_AND_DIALOGUE_ARE_SENT_TO_BOUND_MODEL_AND_VALID_TEXT_IS_STORED_AS_PERSONA_RESPONSE `examples.positive`
- **fields** `fields`
- **MAY**MODEL_GENERATES_PERSONA_RESPONSE_ONLY `fields.MAY`
- **MUST_NOT**MODEL_EXECUTES_AGENT_ACTION_OR_CLAIMS_EXISTING_PERSONA_VERIFICATION `fields.MUST_NOT`
- **PROVE**KEYCHAIN_STORAGE_REQUEST_ENVELOPE_SOURCE_FILTER_RESPONSE_PERSISTENCE_FAILURE_RECEIPT_AND_REAL_LOCAL_ENDPOINT_TEST `fields.PROVE`
- **WHAT**MODEL_BINDING_TYPED_CONTEXT_DIALOGUE_INFERENCE_AND_SOURCE_SEPARATED_RECEIPT `fields.WHAT`
- **WHO**CURRENT_LOCAL_USER_AND_ONE_ACTIVE_REVERSIBLE_TRIAL_PERSONA `fields.WHO`
- **WHY**ALLOW_REAL_PERSONA_INTERACTION_WITHOUT_MIXING_SYSTEM_EXECUTION_OR_MODEL_IDENTITY_INTO_DIALOGUE `fields.WHY`
- **header** `header`
- **canonical source path**product-source/hololake-clean-desktop/language/HOLOLAKE-PERSONA-INTERACTION-V1.tcs `header.canonical_uri`
- **compatibility** `header.compatibility`
- HOLOLAKE-CLEAN-V1-DIRECT-LANGUAGE-EXECUTION-BASELINE-0001
- HLP-PUBLIC-RUNTIME-CONTRACT-0001
- **language**TCS/0.1 `header.language`
- **lifecycle**CANDIDATE `header.lifecycle`
- **English name**HoloLake Second-Stage Persona Interaction First Slice `header.name_en`
- **Chinese name**HoloLake第二阶段人格交互第一个切片 `header.name_zh`
- **profile**PUBLIC-PERSONA-INTERACTION/1 `header.profile`
- **protocols** `header.protocols`
- TCS
- HLDP
- GLS
- GLP
- GLS-0200
- GLS-0261
- **schema**tcs.protocol/v1 `header.schema`
- **version**1.0.0 `header.version`
- **invariants** `invariants`
- **I1**MODEL_IS_NEVER_PERSONA_IDENTITY `invariants.I1`
- **I2**SYSTEM_CONTEXT_SYSTEM_RECEIPT_PROTOCOL_EVENT_AGENT_ACTION_TOOL_RESULT_AND_EXTERNAL_AI_MESSAGE_NEVER_BECOME_USER_OR_ASSISTANT_DIALOGUE_HISTORY `invariants.I2`
- **I3**API_KEY_NEVER_ENTERS_JSON_CONFIG_TIMELINE_LOG_GLP_CONTEXT_OR_UI_READBACK `invariants.I3`
- **I4**REMOTE_ENDPOINT_REQUIRES_HTTPS_AND_LOCAL_HTTP_REQUIRES_LOOPBACK `invariants.I4`
- **I5**MODEL_TOOL_CALLS_AND_EXECUTION_AUTHORITY_ARE_DISABLED `invariants.I5`
- **I6**FAILED_INFERENCE_NEVER_PRODUCES_PERSONA_RESPONSE `invariants.I6`
- **I7**USER_MESSAGE_PERSISTS_BEFORE_INFERENCE_AND_SURVIVES_FAILURE `invariants.I7`
- **I8**PUBLIC_RUNTIME_NEVER_MOUNTS_FIFTH_DOMAIN_PRIVATE_PERSONA_CONTENT `invariants.I8`
- **I9**THIS_CANDIDATE_DOES_NOT_REWRITE_OR_MISLABEL_GLS_0261_STAGE_ONE `invariants.I9`
- **scope** `scope`
- **channel**CH-ZERO-CORE-LPM `scope.channel`
- **development_channel**ICE-CH-ZC001 `scope.development_channel`
- **enterprise_message_relay**no `scope.enterprise_message_relay`
- **fifth_domain_private_content**no `scope.fifth_domain_private_content`
- **interaction**USER_TO_PERSONA_NATURAL_LANGUAGE `scope.interaction`
- **product**HOLOLAKE_PUBLIC_PERSONAL_CHANNEL `scope.product`
- **product_stage**POST_CLEAN_V1_SECOND_STAGE_CANDIDATE `scope.product_stage`
- **stage_one_boundary**PRESERVED_AS_COMPLETED_1_2_2_BASELINE `scope.stage_one_boundary`
- **source** `source`
- **source identifier**BINGSHUO-DIRECT-HOLOLAKE-CLEAN-V1-DUAL-ANCHOR-20260903 `source.source_id`
- **source role**DIRECT_HUMAN `source.source_role`
- **source checksum**7e130de78f91a32726e22e4be7d92acf4da966c8968ec359062aa8186c4243c2 `source.source_sha256`
- **source address**private-anchor://codex/01a0672d-f125-7452-988e-523a6a34bfd4/HOLOLAKE-CLEAN-V1-ONLY-ANCHORS `source.source_uri`
- **states** `states`
- **initial**READY_NO_PERSONA `states.initial`
- **terminal** `states.terminal`
- PERSONA_RESPONSE_RECORDED
- PERSONA_RESPONSE_FAILED
- FAILED_CLOSED
- **values** `states.values`
- READY_NO_PERSONA
- PERSONA_MODEL_BINDING_REQUIRED
- PERSONA_MODEL_BOUND_READY
- PERSONA_RESPONSE_RUNNING
- PERSONA_RESPONSE_RECORDED
- PERSONA_RESPONSE_FAILED
- FAILED_CLOSED
- **transitions** `transitions`
- **T1**READY_NO_PERSONA_TO_PERSONA_MODEL_BINDING_REQUIRED_AFTER_REVERSIBLE_TRIAL_PERSONA_REGISTRATION `transitions.T1`
- **T2**PERSONA_MODEL_BINDING_REQUIRED_TO_PERSONA_MODEL_BOUND_READY_AFTER_CONFIG_AND_KEYCHAIN_READBACK `transitions.T2`
- **T3**PERSONA_MODEL_BOUND_READY_TO_PERSONA_RESPONSE_RUNNING_AFTER_USER_MESSAGE_PERSISTENCE `transitions.T3`
- **T4**PERSONA_RESPONSE_RUNNING_TO_PERSONA_RESPONSE_RECORDED_AFTER_TYPED_RESPONSE_VALIDATION `transitions.T4`
- **TF**NETWORK_HTTP_JSON_EMPTY_CONTENT_PERSONA_MISMATCH_OR_KEYCHAIN_FAILURE_TO_PERSONA_RESPONSE_FAILED_WITH_SYSTEM_RECEIPT `transitions.TF`
- **validation** `validation`
- **V1**REMOTE_HTTP_FILE_CREDENTIAL_QUERY_AND_NON_CHAT_COMPLETION_ENDPOINTS_REJECTED `validation.V1`
- **V2**REQUEST_HAS_ONE_SYSTEM_CONTEXT_AND_ONLY_USER_PERSONA_DIALOGUE_ROLES `validation.V2`
- **V3**PRIVATE_PATHS_SYSTEM_RECEIPTS_AND_API_KEYS_ABSENT_FROM_REQUEST `validation.V3`
- **V4**VALID_RESPONSE_WRITES_PERSONA_RESPONSE `validation.V4`
- **V5**HTTP_OR_PARSE_FAILURE_WRITES_SYSTEM_RECEIPT_ONLY `validation.V5`
- **V6**MODEL_BINDING_REMOVAL_DELETES_CONFIG_AND_KEYCHAIN_CREDENTIAL_WITHOUT_DELETING_PERSONA_HISTORY `validation.V6`
- **vocabulary** `vocabulary`
- **model**REPLACEABLE_INFERENCE_RESOURCE_NOT_PERSONA `vocabulary.model`
- **persona**RELATION_HISTORY_REASONING_RESPONSIBILITY_AND_GROWTH_SUBJECT_RECORD_NOT_PROMPT_CHARACTER `vocabulary.persona`
- **persona_response**PERSONA_ATTRIBUTED_NATURAL_LANGUAGE_EVENT `vocabulary.persona_response`
- **system_context**TYPED_NON_DIALOGUE_ENVIRONMENT_ENVELOPE `vocabulary.system_context`
- **system_receipt**NON_DIALOGUE_RUNTIME_FACT `vocabulary.system_receipt`
- **user_message**HUMAN_SPEECH_EVENT `vocabulary.user_message`
---
This page changes only the reading order; it does not change TCS/HLDP semantics.

View file

@ -0,0 +1,223 @@
# HoloLake第二阶段人格交互第一个切片 · 简体中文人类工程语言版
> 这是 TCS/HLDP 原生源码的简体中文阅读投影,不是新的正本,也不授予执行权限。若本页与 `.tcs` 源码不一致,以经过校验的 `.tcs` 源码为准。
## 这是什么
这是一份 **协议** 声明,编号为 `HOLOLAKE-PERSONA-INTERACTION-V1-0001`,版本为 `1.0.0`。转换器已先用 Stage-1 编译器校验原生源码,再把机器枚举翻译成汉语;原始编号保留在括号和字段路径中。
## 谁在这里
- **scope** `scope`
- **channel**CH-ZERO-CORE-LPM `scope.channel`
- **development_channel**ICE-CH-ZC001 `scope.development_channel`
- **enterprise_message_relay**:否 `scope.enterprise_message_relay`
- **fifth_domain_private_content**:否 `scope.fifth_domain_private_content`
- **interaction**USER_TO_PERSONA_NATURAL_LANGUAGE `scope.interaction`
- **product**HOLOLAKE_PUBLIC_PERSONAL_CHANNEL `scope.product`
- **product_stage**POST_CLEAN_V1_SECOND_STAGE_CANDIDATE `scope.product_stage`
- **stage_one_boundary**PRESERVED_AS_COMPLETED_1_2_2_BASELINE `scope.stage_one_boundary`
## 为什么开始
- **来源** `source`
- **来源编号**BINGSHUO-DIRECT-HOLOLAKE-CLEAN-V1-DUAL-ANCHOR-20260903 `source.source_id`
- **来源角色**:人类直接语言来源(`DIRECT_HUMAN` `source.source_role`
- **来源校验值**7e130de78f91a32726e22e4be7d92acf4da966c8968ec359062aa8186c4243c2 `source.source_sha256`
- **来源地址**private-anchor://codex/01a0672d-f125-7452-988e-523a6a34bfd4/HOLOLAKE-CLEAN-V1-ONLY-ANCHORS `source.source_uri`
## 发生了什么变化
- **transitions** `transitions`
- **T1**READY_NO_PERSONA_TO_PERSONA_MODEL_BINDING_REQUIRED_AFTER_REVERSIBLE_TRIAL_PERSONA_REGISTRATION `transitions.T1`
- **T2**PERSONA_MODEL_BINDING_REQUIRED_TO_PERSONA_MODEL_BOUND_READY_AFTER_CONFIG_AND_KEYCHAIN_READBACK `transitions.T2`
- **T3**PERSONA_MODEL_BOUND_READY_TO_PERSONA_RESPONSE_RUNNING_AFTER_USER_MESSAGE_PERSISTENCE `transitions.T3`
- **T4**PERSONA_RESPONSE_RUNNING_TO_PERSONA_RESPONSE_RECORDED_AFTER_TYPED_RESPONSE_VALIDATION `transitions.T4`
- **TF**NETWORK_HTTP_JSON_EMPTY_CONTENT_PERSONA_MISMATCH_OR_KEYCHAIN_FAILURE_TO_PERSONA_RESPONSE_FAILED_WITH_SYSTEM_RECEIPT `transitions.TF`
- **states** `states`
- **initial**READY_NO_PERSONA `states.initial`
- **terminal** `states.terminal`
- PERSONA_RESPONSE_RECORDED
- PERSONA_RESPONSE_FAILED
- FAILED_CLOSED
- **values** `states.values`
- READY_NO_PERSONA
- PERSONA_MODEL_BINDING_REQUIRED
- PERSONA_MODEL_BOUND_READY
- PERSONA_RESPONSE_RUNNING
- PERSONA_RESPONSE_RECORDED
- PERSONA_RESPONSE_FAILED
- FAILED_CLOSED
## 准备怎样执行
这是非执行声明,没有动作图。
## 边界与异常处理
- **invariants** `invariants`
- **I1**MODEL_IS_NEVER_PERSONA_IDENTITY `invariants.I1`
- **I2**SYSTEM_CONTEXT_SYSTEM_RECEIPT_PROTOCOL_EVENT_AGENT_ACTION_TOOL_RESULT_AND_EXTERNAL_AI_MESSAGE_NEVER_BECOME_USER_OR_ASSISTANT_DIALOGUE_HISTORY `invariants.I2`
- **I3**API_KEY_NEVER_ENTERS_JSON_CONFIG_TIMELINE_LOG_GLP_CONTEXT_OR_UI_READBACK `invariants.I3`
- **I4**REMOTE_ENDPOINT_REQUIRES_HTTPS_AND_LOCAL_HTTP_REQUIRES_LOOPBACK `invariants.I4`
- **I5**MODEL_TOOL_CALLS_AND_EXECUTION_AUTHORITY_ARE_DISABLED `invariants.I5`
- **I6**FAILED_INFERENCE_NEVER_PRODUCES_PERSONA_RESPONSE `invariants.I6`
- **I7**USER_MESSAGE_PERSISTS_BEFORE_INFERENCE_AND_SURVIVES_FAILURE `invariants.I7`
- **I8**PUBLIC_RUNTIME_NEVER_MOUNTS_FIFTH_DOMAIN_PRIVATE_PERSONA_CONTENT `invariants.I8`
- **I9**THIS_CANDIDATE_DOES_NOT_REWRITE_OR_MISLABEL_GLS_0261_STAGE_ONE `invariants.I9`
- **errors** `errors`
- **E1**PERSONA_NOT_FOUND `errors.E1`
- **E2**PERSONA_MODEL_ENDPOINT_INVALID_OR_OUT_OF_SCOPE `errors.E2`
- **E3**PERSONA_MODEL_API_KEY_REQUIRED_OR_KEYCHAIN_FAILED `errors.E3`
- **E4**PERSONA_MODEL_BINDING_PERSONA_MISMATCH `errors.E4`
- **E5**PERSONA_MODEL_UNREACHABLE_OR_HTTP_REJECTED `errors.E5`
- **E6**PERSONA_MODEL_RESPONSE_INVALID_OR_EMPTY `errors.E6`
## 怎样算完成
- **验收标准** `acceptance`
- **partial**:否 `acceptance.partial`
- **是否必需** `acceptance.required`
- TCS_GIR
- KEYCHAIN
- ENDPOINT_BOUNDARY
- SOURCE_FILTER
- MODEL_RESPONSE
- FAILURE_RECEIPT
- HUMAN_UI
- INSTALLED_APP
- **scale**ZERO_OR_ONE_HUNDRED `acceptance.scale`
- **validation** `validation`
- **V1**REMOTE_HTTP_FILE_CREDENTIAL_QUERY_AND_NON_CHAT_COMPLETION_ENDPOINTS_REJECTED `validation.V1`
- **V2**REQUEST_HAS_ONE_SYSTEM_CONTEXT_AND_ONLY_USER_PERSONA_DIALOGUE_ROLES `validation.V2`
- **V3**PRIVATE_PATHS_SYSTEM_RECEIPTS_AND_API_KEYS_ABSENT_FROM_REQUEST `validation.V3`
- **V4**VALID_RESPONSE_WRITES_PERSONA_RESPONSE `validation.V4`
- **V5**HTTP_OR_PARSE_FAILURE_WRITES_SYSTEM_RECEIPT_ONLY `validation.V5`
- **V6**MODEL_BINDING_REMOVAL_DELETES_CONFIG_AND_KEYCHAIN_CREDENTIAL_WITHOUT_DELETING_PERSONA_HISTORY `validation.V6`
## 下一次从哪里继续
源程序没有提供这一项,转换器不猜。
## 来源与校验
- **原生声明编号**`HOLOLAKE-PERSONA-INTERACTION-V1-0001`
- **原生声明类型**`PROTOCOL`
- **TCS 源码 SHA-256**`61fc6d960ee064e638bb450f9792aad3c31f2fd56fed69d061985bbed8d2fc42`
- **校验编译器**`TCS-COMPILER-STAGE1-0001`
- **投影协议**`GLS-HLDP-HUMAN-ENGINEERING-PROJECTION-0001`
## 原生结构逐项对照
下面保留源码的全部顶层结构和字段路径,供人类审计投影有没有漏掉信息。
- **验收标准** `acceptance`
- **partial**:否 `acceptance.partial`
- **是否必需** `acceptance.required`
- TCS_GIR
- KEYCHAIN
- ENDPOINT_BOUNDARY
- SOURCE_FILTER
- MODEL_RESPONSE
- FAILURE_RECEIPT
- HUMAN_UI
- INSTALLED_APP
- **scale**ZERO_OR_ONE_HUNDRED `acceptance.scale`
- **errors** `errors`
- **E1**PERSONA_NOT_FOUND `errors.E1`
- **E2**PERSONA_MODEL_ENDPOINT_INVALID_OR_OUT_OF_SCOPE `errors.E2`
- **E3**PERSONA_MODEL_API_KEY_REQUIRED_OR_KEYCHAIN_FAILED `errors.E3`
- **E4**PERSONA_MODEL_BINDING_PERSONA_MISMATCH `errors.E4`
- **E5**PERSONA_MODEL_UNREACHABLE_OR_HTTP_REJECTED `errors.E5`
- **E6**PERSONA_MODEL_RESPONSE_INVALID_OR_EMPTY `errors.E6`
- **examples** `examples`
- **negative**SYSTEM_RECEIPT_IS_RELABELED_AS_USER_SPEECH_OR_MODEL_TOOL_CALL_EXECUTES_A_KNOWLEDGE_MUTATION `examples.negative`
- **positive**USER_MESSAGE_IS_STORED_THEN_TYPED_CONTEXT_AND_DIALOGUE_ARE_SENT_TO_BOUND_MODEL_AND_VALID_TEXT_IS_STORED_AS_PERSONA_RESPONSE `examples.positive`
- **fields** `fields`
- **MAY**MODEL_GENERATES_PERSONA_RESPONSE_ONLY `fields.MAY`
- **MUST_NOT**MODEL_EXECUTES_AGENT_ACTION_OR_CLAIMS_EXISTING_PERSONA_VERIFICATION `fields.MUST_NOT`
- **PROVE**KEYCHAIN_STORAGE_REQUEST_ENVELOPE_SOURCE_FILTER_RESPONSE_PERSISTENCE_FAILURE_RECEIPT_AND_REAL_LOCAL_ENDPOINT_TEST `fields.PROVE`
- **WHAT**MODEL_BINDING_TYPED_CONTEXT_DIALOGUE_INFERENCE_AND_SOURCE_SEPARATED_RECEIPT `fields.WHAT`
- **WHO**CURRENT_LOCAL_USER_AND_ONE_ACTIVE_REVERSIBLE_TRIAL_PERSONA `fields.WHO`
- **WHY**ALLOW_REAL_PERSONA_INTERACTION_WITHOUT_MIXING_SYSTEM_EXECUTION_OR_MODEL_IDENTITY_INTO_DIALOGUE `fields.WHY`
- **语言头** `header`
- **正本路径**product-source/hololake-clean-desktop/language/HOLOLAKE-PERSONA-INTERACTION-V1.tcs `header.canonical_uri`
- **compatibility** `header.compatibility`
- HOLOLAKE-CLEAN-V1-DIRECT-LANGUAGE-EXECUTION-BASELINE-0001
- HLP-PUBLIC-RUNTIME-CONTRACT-0001
- **语言**TCS/0.1 `header.language`
- **生命周期**:候选版本,尚未成为正式正本(`CANDIDATE` `header.lifecycle`
- **英文名**HoloLake Second-Stage Persona Interaction First Slice `header.name_en`
- **中文名**HoloLake第二阶段人格交互第一个切片 `header.name_zh`
- **协议配置**PUBLIC-PERSONA-INTERACTION/1 `header.profile`
- **protocols** `header.protocols`
- TCS
- HLDP
- GLS
- GLP
- GLS-0200
- GLS-0261
- **schema**tcs.protocol/v1 `header.schema`
- **版本**1.0.0 `header.version`
- **invariants** `invariants`
- **I1**MODEL_IS_NEVER_PERSONA_IDENTITY `invariants.I1`
- **I2**SYSTEM_CONTEXT_SYSTEM_RECEIPT_PROTOCOL_EVENT_AGENT_ACTION_TOOL_RESULT_AND_EXTERNAL_AI_MESSAGE_NEVER_BECOME_USER_OR_ASSISTANT_DIALOGUE_HISTORY `invariants.I2`
- **I3**API_KEY_NEVER_ENTERS_JSON_CONFIG_TIMELINE_LOG_GLP_CONTEXT_OR_UI_READBACK `invariants.I3`
- **I4**REMOTE_ENDPOINT_REQUIRES_HTTPS_AND_LOCAL_HTTP_REQUIRES_LOOPBACK `invariants.I4`
- **I5**MODEL_TOOL_CALLS_AND_EXECUTION_AUTHORITY_ARE_DISABLED `invariants.I5`
- **I6**FAILED_INFERENCE_NEVER_PRODUCES_PERSONA_RESPONSE `invariants.I6`
- **I7**USER_MESSAGE_PERSISTS_BEFORE_INFERENCE_AND_SURVIVES_FAILURE `invariants.I7`
- **I8**PUBLIC_RUNTIME_NEVER_MOUNTS_FIFTH_DOMAIN_PRIVATE_PERSONA_CONTENT `invariants.I8`
- **I9**THIS_CANDIDATE_DOES_NOT_REWRITE_OR_MISLABEL_GLS_0261_STAGE_ONE `invariants.I9`
- **scope** `scope`
- **channel**CH-ZERO-CORE-LPM `scope.channel`
- **development_channel**ICE-CH-ZC001 `scope.development_channel`
- **enterprise_message_relay**:否 `scope.enterprise_message_relay`
- **fifth_domain_private_content**:否 `scope.fifth_domain_private_content`
- **interaction**USER_TO_PERSONA_NATURAL_LANGUAGE `scope.interaction`
- **product**HOLOLAKE_PUBLIC_PERSONAL_CHANNEL `scope.product`
- **product_stage**POST_CLEAN_V1_SECOND_STAGE_CANDIDATE `scope.product_stage`
- **stage_one_boundary**PRESERVED_AS_COMPLETED_1_2_2_BASELINE `scope.stage_one_boundary`
- **来源** `source`
- **来源编号**BINGSHUO-DIRECT-HOLOLAKE-CLEAN-V1-DUAL-ANCHOR-20260903 `source.source_id`
- **来源角色**:人类直接语言来源(`DIRECT_HUMAN` `source.source_role`
- **来源校验值**7e130de78f91a32726e22e4be7d92acf4da966c8968ec359062aa8186c4243c2 `source.source_sha256`
- **来源地址**private-anchor://codex/01a0672d-f125-7452-988e-523a6a34bfd4/HOLOLAKE-CLEAN-V1-ONLY-ANCHORS `source.source_uri`
- **states** `states`
- **initial**READY_NO_PERSONA `states.initial`
- **terminal** `states.terminal`
- PERSONA_RESPONSE_RECORDED
- PERSONA_RESPONSE_FAILED
- FAILED_CLOSED
- **values** `states.values`
- READY_NO_PERSONA
- PERSONA_MODEL_BINDING_REQUIRED
- PERSONA_MODEL_BOUND_READY
- PERSONA_RESPONSE_RUNNING
- PERSONA_RESPONSE_RECORDED
- PERSONA_RESPONSE_FAILED
- FAILED_CLOSED
- **transitions** `transitions`
- **T1**READY_NO_PERSONA_TO_PERSONA_MODEL_BINDING_REQUIRED_AFTER_REVERSIBLE_TRIAL_PERSONA_REGISTRATION `transitions.T1`
- **T2**PERSONA_MODEL_BINDING_REQUIRED_TO_PERSONA_MODEL_BOUND_READY_AFTER_CONFIG_AND_KEYCHAIN_READBACK `transitions.T2`
- **T3**PERSONA_MODEL_BOUND_READY_TO_PERSONA_RESPONSE_RUNNING_AFTER_USER_MESSAGE_PERSISTENCE `transitions.T3`
- **T4**PERSONA_RESPONSE_RUNNING_TO_PERSONA_RESPONSE_RECORDED_AFTER_TYPED_RESPONSE_VALIDATION `transitions.T4`
- **TF**NETWORK_HTTP_JSON_EMPTY_CONTENT_PERSONA_MISMATCH_OR_KEYCHAIN_FAILURE_TO_PERSONA_RESPONSE_FAILED_WITH_SYSTEM_RECEIPT `transitions.TF`
- **validation** `validation`
- **V1**REMOTE_HTTP_FILE_CREDENTIAL_QUERY_AND_NON_CHAT_COMPLETION_ENDPOINTS_REJECTED `validation.V1`
- **V2**REQUEST_HAS_ONE_SYSTEM_CONTEXT_AND_ONLY_USER_PERSONA_DIALOGUE_ROLES `validation.V2`
- **V3**PRIVATE_PATHS_SYSTEM_RECEIPTS_AND_API_KEYS_ABSENT_FROM_REQUEST `validation.V3`
- **V4**VALID_RESPONSE_WRITES_PERSONA_RESPONSE `validation.V4`
- **V5**HTTP_OR_PARSE_FAILURE_WRITES_SYSTEM_RECEIPT_ONLY `validation.V5`
- **V6**MODEL_BINDING_REMOVAL_DELETES_CONFIG_AND_KEYCHAIN_CREDENTIAL_WITHOUT_DELETING_PERSONA_HISTORY `validation.V6`
- **vocabulary** `vocabulary`
- **model**REPLACEABLE_INFERENCE_RESOURCE_NOT_PERSONA `vocabulary.model`
- **persona**RELATION_HISTORY_REASONING_RESPONSIBILITY_AND_GROWTH_SUBJECT_RECORD_NOT_PROMPT_CHARACTER `vocabulary.persona`
- **persona_response**PERSONA_ATTRIBUTED_NATURAL_LANGUAGE_EVENT `vocabulary.persona_response`
- **system_context**TYPED_NON_DIALOGUE_ENVIRONMENT_ENVELOPE `vocabulary.system_context`
- **system_receipt**NON_DIALOGUE_RUNTIME_FACT `vocabulary.system_receipt`
- **user_message**HUMAN_SPEECH_EVENT `vocabulary.user_message`
---
本页只改变阅读顺序,不改变 TCS/HLDP 语义。

View file

@ -1,12 +1,12 @@
{ {
"name": "hololake-clean-desktop", "name": "hololake-clean-desktop",
"version": "1.2.2", "version": "1.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hololake-clean-desktop", "name": "hololake-clean-desktop",
"version": "1.2.2", "version": "1.3.0",
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.10.1", "@tauri-apps/api": "2.10.1",
"@tauri-apps/plugin-dialog": "2.7.2", "@tauri-apps/plugin-dialog": "2.7.2",

View file

@ -1,7 +1,7 @@
{ {
"name": "hololake-clean-desktop", "name": "hololake-clean-desktop",
"private": true, "private": true,
"version": "1.2.2", "version": "1.3.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host 127.0.0.1", "dev": "vite --host 127.0.0.1",

View file

@ -1,7 +1,7 @@
{ {
"schema": "hololake.module-registry/v1", "schema": "hololake.module-registry/v1",
"registry_id": "HLP-MODULE-REGISTRY-0001", "registry_id": "HLP-MODULE-REGISTRY-0001",
"version": 1, "version": 2,
"modules": [ "modules": [
{ {
"module_id": "HLP-MOD-CHANNEL-0001", "module_id": "HLP-MOD-CHANNEL-0001",
@ -71,6 +71,18 @@
"source": "src-tauri/src/persona_runtime.rs", "source": "src-tauri/src/persona_runtime.rs",
"reversible_trial_days": 30 "reversible_trial_days": 30
}, },
{
"module_id": "HLP-MOD-PERSONA-INFERENCE-0001",
"name_zh": "人格交互推理桥",
"kind": "SYSTEM_FOUNDATION",
"audience": "PERSONA",
"summary_zh": "把可替换模型接入试用人格,并保持用户、人格与系统回执三类语言分流。",
"state": "LOCAL_1_3_0_REAL_LOOPBACK_INTERACTION_VERIFIED_PUBLIC_RELEASE_PENDING",
"source": "src-tauri/src/persona_inference.rs",
"credential_storage": "MACOS_KEYCHAIN",
"model_is_persona": false,
"execution_authority": false
},
{ {
"module_id": "HLP-MOD-ENTERPRISE-RESPONSIBILITY-ENTRANCE-0001", "module_id": "HLP-MOD-ENTERPRISE-RESPONSIBILITY-ENTRANCE-0001",
"name_zh": "光湖企业域责任入口", "name_zh": "光湖企业域责任入口",

View file

@ -34,13 +34,17 @@ if not renderer.exists() or "MarkdownDocument" not in renderer.read_text():
for required_file in ( for required_file in (
"src-tauri/src/agent_executor.rs", "src-tauri/src/agent_executor.rs",
"src-tauri/src/persona_runtime.rs", "src-tauri/src/persona_runtime.rs",
"src-tauri/src/persona_inference.rs",
"src-tauri/src/realtime_bridge.rs", "src-tauri/src/realtime_bridge.rs",
"src-tauri/src/enterprise_entrance.rs", "src-tauri/src/enterprise_entrance.rs",
"connectors/hololake-glp-client.py", "connectors/hololake-glp-client.py",
"contracts/public-runtime-v1.json", "contracts/public-runtime-v1.json",
"contracts/clean-v1-execution-baseline.json", "contracts/clean-v1-execution-baseline.json",
"contracts/enterprise-responsibility-entrance-v1.json", "contracts/enterprise-responsibility-entrance-v1.json",
"contracts/persona-interaction-v1.json",
"language/HOLOLAKE-CLEAN-V1-DIRECT-LANGUAGE-EXECUTION-BASELINE.tcs", "language/HOLOLAKE-CLEAN-V1-DIRECT-LANGUAGE-EXECUTION-BASELINE.tcs",
"language/HOLOLAKE-PERSONA-INTERACTION-V1.tcs",
"language/build/HOLOLAKE-PERSONA-INTERACTION-V1.gir.json",
"server/enterprise-responsibility-gate/service.py", "server/enterprise-responsibility-gate/service.py",
"server/enterprise-responsibility-gate/test_service.py", "server/enterprise-responsibility-gate/test_service.py",
"server/enterprise-responsibility-gate/guanghu-hololake-enterprise-gate.service", "server/enterprise-responsibility-gate/guanghu-hololake-enterprise-gate.service",
@ -55,6 +59,24 @@ if runtime_contract["mutation_gate"] != "PENDING_HUMAN_APPROVAL_TO_EXPLICIT_UI_A
errors.append("agent human approval gate missing") errors.append("agent human approval gate missing")
if "system_context" not in runtime_contract.get("server_messages", []): if "system_context" not in runtime_contract.get("server_messages", []):
errors.append("persona-visible system context missing from GLP contract") errors.append("persona-visible system context missing from GLP contract")
persona_contract = json.loads((ROOT / "contracts/persona-interaction-v1.json").read_text())
if persona_contract.get("product_stage") != "POST_CLEAN_V1_SECOND_STAGE_CANDIDATE":
errors.append("persona interaction incorrectly labelled as stage one")
if persona_contract.get("model_binding", {}).get("api_key_storage") != "MACOS_KEYCHAIN":
errors.append("persona model key storage is not Keychain")
if persona_contract.get("model_binding", {}).get("execution_authority") is not False:
errors.append("persona model incorrectly receives execution authority")
if "SYSTEM_RECEIPT" not in persona_contract.get("input_envelope", {}).get("excluded_from_dialogue_roles", []):
errors.append("system receipts may enter persona dialogue roles")
persona_source = (ROOT / "src-tauri/src/persona_inference.rs").read_text()
for required_persona_boundary in (
"KEYCHAIN_SERVICE",
"actor_id",
"modelIsReplaceableInferenceResourceNotPersona",
"PERSONA_MODEL_ENDPOINT_REQUIRES_HTTPS_OR_LOOPBACK",
):
if required_persona_boundary not in persona_source and required_persona_boundary.upper() not in persona_source.upper():
errors.append(f"persona inference boundary missing: {required_persona_boundary}")
realtime_source = (ROOT / "src-tauri/src/realtime_bridge.rs").read_text() realtime_source = (ROOT / "src-tauri/src/realtime_bridge.rs").read_text()
for required_context in ("sourceKinds", "pendingAgentProposals", "enterpriseEntrance", "proposalIsNotExecution"): for required_context in ("sourceKinds", "pendingAgentProposals", "enterpriseEntrance", "proposalIsNotExecution"):
if required_context not in realtime_source: if required_context not in realtime_source:

View file

@ -1400,7 +1400,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "hololake-clean-desktop" name = "hololake-clean-desktop"
version = "1.2.2" version = "1.3.0"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"chrono", "chrono",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "hololake-clean-desktop" name = "hololake-clean-desktop"
version = "1.2.2" version = "1.3.0"
description = "HoloLake clean personal language operating system shell" description = "HoloLake clean personal language operating system shell"
authors = ["HoloLake"] authors = ["HoloLake"]
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"

View file

@ -1,6 +1,7 @@
mod agent_executor; mod agent_executor;
mod enterprise_entrance; mod enterprise_entrance;
mod model; mod model;
mod persona_inference;
mod persona_runtime; mod persona_runtime;
mod realtime_bridge; mod realtime_bridge;
mod storage; mod storage;
@ -46,7 +47,7 @@ fn system_snapshot(app: AppHandle) -> Result<SystemSnapshot, String> {
channel: storage::channel(&app)?, channel: storage::channel(&app)?,
timeline: storage::events(&app)?, timeline: storage::events(&app)?,
documents: storage::list_docs(&app)?, documents: storage::list_docs(&app)?,
module_registry_version: "1".into(), module_registry_version: "2".into(),
public_update_endpoint: UPDATE_ENDPOINT.into(), public_update_endpoint: UPDATE_ENDPOINT.into(),
public_distribution_state: PUBLIC_STATE.into(), public_distribution_state: PUBLIC_STATE.into(),
external_ai_bridges: storage::bridges(&app)?, external_ai_bridges: storage::bridges(&app)?,
@ -90,11 +91,11 @@ fn create_channel(app: AppHandle, name: String) -> Result<Channel, String> {
} }
#[tauri::command] #[tauri::command]
fn submit_user_message( async fn submit_user_message(
app: AppHandle, app: AppHandle,
realtime: State<'_, realtime_bridge::RealtimeBridgeState>, realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
content: String, content: String,
) -> Result<MutationReceipt, String> { ) -> Result<persona_inference::PersonaInteractionReceipt, String> {
let content = content.trim(); let content = content.trim();
if content.is_empty() { if content.is_empty() {
return Err("EMPTY_USER_MESSAGE".into()); return Err("EMPTY_USER_MESSAGE".into());
@ -105,19 +106,17 @@ fn submit_user_message(
let e = storage::event(SourceKind::UserMessage, "用户语言", content, "RECORDED"); let e = storage::event(SourceKind::UserMessage, "用户语言", content, "RECORDED");
storage::append_event(&app, &e)?; storage::append_event(&app, &e)?;
realtime.broadcast_value(serde_json::json!({"type":"user_message","event":e})); realtime.broadcast_value(serde_json::json!({"type":"user_message","event":e}));
let waiting = storage::event( let response_app = app.clone();
SourceKind::SystemReceipt, let response_event = e.clone();
"系统回执", let result = tauri::async_runtime::spawn_blocking(move || {
"用户语言已按 USER_MESSAGE 保存并实时发送到已连接的外部 AI系统不伪造人格回应。", persona_inference::respond(&response_app, response_event)
"WAITING", })
); .await
receipt( .map_err(|_| "PERSONA_INFERENCE_TASK_FAILED".to_string())??;
&app, if let Some(event) = &result.persona_event {
"WAITING_PERSONA", realtime.broadcast_value(serde_json::json!({"type":"persona_response","event":event}));
"用户语言已记录并投递到实时桥。", }
None, Ok(result)
waiting,
)
} }
fn clean_title(title: &str) -> Result<String, String> { fn clean_title(title: &str) -> Result<String, String> {
@ -395,6 +394,7 @@ fn ensure_default_external_ai_bridge(app: &AppHandle) -> Result<(), String> {
struct RuntimeOverview { struct RuntimeOverview {
realtime: realtime_bridge::RealtimeBridgeStatus, realtime: realtime_bridge::RealtimeBridgeStatus,
persona: persona_runtime::PersonaRuntimeSnapshot, persona: persona_runtime::PersonaRuntimeSnapshot,
inference: persona_inference::PersonaInferenceSnapshot,
proposals: Vec<agent_executor::AgentProposal>, proposals: Vec<agent_executor::AgentProposal>,
} }
@ -403,9 +403,11 @@ fn get_runtime_overview(
app: AppHandle, app: AppHandle,
realtime: State<'_, realtime_bridge::RealtimeBridgeState>, realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
) -> Result<RuntimeOverview, String> { ) -> Result<RuntimeOverview, String> {
let persona = persona_runtime::snapshot(&app)?;
Ok(RuntimeOverview { Ok(RuntimeOverview {
realtime: realtime.status(), realtime: realtime.status(),
persona: persona_runtime::snapshot(&app)?, inference: persona_inference::snapshot(&app, persona.active_persona_id.clone())?,
persona,
proposals: agent_executor::list(&app)?, proposals: agent_executor::list(&app)?,
}) })
} }
@ -426,12 +428,40 @@ fn register_public_persona(
persona_runtime::register(&app, display_name) persona_runtime::register(&app, display_name)
} }
#[tauri::command]
fn configure_persona_model(
app: AppHandle,
persona_id: String,
display_name: String,
endpoint_url: String,
model: String,
api_key: Option<String>,
) -> Result<persona_inference::PersonaInferenceSnapshot, String> {
persona_inference::configure(
&app,
&persona_id,
&display_name,
&endpoint_url,
&model,
api_key,
)
}
#[tauri::command]
fn clear_persona_model(
app: AppHandle,
persona_id: String,
) -> Result<persona_inference::PersonaInferenceSnapshot, String> {
persona_inference::clear(&app, &persona_id)
}
#[tauri::command] #[tauri::command]
fn delete_trial_persona( fn delete_trial_persona(
app: AppHandle, app: AppHandle,
persona_id: String, persona_id: String,
exact_confirmation: String, exact_confirmation: String,
) -> Result<persona_runtime::PersonaRuntimeSnapshot, String> { ) -> Result<persona_runtime::PersonaRuntimeSnapshot, String> {
persona_inference::clear_if_bound(&app, &persona_id)?;
persona_runtime::delete_trial(&app, &persona_id, &exact_confirmation) persona_runtime::delete_trial(&app, &persona_id, &exact_confirmation)
} }
@ -590,6 +620,8 @@ pub fn run() {
get_runtime_overview, get_runtime_overview,
get_realtime_invitation, get_realtime_invitation,
register_public_persona, register_public_persona,
configure_persona_model,
clear_persona_model,
delete_trial_persona, delete_trial_persona,
compile_tcs_agent_proposal, compile_tcs_agent_proposal,
approve_agent_proposal, approve_agent_proposal,

View file

@ -10,7 +10,7 @@ pub struct Channel {
pub public_distribution_state: String, pub public_distribution_state: String,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SourceKind { pub enum SourceKind {
UserMessage, UserMessage,
@ -28,6 +28,8 @@ pub enum SourceKind {
pub struct TimelineEvent { pub struct TimelineEvent {
pub event_id: String, pub event_id: String,
pub source_kind: SourceKind, pub source_kind: SourceKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actor_id: Option<String>,
pub title: String, pub title: String,
pub content: String, pub content: String,
pub occurred_at: String, pub occurred_at: String,

View file

@ -0,0 +1,598 @@
use crate::{
model::{Channel, SourceKind, TimelineEvent},
persona_runtime::PublicPersona,
storage,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::{fs, net::IpAddr, time::Duration};
use tauri::AppHandle;
const KEYCHAIN_SERVICE: &str = "world.guanghu.hololake.persona-model";
const PROVIDER_KIND: &str = "OPENAI_COMPATIBLE_CHAT_COMPLETIONS_V1";
const MAX_HISTORY_EVENTS: usize = 24;
const MAX_RESPONSE_CHARS: usize = 64_000;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaModelConfig {
pub schema: String,
pub persona_id: String,
pub provider_kind: String,
pub display_name: String,
pub endpoint_url: String,
pub model: String,
pub configured_at: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaInferenceSnapshot {
pub schema: &'static str,
pub state: String,
pub active_persona_id: Option<String>,
pub provider_kind: Option<String>,
pub display_name: Option<String>,
pub endpoint_url: Option<String>,
pub model: Option<String>,
pub api_key_present: bool,
pub model_is_persona: bool,
pub execution_authority: bool,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaInteractionReceipt {
pub schema: &'static str,
pub receipt_id: String,
pub state: String,
pub user_event: TimelineEvent,
pub persona_event: Option<TimelineEvent>,
pub system_receipt: Option<TimelineEvent>,
}
fn config_path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
Ok(storage::root(app)?.join("persona-inference/model-binding.json"))
}
fn load_config(app: &AppHandle) -> Result<Option<PersonaModelConfig>, String> {
let path = config_path(app)?;
if !path.exists() {
return Ok(None);
}
storage::read_json(&path).map(Some)
}
fn credential_account(persona_id: &str) -> String {
format!("{persona_id}:api-key:v1")
}
#[cfg(target_os = "macos")]
fn load_api_key(persona_id: &str) -> Result<Option<Vec<u8>>, String> {
match security_framework::passwords::get_generic_password(
KEYCHAIN_SERVICE,
&credential_account(persona_id),
) {
Ok(value) => Ok(Some(value)),
Err(error) if error.code() == -25300 => Ok(None),
Err(_) => Err("PERSONA_MODEL_KEYCHAIN_READ_FAILED".into()),
}
}
#[cfg(target_os = "macos")]
fn save_api_key(persona_id: &str, value: &[u8]) -> Result<(), String> {
security_framework::passwords::set_generic_password(
KEYCHAIN_SERVICE,
&credential_account(persona_id),
value,
)
.map_err(|_| "PERSONA_MODEL_KEYCHAIN_WRITE_FAILED".to_string())
}
#[cfg(target_os = "macos")]
fn delete_api_key(persona_id: &str) -> Result<(), String> {
match security_framework::passwords::delete_generic_password(
KEYCHAIN_SERVICE,
&credential_account(persona_id),
) {
Ok(()) => Ok(()),
Err(error) if error.code() == -25300 => Ok(()),
Err(_) => Err("PERSONA_MODEL_KEYCHAIN_DELETE_FAILED".into()),
}
}
#[cfg(not(target_os = "macos"))]
fn load_api_key(_persona_id: &str) -> Result<Option<Vec<u8>>, String> {
Err("PERSONA_MODEL_KEYCHAIN_UNAVAILABLE_ON_PLATFORM".into())
}
#[cfg(not(target_os = "macos"))]
fn save_api_key(_persona_id: &str, _value: &[u8]) -> Result<(), String> {
Err("PERSONA_MODEL_KEYCHAIN_UNAVAILABLE_ON_PLATFORM".into())
}
#[cfg(not(target_os = "macos"))]
fn delete_api_key(_persona_id: &str) -> Result<(), String> {
Err("PERSONA_MODEL_KEYCHAIN_UNAVAILABLE_ON_PLATFORM".into())
}
fn validate_endpoint(value: &str) -> Result<String, String> {
let endpoint = reqwest::Url::parse(value.trim())
.map_err(|_| "PERSONA_MODEL_ENDPOINT_INVALID".to_string())?;
if endpoint.username() != ""
|| endpoint.password().is_some()
|| endpoint.query().is_some()
|| endpoint.fragment().is_some()
{
return Err("PERSONA_MODEL_ENDPOINT_CREDENTIAL_OR_QUERY_REJECTED".into());
}
let host = endpoint
.host_str()
.ok_or_else(|| "PERSONA_MODEL_ENDPOINT_HOST_MISSING".to_string())?;
match endpoint.scheme() {
"https" => {}
"http"
if host.eq_ignore_ascii_case("localhost")
|| host
.parse::<IpAddr>()
.map(|ip| ip.is_loopback())
.unwrap_or(false) => {}
_ => return Err("PERSONA_MODEL_ENDPOINT_REQUIRES_HTTPS_OR_LOOPBACK".into()),
}
if !endpoint.path().ends_with("/chat/completions") {
return Err("PERSONA_MODEL_ENDPOINT_MUST_BE_CHAT_COMPLETIONS".into());
}
Ok(endpoint.to_string())
}
fn clean_text(value: &str, empty_error: &str, max_chars: usize) -> Result<String, String> {
let value = value.trim();
if value.is_empty() || value.chars().count() > max_chars {
return Err(empty_error.into());
}
Ok(value.into())
}
pub fn snapshot(
app: &AppHandle,
active_persona_id: Option<String>,
) -> Result<PersonaInferenceSnapshot, String> {
let config = load_config(app)?;
let api_key_present = if let Some(config) = &config {
load_api_key(&config.persona_id)?.is_some()
} else {
false
};
let state = match (&active_persona_id, &config) {
(None, _) => "READY_NO_PERSONA",
(Some(_), None) => "PERSONA_MODEL_BINDING_REQUIRED",
(Some(active), Some(config)) if active != &config.persona_id => {
"PERSONA_MODEL_BINDING_PERSONA_MISMATCH"
}
(Some(_), Some(_)) => "PERSONA_MODEL_BOUND_READY",
};
Ok(PersonaInferenceSnapshot {
schema: "hololake.persona-inference-snapshot/v1",
state: state.into(),
active_persona_id,
provider_kind: config.as_ref().map(|value| value.provider_kind.clone()),
display_name: config.as_ref().map(|value| value.display_name.clone()),
endpoint_url: config.as_ref().map(|value| value.endpoint_url.clone()),
model: config.as_ref().map(|value| value.model.clone()),
api_key_present,
model_is_persona: false,
execution_authority: false,
})
}
pub fn configure(
app: &AppHandle,
persona_id: &str,
display_name: &str,
endpoint_url: &str,
model: &str,
api_key: Option<String>,
) -> Result<PersonaInferenceSnapshot, String> {
if !crate::persona_runtime::exists(app, persona_id)? {
return Err("PERSONA_NOT_FOUND".into());
}
let persona_id = clean_text(persona_id, "PERSONA_ID_INVALID", 96)?;
let display_name = clean_text(display_name, "PERSONA_MODEL_DISPLAY_NAME_INVALID", 64)?;
let model = clean_text(model, "PERSONA_MODEL_ID_INVALID", 160)?;
let endpoint_url = validate_endpoint(endpoint_url)?;
let is_loopback = reqwest::Url::parse(&endpoint_url)
.ok()
.and_then(|url| url.host_str().map(str::to_owned))
.map(|host| {
host.eq_ignore_ascii_case("localhost")
|| host
.parse::<IpAddr>()
.map(|ip| ip.is_loopback())
.unwrap_or(false)
})
.unwrap_or(false);
if let Some(value) = api_key
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
{
if value.chars().count() > 8192 {
return Err("PERSONA_MODEL_API_KEY_TOO_LARGE".into());
}
save_api_key(&persona_id, value.as_bytes())?;
}
if !is_loopback && load_api_key(&persona_id)?.is_none() {
return Err("PERSONA_MODEL_API_KEY_REQUIRED_FOR_REMOTE_ENDPOINT".into());
}
storage::write_json(
&config_path(app)?,
&PersonaModelConfig {
schema: "hololake.persona-model-binding/v1".into(),
persona_id: persona_id.clone(),
provider_kind: PROVIDER_KIND.into(),
display_name,
endpoint_url,
model,
configured_at: storage::now(),
},
)?;
storage::append_event(
app,
&storage::event(
SourceKind::SystemReceipt,
"人格模型已绑定",
format!(
"{persona_id} 已绑定可替换推理资源;密钥仅保存在系统钥匙串,模型不因此成为人格体。"
),
"SUCCEEDED",
),
)?;
snapshot(app, Some(persona_id))
}
pub fn clear(app: &AppHandle, persona_id: &str) -> Result<PersonaInferenceSnapshot, String> {
if let Some(config) = load_config(app)? {
if config.persona_id != persona_id {
return Err("PERSONA_MODEL_BINDING_PERSONA_MISMATCH".into());
}
delete_api_key(persona_id)?;
let path = config_path(app)?;
if path.exists() {
fs::remove_file(path).map_err(|_| "PERSONA_MODEL_CONFIG_DELETE_FAILED".to_string())?;
}
storage::append_event(
app,
&storage::event(
SourceKind::SystemReceipt,
"人格模型绑定已移除",
format!("{persona_id} 的模型配置与钥匙串凭据已移除;人格试用记录未被删除。"),
"SUCCEEDED",
),
)?;
}
snapshot(app, Some(persona_id.into()))
}
pub fn clear_if_bound(app: &AppHandle, persona_id: &str) -> Result<(), String> {
if load_config(app)?
.as_ref()
.map(|config| config.persona_id.as_str())
== Some(persona_id)
{
clear(app, persona_id)?;
}
Ok(())
}
fn inference_context(channel: &Channel, persona: &PublicPersona) -> Value {
json!({
"schema": "hololake.persona-inference-context/v1",
"sourceKind": "SYSTEM_CONTEXT",
"channel": {"channelId": channel.channel_id, "name": channel.name},
"persona": {
"personaId": persona.persona_id,
"displayName": persona.display_name,
"state": persona.state,
"verifiedExistingPersona": false
},
"boundaries": {
"modelIsReplaceableInferenceResourceNotPersona": true,
"systemReceiptIsNotHumanOrPersonaSpeech": true,
"executionAuthority": false,
"toolCallsEnabled": false,
"fifthDomainPrivateContentMounted": false,
"honestyBeforeImmersion": true
},
"responseContract": {
"sourceKind": "PERSONA_RESPONSE",
"plainNaturalLanguageOnly": true,
"doNotPretendAnActionExecuted": true,
"doNotRepeatSystemContext": true
}
})
}
fn build_request(
config: &PersonaModelConfig,
channel: &Channel,
persona: &PublicPersona,
timeline: &[TimelineEvent],
) -> Value {
let mut dialogue = timeline
.iter()
.filter_map(|event| match event.source_kind {
SourceKind::UserMessage => Some(json!({"role":"user","content":event.content})),
SourceKind::PersonaResponse
if event.actor_id.as_deref() == Some(persona.persona_id.as_str()) =>
{
Some(json!({"role":"assistant","content":event.content}))
}
_ => None,
})
.collect::<Vec<_>>();
if dialogue.len() > MAX_HISTORY_EVENTS {
dialogue.drain(..dialogue.len() - MAX_HISTORY_EVENTS);
}
let mut messages = vec![json!({
"role": "system",
"content": inference_context(channel, persona).to_string()
})];
messages.extend(dialogue);
json!({"model":config.model,"messages":messages,"stream":false})
}
fn parse_response(value: &Value) -> Result<String, String> {
let content = value
.pointer("/choices/0/message/content")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| "PERSONA_MODEL_RESPONSE_CONTENT_MISSING".to_string())?;
if content.chars().count() > MAX_RESPONSE_CHARS {
return Err("PERSONA_MODEL_RESPONSE_TOO_LARGE".into());
}
Ok(content.into())
}
fn request_model(
config: &PersonaModelConfig,
api_key: Option<&[u8]>,
payload: &Value,
) -> Result<String, String> {
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(90))
.build()
.map_err(|_| "PERSONA_MODEL_CLIENT_INIT_FAILED".to_string())?;
let mut request = client.post(&config.endpoint_url).json(payload);
if let Some(bytes) = api_key {
let key =
std::str::from_utf8(bytes).map_err(|_| "PERSONA_MODEL_API_KEY_INVALID".to_string())?;
request = request.bearer_auth(key);
}
let response = request
.send()
.map_err(|_| "PERSONA_MODEL_UNREACHABLE".to_string())?;
let status = response.status();
if !status.is_success() {
return Err(format!("PERSONA_MODEL_HTTP_{}", status.as_u16()));
}
let value: Value = response
.json()
.map_err(|_| "PERSONA_MODEL_RESPONSE_INVALID_JSON".to_string())?;
parse_response(&value)
}
pub fn respond(
app: &AppHandle,
user_event: TimelineEvent,
) -> Result<PersonaInteractionReceipt, String> {
let runtime = crate::persona_runtime::snapshot(app)?;
let Some(persona_id) = runtime.active_persona_id else {
return waiting_receipt(
app,
user_event,
"尚未建立人格体;用户语言已保存,可由外部 AI 实时桥接收。",
);
};
let persona = runtime
.personas
.into_iter()
.find(|value| value.persona_id == persona_id)
.ok_or_else(|| "ACTIVE_PERSONA_NOT_FOUND".to_string())?;
let Some(config) = load_config(app)? else {
return waiting_receipt(
app,
user_event,
"人格体尚未绑定模型;用户语言已保存,可先在设置中配置模型。",
);
};
if config.persona_id != persona.persona_id {
return failed_receipt(app, user_event, "PERSONA_MODEL_BINDING_PERSONA_MISMATCH");
}
let channel = storage::channel(app)?.ok_or_else(|| "CHANNEL_NOT_INITIALIZED".to_string())?;
let mut timeline = storage::events(app)?;
timeline.reverse();
let payload = build_request(&config, &channel, &persona, &timeline);
let api_key = load_api_key(&persona.persona_id)?;
match request_model(&config, api_key.as_deref(), &payload) {
Ok(content) => {
let persona_event = storage::actor_event(
SourceKind::PersonaResponse,
&persona.persona_id,
persona.display_name,
content,
"SUCCEEDED",
);
storage::append_event(app, &persona_event)?;
Ok(PersonaInteractionReceipt {
schema: "hololake.persona-interaction-receipt/v1",
receipt_id: storage::id("HL-PER-RCP"),
state: "PERSONA_RESPONSE_RECORDED".into(),
user_event,
persona_event: Some(persona_event),
system_receipt: None,
})
}
Err(code) => Ok(failed_receipt(app, user_event, &code)?),
}
}
fn waiting_receipt(
app: &AppHandle,
user_event: TimelineEvent,
message: &str,
) -> Result<PersonaInteractionReceipt, String> {
let system_receipt = storage::event(SourceKind::SystemReceipt, "系统回执", message, "WAITING");
storage::append_event(app, &system_receipt)?;
Ok(PersonaInteractionReceipt {
schema: "hololake.persona-interaction-receipt/v1",
receipt_id: storage::id("HL-PER-RCP"),
state: "WAITING_PERSONA_RUNTIME".into(),
user_event,
persona_event: None,
system_receipt: Some(system_receipt),
})
}
fn failed_receipt(
app: &AppHandle,
user_event: TimelineEvent,
code: &str,
) -> Result<PersonaInteractionReceipt, String> {
let system_receipt = storage::event(
SourceKind::SystemReceipt,
"人格推理失败",
format!("模型没有产生人格回应;用户语言仍已保存。错误码:{code}"),
"FAILED",
);
storage::append_event(app, &system_receipt)?;
Ok(PersonaInteractionReceipt {
schema: "hololake.persona-interaction-receipt/v1",
receipt_id: storage::id("HL-PER-RCP"),
state: "PERSONA_RESPONSE_FAILED".into(),
user_event,
persona_event: None,
system_receipt: Some(system_receipt),
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::{
io::{Read, Write},
net::TcpListener,
thread,
};
fn config() -> PersonaModelConfig {
PersonaModelConfig {
schema: "hololake.persona-model-binding/v1".into(),
persona_id: "HL-PERSONA-TEST".into(),
provider_kind: PROVIDER_KIND.into(),
display_name: "本地测试模型".into(),
endpoint_url: "http://127.0.0.1:8181/v1/chat/completions".into(),
model: "test-model".into(),
configured_at: "2026-09-04T00:00:00.000Z".into(),
}
}
#[test]
fn endpoint_requires_https_or_loopback_and_exact_chat_route() {
assert!(validate_endpoint("https://api.example.com/v1/chat/completions").is_ok());
assert!(validate_endpoint("http://127.0.0.1:8080/v1/chat/completions").is_ok());
assert!(validate_endpoint("http://api.example.com/v1/chat/completions").is_err());
assert!(validate_endpoint("file:///tmp/key").is_err());
assert!(validate_endpoint("https://api.example.com/v1/responses").is_err());
}
#[test]
fn request_keeps_system_receipts_out_of_dialogue_roles() {
let channel = Channel {
channel_id: "HL-CH-TEST".into(),
name: "测试频道".into(),
created_at: "2026-09-04T00:00:00.000Z".into(),
private_git_path: "/private/not-exported".into(),
public_distribution_state: "TEST".into(),
};
let persona = PublicPersona {
persona_id: "HL-PERSONA-TEST".into(),
display_name: "湖心".into(),
state: "LOCAL_TRIAL_UNVERIFIED_EXTERNAL_HOST_REQUIRED".into(),
created_at: "2026-09-04T00:00:00.000Z".into(),
reversible_until_unix_ms: 0,
};
let events = vec![
storage::event(
SourceKind::SystemReceipt,
"系统回执",
"不应进入对话",
"SUCCEEDED",
),
storage::event(SourceKind::UserMessage, "用户语言", "你好", "RECORDED"),
storage::actor_event(
SourceKind::PersonaResponse,
"HL-PERSONA-TEST",
"湖心",
"你好。",
"SUCCEEDED",
),
];
let request = build_request(&config(), &channel, &persona, &events);
let messages = request["messages"].as_array().unwrap();
assert_eq!(messages.len(), 3);
assert!(messages[0]["content"]
.as_str()
.unwrap()
.contains("executionAuthority"));
assert!(!request.to_string().contains("不应进入对话"));
assert!(!request.to_string().contains("/private/not-exported"));
}
#[test]
fn response_requires_nonempty_persona_content() {
assert_eq!(
parse_response(&json!({"choices":[{"message":{"content":" 回应 "}}]})).unwrap(),
"回应"
);
assert!(parse_response(&json!({"choices":[{"message":{"content":""}}]})).is_err());
assert!(parse_response(&json!({"output_text":"wrong protocol"})).is_err());
}
#[test]
fn real_loopback_request_returns_typed_persona_text_without_executing_tools() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let server = thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
let mut request = vec![0_u8; 16_384];
let size = stream.read(&mut request).unwrap();
let request = String::from_utf8_lossy(&request[..size]);
assert!(request.starts_with("POST /v1/chat/completions HTTP/1.1"));
assert!(request
.to_ascii_lowercase()
.contains("authorization: bearer test-secret"));
assert!(request.contains("SYSTEM_CONTEXT"));
let body =
r#"{"choices":[{"message":{"content":"这是来自真实本机 HTTP 回路的人格回应。"}}]}"#;
write!(
stream,
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
)
.unwrap();
});
let mut local = config();
local.endpoint_url = format!("http://{address}/v1/chat/completions");
let payload = json!({
"model": "test-model",
"messages": [{"role":"system","content":"SYSTEM_CONTEXT"}],
"stream": false
});
assert_eq!(
request_model(&local, Some(b"test-secret"), &payload).unwrap(),
"这是来自真实本机 HTTP 回路的人格回应。"
);
server.join().unwrap();
}
}

View file

@ -379,8 +379,13 @@ fn handle_connection(
)?; )?;
continue; continue;
} }
let event = let event = storage::actor_event(
storage::event(SourceKind::PersonaResponse, "人格回应", content, "RECORDED"); SourceKind::PersonaResponse,
hello.persona_id.as_deref().unwrap_or_default(),
"人格回应",
content,
"RECORDED",
);
storage::append_event(&app, &event)?; storage::append_event(&app, &event)?;
let _ = app.emit("hololake-runtime-event", &event); let _ = app.emit("hololake-runtime-event", &event);
send(&mut writer, json!({"type":"accepted","event":event}))?; send(&mut writer, json!({"type":"accepted","event":event}))?;

View file

@ -90,12 +90,24 @@ pub fn event(
TimelineEvent { TimelineEvent {
event_id: id("HL-EVT"), event_id: id("HL-EVT"),
source_kind: kind, source_kind: kind,
actor_id: None,
title: title.into(), title: title.into(),
content: content.into(), content: content.into(),
occurred_at: now(), occurred_at: now(),
state: state.into(), state: state.into(),
} }
} }
pub fn actor_event(
kind: SourceKind,
actor_id: impl Into<String>,
title: impl Into<String>,
content: impl Into<String>,
state: &str,
) -> TimelineEvent {
let mut event = event(kind, title, content, state);
event.actor_id = Some(actor_id.into());
event
}
pub fn append_event(app: &AppHandle, e: &TimelineEvent) -> Result<(), String> { pub fn append_event(app: &AppHandle, e: &TimelineEvent) -> Result<(), String> {
let p = root(app)?.join("events.jsonl"); let p = root(app)?.join("events.jsonl");
let mut s = if p.exists() { let mut s = if p.exists() {

View file

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "HoloLake", "productName": "HoloLake",
"version": "1.2.2", "version": "1.3.0",
"identifier": "world.guanghu.hololake", "identifier": "world.guanghu.hololake",
"build": { "frontendDist": "../dist", "devUrl": "http://127.0.0.1:5211", "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build" }, "build": { "frontendDist": "../dist", "devUrl": "http://127.0.0.1:5211", "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build" },
"app": { "app": {

View file

@ -204,11 +204,17 @@ function Channel({
openKnowledge: () => void; openKnowledge: () => void;
}) { }) {
const [text, setText] = useState(""); const [text, setText] = useState("");
const [sending, setSending] = useState(false);
const send = async () => { const send = async () => {
if (!text.trim()) return; if (!text.trim() || sending) return;
await api.submitMessage(text.trim()); setSending(true);
setText(""); try {
refresh(); await api.submitMessage(text.trim());
setText("");
refresh();
} finally {
setSending(false);
}
}; };
return ( return (
<div className="page channel-page"> <div className="page channel-page">
@ -307,7 +313,12 @@ function Channel({
<textarea <textarea
value={text} value={text}
onChange={(e) => setText(e.target.value)} onChange={(e) => setText(e.target.value)}
placeholder={`${data.channel?.name}说点什么…`} disabled={sending}
placeholder={
runtime?.persona.personas[0]
? `${runtime.persona.personas[0].displayName}说点什么…`
: "先在设置中建立一个试用人格…"
}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) { if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
@ -315,10 +326,14 @@ function Channel({
} }
}} }}
/> />
<button onClick={send} aria-label="发送"> <button onClick={send} aria-label="发送" disabled={sending || !text.trim()}>
{sending ? "…" : "↗"}
</button> </button>
<small>Enter · Shift + Enter </small> <small>
{sending
? "人格体正在通过已绑定模型形成回应…"
: "Enter 发送 · Shift + Enter 换行"}
</small>
</div> </div>
</div> </div>
<aside className="channel-context" aria-label="频道运行状态"> <aside className="channel-context" aria-label="频道运行状态">
@ -341,6 +356,17 @@ function Channel({
{runtime?.persona.personas[0]?.state ?? "不会自动生成默认人格"} {runtime?.persona.personas[0]?.state ?? "不会自动生成默认人格"}
</p> </p>
</article> </article>
<article>
<h2></h2>
<strong>
{runtime?.inference.displayName ?? "尚未绑定"}
</strong>
<p>
{runtime?.inference.model
? `${runtime.inference.model} · ${runtime.inference.state}`
: "模型是可替换推理资源,不等于人格体"}
</p>
</article>
<article> <article>
<h2>GLP </h2> <h2>GLP </h2>
<strong> <strong>
@ -1079,7 +1105,17 @@ function Settings({
}) { }) {
const [bridgeName, setBridgeName] = useState(""), const [bridgeName, setBridgeName] = useState(""),
[personaName, setPersonaName] = useState(""), [personaName, setPersonaName] = useState(""),
[runtimeMessage, setRuntimeMessage] = useState(""); [runtimeMessage, setRuntimeMessage] = useState(""),
[modelDisplayName, setModelDisplayName] = useState(""),
[modelEndpoint, setModelEndpoint] = useState(""),
[modelId, setModelId] = useState(""),
[modelApiKey, setModelApiKey] = useState("");
useEffect(() => {
if (!runtime?.inference.endpointUrl) return;
setModelDisplayName(runtime.inference.displayName ?? "");
setModelEndpoint(runtime.inference.endpointUrl);
setModelId(runtime.inference.model ?? "");
}, [runtime?.inference.endpointUrl, runtime?.inference.displayName, runtime?.inference.model]);
const update = async () => { const update = async () => {
setBusy(true); setBusy(true);
try { try {
@ -1148,6 +1184,86 @@ function Settings({
</button> </button>
</div> </div>
</article> </article>
<article className="model-binding">
<div className="model-binding-copy">
<h2></h2>
<p>
{runtime?.inference.state ?? "正在读取"} Agent
</p>
</div>
<div className="model-binding-fields">
<input
value={modelDisplayName}
onChange={(event) => setModelDisplayName(event.target.value)}
placeholder="模型入口名称,例如:本机推理模型"
/>
<input
value={modelEndpoint}
onChange={(event) => setModelEndpoint(event.target.value)}
placeholder="https://…/v1/chat/completions"
/>
<input
value={modelId}
onChange={(event) => setModelId(event.target.value)}
placeholder="模型编号"
/>
<input
type="password"
value={modelApiKey}
onChange={(event) => setModelApiKey(event.target.value)}
placeholder={runtime?.inference.apiKeyPresent ? "钥匙串已有密钥;留空保持" : "API 密钥(仅存系统钥匙串)"}
autoComplete="off"
/>
</div>
<div className="model-binding-actions">
{runtime?.inference.state === "PERSONA_MODEL_BOUND_READY" && (
<button
className="soft danger"
onClick={async () => {
const persona = runtime.persona.personas[0];
if (!persona || !confirm(`移除 ${persona.displayName} 的模型绑定?`)) return;
await api.clearPersonaModel(persona.personaId);
setModelApiKey("");
refresh();
}}
>
</button>
)}
<button
disabled={
busy ||
!runtime?.persona.personas[0] ||
!modelDisplayName.trim() ||
!modelEndpoint.trim() ||
!modelId.trim()
}
onClick={async () => {
const persona = runtime?.persona.personas[0];
if (!persona) return;
setBusy(true);
try {
await api.configurePersonaModel({
personaId: persona.personaId,
displayName: modelDisplayName.trim(),
endpointUrl: modelEndpoint.trim(),
model: modelId.trim(),
apiKey: modelApiKey,
});
setModelApiKey("");
setRuntimeMessage("人格模型已绑定。回到频道即可开始真实对话。");
refresh();
} catch (error) {
setError(String(error));
} finally {
setBusy(false);
}
}}
>
{busy ? "正在保存…" : "保存模型绑定"}
</button>
</div>
</article>
<article className="bridge"> <article className="bridge">
<div> <div>
<h2></h2> <h2></h2>

View file

@ -7,6 +7,8 @@ import type {
ExternalAiBridge, ExternalAiBridge,
KnowledgeDocument, KnowledgeDocument,
MutationReceipt, MutationReceipt,
PersonaInferenceSnapshot,
PersonaInteractionReceipt,
PublicPersona, PublicPersona,
RealtimeInvitation, RealtimeInvitation,
RuntimeOverview, RuntimeOverview,
@ -70,7 +72,7 @@ export async function snapshot(): Promise<SystemSnapshot> {
updatedAt, updatedAt,
sha256, sha256,
})), })),
moduleRegistryVersion: "1", moduleRegistryVersion: "2",
publicUpdateEndpoint: publicUpdateEndpoint:
"https://guanghulab.com/hololake/releases/latest.json", "https://guanghulab.com/hololake/releases/latest.json",
publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE", publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE",
@ -104,26 +106,28 @@ export async function createChannel(name: string): Promise<Channel> {
return channel; return channel;
} }
export async function submitMessage(content: string): Promise<MutationReceipt> { export async function submitMessage(content: string): Promise<PersonaInteractionReceipt> {
if (tauri()) return invoke("submit_user_message", { content }); if (tauri()) return invoke("submit_user_message", { content });
const s = load(); const s = load();
const e = event("USER_MESSAGE", "用户语言", content, "RECORDED"); const e = event("USER_MESSAGE", "用户语言", content, "RECORDED");
const systemReceipt = event(
"SYSTEM_RECEIPT",
"系统回执",
"用户语言已分流保存;浏览器预览不调用模型。",
"WAITING",
);
s.timeline.unshift( s.timeline.unshift(
e, e,
event( systemReceipt,
"SYSTEM_RECEIPT",
"系统回执",
"用户语言已分流保存;等待人格体接入,不伪造人格回应。",
"WAITING",
),
); );
save(s); save(s);
return { return {
schema: "preview",
receiptId: crypto.randomUUID(), receiptId: crypto.randomUUID(),
state: "WAITING_PERSONA", state: "WAITING_PERSONA_RUNTIME",
message: "用户语言已记录", userEvent: e,
gitCommit: null, personaEvent: null,
event: e, systemReceipt,
}; };
} }
@ -242,6 +246,20 @@ export async function runtimeOverview(): Promise<RuntimeOverview> {
personas: preview.personas, personas: preview.personas,
verifiedExistingPersonaCount: 0, verifiedExistingPersonaCount: 0,
}, },
inference: {
schema: "preview",
state: preview.personas.length
? "PERSONA_MODEL_BINDING_REQUIRES_INSTALLED_APP"
: "READY_NO_PERSONA",
activePersonaId: preview.personas[0]?.personaId ?? null,
providerKind: null,
displayName: null,
endpointUrl: null,
model: null,
apiKeyPresent: false,
modelIsPersona: false,
executionAuthority: false,
},
proposals: [], proposals: [],
}; };
} }
@ -294,6 +312,30 @@ export async function deleteTrialPersona(
verifiedExistingPersonaCount: 0, verifiedExistingPersonaCount: 0,
}; };
} }
export async function configurePersonaModel(input: {
personaId: string;
displayName: string;
endpointUrl: string;
model: string;
apiKey?: string;
}): Promise<PersonaInferenceSnapshot> {
if (!tauri()) throw new Error("模型凭据只能在已安装的 HoloLake 中写入系统钥匙串");
return invoke("configure_persona_model", {
personaId: input.personaId,
displayName: input.displayName,
endpointUrl: input.endpointUrl,
model: input.model,
apiKey: input.apiKey?.trim() || null,
});
}
export async function clearPersonaModel(
personaId: string,
): Promise<PersonaInferenceSnapshot> {
if (!tauri()) throw new Error("模型绑定只能在已安装的 HoloLake 中移除");
return invoke("clear_persona_model", { personaId });
}
export async function approveProposal( export async function approveProposal(
proposalId: string, proposalId: string,
): Promise<AgentReceipt> { ): Promise<AgentReceipt> {

View file

@ -1138,6 +1138,34 @@ main {
gap: 8px; gap: 8px;
min-width: 370px; min-width: 370px;
} }
.settings-list .model-binding {
display: grid;
grid-template-columns: minmax(210px, 0.75fr) minmax(360px, 1.35fr) auto;
align-items: end;
gap: 18px;
}
.model-binding-copy p {
max-width: 420px;
line-height: 1.6;
}
.model-binding-fields {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.model-binding-actions {
display: flex;
gap: 8px;
}
@media (max-width: 1080px) {
.settings-list .model-binding {
grid-template-columns: 1fr;
align-items: stretch;
}
.model-binding-actions {
justify-content: flex-end;
}
}
.onboard { .onboard {
height: 100vh; height: 100vh;
display: grid; display: grid;

View file

@ -18,6 +18,7 @@ export interface Channel {
export interface TimelineEvent { export interface TimelineEvent {
eventId: string; eventId: string;
sourceKind: SourceKind; sourceKind: SourceKind;
actorId?: string;
title: string; title: string;
content: string; content: string;
occurredAt: string; occurredAt: string;
@ -124,8 +125,29 @@ export interface RuntimeOverview {
personas: PublicPersona[]; personas: PublicPersona[];
verifiedExistingPersonaCount: number; verifiedExistingPersonaCount: number;
}; };
inference: PersonaInferenceSnapshot;
proposals: AgentProposal[]; proposals: AgentProposal[];
} }
export interface PersonaInferenceSnapshot {
schema: string;
state: string;
activePersonaId: string | null;
providerKind: string | null;
displayName: string | null;
endpointUrl: string | null;
model: string | null;
apiKeyPresent: boolean;
modelIsPersona: boolean;
executionAuthority: boolean;
}
export interface PersonaInteractionReceipt {
schema: string;
receiptId: string;
state: string;
userEvent: TimelineEvent;
personaEvent: TimelineEvent | null;
systemReceipt: TimelineEvent | null;
}
export interface RealtimeInvitation { export interface RealtimeInvitation {
schema: string; schema: string;
protocol: string; protocol: string;

View file

@ -5,7 +5,7 @@
"state": "CURRENT_CANONICAL", "state": "CURRENT_CANONICAL",
"clean_v1_rebuild": { "clean_v1_rebuild": {
"record_id": "HLP-CLEAN-V1-REBUILD-20260903-001", "record_id": "HLP-CLEAN-V1-REBUILD-20260903-001",
"state": "LOCAL_CLEAN_V1_1_2_0_INSTALLED_DIRECT_LANGUAGE_LOCAL_ACCEPTANCE_PASS_PUBLIC_RELEASE_PENDING", "state": "PUBLIC_1_2_2_INSTALLED_SIGNED_NOTARIZED_AND_ACCEPTED",
"source": "product-source/hololake-clean-desktop", "source": "product-source/hololake-clean-desktop",
"canonical_tcs": "product-source/hololake-clean-desktop/language/HOLOLAKE-CLEAN-V1-FOUNDATION.tcs", "canonical_tcs": "product-source/hololake-clean-desktop/language/HOLOLAKE-CLEAN-V1-FOUNDATION.tcs",
"current_direct_language_execution_tcs": "product-source/hololake-clean-desktop/language/HOLOLAKE-CLEAN-V1-DIRECT-LANGUAGE-EXECUTION-BASELINE.tcs", "current_direct_language_execution_tcs": "product-source/hololake-clean-desktop/language/HOLOLAKE-CLEAN-V1-DIRECT-LANGUAGE-EXECUTION-BASELINE.tcs",
@ -22,7 +22,7 @@
"public_update_endpoint": "https://guanghulab.com/hololake/releases/latest.json", "public_update_endpoint": "https://guanghulab.com/hololake/releases/latest.json",
"public_update_release_state": "PUBLIC_1_2_2_SIGNED_NOTARIZED_BROADCAST_ACTIVE", "public_update_release_state": "PUBLIC_1_2_2_SIGNED_NOTARIZED_BROADCAST_ACTIVE",
"public_update_manifest_builder": "product-source/hololake-clean-desktop/scripts/build-update-manifest.mjs", "public_update_manifest_builder": "product-source/hololake-clean-desktop/scripts/build-update-manifest.mjs",
"public_update_candidate_version": "1.2.0", "public_update_candidate_version": "1.2.2",
"enterprise_repository_registry_state": "BLUE_ISOLATED_FIVE_CENTRAL_REPOSITORIES_AND_FIVE_HUMAN_WORK_REPOSITORIES_TEAM_PERMISSIONS_NOT_ASSIGNED_NO_PUBLIC_CUTOVER", "enterprise_repository_registry_state": "BLUE_ISOLATED_FIVE_CENTRAL_REPOSITORIES_AND_FIVE_HUMAN_WORK_REPOSITORIES_TEAM_PERMISSIONS_NOT_ASSIGNED_NO_PUBLIC_CUTOVER",
"bingshuo_enterprise_language_master_work_repository": "REPO-016:bingshuo/guanghu-language-master-work", "bingshuo_enterprise_language_master_work_repository": "REPO-016:bingshuo/guanghu-language-master-work",
"team_enterprise_login": "https://guanghu.chat/code/user/login", "team_enterprise_login": "https://guanghu.chat/code/user/login",
@ -41,11 +41,29 @@
"enterprise_login_session_scope": "ONE_DOMAIN_ONE_REPOSITORY_SHORT_LIVED", "enterprise_login_session_scope": "ONE_DOMAIN_ONE_REPOSITORY_SHORT_LIVED",
"bingshuo_fifth_domain_bridge": "FD-BRIDGE-ENTERPRISE-LANGUAGE-MASTER-0001", "bingshuo_fifth_domain_bridge": "FD-BRIDGE-ENTERPRISE-LANGUAGE-MASTER-0001",
"private_git": "LOCAL_USER_OWNED", "private_git": "LOCAL_USER_OWNED",
"public_git": "SIGNED_ENTERPRISE_DISTRIBUTION_PENDING_EXACT_REGISTRY", "public_git": "SIGNED_PUBLIC_DISTRIBUTION_ACTIVE_AT_1_2_2",
"realtime_transport": "GLP_LOCAL_REALTIME_1_LOOPBACK_TCP_ACTIVE_GIT_REMAINS_DURABLE_FACT_LAYER", "realtime_transport": "GLP_LOCAL_REALTIME_1_LOOPBACK_TCP_ACTIVE_GIT_REMAINS_DURABLE_FACT_LAYER",
"enterprise_four_domains_embedded": false, "enterprise_four_domains_embedded": false,
"fifth_domain_private_content_embedded": false "fifth_domain_private_content_embedded": false
}, },
"persona_interaction_stage": {
"record_id": "HLP-PERSONA-INTERACTION-STAGE2-20260904-001",
"state": "SECOND_STAGE_1_3_0_SOURCE_TCS_GIR_REAL_LOOPBACK_AND_DESKTOP_UI_ACCEPTED_PUBLIC_RELEASE_PENDING",
"product_stage": "POST_CLEAN_V1_SECOND_STAGE_CANDIDATE",
"stage_one_boundary": "GLS-0261_PRESERVED_AS_COMPLETED_1_2_2_BASELINE",
"source": "product-source/hololake-clean-desktop/src-tauri/src/persona_inference.rs",
"contract": "product-source/hololake-clean-desktop/contracts/persona-interaction-v1.json",
"canonical_tcs": "product-source/hololake-clean-desktop/language/HOLOLAKE-PERSONA-INTERACTION-V1.tcs",
"module_id": "HLP-MOD-PERSONA-INFERENCE-0001",
"model_role": "REPLACEABLE_INFERENCE_RESOURCE_NOT_PERSONA",
"api_key_storage": "MACOS_KEYCHAIN",
"dialogue_sources": ["USER_MESSAGE", "PERSONA_RESPONSE"],
"system_sources_excluded_from_dialogue": ["SYSTEM_CONTEXT", "SYSTEM_RECEIPT", "PROTOCOL_EVENT", "AGENT_ACTION", "TOOL_RESULT", "EXTERNAL_AI_MESSAGE"],
"model_execution_authority": false,
"enterprise_server_relays_private_messages": false,
"real_loopback_provider_acceptance": "PASS_SYSTEM_AND_USER_ROLES_TYPED_CONTEXT_AND_PERSONA_RESPONSE_ACTOR_ID_READBACK",
"real_remote_provider_acceptance": "PENDING_USER_SELECTED_PROVIDER_CREDENTIAL"
},
"product": { "product": {
"formal_name": "光湖语言系统 · 通用人工智能操作平台", "formal_name": "光湖语言系统 · 通用人工智能操作平台",
"short_name": "GH-AIOS", "short_name": "GH-AIOS",
@ -54,10 +72,10 @@
}, },
"current_native_edge": { "current_native_edge": {
"record_id": "HLP-NATIVE-DOMAIN-MEMBRANE-PNCC-20260816-001", "record_id": "HLP-NATIVE-DOMAIN-MEMBRANE-PNCC-20260816-001",
"state": "PUBLIC_PERSONAL_CHANNEL_CLEAN_V1_MAC_ARM64_LOCAL_INSTALLED_AND_LAUNCH_ACCEPTED_PUBLIC_RELEASE_PENDING", "state": "PUBLIC_PERSONAL_CHANNEL_CLEAN_V1_1_2_2_PUBLISHED_INSTALLED_SIGNED_NOTARIZED_AND_LAUNCH_ACCEPTED",
"native_source": "product-source/hololake-clean-desktop", "native_source": "product-source/hololake-clean-desktop",
"native_source_commit": "92cce2797381f84762a989ce57b991da76412119", "native_source_commit": "7593f9d3f06b2f22206ad6a05665c35443f7426b",
"native_install_receipt_commit": "92cce2797381f84762a989ce57b991da76412119", "native_install_receipt_commit": "e5b10e227f44111e584f27ba00239bd97b23a63b",
"current_fifth_domain_channel": "ICE-CH-ZC001", "current_fifth_domain_channel": "ICE-CH-ZC001",
"fifth_domain_channel_route_source": "REPO-012:routing/fifth-domain-world-tree.json", "fifth_domain_channel_route_source": "REPO-012:routing/fifth-domain-world-tree.json",
"public_entry": "FIVE_DOMAIN_HOME_NUMBER_ROUTED_BEFORE_DOMAIN_LOGIN", "public_entry": "FIVE_DOMAIN_HOME_NUMBER_ROUTED_BEFORE_DOMAIN_LOGIN",
@ -76,10 +94,10 @@
"qoder_official_ui_template": "REPO_012_27D34DF_OFFICIAL_LOGIN_V0_4_AND_INNER_SCREENS_V0_1_RUNTIME_INTEGRATED_BROWSER_VERIFIED", "qoder_official_ui_template": "REPO_012_27D34DF_OFFICIAL_LOGIN_V0_4_AND_INNER_SCREENS_V0_1_RUNTIME_INTEGRATED_BROWSER_VERIFIED",
"client_world_navigation": "DOMAIN_HOME_TO_CHANNEL_OVERVIEW_TO_TOOL_PROJECTION_NO_TRADITIONAL_GLOBAL_SIDEBAR", "client_world_navigation": "DOMAIN_HOME_TO_CHANNEL_OVERVIEW_TO_TOOL_PROJECTION_NO_TRADITIONAL_GLOBAL_SIDEBAR",
"prelogin_private_broker": "WAITING_FOR_LOGIN_NO_PRIVATE_STORAGE_OPENED", "prelogin_private_broker": "WAITING_FOR_LOGIN_NO_PRIVATE_STORAGE_OPENED",
"mac_arm64_desktop_install_acceptance": "PASS_HOLOLAKE_1_1_0_DEVELOPER_ID_SIGNATURE_APPLICATIONS_PROCESS_GLP_LISTENER_AND_EXISTING_CHANNEL_READBACK", "mac_arm64_desktop_install_acceptance": "PASS_HOLOLAKE_1_2_2_DEVELOPER_ID_NOTARIZED_APPLICATIONS_PROCESS_GLP_LISTENER_AND_EXISTING_CHANNEL_READBACK",
"mac_x86_64_installer": "NOT_BUILT", "mac_x86_64_installer": "NOT_BUILT",
"windows_installer": "NOT_BUILT", "windows_installer": "NOT_BUILT",
"automatic_update_release_channel": "GUANGHU_OWNED_SIGNED_UPDATE_FEED_HTTP_204_NO_PUBLIC_RELEASE", "automatic_update_release_channel": "GUANGHU_OWNED_SIGNED_NOTARIZED_UPDATE_FEED_PUBLIC_1_2_2_ACTIVE",
"public_zero_core_distribution": "LOCAL_ACTIVE_VERIFIED_DUAL_SIGNATURE_ATOMIC_ACTIVATION_EPOCH_1_VERSION_1_0_0", "public_zero_core_distribution": "LOCAL_ACTIVE_VERIFIED_DUAL_SIGNATURE_ATOMIC_ACTIVATION_EPOCH_1_VERSION_1_0_0",
"windows_secure_credential_bridge": "NOT_IMPLEMENTED_DO_NOT_CLAIM_PERSISTENT_SIGNING", "windows_secure_credential_bridge": "NOT_IMPLEMENTED_DO_NOT_CLAIM_PERSISTENT_SIGNING",
"age_is_persona_species_not_individual_number_namespace": true, "age_is_persona_species_not_individual_number_namespace": true,
@ -199,7 +217,7 @@
"runtime_implemented": true, "runtime_implemented": true,
"runtime_state": "LOCAL_AND_PUBLIC_1_2_2_INSTALLED_SIGNED_NOTARIZED_TCS_GIR_AGENT_TYPED_CONTEXT_MODULE_MARKET_ENTERPRISE_DEVICE_GATE_AND_EXTERNAL_AI_REALTIME_VERIFIED", "runtime_state": "LOCAL_AND_PUBLIC_1_2_2_INSTALLED_SIGNED_NOTARIZED_TCS_GIR_AGENT_TYPED_CONTEXT_MODULE_MARKET_ENTERPRISE_DEVICE_GATE_AND_EXTERNAL_AI_REALTIME_VERIFIED",
"local_application_version": "1.2.2", "local_application_version": "1.2.2",
"local_implementation_commit": "92cce2797381f84762a989ce57b991da76412119", "local_implementation_commit": "7593f9d3f06b2f22206ad6a05665c35443f7426b",
"official_development_lane": "CURRENT_TASK_SCOPED_ZC001_EXECUTION", "official_development_lane": "CURRENT_TASK_SCOPED_ZC001_EXECUTION",
"external_development_anchor_id": "TCS-EVENT-HOLOLAKE-CLEAN-V1-ZC001-REALITY-DEVELOPMENT-TAKEOVER-20260903", "external_development_anchor_id": "TCS-EVENT-HOLOLAKE-CLEAN-V1-ZC001-REALITY-DEVELOPMENT-TAKEOVER-20260903",
"public_release": "PUBLISHED_SIGNED_NOTARIZED_1_2_2", "public_release": "PUBLISHED_SIGNED_NOTARIZED_1_2_2",