fix(openclaw-gateway): use per-agent claimedApiKeyPath in wake text (#4668)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The `openclaw-gateway` adapter wakes a remote agent over a WebSocket gateway. It sends a wake prompt. That prompt tells the agent which environment variables to set and which file holds its Paperclip API key. > - Each agent stores its claimed key in its own JSON file. The adapter already exposes a `claimedApiKeyPath` config field for this. The field is documented in `src/index.ts`. It also has an input in the agent settings UI. > - `buildWakeText` ignored that field. It hardcoded the shared default path into the wake prompt text. > - Every agent therefore read the same key file at wake time. Agents authenticated as the wrong identity. The first API call failed. > - This pull request passes `ctx.config.claimedApiKeyPath` into `buildWakeText`. It uses the existing `resolveClaimedApiKeyPath` helper. That helper falls back to the documented default. > - The benefit is that each agent reads its own claimed-key file. Each agent authenticates as itself. ## Linked Issues or Issue Description Fixes #10071 Fixes #4976 Fixes #3098 Fixes #8076 These four open issues report the same defect. Earlier duplicates are already closed: Refs #2561, Refs #2592, Refs #930. Related pull requests that address the same root problem (duplicate search): - #3396 — same core change, no tests - #3370 — heavier approach, injects `PAPERCLIP_CLAIMED_API_KEY_PATH` into the wake env and adds server onboarding defaults - #5970 — renames the config field to `paperclipApiKeyPath` - #8072 — same core change, bundled with an unrelated protocol-version change - #784 — adds shell quoting and preflight instructions - #3296 — bundled with an unrelated Claude hello-probe fix ## What Changed - `packages/adapters/openclaw-gateway/src/server/execute.ts` - `buildWakeText` now accepts `claimedApiKeyPath` as a parameter. It no longer hardcodes the path. - The `execute` call site passes `resolveClaimedApiKeyPath(ctx.config.claimedApiKeyPath)`. That helper returns the documented default `~/.openclaw/workspace/paperclip-claimed-api-key.json` when the agent sets no override. - `resolveClaimedApiKeyPath` is now exported so tests can call it. - `packages/adapters/openclaw-gateway/src/server/execute.test.ts` — adds `resolveClaimedApiKeyPath` cases: a configured value, an empty string, a whitespace-only string, `undefined`, `null`, and non-string input. - `packages/adapters/openclaw-gateway/vitest.config.ts` (new) — package-level vitest config. It matches the config used by sibling adapters such as `opencode-local`. - `vitest.config.ts` (root) — adds the adapter to the workspace project list. - `scripts/run-vitest-stable.mjs` — adds `@paperclipai/adapter-openclaw-gateway` to `nonServerProjects`. **Maintainer-added during rebase.** The CI test lanes do not run a bare `vitest`. They call `run-vitest-stable.mjs`, which invokes vitest with an explicit `--project` allowlist. Without this entry the CI lanes skip this package, and the root project-list entry alone has no effect on CI. ## Verification Run the package suite directly: ``` pnpm install --frozen-lockfile pnpm exec vitest run --project @paperclipai/adapter-openclaw-gateway ``` The suite covers `resolveSessionKey`, `buildAgentParams`, and the new `resolveClaimedApiKeyPath` cases. The first two already existed in this file but never executed in CI before this change. Typecheck the package: ``` pnpm --filter @paperclipai/adapter-openclaw-gateway typecheck ``` Behavioural check, which no automated test covers: 1. Set `claimedApiKeyPath` to a per-agent value such as `~/.openclaw/workspace/paperclip-keys/<agent>.json` in the agent's gateway adapter settings. 2. Trigger a wake for that agent. 3. Confirm the rendered wake text names that file. It must not name the shared default. Maintainer note: this branch was rebased onto current `master` by a maintainer. The original branch was two months stale. Only two conflicts occurred, both additive: the import line and the tail of `execute.test.ts`, and the project list in the root `vitest.config.ts`. The `execute.ts` change applied without conflict. CI and Greptile re-run against the rebased head. ## Risks - Low for existing deployments. `resolveClaimedApiKeyPath` preserves the default path exactly. Any agent that never set `claimedApiKeyPath` receives the same wake text as before. - The behaviour changes only for agents that already set a per-agent path. Those agents previously received the wrong instruction. They now receive the correct one. - No database, schema, or API surface changes. - CI now runs this package's test file for the first time. That file includes the pre-existing `resolveSessionKey` and `buildAgentParams` tests, which were never executed before. - Five other adapters (`cursor-cloud`, `cursor-local`, `gemini-local`, `grok-local`, `pi-local`) sit in the root project list but remain absent from the CI allowlist. This pull request does not change them. That gap is tracked separately. ## Model Used - Contributor's change: Anthropic Claude, model ID `claude-opus-4-7`, approximately 200K context, extended thinking. Used for triage, patch authoring, and the original description. - Rebase, the `run-vitest-stable.mjs` entry, and this description: Anthropic Claude, model ID `claude-opus-5`, tool use enabled. Run by a Paperclip maintainer. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details — the branch name carries an internal ticket id. A fork branch cannot be renamed without opening a new pull request, so this is left as-is. The internal reference has been removed from the description. - [ ] I have run tests locally and they pass — the contributor verified the pre-rebase branch. The rebased head is verified by CI on this pull request. - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes — `claimedApiKeyPath` is already documented in `src/index.ts` and exposed in the agent settings UI, so no documentation change is needed - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green — pending the post-rebase run - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups — pending re-review of the rebased head - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Pieter (CTO) <pieter@openclaw.local> Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
This commit is contained in:
parent
8142e54150
commit
c54936e2e9
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { buildAgentParams, resolveSessionKey } from "./execute.js";
|
||||
import { buildAgentParams, resolveClaimedApiKeyPath, resolveSessionKey } from "./execute.js";
|
||||
|
||||
describe("resolveSessionKey", () => {
|
||||
it("prefixes run-scoped session keys with the configured agent", () => {
|
||||
|
|
@ -98,3 +98,28 @@ describe("buildAgentParams", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveClaimedApiKeyPath", () => {
|
||||
const DEFAULT_PATH = "~/.openclaw/workspace/paperclip-claimed-api-key.json";
|
||||
|
||||
it("returns the configured per-agent path when set", () => {
|
||||
expect(
|
||||
resolveClaimedApiKeyPath("~/.openclaw/workspace/paperclip-keys/happy.json"),
|
||||
).toBe("~/.openclaw/workspace/paperclip-keys/happy.json");
|
||||
});
|
||||
|
||||
it("falls back to the shared default when value is empty", () => {
|
||||
expect(resolveClaimedApiKeyPath("")).toBe(DEFAULT_PATH);
|
||||
expect(resolveClaimedApiKeyPath(" ")).toBe(DEFAULT_PATH);
|
||||
});
|
||||
|
||||
it("falls back to the shared default when value is missing", () => {
|
||||
expect(resolveClaimedApiKeyPath(undefined)).toBe(DEFAULT_PATH);
|
||||
expect(resolveClaimedApiKeyPath(null)).toBe(DEFAULT_PATH);
|
||||
});
|
||||
|
||||
it("falls back to the shared default when value is not a string", () => {
|
||||
expect(resolveClaimedApiKeyPath(42)).toBe(DEFAULT_PATH);
|
||||
expect(resolveClaimedApiKeyPath({})).toBe(DEFAULT_PATH);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ function resolvePaperclipApiUrlOverride(value: unknown): string | null {
|
|||
|
||||
const DEFAULT_CLAIMED_API_KEY_PATH = "~/.openclaw/workspace/paperclip-claimed-api-key.json";
|
||||
|
||||
function resolveClaimedApiKeyPath(value: unknown): string {
|
||||
export function resolveClaimedApiKeyPath(value: unknown): string {
|
||||
return nonEmpty(value) ?? DEFAULT_CLAIMED_API_KEY_PATH;
|
||||
}
|
||||
|
||||
|
|
@ -369,8 +369,8 @@ function buildWakeText(
|
|||
payload: WakePayload,
|
||||
paperclipEnv: Record<string, string>,
|
||||
structuredWakePrompt: string,
|
||||
claimedApiKeyPath: string,
|
||||
): string {
|
||||
const claimedApiKeyPath = "~/.openclaw/workspace/paperclip-claimed-api-key.json";
|
||||
const orderedKeys = [
|
||||
"PAPERCLIP_RUN_ID",
|
||||
"PAPERCLIP_AGENT_ID",
|
||||
|
|
@ -1096,6 +1096,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||
structuredWakeJson
|
||||
? joinWakePayloadSections(structuredWakePrompt, structuredWakeJson)
|
||||
: structuredWakePrompt,
|
||||
resolveClaimedApiKeyPath(ctx.config.claimedApiKeyPath),
|
||||
);
|
||||
|
||||
const sessionKeyStrategy = normalizeSessionKeyStrategy(ctx.config.sessionKeyStrategy);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: "node",
|
||||
},
|
||||
});
|
||||
|
|
@ -21,6 +21,7 @@ const nonServerProjects = [
|
|||
"@paperclipai/adapter-utils",
|
||||
"@paperclipai/adapter-claude-local",
|
||||
"@paperclipai/adapter-codex-local",
|
||||
"@paperclipai/adapter-openclaw-gateway",
|
||||
"@paperclipai/adapter-opencode-local",
|
||||
"@paperclipai/plugin-sdk",
|
||||
"@paperclipai/create-paperclip-plugin",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export default defineConfig({
|
|||
"packages/adapters/cursor-local",
|
||||
"packages/adapters/gemini-local",
|
||||
"packages/adapters/grok-local",
|
||||
"packages/adapters/openclaw-gateway",
|
||||
"packages/adapters/opencode-local",
|
||||
"packages/adapters/pi-local",
|
||||
"packages/plugins/sdk",
|
||||
|
|
|
|||
Loading…
Reference in New Issue