@ai-setting/roy-plugin-tool-debug
v0.3.0
Published
roy-agent plugin: print compact single-line hook context (EnvContext + payload + project_path) at tool:after.execute and agent:before/after.llm for local debugging.
Maintainers
Readme
@ai-setting/roy-plugin-tool-debug
roy-agent plugin that prints compact hook context (EnvContext + payload + project_path) at three hook points during local development. Drop it into any roy-agent host to see exactly what your hooks receive — no setup, no HTTP server, no UI.
What's new in v0.3.0
The plugin was emitting two compact debug boxes per tool invocation
(tool:before.execute and tool:after.execute), each with a wide
┌─ … ─┐ / └─ … ┘ border. That's a lot of horizontal padding and
visual noise for what is ultimately one logical event.
v0.3.0 collapses this into a single compact block per tool call, adds
the related task's project_path (resolved via roy-agent tasks get,
cached, with graceful degradation), and uses ASCII-free compact
formatting:
[tool:after] bash ✓ 12ms Task:#2090 Iter:N/A Path:/home/.../roy-agent
Args: {"cmd":"ls"}
Res: okThe previous tool:before.execute hook is removed — its payload
(args + EnvContext) is already visible in the after hook, so emitting
twice per tool call was redundant.
Hook points
| Hook | Purpose |
| ------------------- | ---------------------------------------------------------- |
| tool:after.execute | EnvContext + args + result + project_path (if a task is associated) |
| agent:before.llm | EnvContext + iteration + message count + project_path |
| agent:after.llm | EnvContext + LLM content length + tool-call count + project_path |
Output format (v0.3.0+)
tool:after.execute emits a single-line header plus indented
payload lines:
[tool:after] bash ✓ 12ms Task:#2090 Iter:N/A Sess:session_09617c10-… Path:/home/dzk/.../roy-agent
Args: {"cmd":"ls -la"}
Res: okFailure path uses ✗ in red:
[tool:after] write ✗ 8ms Task:#2093 Path:/home/dzk/.../roy-agent
Args: {"path":"/tmp/foo.txt","content":"aaaa…"}
Res: bbb…[truncated 36 chars]When Args / Res exceeds indentWidth (default 100), they wrap
onto multiple indented lines. The [truncated N chars] marker stays
atomic on the last wrapped line.
agent:before.llm and agent:after.llm use the same compact single-
line format with their own field set (iteration, message count,
content length, tool-call count).
Color scheme
| Element | Color | SGR code |
| ------------------ | -------- | -------- |
| Tool name | white | 37 |
| Key (label) | cyan | 36 |
| Task / Iter / Count | yellow | 33 |
| Session / Trace / Req ID | magenta | 35 |
| Success (✓) | green | 32 |
| Failure (✗) | red | 31 |
| Duration (ms) / N/A / (none) | gray | 90 |
| Default value | reset | 0 |
Controlling color
| Setting | Result |
| ---------------- | --------------------------------------------------- |
| color: "auto" | (default) ON when stdout is a TTY; respects NO_COLOR (off) and FORCE_COLOR (on) env vars. |
| color: "always"| Force ANSI on (useful for CI logs that render color). |
| color: "never" | Strip all ANSI (plain text only). |
| NO_COLOR=1 | Disable color regardless of mode. Honors the no-color.org spec. |
| FORCE_COLOR=1 | Enable color regardless of TTY. |
Install
bun add @ai-setting/roy-plugin-tool-debug
# or
npm install @ai-setting/roy-plugin-tool-debugUsage
import { createToolDebugPlugin } from "@ai-setting/roy-plugin-tool-debug";
const plugin = createToolDebugPlugin({
truncateAt: 500, // max chars for args preview
resultTruncateAt: 300, // max chars for tool:after.execute result output
bannerOnInit: true, // print a one-line start banner on init()
color: "auto", // "auto" | "always" | "never"
fieldSeparator: " │ ", // field separator inside the compact line
keyColor: "cyan", // any AnsiColor: cyan | yellow | green | red | magenta | gray | white | reset
showProjectPath: true, // resolve and show task project_path (default: true)
projectPathTtlMs: 60_000, // cache TTL for project_path lookups
indentWidth: 100, // wrap width for indented Args/Res/Path lines
});
await plugin.init(env); // env is the host's BasePluginEnv (provides registerHook)Config
All fields are optional. Defaults match the values in plugin.json.
| Field | Type | Default | Description |
| ------------------ | -------------------------- | ------------- | -------------------------------------------------------- |
| truncateAt | number | 500 | Max chars for args / llmOutput preview. |
| resultTruncateAt | number | 300 | Override for tool:after.execute result output. |
| bannerOnInit | boolean | true | Print a one-line start banner on init(). |
| color | "auto" | "always" | "never" | "auto" | Color mode. Honors NO_COLOR / FORCE_COLOR / TTY. |
| fieldSeparator | string | " │ " | Separator between fields in the compact line. |
| keyColor | AnsiColor | "cyan" | Default ANSI color for keys. |
| showProjectPath | boolean | true | Whether to resolve and display the task's project_path. |
| projectPathTtlMs | number | 60000 | Cache TTL for project_path lookups (ms). 0 disables cache. |
| indentWidth | number | 100 | Wrap width for indented payload lines (Args / Res / Path). |
Project path resolution
When a currentTaskId is present in the EnvContext, the plugin invokes
roy-agent tasks get <id> --jsonto read the related task's project_path and renders it as a Path:
field on every debug line. The resolver is cached (default TTL 60s)
so it adds at most one CLI spawn per task per minute — negligible on
real workloads.
The resolver gracefully degrades:
- Task id missing → no
Path:line. - CLI binary missing → no
Path:line (no exception thrown). - Task id unknown / CLI exits non-zero → no
Path:line. - JSON missing
project_path→ noPath:line.
You can disable the feature entirely with showProjectPath: false.
Why
When debugging hook semantics, the four things you usually want to know are:
- What is in the EnvContext? —
currentTaskId,iteration,sessionId,traceId,requestId,sourceId,userId,replyChannel. - What payload did the hook receive? —
args,result,messages,llmOutput. - Which project does this hook belong to? —
project_pathfrom the related task. - Which path did the value come from? — auto-injected by HookManager, or looked up via AsyncLocalStorage.
This plugin answers all four in one canonical place.
Compatibility
@ai-setting/roy-agent-coreis an optional peer dependency. When the host ships it, the plugin reads the auto-injected EnvContext fromctx.metadata.envContext. When it doesn't, the plugin falls back togetEnvContext()from AsyncLocalStorage. Both code paths are tested.roy-agentCLI is only invoked for project_path lookup. If the binary is not on PATH, that lookup degrades to "no path" — never a hard failure.
License
MIT
