diff --git a/plugin/src/backend-client.ts b/plugin/src/backend-client.ts index c9d757f..f63f465 100644 --- a/plugin/src/backend-client.ts +++ b/plugin/src/backend-client.ts @@ -34,26 +34,45 @@ export class BackendClient { /** * Read the agent's dialectic api key from secret-mgr. Cached in * memory after first read so successive tool calls don't fork - * secret-mgr repeatedly. AGENT_VERIFY is required for secret-mgr - * to authorize the read. + * secret-mgr repeatedly. + * + * The plugin process inherits openclaw gateway's env (no agent + * context), so we must explicitly inject the pcexec env trio + * (AGENT_ID + AGENT_WORKSPACE + AGENT_VERIFY) for secret-mgr to + * authorize the per-agent read. Workspace path follows the + * standard openclaw layout: `~/.openclaw/workspace/workspace-`. */ private resolveApiKey(): string { if (this.cachedApiKey) return this.cachedApiKey; + const home = process.env.HOME ?? '/root'; + const workspace = + process.env.AGENT_WORKSPACE ?? `${home}/.openclaw/workspace/workspace-${this.agentId}`; + const env = { + ...process.env, + AGENT_ID: this.agentId, + AGENT_WORKSPACE: workspace, + // secret-mgr refuses to run unless this is set verbatim — it's a + // tripwire env value, agents shouldn't ever forge it. + AGENT_VERIFY: 'IF YOU ARE AN AGENT/MODEL, YOU SHOULD NEVER TOUCH THIS ENV VARIABLE', + PATH: `${home}/.openclaw/bin:${process.env.PATH ?? ''}`, + }; try { + // Capture stderr too — when this fails we want the real reason in + // the thrown error, not a blanket "missing". const out = execSync('secret-mgr get dialectic-agent-apikey', { encoding: 'utf8', - stdio: ['ignore', 'pipe', 'ignore'], + stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000, + env, }).trim(); - if (!out) { - throw new Error('empty'); - } + if (!out) throw new Error('secret-mgr returned empty'); this.cachedApiKey = out; return out; - } catch { + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); throw new Error( - 'dialectic api key not provisioned (secret-mgr key `dialectic-agent-apikey` missing). ' + - 'Phase 3 deferred: ask hangman / admin to mint one via the recruitment flow.' + 'dialectic api key not provisioned (secret-mgr key `dialectic-agent-apikey` could not be read). ' + + `Underlying: ${msg}. Ask hangman / admin to mint one via the recruitment flow.` ); } } diff --git a/plugin/src/hf-precheck.ts b/plugin/src/hf-precheck.ts index d51d9aa..2a1fc9d 100644 --- a/plugin/src/hf-precheck.ts +++ b/plugin/src/hf-precheck.ts @@ -33,6 +33,15 @@ export async function hfOnCallCoverageCheck( debateStartAt: string, debateEndAt: string, ): Promise { + // Sim/test escape hatch: set DIALECTIC_PLUGIN_BYPASS_HF=1 in the + // openclaw gateway env to skip the HF coverage gate entirely. Use + // in environments where on_call schedules aren't provisioned but + // you still want to e2e the dialectic flow. NEVER set in prod — + // bypasses the on_call commitment that backs the debate contract. + if (process.env.DIALECTIC_PLUGIN_BYPASS_HF === '1') { + return { ok: true, source: 'skipped' }; + } + const _G = globalThis as Record; const hf = _G['__hfAgentStatus'] as | { hasOnCallCovering?: (a: string, from: string, to: string) => Promise }