refactor: relocate plugin entry files into src/adapters/<platform>/

Pre-fix layout had three platform plugin entry files at the src/ root:

  src/pi-extension.ts        — Pi Coding Agent extension
  src/pi-mcp-bridge.ts       — Pi MCP bridge (added in #426)
  src/openclaw-plugin.ts     — OpenClaw gateway plugin
  src/opencode-plugin.ts     — OpenCode plugin

Every other platform follows the src/adapters/<name>/ pattern (config,
hooks, index, …). The four root-level files were the last hold-outs:
inconsistent layout, plus they made adapter discovery harder for new
contributors.

## Changes (file moves)

  - git mv src/pi-extension.ts        → src/adapters/pi/extension.ts
  - git mv src/pi-mcp-bridge.ts       → src/adapters/pi/mcp-bridge.ts
  - git mv src/openclaw-plugin.ts     → src/adapters/openclaw/plugin.ts
  - git mv src/opencode-plugin.ts     → src/adapters/opencode/plugin.ts

## Internal import-path updates inside the moved files

  - ./session/db.js    → ../../session/db.js   (depth +2)
  - ./types.js         → ../../types.js
  - ./adapters/X/Y.js  → ./Y.js                (now sibling)
  - ./adapters/types.js → ../types.js          (now parent)
  - ./pi-mcp-bridge.js → ./mcp-bridge.js       (renamed + sibling)

## Runtime path-resolution updates

The plugins read sibling resources (hooks/, package.json, etc.) via
`resolve(buildDir, "..")`. After the move buildDir lives 2 dirs
deeper, so every `..` is now `../../..`:

  - resolve(buildDir, "..")                                 → resolve(buildDir, "..", "..", "..")
  - resolve(buildDir, "..", "hooks", "core", "routing.mjs")    → resolve(buildDir, "..", "..", "..", "hooks", "core", "routing.mjs")
  - (and similar for routing-block / tool-naming / auto-injection)

For opencode/plugin.ts the version-from-package.json walker prepends
`../../../package.json` to its search list (keeps the legacy
`../package.json` and `./package.json` entries as fall-backs so
unbundled or old-layout dev environments still resolve).

## Build-output paths in package.json

tsc preserves src/ structure under build/, so:

  ./build/pi-extension.js     → ./build/adapters/pi/extension.js
  ./build/openclaw-plugin.js  → ./build/adapters/openclaw/plugin.js
  ./build/opencode-plugin.js  → ./build/adapters/opencode/plugin.js

Updated:

  - package.json: pi.extensions[0], openclaw.extensions[0], main,
    exports["."], exports["./plugin"], exports["./openclaw"]
  - .pi/extensions/context-mode/index.ts: re-export delegate path
  - .openclaw-plugin/index.ts: re-export delegate path + JSDoc

## Test-side updates

  - tests/pi-extension.test.ts: dynamic-import paths updated
  - tests/opencode-plugin.test.ts: dynamic-import paths updated
  - tests/plugins/openclaw.test.ts: dynamic-import paths updated
  - tests/core/cli.test.ts: 4 dynamic-import paths + 1
    `readFileSync(src/openclaw-plugin.ts)` source-grep updated to the
    new location
  - src/adapters/detect.ts: comment-line ref updated
  - tests/adapters/detect.test.ts: comment-line ref updated

## Test plan

  - [x] npm run build                                        clean
  - [x] npm run typecheck                                    clean
  - [x] npm test                                             73 files,
        2407 pass / 25 skipped / 0 fail
  - [x] npx vitest run tests/opencode-plugin.test.ts         33/33 pass
        (regression: marker test that needed package.json walker fix)
  - [x] npx vitest run tests/plugins/openclaw.test.ts        225/225 pass
  - [x] npx vitest run tests/pi-extension.test.ts            45/45 pass
        (incl. the wiring guard added in the previous commit)
  - [x] npx vitest run tests/core/cli.test.ts -t "openclaw-plugin.ts doctor/upgrade"
        passes against the new src/adapters/openclaw/plugin.ts location
  - [x] Manual sanity: every old root-level path (build/pi-extension.js,
        src/opencode-plugin.ts, etc.) is gone from the repo — grep
        confirms zero stale refs in src/ + tests/ + package.json + the
        .pi/.openclaw-plugin/ thin wrappers.

Co-Authored-By: Ora Studio <noreply@oratelecom.net>
This commit is contained in:
Ousama Ben Younes
2026-05-07 22:46:41 +00:00
co-authored by Ora Studio
parent 5bb1b0f25a
commit 4911c0716a
13 changed files with 86 additions and 81 deletions
+3 -2
View File
@@ -6,6 +6,7 @@
* discovering plugins from the .openclaw-plugin/ directory.
*
* The actual plugin definition (object form with id, name, configSchema,
* register) lives in src/openclaw-plugin.ts, compiled to build/openclaw-plugin.js.
* register) lives in src/adapters/openclaw/plugin.ts, compiled to
* build/adapters/openclaw/plugin.js.
*/
export { default } from "../build/openclaw-plugin.js";
export { default } from "../build/adapters/openclaw/plugin.js";
+1 -1
View File
@@ -1 +1 @@
export { default } from "../../../build/pi-extension.js";
export { default } from "../../../build/adapters/pi/extension.js";
+6 -6
View File
@@ -29,7 +29,7 @@
"homepage": "https://github.com/mksglu/context-mode#readme",
"pi": {
"extensions": [
"./build/pi-extension.js"
"./build/adapters/pi/extension.js"
],
"skills": [
"./skills"
@@ -37,15 +37,15 @@
},
"openclaw": {
"extensions": [
"./build/openclaw-plugin.js"
"./build/adapters/openclaw/plugin.js"
]
},
"bugs": "https://github.com/mksglu/context-mode/issues",
"main": "./build/opencode-plugin.js",
"main": "./build/adapters/opencode/plugin.js",
"exports": {
".": "./build/opencode-plugin.js",
"./plugin": "./build/opencode-plugin.js",
"./openclaw": "./build/openclaw-plugin.js",
".": "./build/adapters/opencode/plugin.js",
"./plugin": "./build/adapters/opencode/plugin.js",
"./openclaw": "./build/adapters/openclaw/plugin.js",
"./cli": "./cli.bundle.mjs"
},
"bin": {
+1 -1
View File
@@ -67,7 +67,7 @@ export const PLATFORM_ENV_VARS = [
// qwen-code — QWEN_PROJECT_DIR per QwenLM/qwen-code docs/users/features/hooks.md.
// (QWEN_SESSION_ID removed — 0 hits in qwen-code repository.)
["qwen-code", ["QWEN_PROJECT_DIR"]],
// pi — PI_PROJECT_DIR consumed by src/pi-extension.ts:154 + src/server.ts:153
// pi — PI_PROJECT_DIR consumed by src/adapters/pi/extension.ts:154 + src/server.ts:153
// — implies the Pi runtime sets it before invoking the extension.
["pi", ["PI_PROJECT_DIR"]],
// openclaw — removed (runtime never sets OPENCLAW_HOME or OPENCLAW_CLI;
@@ -35,17 +35,17 @@ import { homedir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { SessionDB } from "./session/db.js";
import { OpenClawSessionDB } from "./adapters/openclaw/session-db.js";
import { extractEvents, extractUserEvents } from "./session/extract.js";
import type { HookInput } from "./session/extract.js";
import { buildResumeSnapshot } from "./session/snapshot.js";
import type { SessionEvent } from "./types.js";
import { SessionDB } from "../../session/db.js";
import { OpenClawSessionDB } from "./session-db.js";
import { extractEvents, extractUserEvents } from "../../session/extract.js";
import type { HookInput } from "../../session/extract.js";
import { buildResumeSnapshot } from "../../session/snapshot.js";
import type { SessionEvent } from "../../types.js";
import { WorkspaceRouter } from "./adapters/openclaw/workspace-router.js";
import { buildNodeCommand } from "./adapters/types.js";
import { OPENCLAW_TOOL_DEFS } from "./adapters/openclaw/mcp-tools.js";
import type { OpenClawToolDef } from "./adapters/openclaw/mcp-tools.js";
import { WorkspaceRouter } from "./workspace-router.js";
import { buildNodeCommand } from "../types.js";
import { OPENCLAW_TOOL_DEFS } from "./mcp-tools.js";
import type { OpenClawToolDef } from "./mcp-tools.js";
// ── System-reminder filter (CCv2 — SLICE OClaw-3) ─────────
// Mirror hooks/userpromptsubmit.mjs:30-33: skip system-generated wrappers
@@ -244,7 +244,7 @@ export default {
// Resolve build dir from compiled JS location
const buildDir = dirname(fileURLToPath(import.meta.url));
const projectDir = process.cwd();
const pluginRoot = resolve(buildDir, "..");
const pluginRoot = resolve(buildDir, "..", "..", "..");
// Structured logger — wraps api.logger, falls back to no-op.
// info/error always emit; debug only when api.logger.debug is present
@@ -280,17 +280,17 @@ export default {
// MCP-prefix substitution stays in lockstep with hooks/routing-block.mjs.
let routingInstructions = "";
const initPromise = (async () => {
const routingPath = resolve(buildDir, "..", "hooks", "core", "routing.mjs");
const routingPath = resolve(buildDir, "..", "..", "..", "hooks", "core", "routing.mjs");
const routing = await import(pathToFileURL(routingPath).href);
await routing.initSecurity(buildDir);
try {
const blockMod = await import(
pathToFileURL(resolve(buildDir, "..", "hooks", "routing-block.mjs")).href
pathToFileURL(resolve(buildDir, "..", "..", "..", "hooks", "routing-block.mjs")).href
);
const namingMod = await import(
pathToFileURL(
resolve(buildDir, "..", "hooks", "core", "tool-naming.mjs"),
resolve(buildDir, "..", "..", "..", "hooks", "core", "tool-naming.mjs"),
).href
);
const toolNamer = namingMod.createToolNamer("openclaw");
@@ -599,7 +599,7 @@ export default {
}
const events = extractUserEvents(messageText);
for (const ev of events) {
db.insertEvent(sid, ev as import("./types.js").SessionEvent, "PostToolUse");
db.insertEvent(sid, ev as import("../../types.js").SessionEvent, "PostToolUse");
}
} catch {
// best effort — never break model resolution
@@ -25,13 +25,13 @@ import { dirname, resolve, join } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { existsSync, readFileSync } from "node:fs";
import { SessionDB } from "./session/db.js";
import { extractEvents, extractUserEvents } from "./session/extract.js";
import type { HookInput } from "./session/extract.js";
import { buildResumeSnapshot } from "./session/snapshot.js";
import type { SessionEvent } from "./types.js";
import { AdapterPlatformType, OpenCodeAdapter } from "./adapters/opencode/index.js";
import { PLATFORM_ENV_VARS } from "./adapters/detect.js";
import { SessionDB } from "../../session/db.js";
import { extractEvents, extractUserEvents } from "../../session/extract.js";
import type { HookInput } from "../../session/extract.js";
import { buildResumeSnapshot } from "../../session/snapshot.js";
import type { SessionEvent } from "../../types.js";
import { AdapterPlatformType, OpenCodeAdapter } from "./index.js";
import { PLATFORM_ENV_VARS } from "../detect.js";
// Read package.json version once at module load (not on every hook call).
// Used in the resume-injection visible signal so users can confirm in
@@ -39,7 +39,11 @@ import { PLATFORM_ENV_VARS } from "./adapters/detect.js";
const VERSION: string = (() => {
try {
const pkgRoot = dirname(fileURLToPath(import.meta.url));
for (const rel of ["../package.json", "./package.json"]) {
// Search both the legacy depths (when bundled flat under build/) and
// the post-refactor depths (when compiled to build/adapters/opencode/).
// `../../../package.json` is the canonical location after the
// `src/opencode-plugin.ts → src/adapters/opencode/plugin.ts` move.
for (const rel of ["../../../package.json", "../package.json", "./package.json"]) {
const p = resolve(pkgRoot, rel);
if (existsSync(p)) return JSON.parse(readFileSync(p, "utf8")).version ?? "unknown";
}
@@ -225,17 +229,17 @@ async function createContextModePlugin(ctx: PluginContext) {
const buildDir = dirname(fileURLToPath(import.meta.url));
// Load routing module (ESM .mjs, lives outside build/ in hooks/)
const routingPath = resolve(buildDir, "..", "hooks", "core", "routing.mjs");
const routingPath = resolve(buildDir, "..", "..", "..", "hooks", "core", "routing.mjs");
const routing = await import(pathToFileURL(routingPath).href);
await routing.initSecurity(buildDir);
// OC-1 / OC-3: Load hook helpers once at plugin init. Dynamic import keeps
// the .mjs ESM islands isolated from the .ts compile graph.
const routingBlockPath = resolve(buildDir, "..", "hooks", "routing-block.mjs");
const routingBlockPath = resolve(buildDir, "..", "..", "..", "hooks", "routing-block.mjs");
const routingBlockMod = await import(pathToFileURL(routingBlockPath).href);
const toolNamingPath = resolve(buildDir, "..", "hooks", "core", "tool-naming.mjs");
const toolNamingPath = resolve(buildDir, "..", "..", "..", "hooks", "core", "tool-naming.mjs");
const toolNamingMod = await import(pathToFileURL(toolNamingPath).href);
const autoInjectionPath = resolve(buildDir, "..", "hooks", "auto-injection.mjs");
const autoInjectionPath = resolve(buildDir, "..", "..", "..", "hooks", "auto-injection.mjs");
const autoInjectionMod = await import(pathToFileURL(autoInjectionPath).href);
// Pre-build the routing block once per process — it is platform-specific
@@ -16,12 +16,12 @@ import { existsSync, mkdirSync } from "node:fs";
import { homedir } from "node:os";
import { join, resolve, dirname } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { SessionDB } from "./session/db.js";
import { extractEvents, extractUserEvents } from "./session/extract.js";
import type { HookInput } from "./session/extract.js";
import { buildResumeSnapshot } from "./session/snapshot.js";
import type { SessionEvent } from "./types.js";
import { bootstrapMCPTools, type BridgeHandle } from "./pi-mcp-bridge.js";
import { SessionDB } from "../../session/db.js";
import { extractEvents, extractUserEvents } from "../../session/extract.js";
import type { HookInput } from "../../session/extract.js";
import { buildResumeSnapshot } from "../../session/snapshot.js";
import type { SessionEvent } from "../../types.js";
import { bootstrapMCPTools, type BridgeHandle } from "./mcp-bridge.js";
// ── Pi Tool Name Mapping ─────────────────────────────────
// Pi uses lowercase; shared extractors expect PascalCase (Claude Code convention).
@@ -213,7 +213,7 @@ function handleCommandText(
/** Pi extension default export. Called once by Pi runtime with the extension API. */
export default function piExtension(pi: any): void {
const buildDir = dirname(fileURLToPath(import.meta.url));
const pluginRoot = resolve(buildDir, "..");
const pluginRoot = resolve(buildDir, "..", "..", "..");
const projectDir = process.env.PI_PROJECT_DIR || process.cwd();
const db = getOrCreateDB();
+1 -1
View File
@@ -152,7 +152,7 @@ describe("detectPlatform", () => {
// ── Pi ─────────────────────────────────────────────────
// Pi runtime sets PI_PROJECT_DIR before invoking the extension —
// verified by src/pi-extension.ts:154 + src/server.ts:153 consumers.
// verified by src/adapters/pi/extension.ts:154 + src/server.ts:153 consumers.
it("detects pi via PI_PROJECT_DIR env var", () => {
process.env.PI_PROJECT_DIR = "/some/project";
+4 -4
View File
@@ -823,7 +823,7 @@ describe("Bin entry uses cli.bundle.mjs", () => {
});
it("openclaw-plugin.ts doctor/upgrade use cli.bundle.mjs with fallback", () => {
const src = readFileSync(resolve(ROOT, "src", "openclaw-plugin.ts"), "utf-8");
const src = readFileSync(resolve(ROOT, "src", "adapters", "openclaw", "plugin.ts"), "utf-8");
expect(src).toContain("cli.bundle.mjs");
// Find the registerCommand blocks, not comments
const doctorIdx = src.indexOf('name: "ctx-doctor"');
@@ -934,19 +934,19 @@ describe("SKILL.md prefers MCP tool over Bash", () => {
describe("Package exports", () => {
test("named export exposes ContextModePlugin factory", async () => {
const mod = await import("../../src/opencode-plugin.js");
const mod = await import("../../src/adapters/opencode/plugin.js");
expect(mod.ContextModePlugin).toBeDefined();
expect(typeof mod.ContextModePlugin).toBe("function");
});
test("default export has KiloCode PluginModule shape { server }", async () => {
const mod = (await import("../../src/opencode-plugin.js")) as any;
const mod = (await import("../../src/adapters/opencode/plugin.js")) as any;
expect(mod.default).toBeDefined();
expect(typeof mod.default.server).toBe("function");
});
test("default export does not leak CLI internals", async () => {
const mod = (await import("../../src/opencode-plugin.js")) as any;
const mod = (await import("../../src/adapters/opencode/plugin.js")) as any;
expect(mod.toUnixPath).toBeUndefined();
expect(mod.doctor).toBeUndefined();
expect(mod.upgrade).toBeUndefined();
+1 -1
View File
@@ -21,7 +21,7 @@ import { tmpdir } from "node:os";
*/
async function createTestPlugin(tempDir: string) {
// Import the plugin module
const { ContextModePlugin } = await import("../src/opencode-plugin.js");
const { ContextModePlugin } = await import("../src/adapters/opencode/plugin.js");
// Monkey-patch the session dir to use temp directory
// The plugin uses homedir() internally, but we can control the DB path
+11 -11
View File
@@ -2,7 +2,7 @@ import "./setup-home";
/**
* Pi Extension Tests — TDD vertical slices.
*
* The Pi extension (src/pi-extension.ts) is a default-exported function that
* The Pi extension (src/adapters/pi/extension.ts) is a default-exported function that
* receives a Pi API object and registers event handlers. Since we cannot test
* against a real Pi runtime, we mock the Pi API to capture registered handlers
* and invoke them with simulated events.
@@ -77,7 +77,7 @@ async function registerPiExtension(
process.env.PI_PROJECT_DIR = projectDir;
process.env.CLAUDE_PROJECT_DIR = projectDir;
const mod = await import("../src/pi-extension.js");
const mod = await import("../src/adapters/pi/extension.js");
const register = mod.default;
await register(mockApi);
@@ -767,7 +767,7 @@ describe("Pi MCP bridge (#426)", () => {
}
});
`);
const { MCPStdioClient } = await import("../src/pi-mcp-bridge.js");
const { MCPStdioClient } = await import("../src/adapters/pi/mcp-bridge.js");
const client = new MCPStdioClient(fakePath);
client.start();
try {
@@ -803,7 +803,7 @@ describe("Pi MCP bridge (#426)", () => {
}
});
`);
const { MCPStdioClient } = await import("../src/pi-mcp-bridge.js");
const { MCPStdioClient } = await import("../src/adapters/pi/mcp-bridge.js");
const client = new MCPStdioClient(fakePath);
client.start();
try {
@@ -822,7 +822,7 @@ describe("Pi MCP bridge (#426)", () => {
const fakePath = writeFakeServer(`
process.stdin.once("data", () => process.exit(0));
`);
const { MCPStdioClient } = await import("../src/pi-mcp-bridge.js");
const { MCPStdioClient } = await import("../src/adapters/pi/mcp-bridge.js");
const client = new MCPStdioClient(fakePath);
client.start();
const promise = client.request("tools/list", {});
@@ -835,7 +835,7 @@ describe("Pi MCP bridge (#426)", () => {
process.stdin.on("data", () => {});
setInterval(() => {}, 1000);
`);
const { MCPStdioClient } = await import("../src/pi-mcp-bridge.js");
const { MCPStdioClient } = await import("../src/adapters/pi/mcp-bridge.js");
const client = new MCPStdioClient(fakePath);
client.start();
try {
@@ -865,7 +865,7 @@ describe("Pi MCP bridge (#426)", () => {
}
});
`);
const { MCPStdioClient } = await import("../src/pi-mcp-bridge.js");
const { MCPStdioClient } = await import("../src/adapters/pi/mcp-bridge.js");
const client = new MCPStdioClient(fakePath);
client.start();
try {
@@ -905,7 +905,7 @@ describe("Pi MCP bridge (#426)", () => {
},
};
const { bootstrapMCPTools } = await import("../src/pi-mcp-bridge.js");
const { bootstrapMCPTools } = await import("../src/adapters/pi/mcp-bridge.js");
bridge = await bootstrapMCPTools(fakePi, mcpEntry, { env: mcpEnv });
// Pin the canonical names — adding new MCP tools is fine
@@ -940,7 +940,7 @@ describe("Pi MCP bridge (#426)", () => {
registerTool: (tool: any) => registered.push(tool),
};
const { bootstrapMCPTools } = await import("../src/pi-mcp-bridge.js");
const { bootstrapMCPTools } = await import("../src/adapters/pi/mcp-bridge.js");
bridge = await bootstrapMCPTools(fakePi, mcpEntry, { env: mcpEnv });
const indexTool = registered.find((t) => t.name === "ctx_index");
@@ -966,7 +966,7 @@ describe("Pi MCP bridge (#426)", () => {
//
// This is the regression that the rest of the suite does NOT catch: if
// a future refactor drops the `bootstrapMCPTools(pi, …)` call from
// src/pi-extension.ts but keeps the bridge module intact, every other
// src/adapters/pi/extension.ts but keeps the bridge module intact, every other
// bridge test stays green and the bug silently re-enters. We assert
// here that the extension's default export, after `_mcpBridgeReady`
// settles, has actually called `pi.registerTool` for at least the
@@ -980,7 +980,7 @@ describe("Pi MCP bridge (#426)", () => {
// Bootstrap is fire-and-forget on extension load — wait on the
// exported promise so the test does not race the spawn.
const mod = await import("../src/pi-extension.js");
const mod = await import("../src/adapters/pi/extension.js");
await mod._mcpBridgeReady;
const calls = (wireApi.registerTool as any).mock.calls as Array<[any]>;
+20 -20
View File
@@ -121,7 +121,7 @@ function createMockApiFull() {
async function createTestPlugin(tempDir: string) {
// Each test gets an isolated DB by running from a unique cwd (tempDir).
// The plugin reads process.cwd() for projectDir — no fake env var needed.
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const mock = createMockApiFull();
await plugin.register(mock.api);
return mock;
@@ -209,7 +209,7 @@ describe("OpenClawPlugin", () => {
describe("object export", () => {
it("exports object with id, name, configSchema, register", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
expect(plugin.id).toBe("context-mode");
expect(plugin.name).toBe("Context Mode");
expect(plugin.configSchema).toBeDefined();
@@ -218,7 +218,7 @@ describe("OpenClawPlugin", () => {
});
it("configSchema has enabled property", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
expect(plugin.configSchema.properties.enabled).toBeDefined();
expect(plugin.configSchema.properties.enabled.type).toBe("boolean");
expect(plugin.configSchema.properties.enabled.default).toBe(true);
@@ -798,7 +798,7 @@ describe("Plugin exports", () => {
beforeEach(() => { vi.resetModules(); });
test("plugin exports id, name, configSchema, register", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
assert.equal(plugin.id, "context-mode");
assert.equal(plugin.name, "Context Mode");
assert.ok(plugin.configSchema);
@@ -810,7 +810,7 @@ describe("session_start hook", () => {
beforeEach(() => { vi.resetModules(); });
test("session_start hook is registered", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -820,7 +820,7 @@ describe("session_start hook", () => {
});
test("session_start hook is registered with no priority (void hook)", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -831,7 +831,7 @@ describe("session_start hook", () => {
});
test("session_start handler resets resumeInjected — verified via before_prompt_build sequence", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -861,7 +861,7 @@ describe("compaction hooks", () => {
beforeEach(() => { vi.resetModules(); });
test("before_compaction hook is registered", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -871,7 +871,7 @@ describe("compaction hooks", () => {
});
test("after_compaction hook is registered", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -917,7 +917,7 @@ describe("resume injection (before_prompt_build)", () => {
beforeEach(() => { vi.resetModules(); });
test("before_prompt_build resume hook is registered at priority 10", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -1131,7 +1131,7 @@ describe("before_model_resolve hook", () => {
beforeEach(() => { vi.resetModules(); });
test("before_model_resolve hook is registered", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -1150,7 +1150,7 @@ describe("before_model_resolve hook", () => {
});
test("before_model_resolve handler runs without throwing on decision message", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -1165,7 +1165,7 @@ describe("before_model_resolve hook", () => {
});
test("before_model_resolve is silent when userMessage is empty", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -1187,7 +1187,7 @@ describe("command lifecycle hooks", () => {
beforeEach(() => { vi.resetModules(); });
test("command:reset hook is registered", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, hooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -1197,7 +1197,7 @@ describe("command lifecycle hooks", () => {
});
test("command:stop hook is registered", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, hooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -1207,7 +1207,7 @@ describe("command lifecycle hooks", () => {
});
test("command:reset handler runs cleanupOldSessions without throwing", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, hooks } = createMockApiHooks();
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -1226,7 +1226,7 @@ describe("verbose logging", () => {
beforeEach(() => { vi.resetModules(); });
test("plugin works without logger (logger is optional)", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api } = createMockApiHooks(false); // no logger
assert.doesNotThrow(() =>
@@ -1235,7 +1235,7 @@ describe("verbose logging", () => {
});
test("session_start emits info log when logger is provided", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks, logLines } = createMockApiHooks(true);
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -1249,7 +1249,7 @@ describe("verbose logging", () => {
});
test("after_tool_call emits debug log for captured events when logger provided", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks, logLines } = createMockApiHooks(true);
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);
@@ -1268,7 +1268,7 @@ describe("verbose logging", () => {
});
test("before_prompt_build emits debug log when resume is injected", async () => {
const { default: plugin } = await import("../../src/openclaw-plugin.js");
const { default: plugin } = await import("../../src/adapters/openclaw/plugin.js");
const { api, typedHooks, logLines } = createMockApiHooks(true);
plugin.register(api as unknown as Parameters<typeof plugin.register>[0]);