feat(cline): add native Cline plugin#110
Conversation
Adds a Cline integration parallel to the existing Hermes and Claude Code
adapters, so AgentGuard can sit in front of Cline's tool-execution loop and
block risky shell, file, and network actions before they run.
Surfaces:
- ClineAdapter (src/adapters/cline.ts) maps Cline tool names (run_commands,
execute_command, write_to_file/write_file/replace_in_file/editor,
read_files/read_file, web_fetch, browser_action, web_search) to the shared
HookAdapter contract used by the engine.
- Runtime plugin (plugins/cline/) implements @cline/core's AgentPlugin shape
with hooks.beforeTool/afterTool — install via `cline plugin install
~/.cline/plugins/agentguard`.
- File-hook bridge (skills/agentguard/scripts/cline-hook.js) handles Cline's
stdin/stdout PreToolUse/PostToolUse protocol, mapping deny -> cancel and
ask -> review (Cline supports both natively, no lossy translation).
- `agentguard init --agent cline` copies the skill, drops PreToolUse.js /
PostToolUse.js shims into .cline/hooks/ (chmod +x), and stages the runtime
plugin in .cline/plugins/agentguard/.
Wiring:
- 'cline' added to AgentInstaller, RuntimeAgentHost, AgentGuardAgentHost,
SUPPORTED_AGENT_INSTALLERS, and AUTO_AGENT_DETECTION.
- resolveCronAgentHost narrowed to the cron-capable subset so the new host
doesn't leak into cron backends that don't target it.
Fail policy mirrors Hermes: out-of-scope tools always pass through; for
in-scope tools the file-hook fails closed by default (override:
AGENTGUARD_CLINE_FAIL_OPEN=1) and the runtime plugin fails open by default
(override: AGENTGUARD_CLINE_FAIL_CLOSED=1) since users may not always have
the engine installed alongside Cline.
Existing 415 tests pass. End-to-end smoke:
- run_commands rm -rf / -> {"cancel":true,"errorMessage":"..."}
- run_commands echo hello -> {}
- write_to_file .env -> {"review":true,"context":"..."}
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AgentGuard PR ReviewI found a few concrete regressions and security gaps in the Cline integration.
|
Adds 32 tests bringing the suite from 415 to 447, all passing.
src/tests/adapter.test.ts — ClineAdapter unit tests:
- parseInput covers both the file-hook shape (tool_call.{name,input}) and
the runtime-plugin shape (toolCall.toolName), tool_result -> 'post',
workspaceRoots[0] -> cwd, preToolUse.parameters fallback, and empty input
- mapToolToActionType covers every entry in TOOL_ACTION_MAP plus null for
out-of-scope tools (use_mcp_tool, ask_followup_question, '')
- buildEnvelope covers run_commands string + commands[] array (joined),
write_to_file, read_files with files: [{path}], web_fetch, web_search,
and null return for unmapped tools
- inferInitiatingSkill: null by default, honors initiating_skill metadata
src/tests/smoke.test.ts — cline-hook.js subprocess E2E:
- allow echo hello
- block rm -rf / via {cancel, errorMessage}
- map AgentGuard 'ask' to Cline {review, context} for .env writes
- accept runtime-plugin toolCall shape
- flatten run_commands.commands array before evaluating (catches the
joined `echo ok && rm -rf /` case)
- post-tool events audit-only -> {}
- out-of-scope tools (use_mcp_tool) passthrough -> {}
- missing required field (run_commands without command) -> block
- AGENTGUARD_TEST_FORCE_ENGINE_LOAD_FAILURE=1 fails closed by default
- AGENTGUARD_CLINE_FAIL_OPEN=1 inverts to allow on engine failure
- invalid stdin exits promptly with cancel decision
Bug fix surfaced by the commands-array test: cline-hook.js's
normalizeForRuntime now flattens `tool_input.commands: string[]` into a
single `command` field before handing off to protectAction. The runtime
engine's input picker only looks at `command`/`cmd`, so without the
flatten the array passed through unevaluated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five issues raised in review; this commit fixes four and verifies the fifth
was a false positive.
1. HIGH — installer no longer writes a spawnSync shim.
PreToolUse.js and PostToolUse.js are now cline-hook.js copied verbatim
(same script, branches internally on hookName). One fewer process, no
stdin-inheritance question. Installer test asserts byte-identity with
the bundled source and that the file does not contain `spawnSync`.
2. HIGH — packaging was already correct.
`plugins` is in package.json "files", so plugins/cline ships with the
npm package. Added a comment in installers.ts noting this so the path
contract is explicit.
3. MEDIUM — robust path resolution in cline-hook.js.
Replaced `import.meta.url.replace('file://', '')` (broken on Windows,
wrong join semantics) with `fileURLToPath() + dirname()`, and only
attempt the bundled engine when the resolved path actually exists.
Falls through to the `@goplus/agentguard` bare specifier otherwise.
4. MEDIUM — multi-file read_files prefers the worst-case path.
When read_files lists multiple files, the adapter now scans them with
isSensitivePath and picks any sensitive target as the envelope's
primary path (with the full list in `paths` and the flagged target in
`sensitive_path`). A two-file [benign, .env] read now blocks instead
of slipping through on the first benign entry. Two adapter tests lock
the contract in (sensitive-wins + paths preserved for benign lists).
5. MEDIUM — both Cline surfaces now default fail-closed under one env var.
Runtime plugin previously defaulted fail-open via
AGENTGUARD_CLINE_FAIL_CLOSED. Now both the file hook and the runtime
plugin default to fail-closed and read AGENTGUARD_CLINE_FAIL_OPEN=1 to
invert. A security gate that quietly turns off isn't a security gate;
this matches the Hermes plugin's posture and removes the cross-surface
divergence the reviewer flagged.
Tests: 451/451 pass (was 447; +4 new — multi-file sensitive/benign, hook
copied-not-shimmed, hook executable bit).
End-to-end verified: `agentguard init --agent cline --force` against a
fresh temp dir installs PreToolUse.js byte-identical to the source, and
invoking it as Cline does correctly fails closed when the engine is
unreachable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the review. All five items addressed in
Tests: 451/451 pass (was 447 before the review fixes; +4 new — multi-file sensitive picking, multi-file benign passthrough, hook-copied-not-shimmed byte-identity, hook executable bit on POSIX). End-to-end smoke against a fresh temp install dir:
Happy to split into smaller commits or rebase if that's easier to review. |
Summary
Adds a Cline integration parallel to the existing Hermes and Claude Code adapters, so AgentGuard can sit in front of Cline's tool-execution loop and block risky shell, file, and network actions before they run.
Cline ships both a typed runtime plugin SDK (
@cline/core'sAgentPluginwithhooks.beforeTool/afterTool) and a stdin/stdout file-hook system (.cline/hooks/PreToolUse.*,PostToolUse.*). This PR wires AgentGuard into both, reusing the existingHookAdapter/evaluateHookengine — detection logic stays in one place.What changed
src/adapters/cline.ts—ClineAdaptermapping Cline tool names (run_commands,execute_command,write_to_file/write_file/replace_in_file/editor,read_files/read_file,web_fetch,browser_action,web_search) to the shared adapter contract.plugins/cline/— Native Cline runtime plugin (@cline/coreAgentPluginshape) withbeforeTool/afterTool.deny→{skip, reason},ask→{review, reason}— Cline supports both natively, no lossy translation.skills/agentguard/scripts/cline-hook.js— File-hook bridge handling Cline'stool_call/tool_resultJSON protocol; delegates to the unifiedprotectActionAPI withagentHost: 'cline'and falls back toClineAdapter+evaluateHookon older engine builds.agentguard init --agent cline— Copies the skill, drops executablePreToolUse.js/PostToolUse.jsshims into.cline/hooks/, and stages the runtime plugin in.cline/plugins/agentguard/.'cline'throughAgentInstaller,RuntimeAgentHost,AgentGuardAgentHost,SUPPORTED_AGENT_INSTALLERS, andAUTO_AGENT_DETECTION. NarrowedresolveCronAgentHostto the cron-capable subset so the new host doesn't leak into cron backends that don't target it.Install / use
Or use only the file hooks —
agentguard init --agent clinewrites them by default.Fail policy
Mirrors Hermes:
AGENTGUARD_CLINE_FAIL_OPEN=1to invert).AGENTGUARD_CLINE_FAIL_CLOSED=1to invert) since users may not always have the engine installed alongside Cline.Test plan
npm run build— cleannpm test— all 415 existing tests pass, no regressionsrun_commands rm -rf /→{"cancel":true,"errorMessage":"..."}run_commands echo hello→{}write_to_file .env→{"review":true,"context":"..."}agentguard init --agent clinewrites~/.cline/skills/agentguard, executablePreToolUse.js/PostToolUse.jsshims, and~/.cline/plugins/agentguard/{index.ts,package.json,README.md,cline.plugin.json}plugins/cline/tests/— happy to follow up if you want me to add them in this PR.References:
env-blocker.tsfor thebeforeTool→{skip, reason}pattern this mirrors)🤖 Generated with Claude Code