426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
270 lines
9.2 KiB
TypeScript
270 lines
9.2 KiB
TypeScript
/**
|
|
* Integration tests for the assembled `codingToolsPlugin` — service registration,
|
|
* action wiring, and auto-enable gating — exercised in-process against the real
|
|
* filesystem.
|
|
*/
|
|
import * as fs from "node:fs/promises";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import type { IAgentRuntime, Memory, Service, UUID } from "@elizaos/core";
|
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
import * as pluginModule from "../src/index.js";
|
|
import codingToolsPlugin, {
|
|
availableToolsProvider,
|
|
CODING_TOOLS_CONTEXTS,
|
|
FILE_STATE_SERVICE,
|
|
FileStateService,
|
|
RIPGREP_SERVICE,
|
|
RipgrepService,
|
|
SANDBOX_SERVICE,
|
|
SandboxService,
|
|
SESSION_CWD_SERVICE,
|
|
SessionCwdService,
|
|
} from "../src/index.js";
|
|
|
|
const EXPECTED_ACTIONS = ["FILE", "SHELL", "WORKTREE"];
|
|
|
|
describe("@elizaos/plugin-coding-tools — plugin export shape", () => {
|
|
it("exports a Plugin with the expected name", () => {
|
|
expect(codingToolsPlugin.name).toBe("coding-tools");
|
|
expect(codingToolsPlugin.description).toBeTruthy();
|
|
});
|
|
|
|
it("registers the consolidated top-level coding actions", () => {
|
|
const actions = codingToolsPlugin.actions ?? [];
|
|
const names = actions.map((a) => a.name).sort();
|
|
expect(names).toEqual([...EXPECTED_ACTIONS].sort());
|
|
});
|
|
|
|
it("does not register legacy leaf actions as planner-facing actions", () => {
|
|
const names = new Set((codingToolsPlugin.actions ?? []).map((a) => a.name));
|
|
for (const legacyName of [
|
|
"READ",
|
|
"WRITE",
|
|
"EDIT",
|
|
"BASH",
|
|
"GREP",
|
|
"GLOB",
|
|
"LS",
|
|
"WEB_FETCH",
|
|
"ASK_USER_QUESTION",
|
|
"ENTER_WORKTREE",
|
|
"EXIT_WORKTREE",
|
|
]) {
|
|
expect(names.has(legacyName), legacyName).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("FILE exposes only canonical file umbrella similes", () => {
|
|
const fileAction = (codingToolsPlugin.actions ?? []).find(
|
|
(action) => action.name === "FILE",
|
|
);
|
|
expect(fileAction?.similes).toEqual(["FILE_OPERATION", "FILE_IO"]);
|
|
});
|
|
|
|
it("each action has the required fields", () => {
|
|
for (const action of codingToolsPlugin.actions ?? []) {
|
|
expect(action.name, action.name).toBeTruthy();
|
|
expect(action.description, action.name).toBeTruthy();
|
|
expect(action.handler, action.name).toBeTypeOf("function");
|
|
expect(action.validate, action.name).toBeTypeOf("function");
|
|
}
|
|
});
|
|
|
|
it("action names are unique", () => {
|
|
const names = (codingToolsPlugin.actions ?? []).map((a) => a.name);
|
|
expect(new Set(names).size).toBe(names.length);
|
|
});
|
|
|
|
it("exports the 4 active services", () => {
|
|
const services = codingToolsPlugin.services ?? [];
|
|
expect(services).toContain(FileStateService);
|
|
expect(services).toContain(SandboxService);
|
|
expect(services).toContain(SessionCwdService);
|
|
expect(services).toContain(RipgrepService);
|
|
expect(services.length).toBe(4);
|
|
});
|
|
|
|
it("does not export removed actions or service constants", () => {
|
|
expect("bashAction" in pluginModule).toBe(false);
|
|
expect("BASH_AST_SERVICE" in pluginModule).toBe(false);
|
|
expect("OS_SANDBOX_SERVICE" in pluginModule).toBe(false);
|
|
expect("SHELL_TASK_SERVICE" in pluginModule).toBe(false);
|
|
expect("ShellTaskService" in pluginModule).toBe(false);
|
|
expect("notebookEditAction" in pluginModule).toBe(false);
|
|
expect("taskOutputAction" in pluginModule).toBe(false);
|
|
expect("taskStopAction" in pluginModule).toBe(false);
|
|
expect("todoWriteAction" in pluginModule).toBe(false);
|
|
});
|
|
|
|
it("exports the available-tools provider at position -10 with code/terminal/automation gates", () => {
|
|
expect(codingToolsPlugin.providers ?? []).toContain(availableToolsProvider);
|
|
expect(availableToolsProvider.position).toBe(-10);
|
|
expect(availableToolsProvider.contexts).toEqual([...CODING_TOOLS_CONTEXTS]);
|
|
});
|
|
|
|
it("validate ignores CODING_TOOLS_DISABLE — kill switch was removed", async () => {
|
|
const runtime = {
|
|
agentId: "00000000-0000-0000-0000-000000000000",
|
|
getSetting: (key: string) =>
|
|
key === "CODING_TOOLS_DISABLE" ? true : undefined,
|
|
getService: () => null,
|
|
} as IAgentRuntime;
|
|
const message = { roomId: "r" } as Memory;
|
|
for (const action of codingToolsPlugin.actions ?? []) {
|
|
const ok = await action.validate?.(runtime, message);
|
|
expect(ok, action.name).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("@elizaos/plugin-coding-tools — end-to-end smoke", () => {
|
|
let tmpDir: string;
|
|
let runtime: IAgentRuntime;
|
|
let services: Map<string, Service>;
|
|
const cleanup: Array<() => Promise<void>> = [];
|
|
|
|
beforeAll(async () => {
|
|
tmpDir = await fs.realpath(
|
|
await fs.mkdtemp(path.join(os.tmpdir(), "ct-integ-")),
|
|
);
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "needle.txt"),
|
|
"this file contains the NEEDLE word\n",
|
|
"utf8",
|
|
);
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "other.md"),
|
|
"# heading\n\nbody text\n",
|
|
"utf8",
|
|
);
|
|
|
|
services = new Map();
|
|
runtime = {
|
|
agentId: "00000000-0000-0000-0000-000000000000" as UUID,
|
|
getSetting: (_key: string) => undefined,
|
|
getService: (key: string) => services.get(key) ?? null,
|
|
} as IAgentRuntime;
|
|
|
|
const fileState = await FileStateService.start(runtime);
|
|
const sandbox = await SandboxService.start(runtime);
|
|
const session = await SessionCwdService.start(runtime);
|
|
const rg = await RipgrepService.start(runtime);
|
|
services.set(FILE_STATE_SERVICE, fileState);
|
|
services.set(SANDBOX_SERVICE, sandbox);
|
|
services.set(SESSION_CWD_SERVICE, session);
|
|
services.set(RIPGREP_SERVICE, rg);
|
|
cleanup.push(() => fileState.stop());
|
|
cleanup.push(() => sandbox.stop());
|
|
cleanup.push(() => session.stop());
|
|
cleanup.push(() => rg.stop());
|
|
session.setCwd("smoke-room", tmpDir);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
for (const fn of cleanup) {
|
|
try {
|
|
await fn();
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
function findAction(name: string) {
|
|
const actions = codingToolsPlugin.actions ?? [];
|
|
const a = actions.find((x) => x.name === name);
|
|
if (!a) throw new Error(`action ${name} not found`);
|
|
return a;
|
|
}
|
|
|
|
function makeMessage(): Memory {
|
|
return { roomId: "smoke-room" } as Memory;
|
|
}
|
|
|
|
it("FILE action=read returns a known file's contents", async () => {
|
|
const action = findAction("FILE");
|
|
const result = await action.handler?.(runtime, makeMessage(), undefined, {
|
|
parameters: {
|
|
action: "read",
|
|
file_path: path.join(tmpDir, "needle.txt"),
|
|
},
|
|
});
|
|
expect(result.success).toBe(true);
|
|
expect(result.text).toContain("NEEDLE");
|
|
});
|
|
|
|
it("FILE action=write creates a new file", async () => {
|
|
const action = findAction("FILE");
|
|
const target = path.join(tmpDir, "smoke-out.txt");
|
|
const result = await action.handler?.(runtime, makeMessage(), undefined, {
|
|
parameters: { action: "write", file_path: target, content: "smoke ok" },
|
|
});
|
|
expect(result.success).toBe(true);
|
|
const written = await fs.readFile(target, "utf8");
|
|
expect(written).toBe("smoke ok");
|
|
});
|
|
|
|
it("SHELL echo hello", async () => {
|
|
const action = findAction("SHELL");
|
|
const result = await action.handler?.(runtime, makeMessage(), undefined, {
|
|
parameters: { command: "echo smoke-bash-ok", cwd: tmpDir },
|
|
});
|
|
expect(result.success).toBe(true);
|
|
expect(result.text).toContain("smoke-bash-ok");
|
|
expect(result.text).toContain("[exit 0]");
|
|
});
|
|
|
|
it("FILE action=glob lists *.txt files", async () => {
|
|
const action = findAction("FILE");
|
|
const result = await action.handler?.(runtime, makeMessage(), undefined, {
|
|
parameters: { action: "glob", pattern: "*.txt", path: tmpDir },
|
|
});
|
|
expect(result.success).toBe(true);
|
|
expect(result.text).toContain("needle.txt");
|
|
});
|
|
|
|
it("FILE action=ls shows fixture entries", async () => {
|
|
const action = findAction("FILE");
|
|
const result = await action.handler?.(runtime, makeMessage(), undefined, {
|
|
parameters: { action: "ls", path: tmpDir },
|
|
});
|
|
expect(result.success).toBe(true);
|
|
expect(result.text).toContain("needle.txt");
|
|
expect(result.text).toContain("other.md");
|
|
});
|
|
|
|
it("GREP finds the NEEDLE token (skip when ripgrep absent)", async () => {
|
|
const rg = services.get(RIPGREP_SERVICE) as RipgrepService | undefined;
|
|
if (!rg) return;
|
|
const fs2 = await import("node:fs");
|
|
const initial = rg.binary();
|
|
if (!fs2.existsSync(initial)) {
|
|
const candidates = [
|
|
"/opt/homebrew/bin/rg",
|
|
"/usr/local/bin/rg",
|
|
"/usr/bin/rg",
|
|
];
|
|
const sys = candidates.find((p) => fs2.existsSync(p));
|
|
if (!sys) return;
|
|
(rg as { rgPath: string }).rgPath = sys;
|
|
}
|
|
const action = findAction("FILE");
|
|
const result = await action.handler?.(runtime, makeMessage(), undefined, {
|
|
parameters: { action: "grep", pattern: "NEEDLE", path: tmpDir },
|
|
});
|
|
expect(result.success).toBe(true);
|
|
expect(result.text).toContain("needle.txt");
|
|
});
|
|
|
|
it("WORKTREE action=enter in a non-git dir fails cleanly", async () => {
|
|
const action = findAction("WORKTREE");
|
|
const result = await action.handler?.(runtime, makeMessage(), undefined, {
|
|
parameters: { action: "enter" },
|
|
});
|
|
expect(result.success).toBe(false);
|
|
expect(result.text).toContain("io_error");
|
|
});
|
|
});
|