Skip to content

Commit 48b6d1c

Browse files
authored
test: add config tests under test/ for CI (#9)
* add INSTALL.md, install.sh, Agent Skill * add powermem skills * docs: update README.md * add release workflow * OpenClaw Memory (PowerMem) * fixed: release ci * test: add config tests under test/ for CI
1 parent 5565b2c commit 48b6d1c

5 files changed

Lines changed: 163 additions & 16 deletions

File tree

.github/workflows/release.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ on:
44
push:
55
tags:
66
- 'v*'
7+
pull_request:
78
# Manual trigger for dev: lint, test, and package only; no GitHub Release or npm publish
89
workflow_dispatch:
10+
inputs:
11+
ref:
12+
description: 'Branch, tag, or PR ref (e.g. refs/pull/123/merge). Leave empty to run on default branch.'
13+
required: false
14+
type: string
915

1016
permissions:
1117
contents: write
@@ -17,6 +23,7 @@ jobs:
1723
- name: Checkout
1824
uses: actions/checkout@v4
1925
with:
26+
ref: ${{ github.event.inputs.ref || github.ref }}
2027
fetch-depth: 0
2128

2229
- name: Setup Node.js

index.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
*/
99

1010
import { Type } from "@sinclair/typebox";
11-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core";
11+
import type {
12+
OpenClawPluginApi,
13+
OpenClawPluginCliContext,
14+
} from "openclaw/plugin-sdk/memory-core";
15+
import type { OpenClawPluginServiceContext } from "openclaw/plugin-sdk";
1216

1317
import {
1418
powerMemConfigSchema,
@@ -64,7 +68,7 @@ const memoryPlugin = {
6468
Type.Number({ description: "Min score 0–1 to include (default: plugin recallScoreThreshold)" }),
6569
),
6670
}),
67-
async execute(_toolCallId, params) {
71+
async execute(_toolCallId: string, params: Record<string, unknown>) {
6872
const limit =
6973
typeof (params as { limit?: number }).limit === "number"
7074
? Math.max(1, Math.min(100, Math.floor((params as { limit: number }).limit)))
@@ -73,6 +77,7 @@ const memoryPlugin = {
7377
typeof (params as { scoreThreshold?: number }).scoreThreshold === "number"
7478
? Math.max(0, Math.min(1, (params as { scoreThreshold: number }).scoreThreshold))
7579
: (cfg.recallScoreThreshold ?? 0);
80+
const query = String((params as { query?: string }).query ?? "");
7681

7782
try {
7883
const requestLimit = Math.min(100, Math.max(limit * 2, limit + 10));
@@ -136,7 +141,7 @@ const memoryPlugin = {
136141
Type.Number({ description: "Importance 0-1 (default: 0.7)" }),
137142
),
138143
}),
139-
async execute(_toolCallId, params) {
144+
async execute(_toolCallId: string, params: Record<string, unknown>) {
140145
const { text, importance = 0.7 } = params as {
141146
text: string;
142147
importance?: number;
@@ -195,7 +200,7 @@ const memoryPlugin = {
195200
query: Type.Optional(Type.String({ description: "Search to find memory" })),
196201
memoryId: Type.Optional(Type.String({ description: "Specific memory ID" })),
197202
}),
198-
async execute(_toolCallId, params) {
203+
async execute(_toolCallId: string, params: Record<string, unknown>) {
199204
const { query, memoryId } = params as { query?: string; memoryId?: string };
200205

201206
try {
@@ -277,7 +282,7 @@ const memoryPlugin = {
277282
// ========================================================================
278283

279284
api.registerCli(
280-
({ program }) => {
285+
({ program }: OpenClawPluginCliContext) => {
281286
const ltm = program
282287
.command("ltm")
283288
.description("PowerMem long-term memory plugin commands");
@@ -287,7 +292,9 @@ const memoryPlugin = {
287292
.description("Search memories")
288293
.argument("<query>", "Search query")
289294
.option("--limit <n>", "Max results", "5")
290-
.action(async (query: string, opts: { limit?: string }) => {
295+
.action(async (...args: unknown[]) => {
296+
const query = String(args[0] ?? "");
297+
const opts = (args[1] ?? {}) as { limit?: string };
291298
const limit = parseInt(opts.limit ?? "5", 10);
292299
const results = await client.search(query, limit);
293300
console.log(JSON.stringify(results, null, 2));
@@ -310,7 +317,8 @@ const memoryPlugin = {
310317
.command("add")
311318
.description("Manually add a memory (for testing or one-off storage)")
312319
.argument("<text>", "Content to store")
313-
.action(async (text: string) => {
320+
.action(async (...args: unknown[]) => {
321+
const text = String(args[0] ?? "");
314322
try {
315323
const created = await client.add(text.trim(), { infer: cfg.inferOnAdd });
316324
if (created.length === 0) {
@@ -332,15 +340,16 @@ const memoryPlugin = {
332340
// ========================================================================
333341

334342
if (cfg.autoRecall) {
335-
api.on("before_agent_start", async (event) => {
336-
if (!event.prompt || event.prompt.length < 5) return;
343+
api.on("before_agent_start", async (event: unknown) => {
344+
const e = event as { prompt: string; messages?: unknown[] };
345+
if (!e.prompt || e.prompt.length < 5) return;
337346

338347
const recallLimit = Math.max(1, Math.min(100, cfg.recallLimit ?? 5));
339348
const scoreThreshold = Math.max(0, Math.min(1, cfg.recallScoreThreshold ?? 0));
340349

341350
try {
342351
const requestLimit = Math.min(100, Math.max(recallLimit * 2, recallLimit + 10));
343-
const raw = await client.search(event.prompt, requestLimit);
352+
const raw = await client.search(e.prompt, requestLimit);
344353
const results = raw
345354
.filter((r) => (r.score ?? 0) >= scoreThreshold)
346355
.slice(0, recallLimit);
@@ -360,14 +369,15 @@ const memoryPlugin = {
360369
}
361370

362371
if (cfg.autoCapture) {
363-
api.on("agent_end", async (event) => {
364-
if (!event.success || !event.messages || event.messages.length === 0) {
372+
api.on("agent_end", async (event: unknown) => {
373+
const e = event as { messages: unknown[]; success: boolean; error?: string };
374+
if (!e.success || !e.messages || e.messages.length === 0) {
365375
return;
366376
}
367377

368378
try {
369379
const texts: string[] = [];
370-
for (const msg of event.messages) {
380+
for (const msg of e.messages) {
371381
if (!msg || typeof msg !== "object") continue;
372382
const msgObj = msg as Record<string, unknown>;
373383
const role = msgObj.role;
@@ -433,7 +443,7 @@ const memoryPlugin = {
433443

434444
api.registerService({
435445
id: "memory-powermem",
436-
start: async (_ctx) => {
446+
start: async (_ctx: OpenClawPluginServiceContext) => {
437447
try {
438448
const h = await client.health();
439449
const where = cfg.mode === "cli" ? `cli ${cfg.pmemPath ?? "pmem"}` : cfg.baseUrl;
@@ -450,7 +460,7 @@ const memoryPlugin = {
450460
);
451461
}
452462
},
453-
stop: (_ctx) => {
463+
stop: (_ctx: OpenClawPluginServiceContext) => {
454464
api.logger.info("memory-powermem: stopped");
455465
},
456466
});

openclaw-plugin-sdk.d.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Minimal type declarations for openclaw plugin-sdk so the extension type-checks
3+
* without installing openclaw (faster CI). Runtime resolves via openclaw's loader.
4+
*/
5+
declare module "openclaw/plugin-sdk/memory-core" {
6+
type CommandChain = {
7+
command: (name: string, description?: string) => CommandChain;
8+
description: (desc: string) => CommandChain;
9+
argument: (name: string, description?: string) => CommandChain;
10+
option: (flags: string, description?: string, defaultValue?: string) => CommandChain;
11+
action: (fn: (...args: unknown[]) => void | Promise<void>) => CommandChain;
12+
};
13+
14+
export type OpenClawPluginCliContext = {
15+
program: {
16+
command: (name: string, description?: string) => CommandChain;
17+
};
18+
config: unknown;
19+
logger: { info: (msg: string) => void; warn: (msg: string) => void; debug?: (msg: string) => void };
20+
};
21+
22+
type ServiceContext = {
23+
config: unknown;
24+
workspaceDir?: string;
25+
stateDir: string;
26+
logger: { info: (msg: string) => void; warn: (msg: string) => void };
27+
};
28+
29+
export type OpenClawPluginApi = {
30+
pluginConfig?: Record<string, unknown>;
31+
logger: { info: (msg: string) => void; warn: (msg: string) => void; debug?: (msg: string) => void };
32+
registerTool: (tool: unknown, opts?: { name?: string; names?: string[] }) => void;
33+
registerCli: (
34+
registrar: (ctx: OpenClawPluginCliContext) => void | Promise<void>,
35+
opts?: { commands?: string[] },
36+
) => void;
37+
on: (hookName: string, handler: (event: unknown) => unknown | Promise<unknown>) => void;
38+
registerService: (service: {
39+
id: string;
40+
start: (ctx: ServiceContext) => void | Promise<void>;
41+
stop?: (ctx: ServiceContext) => void;
42+
}) => void;
43+
};
44+
}
45+
46+
declare module "openclaw/plugin-sdk" {
47+
export type OpenClawPluginServiceContext = {
48+
config: unknown;
49+
workspaceDir?: string;
50+
stateDir: string;
51+
logger: { info: (msg: string) => void; warn: (msg: string) => void };
52+
};
53+
}

test/config.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Tests for PowerMem plugin config parsing and resolvers.
3+
*/
4+
import { describe, it, expect } from "vitest";
5+
import {
6+
powerMemConfigSchema,
7+
resolveUserId,
8+
resolveAgentId,
9+
DEFAULT_USER_ID,
10+
DEFAULT_AGENT_ID,
11+
type PowerMemConfig,
12+
} from "../config.js";
13+
14+
describe("powerMemConfigSchema", () => {
15+
it("parses valid http config with required fields", () => {
16+
const cfg = powerMemConfigSchema.parse({
17+
mode: "http",
18+
baseUrl: "http://localhost:8000",
19+
autoCapture: true,
20+
autoRecall: true,
21+
inferOnAdd: true,
22+
}) as PowerMemConfig;
23+
expect(cfg.mode).toBe("http");
24+
expect(cfg.baseUrl).toBe("http://localhost:8000");
25+
expect(cfg.autoCapture).toBe(true);
26+
expect(cfg.autoRecall).toBe(true);
27+
expect(cfg.inferOnAdd).toBe(true);
28+
expect(cfg.recallLimit).toBe(5);
29+
expect(cfg.recallScoreThreshold).toBe(0);
30+
});
31+
32+
it("parses valid cli config", () => {
33+
const cfg = powerMemConfigSchema.parse({
34+
mode: "cli",
35+
baseUrl: "",
36+
autoCapture: false,
37+
autoRecall: true,
38+
inferOnAdd: false,
39+
}) as PowerMemConfig;
40+
expect(cfg.mode).toBe("cli");
41+
expect(cfg.pmemPath).toBe("pmem");
42+
});
43+
44+
it("rejects non-object config", () => {
45+
expect(() => powerMemConfigSchema.parse(null)).toThrow("memory-powermem config required");
46+
expect(() => powerMemConfigSchema.parse("")).toThrow();
47+
});
48+
49+
it("rejects http mode without baseUrl", () => {
50+
expect(() =>
51+
powerMemConfigSchema.parse({
52+
mode: "http",
53+
baseUrl: "",
54+
autoCapture: true,
55+
autoRecall: true,
56+
inferOnAdd: true,
57+
}),
58+
).toThrow("baseUrl is required when mode is http");
59+
});
60+
});
61+
62+
describe("resolveUserId / resolveAgentId", () => {
63+
it("returns default user/agent when not set", () => {
64+
const cfg = { userId: undefined, agentId: undefined } as PowerMemConfig;
65+
expect(resolveUserId(cfg)).toBe(DEFAULT_USER_ID);
66+
expect(resolveAgentId(cfg)).toBe(DEFAULT_AGENT_ID);
67+
});
68+
69+
it("returns configured user/agent when set", () => {
70+
const cfg = {
71+
userId: "user-1",
72+
agentId: "agent-1",
73+
} as PowerMemConfig;
74+
expect(resolveUserId(cfg)).toBe("user-1");
75+
expect(resolveAgentId(cfg)).toBe("agent-1");
76+
});
77+
});

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
"isolatedModules": true,
1212
"types": ["node", "vitest/globals"]
1313
},
14-
"include": ["*.ts", "*.test.ts"],
14+
"include": ["*.ts", "openclaw-plugin-sdk.d.ts", "test/**/*.ts"],
1515
"exclude": ["node_modules"]
1616
}

0 commit comments

Comments
 (0)