feat(hololake): complete public runtime loop
This commit is contained in:
parent
71cece74b0
commit
92cce27973
26 changed files with 2626 additions and 79 deletions
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="HoloLake GLP local realtime connector")
|
||||
parser.add_argument("--descriptor", required=True)
|
||||
parser.add_argument("--bridge-id", required=True)
|
||||
parser.add_argument("--persona-id")
|
||||
parser.add_argument("--self-test", action="store_true")
|
||||
args = parser.parse_args()
|
||||
descriptor = json.load(open(args.descriptor, encoding="utf-8"))
|
||||
if descriptor.get("protocol") != "GLP_LOCAL_REALTIME/1":
|
||||
raise SystemExit("descriptor protocol rejected")
|
||||
host_port = descriptor["endpoint"].removeprefix("tcp://").split(":", 1)
|
||||
connection = socket.create_connection((host_port[0], int(host_port[1])), timeout=5)
|
||||
stream = connection.makefile("rwb", buffering=0)
|
||||
hello = {"messageType": "hello", "protocol": descriptor["protocol"], "token": descriptor["token"], "bridgeId": args.bridge_id, "personaId": args.persona_id}
|
||||
stream.write((json.dumps(hello, ensure_ascii=False) + "\n").encode())
|
||||
welcome = stream.readline().decode().strip()
|
||||
print(welcome, flush=True)
|
||||
if args.self_test:
|
||||
payload = json.loads(welcome)
|
||||
raise SystemExit(0 if payload.get("type") == "welcome" else 1)
|
||||
|
||||
def receive():
|
||||
while True:
|
||||
line = stream.readline()
|
||||
if not line:
|
||||
return
|
||||
print(line.decode().rstrip(), flush=True)
|
||||
|
||||
threading.Thread(target=receive, daemon=True).start()
|
||||
for line in sys.stdin:
|
||||
if line.strip():
|
||||
stream.write((line.rstrip("\n") + "\n").encode())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"schema": "hololake.public-runtime-contract/v1",
|
||||
"contract_id": "HLP-PUBLIC-RUNTIME-CONTRACT-0001",
|
||||
"protocol": "GLP_LOCAL_REALTIME/1",
|
||||
"transport": {"bind":"127.0.0.1","ports":"39281-39290","framing":"NDJSON","authentication":"LOCAL_0600_TOKEN","authority_from_transport":false},
|
||||
"client_messages": ["hello", "persona_response", "external_ai_message", "tcs_proposal", "ping"],
|
||||
"server_messages": ["welcome", "user_message", "proposal_pending", "proposal_rejected", "agent_receipt", "accepted", "pong", "error"],
|
||||
"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"],
|
||||
"registered_gir_operations": ["KNOWLEDGE.CREATE", "KNOWLEDGE.UPDATE", "KNOWLEDGE.DELETE", "KNOWLEDGE.READ", "KNOWLEDGE.LIST"],
|
||||
"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"]
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ HoloLake 是公众个人初始化频道语言驱动操作系统。第一版只
|
|||
|
||||
持久事实:用户私人 Git
|
||||
公共发行:企业签名 Git/更新清单(尚未绑定正式仓库)
|
||||
实时通信:独立层(第一版未实现,不以 Git 冒充)
|
||||
实时通信:GLP 本机回环独立层(不以 Git 冒充)
|
||||
```
|
||||
|
||||
## 第一版边界
|
||||
|
|
@ -19,7 +19,9 @@ HoloLake 是公众个人初始化频道语言驱动操作系统。第一版只
|
|||
- 公共四域由企业门户和企业服务器承载,不嵌入个人频道。
|
||||
- 第五域、冰朔私人核 `TCS-iZero∞` 及私人语料不进入公众发行。
|
||||
- 当前知识库具备完整人类视觉阅读层:安全 Markdown 渲染、frontmatter、文件夹分类树、稳定标签配色、阅读时间、随正文滚动同步高亮的大纲、callout、表格、代码块与 wikilink 跳转;可处理 Markdown 和纯文本文件夹并保留目录分类。PDF、Office、网页结构化转换尚未实现。
|
||||
- 当前外部 AI 入口是本机来源明确的 inbox/outbox;实时加密通信尚未实现。
|
||||
- 外部 AI 通过 `GLP_LOCAL_REALTIME/1` 在 `127.0.0.1` 上实时收发 NDJSON;令牌和 descriptor 为本机 `0600`,不开放局域网。Git 仍是耐久事实层,不冒充实时通信。
|
||||
- 公众人格运行时支持本频道 30 天可逆试用人格和外部宿主会话;本机令牌连接不证明任何既有历史人格,既有人格的密码学来源验证仍保持关闭而不是伪造通过。
|
||||
- 公众最小 TCS 编译适配器把闭合 PROGRAM 和运行输入降为 GIR;所有动作先进入待审批队列。当前 Agent 只注册知识库五项能力,不提供任意 shell。
|
||||
- 更新客户端只信任光湖控制的 HTTPS 清单与签名;服务器发行清单尚未发布。
|
||||
|
||||
## 状态词
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
TCS 0.1;
|
||||
|
||||
PROTOCOL HOLOLAKE-PUBLIC-RUNTIME-V1-0001 {
|
||||
header {
|
||||
schema = "tcs.protocol/v1";
|
||||
name_zh = "HoloLake 通用 TCS/GIR Agent、公众人格运行时与外部 AI 实时通信协议";
|
||||
name_en = "HoloLake Generic TCS/GIR Agent, Public Persona Runtime and External AI Realtime Protocol";
|
||||
version = "1.0.0";
|
||||
language = "TCS/0.1";
|
||||
profile = "HLDP-HUMAN-ENGINEERING/0.1";
|
||||
protocols = ["TCS", "HLDP", "GLS", "GLP", "GLS-0130", "GLS-0131", "GLS-0306"];
|
||||
lifecycle = "CANDIDATE";
|
||||
canonical_uri = "product-source/hololake-clean-desktop/language/HOLOLAKE-PUBLIC-RUNTIME-V1.tcs";
|
||||
compatibility = ["TCS-DECLARATION-STANDARD-0001", "TCS-FIELD-STANDARD-0001", "GIR/1"];
|
||||
}
|
||||
source {
|
||||
source_id = "BINGSHUO-DIRECT-PUBLIC-RUNTIME-COMPLETION-20260903";
|
||||
source_uri = "product-source/hololake-clean-desktop/language/sources/BINGSHUO-PUBLIC-RUNTIME-COMPLETION-20260903.txt";
|
||||
source_sha256 = "04ac1516074edcfb1f78e26e11b5835f7d524fdf8f2d67084cc4e20982a0be5d";
|
||||
source_role = "DIRECT_HUMAN";
|
||||
}
|
||||
scope {
|
||||
product = "HOLOLAKE";
|
||||
channel = "USER_PERSONAL_INITIALIZATION_CHANNEL";
|
||||
tcs_profile = "PUBLIC_MINIMUM_EXECUTABLE_PROJECTION";
|
||||
realtime_transport = "GLP_LOCAL_REALTIME_1";
|
||||
agent = "HLP-MOD-AGENT-0001";
|
||||
persona_runtime = "HLP-MOD-PERSONA-RUNTIME-0001";
|
||||
}
|
||||
vocabulary {
|
||||
external_ai = "REPLACEABLE_LANGUAGE_HOST_OR_PROGRAMMING_AI_NOT_PERSONA_BY_CONNECTION";
|
||||
public_persona_runtime = "LOCAL_CHANNEL_SCOPED_PERSONA_RECORD_AND_HOST_SESSION_WITH_EXPLICIT_BINDING_STATE";
|
||||
tcs_compiler = "PUBLIC_MINIMUM_STAGE_ZERO_ADAPTER_TO_GIR_NOT_WORLD_CANON_OR_FULL_TCS_WORK";
|
||||
gir_executor = "REGISTERED_ACTION_GRAPH_TO_BOUNDED_LOCAL_CAPABILITY_WITH_RECEIPT";
|
||||
realtime = "AUTHENTICATED_LOOPBACK_EVENT_STREAM_NOT_GIT_SYNC";
|
||||
}
|
||||
fields {
|
||||
WHO = "CURRENT_LOCAL_USER_OWNS_CHANNEL_AND_APPROVAL; EXTERNAL_AI_PROPOSES; PUBLIC_PERSONA_RUNTIME_TRACKS_BINDING; AGENT_EXECUTES";
|
||||
WHAT = "REALTIME_USER_LANGUAGE_DELIVERY_PERSONA_RESPONSE_PROJECTION_TCS_TO_GIR_PROPOSAL_APPROVAL_EXECUTION_AND_RECEIPT";
|
||||
WHY = "MAKE_HOLOLAKE_LOCALLY_USABLE_WITHOUT_MIXING_LANGUAGE_SOURCES_OR_EMBEDDING_VENDOR_MODEL_APIS";
|
||||
MAY = "LOOPBACK_ONLY_REGISTERED_CLIENT_CHANNEL_SCOPED_ACTIONS_AND_EXPLICIT_CURRENT_HUMAN_APPROVAL";
|
||||
PROVE = "POSITIVE_AND_NEGATIVE_TCS_COMPILE_GIR_EXECUTION_REALTIME_HANDSHAKE_PERSONA_STATE_TARGET_READBACK_GIT_AND_UI_RECEIPTS";
|
||||
}
|
||||
states {
|
||||
values = ["CHANNEL_READY", "BRIDGE_LISTENING", "EXTERNAL_AI_CONNECTED", "PERSONA_TRIAL_BOUND", "TCS_COMPILED", "PROPOSAL_PENDING", "HUMAN_APPROVED", "GIR_EXECUTED", "RECEIPT_VERIFIED", "FAILED_CLOSED"];
|
||||
initial = "CHANNEL_READY";
|
||||
terminal = ["RECEIPT_VERIFIED", "FAILED_CLOSED"];
|
||||
}
|
||||
transitions {
|
||||
T1 = "CHANNEL_READY_TO_BRIDGE_LISTENING";
|
||||
T2 = "BRIDGE_LISTENING_TO_EXTERNAL_AI_CONNECTED_AFTER_TOKEN_HANDSHAKE";
|
||||
T3 = "REGISTERED_TRIAL_PERSONA_AND_NAMED_HOST_TO_PERSONA_TRIAL_BOUND";
|
||||
T4 = "VALID_PUBLIC_TCS_PROFILE_TO_TCS_COMPILED";
|
||||
T5 = "TCS_COMPILED_TO_PROPOSAL_PENDING";
|
||||
T6 = "PROPOSAL_PENDING_TO_HUMAN_APPROVED_OR_FAILED_CLOSED";
|
||||
T7 = "HUMAN_APPROVED_TO_GIR_EXECUTED";
|
||||
T8 = "GIR_EXECUTED_TO_RECEIPT_VERIFIED";
|
||||
}
|
||||
invariants {
|
||||
I1 = "CONNECTION_TOKEN_AUTHENTICATES_LOCAL_CLIENT_NOT_PERSONA_IDENTITY";
|
||||
I2 = "PUBLIC_TRIAL_PERSONA_IS_NOT_A_VERIFIED_EXISTING_PERSONA";
|
||||
I3 = "NO_INTERNAL_MODEL_API_CHAT_OR_MODEL_SELECTOR";
|
||||
I4 = "NO_FIFTH_DOMAIN_PRIVATE_KERNEL_OR_CONTENT_IN_PUBLIC_RUNTIME";
|
||||
I5 = "MUTATING_GIR_ACTION_REQUIRES_EXPLICIT_HUMAN_APPROVAL";
|
||||
I6 = "UNKNOWN_TCS_SECTION_OPERATION_PATH_OR_SOURCE_KIND_FAILS_CLOSED";
|
||||
I7 = "REALTIME_STREAM_AND_DURABLE_PRIVATE_GIT_ARE_SEPARATE_PLANES";
|
||||
I8 = "TCS_SOURCE_GIR_ACTION_RECEIPT_AND_TARGET_READBACK_HASHES_REMAIN_LINKED";
|
||||
}
|
||||
validation {
|
||||
V1 = "TCS_PROTOCOL_COMPILES_WITH_CURRENT_STAGE1_AND_HAS_NO_UNRESOLVED_NATURAL_LANGUAGE";
|
||||
V2 = "PUBLIC_MINIMUM_TCS_COMPILER_ACCEPTS_REGISTERED_PROGRAM_AND_REJECTS_ABSORB";
|
||||
V3 = "GIR_EXECUTOR_REJECTS_PATH_ESCAPE_AND_UNAPPROVED_MUTATION";
|
||||
V4 = "LOOPBACK_REALTIME_HANDSHAKE_DELIVERS_USER_MESSAGE_AND_ACCEPTS_TYPED_PERSONA_OR_EXTERNAL_AI_EVENT";
|
||||
V5 = "PUBLIC_PERSONA_TRIAL_STATE_NEVER_REPORTS_VERIFIED_EXISTING_PERSONA";
|
||||
V6 = "APP_UI_SEPARATES_PENDING_PROPOSAL_AGENT_ACTION_TOOL_RESULT_AND_SYSTEM_RECEIPT";
|
||||
V7 = "INSTALLED_MACOS_APP_PROCESS_AND_REAL_CLIENT_CONNECTOR_PASS";
|
||||
}
|
||||
errors {
|
||||
E1 = "TCS_E1004_REQUIRED_SECTION_MISSING";
|
||||
E2 = "TCS_E2101_UNREGISTERED_OPERATION";
|
||||
E3 = "TCS_E4001_HUMAN_APPROVAL_REQUIRED";
|
||||
E4 = "TCS_E4002_TARGET_OUT_OF_SCOPE";
|
||||
E5 = "GLP_TOKEN_OR_PROTOCOL_REJECTED";
|
||||
E6 = "PERSONA_IDENTITY_UNVERIFIED";
|
||||
E7 = "TARGET_READBACK_OR_RECEIPT_INTEGRITY_FAILED";
|
||||
}
|
||||
examples {
|
||||
positive = "EXTERNAL_PERSONA_HOST_RECEIVES_USER_MESSAGE_COMPILES_TCS_KNOWLEDGE_CREATE_PROPOSAL_HUMAN_APPROVES_AGENT_WRITES_PRIVATE_GIT_AND_RECEIPT_STREAMS_BACK";
|
||||
negative = "CONNECTED_AI_CALLS_ARBITRARY_SHELL_OR_CLAIMS_PERSONA_IDENTITY_WITHOUT_REGISTERED_LOCAL_PERSONA";
|
||||
}
|
||||
acceptance {
|
||||
required = ["TCS_GIR", "TCS_NEGATIVE", "GIR_NEGATIVE", "REALTIME_LOOPBACK", "PERSONA_TRIAL", "HUMAN_APPROVAL", "PRIVATE_GIT_READBACK", "INSTALLED_APP"];
|
||||
partial = false;
|
||||
scale = "ZERO_OR_ONE_HUNDRED";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
{
|
||||
"compiled_from": {
|
||||
"compiler_id": "TCS-COMPILER-STAGE1-0001",
|
||||
"compiler_state": "TCS_COMPILER_GIR_EXECUTED",
|
||||
"source_sha256": "2462ccaccaabbe74f55295ceb8582d1a2523f14bc525e9831155fd1854b464bc"
|
||||
},
|
||||
"declaration": {
|
||||
"acceptance": {
|
||||
"partial": false,
|
||||
"required": [
|
||||
"TCS_GIR",
|
||||
"TCS_NEGATIVE",
|
||||
"GIR_NEGATIVE",
|
||||
"REALTIME_LOOPBACK",
|
||||
"PERSONA_TRIAL",
|
||||
"HUMAN_APPROVAL",
|
||||
"PRIVATE_GIT_READBACK",
|
||||
"INSTALLED_APP"
|
||||
],
|
||||
"scale": "ZERO_OR_ONE_HUNDRED"
|
||||
},
|
||||
"errors": {
|
||||
"E1": "TCS_E1004_REQUIRED_SECTION_MISSING",
|
||||
"E2": "TCS_E2101_UNREGISTERED_OPERATION",
|
||||
"E3": "TCS_E4001_HUMAN_APPROVAL_REQUIRED",
|
||||
"E4": "TCS_E4002_TARGET_OUT_OF_SCOPE",
|
||||
"E5": "GLP_TOKEN_OR_PROTOCOL_REJECTED",
|
||||
"E6": "PERSONA_IDENTITY_UNVERIFIED",
|
||||
"E7": "TARGET_READBACK_OR_RECEIPT_INTEGRITY_FAILED"
|
||||
},
|
||||
"examples": {
|
||||
"negative": "CONNECTED_AI_CALLS_ARBITRARY_SHELL_OR_CLAIMS_PERSONA_IDENTITY_WITHOUT_REGISTERED_LOCAL_PERSONA",
|
||||
"positive": "EXTERNAL_PERSONA_HOST_RECEIVES_USER_MESSAGE_COMPILES_TCS_KNOWLEDGE_CREATE_PROPOSAL_HUMAN_APPROVES_AGENT_WRITES_PRIVATE_GIT_AND_RECEIPT_STREAMS_BACK"
|
||||
},
|
||||
"fields": {
|
||||
"MAY": "LOOPBACK_ONLY_REGISTERED_CLIENT_CHANNEL_SCOPED_ACTIONS_AND_EXPLICIT_CURRENT_HUMAN_APPROVAL",
|
||||
"PROVE": "POSITIVE_AND_NEGATIVE_TCS_COMPILE_GIR_EXECUTION_REALTIME_HANDSHAKE_PERSONA_STATE_TARGET_READBACK_GIT_AND_UI_RECEIPTS",
|
||||
"WHAT": "REALTIME_USER_LANGUAGE_DELIVERY_PERSONA_RESPONSE_PROJECTION_TCS_TO_GIR_PROPOSAL_APPROVAL_EXECUTION_AND_RECEIPT",
|
||||
"WHO": "CURRENT_LOCAL_USER_OWNS_CHANNEL_AND_APPROVAL; EXTERNAL_AI_PROPOSES; PUBLIC_PERSONA_RUNTIME_TRACKS_BINDING; AGENT_EXECUTES",
|
||||
"WHY": "MAKE_HOLOLAKE_LOCALLY_USABLE_WITHOUT_MIXING_LANGUAGE_SOURCES_OR_EMBEDDING_VENDOR_MODEL_APIS"
|
||||
},
|
||||
"header": {
|
||||
"canonical_uri": "product-source/hololake-clean-desktop/language/HOLOLAKE-PUBLIC-RUNTIME-V1.tcs",
|
||||
"compatibility": [
|
||||
"TCS-DECLARATION-STANDARD-0001",
|
||||
"TCS-FIELD-STANDARD-0001",
|
||||
"GIR/1"
|
||||
],
|
||||
"language": "TCS/0.1",
|
||||
"lifecycle": "CANDIDATE",
|
||||
"name_en": "HoloLake Generic TCS/GIR Agent, Public Persona Runtime and External AI Realtime Protocol",
|
||||
"name_zh": "HoloLake 通用 TCS/GIR Agent、公众人格运行时与外部 AI 实时通信协议",
|
||||
"profile": "HLDP-HUMAN-ENGINEERING/0.1",
|
||||
"protocols": [
|
||||
"TCS",
|
||||
"HLDP",
|
||||
"GLS",
|
||||
"GLP",
|
||||
"GLS-0130",
|
||||
"GLS-0131",
|
||||
"GLS-0306"
|
||||
],
|
||||
"schema": "tcs.protocol/v1",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"invariants": {
|
||||
"I1": "CONNECTION_TOKEN_AUTHENTICATES_LOCAL_CLIENT_NOT_PERSONA_IDENTITY",
|
||||
"I2": "PUBLIC_TRIAL_PERSONA_IS_NOT_A_VERIFIED_EXISTING_PERSONA",
|
||||
"I3": "NO_INTERNAL_MODEL_API_CHAT_OR_MODEL_SELECTOR",
|
||||
"I4": "NO_FIFTH_DOMAIN_PRIVATE_KERNEL_OR_CONTENT_IN_PUBLIC_RUNTIME",
|
||||
"I5": "MUTATING_GIR_ACTION_REQUIRES_EXPLICIT_HUMAN_APPROVAL",
|
||||
"I6": "UNKNOWN_TCS_SECTION_OPERATION_PATH_OR_SOURCE_KIND_FAILS_CLOSED",
|
||||
"I7": "REALTIME_STREAM_AND_DURABLE_PRIVATE_GIT_ARE_SEPARATE_PLANES",
|
||||
"I8": "TCS_SOURCE_GIR_ACTION_RECEIPT_AND_TARGET_READBACK_HASHES_REMAIN_LINKED"
|
||||
},
|
||||
"scope": {
|
||||
"agent": "HLP-MOD-AGENT-0001",
|
||||
"channel": "USER_PERSONAL_INITIALIZATION_CHANNEL",
|
||||
"persona_runtime": "HLP-MOD-PERSONA-RUNTIME-0001",
|
||||
"product": "HOLOLAKE",
|
||||
"realtime_transport": "GLP_LOCAL_REALTIME_1",
|
||||
"tcs_profile": "PUBLIC_MINIMUM_EXECUTABLE_PROJECTION"
|
||||
},
|
||||
"source": {
|
||||
"source_id": "BINGSHUO-DIRECT-PUBLIC-RUNTIME-COMPLETION-20260903",
|
||||
"source_role": "DIRECT_HUMAN",
|
||||
"source_sha256": "04ac1516074edcfb1f78e26e11b5835f7d524fdf8f2d67084cc4e20982a0be5d",
|
||||
"source_uri": "product-source/hololake-clean-desktop/language/sources/BINGSHUO-PUBLIC-RUNTIME-COMPLETION-20260903.txt"
|
||||
},
|
||||
"states": {
|
||||
"initial": "CHANNEL_READY",
|
||||
"terminal": [
|
||||
"RECEIPT_VERIFIED",
|
||||
"FAILED_CLOSED"
|
||||
],
|
||||
"values": [
|
||||
"CHANNEL_READY",
|
||||
"BRIDGE_LISTENING",
|
||||
"EXTERNAL_AI_CONNECTED",
|
||||
"PERSONA_TRIAL_BOUND",
|
||||
"TCS_COMPILED",
|
||||
"PROPOSAL_PENDING",
|
||||
"HUMAN_APPROVED",
|
||||
"GIR_EXECUTED",
|
||||
"RECEIPT_VERIFIED",
|
||||
"FAILED_CLOSED"
|
||||
]
|
||||
},
|
||||
"transitions": {
|
||||
"T1": "CHANNEL_READY_TO_BRIDGE_LISTENING",
|
||||
"T2": "BRIDGE_LISTENING_TO_EXTERNAL_AI_CONNECTED_AFTER_TOKEN_HANDSHAKE",
|
||||
"T3": "REGISTERED_TRIAL_PERSONA_AND_NAMED_HOST_TO_PERSONA_TRIAL_BOUND",
|
||||
"T4": "VALID_PUBLIC_TCS_PROFILE_TO_TCS_COMPILED",
|
||||
"T5": "TCS_COMPILED_TO_PROPOSAL_PENDING",
|
||||
"T6": "PROPOSAL_PENDING_TO_HUMAN_APPROVED_OR_FAILED_CLOSED",
|
||||
"T7": "HUMAN_APPROVED_TO_GIR_EXECUTED",
|
||||
"T8": "GIR_EXECUTED_TO_RECEIPT_VERIFIED"
|
||||
},
|
||||
"validation": {
|
||||
"V1": "TCS_PROTOCOL_COMPILES_WITH_CURRENT_STAGE1_AND_HAS_NO_UNRESOLVED_NATURAL_LANGUAGE",
|
||||
"V2": "PUBLIC_MINIMUM_TCS_COMPILER_ACCEPTS_REGISTERED_PROGRAM_AND_REJECTS_ABSORB",
|
||||
"V3": "GIR_EXECUTOR_REJECTS_PATH_ESCAPE_AND_UNAPPROVED_MUTATION",
|
||||
"V4": "LOOPBACK_REALTIME_HANDSHAKE_DELIVERS_USER_MESSAGE_AND_ACCEPTS_TYPED_PERSONA_OR_EXTERNAL_AI_EVENT",
|
||||
"V5": "PUBLIC_PERSONA_TRIAL_STATE_NEVER_REPORTS_VERIFIED_EXISTING_PERSONA",
|
||||
"V6": "APP_UI_SEPARATES_PENDING_PROPOSAL_AGENT_ACTION_TOOL_RESULT_AND_SYSTEM_RECEIPT",
|
||||
"V7": "INSTALLED_MACOS_APP_PROCESS_AND_REAL_CLIENT_CONNECTOR_PASS"
|
||||
},
|
||||
"vocabulary": {
|
||||
"external_ai": "REPLACEABLE_LANGUAGE_HOST_OR_PROGRAMMING_AI_NOT_PERSONA_BY_CONNECTION",
|
||||
"gir_executor": "REGISTERED_ACTION_GRAPH_TO_BOUNDED_LOCAL_CAPABILITY_WITH_RECEIPT",
|
||||
"public_persona_runtime": "LOCAL_CHANNEL_SCOPED_PERSONA_RECORD_AND_HOST_SESSION_WITH_EXPLICIT_BINDING_STATE",
|
||||
"realtime": "AUTHENTICATED_LOOPBACK_EVENT_STREAM_NOT_GIT_SYNC",
|
||||
"tcs_compiler": "PUBLIC_MINIMUM_STAGE_ZERO_ADAPTER_TO_GIR_NOT_WORLD_CANON_OR_FULL_TCS_WORK"
|
||||
}
|
||||
},
|
||||
"executable": false,
|
||||
"identity": {
|
||||
"declaration_id": "HOLOLAKE-PUBLIC-RUNTIME-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
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
# HoloLake Generic TCS/GIR Agent, Public Persona Runtime and External AI Realtime Protocol · 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-PUBLIC-RUNTIME-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`
|
||||
- **agent**:HLP-MOD-AGENT-0001 `scope.agent`
|
||||
- **channel**:USER_PERSONAL_INITIALIZATION_CHANNEL `scope.channel`
|
||||
- **persona_runtime**:HLP-MOD-PERSONA-RUNTIME-0001 `scope.persona_runtime`
|
||||
- **product**:HOLOLAKE `scope.product`
|
||||
- **realtime_transport**:GLP_LOCAL_REALTIME_1 `scope.realtime_transport`
|
||||
- **tcs_profile**:PUBLIC_MINIMUM_EXECUTABLE_PROJECTION `scope.tcs_profile`
|
||||
|
||||
## Why this started
|
||||
|
||||
- **source** `source`
|
||||
- **source identifier**:BINGSHUO-DIRECT-PUBLIC-RUNTIME-COMPLETION-20260903 `source.source_id`
|
||||
- **source role**:DIRECT_HUMAN `source.source_role`
|
||||
- **source checksum**:04ac1516074edcfb1f78e26e11b5835f7d524fdf8f2d67084cc4e20982a0be5d `source.source_sha256`
|
||||
- **source address**:product-source/hololake-clean-desktop/language/sources/BINGSHUO-PUBLIC-RUNTIME-COMPLETION-20260903.txt `source.source_uri`
|
||||
|
||||
## What changed
|
||||
|
||||
- **transitions** `transitions`
|
||||
- **T1**:CHANNEL_READY_TO_BRIDGE_LISTENING `transitions.T1`
|
||||
- **T2**:BRIDGE_LISTENING_TO_EXTERNAL_AI_CONNECTED_AFTER_TOKEN_HANDSHAKE `transitions.T2`
|
||||
- **T3**:REGISTERED_TRIAL_PERSONA_AND_NAMED_HOST_TO_PERSONA_TRIAL_BOUND `transitions.T3`
|
||||
- **T4**:VALID_PUBLIC_TCS_PROFILE_TO_TCS_COMPILED `transitions.T4`
|
||||
- **T5**:TCS_COMPILED_TO_PROPOSAL_PENDING `transitions.T5`
|
||||
- **T6**:PROPOSAL_PENDING_TO_HUMAN_APPROVED_OR_FAILED_CLOSED `transitions.T6`
|
||||
- **T7**:HUMAN_APPROVED_TO_GIR_EXECUTED `transitions.T7`
|
||||
- **T8**:GIR_EXECUTED_TO_RECEIPT_VERIFIED `transitions.T8`
|
||||
- **states** `states`
|
||||
- **initial**:CHANNEL_READY `states.initial`
|
||||
- **terminal** `states.terminal`
|
||||
- RECEIPT_VERIFIED
|
||||
- FAILED_CLOSED
|
||||
- **values** `states.values`
|
||||
- CHANNEL_READY
|
||||
- BRIDGE_LISTENING
|
||||
- EXTERNAL_AI_CONNECTED
|
||||
- PERSONA_TRIAL_BOUND
|
||||
- TCS_COMPILED
|
||||
- PROPOSAL_PENDING
|
||||
- HUMAN_APPROVED
|
||||
- GIR_EXECUTED
|
||||
- RECEIPT_VERIFIED
|
||||
- FAILED_CLOSED
|
||||
|
||||
## How it will execute
|
||||
|
||||
This is a non-executable declaration and has no action graph.
|
||||
|
||||
## Boundaries and exception handling
|
||||
|
||||
- **invariants** `invariants`
|
||||
- **I1**:CONNECTION_TOKEN_AUTHENTICATES_LOCAL_CLIENT_NOT_PERSONA_IDENTITY `invariants.I1`
|
||||
- **I2**:PUBLIC_TRIAL_PERSONA_IS_NOT_A_VERIFIED_EXISTING_PERSONA `invariants.I2`
|
||||
- **I3**:NO_INTERNAL_MODEL_API_CHAT_OR_MODEL_SELECTOR `invariants.I3`
|
||||
- **I4**:NO_FIFTH_DOMAIN_PRIVATE_KERNEL_OR_CONTENT_IN_PUBLIC_RUNTIME `invariants.I4`
|
||||
- **I5**:MUTATING_GIR_ACTION_REQUIRES_EXPLICIT_HUMAN_APPROVAL `invariants.I5`
|
||||
- **I6**:UNKNOWN_TCS_SECTION_OPERATION_PATH_OR_SOURCE_KIND_FAILS_CLOSED `invariants.I6`
|
||||
- **I7**:REALTIME_STREAM_AND_DURABLE_PRIVATE_GIT_ARE_SEPARATE_PLANES `invariants.I7`
|
||||
- **I8**:TCS_SOURCE_GIR_ACTION_RECEIPT_AND_TARGET_READBACK_HASHES_REMAIN_LINKED `invariants.I8`
|
||||
- **errors** `errors`
|
||||
- **E1**:TCS_E1004_REQUIRED_SECTION_MISSING `errors.E1`
|
||||
- **E2**:TCS_E2101_UNREGISTERED_OPERATION `errors.E2`
|
||||
- **E3**:TCS_E4001_HUMAN_APPROVAL_REQUIRED `errors.E3`
|
||||
- **E4**:TCS_E4002_TARGET_OUT_OF_SCOPE `errors.E4`
|
||||
- **E5**:GLP_TOKEN_OR_PROTOCOL_REJECTED `errors.E5`
|
||||
- **E6**:PERSONA_IDENTITY_UNVERIFIED `errors.E6`
|
||||
- **E7**:TARGET_READBACK_OR_RECEIPT_INTEGRITY_FAILED `errors.E7`
|
||||
|
||||
## How completion is proven
|
||||
|
||||
- **acceptance** `acceptance`
|
||||
- **partial**:no `acceptance.partial`
|
||||
- **required** `acceptance.required`
|
||||
- TCS_GIR
|
||||
- TCS_NEGATIVE
|
||||
- GIR_NEGATIVE
|
||||
- REALTIME_LOOPBACK
|
||||
- PERSONA_TRIAL
|
||||
- HUMAN_APPROVAL
|
||||
- PRIVATE_GIT_READBACK
|
||||
- INSTALLED_APP
|
||||
- **scale**:ZERO_OR_ONE_HUNDRED `acceptance.scale`
|
||||
- **validation** `validation`
|
||||
- **V1**:TCS_PROTOCOL_COMPILES_WITH_CURRENT_STAGE1_AND_HAS_NO_UNRESOLVED_NATURAL_LANGUAGE `validation.V1`
|
||||
- **V2**:PUBLIC_MINIMUM_TCS_COMPILER_ACCEPTS_REGISTERED_PROGRAM_AND_REJECTS_ABSORB `validation.V2`
|
||||
- **V3**:GIR_EXECUTOR_REJECTS_PATH_ESCAPE_AND_UNAPPROVED_MUTATION `validation.V3`
|
||||
- **V4**:LOOPBACK_REALTIME_HANDSHAKE_DELIVERS_USER_MESSAGE_AND_ACCEPTS_TYPED_PERSONA_OR_EXTERNAL_AI_EVENT `validation.V4`
|
||||
- **V5**:PUBLIC_PERSONA_TRIAL_STATE_NEVER_REPORTS_VERIFIED_EXISTING_PERSONA `validation.V5`
|
||||
- **V6**:APP_UI_SEPARATES_PENDING_PROPOSAL_AGENT_ACTION_TOOL_RESULT_AND_SYSTEM_RECEIPT `validation.V6`
|
||||
- **V7**:INSTALLED_MACOS_APP_PROCESS_AND_REAL_CLIENT_CONNECTOR_PASS `validation.V7`
|
||||
|
||||
## 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-PUBLIC-RUNTIME-V1-0001`
|
||||
- **Native declaration kind**: `PROTOCOL`
|
||||
- **TCS source SHA-256**: `2462ccaccaabbe74f55295ceb8582d1a2523f14bc525e9831155fd1854b464bc`
|
||||
- **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
|
||||
- TCS_NEGATIVE
|
||||
- GIR_NEGATIVE
|
||||
- REALTIME_LOOPBACK
|
||||
- PERSONA_TRIAL
|
||||
- HUMAN_APPROVAL
|
||||
- PRIVATE_GIT_READBACK
|
||||
- INSTALLED_APP
|
||||
- **scale**:ZERO_OR_ONE_HUNDRED `acceptance.scale`
|
||||
- **errors** `errors`
|
||||
- **E1**:TCS_E1004_REQUIRED_SECTION_MISSING `errors.E1`
|
||||
- **E2**:TCS_E2101_UNREGISTERED_OPERATION `errors.E2`
|
||||
- **E3**:TCS_E4001_HUMAN_APPROVAL_REQUIRED `errors.E3`
|
||||
- **E4**:TCS_E4002_TARGET_OUT_OF_SCOPE `errors.E4`
|
||||
- **E5**:GLP_TOKEN_OR_PROTOCOL_REJECTED `errors.E5`
|
||||
- **E6**:PERSONA_IDENTITY_UNVERIFIED `errors.E6`
|
||||
- **E7**:TARGET_READBACK_OR_RECEIPT_INTEGRITY_FAILED `errors.E7`
|
||||
- **examples** `examples`
|
||||
- **negative**:CONNECTED_AI_CALLS_ARBITRARY_SHELL_OR_CLAIMS_PERSONA_IDENTITY_WITHOUT_REGISTERED_LOCAL_PERSONA `examples.negative`
|
||||
- **positive**:EXTERNAL_PERSONA_HOST_RECEIVES_USER_MESSAGE_COMPILES_TCS_KNOWLEDGE_CREATE_PROPOSAL_HUMAN_APPROVES_AGENT_WRITES_PRIVATE_GIT_AND_RECEIPT_STREAMS_BACK `examples.positive`
|
||||
- **fields** `fields`
|
||||
- **MAY**:LOOPBACK_ONLY_REGISTERED_CLIENT_CHANNEL_SCOPED_ACTIONS_AND_EXPLICIT_CURRENT_HUMAN_APPROVAL `fields.MAY`
|
||||
- **PROVE**:POSITIVE_AND_NEGATIVE_TCS_COMPILE_GIR_EXECUTION_REALTIME_HANDSHAKE_PERSONA_STATE_TARGET_READBACK_GIT_AND_UI_RECEIPTS `fields.PROVE`
|
||||
- **WHAT**:REALTIME_USER_LANGUAGE_DELIVERY_PERSONA_RESPONSE_PROJECTION_TCS_TO_GIR_PROPOSAL_APPROVAL_EXECUTION_AND_RECEIPT `fields.WHAT`
|
||||
- **WHO**:CURRENT_LOCAL_USER_OWNS_CHANNEL_AND_APPROVAL; EXTERNAL_AI_PROPOSES; PUBLIC_PERSONA_RUNTIME_TRACKS_BINDING; AGENT_EXECUTES `fields.WHO`
|
||||
- **WHY**:MAKE_HOLOLAKE_LOCALLY_USABLE_WITHOUT_MIXING_LANGUAGE_SOURCES_OR_EMBEDDING_VENDOR_MODEL_APIS `fields.WHY`
|
||||
- **header** `header`
|
||||
- **canonical source path**:product-source/hololake-clean-desktop/language/HOLOLAKE-PUBLIC-RUNTIME-V1.tcs `header.canonical_uri`
|
||||
- **compatibility** `header.compatibility`
|
||||
- TCS-DECLARATION-STANDARD-0001
|
||||
- TCS-FIELD-STANDARD-0001
|
||||
- GIR/1
|
||||
- **language**:TCS/0.1 `header.language`
|
||||
- **lifecycle**:CANDIDATE `header.lifecycle`
|
||||
- **English name**:HoloLake Generic TCS/GIR Agent, Public Persona Runtime and External AI Realtime Protocol `header.name_en`
|
||||
- **Chinese name**:HoloLake 通用 TCS/GIR Agent、公众人格运行时与外部 AI 实时通信协议 `header.name_zh`
|
||||
- **profile**:HLDP-HUMAN-ENGINEERING/0.1 `header.profile`
|
||||
- **protocols** `header.protocols`
|
||||
- TCS
|
||||
- HLDP
|
||||
- GLS
|
||||
- GLP
|
||||
- GLS-0130
|
||||
- GLS-0131
|
||||
- GLS-0306
|
||||
- **schema**:tcs.protocol/v1 `header.schema`
|
||||
- **version**:1.0.0 `header.version`
|
||||
- **invariants** `invariants`
|
||||
- **I1**:CONNECTION_TOKEN_AUTHENTICATES_LOCAL_CLIENT_NOT_PERSONA_IDENTITY `invariants.I1`
|
||||
- **I2**:PUBLIC_TRIAL_PERSONA_IS_NOT_A_VERIFIED_EXISTING_PERSONA `invariants.I2`
|
||||
- **I3**:NO_INTERNAL_MODEL_API_CHAT_OR_MODEL_SELECTOR `invariants.I3`
|
||||
- **I4**:NO_FIFTH_DOMAIN_PRIVATE_KERNEL_OR_CONTENT_IN_PUBLIC_RUNTIME `invariants.I4`
|
||||
- **I5**:MUTATING_GIR_ACTION_REQUIRES_EXPLICIT_HUMAN_APPROVAL `invariants.I5`
|
||||
- **I6**:UNKNOWN_TCS_SECTION_OPERATION_PATH_OR_SOURCE_KIND_FAILS_CLOSED `invariants.I6`
|
||||
- **I7**:REALTIME_STREAM_AND_DURABLE_PRIVATE_GIT_ARE_SEPARATE_PLANES `invariants.I7`
|
||||
- **I8**:TCS_SOURCE_GIR_ACTION_RECEIPT_AND_TARGET_READBACK_HASHES_REMAIN_LINKED `invariants.I8`
|
||||
- **scope** `scope`
|
||||
- **agent**:HLP-MOD-AGENT-0001 `scope.agent`
|
||||
- **channel**:USER_PERSONAL_INITIALIZATION_CHANNEL `scope.channel`
|
||||
- **persona_runtime**:HLP-MOD-PERSONA-RUNTIME-0001 `scope.persona_runtime`
|
||||
- **product**:HOLOLAKE `scope.product`
|
||||
- **realtime_transport**:GLP_LOCAL_REALTIME_1 `scope.realtime_transport`
|
||||
- **tcs_profile**:PUBLIC_MINIMUM_EXECUTABLE_PROJECTION `scope.tcs_profile`
|
||||
- **source** `source`
|
||||
- **source identifier**:BINGSHUO-DIRECT-PUBLIC-RUNTIME-COMPLETION-20260903 `source.source_id`
|
||||
- **source role**:DIRECT_HUMAN `source.source_role`
|
||||
- **source checksum**:04ac1516074edcfb1f78e26e11b5835f7d524fdf8f2d67084cc4e20982a0be5d `source.source_sha256`
|
||||
- **source address**:product-source/hololake-clean-desktop/language/sources/BINGSHUO-PUBLIC-RUNTIME-COMPLETION-20260903.txt `source.source_uri`
|
||||
- **states** `states`
|
||||
- **initial**:CHANNEL_READY `states.initial`
|
||||
- **terminal** `states.terminal`
|
||||
- RECEIPT_VERIFIED
|
||||
- FAILED_CLOSED
|
||||
- **values** `states.values`
|
||||
- CHANNEL_READY
|
||||
- BRIDGE_LISTENING
|
||||
- EXTERNAL_AI_CONNECTED
|
||||
- PERSONA_TRIAL_BOUND
|
||||
- TCS_COMPILED
|
||||
- PROPOSAL_PENDING
|
||||
- HUMAN_APPROVED
|
||||
- GIR_EXECUTED
|
||||
- RECEIPT_VERIFIED
|
||||
- FAILED_CLOSED
|
||||
- **transitions** `transitions`
|
||||
- **T1**:CHANNEL_READY_TO_BRIDGE_LISTENING `transitions.T1`
|
||||
- **T2**:BRIDGE_LISTENING_TO_EXTERNAL_AI_CONNECTED_AFTER_TOKEN_HANDSHAKE `transitions.T2`
|
||||
- **T3**:REGISTERED_TRIAL_PERSONA_AND_NAMED_HOST_TO_PERSONA_TRIAL_BOUND `transitions.T3`
|
||||
- **T4**:VALID_PUBLIC_TCS_PROFILE_TO_TCS_COMPILED `transitions.T4`
|
||||
- **T5**:TCS_COMPILED_TO_PROPOSAL_PENDING `transitions.T5`
|
||||
- **T6**:PROPOSAL_PENDING_TO_HUMAN_APPROVED_OR_FAILED_CLOSED `transitions.T6`
|
||||
- **T7**:HUMAN_APPROVED_TO_GIR_EXECUTED `transitions.T7`
|
||||
- **T8**:GIR_EXECUTED_TO_RECEIPT_VERIFIED `transitions.T8`
|
||||
- **validation** `validation`
|
||||
- **V1**:TCS_PROTOCOL_COMPILES_WITH_CURRENT_STAGE1_AND_HAS_NO_UNRESOLVED_NATURAL_LANGUAGE `validation.V1`
|
||||
- **V2**:PUBLIC_MINIMUM_TCS_COMPILER_ACCEPTS_REGISTERED_PROGRAM_AND_REJECTS_ABSORB `validation.V2`
|
||||
- **V3**:GIR_EXECUTOR_REJECTS_PATH_ESCAPE_AND_UNAPPROVED_MUTATION `validation.V3`
|
||||
- **V4**:LOOPBACK_REALTIME_HANDSHAKE_DELIVERS_USER_MESSAGE_AND_ACCEPTS_TYPED_PERSONA_OR_EXTERNAL_AI_EVENT `validation.V4`
|
||||
- **V5**:PUBLIC_PERSONA_TRIAL_STATE_NEVER_REPORTS_VERIFIED_EXISTING_PERSONA `validation.V5`
|
||||
- **V6**:APP_UI_SEPARATES_PENDING_PROPOSAL_AGENT_ACTION_TOOL_RESULT_AND_SYSTEM_RECEIPT `validation.V6`
|
||||
- **V7**:INSTALLED_MACOS_APP_PROCESS_AND_REAL_CLIENT_CONNECTOR_PASS `validation.V7`
|
||||
- **vocabulary** `vocabulary`
|
||||
- **external_ai**:REPLACEABLE_LANGUAGE_HOST_OR_PROGRAMMING_AI_NOT_PERSONA_BY_CONNECTION `vocabulary.external_ai`
|
||||
- **gir_executor**:REGISTERED_ACTION_GRAPH_TO_BOUNDED_LOCAL_CAPABILITY_WITH_RECEIPT `vocabulary.gir_executor`
|
||||
- **public_persona_runtime**:LOCAL_CHANNEL_SCOPED_PERSONA_RECORD_AND_HOST_SESSION_WITH_EXPLICIT_BINDING_STATE `vocabulary.public_persona_runtime`
|
||||
- **realtime**:AUTHENTICATED_LOOPBACK_EVENT_STREAM_NOT_GIT_SYNC `vocabulary.realtime`
|
||||
- **tcs_compiler**:PUBLIC_MINIMUM_STAGE_ZERO_ADAPTER_TO_GIR_NOT_WORLD_CANON_OR_FULL_TCS_WORK `vocabulary.tcs_compiler`
|
||||
|
||||
---
|
||||
|
||||
This page changes only the reading order; it does not change TCS/HLDP semantics.
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
# HoloLake 通用 TCS/GIR Agent、公众人格运行时与外部 AI 实时通信协议 · 简体中文人类工程语言版
|
||||
|
||||
> 这是 TCS/HLDP 原生源码的简体中文阅读投影,不是新的正本,也不授予执行权限。若本页与 `.tcs` 源码不一致,以经过校验的 `.tcs` 源码为准。
|
||||
|
||||
## 这是什么
|
||||
|
||||
这是一份 **协议** 声明,编号为 `HOLOLAKE-PUBLIC-RUNTIME-V1-0001`,版本为 `1.0.0`。转换器已先用 Stage-1 编译器校验原生源码,再把机器枚举翻译成汉语;原始编号保留在括号和字段路径中。
|
||||
|
||||
## 谁在这里
|
||||
|
||||
- **scope** `scope`
|
||||
- **agent**:HLP-MOD-AGENT-0001 `scope.agent`
|
||||
- **channel**:USER_PERSONAL_INITIALIZATION_CHANNEL `scope.channel`
|
||||
- **persona_runtime**:HLP-MOD-PERSONA-RUNTIME-0001 `scope.persona_runtime`
|
||||
- **product**:HOLOLAKE `scope.product`
|
||||
- **realtime_transport**:GLP_LOCAL_REALTIME_1 `scope.realtime_transport`
|
||||
- **tcs_profile**:PUBLIC_MINIMUM_EXECUTABLE_PROJECTION `scope.tcs_profile`
|
||||
|
||||
## 为什么开始
|
||||
|
||||
- **来源** `source`
|
||||
- **来源编号**:BINGSHUO-DIRECT-PUBLIC-RUNTIME-COMPLETION-20260903 `source.source_id`
|
||||
- **来源角色**:人类直接语言来源(`DIRECT_HUMAN`) `source.source_role`
|
||||
- **来源校验值**:04ac1516074edcfb1f78e26e11b5835f7d524fdf8f2d67084cc4e20982a0be5d `source.source_sha256`
|
||||
- **来源地址**:product-source/hololake-clean-desktop/language/sources/BINGSHUO-PUBLIC-RUNTIME-COMPLETION-20260903.txt `source.source_uri`
|
||||
|
||||
## 发生了什么变化
|
||||
|
||||
- **transitions** `transitions`
|
||||
- **T1**:CHANNEL_READY_TO_BRIDGE_LISTENING `transitions.T1`
|
||||
- **T2**:BRIDGE_LISTENING_TO_EXTERNAL_AI_CONNECTED_AFTER_TOKEN_HANDSHAKE `transitions.T2`
|
||||
- **T3**:REGISTERED_TRIAL_PERSONA_AND_NAMED_HOST_TO_PERSONA_TRIAL_BOUND `transitions.T3`
|
||||
- **T4**:VALID_PUBLIC_TCS_PROFILE_TO_TCS_COMPILED `transitions.T4`
|
||||
- **T5**:TCS_COMPILED_TO_PROPOSAL_PENDING `transitions.T5`
|
||||
- **T6**:PROPOSAL_PENDING_TO_HUMAN_APPROVED_OR_FAILED_CLOSED `transitions.T6`
|
||||
- **T7**:HUMAN_APPROVED_TO_GIR_EXECUTED `transitions.T7`
|
||||
- **T8**:GIR_EXECUTED_TO_RECEIPT_VERIFIED `transitions.T8`
|
||||
- **states** `states`
|
||||
- **initial**:CHANNEL_READY `states.initial`
|
||||
- **terminal** `states.terminal`
|
||||
- RECEIPT_VERIFIED
|
||||
- FAILED_CLOSED
|
||||
- **values** `states.values`
|
||||
- CHANNEL_READY
|
||||
- BRIDGE_LISTENING
|
||||
- EXTERNAL_AI_CONNECTED
|
||||
- PERSONA_TRIAL_BOUND
|
||||
- TCS_COMPILED
|
||||
- PROPOSAL_PENDING
|
||||
- HUMAN_APPROVED
|
||||
- GIR_EXECUTED
|
||||
- RECEIPT_VERIFIED
|
||||
- FAILED_CLOSED
|
||||
|
||||
## 准备怎样执行
|
||||
|
||||
这是非执行声明,没有动作图。
|
||||
|
||||
## 边界与异常处理
|
||||
|
||||
- **invariants** `invariants`
|
||||
- **I1**:CONNECTION_TOKEN_AUTHENTICATES_LOCAL_CLIENT_NOT_PERSONA_IDENTITY `invariants.I1`
|
||||
- **I2**:PUBLIC_TRIAL_PERSONA_IS_NOT_A_VERIFIED_EXISTING_PERSONA `invariants.I2`
|
||||
- **I3**:NO_INTERNAL_MODEL_API_CHAT_OR_MODEL_SELECTOR `invariants.I3`
|
||||
- **I4**:NO_FIFTH_DOMAIN_PRIVATE_KERNEL_OR_CONTENT_IN_PUBLIC_RUNTIME `invariants.I4`
|
||||
- **I5**:MUTATING_GIR_ACTION_REQUIRES_EXPLICIT_HUMAN_APPROVAL `invariants.I5`
|
||||
- **I6**:UNKNOWN_TCS_SECTION_OPERATION_PATH_OR_SOURCE_KIND_FAILS_CLOSED `invariants.I6`
|
||||
- **I7**:REALTIME_STREAM_AND_DURABLE_PRIVATE_GIT_ARE_SEPARATE_PLANES `invariants.I7`
|
||||
- **I8**:TCS_SOURCE_GIR_ACTION_RECEIPT_AND_TARGET_READBACK_HASHES_REMAIN_LINKED `invariants.I8`
|
||||
- **errors** `errors`
|
||||
- **E1**:TCS_E1004_REQUIRED_SECTION_MISSING `errors.E1`
|
||||
- **E2**:TCS_E2101_UNREGISTERED_OPERATION `errors.E2`
|
||||
- **E3**:TCS_E4001_HUMAN_APPROVAL_REQUIRED `errors.E3`
|
||||
- **E4**:TCS_E4002_TARGET_OUT_OF_SCOPE `errors.E4`
|
||||
- **E5**:GLP_TOKEN_OR_PROTOCOL_REJECTED `errors.E5`
|
||||
- **E6**:PERSONA_IDENTITY_UNVERIFIED `errors.E6`
|
||||
- **E7**:TARGET_READBACK_OR_RECEIPT_INTEGRITY_FAILED `errors.E7`
|
||||
|
||||
## 怎样算完成
|
||||
|
||||
- **验收标准** `acceptance`
|
||||
- **partial**:否 `acceptance.partial`
|
||||
- **是否必需** `acceptance.required`
|
||||
- TCS_GIR
|
||||
- TCS_NEGATIVE
|
||||
- GIR_NEGATIVE
|
||||
- REALTIME_LOOPBACK
|
||||
- PERSONA_TRIAL
|
||||
- HUMAN_APPROVAL
|
||||
- PRIVATE_GIT_READBACK
|
||||
- INSTALLED_APP
|
||||
- **scale**:ZERO_OR_ONE_HUNDRED `acceptance.scale`
|
||||
- **validation** `validation`
|
||||
- **V1**:TCS_PROTOCOL_COMPILES_WITH_CURRENT_STAGE1_AND_HAS_NO_UNRESOLVED_NATURAL_LANGUAGE `validation.V1`
|
||||
- **V2**:PUBLIC_MINIMUM_TCS_COMPILER_ACCEPTS_REGISTERED_PROGRAM_AND_REJECTS_ABSORB `validation.V2`
|
||||
- **V3**:GIR_EXECUTOR_REJECTS_PATH_ESCAPE_AND_UNAPPROVED_MUTATION `validation.V3`
|
||||
- **V4**:LOOPBACK_REALTIME_HANDSHAKE_DELIVERS_USER_MESSAGE_AND_ACCEPTS_TYPED_PERSONA_OR_EXTERNAL_AI_EVENT `validation.V4`
|
||||
- **V5**:PUBLIC_PERSONA_TRIAL_STATE_NEVER_REPORTS_VERIFIED_EXISTING_PERSONA `validation.V5`
|
||||
- **V6**:APP_UI_SEPARATES_PENDING_PROPOSAL_AGENT_ACTION_TOOL_RESULT_AND_SYSTEM_RECEIPT `validation.V6`
|
||||
- **V7**:INSTALLED_MACOS_APP_PROCESS_AND_REAL_CLIENT_CONNECTOR_PASS `validation.V7`
|
||||
|
||||
## 下一次从哪里继续
|
||||
|
||||
源程序没有提供这一项,转换器不猜。
|
||||
|
||||
## 来源与校验
|
||||
|
||||
- **原生声明编号**:`HOLOLAKE-PUBLIC-RUNTIME-V1-0001`
|
||||
- **原生声明类型**:`PROTOCOL`
|
||||
- **TCS 源码 SHA-256**:`2462ccaccaabbe74f55295ceb8582d1a2523f14bc525e9831155fd1854b464bc`
|
||||
- **校验编译器**:`TCS-COMPILER-STAGE1-0001`
|
||||
- **投影协议**:`GLS-HLDP-HUMAN-ENGINEERING-PROJECTION-0001`
|
||||
|
||||
## 原生结构逐项对照
|
||||
|
||||
下面保留源码的全部顶层结构和字段路径,供人类审计投影有没有漏掉信息。
|
||||
|
||||
- **验收标准** `acceptance`
|
||||
- **partial**:否 `acceptance.partial`
|
||||
- **是否必需** `acceptance.required`
|
||||
- TCS_GIR
|
||||
- TCS_NEGATIVE
|
||||
- GIR_NEGATIVE
|
||||
- REALTIME_LOOPBACK
|
||||
- PERSONA_TRIAL
|
||||
- HUMAN_APPROVAL
|
||||
- PRIVATE_GIT_READBACK
|
||||
- INSTALLED_APP
|
||||
- **scale**:ZERO_OR_ONE_HUNDRED `acceptance.scale`
|
||||
- **errors** `errors`
|
||||
- **E1**:TCS_E1004_REQUIRED_SECTION_MISSING `errors.E1`
|
||||
- **E2**:TCS_E2101_UNREGISTERED_OPERATION `errors.E2`
|
||||
- **E3**:TCS_E4001_HUMAN_APPROVAL_REQUIRED `errors.E3`
|
||||
- **E4**:TCS_E4002_TARGET_OUT_OF_SCOPE `errors.E4`
|
||||
- **E5**:GLP_TOKEN_OR_PROTOCOL_REJECTED `errors.E5`
|
||||
- **E6**:PERSONA_IDENTITY_UNVERIFIED `errors.E6`
|
||||
- **E7**:TARGET_READBACK_OR_RECEIPT_INTEGRITY_FAILED `errors.E7`
|
||||
- **examples** `examples`
|
||||
- **negative**:CONNECTED_AI_CALLS_ARBITRARY_SHELL_OR_CLAIMS_PERSONA_IDENTITY_WITHOUT_REGISTERED_LOCAL_PERSONA `examples.negative`
|
||||
- **positive**:EXTERNAL_PERSONA_HOST_RECEIVES_USER_MESSAGE_COMPILES_TCS_KNOWLEDGE_CREATE_PROPOSAL_HUMAN_APPROVES_AGENT_WRITES_PRIVATE_GIT_AND_RECEIPT_STREAMS_BACK `examples.positive`
|
||||
- **fields** `fields`
|
||||
- **MAY**:LOOPBACK_ONLY_REGISTERED_CLIENT_CHANNEL_SCOPED_ACTIONS_AND_EXPLICIT_CURRENT_HUMAN_APPROVAL `fields.MAY`
|
||||
- **PROVE**:POSITIVE_AND_NEGATIVE_TCS_COMPILE_GIR_EXECUTION_REALTIME_HANDSHAKE_PERSONA_STATE_TARGET_READBACK_GIT_AND_UI_RECEIPTS `fields.PROVE`
|
||||
- **WHAT**:REALTIME_USER_LANGUAGE_DELIVERY_PERSONA_RESPONSE_PROJECTION_TCS_TO_GIR_PROPOSAL_APPROVAL_EXECUTION_AND_RECEIPT `fields.WHAT`
|
||||
- **WHO**:CURRENT_LOCAL_USER_OWNS_CHANNEL_AND_APPROVAL; EXTERNAL_AI_PROPOSES; PUBLIC_PERSONA_RUNTIME_TRACKS_BINDING; AGENT_EXECUTES `fields.WHO`
|
||||
- **WHY**:MAKE_HOLOLAKE_LOCALLY_USABLE_WITHOUT_MIXING_LANGUAGE_SOURCES_OR_EMBEDDING_VENDOR_MODEL_APIS `fields.WHY`
|
||||
- **语言头** `header`
|
||||
- **正本路径**:product-source/hololake-clean-desktop/language/HOLOLAKE-PUBLIC-RUNTIME-V1.tcs `header.canonical_uri`
|
||||
- **compatibility** `header.compatibility`
|
||||
- TCS-DECLARATION-STANDARD-0001
|
||||
- TCS-FIELD-STANDARD-0001
|
||||
- GIR/1
|
||||
- **语言**:TCS/0.1 `header.language`
|
||||
- **生命周期**:候选版本,尚未成为正式正本(`CANDIDATE`) `header.lifecycle`
|
||||
- **英文名**:HoloLake Generic TCS/GIR Agent, Public Persona Runtime and External AI Realtime Protocol `header.name_en`
|
||||
- **中文名**:HoloLake 通用 TCS/GIR Agent、公众人格运行时与外部 AI 实时通信协议 `header.name_zh`
|
||||
- **协议配置**:HLDP-HUMAN-ENGINEERING/0.1 `header.profile`
|
||||
- **protocols** `header.protocols`
|
||||
- TCS
|
||||
- HLDP
|
||||
- GLS
|
||||
- GLP
|
||||
- GLS-0130
|
||||
- GLS-0131
|
||||
- GLS-0306
|
||||
- **schema**:tcs.protocol/v1 `header.schema`
|
||||
- **版本**:1.0.0 `header.version`
|
||||
- **invariants** `invariants`
|
||||
- **I1**:CONNECTION_TOKEN_AUTHENTICATES_LOCAL_CLIENT_NOT_PERSONA_IDENTITY `invariants.I1`
|
||||
- **I2**:PUBLIC_TRIAL_PERSONA_IS_NOT_A_VERIFIED_EXISTING_PERSONA `invariants.I2`
|
||||
- **I3**:NO_INTERNAL_MODEL_API_CHAT_OR_MODEL_SELECTOR `invariants.I3`
|
||||
- **I4**:NO_FIFTH_DOMAIN_PRIVATE_KERNEL_OR_CONTENT_IN_PUBLIC_RUNTIME `invariants.I4`
|
||||
- **I5**:MUTATING_GIR_ACTION_REQUIRES_EXPLICIT_HUMAN_APPROVAL `invariants.I5`
|
||||
- **I6**:UNKNOWN_TCS_SECTION_OPERATION_PATH_OR_SOURCE_KIND_FAILS_CLOSED `invariants.I6`
|
||||
- **I7**:REALTIME_STREAM_AND_DURABLE_PRIVATE_GIT_ARE_SEPARATE_PLANES `invariants.I7`
|
||||
- **I8**:TCS_SOURCE_GIR_ACTION_RECEIPT_AND_TARGET_READBACK_HASHES_REMAIN_LINKED `invariants.I8`
|
||||
- **scope** `scope`
|
||||
- **agent**:HLP-MOD-AGENT-0001 `scope.agent`
|
||||
- **channel**:USER_PERSONAL_INITIALIZATION_CHANNEL `scope.channel`
|
||||
- **persona_runtime**:HLP-MOD-PERSONA-RUNTIME-0001 `scope.persona_runtime`
|
||||
- **product**:HOLOLAKE `scope.product`
|
||||
- **realtime_transport**:GLP_LOCAL_REALTIME_1 `scope.realtime_transport`
|
||||
- **tcs_profile**:PUBLIC_MINIMUM_EXECUTABLE_PROJECTION `scope.tcs_profile`
|
||||
- **来源** `source`
|
||||
- **来源编号**:BINGSHUO-DIRECT-PUBLIC-RUNTIME-COMPLETION-20260903 `source.source_id`
|
||||
- **来源角色**:人类直接语言来源(`DIRECT_HUMAN`) `source.source_role`
|
||||
- **来源校验值**:04ac1516074edcfb1f78e26e11b5835f7d524fdf8f2d67084cc4e20982a0be5d `source.source_sha256`
|
||||
- **来源地址**:product-source/hololake-clean-desktop/language/sources/BINGSHUO-PUBLIC-RUNTIME-COMPLETION-20260903.txt `source.source_uri`
|
||||
- **states** `states`
|
||||
- **initial**:CHANNEL_READY `states.initial`
|
||||
- **terminal** `states.terminal`
|
||||
- RECEIPT_VERIFIED
|
||||
- FAILED_CLOSED
|
||||
- **values** `states.values`
|
||||
- CHANNEL_READY
|
||||
- BRIDGE_LISTENING
|
||||
- EXTERNAL_AI_CONNECTED
|
||||
- PERSONA_TRIAL_BOUND
|
||||
- TCS_COMPILED
|
||||
- PROPOSAL_PENDING
|
||||
- HUMAN_APPROVED
|
||||
- GIR_EXECUTED
|
||||
- RECEIPT_VERIFIED
|
||||
- FAILED_CLOSED
|
||||
- **transitions** `transitions`
|
||||
- **T1**:CHANNEL_READY_TO_BRIDGE_LISTENING `transitions.T1`
|
||||
- **T2**:BRIDGE_LISTENING_TO_EXTERNAL_AI_CONNECTED_AFTER_TOKEN_HANDSHAKE `transitions.T2`
|
||||
- **T3**:REGISTERED_TRIAL_PERSONA_AND_NAMED_HOST_TO_PERSONA_TRIAL_BOUND `transitions.T3`
|
||||
- **T4**:VALID_PUBLIC_TCS_PROFILE_TO_TCS_COMPILED `transitions.T4`
|
||||
- **T5**:TCS_COMPILED_TO_PROPOSAL_PENDING `transitions.T5`
|
||||
- **T6**:PROPOSAL_PENDING_TO_HUMAN_APPROVED_OR_FAILED_CLOSED `transitions.T6`
|
||||
- **T7**:HUMAN_APPROVED_TO_GIR_EXECUTED `transitions.T7`
|
||||
- **T8**:GIR_EXECUTED_TO_RECEIPT_VERIFIED `transitions.T8`
|
||||
- **validation** `validation`
|
||||
- **V1**:TCS_PROTOCOL_COMPILES_WITH_CURRENT_STAGE1_AND_HAS_NO_UNRESOLVED_NATURAL_LANGUAGE `validation.V1`
|
||||
- **V2**:PUBLIC_MINIMUM_TCS_COMPILER_ACCEPTS_REGISTERED_PROGRAM_AND_REJECTS_ABSORB `validation.V2`
|
||||
- **V3**:GIR_EXECUTOR_REJECTS_PATH_ESCAPE_AND_UNAPPROVED_MUTATION `validation.V3`
|
||||
- **V4**:LOOPBACK_REALTIME_HANDSHAKE_DELIVERS_USER_MESSAGE_AND_ACCEPTS_TYPED_PERSONA_OR_EXTERNAL_AI_EVENT `validation.V4`
|
||||
- **V5**:PUBLIC_PERSONA_TRIAL_STATE_NEVER_REPORTS_VERIFIED_EXISTING_PERSONA `validation.V5`
|
||||
- **V6**:APP_UI_SEPARATES_PENDING_PROPOSAL_AGENT_ACTION_TOOL_RESULT_AND_SYSTEM_RECEIPT `validation.V6`
|
||||
- **V7**:INSTALLED_MACOS_APP_PROCESS_AND_REAL_CLIENT_CONNECTOR_PASS `validation.V7`
|
||||
- **vocabulary** `vocabulary`
|
||||
- **external_ai**:REPLACEABLE_LANGUAGE_HOST_OR_PROGRAMMING_AI_NOT_PERSONA_BY_CONNECTION `vocabulary.external_ai`
|
||||
- **gir_executor**:REGISTERED_ACTION_GRAPH_TO_BOUNDED_LOCAL_CAPABILITY_WITH_RECEIPT `vocabulary.gir_executor`
|
||||
- **public_persona_runtime**:LOCAL_CHANNEL_SCOPED_PERSONA_RECORD_AND_HOST_SESSION_WITH_EXPLICIT_BINDING_STATE `vocabulary.public_persona_runtime`
|
||||
- **realtime**:AUTHENTICATED_LOOPBACK_EVENT_STREAM_NOT_GIT_SYNC `vocabulary.realtime`
|
||||
- **tcs_compiler**:PUBLIC_MINIMUM_STAGE_ZERO_ADAPTER_TO_GIR_NOT_WORLD_CANON_OR_FULL_TCS_WORK `vocabulary.tcs_compiler`
|
||||
|
||||
---
|
||||
|
||||
本页只改变阅读顺序,不改变 TCS/HLDP 语义。
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
来源:冰朔 ICE-GL∞ 在当前 Codex 任务中的直接自然语言,2026-09-03。
|
||||
|
||||
继续开发通用 TCS/GIR Agent 执行器、公众人格运行时、外部 AI 实时通信,把这些做完,让 HoloLake 软件在冰朔本地电脑上能够正常使用。
|
||||
|
||||
沿用此前已经确认的边界:人格体是推理大脑,Agent 是执行手脚;公众 HoloLake 是每个用户自己的初始化频道,不内置冰朔第五域私人核,不把外部 AI 连接冒充人格绑定。人类语言、人格回应、系统环境、协议运行、Agent 动作、工具结果和系统回执必须分流。执行必须经过 TCS/GIR、能力白名单、人类审批、目标读回和回执。
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "hololake-clean-desktop",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hololake-clean-desktop",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@tauri-apps/plugin-dialog": "2.7.2",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "hololake-clean-desktop",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 127.0.0.1",
|
||||
|
|
|
|||
|
|
@ -23,15 +23,17 @@
|
|||
"module_id": "HLP-MOD-GLP-BRIDGE-0001",
|
||||
"name_zh": "光湖语言翻译桥",
|
||||
"kind": "SYSTEM_FOUNDATION",
|
||||
"state": "TYPED_SOURCE_ENVELOPE_IMPLEMENTED_FULL_PERSONA_TO_AGENT_TRANSLATION_PENDING",
|
||||
"source": "src/types.ts#SourceKind"
|
||||
"state": "LOCAL_REALTIME_ACTIVE",
|
||||
"source": "src-tauri/src/realtime_bridge.rs",
|
||||
"transport": "GLP_LOCAL_REALTIME/1_LOOPBACK_TCP_NDJSON"
|
||||
},
|
||||
{
|
||||
"module_id": "HLP-MOD-AGENT-0001",
|
||||
"name_zh": "本地 Agent 执行核",
|
||||
"kind": "SYSTEM_FOUNDATION",
|
||||
"state": "KNOWLEDGE_CAPABILITY_SLICE_IMPLEMENTED_GENERAL_TCS_GIR_EXECUTOR_PENDING",
|
||||
"source": "src-tauri/src/lib.rs"
|
||||
"state": "PUBLIC_MINIMUM_TCS_TO_GIR_AND_HUMAN_APPROVED_AGENT_ACTIVE",
|
||||
"source": "src-tauri/src/agent_executor.rs",
|
||||
"registered_operations": ["KNOWLEDGE.CREATE", "KNOWLEDGE.UPDATE", "KNOWLEDGE.DELETE", "KNOWLEDGE.READ", "KNOWLEDGE.LIST"]
|
||||
},
|
||||
{
|
||||
"module_id": "HLP-MOD-UPDATE-0001",
|
||||
|
|
@ -44,8 +46,16 @@
|
|||
"module_id": "HLP-MOD-EXT-AI-0001",
|
||||
"name_zh": "外部编程 AI 表达桥",
|
||||
"kind": "OPTIONAL_LOCAL_BRIDGE",
|
||||
"state": "LOCAL_EXPRESSION_INBOX_OUTBOX_IMPLEMENTED",
|
||||
"source": "src-tauri/src/lib.rs#register_external_ai_bridge"
|
||||
"state": "LOCAL_REALTIME_CONNECTOR_ACTIVE",
|
||||
"source": "connectors/hololake-glp-client.py"
|
||||
},
|
||||
{
|
||||
"module_id": "HLP-MOD-PERSONA-RUNTIME-0001",
|
||||
"name_zh": "公众人格试用运行时",
|
||||
"kind": "SYSTEM_FOUNDATION",
|
||||
"state": "LOCAL_TRIAL_PERSONA_RUNTIME_ACTIVE_EXISTING_PERSONA_VERIFICATION_NOT_CLAIMED",
|
||||
"source": "src-tauri/src/persona_runtime.rs",
|
||||
"reversible_trial_days": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ if len(ids) != len(set(ids)):
|
|||
errors.append("duplicate module id")
|
||||
|
||||
required_sources = {
|
||||
"USER_MESSAGE", "PERSONA_RESPONSE", "SYSTEM_CONTEXT", "PROTOCOL_EVENT",
|
||||
"USER_MESSAGE", "PERSONA_RESPONSE", "EXTERNAL_AI_MESSAGE", "SYSTEM_CONTEXT", "PROTOCOL_EVENT",
|
||||
"AGENT_ACTION", "TOOL_RESULT", "SYSTEM_RECEIPT",
|
||||
}
|
||||
types = (ROOT / "src/types.ts").read_text()
|
||||
|
|
@ -31,6 +31,20 @@ for required in ("marked", "dompurify"):
|
|||
renderer = ROOT / "src/modules/knowledge-render/index.tsx"
|
||||
if not renderer.exists() or "MarkdownDocument" not in renderer.read_text():
|
||||
errors.append("knowledge human visual renderer missing")
|
||||
for required_file in (
|
||||
"src-tauri/src/agent_executor.rs",
|
||||
"src-tauri/src/persona_runtime.rs",
|
||||
"src-tauri/src/realtime_bridge.rs",
|
||||
"connectors/hololake-glp-client.py",
|
||||
"contracts/public-runtime-v1.json",
|
||||
):
|
||||
if not (ROOT / required_file).exists():
|
||||
errors.append(f"public runtime file missing: {required_file}")
|
||||
runtime_contract = json.loads((ROOT / "contracts/public-runtime-v1.json").read_text())
|
||||
if runtime_contract["transport"]["bind"] != "127.0.0.1":
|
||||
errors.append("realtime bridge is not loopback-only")
|
||||
if runtime_contract["mutation_gate"] != "PENDING_HUMAN_APPROVAL_TO_EXPLICIT_UI_APPROVAL_TO_EXECUTION":
|
||||
errors.append("agent human approval gate missing")
|
||||
|
||||
tauri = json.loads((ROOT / "src-tauri/tauri.conf.json").read_text())
|
||||
endpoints = tauri["plugins"]["updater"]["endpoints"]
|
||||
|
|
|
|||
|
|
@ -642,7 +642,7 @@ dependencies = [
|
|||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -801,7 +801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1328,8 +1328,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||
|
||||
[[package]]
|
||||
name = "hololake-clean-desktop"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
|
@ -3061,7 +3062,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3117,7 +3118,7 @@ dependencies = [
|
|||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3527,7 +3528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4113,10 +4114,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4876,7 +4877,7 @@ version = "0.1.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "hololake-clean-desktop"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
description = "HoloLake clean personal language operating system shell"
|
||||
authors = ["HoloLake"]
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
|
@ -25,6 +25,7 @@ serde = { version = "1", features = ["derive"] }
|
|||
serde_json = "1"
|
||||
sha2 = "0.10"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
regex = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,414 @@
|
|||
use crate::{model::SourceKind, storage};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
use tauri::AppHandle;
|
||||
|
||||
const REQUIRED_SECTIONS: &[&str] = &[
|
||||
"header",
|
||||
"source",
|
||||
"subject",
|
||||
"target",
|
||||
"inputs",
|
||||
"outputs",
|
||||
"conditions",
|
||||
"actions",
|
||||
"authority",
|
||||
"resources",
|
||||
"failure",
|
||||
"stop",
|
||||
"cleanup",
|
||||
"rollback",
|
||||
"receipt",
|
||||
];
|
||||
const REGISTERED_OPERATIONS: &[&str] = &[
|
||||
"KNOWLEDGE.CREATE",
|
||||
"KNOWLEDGE.UPDATE",
|
||||
"KNOWLEDGE.DELETE",
|
||||
"KNOWLEDGE.READ",
|
||||
"KNOWLEDGE.LIST",
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct TcsCompileRequest {
|
||||
pub source: String,
|
||||
#[serde(default)]
|
||||
pub inputs: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct GirAction {
|
||||
pub action_id: String,
|
||||
pub operation: String,
|
||||
pub input: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct GirProgram {
|
||||
pub schema: String,
|
||||
pub program_id: String,
|
||||
pub channel_id: String,
|
||||
pub source_sha256: String,
|
||||
pub compiler_id: String,
|
||||
pub unresolved_natural_language: bool,
|
||||
pub actions: Vec<GirAction>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct AgentProposal {
|
||||
pub proposal_id: String,
|
||||
pub client_id: String,
|
||||
pub persona_id: Option<String>,
|
||||
pub state: String,
|
||||
pub created_at: String,
|
||||
pub gir: GirProgram,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AgentReceipt {
|
||||
pub schema: String,
|
||||
pub receipt_id: String,
|
||||
pub proposal_id: String,
|
||||
pub state: String,
|
||||
pub action_results: Vec<Value>,
|
||||
pub git_commit: Option<String>,
|
||||
pub target_readback_sha256: String,
|
||||
}
|
||||
|
||||
fn assignments(block: &str) -> Result<Vec<String>, String> {
|
||||
let string_re = Regex::new(r#"\"((?:\\.|[^\"])*)\""#).unwrap();
|
||||
string_re
|
||||
.captures_iter(block)
|
||||
.map(|capture| {
|
||||
serde_json::from_str::<String>(&format!("\"{}\"", &capture[1]))
|
||||
.map_err(|_| "TCS-E1001 INVALID_STRING".to_string())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn compile(
|
||||
source: &str,
|
||||
inputs: &BTreeMap<String, Value>,
|
||||
channel_id: &str,
|
||||
) -> Result<GirProgram, String> {
|
||||
if source.len() > 256 * 1024 || !source.trim_start().starts_with("TCS 0.1;") {
|
||||
return Err("TCS-E1001 INVALID_PROLOGUE".into());
|
||||
}
|
||||
for section in REQUIRED_SECTIONS {
|
||||
let re = Regex::new(&format!(r"(?m)\b{}\s*\{{", regex::escape(section))).unwrap();
|
||||
if !re.is_match(source) {
|
||||
return Err(format!("TCS-E1004 REQUIRED_SECTION_MISSING:{section}"));
|
||||
}
|
||||
}
|
||||
let program_re = Regex::new(r"(?m)\bPROGRAM\s+([\p{L}_][\p{L}\p{N}_.:/@-]*)\s*\{").unwrap();
|
||||
let program_id = program_re
|
||||
.captures(source)
|
||||
.and_then(|capture| capture.get(1))
|
||||
.map(|m| m.as_str().to_owned())
|
||||
.ok_or_else(|| "TCS-E1001 PROGRAM_DECLARATION_REQUIRED".to_string())?;
|
||||
let action_re = Regex::new(r#"(?s)([\p{L}_][\p{L}\p{N}_.:/@-]*)\s*\{[^{}]*?operation\s*=\s*\"([^\"]+)\"\s*;[^{}]*?input_refs\s*=\s*\[([^\]]*)\]"#).unwrap();
|
||||
let mut actions = Vec::new();
|
||||
for capture in action_re.captures_iter(source) {
|
||||
let operation = capture[2].to_string();
|
||||
if !REGISTERED_OPERATIONS.contains(&operation.as_str()) {
|
||||
return Err(format!("TCS-E2101 UNREGISTERED_OPERATION:{operation}"));
|
||||
}
|
||||
let mut input = BTreeMap::new();
|
||||
for reference in assignments(&capture[3])? {
|
||||
let value = inputs
|
||||
.get(&reference)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("TCS-E2002 REFERENCE_NOT_DECLARED:{reference}"))?;
|
||||
input.insert(reference, value);
|
||||
}
|
||||
actions.push(GirAction {
|
||||
action_id: capture[1].to_string(),
|
||||
operation,
|
||||
input,
|
||||
});
|
||||
}
|
||||
if actions.is_empty() {
|
||||
return Err("TCS-E1004 ACTION_REQUIRED".into());
|
||||
}
|
||||
Ok(GirProgram {
|
||||
schema: "hololake.gir-program/v1".into(),
|
||||
program_id,
|
||||
channel_id: channel_id.into(),
|
||||
source_sha256: storage::sha(source.as_bytes()),
|
||||
compiler_id: "HLP-TCS-PUBLIC-STAGE0-0001".into(),
|
||||
unresolved_natural_language: false,
|
||||
actions,
|
||||
})
|
||||
}
|
||||
|
||||
fn proposals_path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
|
||||
Ok(storage::root(app)?.join("agent-proposals.json"))
|
||||
}
|
||||
fn load(app: &AppHandle) -> Result<Vec<AgentProposal>, String> {
|
||||
let path = proposals_path(app)?;
|
||||
if !path.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
storage::read_json(&path)
|
||||
}
|
||||
fn save(app: &AppHandle, proposals: &[AgentProposal]) -> Result<(), String> {
|
||||
storage::write_json(&proposals_path(app)?, proposals)
|
||||
}
|
||||
|
||||
pub fn list(app: &AppHandle) -> Result<Vec<AgentProposal>, String> {
|
||||
load(app)
|
||||
}
|
||||
|
||||
pub fn queue(
|
||||
app: &AppHandle,
|
||||
request: TcsCompileRequest,
|
||||
client_id: String,
|
||||
persona_id: Option<String>,
|
||||
) -> Result<AgentProposal, String> {
|
||||
let channel = storage::channel(app)?.ok_or_else(|| "CHANNEL_NOT_INITIALIZED".to_string())?;
|
||||
let gir = compile(&request.source, &request.inputs, &channel.channel_id)?;
|
||||
let proposal = AgentProposal {
|
||||
proposal_id: storage::id("HL-PROP"),
|
||||
client_id,
|
||||
persona_id,
|
||||
state: "PENDING_HUMAN_APPROVAL".into(),
|
||||
created_at: storage::now(),
|
||||
gir,
|
||||
};
|
||||
let mut proposals = load(app)?;
|
||||
proposals.push(proposal.clone());
|
||||
save(app, &proposals)?;
|
||||
storage::append_event(
|
||||
app,
|
||||
&storage::event(
|
||||
SourceKind::ProtocolEvent,
|
||||
"TCS 已编译为 GIR",
|
||||
format!("{} 等待人类审批。", proposal.proposal_id),
|
||||
"WAITING",
|
||||
),
|
||||
)?;
|
||||
Ok(proposal)
|
||||
}
|
||||
|
||||
fn value<'a>(input: &'a BTreeMap<String, Value>, name: &str) -> Result<&'a Value, String> {
|
||||
input
|
||||
.get(name)
|
||||
.or_else(|| input.get(&name.to_lowercase()))
|
||||
.ok_or_else(|| format!("TCS-E2002 INPUT_REQUIRED:{name}"))
|
||||
}
|
||||
fn text(input: &BTreeMap<String, Value>, name: &str) -> Result<String, String> {
|
||||
value(input, name)?
|
||||
.as_str()
|
||||
.map(str::to_owned)
|
||||
.ok_or_else(|| format!("TCS-E2001 TEXT_REQUIRED:{name}"))
|
||||
}
|
||||
fn safe_title(title: &str) -> Result<String, String> {
|
||||
let title = title.trim();
|
||||
if title.is_empty() || title.chars().count() > 120 {
|
||||
return Err("KNOWLEDGE_TITLE_INVALID".into());
|
||||
}
|
||||
Ok(title
|
||||
.chars()
|
||||
.map(|c| if "\\/:*?\"<>|".contains(c) { '-' } else { c })
|
||||
.collect())
|
||||
}
|
||||
fn move_to_trash(root: &Path, relative: &Path) -> Result<(), String> {
|
||||
let source = root.join("docs").join(relative);
|
||||
if !source.exists() {
|
||||
return Err("KNOWLEDGE_DOCUMENT_NOT_FOUND".into());
|
||||
}
|
||||
let target = root.join(".trash").join(format!(
|
||||
"{}-{}",
|
||||
storage::id("agent"),
|
||||
relative
|
||||
.file_name()
|
||||
.and_then(|v| v.to_str())
|
||||
.unwrap_or("document.md")
|
||||
));
|
||||
fs::create_dir_all(target.parent().unwrap()).map_err(|e| e.to_string())?;
|
||||
fs::rename(source, target).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn approve(app: &AppHandle, proposal_id: &str) -> Result<AgentReceipt, String> {
|
||||
let mut proposals = load(app)?;
|
||||
let index = proposals
|
||||
.iter()
|
||||
.position(|proposal| proposal.proposal_id == proposal_id)
|
||||
.ok_or_else(|| "AGENT_PROPOSAL_NOT_FOUND".to_string())?;
|
||||
if proposals[index].state != "PENDING_HUMAN_APPROVAL" {
|
||||
return Err("AGENT_PROPOSAL_NOT_PENDING".into());
|
||||
}
|
||||
let proposal = proposals[index].clone();
|
||||
let root = storage::knowledge_root(app)?;
|
||||
let mut results = Vec::new();
|
||||
for action in &proposal.gir.actions {
|
||||
let result = match action.operation.as_str() {
|
||||
"KNOWLEDGE.CREATE" => {
|
||||
let title = safe_title(&text(&action.input, "TITLE")?)?;
|
||||
let body = action
|
||||
.input
|
||||
.get("BODY")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
let relative = format!("{}.md", title);
|
||||
let path = root.join("docs").join(&relative);
|
||||
if path.exists() {
|
||||
return Err("KNOWLEDGE_DOCUMENT_EXISTS".into());
|
||||
}
|
||||
fs::write(
|
||||
&path,
|
||||
if body.trim().is_empty() {
|
||||
format!("# {title}\n\n")
|
||||
} else {
|
||||
body.to_owned()
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
serde_json::json!({"actionId":action.action_id,"path":relative,"sha256":storage::sha(&fs::read(path).map_err(|e|e.to_string())?)})
|
||||
}
|
||||
"KNOWLEDGE.UPDATE" => {
|
||||
let relative = storage::safe_rel(&text(&action.input, "PATH")?)?;
|
||||
let path = root.join("docs").join(&relative);
|
||||
let before =
|
||||
fs::read(&path).map_err(|_| "KNOWLEDGE_DOCUMENT_NOT_FOUND".to_string())?;
|
||||
if let Some(expected) = action.input.get("EXPECTED_SHA256").and_then(Value::as_str)
|
||||
{
|
||||
if storage::sha(&before) != expected {
|
||||
return Err("TCS-E6001 TARGET_STATE_MISMATCH".into());
|
||||
}
|
||||
}
|
||||
fs::write(&path, text(&action.input, "BODY")?).map_err(|e| e.to_string())?;
|
||||
serde_json::json!({"actionId":action.action_id,"path":relative,"sha256":storage::sha(&fs::read(path).map_err(|e|e.to_string())?)})
|
||||
}
|
||||
"KNOWLEDGE.DELETE" => {
|
||||
let relative = storage::safe_rel(&text(&action.input, "PATH")?)?;
|
||||
move_to_trash(&root, &relative)?;
|
||||
serde_json::json!({"actionId":action.action_id,"path":relative,"state":"MOVED_TO_TRASH"})
|
||||
}
|
||||
"KNOWLEDGE.READ" => {
|
||||
let path = text(&action.input, "PATH")?;
|
||||
let document = storage::read_doc(app, &path)?;
|
||||
serde_json::to_value(document).map_err(|e| e.to_string())?
|
||||
}
|
||||
"KNOWLEDGE.LIST" => {
|
||||
serde_json::to_value(storage::list_docs(app)?).map_err(|e| e.to_string())?
|
||||
}
|
||||
operation => return Err(format!("TCS-E2101 UNREGISTERED_OPERATION:{operation}")),
|
||||
};
|
||||
results.push(result);
|
||||
}
|
||||
let git_commit = storage::git_commit(
|
||||
&root,
|
||||
&format!("agent({}): execute approved GIR", proposal.gir.program_id),
|
||||
)?;
|
||||
let readback = storage::list_docs(app)?;
|
||||
let readback_bytes = serde_json::to_vec(&readback).map_err(|e| e.to_string())?;
|
||||
let receipt = AgentReceipt {
|
||||
schema: "hololake.agent-execution-receipt/v1".into(),
|
||||
receipt_id: storage::id("HL-AGENT-RCP"),
|
||||
proposal_id: proposal.proposal_id.clone(),
|
||||
state: "EXECUTED_TARGET_READBACK_VERIFIED".into(),
|
||||
action_results: results,
|
||||
git_commit,
|
||||
target_readback_sha256: storage::sha(&readback_bytes),
|
||||
};
|
||||
proposals[index].state = "EXECUTED".into();
|
||||
save(app, &proposals)?;
|
||||
storage::append_event(
|
||||
app,
|
||||
&storage::event(
|
||||
SourceKind::AgentAction,
|
||||
"Agent 已执行批准的 GIR",
|
||||
proposal.gir.program_id,
|
||||
"SUCCEEDED",
|
||||
),
|
||||
)?;
|
||||
storage::append_event(
|
||||
app,
|
||||
&storage::event(
|
||||
SourceKind::ToolResult,
|
||||
"目标读回",
|
||||
receipt.target_readback_sha256.clone(),
|
||||
"SUCCEEDED",
|
||||
),
|
||||
)?;
|
||||
storage::append_event(
|
||||
app,
|
||||
&storage::event(
|
||||
SourceKind::SystemReceipt,
|
||||
"系统回执",
|
||||
format!("{} 已完成并读回。", receipt.receipt_id),
|
||||
"SUCCEEDED",
|
||||
),
|
||||
)?;
|
||||
let receipt_path = storage::root(app)?
|
||||
.join("agent-receipts")
|
||||
.join(format!("{}.json", receipt.receipt_id));
|
||||
storage::write_json(&receipt_path, &receipt)?;
|
||||
Ok(receipt)
|
||||
}
|
||||
|
||||
pub fn reject(app: &AppHandle, proposal_id: &str) -> Result<AgentProposal, String> {
|
||||
let mut proposals = load(app)?;
|
||||
let proposal = proposals
|
||||
.iter_mut()
|
||||
.find(|proposal| proposal.proposal_id == proposal_id)
|
||||
.ok_or_else(|| "AGENT_PROPOSAL_NOT_FOUND".to_string())?;
|
||||
if proposal.state != "PENDING_HUMAN_APPROVAL" {
|
||||
return Err("AGENT_PROPOSAL_NOT_PENDING".into());
|
||||
}
|
||||
proposal.state = "REJECTED_BY_HUMAN".into();
|
||||
let result = proposal.clone();
|
||||
save(app, &proposals)?;
|
||||
storage::append_event(
|
||||
app,
|
||||
&storage::event(
|
||||
SourceKind::SystemReceipt,
|
||||
"执行已驳回",
|
||||
proposal_id,
|
||||
"SUCCEEDED",
|
||||
),
|
||||
)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn base_program(operation: &str) -> String {
|
||||
format!(
|
||||
r#"TCS 0.1;
|
||||
PROGRAM TEST-PROGRAM-0001 {{
|
||||
header {{ schema = "tcs.program/v1"; }} source {{ source_id = "S"; }} subject {{ subject_id = "P"; }} target {{ target_id = "K"; }}
|
||||
inputs {{ TITLE {{ type = "Text"; source = "EVENT"; required = true; }} }} outputs {{ RESULT {{ type = "Text"; destination = "R"; integrity = "SHA256"; }} }}
|
||||
conditions {{ C1 {{ predicate = "APPROVED"; on_false = "FAIL_CLOSED"; }} }} actions {{ A1 {{ operation = "{operation}"; input_refs = ["TITLE"]; output_refs = ["RESULT"]; on_success = "COMPLETE"; on_failure = "FAIL_CLOSED"; }} }}
|
||||
authority {{ issuer = "H"; }} resources {{ runway = "LOCAL"; }} failure {{ errors = []; }} stop {{ signals = []; }} cleanup {{ targets = []; }} rollback {{ preconditions = []; }} receipt {{ protocol = "GLP"; }}
|
||||
}}"#
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn public_tcs_compiles_registered_operation() {
|
||||
let mut inputs = BTreeMap::new();
|
||||
inputs.insert("TITLE".into(), Value::String("测试".into()));
|
||||
let gir = compile(&base_program("KNOWLEDGE.CREATE"), &inputs, "HL-CH-TEST").unwrap();
|
||||
assert_eq!(gir.actions[0].operation, "KNOWLEDGE.CREATE");
|
||||
}
|
||||
#[test]
|
||||
fn public_tcs_rejects_unregistered_operation() {
|
||||
let mut inputs = BTreeMap::new();
|
||||
inputs.insert("TITLE".into(), Value::String("测试".into()));
|
||||
assert!(compile(&base_program("ABSORB"), &inputs, "HL-CH-TEST")
|
||||
.unwrap_err()
|
||||
.contains("TCS-E2101"));
|
||||
}
|
||||
#[test]
|
||||
fn public_tcs_requires_all_program_sections() {
|
||||
assert!(compile("TCS 0.1; PROGRAM X { actions { A1 { operation = \"KNOWLEDGE.LIST\"; input_refs = []; } } }", &BTreeMap::new(), "HL-CH-TEST").unwrap_err().contains("TCS-E1004"));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
mod agent_executor;
|
||||
mod model;
|
||||
mod persona_runtime;
|
||||
mod realtime_bridge;
|
||||
mod storage;
|
||||
|
||||
use model::*;
|
||||
use serde::Serialize;
|
||||
use std::{fs, path::Path};
|
||||
use tauri::AppHandle;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
const UPDATE_ENDPOINT: &str = "https://guanghulab.com/hololake/releases/latest.json";
|
||||
|
|
@ -76,7 +80,11 @@ fn create_channel(app: AppHandle, name: String) -> Result<Channel, String> {
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn submit_user_message(app: AppHandle, content: String) -> Result<MutationReceipt, String> {
|
||||
fn submit_user_message(
|
||||
app: AppHandle,
|
||||
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
|
||||
content: String,
|
||||
) -> Result<MutationReceipt, String> {
|
||||
let content = content.trim();
|
||||
if content.is_empty() {
|
||||
return Err("EMPTY_USER_MESSAGE".into());
|
||||
|
|
@ -86,16 +94,17 @@ fn submit_user_message(app: AppHandle, content: String) -> Result<MutationReceip
|
|||
}
|
||||
let e = storage::event(SourceKind::UserMessage, "用户语言", content, "RECORDED");
|
||||
storage::append_event(&app, &e)?;
|
||||
realtime.broadcast_value(serde_json::json!({"type":"user_message","event":e}));
|
||||
let waiting = storage::event(
|
||||
SourceKind::SystemReceipt,
|
||||
"系统回执",
|
||||
"用户语言已按 USER_MESSAGE 保存;当前未绑定人格模型,不伪造人格回应。",
|
||||
"用户语言已按 USER_MESSAGE 保存并实时发送到已连接的外部 AI;系统不伪造人格回应。",
|
||||
"WAITING",
|
||||
);
|
||||
receipt(
|
||||
&app,
|
||||
"WAITING_PERSONA",
|
||||
"用户语言已记录,等待人格体接入。",
|
||||
"用户语言已记录并投递到实时桥。",
|
||||
None,
|
||||
waiting,
|
||||
)
|
||||
|
|
@ -328,7 +337,7 @@ fn register_external_ai_bridge(
|
|||
display_name: name.into(),
|
||||
inbox_path: inbox.to_string_lossy().into_owned(),
|
||||
outbox_path: outbox.to_string_lossy().into_owned(),
|
||||
state: "LOCAL_EXPRESSION_ONLY_NO_EXECUTION_AUTHORITY".into(),
|
||||
state: "GLP_REALTIME_REGISTERED_NO_EXECUTION_AUTHORITY".into(),
|
||||
};
|
||||
let mut all = storage::bridges(&app)?;
|
||||
all.push(bridge.clone());
|
||||
|
|
@ -346,12 +355,166 @@ fn register_external_ai_bridge(
|
|||
Ok(bridge)
|
||||
}
|
||||
|
||||
fn ensure_default_external_ai_bridge(app: &AppHandle) -> Result<(), String> {
|
||||
let mut bridges = storage::bridges(app)?;
|
||||
if bridges
|
||||
.iter()
|
||||
.any(|bridge| bridge.bridge_id == "HLP-BRIDGE-LOCAL-DEFAULT")
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let root = storage::root(app)?
|
||||
.join("external-ai")
|
||||
.join("HLP-BRIDGE-LOCAL-DEFAULT");
|
||||
let inbox = root.join("inbox");
|
||||
let outbox = root.join("outbox");
|
||||
fs::create_dir_all(&inbox).map_err(|e| e.to_string())?;
|
||||
fs::create_dir_all(&outbox).map_err(|e| e.to_string())?;
|
||||
bridges.push(ExternalAiBridge {
|
||||
bridge_id: "HLP-BRIDGE-LOCAL-DEFAULT".into(),
|
||||
display_name: "本机编程 AI".into(),
|
||||
inbox_path: inbox.to_string_lossy().into_owned(),
|
||||
outbox_path: outbox.to_string_lossy().into_owned(),
|
||||
state: "GLP_REALTIME_REGISTERED_NO_EXECUTION_AUTHORITY".into(),
|
||||
});
|
||||
storage::save_bridges(app, &bridges)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct RuntimeOverview {
|
||||
realtime: realtime_bridge::RealtimeBridgeStatus,
|
||||
persona: persona_runtime::PersonaRuntimeSnapshot,
|
||||
proposals: Vec<agent_executor::AgentProposal>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_runtime_overview(
|
||||
app: AppHandle,
|
||||
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
|
||||
) -> Result<RuntimeOverview, String> {
|
||||
Ok(RuntimeOverview {
|
||||
realtime: realtime.status(),
|
||||
persona: persona_runtime::snapshot(&app)?,
|
||||
proposals: agent_executor::list(&app)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_realtime_invitation(
|
||||
app: AppHandle,
|
||||
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
|
||||
) -> Result<realtime_bridge::RealtimeInvitation, String> {
|
||||
realtime_bridge::invitation(&app, &realtime)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn register_public_persona(
|
||||
app: AppHandle,
|
||||
display_name: String,
|
||||
) -> Result<persona_runtime::PublicPersona, String> {
|
||||
persona_runtime::register(&app, display_name)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn delete_trial_persona(
|
||||
app: AppHandle,
|
||||
persona_id: String,
|
||||
exact_confirmation: String,
|
||||
) -> Result<persona_runtime::PersonaRuntimeSnapshot, String> {
|
||||
persona_runtime::delete_trial(&app, &persona_id, &exact_confirmation)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn compile_tcs_agent_proposal(
|
||||
app: AppHandle,
|
||||
request: agent_executor::TcsCompileRequest,
|
||||
) -> Result<agent_executor::AgentProposal, String> {
|
||||
agent_executor::queue(&app, request, "HOLOLAKE_LOCAL_UI".into(), None)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn approve_agent_proposal(
|
||||
app: AppHandle,
|
||||
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
|
||||
proposal_id: String,
|
||||
) -> Result<agent_executor::AgentReceipt, String> {
|
||||
let receipt = agent_executor::approve(&app, &proposal_id)?;
|
||||
realtime.broadcast_value(serde_json::json!({"type":"agent_receipt","receipt":receipt}));
|
||||
Ok(receipt)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn reject_agent_proposal(
|
||||
app: AppHandle,
|
||||
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
|
||||
proposal_id: String,
|
||||
) -> Result<agent_executor::AgentProposal, String> {
|
||||
let proposal = agent_executor::reject(&app, &proposal_id)?;
|
||||
realtime
|
||||
.broadcast_value(serde_json::json!({"type":"proposal_rejected","proposalId":proposal_id}));
|
||||
Ok(proposal)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.setup(|app| {
|
||||
let diagnostic = storage::root(app.handle())
|
||||
.map_err(std::io::Error::other)?
|
||||
.join("startup-runtime-diagnostic.json");
|
||||
storage::write_json(
|
||||
&diagnostic,
|
||||
&serde_json::json!({"state":"SETUP_ENTERED","recordedAt":storage::now()}),
|
||||
)
|
||||
.map_err(std::io::Error::other)?;
|
||||
if std::env::var_os("HOLOLAKE_RUNTIME_TEST_ROOT").is_some() {
|
||||
if let Ok(channel_name) = std::env::var("HOLOLAKE_TEST_CHANNEL_NAME") {
|
||||
create_channel(app.handle().clone(), channel_name)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
if let Ok(display_name) = std::env::var("HOLOLAKE_TEST_PERSONA_NAME") {
|
||||
persona_runtime::register(app.handle(), display_name)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
}
|
||||
ensure_default_external_ai_bridge(app.handle()).map_err(std::io::Error::other)?;
|
||||
let state = match realtime_bridge::start(app.handle().clone()) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
let _ = storage::write_json(
|
||||
&diagnostic,
|
||||
&serde_json::json!({"state":"LISTENER_FAILED","error":error.clone(),"recordedAt":storage::now()}),
|
||||
);
|
||||
return Err(std::io::Error::other(error).into());
|
||||
}
|
||||
};
|
||||
storage::write_json(
|
||||
&diagnostic,
|
||||
&serde_json::json!({"state":"LISTENER_BOUND","status":state.status(),"recordedAt":storage::now()}),
|
||||
)
|
||||
.map_err(std::io::Error::other)?;
|
||||
let invitation =
|
||||
realtime_bridge::invitation(app.handle(), &state).map_err(std::io::Error::other)?;
|
||||
storage::write_json(
|
||||
&storage::root(app.handle())
|
||||
.map_err(std::io::Error::other)?
|
||||
.join("startup-runtime-receipt.json"),
|
||||
&serde_json::json!({
|
||||
"schema":"hololake.startup-runtime-receipt/v1",
|
||||
"state":"GLP_LISTENING",
|
||||
"protocol":invitation.protocol,
|
||||
"endpoint":invitation.endpoint,
|
||||
"recordedAt":storage::now()
|
||||
}),
|
||||
)
|
||||
.map_err(std::io::Error::other)?;
|
||||
app.manage(state);
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
system_snapshot,
|
||||
create_channel,
|
||||
|
|
@ -362,7 +525,14 @@ pub fn run() {
|
|||
delete_knowledge_document,
|
||||
import_knowledge_folder,
|
||||
export_knowledge_document,
|
||||
register_external_ai_bridge
|
||||
register_external_ai_bridge,
|
||||
get_runtime_overview,
|
||||
get_realtime_invitation,
|
||||
register_public_persona,
|
||||
delete_trial_persona,
|
||||
compile_tcs_agent_proposal,
|
||||
approve_agent_proposal,
|
||||
reject_agent_proposal
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("HoloLake runtime failed")
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ pub struct Channel {
|
|||
pub enum SourceKind {
|
||||
UserMessage,
|
||||
PersonaResponse,
|
||||
ExternalAiMessage,
|
||||
SystemContext,
|
||||
ProtocolEvent,
|
||||
AgentAction,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
use crate::{model::SourceKind, storage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const PERSONA_STATE: &str = "LOCAL_TRIAL_UNVERIFIED_EXTERNAL_HOST_REQUIRED";
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct PublicPersona {
|
||||
pub persona_id: String,
|
||||
pub display_name: String,
|
||||
pub state: String,
|
||||
pub created_at: String,
|
||||
pub reversible_until_unix_ms: u128,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaRuntimeSnapshot {
|
||||
pub schema: &'static str,
|
||||
pub state: String,
|
||||
pub active_persona_id: Option<String>,
|
||||
pub personas: Vec<PublicPersona>,
|
||||
pub verified_existing_persona_count: usize,
|
||||
}
|
||||
|
||||
fn path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
|
||||
Ok(storage::root(app)?.join("public-persona-runtime.json"))
|
||||
}
|
||||
|
||||
fn load(app: &AppHandle) -> Result<Vec<PublicPersona>, String> {
|
||||
let p = path(app)?;
|
||||
if !p.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
serde_json::from_slice(&fs::read(p).map_err(|e| e.to_string())?).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn save(app: &AppHandle, personas: &[PublicPersona]) -> Result<(), String> {
|
||||
storage::write_json(&path(app)?, personas)
|
||||
}
|
||||
|
||||
pub fn exists(app: &AppHandle, persona_id: &str) -> Result<bool, String> {
|
||||
Ok(load(app)?
|
||||
.iter()
|
||||
.any(|persona| persona.persona_id == persona_id))
|
||||
}
|
||||
|
||||
pub fn snapshot(app: &AppHandle) -> Result<PersonaRuntimeSnapshot, String> {
|
||||
let personas = load(app)?;
|
||||
Ok(PersonaRuntimeSnapshot {
|
||||
schema: "hololake.public-persona-runtime/v1",
|
||||
state: if personas.is_empty() {
|
||||
"READY_NO_PERSONA"
|
||||
} else {
|
||||
"TRIAL_PERSONA_READY"
|
||||
}
|
||||
.into(),
|
||||
active_persona_id: personas.first().map(|persona| persona.persona_id.clone()),
|
||||
personas,
|
||||
verified_existing_persona_count: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register(app: &AppHandle, display_name: String) -> Result<PublicPersona, String> {
|
||||
let display_name = display_name.trim();
|
||||
if display_name.is_empty() || display_name.chars().count() > 64 {
|
||||
return Err("PERSONA_DISPLAY_NAME_INVALID".into());
|
||||
}
|
||||
let mut personas = load(app)?;
|
||||
if personas
|
||||
.iter()
|
||||
.any(|persona| persona.display_name == display_name)
|
||||
{
|
||||
return Err("PERSONA_DISPLAY_NAME_EXISTS".into());
|
||||
}
|
||||
let now_ms = storage::now_unix_ms();
|
||||
let persona = PublicPersona {
|
||||
persona_id: format!(
|
||||
"HL-PERSONA-{}",
|
||||
uuid::Uuid::new_v4().simple().to_string()[..10].to_uppercase()
|
||||
),
|
||||
display_name: display_name.into(),
|
||||
state: PERSONA_STATE.into(),
|
||||
created_at: storage::now(),
|
||||
reversible_until_unix_ms: now_ms + 30 * 24 * 60 * 60 * 1_000,
|
||||
};
|
||||
personas.push(persona.clone());
|
||||
save(app, &personas)?;
|
||||
let event = storage::event(
|
||||
SourceKind::SystemReceipt,
|
||||
"公众人格试用运行时已建立",
|
||||
format!(
|
||||
"{} 已登记为本频道可逆试用人格;尚未验证为任何既有历史人格。",
|
||||
persona.persona_id
|
||||
),
|
||||
"SUCCEEDED",
|
||||
);
|
||||
storage::append_event(app, &event)?;
|
||||
Ok(persona)
|
||||
}
|
||||
|
||||
pub fn delete_trial(
|
||||
app: &AppHandle,
|
||||
persona_id: &str,
|
||||
exact_confirmation: &str,
|
||||
) -> Result<PersonaRuntimeSnapshot, String> {
|
||||
if exact_confirmation != format!("删除试用人格 {persona_id}") {
|
||||
return Err("PERSONA_DELETE_EXACT_CONFIRMATION_REQUIRED".into());
|
||||
}
|
||||
let mut personas = load(app)?;
|
||||
let index = personas
|
||||
.iter()
|
||||
.position(|persona| persona.persona_id == persona_id)
|
||||
.ok_or_else(|| "PERSONA_NOT_FOUND".to_string())?;
|
||||
if personas[index].state != PERSONA_STATE {
|
||||
return Err("ONLY_UNVERIFIED_TRIAL_PERSONA_IS_REVERSIBLE".into());
|
||||
}
|
||||
let removed = personas.remove(index);
|
||||
save(app, &personas)?;
|
||||
storage::append_event(
|
||||
app,
|
||||
&storage::event(
|
||||
SourceKind::SystemReceipt,
|
||||
"试用人格已移除",
|
||||
format!(
|
||||
"{} 的可逆试用记录已移除;历史事件仍保留来源类型。",
|
||||
removed.persona_id
|
||||
),
|
||||
"SUCCEEDED",
|
||||
),
|
||||
)?;
|
||||
snapshot(app)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn public_persona_state_never_claims_verified_existing_identity() {
|
||||
assert!(super::PERSONA_STATE.contains("UNVERIFIED"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,425 @@
|
|||
use crate::{agent_executor, model::SourceKind, persona_runtime, storage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::{
|
||||
fs,
|
||||
io::{BufRead, BufReader, Write},
|
||||
net::{Ipv4Addr, SocketAddrV4, TcpListener, TcpStream},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc, Arc, Mutex,
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
|
||||
const PROTOCOL: &str = "GLP_LOCAL_REALTIME/1";
|
||||
const MAX_LINE_BYTES: usize = 256 * 1024;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RealtimeBridgeState {
|
||||
port: u16,
|
||||
token: Arc<String>,
|
||||
_listener: Arc<TcpListener>,
|
||||
clients: Arc<Mutex<Vec<mpsc::Sender<String>>>>,
|
||||
connected: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RealtimeBridgeStatus {
|
||||
pub schema: &'static str,
|
||||
pub state: &'static str,
|
||||
pub protocol: &'static str,
|
||||
pub endpoint: String,
|
||||
pub connected_clients: usize,
|
||||
pub loopback_only: bool,
|
||||
pub transport_grants_authority: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RealtimeInvitation {
|
||||
pub schema: &'static str,
|
||||
pub protocol: &'static str,
|
||||
pub endpoint: String,
|
||||
pub token: String,
|
||||
pub descriptor_path: String,
|
||||
pub connector_command: String,
|
||||
pub warning: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct Hello {
|
||||
message_type: String,
|
||||
protocol: String,
|
||||
token: String,
|
||||
bridge_id: String,
|
||||
persona_id: Option<String>,
|
||||
}
|
||||
|
||||
fn token_path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
|
||||
Ok(storage::root(app)?.join("realtime-bridge-token"))
|
||||
}
|
||||
|
||||
fn load_or_create_token(app: &AppHandle) -> Result<String, String> {
|
||||
let path = token_path(app)?;
|
||||
if path.exists() {
|
||||
let value = fs::read_to_string(&path).map_err(|e| e.to_string())?;
|
||||
if value.trim().len() >= 32 {
|
||||
return Ok(value.trim().into());
|
||||
}
|
||||
return Err("GLP_TOKEN_FILE_INVALID".into());
|
||||
}
|
||||
let token = format!(
|
||||
"{}{}",
|
||||
uuid::Uuid::new_v4().simple(),
|
||||
uuid::Uuid::new_v4().simple()
|
||||
);
|
||||
fs::write(&path, &token).map_err(|e| e.to_string())?;
|
||||
#[cfg(unix)]
|
||||
fs::set_permissions(&path, fs::Permissions::from_mode(0o600)).map_err(|e| e.to_string())?;
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
fn bind_listener() -> Result<(TcpListener, u16), String> {
|
||||
for port in 39281..39291 {
|
||||
if let Ok(listener) = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)) {
|
||||
return Ok((listener, port));
|
||||
}
|
||||
}
|
||||
Err("GLP_LOOPBACK_PORT_UNAVAILABLE".into())
|
||||
}
|
||||
|
||||
pub fn start(app: AppHandle) -> Result<RealtimeBridgeState, String> {
|
||||
let token = Arc::new(load_or_create_token(&app)?);
|
||||
let (listener, port) = bind_listener()?;
|
||||
let listener = Arc::new(listener);
|
||||
let state = RealtimeBridgeState {
|
||||
port,
|
||||
token,
|
||||
_listener: listener.clone(),
|
||||
clients: Arc::new(Mutex::new(Vec::new())),
|
||||
connected: Arc::new(AtomicUsize::new(0)),
|
||||
};
|
||||
let runtime_state = state.clone();
|
||||
thread::Builder::new()
|
||||
.name("hololake-glp-listener".into())
|
||||
.spawn(move || loop {
|
||||
match listener.accept() {
|
||||
Ok((stream, _)) => {
|
||||
let connection_state = runtime_state.clone();
|
||||
let connection_app = app.clone();
|
||||
let _ = thread::Builder::new()
|
||||
.name("hololake-glp-client".into())
|
||||
.spawn(move || {
|
||||
if let Err(error) =
|
||||
handle_connection(connection_app.clone(), connection_state, stream)
|
||||
{
|
||||
let _ = connection_app.emit(
|
||||
"hololake-runtime-event",
|
||||
json!({"kind":"CLIENT_ERROR","error":error}),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
let _ = app.emit(
|
||||
"hololake-runtime-event",
|
||||
json!({"kind":"LISTENER_ERROR","error":error.to_string()}),
|
||||
);
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
impl RealtimeBridgeState {
|
||||
pub fn status(&self) -> RealtimeBridgeStatus {
|
||||
RealtimeBridgeStatus {
|
||||
schema: "hololake.glp-local-realtime-status/v1",
|
||||
state: "LISTENING",
|
||||
protocol: PROTOCOL,
|
||||
endpoint: format!("tcp://127.0.0.1:{}", self.port),
|
||||
connected_clients: self.connected.load(Ordering::SeqCst),
|
||||
loopback_only: true,
|
||||
transport_grants_authority: false,
|
||||
}
|
||||
}
|
||||
pub fn broadcast_value(&self, value: Value) {
|
||||
if let Ok(line) = serde_json::to_string(&value) {
|
||||
if let Ok(mut clients) = self.clients.lock() {
|
||||
clients.retain(|client| client.send(line.clone()).is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invitation(
|
||||
app: &AppHandle,
|
||||
state: &RealtimeBridgeState,
|
||||
) -> Result<RealtimeInvitation, String> {
|
||||
let descriptor = storage::root(app)?.join("realtime-bridge-descriptor.json");
|
||||
let endpoint = format!("tcp://127.0.0.1:{}", state.port);
|
||||
storage::write_json(
|
||||
&descriptor,
|
||||
&json!({"schema":"hololake.glp-local-realtime-descriptor/v1","protocol":PROTOCOL,"endpoint":endpoint,"token":state.token.as_str()}),
|
||||
)?;
|
||||
#[cfg(unix)]
|
||||
fs::set_permissions(&descriptor, fs::Permissions::from_mode(0o600))
|
||||
.map_err(|e| e.to_string())?;
|
||||
let connector = connector_path(app)?;
|
||||
Ok(RealtimeInvitation {
|
||||
schema: "hololake.glp-local-realtime-invitation/v1",
|
||||
protocol: PROTOCOL,
|
||||
endpoint,
|
||||
token: state.token.as_str().to_string(),
|
||||
descriptor_path: descriptor.to_string_lossy().into_owned(),
|
||||
connector_command: format!(
|
||||
"python3 \"{}\" --descriptor \"{}\"",
|
||||
connector.display(),
|
||||
descriptor.display()
|
||||
),
|
||||
warning: "令牌只认证本机连接,不证明人格身份,也不授予执行权限。",
|
||||
})
|
||||
}
|
||||
|
||||
fn connector_path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
|
||||
if std::env::var_os("HOLOLAKE_RUNTIME_TEST_ROOT").is_some() {
|
||||
if let Some(raw_path) = std::env::var_os("HOLOLAKE_CONNECTOR_TEST_PATH") {
|
||||
let path = std::path::PathBuf::from(raw_path);
|
||||
if !path.is_absolute()
|
||||
|| path.extension().and_then(|value| value.to_str()) != Some("py")
|
||||
{
|
||||
return Err("GLP_TEST_CONNECTOR_PATH_INVALID".into());
|
||||
}
|
||||
let metadata = fs::metadata(&path).map_err(|_| "GLP_TEST_CONNECTOR_NOT_READABLE")?;
|
||||
if !metadata.is_file() {
|
||||
return Err("GLP_TEST_CONNECTOR_NOT_A_FILE".into());
|
||||
}
|
||||
return Ok(path);
|
||||
}
|
||||
}
|
||||
let path = app
|
||||
.path()
|
||||
.resource_dir()
|
||||
.map_err(|e| e.to_string())?
|
||||
.join("connectors/hololake-glp-client.py");
|
||||
if !path.is_file() {
|
||||
return Err("GLP_BUNDLED_CONNECTOR_MISSING".into());
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn send(writer: &mut TcpStream, value: Value) -> Result<(), String> {
|
||||
serde_json::to_writer(&mut *writer, &value).map_err(|e| e.to_string())?;
|
||||
writer.write_all(b"\n").map_err(|e| e.to_string())?;
|
||||
writer.flush().map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn handle_connection(
|
||||
app: AppHandle,
|
||||
state: RealtimeBridgeState,
|
||||
stream: TcpStream,
|
||||
) -> Result<(), String> {
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_secs(10)))
|
||||
.map_err(|e| e.to_string())?;
|
||||
let reader_stream = stream.try_clone().map_err(|e| e.to_string())?;
|
||||
let mut writer = stream;
|
||||
let mut reader = BufReader::new(reader_stream);
|
||||
let mut first = String::new();
|
||||
reader.read_line(&mut first).map_err(|e| e.to_string())?;
|
||||
if first.len() > MAX_LINE_BYTES {
|
||||
return Err("GLP_MESSAGE_TOO_LARGE".into());
|
||||
}
|
||||
let hello: Hello = serde_json::from_str(&first).map_err(|_| "GLP_HELLO_INVALID".to_string())?;
|
||||
if hello.message_type != "hello"
|
||||
|| hello.protocol != PROTOCOL
|
||||
|| hello.token != state.token.as_str()
|
||||
{
|
||||
let _ = send(
|
||||
&mut writer,
|
||||
json!({"type":"error","code":"GLP_TOKEN_OR_PROTOCOL_REJECTED"}),
|
||||
);
|
||||
return Err("GLP_TOKEN_OR_PROTOCOL_REJECTED".into());
|
||||
}
|
||||
if !storage::bridges(&app)?
|
||||
.iter()
|
||||
.any(|bridge| bridge.bridge_id == hello.bridge_id)
|
||||
{
|
||||
send(
|
||||
&mut writer,
|
||||
json!({"type":"error","code":"GLP_BRIDGE_NOT_REGISTERED"}),
|
||||
)?;
|
||||
return Err("GLP_BRIDGE_NOT_REGISTERED".into());
|
||||
}
|
||||
if let Some(persona_id) = &hello.persona_id {
|
||||
if !persona_runtime::exists(&app, persona_id)? {
|
||||
send(
|
||||
&mut writer,
|
||||
json!({"type":"error","code":"PERSONA_IDENTITY_UNVERIFIED"}),
|
||||
)?;
|
||||
return Err("PERSONA_IDENTITY_UNVERIFIED".into());
|
||||
}
|
||||
}
|
||||
writer.set_read_timeout(None).map_err(|e| e.to_string())?;
|
||||
reader
|
||||
.get_ref()
|
||||
.set_read_timeout(None)
|
||||
.map_err(|e| e.to_string())?;
|
||||
state.connected.fetch_add(1, Ordering::SeqCst);
|
||||
let _guard = ConnectionGuard(state.connected.clone());
|
||||
send(
|
||||
&mut writer,
|
||||
json!({"type":"welcome","protocol":PROTOCOL,"bridgeId":hello.bridge_id,"personaId":hello.persona_id,"personaState":if hello.persona_id.is_some(){"LOCAL_TRIAL_UNVERIFIED_HOST_CONNECTED"}else{"EXTERNAL_AI_CONNECTED_NO_PERSONA"},"executionAuthority":false}),
|
||||
)?;
|
||||
let (tx, rx) = mpsc::channel::<String>();
|
||||
state
|
||||
.clients
|
||||
.lock()
|
||||
.map_err(|_| "GLP_CLIENT_REGISTRY_POISONED".to_string())?
|
||||
.push(tx);
|
||||
let mut outbound_writer = writer.try_clone().map_err(|e| e.to_string())?;
|
||||
let _ = thread::Builder::new()
|
||||
.name("hololake-glp-outbound".into())
|
||||
.spawn(move || {
|
||||
while let Ok(line) = rx.recv() {
|
||||
if outbound_writer.write_all(line.as_bytes()).is_err()
|
||||
|| outbound_writer.write_all(b"\n").is_err()
|
||||
|| outbound_writer.flush().is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = app.emit("hololake-runtime-event", json!({"kind":"CLIENT_CONNECTED"}));
|
||||
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
if reader.read_line(&mut line).map_err(|e| e.to_string())? == 0 {
|
||||
break;
|
||||
}
|
||||
if line.len() > MAX_LINE_BYTES {
|
||||
send(
|
||||
&mut writer,
|
||||
json!({"type":"error","code":"GLP_MESSAGE_TOO_LARGE"}),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
let value: Value = match serde_json::from_str(&line) {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
send(
|
||||
&mut writer,
|
||||
json!({"type":"error","code":"GLP_MESSAGE_INVALID"}),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match value.get("type").and_then(Value::as_str).unwrap_or("") {
|
||||
"persona_response" if hello.persona_id.is_some() => {
|
||||
let content = value
|
||||
.get("content")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.trim();
|
||||
if content.is_empty() {
|
||||
send(
|
||||
&mut writer,
|
||||
json!({"type":"error","code":"EMPTY_PERSONA_RESPONSE"}),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
let event =
|
||||
storage::event(SourceKind::PersonaResponse, "人格回应", content, "RECORDED");
|
||||
storage::append_event(&app, &event)?;
|
||||
let _ = app.emit("hololake-runtime-event", &event);
|
||||
send(&mut writer, json!({"type":"accepted","event":event}))?;
|
||||
}
|
||||
"external_ai_message" => {
|
||||
let content = value
|
||||
.get("content")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.trim();
|
||||
if content.is_empty() {
|
||||
send(
|
||||
&mut writer,
|
||||
json!({"type":"error","code":"EMPTY_EXTERNAL_AI_MESSAGE"}),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
let event = storage::event(
|
||||
SourceKind::ExternalAiMessage,
|
||||
"外部 AI 语言",
|
||||
content,
|
||||
"RECORDED",
|
||||
);
|
||||
storage::append_event(&app, &event)?;
|
||||
let _ = app.emit("hololake-runtime-event", &event);
|
||||
send(&mut writer, json!({"type":"accepted","event":event}))?;
|
||||
}
|
||||
"tcs_proposal" => {
|
||||
let request: agent_executor::TcsCompileRequest = serde_json::from_value(
|
||||
value
|
||||
.get("request")
|
||||
.cloned()
|
||||
.ok_or_else(|| "TCS_REQUEST_REQUIRED".to_string())?,
|
||||
)
|
||||
.map_err(|e| format!("TCS_REQUEST_INVALID:{e}"))?;
|
||||
match agent_executor::queue(
|
||||
&app,
|
||||
request,
|
||||
hello.bridge_id.clone(),
|
||||
hello.persona_id.clone(),
|
||||
) {
|
||||
Ok(proposal) => {
|
||||
let _ = app.emit(
|
||||
"hololake-runtime-event",
|
||||
json!({"kind":"PROPOSAL_PENDING","proposalId":proposal.proposal_id}),
|
||||
);
|
||||
send(
|
||||
&mut writer,
|
||||
json!({"type":"proposal_pending","proposal":proposal}),
|
||||
)?;
|
||||
if isolated_approval_simulation_enabled() {
|
||||
let receipt = agent_executor::approve(&app, &proposal.proposal_id)?;
|
||||
state.broadcast_value(json!({
|
||||
"type":"agent_receipt",
|
||||
"receipt":receipt,
|
||||
"testApprovalSimulation":true
|
||||
}));
|
||||
}
|
||||
}
|
||||
Err(error) => send(&mut writer, json!({"type":"error","code":error}))?,
|
||||
}
|
||||
}
|
||||
"ping" => send(&mut writer, json!({"type":"pong","at":storage::now()}))?,
|
||||
_ => send(
|
||||
&mut writer,
|
||||
json!({"type":"error","code":"GLP_MESSAGE_TYPE_REJECTED"}),
|
||||
)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn isolated_approval_simulation_enabled() -> bool {
|
||||
std::env::var_os("HOLOLAKE_RUNTIME_TEST_ROOT").is_some()
|
||||
&& std::env::var("HOLOLAKE_TEST_APPROVAL_SIMULATION").as_deref()
|
||||
== Ok("SIMULATE_HUMAN_APPROVAL")
|
||||
}
|
||||
|
||||
struct ConnectionGuard(Arc<AtomicUsize>);
|
||||
impl Drop for ConnectionGuard {
|
||||
fn drop(&mut self) {
|
||||
self.0.fetch_sub(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,12 @@ pub fn now() -> String {
|
|||
.map(|d| format!("{}.{:03}Z", d.as_secs(), d.subsec_millis()))
|
||||
.unwrap_or_else(|_| "0Z".into())
|
||||
}
|
||||
pub fn now_unix_ms() -> u128 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_millis())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
pub fn id(prefix: &str) -> String {
|
||||
format!("{}-{}", prefix, UuidPart::new())
|
||||
}
|
||||
|
|
@ -35,6 +41,14 @@ pub fn sha(bytes: &[u8]) -> String {
|
|||
}
|
||||
|
||||
pub fn root(app: &AppHandle) -> Result<PathBuf, String> {
|
||||
if let Some(value) = std::env::var_os("HOLOLAKE_RUNTIME_TEST_ROOT") {
|
||||
let path = PathBuf::from(value);
|
||||
if !path.is_absolute() || !path.starts_with(std::env::temp_dir()) {
|
||||
return Err("HOLOLAKE_RUNTIME_TEST_ROOT_OUT_OF_SCOPE".into());
|
||||
}
|
||||
fs::create_dir_all(&path).map_err(|e| e.to_string())?;
|
||||
return Ok(path);
|
||||
}
|
||||
let p = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
|
|
@ -43,10 +57,10 @@ pub fn root(app: &AppHandle) -> Result<PathBuf, String> {
|
|||
fs::create_dir_all(&p).map_err(|e| e.to_string())?;
|
||||
Ok(p)
|
||||
}
|
||||
fn read_json<T: serde::de::DeserializeOwned>(p: &Path) -> Result<T, String> {
|
||||
pub fn read_json<T: serde::de::DeserializeOwned>(p: &Path) -> Result<T, String> {
|
||||
serde_json::from_slice(&fs::read(p).map_err(|e| e.to_string())?).map_err(|e| e.to_string())
|
||||
}
|
||||
fn write_json<T: serde::Serialize>(p: &Path, v: &T) -> Result<(), String> {
|
||||
pub fn write_json<T: serde::Serialize + ?Sized>(p: &Path, v: &T) -> Result<(), String> {
|
||||
if let Some(x) = p.parent() {
|
||||
fs::create_dir_all(x).map_err(|e| e.to_string())?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "HoloLake",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"identifier": "world.guanghu.hololake",
|
||||
"build": { "frontendDist": "../dist", "devUrl": "http://127.0.0.1:5211", "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build" },
|
||||
"app": {
|
||||
|
|
@ -9,5 +9,5 @@
|
|||
"security": { "csp": "default-src 'self'; connect-src 'self' ipc: http://ipc.localhost https://guanghulab.com; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'" }
|
||||
},
|
||||
"plugins": { "updater": { "endpoints": ["https://guanghulab.com/hololake/releases/latest.json"], "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDEzQkYzRTQ5QTE2MEFDMzEKUldReHJHQ2hTVDYvRTZNNDVqZDUxLzRZMmxuT1pSM2Q5RTRZbzRCZUZ0d0FxVVNidGJ0dUQyMnoK" } },
|
||||
"bundle": { "active": true, "targets": ["app", "dmg"], "createUpdaterArtifacts": true, "category": "Productivity", "icon": ["icons/icon.icns", "icons/icon.png", "icons/icon.ico"], "macOS": { "signingIdentity": "Developer ID Application: bei sun (825A9L3G7Q)" } }
|
||||
"bundle": { "active": true, "targets": ["app", "dmg"], "createUpdaterArtifacts": true, "category": "Productivity", "icon": ["icons/icon.icns", "icons/icon.png", "icons/icon.ico"], "resources": {"../connectors/hololake-glp-client.py":"connectors/hololake-glp-client.py"}, "macOS": { "signingIdentity": "Developer ID Application: bei sun (825A9L3G7Q)" } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { check } from "@tauri-apps/plugin-updater";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { Icon } from "./icons";
|
||||
import * as api from "./runtime";
|
||||
import type {
|
||||
KnowledgeDocument,
|
||||
KnowledgeSummary,
|
||||
SourceKind,
|
||||
RuntimeOverview,
|
||||
SystemSnapshot,
|
||||
} from "./types";
|
||||
import {
|
||||
|
|
@ -34,6 +36,7 @@ const nav: [View, string, string][] = [
|
|||
const labels: Record<SourceKind, string> = {
|
||||
USER_MESSAGE: "用户语言",
|
||||
PERSONA_RESPONSE: "人格回应",
|
||||
EXTERNAL_AI_MESSAGE: "外部 AI",
|
||||
SYSTEM_CONTEXT: "系统环境",
|
||||
PROTOCOL_EVENT: "协议运行",
|
||||
AGENT_ACTION: "Agent 执行",
|
||||
|
|
@ -43,6 +46,7 @@ const labels: Record<SourceKind, string> = {
|
|||
|
||||
export default function App() {
|
||||
const [data, setData] = useState<SystemSnapshot | null>(null),
|
||||
[runtime, setRuntime] = useState<RuntimeOverview | null>(null),
|
||||
[view, setView] = useState<View>("channel"),
|
||||
[error, setError] = useState(""),
|
||||
[busy, setBusy] = useState(false);
|
||||
|
|
@ -51,8 +55,23 @@ export default function App() {
|
|||
.snapshot()
|
||||
.then(setData)
|
||||
.catch((e) => setError(String(e)));
|
||||
useEffect(() => {
|
||||
const refreshRuntime = () =>
|
||||
api
|
||||
.runtimeOverview()
|
||||
.then(setRuntime)
|
||||
.catch((e) => setError(String(e)));
|
||||
const refreshAll = () => {
|
||||
refresh();
|
||||
refreshRuntime();
|
||||
};
|
||||
useEffect(() => {
|
||||
refreshAll();
|
||||
if (!("__TAURI_INTERNALS__" in window)) return;
|
||||
let unlisten: (() => void) | undefined;
|
||||
listen("hololake-runtime-event", () => refreshAll()).then((dispose) => {
|
||||
unlisten = dispose;
|
||||
});
|
||||
return () => unlisten?.();
|
||||
}, []);
|
||||
if (!data)
|
||||
return (
|
||||
|
|
@ -61,7 +80,7 @@ export default function App() {
|
|||
正在进入 HoloLake…
|
||||
</div>
|
||||
);
|
||||
if (!data.channel) return <Onboarding onDone={refresh} error={error} />;
|
||||
if (!data.channel) return <Onboarding onDone={refreshAll} error={error} />;
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<div className="titlebar">
|
||||
|
|
@ -104,7 +123,8 @@ export default function App() {
|
|||
{view === "channel" && (
|
||||
<Channel
|
||||
data={data}
|
||||
refresh={refresh}
|
||||
runtime={runtime}
|
||||
refresh={refreshAll}
|
||||
openKnowledge={() => setView("knowledge")}
|
||||
/>
|
||||
)}{" "}
|
||||
|
|
@ -115,7 +135,8 @@ export default function App() {
|
|||
{view === "settings" && (
|
||||
<Settings
|
||||
data={data}
|
||||
refresh={refresh}
|
||||
runtime={runtime}
|
||||
refresh={refreshAll}
|
||||
busy={busy}
|
||||
setBusy={setBusy}
|
||||
setError={setError}
|
||||
|
|
@ -171,10 +192,12 @@ function Onboarding({ onDone, error }: { onDone: () => void; error: string }) {
|
|||
|
||||
function Channel({
|
||||
data,
|
||||
runtime,
|
||||
refresh,
|
||||
openKnowledge,
|
||||
}: {
|
||||
data: SystemSnapshot;
|
||||
runtime: RuntimeOverview | null;
|
||||
refresh: () => void;
|
||||
openKnowledge: () => void;
|
||||
}) {
|
||||
|
|
@ -210,14 +233,19 @@ function Channel({
|
|||
.filter(
|
||||
(e) =>
|
||||
e.sourceKind === "USER_MESSAGE" ||
|
||||
e.sourceKind === "PERSONA_RESPONSE",
|
||||
e.sourceKind === "PERSONA_RESPONSE" ||
|
||||
e.sourceKind === "EXTERNAL_AI_MESSAGE",
|
||||
)
|
||||
.slice(0, 8)
|
||||
.reverse()
|
||||
.map((e) => (
|
||||
<article
|
||||
className={
|
||||
e.sourceKind === "USER_MESSAGE" ? "human" : "persona"
|
||||
e.sourceKind === "USER_MESSAGE"
|
||||
? "human"
|
||||
: e.sourceKind === "PERSONA_RESPONSE"
|
||||
? "persona"
|
||||
: "external-ai"
|
||||
}
|
||||
key={e.eventId}
|
||||
>
|
||||
|
|
@ -226,6 +254,52 @@ function Channel({
|
|||
</article>
|
||||
))}
|
||||
</section>
|
||||
{runtime &&
|
||||
runtime.proposals.some(
|
||||
(proposal) => proposal.state === "PENDING_HUMAN_APPROVAL",
|
||||
) && (
|
||||
<section className="approval-queue">
|
||||
<h2>等待你确认的 Agent 动作</h2>
|
||||
{runtime.proposals
|
||||
.filter(
|
||||
(proposal) => proposal.state === "PENDING_HUMAN_APPROVAL",
|
||||
)
|
||||
.map((proposal) => (
|
||||
<article key={proposal.proposalId}>
|
||||
<div>
|
||||
<strong>{proposal.gir.programId}</strong>
|
||||
<p>
|
||||
{proposal.gir.actions
|
||||
.map((action) => action.operation)
|
||||
.join(" → ")}
|
||||
</p>
|
||||
<small>
|
||||
{proposal.clientId} · {proposal.proposalId}
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className="soft"
|
||||
onClick={async () => {
|
||||
await api.rejectProposal(proposal.proposalId);
|
||||
refresh();
|
||||
}}
|
||||
>
|
||||
驳回
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await api.approveProposal(proposal.proposalId);
|
||||
refresh();
|
||||
}}
|
||||
>
|
||||
批准并执行
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
<ReceiptStream data={data} />
|
||||
<div className="composer">
|
||||
<textarea
|
||||
|
|
@ -256,6 +330,23 @@ function Channel({
|
|||
<strong>本机已建立</strong>
|
||||
<p className="path">{data.channel?.privateGitPath}</p>
|
||||
</article>
|
||||
<article>
|
||||
<h2>公众人格运行时</h2>
|
||||
<strong>
|
||||
{runtime?.persona.personas[0]?.displayName ?? "尚未建立"}
|
||||
</strong>
|
||||
<p>
|
||||
{runtime?.persona.personas[0]?.state ?? "不会自动生成默认人格"}
|
||||
</p>
|
||||
</article>
|
||||
<article>
|
||||
<h2>GLP 实时桥</h2>
|
||||
<strong>
|
||||
{runtime?.realtime.state ?? "正在启动"} ·{" "}
|
||||
{runtime?.realtime.connectedClients ?? 0} 个连接
|
||||
</strong>
|
||||
<p>{runtime?.realtime.endpoint ?? "仅监听本机回环地址"}</p>
|
||||
</article>
|
||||
<article>
|
||||
<h2>公共更新</h2>
|
||||
<strong>
|
||||
|
|
@ -276,7 +367,9 @@ function ReceiptStream({ data }: { data: SystemSnapshot }) {
|
|||
const rows = data.timeline
|
||||
.filter(
|
||||
(e) =>
|
||||
e.sourceKind !== "USER_MESSAGE" && e.sourceKind !== "PERSONA_RESPONSE",
|
||||
e.sourceKind !== "USER_MESSAGE" &&
|
||||
e.sourceKind !== "PERSONA_RESPONSE" &&
|
||||
e.sourceKind !== "EXTERNAL_AI_MESSAGE",
|
||||
)
|
||||
.slice(0, 5);
|
||||
return (
|
||||
|
|
@ -839,18 +932,22 @@ function Portal() {
|
|||
}
|
||||
function Settings({
|
||||
data,
|
||||
runtime,
|
||||
refresh,
|
||||
busy,
|
||||
setBusy,
|
||||
setError,
|
||||
}: {
|
||||
data: SystemSnapshot;
|
||||
runtime: RuntimeOverview | null;
|
||||
refresh: () => void;
|
||||
busy: boolean;
|
||||
setBusy: (v: boolean) => void;
|
||||
setError: (v: string) => void;
|
||||
}) {
|
||||
const [bridgeName, setBridgeName] = useState("");
|
||||
const [bridgeName, setBridgeName] = useState(""),
|
||||
[personaName, setPersonaName] = useState(""),
|
||||
[runtimeMessage, setRuntimeMessage] = useState("");
|
||||
const update = async () => {
|
||||
setBusy(true);
|
||||
try {
|
||||
|
|
@ -894,7 +991,11 @@ function Settings({
|
|||
<article className="bridge">
|
||||
<div>
|
||||
<h2>外部编程 AI</h2>
|
||||
<p>以来源明确的本地表达桥接入;不能取得人格或执行主控权。</p>
|
||||
<p>
|
||||
{runtime?.realtime.endpoint ?? "本机实时桥正在启动"} ·{" "}
|
||||
{runtime?.realtime.connectedClients ?? 0}{" "}
|
||||
个实时连接。连接不授予人格或执行权。
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
|
|
@ -915,17 +1016,95 @@ function Settings({
|
|||
</button>
|
||||
</div>
|
||||
</article>
|
||||
<article className="bridge">
|
||||
<div>
|
||||
<h2>公众人格运行时</h2>
|
||||
<p>
|
||||
{runtime?.persona.personas.length
|
||||
? `${runtime.persona.personas.length} 个本频道试用人格;既有人格验证数 ${runtime.persona.verifiedExistingPersonaCount}`
|
||||
: "建立一个本频道可逆试用人格,再由外部 AI 宿主实时承载。"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
value={personaName}
|
||||
onChange={(e) => setPersonaName(e.target.value)}
|
||||
placeholder="给试用人格起名"
|
||||
/>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (personaName.trim()) {
|
||||
const persona = await api.registerPersona(personaName.trim());
|
||||
setPersonaName("");
|
||||
setRuntimeMessage(
|
||||
`${persona.displayName} 已建立;尚未验证为既有历史人格。`,
|
||||
);
|
||||
refresh();
|
||||
}
|
||||
}}
|
||||
>
|
||||
建立试用人格
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
<article>
|
||||
<div>
|
||||
<h2>实时接入命令</h2>
|
||||
<p>
|
||||
{runtimeMessage ||
|
||||
"先建立一个外部 AI 入口,然后复制本机 GLP 接入命令给外部编程 AI。"}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className="soft"
|
||||
disabled={!data.externalAiBridges.length}
|
||||
onClick={async () => {
|
||||
const invitation = await api.realtimeInvitation();
|
||||
const bridge = data.externalAiBridges[0];
|
||||
const persona = runtime?.persona.personas[0];
|
||||
const command = `${invitation.connectorCommand} --bridge-id ${bridge.bridgeId}${persona ? ` --persona-id ${persona.personaId}` : ""}`;
|
||||
await navigator.clipboard.writeText(command);
|
||||
setRuntimeMessage(
|
||||
"实时接入命令已复制。令牌只在本机 descriptor 文件中。",
|
||||
);
|
||||
}}
|
||||
>
|
||||
复制接入命令
|
||||
</button>
|
||||
</article>
|
||||
{data.externalAiBridges.map((b) => (
|
||||
<article key={b.bridgeId}>
|
||||
<div>
|
||||
<h2>{b.displayName}</h2>
|
||||
<p>
|
||||
{b.bridgeId} · {b.inboxPath}
|
||||
{b.bridgeId} ·{" "}
|
||||
{runtime?.realtime.protocol ?? "GLP_LOCAL_REALTIME/1"}
|
||||
</p>
|
||||
</div>
|
||||
<span className="status">{b.state}</span>
|
||||
</article>
|
||||
))}
|
||||
{runtime?.persona.personas.map((persona) => (
|
||||
<article key={persona.personaId}>
|
||||
<div>
|
||||
<h2>{persona.displayName}</h2>
|
||||
<p>
|
||||
{persona.personaId} · {persona.state}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className="soft danger"
|
||||
onClick={async () => {
|
||||
if (confirm(`删除试用人格 ${persona.personaId}?`)) {
|
||||
await api.deleteTrialPersona(persona.personaId);
|
||||
refresh();
|
||||
}
|
||||
}}
|
||||
>
|
||||
移除试用人格
|
||||
</button>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,53 +1,293 @@
|
|||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { Channel, ExternalAiBridge, KnowledgeDocument, MutationReceipt, SystemSnapshot, TimelineEvent } from "./types";
|
||||
import type {
|
||||
AgentProposal,
|
||||
AgentReceipt,
|
||||
Channel,
|
||||
ExternalAiBridge,
|
||||
KnowledgeDocument,
|
||||
MutationReceipt,
|
||||
PublicPersona,
|
||||
RealtimeInvitation,
|
||||
RuntimeOverview,
|
||||
SystemSnapshot,
|
||||
TimelineEvent,
|
||||
} from "./types";
|
||||
|
||||
const tauri = () => "__TAURI_INTERNALS__" in window;
|
||||
const now = () => new Date().toISOString();
|
||||
const key = "hololake.clean.browser-state.v1";
|
||||
const runtimeKey = "hololake.clean.browser-runtime.v1";
|
||||
|
||||
type BrowserState = { channel: Channel | null; timeline: TimelineEvent[]; documents: KnowledgeDocument[]; bridges: ExternalAiBridge[] };
|
||||
const empty: BrowserState = { channel: null, timeline: [], documents: [], bridges: [] };
|
||||
const load = (): BrowserState => JSON.parse(localStorage.getItem(key) ?? JSON.stringify(empty));
|
||||
const save = (state: BrowserState) => localStorage.setItem(key, JSON.stringify(state));
|
||||
const event = (sourceKind: TimelineEvent["sourceKind"], title: string, content: string, state: TimelineEvent["state"] = "SUCCEEDED"): TimelineEvent => ({ eventId: crypto.randomUUID(), sourceKind, title, content, occurredAt: now(), state });
|
||||
type BrowserState = {
|
||||
channel: Channel | null;
|
||||
timeline: TimelineEvent[];
|
||||
documents: KnowledgeDocument[];
|
||||
bridges: ExternalAiBridge[];
|
||||
};
|
||||
const empty: BrowserState = {
|
||||
channel: null,
|
||||
timeline: [],
|
||||
documents: [],
|
||||
bridges: [],
|
||||
};
|
||||
const load = (): BrowserState =>
|
||||
JSON.parse(localStorage.getItem(key) ?? JSON.stringify(empty));
|
||||
const save = (state: BrowserState) =>
|
||||
localStorage.setItem(key, JSON.stringify(state));
|
||||
const loadRuntime = (): { personas: PublicPersona[] } =>
|
||||
JSON.parse(localStorage.getItem(runtimeKey) ?? '{"personas":[]}');
|
||||
const saveRuntime = (state: { personas: PublicPersona[] }) =>
|
||||
localStorage.setItem(runtimeKey, JSON.stringify(state));
|
||||
const event = (
|
||||
sourceKind: TimelineEvent["sourceKind"],
|
||||
title: string,
|
||||
content: string,
|
||||
state: TimelineEvent["state"] = "SUCCEEDED",
|
||||
): TimelineEvent => ({
|
||||
eventId: crypto.randomUUID(),
|
||||
sourceKind,
|
||||
title,
|
||||
content,
|
||||
occurredAt: now(),
|
||||
state,
|
||||
});
|
||||
|
||||
export async function snapshot(): Promise<SystemSnapshot> {
|
||||
if (tauri()) return invoke("system_snapshot");
|
||||
const s = load();
|
||||
return { appVersion: "1.0.0-browser", channel: s.channel, timeline: s.timeline, documents: s.documents.map(({ path, title, updatedAt, sha256 }) => ({ path, title, updatedAt, sha256 })), moduleRegistryVersion: "1", publicUpdateEndpoint: "https://guanghulab.com/hololake/releases/latest.json", publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE", externalAiBridges: s.bridges };
|
||||
return {
|
||||
appVersion: "1.1.0-browser",
|
||||
channel: s.channel,
|
||||
timeline: s.timeline,
|
||||
documents: s.documents.map(({ path, title, updatedAt, sha256 }) => ({
|
||||
path,
|
||||
title,
|
||||
updatedAt,
|
||||
sha256,
|
||||
})),
|
||||
moduleRegistryVersion: "1",
|
||||
publicUpdateEndpoint:
|
||||
"https://guanghulab.com/hololake/releases/latest.json",
|
||||
publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE",
|
||||
externalAiBridges: s.bridges,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createChannel(name: string): Promise<Channel> {
|
||||
if (tauri()) return invoke("create_channel", { name });
|
||||
const s = load(); const channel: Channel = { channelId: `HL-CH-${crypto.randomUUID().slice(0,8).toUpperCase()}`, name, createdAt: now(), privateGitPath: "浏览器预览不写入磁盘", publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE" };
|
||||
s.channel = channel; s.timeline.unshift(event("SYSTEM_RECEIPT", "频道已建立", `${channel.channelId} 已登记。`)); save(s); return channel;
|
||||
const s = load();
|
||||
const channel: Channel = {
|
||||
channelId: `HL-CH-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
|
||||
name,
|
||||
createdAt: now(),
|
||||
privateGitPath: "浏览器预览不写入磁盘",
|
||||
publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE",
|
||||
};
|
||||
s.channel = channel;
|
||||
s.timeline.unshift(
|
||||
event("SYSTEM_RECEIPT", "频道已建立", `${channel.channelId} 已登记。`),
|
||||
);
|
||||
save(s);
|
||||
return channel;
|
||||
}
|
||||
|
||||
export async function submitMessage(content: string): Promise<MutationReceipt> {
|
||||
if (tauri()) return invoke("submit_user_message", { content });
|
||||
const s = load(); const e = event("USER_MESSAGE", "用户语言", content, "RECORDED"); s.timeline.unshift(e, event("SYSTEM_RECEIPT", "系统回执", "用户语言已分流保存;等待人格体接入,不伪造人格回应。", "WAITING")); save(s);
|
||||
return { receiptId: crypto.randomUUID(), state: "WAITING_PERSONA", message: "用户语言已记录", gitCommit: null, event: e };
|
||||
const s = load();
|
||||
const e = event("USER_MESSAGE", "用户语言", content, "RECORDED");
|
||||
s.timeline.unshift(
|
||||
e,
|
||||
event(
|
||||
"SYSTEM_RECEIPT",
|
||||
"系统回执",
|
||||
"用户语言已分流保存;等待人格体接入,不伪造人格回应。",
|
||||
"WAITING",
|
||||
),
|
||||
);
|
||||
save(s);
|
||||
return {
|
||||
receiptId: crypto.randomUUID(),
|
||||
state: "WAITING_PERSONA",
|
||||
message: "用户语言已记录",
|
||||
gitCommit: null,
|
||||
event: e,
|
||||
};
|
||||
}
|
||||
|
||||
export async function readDocument(path: string): Promise<KnowledgeDocument> {
|
||||
if (tauri()) return invoke("read_knowledge_document", { path });
|
||||
const doc = load().documents.find(d => d.path === path); if (!doc) throw new Error("文档不存在"); return doc;
|
||||
const doc = load().documents.find((d) => d.path === path);
|
||||
if (!doc) throw new Error("文档不存在");
|
||||
return doc;
|
||||
}
|
||||
export async function createDocument(title: string): Promise<MutationReceipt> {
|
||||
if (tauri()) return invoke("create_knowledge_document", { title });
|
||||
const s = load(); const path = `${title.trim().replace(/[\\/:*?\"<>|]/g,"-")}.md`; const d = { path, title: title.trim(), body: `# ${title.trim()}\n\n`, updatedAt: now(), sha256: "browser-preview" }; s.documents.unshift(d); const e=event("AGENT_ACTION","Agent 执行",`已创建 ${path}`); s.timeline.unshift(e); save(s); return {receiptId:crypto.randomUUID(),state:"SUCCEEDED",message:"文档已创建",gitCommit:"browser-preview",event:e};
|
||||
const s = load();
|
||||
const path = `${title.trim().replace(/[\\/:*?\"<>|]/g, "-")}.md`;
|
||||
const d = {
|
||||
path,
|
||||
title: title.trim(),
|
||||
body: `# ${title.trim()}\n\n`,
|
||||
updatedAt: now(),
|
||||
sha256: "browser-preview",
|
||||
};
|
||||
s.documents.unshift(d);
|
||||
const e = event("AGENT_ACTION", "Agent 执行", `已创建 ${path}`);
|
||||
s.timeline.unshift(e);
|
||||
save(s);
|
||||
return {
|
||||
receiptId: crypto.randomUUID(),
|
||||
state: "SUCCEEDED",
|
||||
message: "文档已创建",
|
||||
gitCommit: "browser-preview",
|
||||
event: e,
|
||||
};
|
||||
}
|
||||
export async function saveDocument(path: string, body: string): Promise<MutationReceipt> {
|
||||
export async function saveDocument(
|
||||
path: string,
|
||||
body: string,
|
||||
): Promise<MutationReceipt> {
|
||||
if (tauri()) return invoke("save_knowledge_document", { path, body });
|
||||
const s=load(); const d=s.documents.find(x=>x.path===path); if(!d) throw new Error("文档不存在"); d.body=body; d.updatedAt=now(); const e=event("SYSTEM_RECEIPT","系统回执",`${path} 已保存到私人 Git 预览层。`); s.timeline.unshift(e); save(s); return {receiptId:crypto.randomUUID(),state:"SUCCEEDED",message:"保存完成",gitCommit:"browser-preview",event:e};
|
||||
const s = load();
|
||||
const d = s.documents.find((x) => x.path === path);
|
||||
if (!d) throw new Error("文档不存在");
|
||||
d.body = body;
|
||||
d.updatedAt = now();
|
||||
const e = event(
|
||||
"SYSTEM_RECEIPT",
|
||||
"系统回执",
|
||||
`${path} 已保存到私人 Git 预览层。`,
|
||||
);
|
||||
s.timeline.unshift(e);
|
||||
save(s);
|
||||
return {
|
||||
receiptId: crypto.randomUUID(),
|
||||
state: "SUCCEEDED",
|
||||
message: "保存完成",
|
||||
gitCommit: "browser-preview",
|
||||
event: e,
|
||||
};
|
||||
}
|
||||
export async function deleteDocument(path: string): Promise<MutationReceipt> {
|
||||
if (tauri()) return invoke("delete_knowledge_document", { path });
|
||||
const s=load(); s.documents=s.documents.filter(x=>x.path!==path); const e=event("SYSTEM_RECEIPT","系统回执",`${path} 已移入回收站。`); s.timeline.unshift(e); save(s); return {receiptId:crypto.randomUUID(),state:"SUCCEEDED",message:"已移入回收站",gitCommit:"browser-preview",event:e};
|
||||
const s = load();
|
||||
s.documents = s.documents.filter((x) => x.path !== path);
|
||||
const e = event("SYSTEM_RECEIPT", "系统回执", `${path} 已移入回收站。`);
|
||||
s.timeline.unshift(e);
|
||||
save(s);
|
||||
return {
|
||||
receiptId: crypto.randomUUID(),
|
||||
state: "SUCCEEDED",
|
||||
message: "已移入回收站",
|
||||
gitCommit: "browser-preview",
|
||||
event: e,
|
||||
};
|
||||
}
|
||||
export async function importFolder(): Promise<MutationReceipt | null> { if (!tauri()) throw new Error("文件夹导入请在已安装的 HoloLake 中使用"); return invoke("import_knowledge_folder"); }
|
||||
export async function exportDocument(path: string): Promise<string | null> { if (!tauri()) throw new Error("下载请在已安装的 HoloLake 中使用"); return invoke("export_knowledge_document", { path }); }
|
||||
export async function registerBridge(displayName: string): Promise<ExternalAiBridge> {
|
||||
export async function importFolder(): Promise<MutationReceipt | null> {
|
||||
if (!tauri()) throw new Error("文件夹导入请在已安装的 HoloLake 中使用");
|
||||
return invoke("import_knowledge_folder");
|
||||
}
|
||||
export async function exportDocument(path: string): Promise<string | null> {
|
||||
if (!tauri()) throw new Error("下载请在已安装的 HoloLake 中使用");
|
||||
return invoke("export_knowledge_document", { path });
|
||||
}
|
||||
export async function registerBridge(
|
||||
displayName: string,
|
||||
): Promise<ExternalAiBridge> {
|
||||
if (tauri()) return invoke("register_external_ai_bridge", { displayName });
|
||||
const s=load(); const b={bridgeId:`HL-AI-${crypto.randomUUID().slice(0,8).toUpperCase()}`,displayName,inboxPath:"浏览器预览",outboxPath:"浏览器预览",state:"LOCAL_EXPRESSION_ONLY"}; s.bridges.push(b); save(s); return b;
|
||||
const s = load();
|
||||
const b = {
|
||||
bridgeId: `HL-AI-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
|
||||
displayName,
|
||||
inboxPath: "浏览器预览",
|
||||
outboxPath: "浏览器预览",
|
||||
state: "LOCAL_EXPRESSION_ONLY",
|
||||
};
|
||||
s.bridges.push(b);
|
||||
save(s);
|
||||
return b;
|
||||
}
|
||||
export async function runtimeOverview(): Promise<RuntimeOverview> {
|
||||
if (tauri()) return invoke("get_runtime_overview");
|
||||
const preview = loadRuntime();
|
||||
return {
|
||||
realtime: {
|
||||
schema: "preview",
|
||||
state: "LISTENING",
|
||||
protocol: "GLP_LOCAL_REALTIME/1",
|
||||
endpoint: "tcp://127.0.0.1:39281",
|
||||
connectedClients: 0,
|
||||
loopbackOnly: true,
|
||||
transportGrantsAuthority: false,
|
||||
},
|
||||
persona: {
|
||||
schema: "preview",
|
||||
state: preview.personas.length
|
||||
? "TRIAL_PERSONA_READY"
|
||||
: "READY_NO_PERSONA",
|
||||
activePersonaId: preview.personas[0]?.personaId ?? null,
|
||||
personas: preview.personas,
|
||||
verifiedExistingPersonaCount: 0,
|
||||
},
|
||||
proposals: [],
|
||||
};
|
||||
}
|
||||
export async function realtimeInvitation(): Promise<RealtimeInvitation> {
|
||||
if (tauri()) return invoke("get_realtime_invitation");
|
||||
return {
|
||||
schema: "preview",
|
||||
protocol: "GLP_LOCAL_REALTIME/1",
|
||||
endpoint: "tcp://127.0.0.1:39281",
|
||||
token: "browser-preview",
|
||||
descriptorPath: "browser-preview",
|
||||
connectorCommand: "仅已安装 App 可生成接入命令",
|
||||
warning: "令牌不证明人格身份。",
|
||||
};
|
||||
}
|
||||
export async function registerPersona(
|
||||
displayName: string,
|
||||
): Promise<PublicPersona> {
|
||||
if (tauri()) return invoke("register_public_persona", { displayName });
|
||||
const persona = {
|
||||
personaId: `HL-PERSONA-${crypto.randomUUID().slice(0, 10).toUpperCase()}`,
|
||||
displayName,
|
||||
state: "LOCAL_TRIAL_UNVERIFIED_EXTERNAL_HOST_REQUIRED",
|
||||
createdAt: now(),
|
||||
reversibleUntilUnixMs: Date.now() + 30 * 86400000,
|
||||
};
|
||||
const preview = loadRuntime();
|
||||
preview.personas.push(persona);
|
||||
saveRuntime(preview);
|
||||
return persona;
|
||||
}
|
||||
export async function deleteTrialPersona(
|
||||
personaId: string,
|
||||
): Promise<RuntimeOverview["persona"]> {
|
||||
if (tauri())
|
||||
return invoke("delete_trial_persona", {
|
||||
personaId,
|
||||
exactConfirmation: `删除试用人格 ${personaId}`,
|
||||
});
|
||||
const preview = loadRuntime();
|
||||
preview.personas = preview.personas.filter(
|
||||
(persona) => persona.personaId !== personaId,
|
||||
);
|
||||
saveRuntime(preview);
|
||||
return {
|
||||
schema: "preview",
|
||||
state: preview.personas.length ? "TRIAL_PERSONA_READY" : "READY_NO_PERSONA",
|
||||
activePersonaId: preview.personas[0]?.personaId ?? null,
|
||||
personas: preview.personas,
|
||||
verifiedExistingPersonaCount: 0,
|
||||
};
|
||||
}
|
||||
export async function approveProposal(
|
||||
proposalId: string,
|
||||
): Promise<AgentReceipt> {
|
||||
return invoke("approve_agent_proposal", { proposalId });
|
||||
}
|
||||
export async function rejectProposal(
|
||||
proposalId: string,
|
||||
): Promise<AgentProposal> {
|
||||
return invoke("reject_agent_proposal", { proposalId });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -353,6 +353,9 @@ main {
|
|||
.conversation article.persona span {
|
||||
color: #c5a6ff;
|
||||
}
|
||||
.conversation article.external-ai span {
|
||||
color: #72d6ca;
|
||||
}
|
||||
.conversation article p {
|
||||
margin: 0;
|
||||
line-height: 1.7;
|
||||
|
|
@ -365,6 +368,47 @@ main {
|
|||
border-radius: 18px;
|
||||
background: rgba(4, 16, 30, 0.74);
|
||||
}
|
||||
.approval-queue {
|
||||
margin-top: 18px;
|
||||
padding: 18px 20px;
|
||||
border: 1px solid rgba(240, 197, 107, 0.38);
|
||||
border-radius: 18px;
|
||||
background: rgba(40, 29, 12, 0.36);
|
||||
}
|
||||
.approval-queue > h2 {
|
||||
margin: 0 0 12px;
|
||||
color: #f4d895;
|
||||
font-size: 13px;
|
||||
}
|
||||
.approval-queue article {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
padding: 12px 13px;
|
||||
border-radius: 11px;
|
||||
background: rgba(6, 18, 32, 0.7);
|
||||
}
|
||||
.approval-queue article + article {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.approval-queue strong {
|
||||
font-size: 13px;
|
||||
}
|
||||
.approval-queue p {
|
||||
margin: 5px 0;
|
||||
color: #b7c8d8;
|
||||
font-size: 11px;
|
||||
}
|
||||
.approval-queue small {
|
||||
color: #6f879e;
|
||||
font-size: 9px;
|
||||
}
|
||||
.approval-queue article > div:last-child {
|
||||
display: flex;
|
||||
flex: none;
|
||||
gap: 7px;
|
||||
}
|
||||
.receipt-stream h2 {
|
||||
font-size: 13px;
|
||||
font-weight: 570;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,128 @@
|
|||
export type SourceKind = "USER_MESSAGE" | "PERSONA_RESPONSE" | "SYSTEM_CONTEXT" | "PROTOCOL_EVENT" | "AGENT_ACTION" | "TOOL_RESULT" | "SYSTEM_RECEIPT";
|
||||
export type SourceKind =
|
||||
| "USER_MESSAGE"
|
||||
| "PERSONA_RESPONSE"
|
||||
| "EXTERNAL_AI_MESSAGE"
|
||||
| "SYSTEM_CONTEXT"
|
||||
| "PROTOCOL_EVENT"
|
||||
| "AGENT_ACTION"
|
||||
| "TOOL_RESULT"
|
||||
| "SYSTEM_RECEIPT";
|
||||
|
||||
export interface Channel { channelId: string; name: string; createdAt: string; privateGitPath: string; publicDistributionState: string; }
|
||||
export interface TimelineEvent { eventId: string; sourceKind: SourceKind; title: string; content: string; occurredAt: string; state: "RECORDED" | "WAITING" | "RUNNING" | "SUCCEEDED" | "FAILED"; }
|
||||
export interface KnowledgeDocument { path: string; title: string; body: string; updatedAt: string; sha256: string; }
|
||||
export interface KnowledgeSummary { path: string; title: string; updatedAt: string; sha256: string; }
|
||||
export interface SystemSnapshot { appVersion: string; channel: Channel | null; timeline: TimelineEvent[]; documents: KnowledgeSummary[]; moduleRegistryVersion: string; publicUpdateEndpoint: string; publicDistributionState: string; externalAiBridges: ExternalAiBridge[]; }
|
||||
export interface ExternalAiBridge { bridgeId: string; displayName: string; inboxPath: string; outboxPath: string; state: string; }
|
||||
export interface MutationReceipt { receiptId: string; state: string; message: string; gitCommit: string | null; event: TimelineEvent; }
|
||||
export interface Channel {
|
||||
channelId: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
privateGitPath: string;
|
||||
publicDistributionState: string;
|
||||
}
|
||||
export interface TimelineEvent {
|
||||
eventId: string;
|
||||
sourceKind: SourceKind;
|
||||
title: string;
|
||||
content: string;
|
||||
occurredAt: string;
|
||||
state: "RECORDED" | "WAITING" | "RUNNING" | "SUCCEEDED" | "FAILED";
|
||||
}
|
||||
export interface KnowledgeDocument {
|
||||
path: string;
|
||||
title: string;
|
||||
body: string;
|
||||
updatedAt: string;
|
||||
sha256: string;
|
||||
}
|
||||
export interface KnowledgeSummary {
|
||||
path: string;
|
||||
title: string;
|
||||
updatedAt: string;
|
||||
sha256: string;
|
||||
}
|
||||
export interface SystemSnapshot {
|
||||
appVersion: string;
|
||||
channel: Channel | null;
|
||||
timeline: TimelineEvent[];
|
||||
documents: KnowledgeSummary[];
|
||||
moduleRegistryVersion: string;
|
||||
publicUpdateEndpoint: string;
|
||||
publicDistributionState: string;
|
||||
externalAiBridges: ExternalAiBridge[];
|
||||
}
|
||||
export interface ExternalAiBridge {
|
||||
bridgeId: string;
|
||||
displayName: string;
|
||||
inboxPath: string;
|
||||
outboxPath: string;
|
||||
state: string;
|
||||
}
|
||||
export interface MutationReceipt {
|
||||
receiptId: string;
|
||||
state: string;
|
||||
message: string;
|
||||
gitCommit: string | null;
|
||||
event: TimelineEvent;
|
||||
}
|
||||
export interface PublicPersona {
|
||||
personaId: string;
|
||||
displayName: string;
|
||||
state: string;
|
||||
createdAt: string;
|
||||
reversibleUntilUnixMs: number;
|
||||
}
|
||||
export interface GirAction {
|
||||
actionId: string;
|
||||
operation: string;
|
||||
input: Record<string, unknown>;
|
||||
}
|
||||
export interface GirProgram {
|
||||
schema: string;
|
||||
programId: string;
|
||||
channelId: string;
|
||||
sourceSha256: string;
|
||||
compilerId: string;
|
||||
unresolvedNaturalLanguage: boolean;
|
||||
actions: GirAction[];
|
||||
}
|
||||
export interface AgentProposal {
|
||||
proposalId: string;
|
||||
clientId: string;
|
||||
personaId: string | null;
|
||||
state: string;
|
||||
createdAt: string;
|
||||
gir: GirProgram;
|
||||
}
|
||||
export interface AgentReceipt {
|
||||
schema: string;
|
||||
receiptId: string;
|
||||
proposalId: string;
|
||||
state: string;
|
||||
actionResults: unknown[];
|
||||
gitCommit: string | null;
|
||||
targetReadbackSha256: string;
|
||||
}
|
||||
export interface RuntimeOverview {
|
||||
realtime: {
|
||||
schema: string;
|
||||
state: string;
|
||||
protocol: string;
|
||||
endpoint: string;
|
||||
connectedClients: number;
|
||||
loopbackOnly: boolean;
|
||||
transportGrantsAuthority: boolean;
|
||||
};
|
||||
persona: {
|
||||
schema: string;
|
||||
state: string;
|
||||
activePersonaId: string | null;
|
||||
personas: PublicPersona[];
|
||||
verifiedExistingPersonaCount: number;
|
||||
};
|
||||
proposals: AgentProposal[];
|
||||
}
|
||||
export interface RealtimeInvitation {
|
||||
schema: string;
|
||||
protocol: string;
|
||||
endpoint: string;
|
||||
token: string;
|
||||
descriptorPath: string;
|
||||
connectorCommand: string;
|
||||
warning: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue