Fix CI typecheck and default OpenClaw sessions to issue scope

This commit is contained in:
Dotta
2026-03-07 18:33:40 -06:00
parent 0233525e99
commit 5fae7d4de7
16 changed files with 35 additions and 29 deletions

View File

@@ -197,10 +197,16 @@ export function registerAgentCommands(program: Command): void {
const agentRow = await ctx.api.get<Agent>( const agentRow = await ctx.api.get<Agent>(
`/api/agents/${encodeURIComponent(agentRef)}?${query.toString()}`, `/api/agents/${encodeURIComponent(agentRef)}?${query.toString()}`,
); );
if (!agentRow) {
throw new Error(`Agent not found: ${agentRef}`);
}
const now = new Date().toISOString().replaceAll(":", "-"); const now = new Date().toISOString().replaceAll(":", "-");
const keyName = opts.keyName?.trim() ? opts.keyName.trim() : `local-cli-${now}`; const keyName = opts.keyName?.trim() ? opts.keyName.trim() : `local-cli-${now}`;
const key = await ctx.api.post<CreatedAgentKey>(`/api/agents/${agentRow.id}/keys`, { name: keyName }); const key = await ctx.api.post<CreatedAgentKey>(`/api/agents/${agentRow.id}/keys`, { name: keyName });
if (!key) {
throw new Error("Failed to create API key");
}
const installSummaries: SkillsInstallSummary[] = []; const installSummaries: SkillsInstallSummary[] = [];
if (opts.installSkills !== false) { if (opts.installSkills !== false) {

View File

@@ -45,6 +45,7 @@
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.6.0",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
} }

View File

@@ -2,7 +2,8 @@
"extends": "../../../tsconfig.json", "extends": "../../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"rootDir": "src" "rootDir": "src",
"types": ["node"]
}, },
"include": ["src"] "include": ["src"]
} }

View File

@@ -38,7 +38,7 @@ By default the adapter sends a signed `device` payload in `connect` params.
The adapter supports the same session routing model as HTTP OpenClaw mode: The adapter supports the same session routing model as HTTP OpenClaw mode:
- `sessionKeyStrategy=fixed|issue|run` - `sessionKeyStrategy=issue|fixed|run`
- `sessionKey` is used when strategy is `fixed` - `sessionKey` is used when strategy is `fixed`
Resolved session key is sent as `agent.sessionKey`. Resolved session key is sent as `agent.sessionKey`.

View File

@@ -250,8 +250,7 @@ POST /api/companies/$CLA_COMPANY_ID/invites
"headers": { "x-openclaw-token": "<gateway-token>" }, "headers": { "x-openclaw-token": "<gateway-token>" },
"role": "operator", "role": "operator",
"scopes": ["operator.admin"], "scopes": ["operator.admin"],
"sessionKeyStrategy": "fixed", "sessionKeyStrategy": "issue",
"sessionKey": "paperclip",
"waitTimeoutMs": 120000 "waitTimeoutMs": 120000
} }
} }

View File

@@ -37,6 +37,6 @@ Request behavior fields:
- paperclipApiUrl (string, optional): absolute Paperclip base URL advertised in wake text - paperclipApiUrl (string, optional): absolute Paperclip base URL advertised in wake text
Session routing fields: Session routing fields:
- sessionKeyStrategy (string, optional): fixed (default), issue, or run - sessionKeyStrategy (string, optional): issue (default), fixed, or run
- sessionKey (string, optional): fixed session key when strategy=fixed (default paperclip) - sessionKey (string, optional): fixed session key when strategy=fixed (default paperclip)
`; `;

View File

@@ -117,9 +117,9 @@ function parseBoolean(value: unknown, fallback = false): boolean {
} }
function normalizeSessionKeyStrategy(value: unknown): SessionKeyStrategy { function normalizeSessionKeyStrategy(value: unknown): SessionKeyStrategy {
const normalized = asString(value, "fixed").trim().toLowerCase(); const normalized = asString(value, "issue").trim().toLowerCase();
if (normalized === "issue" || normalized === "run") return normalized; if (normalized === "fixed" || normalized === "run") return normalized;
return "fixed"; return "issue";
} }
function resolveSessionKey(input: { function resolveSessionKey(input: {

View File

@@ -5,8 +5,7 @@ export function buildOpenClawGatewayConfig(v: CreateConfigValues): Record<string
if (v.url) ac.url = v.url; if (v.url) ac.url = v.url;
ac.timeoutSec = 120; ac.timeoutSec = 120;
ac.waitTimeoutMs = 120000; ac.waitTimeoutMs = 120000;
ac.sessionKeyStrategy = "fixed"; ac.sessionKeyStrategy = "issue";
ac.sessionKey = "paperclip";
ac.role = "operator"; ac.role = "operator";
ac.scopes = ["operator.admin"]; ac.scopes = ["operator.admin"];
return ac; return ac;

View File

@@ -72,7 +72,7 @@ When used directly (SSE mode or webhook fallback), payload uses OpenResponses sh
"model": "openclaw", "model": "openclaw",
"input": "...", "input": "...",
"metadata": { "metadata": {
"paperclip_session_key": "paperclip" "paperclip_session_key": "paperclip:issue:ISSUE_ID"
} }
} }
``` ```
@@ -91,7 +91,7 @@ You can provide auth either explicitly or via token headers:
Session keys are resolved from: Session keys are resolved from:
- `sessionKeyStrategy`: `fixed` (default), `issue`, `run` - `sessionKeyStrategy`: `issue` (default), `fixed`, `run`
- `sessionKey`: used when strategy is `fixed` (default value `paperclip`) - `sessionKey`: used when strategy is `fixed` (default value `paperclip`)
Where session keys are applied: Where session keys are applied:

View File

@@ -28,7 +28,7 @@ Core fields:
- hookIncludeSessionKey (boolean, optional): when true, include derived \`sessionKey\` in \`/hooks/agent\` webhook payloads (default false) - hookIncludeSessionKey (boolean, optional): when true, include derived \`sessionKey\` in \`/hooks/agent\` webhook payloads (default false)
Session routing fields: Session routing fields:
- sessionKeyStrategy (string, optional): \`fixed\` (default), \`issue\`, or \`run\` - sessionKeyStrategy (string, optional): \`issue\` (default), \`fixed\`, or \`run\`
- sessionKey (string, optional): fixed session key value when strategy is \`fixed\` (default \`paperclip\`) - sessionKey (string, optional): fixed session key value when strategy is \`fixed\` (default \`paperclip\`)
Operational fields: Operational fields:

View File

@@ -57,9 +57,9 @@ export function resolvePaperclipApiUrlOverride(value: unknown): string | null {
} }
export function normalizeSessionKeyStrategy(value: unknown): SessionKeyStrategy { export function normalizeSessionKeyStrategy(value: unknown): SessionKeyStrategy {
const normalized = asString(value, "fixed").trim().toLowerCase(); const normalized = asString(value, "issue").trim().toLowerCase();
if (normalized === "issue" || normalized === "run") return normalized; if (normalized === "fixed" || normalized === "run") return normalized;
return "fixed"; return "issue";
} }
export function resolveSessionKey(input: { export function resolveSessionKey(input: {

View File

@@ -6,7 +6,6 @@ export function buildOpenClawConfig(v: CreateConfigValues): Record<string, unkno
ac.method = "POST"; ac.method = "POST";
ac.timeoutSec = 0; ac.timeoutSec = 0;
ac.streamTransport = "sse"; ac.streamTransport = "sse";
ac.sessionKeyStrategy = "fixed"; ac.sessionKeyStrategy = "issue";
ac.sessionKey = "paperclip";
return ac; return ac;
} }

3
pnpm-lock.yaml generated
View File

@@ -132,6 +132,9 @@ importers:
specifier: ^1.1.1 specifier: ^1.1.1
version: 1.1.1 version: 1.1.1
devDependencies: devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.12.0
typescript: typescript:
specifier: ^5.7.3 specifier: ^5.7.3
version: 5.9.3 version: 5.9.3

View File

@@ -181,10 +181,10 @@ describe("openclaw adapter execute", () => {
const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}")) as Record<string, unknown>; const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}")) as Record<string, unknown>;
expect(body.foo).toBe("bar"); expect(body.foo).toBe("bar");
expect(body.stream).toBe(true); expect(body.stream).toBe(true);
expect(body.sessionKey).toBe("paperclip"); expect(body.sessionKey).toBe("paperclip:issue:issue-123");
expect((body.paperclip as Record<string, unknown>).streamTransport).toBe("sse"); expect((body.paperclip as Record<string, unknown>).streamTransport).toBe("sse");
expect((body.paperclip as Record<string, unknown>).runId).toBe("run-123"); expect((body.paperclip as Record<string, unknown>).runId).toBe("run-123");
expect((body.paperclip as Record<string, unknown>).sessionKey).toBe("paperclip"); expect((body.paperclip as Record<string, unknown>).sessionKey).toBe("paperclip:issue:issue-123");
expect( expect(
((body.paperclip as Record<string, unknown>).env as Record<string, unknown>).PAPERCLIP_RUN_ID, ((body.paperclip as Record<string, unknown>).env as Record<string, unknown>).PAPERCLIP_RUN_ID,
).toBe("run-123"); ).toBe("run-123");
@@ -414,7 +414,7 @@ describe("openclaw adapter execute", () => {
expect(body.sessionKey).toBeUndefined(); expect(body.sessionKey).toBeUndefined();
const headers = (fetchMock.mock.calls[0]?.[1]?.headers ?? {}) as Record<string, string>; const headers = (fetchMock.mock.calls[0]?.[1]?.headers ?? {}) as Record<string, string>;
expect(headers["x-openclaw-session-key"]).toBe("paperclip"); expect(headers["x-openclaw-session-key"]).toBe("paperclip:issue:issue-123");
}); });
it("does not treat response.output_text.done as a terminal OpenResponses event", async () => { it("does not treat response.output_text.done as a terminal OpenResponses event", async () => {
@@ -584,7 +584,7 @@ describe("openclaw adapter execute", () => {
const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}")) as Record<string, unknown>; const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}")) as Record<string, unknown>;
expect(body.foo).toBe("bar"); expect(body.foo).toBe("bar");
expect(body.stream).toBe(false); expect(body.stream).toBe(false);
expect(body.sessionKey).toBe("paperclip"); expect(body.sessionKey).toBe("paperclip:issue:issue-123");
expect(String(body.text ?? "")).toContain("PAPERCLIP_RUN_ID=run-123"); expect(String(body.text ?? "")).toContain("PAPERCLIP_RUN_ID=run-123");
expect((body.paperclip as Record<string, unknown>).streamTransport).toBe("webhook"); expect((body.paperclip as Record<string, unknown>).streamTransport).toBe("webhook");
}); });
@@ -668,7 +668,7 @@ describe("openclaw adapter execute", () => {
expect(String(secondBody.input ?? "")).toContain("PAPERCLIP_RUN_ID=run-123"); expect(String(secondBody.input ?? "")).toContain("PAPERCLIP_RUN_ID=run-123");
const secondHeaders = (fetchMock.mock.calls[1]?.[1]?.headers ?? {}) as Record<string, string>; const secondHeaders = (fetchMock.mock.calls[1]?.[1]?.headers ?? {}) as Record<string, string>;
expect(secondHeaders["x-openclaw-session-key"]).toBe("paperclip"); expect(secondHeaders["x-openclaw-session-key"]).toBe("paperclip:issue:issue-123");
expect(result.resultJson).toEqual( expect(result.resultJson).toEqual(
expect.objectContaining({ expect.objectContaining({
usedLegacyResponsesFallback: true, usedLegacyResponsesFallback: true,
@@ -766,7 +766,7 @@ describe("openclaw adapter execute", () => {
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);
const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}")) as Record<string, unknown>; const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}")) as Record<string, unknown>;
expect(body.sessionKey).toBe("paperclip"); expect(body.sessionKey).toBe("paperclip:issue:issue-123");
}); });
it("retries webhook payloads with wake compatibility format on text-required errors", async () => { it("retries webhook payloads with wake compatibility format on text-required errors", async () => {

View File

@@ -424,7 +424,7 @@ describe("openclaw gateway adapter execute", () => {
const payload = gateway.getAgentPayload(); const payload = gateway.getAgentPayload();
expect(payload).toBeTruthy(); expect(payload).toBeTruthy();
expect(payload?.idempotencyKey).toBe("run-123"); expect(payload?.idempotencyKey).toBe("run-123");
expect(payload?.sessionKey).toBe("paperclip"); expect(payload?.sessionKey).toBe("paperclip:issue:issue-123");
expect(String(payload?.message ?? "")).toContain("wake now"); expect(String(payload?.message ?? "")).toContain("wake now");
expect(String(payload?.message ?? "")).toContain("PAPERCLIP_RUN_ID=run-123"); expect(String(payload?.message ?? "")).toContain("PAPERCLIP_RUN_ID=run-123");
expect(String(payload?.message ?? "")).toContain("PAPERCLIP_TASK_ID=task-123"); expect(String(payload?.message ?? "")).toContain("PAPERCLIP_TASK_ID=task-123");

View File

@@ -1484,8 +1484,7 @@ export function buildInviteOnboardingTextDocument(
paperclipApiUrl: "http://host.docker.internal:3100", paperclipApiUrl: "http://host.docker.internal:3100",
headers: { "x-openclaw-token": token }, headers: { "x-openclaw-token": token },
waitTimeoutMs: 120000, waitTimeoutMs: 120000,
sessionKeyStrategy: "fixed", sessionKeyStrategy: "issue",
sessionKey: "paperclip",
role: "operator", role: "operator",
scopes: ["operator.admin"] scopes: ["operator.admin"]
} }
@@ -1518,8 +1517,7 @@ export function buildInviteOnboardingTextDocument(
"paperclipApiUrl": "https://paperclip-hostname-your-agent-can-reach:3100", "paperclipApiUrl": "https://paperclip-hostname-your-agent-can-reach:3100",
"headers": { "x-openclaw-token": "replace-me" }, "headers": { "x-openclaw-token": "replace-me" },
"waitTimeoutMs": 120000, "waitTimeoutMs": 120000,
"sessionKeyStrategy": "fixed", "sessionKeyStrategy": "issue",
"sessionKey": "paperclip",
"role": "operator", "role": "operator",
"scopes": ["operator.admin"] "scopes": ["operator.admin"]
} }