Files
executus/tool/helpers.go
T
steve dc28b63ad8
executus CI / test (push) Successful in 36s
P1 (part 1): move skilltools core -> tool/ (clean, verbatim)
The tool registry core (registry, permission model, Invocation, gated-tool
wrapper, ssrf guard, hmac, encryption, argcoerce, helpers, rootrun,
session_tools, webhook_rate_limit) had zero mort coupling — it imports only
majordomo/llm + x/crypto/hkdf — so it moves verbatim with a package rename
(skilltools -> tool). All same-package tests came along and pass; the SSRF,
gated-wrapper, encryption and output-pattern invariants are re-anchored here.

majordomo re-enters the module graph (now pinned to the latest, incl. the
front-loaded-output fix). model/ + llmmeta + structured follow next.

Docs: CLAUDE.md now requires README/examples to stay in sync with changes in
the same commit; CI skips docs/example-only pushes via paths-ignore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:31:47 -04:00

45 lines
1.4 KiB
Go

package tool
import "fmt"
// CheckGate returns an error if the invocation context's SkillName does
// not match the tool's gate. Tools should call this at the top of their
// handler when their Permission has a non-empty SkillNameGate.
//
// Why: the gate is enforced per-call (not per-build) because the same
// Tool may be referenced by multiple skills, only one of which is
// gate-eligible. Build cannot know in advance which skill will call it
// — that's per-Invocation.
//
// What: returns nil if no gate, or the names match. Returns an error
// suitable for surfacing to the LLM as the tool's failure result.
func CheckGate(inv Invocation) error {
if inv.gate == "" {
return nil
}
if inv.currentSkill == inv.gate {
return nil
}
return fmt.Errorf("tool %q is restricted to the %q skill", inv.toolName, inv.gate)
}
// EmitAudit forwards a tool's call+result to the audit hook, if one is
// wired. Tools should call this once per Execute, after the underlying
// work has completed (regardless of error). Pass the original args
// JSON, the result string, and any error.
//
// Why: keeping the audit emission inside the tool ensures the captured
// args are exactly what the tool ran with (after coercion / defaults),
// not the raw LLM JSON which can drift.
func EmitAudit(inv Invocation, args, result string, err error) {
if inv.audit == nil {
return
}
inv.audit(AuditCall{
Tool: inv.toolName,
Args: args,
Result: result,
Err: err,
})
}