hiwork-memory
v0.2.2
Published
HiWork 记忆插件:接 TencentDB Agent Memory(memory-core /v3),给 Agent 提供只读记忆检索工具,并在桌面端提供记忆设置页。
Readme
hiwork-memory
HiWork 的记忆插件:把腾讯 TencentDB Agent Memory 接进桌面端——让 Agent 能查得到「人和团队以前说过什么」。
访问层走 Panel(memory-hub,8125)的 /api/v1/chat-memory/*:只有它做真实鉴权(x-tdai-user-key)
与 private / team 可见性校验。memory-core 的 /v3 数据面只检查 Authorization 头是否存在
(实测假 Key 也能读到全员记忆),所以插件不再直连它。
员工零操作(2026-09-18 起)
服务地址、实例 ID、团队 ID、凭据全部由网关下发,员工侧零输入——设置页没有任何可填字段:
hiwork-admin GET /api/me/memory-config → hiwork-core 的 hiworkMemory host 面 → 本插件- 插件侧没有手工配置路径(裁定:目标用户是非技术员工,不提供逃生舱);
开发 / CI 需要绕过网关时走环境变量
HIWORK_MEMORY_*(非 UI 通道)。 cordis.patch.yml里没有任何可配置项(旧的baseUrl/serviceId/apiKey/teamId已删)。- 凭据只在 Host 内存里活:不落盘到别处、不进日志、Web 半边只拿得到掩码。
状态:M0 Host + Web 半边已交付(0.1.0,零操作口径)
- Host 半边:配置解析(下发 → 环境变量 → 缓存)+ Panel 出网客户端(列资产 / 读层 / 检索)+ 4 个只读工具 + 系统提示词段落 +
/hiwork-memoryloopback RPC(snapshot/config-refresh)。- Web 半边:设置页三形态(正常 / 降级 / 管理员未开启)+ 一个总开关 + 只读折叠诊断。
- 尚未发布 npm,也未进桌面端
BUNDLED_PLUGINS(未发 npm 的插件不进内置清单)。 要试用只能走pnpm pack+ profile 的file:安装,或 spec §D9 的archive:重打包流程。- 设计:
hiwork-desktop/docs/superpowers/specs/2026-09-18-hiwork-memory-design.md; 实施计划:hiwork-desktop/docs/superpowers/plans/2026-09-18-hiwork-memory-m0.md; 网关侧:hiwork-admin/docs/superpowers/specs/2026-09-18-memory-provisioning-design.md。
两个半边
| 半边 | 做什么 |
| --- | --- |
| Host(src/*.ts) | 配置解析与出网、4 个只读工具、系统提示词片段、loopback RPC。是唯一事实来源 |
| Web(src/client/**) | 设置页三形态 + 总开关 + 只读诊断(不发 HTTP、拿不到凭据明文) |
- 包名 / bundle id:
hiwork-memory - client 插件名:
hiwork-memory-client - RPC 频道:
/hiwork-memory(端点只有snapshot/config-refresh;config-save与config-test已删) - 存储域(配置缓存):
hiwork_memory(域名只允许[a-z0-9_],写连字符会启动即抛StorageError) - Agent 工具(M0 全部只读):
memory_search、memory_conversation_search、memory_read_profile、memory_read_file - 后端(内网):
http://192.168.51.96:8125(Panel;路由POST /api/v1/chat-memory/{mine,team-assets,layer,search})
目录
src/
index.ts Host 入口:开存储域 → 配置解析 → 服务 → 工具 → 提示词 → RPC(apply 里全部接线)
types.ts 领域类型(含资产)、默认值、存储域定义(hiwork_memory)、错误码与错误类
config-source.ts 配置解析:网关下发(hiworkMemory)→ 环境变量 → 缓存;失败给原因不给异常
memory-client.ts Panel /api/v1/chat-memory/* 出网客户端(鉴权头、信封、超时、score 丢弃、错误归一)
service.ts MemoryService:配置取用与刷新 / 资产列举与 TTL 缓存 / 按资产扇出 / 快照
identity.ts 登录身份读取(**只用于展示与团队提示**,不是隔离键)
tools.ts 4 个只读 Agent 工具(defineTool,先列资产再在资产内检索)
prompt.ts 系统提示词段落文案(hiwork-memory:guide,order 64)
protocol.ts /hiwork-memory 的线上契约(视图类型 + .strict() 请求 schema)
rpc.ts /hiwork-memory loopback RPC(snapshot / config-refresh)
tool-names.ts 工具名单一事实源(零依赖,Web 半边认领 keyed 座位时共用)
client/
index.ts Web 入口:注册设置页座位
contracts.ts Web 侧视图类型(与 protocol.ts 的 MemorySnapshotView 逐字一致)
runtime.ts RPC 调用与状态分发(唯一动作 = config-refresh)
locales.ts zh / en 词典(三形态文案在此,不得硬编码)
SettingsSection.tsx 设置页:三形态 + 总开关 + 只读折叠诊断
scripts/
host-build.mjs Host 产物自包含打包(esbuild,内联全部依赖)
client-build.mjs Client 产物闭包工厂打包(esbuild,模拟 rc.1 模块表)
tests/
smoke.spec.ts 脚手架冒烟:入口、注入面、patch 安全面、产物纯度
no-manual-config.spec.ts ★ 零操作静态门禁:设置页无可填字段、端点白名单只有两个、打包纯度、契约对撞
service.spec.ts 配置解析四通道 + 团队门槛 + 资产缓存 TTL + 扇出上限 + 快照
memory-client.spec.ts Panel 请求构造(头 / body 字段名)、信封解包、错误码映射、score 丢弃
tools.spec.ts 4 个工具 happy path / 越界 agent / 取消 / 截断 / 无 score / 单资产失败进 warnings
rpc.spec.ts 端点白名单、.strict()、凭据不回显、snapshot 永不抛错
identity-scope.spec.ts 身份读取三态 + **删除项守门**(双命名空间 / `/v3` 直连不许回归)
plugin-entry.spec.ts Host 接线(挂工具、注册频道与提示词、幂等清理)
client-runtime.spec.ts Web 运行时与设置页三形态渲染(jsdom)
cordis.patch.yml bundle 叠加(**无任何可配置项**,见文件头)命令
| 命令 | 作用 |
| --- | --- |
| pnpm typecheck | 严格类型检查(tsc --noEmit) |
| pnpm build | 构建 lib/index.js(Host)与 lib/client.js(Web)+ 类型声明到 lib/types/ |
| pnpm test | Vitest(DOM 用例首行加 // @vitest-environment jsdom) |
| pnpm verify | typecheck → build → test(提交前跑这个) |
smoke:live(打真实后端的联调冒烟)尚未加入:脚本还没写,因此 package.json 里也不声明它,
避免留一个指向不存在文件的脚本。
Host 半边(M0 已交付)
| 面 | 落点 | 口径 |
| --- | --- | --- |
| 配置解析 | config-source.ts | 优先级 = 网关下发(ctx.get('hiworkMemory').config())> 环境变量 HIWORK_MEMORY_* > 存储域缓存;失败返回 null + reason(不抛错),reason ∈ not-configured / unauthenticated / unreachable / error / team-required |
| 401 自愈 | service.ts | 工具遇 unauthorized → force 重取一次配置 → 重试原请求一次;仍失败才降级 |
| 出网客户端 | memory-client.ts | POST {panelUrl}/api/v1/chat-memory/<route>;头只有 content-type + x-tdai-service-id + x-tdai-user-key(不发 Authorization);body 字段名就是契约:team_id / block_id / layer / query / limit / offset |
| 资产可见性 | Panel + service.ts | 无本地隔离键:mine = 我的资产、team-assets = 团队共享(该端点不回 scope,缺省当 team);工具默认两个来源都查,逐条标资产标题 + private / team |
| 4 个只读工具 | tools.ts | 每个工具先列资产再在资产内检索:memory_search(L1)/ memory_conversation_search(L0)/ memory_read_profile(L3 正文 + L2 清单)/ memory_read_file(L2 按路径,个人资产优先);恒挂在根 Agent 上 |
| 系统提示词 | prompt.ts | 段落名 hiwork-memory:guide、order 64;ctx.get('systemPrompt') 缺失时不注册 |
| loopback RPC | rpc.ts + protocol.ts | 频道 /hiwork-memory,端点 snapshot / config-refresh,payload 一律 .strict() |
五条不变量(改动前先读 config-source.ts / memory-client.ts / service.ts / tools.ts 的文件头):
team_id是所有取数的前提:实测chat-memory/{mine,my-agents,team-assets}空 body 一律400 MISSING_TEAM_ID,而记忆里没有「与团队无关的个人域」(scope: private的资产也挂在某个 team 下)。 所以teamIds: []= 记忆不可用(降级),不是「只用个人域」。- 不向模型返回
score:实测它是检索退化值(1.3749981093775994e-06),当相关度用会误导模型——映射层直接丢。 - 截断显式:单条超过
maxChars(默认 1200)时截断并标truncated: true。 - 失败收敛:所有 Panel 失败 →
{ ok:false, code, message };code ∈ misconfigured / unauthorized / forbidden / not-found / not-chat-memory / bad-request / unreachable / timeout / cancelled / upstream / internal;message 不含凭据、不含 URL/HTTP 状态码原文(消毒口径同hiwork-core/src/office-credentials.ts)。 - 凭据单向:Web 半边只拿得到掩码(
credentialMask,形如sk-mem-****1234);snapshot不出网也永不抛错,问题进reason/message/lastError。
单个资产失败不影响整单:条目照回、失败进 warnings(带 assetId / title / code),被选中的资产全部失败才返回失败。
资产列表按 60s TTL 缓存(ASSET_CACHE_TTL_MS):一次工具调用要先打两次列表接口,不缓存等于「每问一句记忆都多打两枪」。
失败不缓存(下次调用会重试,不会把瞬时故障锁 60 秒)。snapshot 里的 assets 是最近一次成功列举的结果。
配置与凭据
没有可配置的地方——这是本插件的产品口径,不是遗漏:
# cordis.patch.yml:bundle 叠加,无任何可配置项
- insert:
- id: hiwork-memory
name: 'hiwork-memory'- 网关下发是唯一来源:
hiwork-core的hiworkMemory调hiwork-admin的GET /api/me/memory-config,返回panel_url/service_id/team_ids/api_key(+provisioned_at), 由config-source.ts转成内部 camelCase 视图。snake_case → camelCase 的转换只在 core 那一处做。 - 开发 / CI 走环境变量:
HIWORK_MEMORY_PANEL_URL/HIWORK_MEMORY_SERVICE_ID/HIWORK_MEMORY_TEAM_IDS/HIWORK_MEMORY_USER_KEY。 - 没拿到
teamIds时工具返回team-required,文案引导「重新打开记忆设置页」——不会让用户去填 ID (网关侧已保证正常至少给一个团队,含自动建的部署级兜底团队hiwork-全员)。 - 每用户一把
sk-mem-key,鉴权与可见性由 Panel 强制;任何文件、日志、报告里都不得出现 Key 明文 (tests/smoke.spec.ts会拦cordis.patch.yml里的sk-…形状)。
打包纯度(别凭直觉改)
- Host 产物必须自包含:桌面端补种插件时会清空 profile 的
node_modules/@deepseek-ai/*(hiwork-desktop/src/plugin-seed.ts的stripOfficialProfileDependencies),所以lib/index.js里除 Node 内建外不能有任何模块说明符(实测产物里只有一个from "node:module", 那是 esbuild 为 CJS 互操作注入的createRequire,不是包依赖);src/index.ts对官方包只做 type-only import。 - Client 产物只能 require rc.1 模块表种子(react 系 + 少量
dsh-client-*):src/client/**对官方包只做 type-only import,运行时只 import react / react/jsx-runtime。 产物是window.__ModuleLoader__.load({ id, factory })闭包工厂。 - 两条门禁都在
tests/smoke.spec.ts与tests/no-manual-config.spec.ts里(产物存在时生效,先pnpm build再pnpm test)。
本地试用(不打包桌面端)
pnpm build && pnpm pack # 产出 hiwork-memory-0.1.0.tgz在 DSH profile(如 ~/.dsh/profiles/web)的 package.json 里加依赖
"hiwork-memory": "file:<绝对路径>/hiwork-memory-0.1.0.tgz",并在 dsh.profile.bundles
追加 "hiwork-memory",然后 pnpm install --force 并重启 dsh web
(Host 半边变化必须重启,只改 client 时刷新页面即可)。
零操作口径下,装好后不需要在设置页填任何东西:登录态由
hiwork-core带着去网关取配置。 若看到「记忆暂时不可用」,先确认网关侧memory.panel_url/admin_key已配(未配时端点回 503 「管理员还没开启团队记忆」)。
仓库约定
- 分支
dev(不是main),远端名gitlab(不是origin),与同族插件一致。 - 依赖与 peer 版本对齐兄弟插件
hiwork-knowledge,DSH 官方包统一0.1.5-rc.1/ cordis4.0.2。
里程碑
| 阶段 | 内容 | 状态 |
| --- | --- | --- |
| M0 脚手架 | 目录结构、构建/测试脚本、占位入口 | ✅ |
| M0 Host(/v3 直连) | 凭据 + /v3 客户端 + 4 个只读工具 + /hiwork-memory RPC | ✅(已被 Panel 访问层取代) |
| M0 Host(Panel 访问层) | 改走 /api/v1/chat-memory/*:列资产 → 资产内检索、资产缓存、删除双命名空间 | ✅ |
| M0 Web(手工配置) | 设置页(服务地址 / 实例 ID / 团队 ID / 凭据 + 连接自检 + 资产清单) | ✅(已被零操作口径取代) |
| M0 零操作 | 配置改 100% 网关下发;设置页三形态(无输入框);删 config-save / config-test;teamIds 数组 | ✅ 本次 |
| M0 验收 | 真实后端联调冒烟 + 跨用户越权负向断言 | 待开工(需真机点一遍) |
| M1 | 每轮注入(agent/pre-step,默认开 + 5 条/2000 字) | — |
| M2 | 对话沉淀(session/event → L0,写「自己的 agent」) | — |
| M3 | 中央页记忆浏览(feature center) | — |
