P1 (part 1): move skilltools core -> tool/ (clean, verbatim)
executus CI / test (push) Successful in 36s

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>
This commit is contained in:
2026-06-26 19:31:47 -04:00
parent d2c18ad5bb
commit dc28b63ad8
24 changed files with 3461 additions and 1 deletions
+99
View File
@@ -0,0 +1,99 @@
package tool
import (
"context"
llm "gitea.stevedudenhoeffer.com/steve/majordomo/llm"
)
// AgentSession is the live-run handle a SessionToolFactory receives.
// It is implemented by the executors (agentexec / skillexec / scaddy's
// adapter) on top of majordomo's agent loop and exposes the one mid-run
// mutation session tools need: feeding content back into the running
// conversation.
//
// Why an interface (vs the concrete agent type): legacy agentkit handed the
// factory a *agentkit.Agent so tools could call agent.AttachImages.
// majordomo's *agent.Agent is deliberately immutable mid-run — message
// injection happens through the run-scoped steer mailbox
// (agent.WithSteer). A narrow interface lets each executor implement
// AttachImages over its own steer queue without skilltools importing
// the agent package, and keeps session tools testable with a two-line
// fake.
type AgentSession interface {
// AttachImages queues a user-role message (text plus image parts)
// for injection into the conversation before the agent's next
// step. Used by tools that produce visual feedback the model must
// see on its following turn (e.g. scaddy's rendered OpenSCAD
// previews). Safe to call from inside a tool handler; the message
// lands after the current step's tool results.
AttachImages(text string, images ...llm.ImagePart)
}
// SessionToolFactory builds per-session tools that close over the live
// agent session. Called by the executor after the agent is constructed
// but before it runs. See Invocation.SessionToolFactory for the
// rationale (static ExtraTools cannot reach the running agent).
type SessionToolFactory func(session AgentSession) SessionTools
// SessionTools carries per-session tools plus optional post-run and
// teardown hooks. It replaces legacy agentkit's SessionTools with the same
// three-field shape, re-based on majordomo types.
type SessionTools struct {
// Tools to add to the agent's toolbox for this run only.
Tools []llm.Tool
// PostRun, if set, is called after the agent run completes
// (successfully or not). It receives the full run transcript (the
// agent Result's Messages — also populated on partial results from
// agent.ErrMaxSteps / agent.ErrToolLoop), the agent's text output,
// and the run error, so the hook can decide whether to attempt
// artifact production on partial success (e.g. scaddy ships the
// latest SCAD even when the step budget ran out). The returned
// PostRunResult is attached to the executor's run result. Errors
// inside PostRun must be handled by the hook itself — the executor
// logs a nil return but never fails the run over it; the agent's
// output is the source of truth.
//
// Why a transcript slice (vs the live agent): the consumers only
// ever read the message history (thought-chain transcripts); the
// majordomo agent exposes that on Result, not on the Agent.
PostRun func(ctx context.Context, transcript []llm.Message, output string, runErr error) *PostRunResult
// Cleanup, if set, is deferred by the executor immediately after
// the factory returns. Called even if the run fails or PostRun
// panics. Use for temp directory removal, closing file handles,
// etc.
Cleanup func()
}
// PostRunResult carries artifacts produced by the PostRun hook.
// Attached to the executor's run result so callers (Discord command
// handlers, HTTP API responses) can inspect and deliver the artifacts.
//
// Why a separate struct (vs returning artifacts inline): post-
// processing may produce multiple typed artifacts (PNGs, STLs, SCAD
// source) that the delivery layer classifies and routes differently.
// A flat []Artifact + arbitrary Metadata covers the known use cases
// without over-specifying the shape.
type PostRunResult struct {
// Artifacts are files produced during post-processing
// (e.g., rendered PNGs, STL files, SCAD source).
Artifacts []Artifact
// Metadata is arbitrary key-value data the delivery layer can
// use for formatting (e.g., iteration count, model name, notes).
Metadata map[string]any
}
// Artifact is a named binary blob produced by post-run processing.
//
// Why: the delivery layer needs name + type + bytes to classify
// each artifact (PNG → embed image, STL → filetransfer upload,
// SCAD → paste upload). A struct with these three fields is the
// minimal viable description.
type Artifact struct {
Name string // e.g., "model.stl", "preview_iso.png"
MimeType string // e.g., "model/stl", "image/png"
Data []byte
}