Files
HomeAgent/cmd/ohos/HologramAgent/entry/src/main/ets/common/DeviceBridgeSession.ets
JianFeeeee f256098c9f refactor: 全面改名为 HologramAgent + 提示词技术内容下沉到 skill/工具描述 + 修 21 处乱码
## ① 全面改名 HomeAgent → HologramAgent

仓库已改名,但代码与文档里仍有大量旧名。本次按"用户能看到的优先"全量替换:

- 用户可见:登录页标题/页脚、默认人格第一句、CLI 状态输出、启动日志、
  plugin_install 的工具描述、AI 图片插件的 User-Agent
- 文档与脚本:README、assets/docs、docs/zh、deploy/*.sh、.github/workflows
- 鸿蒙工程:cmd/ohos/HomeAgent → cmd/ohos/HologramAgent(git mv)
  目录内的 module/app 描述同步;**证书路径原样保留**
  (default_HomeAgent_xxx.cer/p7b/p12 是签名材料的实际文件名,改了会找不到证书)
- 插件元数据:plugin.json 的 author

★ 必须保持原样的两处(改了会坏):
  · legacySeededPromptSignature = "你是 HomeAgent 的看板娘「小宅」…"
    —— 那是识别历史播种人格卡的**原文锚点**,改了就识别不出旧数据
  · 公开 SDK 的 ProxyAuthHomeAgent = "homeagent"
    —— 那是 SDK 契约里的值,属独立仓库,本仓不改

## ② 修 21 处乱码(U+FFFD)

这些是历史编辑留下的编码破坏,全在注释里,例如:
  「赌注重下���遍」→「赌注重下一遍」
  「「不��滤」」  →「「不过滤」」
  「**待��退场**」→「**待退场**」

★ 保留 providers/chineseclip/tokenizer.go 里的 2 处:那是**故意**用
  U+FFFD 演示"非法 UTF-8 字节被归一成 U+FFFD",不是乱码。

## ③ 提示词技术内容下沉(用户:技术性内容应该收敛到 skill 与 -help 中)

实测系统提示词 2309 字,其中两段几乎全是**模型无法据以行动的实现细节**:

  【工具执行顺序】1272 字(ParallelSafe 声明、批内降级、batchRunnable 判据…)
  【三层记忆】   1069 字(L1/L2/L3 编号、消费式删除、向量空间、指纹…)

收敛后 **2309 → 1723 字(-25%)**,保留的全是"模型据此做什么不同的事":

  工具顺序:默认并行 / 不要依赖先后 / 同通道保序 / 写类工具可能串行
  三层记忆:时序里能看最近 / doc_query 把历史事件按原始时间戳插回时序 /
           关系用 memory_recall / 成文知识用 knowledge_search

判断口径写进判据:新加一段提示词前先问"模型会据此做什么不同的事",
答不上来就该进 skill 或工具描述。

## 判据

prompt_memory_test.go 新增 2 条:
- TestSystemPromptHasNoImplementationJargon
  黑名单:ParallelSafe / batchRunnable / 消费式 / DenseCosine / fingerprint /
  向量空间 / 批内降级 / 自激
- TestSystemPromptStaysCompact:上限 2000 字(当前 1723)

变异:把 "ParallelSafe 声明、批内降级" 加回工具顺序段 ⇒ 第一条立刻红。

## 验证

- go build ./... / go vet ./... 干净
- go test ./... -count=1:58 包 0 失败
- make check-client-versions:客户端与内核版本一致
2026-10-22 20:49:44 +08:00

181 lines
6.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { common } from '@kit.AbilityKit';
import { http } from '@kit.NetworkKit';
import { deviceBridge } from './DeviceBridge';
import { installCmdRouter, setBridgeAppContext } from './BridgeRouter';
import { LOCAL_DEVICE_CAPS, shutdownSpeakerUse } from './BridgeCaps';
import { connStore } from './ConnStore';
import { ConnectionConfig } from '../model/Model';
export { LOCAL_DEVICE_CAPS } from './BridgeCaps';
let bridgeStarting: boolean = false;
let foregroundActive: boolean = false;
let rootUIReady: boolean = false;
let bridgeGeneration: number = 0;
let stateTrackingReady: boolean = false;
function ensureBridgeStateTracking(): void {
if (stateTrackingReady) {
return;
}
stateTrackingReady = true;
AppStorage.setOrCreate<boolean>('deviceBridgeConnected', false);
deviceBridge.setStateListener((open: boolean) => {
AppStorage.set<boolean>('deviceBridgeConnected', open);
});
}
/**
* 把门户 HTTP 地址转换为同源设备桥 WebSocket 地址(**回退路径**)。
*
* 网关改造为子域反代后位于 devices.<基域名>,而基域名与子域标签都是**服务端
* 配置**,客户端拼不出来。正常路径是先调 discoverGateway() 问服务端;
* 本函数只在「服务端没有发现端点」(老版本 HologramAgent)时兜底,
* 保留旧部署的可用性。
*/
export function deviceGatewayUrl(base: string): string {
let trimmed: string = base.trim();
while (trimmed.length > 0 && trimmed.charAt(trimmed.length - 1) === '/') {
trimmed = trimmed.substring(0, trimmed.length - 1);
}
let scheme: string = 'ws://';
let rest: string = trimmed;
if (trimmed.startsWith('https://')) {
scheme = 'wss://';
rest = trimmed.substring('https://'.length);
} else if (trimmed.startsWith('http://')) {
rest = trimmed.substring('http://'.length);
} else if (trimmed.startsWith('wss://')) {
scheme = 'wss://';
rest = trimmed.substring('wss://'.length);
} else if (trimmed.startsWith('ws://')) {
rest = trimmed.substring('ws://'.length);
}
return scheme + rest + '/api/v1/device/ws';
}
/**
* 应用进入前台后建立全局设备桥。它不再依赖用户先打开“设备”Tab,
* 因而 screensue、clipboardsee 等前台能力从主页面加载后即可接收。
*/
/**
* 向门户询问设备网关的**权威地址**。
*
* 为什么必须问而不是自己拼:网关现在挂在 devices.<基域名> 子域上,基域名
* (webui.base_domain,默认 localhost)与子域标签(插件声明里可改)都在
* 服务端,客户端无从得知。服务端作答是唯一不会漂移的做法。
*
* 失败/老版本(404)不报错,返回空串让调用方回退到 deviceGatewayUrl()——
* 发现是增强而非必需。
*/
async function discoverGateway(portalUrl: string, apiKey: string): Promise<string> {
let base: string = portalUrl.trim();
if (base.length === 0) {
return '';
}
// 用户配置里可能填的是完整网关地址:截到门户根再拼发现路径
const cut: number = base.indexOf('/api/v1/');
if (cut >= 0) {
base = base.substring(0, cut);
}
while (base.length > 0 && base.charAt(base.length - 1) === '/') {
base = base.substring(0, base.length - 1);
}
try {
const r = await http.createHttp().request(base + '/api/v1/device/gateway', {
method: http.RequestMethod.GET,
header: { 'X-API-Key': apiKey } as Record<string, string>,
connectTimeout: 5000,
readTimeout: 5000,
});
if (r.responseCode !== 200) {
return '';
}
const body: string = typeof r.result === 'string' ? r.result : '';
const parsed: Record<string, Object> = JSON.parse(body) as Record<string, Object>;
// 服务端明确报告不可用(没有声明设备网关反代)时不返回地址,
// 让调用方回退,而不是拿着一个连不上的 URL 反复重连。
if (parsed['available'] !== true) {
return '';
}
// 优先**门户同源形态**(url_portal:同一 host、同一端口)。
//
// 原因:子域形态 devices.<基域名> 依赖 DNS 解析,而 *.localhost 只有
// 浏览器内置该特例(RFC 6761)—— 应用内 HTTP/WS 客户端走系统解析器,
// 通常解析不到。门户同源形态无任何 DNS 依赖,永远可解析。
const portal: string = parsed['url_portal'] as string;
if (portal !== undefined && portal !== null && portal.length > 0) {
return portal;
}
const url: string = parsed['url'] as string;
return url === undefined || url === null ? '' : url;
} catch (e) {
// 网络失败 / 老版本无此端点:静默回退
return '';
}
}
/** 解析设备桥最终使用的网关地址:优先服务端发现,回退同源推导。 */
async function resolveGatewayUrl(portalUrl: string, apiKey: string): Promise<string> {
const discovered: string = await discoverGateway(portalUrl, apiKey);
if (discovered.length > 0) {
return discovered;
}
return deviceGatewayUrl(portalUrl);
}
export async function startForegroundBridge(context: common.UIAbilityContext): Promise<void> {
foregroundActive = true;
setBridgeAppContext(context);
installCmdRouter();
ensureBridgeStateTracking();
if (!rootUIReady || deviceBridge.isConnected() || bridgeStarting) {
return;
}
const cur: ConnectionConfig | null = connStore.getCurrentConnection();
if (cur === null || cur.url.length === 0 || cur.apiKey.length === 0) {
return;
}
bridgeStarting = true;
bridgeGeneration = bridgeGeneration + 1;
const generation: number = bridgeGeneration;
const deviceId: string = connStore.ensureDeviceId();
try {
const gatewayUrl: string = await resolveGatewayUrl(cur.url, cur.apiKey);
await deviceBridge.connect(
gatewayUrl, cur.apiKey, deviceId,
LOCAL_DEVICE_CAPS, 'ohos-phone', connStore.getDeviceAuth(), connStore.getDeviceName());
if (!foregroundActive || generation !== bridgeGeneration) {
deviceBridge.disconnect();
}
} catch (e) {
// DeviceBridge 自己会安排重连;前台启动不弹技术错误打扰用户。
}
if (generation === bridgeGeneration) {
bridgeStarting = false;
}
}
/** 根页面挂载完成后才连接,避免首条 screensue 到达时展示层尚未注册。 */
export function markForegroundBridgeUIReady(context: common.UIAbilityContext): void {
rootUIReady = true;
startForegroundBridge(context);
}
/** 后台不接受需要前台 UI/剪贴板授权的命令。 */
export function stopForegroundBridge(): void {
foregroundActive = false;
bridgeGeneration = bridgeGeneration + 1;
bridgeStarting = false;
shutdownSpeakerUse();
deviceBridge.disconnect();
}
/** 连接配置切换或修改后立即让设备桥使用新地址和 Token。 */
export async function restartForegroundBridge(context: common.UIAbilityContext): Promise<void> {
bridgeGeneration = bridgeGeneration + 1;
bridgeStarting = false;
deviceBridge.disconnect();
await startForegroundBridge(context);
}