From cb331d33402659551cc7b07d6df1e58f05b16da9 Mon Sep 17 00:00:00 2001 From: hzhang Date: Sat, 6 Jun 2026 00:32:52 +0100 Subject: [PATCH] feat(kb): per-agent hf-token via secret-mgr (openclaw mirror) Phase 4c openclaw side. dynamic-kb-* tools now resolve a per-agent hf-token via the existing secret-mgr binary (same path ClawSkills `tc-ctrl get-token` workflow uses), instead of relying solely on the plugin-level apiKey config. plugin/index.ts kbDeps.tokenFor: spawns secret-mgr with AGENT_ID env set, parses stdout, 5s timeout. Returns null on any failure so the caller cleanly falls back to the plugin-level KBClient (which uses Config.APIKey via Bearer). plugin/index.ts kbDeps.makeClient: builds a fresh KBClient bound to the resolved per-agent token. Plugin-level Client stays as the fallback when no agent token is set. Cross-runtime parity: Plexum side uses HostAPI.GetAgentSecret RPC, openclaw side shells out to secret-mgr. Same KBDeps interface; same behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) --- plugin/index.ts | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/plugin/index.ts b/plugin/index.ts index b792ba1..142b443 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -940,9 +940,10 @@ function register(api: PluginAPI): void { // ---- dynamic-kb-* family (DESIGN-DYNAMIC-BLOCK.md §3.3 / §4.4) // Cross-runtime mirror of HarborForge.PlexumPlugin/internal/tools/kb.go + - // /internal/kbblock + /internal/kbclient. v1 auth = plugin-level apiKey - // via Bearer (per-agent hf-token resolution is a TODO matching the - // Plexum side). + // /internal/kbblock + /internal/kbclient. v1 auth: per-agent hf-token + // resolved via secret-mgr (matches the rest of the Hangman-Lab + // openclaw plugins). Falls back to plugin-level apiKey when no + // per-agent token is configured. const kbCfg = resolveConfig(); const kbBackendUrl = typeof kbCfg?.backendUrl === 'string' && kbCfg.backendUrl @@ -951,8 +952,24 @@ function register(api: PluginAPI): void { const kbApiKey = typeof kbCfg?.apiKey === 'string' ? (kbCfg.apiKey as string) : ''; const kbDeps: KBDeps = { client: kbApiKey ? new KBClient(kbBackendUrl, kbApiKey) : null, - tokenFor: null, - makeClient: null, + tokenFor: async (agentId: string): Promise => { + // Per-agent hf-token via secret-mgr (decision #20 mirror). + // Spawn the existing secret-mgr binary with AGENT_ID set; same + // path ClawSkills `tc-ctrl get-token` workflow shells to. + try { + const { spawnSync } = await import('node:child_process'); + const res = spawnSync('secret-mgr', ['get-secret', '--key', 'hf-token'], { + env: { ...process.env, AGENT_ID: agentId }, + encoding: 'utf8', + timeout: 5000, + }); + if (res.status === 0 && res.stdout.trim()) return res.stdout.trim(); + return null; + } catch { + return null; + } + }, + makeClient: (token: string) => new KBClient(kbBackendUrl, token), turnFor: () => 0, };