5.2 KiB
Action implementation contract — plugin-coding-tools
This document is the brief every action-implementing agent operates from. Read it once, then implement the action(s) you were assigned.
Files you may write
src/actions/<your-action>.ts— the action implementationsrc/actions/__tests__/<your-action>.test.ts— vitest tests
Do not modify anything else. The plugin scaffold (services, types,
providers, build config, index.ts) is already wired up and depends on the
exact export names listed in src/actions/index.ts.
Required export shape
import type { Action } from "@elizaos/core";
export const <yourActionName>: Action = { /* ... */ };
The export name must match the entry in src/actions/index.ts.
Action skeleton
import {
type Action,
type ActionResult,
type HandlerCallback,
type IAgentRuntime,
type Memory,
type State,
logger as coreLogger,
} from "@elizaos/core";
import {
failureToActionResult,
readNumberParam,
readStringParam,
successActionResult,
} from "../lib/format.js";
import { CODING_TOOLS_CONTEXTS, CODING_TOOLS_LOG_PREFIX } from "../types.js";
// import services from "../services/index.js" as needed
export const myAction: Action = {
name: "MY_ACTION",
contexts: [...CODING_TOOLS_CONTEXTS],
contextGate: { anyOf: ["code", "terminal", "automation"] },
similes: ["MY_SIMILE"],
description: "One-paragraph description for the planner.",
descriptionCompressed: "Short tagline (under 80 chars).",
parameters: [
{ name: "file_path", description: "Absolute path", required: true,
schema: { type: "string" } },
// ...
],
validate: async (runtime: IAgentRuntime, _message: Memory, _state?: State) => {
const disable = runtime.getSetting?.("CODING_TOOLS_DISABLE");
if (disable === true || disable === "true" || disable === "1") return false;
return true;
},
handler: async (
runtime: IAgentRuntime,
message: Memory,
state?: State,
options?: unknown,
callback?: HandlerCallback,
): Promise<ActionResult> => {
// 1. Parse params via readStringParam / readNumberParam / etc.
// 2. Validate via SandboxService etc.
// 3. Do the work.
// 4. Return successActionResult(text, data) | failureToActionResult(failure)
// 5. Optionally invoke callback({ text, source: "coding-tools" })
},
};
Available services (resolve via runtime.getService<T>(serviceType))
FileStateService(FILE_STATE_SERVICE) —recordRead,recordWrite,assertWritable,invalidate. Use this for mtime-gated Write/Edit.SandboxService(SANDBOX_SERVICE) —validatePath(conversationId, abs),validateCommand(cmd),addRoot/removeRoot,rootsFor. Always validate paths through this before reading or writing.SessionCwdService(SESSION_CWD_SERVICE) —getCwd,setCwd,pushWorktree,popWorktree. Use as default when an optionalpath/cwdparam is omitted.RipgrepService(RIPGREP_SERVICE) —search(options, mode). GREP only.ShellTaskService(SHELL_TASK_SERVICE) —start_,get,waitFor,stop_. SHELL backgrounding + TASK_OUTPUT + TASK_STOP.
Path & cwd rules (NON-NEGOTIABLE)
- READ / WRITE / EDIT / NOTEBOOK_EDIT —
file_pathis required and must be an absolute path. Reject relative paths viaSandboxService.validatePath. - GLOB / GREP / LS —
pathparameter optional. When omitted, default toSessionCwdService.getCwd(conversationId). When provided, must be absolute, validated through SandboxService. - SHELL — runs with
cwddefaulting toSessionCwdService.getCwd(...). Optionalcwdparameter overrides; must be absolute and within roots. - WORKTREE action=enter — call
SandboxService.addRootandSessionCwdService.pushWorktreeso the new path is reachable.
Conversation ID
conversationId for service keys = message.roomId (string-coerced). If
message.roomId is missing, fail with failureToActionResult({reason: "missing_param", message: "no roomId"}).
Error handling
Use failureToActionResult for all failures. Don't throw out of the handler.
Always return ActionResult with success: true | false and a text field.
Tests
Use vitest. Mock the runtime where needed via a small helper or inline. Test
both happy path and one or two failure cases (path outside roots, file not
read first, etc.). Tests live in src/actions/__tests__/<name>.test.ts.
Run with bun run test --filter=<your-action> from the plugin dir.
Imports
@elizaos/corefor types, logger, Service.node:fs/promises,node:path,node:child_process,node:cryptofor Node APIs. No external deps beyond what package.json already declares (@vscode/ripgrepis the only runtime dep).- Always use
.jsextension on relative imports (ESM rule).
Style
- Match the style of the existing action implementations in
src/actions/. - Use
coreLogger.debug/.warnfor diagnostic logs prefixed withCODING_TOOLS_LOG_PREFIX. - No comments narrating obvious code. Only comment non-obvious WHY.
When in doubt
Read the spec for your action in this brief, look at how Claude Code does it
(claude-code/src/tools/<ToolName>/), then implement the simplest correct
thing that satisfies both. Do not invent extra parameters.