mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 01:07:35 +00:00
## ① 全面改名 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:客户端与内核版本一致
175 lines
6.5 KiB
Plaintext
175 lines
6.5 KiB
Plaintext
/**
|
||
* 插件数据获取(无 UI 依赖)。
|
||
*
|
||
* 从 pages/PluginsPage.ets 抽出:三处接口的取数、合并口径与字段解析
|
||
* 都放在这里,页面只负责把结果落到 @State。
|
||
*
|
||
* 数据源对齐 WebGUI renderPlugins:
|
||
* - GET /kernel → plugins[{name,loaded}](含全部内置插件)+ tools(按 plugin 归属)
|
||
* - GET /plugins → 已安装外部插件元数据(version/description 等)
|
||
* - GET /plugins/disabled → {disabled:[{name,...}]}
|
||
* 三方按名称合并去重排序。
|
||
*/
|
||
|
||
import { apiClient } from './ApiClient';
|
||
import { PluginRow, PluginDetail, emptyPluginDetail } from '../model/Model';
|
||
|
||
/** 拉取并合并插件列表;失败时抛错,由调用方转成人话提示。 */
|
||
export async function fetchPluginRows(): Promise<PluginRow[]> {
|
||
// ---- kernel: loaded plugins + tool ownership ----
|
||
const loadedMap: Map<string, boolean> = new Map<string, boolean>();
|
||
const toolsByPlugin: Map<string, string[]> = new Map<string, string[]>();
|
||
const kResp = await apiClient.getWithTimeout('/kernel', 12000);
|
||
const kernelObj: Record<string, Object> = JSON.parse(kResp.body) as Record<string, Object>;
|
||
const kpRaw: Object | undefined = kernelObj['plugins'];
|
||
if (kpRaw !== undefined && kpRaw !== null) {
|
||
const kpArr: Object[] = kpRaw as Object[];
|
||
for (let i = 0; i < kpArr.length; i++) {
|
||
const item: Record<string, Object> = kpArr[i] as Record<string, Object>;
|
||
const n: string = item['name'] as string ?? '';
|
||
if (n.length === 0) {
|
||
continue;
|
||
}
|
||
loadedMap.set(n, item['loaded'] as boolean ?? true);
|
||
}
|
||
}
|
||
const tRaw: Object | undefined = kernelObj['tools'];
|
||
if (tRaw !== undefined && tRaw !== null) {
|
||
const tArr: Object[] = tRaw as Object[];
|
||
for (let i = 0; i < tArr.length; i++) {
|
||
const item: Record<string, Object> = tArr[i] as Record<string, Object>;
|
||
const tn: string = item['name'] as string ?? '';
|
||
const owner: string = item['plugin'] as string ?? '';
|
||
if (tn.length === 0 || owner.length === 0) {
|
||
continue;
|
||
}
|
||
let list: string[] | undefined = toolsByPlugin.get(owner);
|
||
if (list === undefined) {
|
||
list = [];
|
||
toolsByPlugin.set(owner, list);
|
||
}
|
||
// 每插件最多展示 8 个工具名,避免卡片过长
|
||
if (list.length < 8) {
|
||
list.push(tn);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---- installed external plugins metadata ----
|
||
const externalMeta: Map<string, Record<string, Object>> = new Map<string, Record<string, Object>>();
|
||
try {
|
||
const pResp = await apiClient.getWithTimeout('/plugins', 10000);
|
||
const bodyTrim = pResp.body.trim();
|
||
let arr: Object[] = [];
|
||
if (bodyTrim.length > 0 && bodyTrim.charAt(0) === '[') {
|
||
arr = JSON.parse(pResp.body) as Object[];
|
||
} else {
|
||
const obj: Record<string, Object> = JSON.parse(pResp.body) as Record<string, Object>;
|
||
const rawList: Object = obj['plugins'] ?? obj['data'];
|
||
if (rawList !== undefined && rawList !== null) {
|
||
arr = rawList as Object[];
|
||
}
|
||
}
|
||
for (let i = 0; i < arr.length; i++) {
|
||
const item: Record<string, Object> = arr[i] as Record<string, Object>;
|
||
const n: string = item['name'] as string ?? '';
|
||
if (n.length > 0) {
|
||
externalMeta.set(n, item);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
// 外部列表失败不阻塞内置展示
|
||
}
|
||
|
||
// ---- disabled list ----
|
||
const disabledNames: Set<string> = new Set<string>();
|
||
try {
|
||
const dResp = await apiClient.getWithTimeout('/plugins/disabled', 8000);
|
||
const dObj: Record<string, Object> = JSON.parse(dResp.body) as Record<string, Object>;
|
||
const dArr: Object | undefined = dObj['disabled'];
|
||
if (dArr !== undefined && dArr !== null) {
|
||
const items: Object[] = dArr as Object[];
|
||
for (let di = 0; di < items.length; di++) {
|
||
const dItem: Record<string, Object> = items[di] as Record<string, Object>;
|
||
const dn: string = dItem['name'] as string ?? '';
|
||
if (dn.length > 0) {
|
||
disabledNames.add(dn);
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
// disabled endpoint may not exist; ignore
|
||
}
|
||
|
||
// ---- merge: allNames sorted(与 GUI 一致)----
|
||
const allNames: Set<string> = new Set<string>();
|
||
loadedMap.forEach((v: boolean, k: string) => {
|
||
allNames.add(k);
|
||
});
|
||
externalMeta.forEach((v: Record<string, Object>, k: string) => {
|
||
allNames.add(k);
|
||
});
|
||
disabledNames.forEach((n: string) => {
|
||
allNames.add(n);
|
||
});
|
||
const names: string[] = Array.from(allNames);
|
||
names.sort();
|
||
|
||
const rows: PluginRow[] = [];
|
||
for (let i = 0; i < names.length; i++) {
|
||
const name: string = names[i];
|
||
const meta: Record<string, Object> | undefined = externalMeta.get(name);
|
||
const tools: string[] | undefined = toolsByPlugin.get(name);
|
||
const row: PluginRow = {
|
||
name: name,
|
||
loaded: loadedMap.get(name) ?? false,
|
||
disabled: disabledNames.has(name),
|
||
external: externalMeta.has(name),
|
||
version: meta !== undefined ? meta['version'] as string ?? '' : '',
|
||
description: meta !== undefined ? meta['description'] as string ?? '' : '',
|
||
tools: tools,
|
||
};
|
||
rows.push(row);
|
||
}
|
||
return rows;
|
||
}
|
||
|
||
function strArray(raw: Object | undefined): string[] {
|
||
const out: string[] = [];
|
||
if (raw === undefined || raw === null) {
|
||
return out;
|
||
}
|
||
const arr: Object[] = raw as Object[];
|
||
for (let i = 0; i < arr.length; i++) {
|
||
const s: string = arr[i] as string ?? '';
|
||
if (s.length > 0) {
|
||
out.push(s);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* GET /plugins/{name} —— 后端返回插件清单字段。
|
||
* WebGUI 只是把它 JSON.stringify 进 <pre>,这里逐字段结构化展示。
|
||
* 内置插件不在 /plugins 里,取不到详情时由调用方退回列表已有信息。
|
||
*/
|
||
export async function fetchPluginDetail(name: string): Promise<PluginDetail> {
|
||
const resp = await apiClient.getWithTimeout('/plugins/' + name, 10000);
|
||
const o: Record<string, Object> = JSON.parse(resp.body) as Record<string, Object>;
|
||
const d: PluginDetail = emptyPluginDetail();
|
||
d.name = o['name'] as string ?? name;
|
||
d.version = o['version'] as string ?? '';
|
||
d.description = o['description'] as string ?? '';
|
||
d.author = o['author'] as string ?? '';
|
||
d.license = o['license'] as string ?? '';
|
||
d.homepage = o['homepage'] as string ?? '';
|
||
d.repository = o['repository'] as string ?? '';
|
||
d.entry = o['entry'] as string ?? '';
|
||
d.minVersion = o['min_version'] as string ?? '';
|
||
d.deprecated = o['deprecated'] as boolean ?? false;
|
||
d.tags = strArray(o['tags']);
|
||
d.files = strArray(o['files']);
|
||
return d;
|
||
}
|