Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath, errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim, core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(), core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives. Framework boundary exceptions preserved where stdlib types are required by external interfaces (Gin, net/http, CGo, Wails, bubbletea). Co-Authored-By: Virgil <virgil@lethean.io>
170 lines
4.7 KiB
Go
170 lines
4.7 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package agentic
|
|
|
|
import (
|
|
"context"
|
|
core "dappco.re/go/core"
|
|
coremcp "dappco.re/go/mcp/pkg/mcp"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
// input := agentic.RemoteDispatchInput{Host: "charon", Repo: "go-io", Task: "Run the review queue"}
|
|
type RemoteDispatchInput struct {
|
|
Host string `json:"host"`
|
|
Repo string `json:"repo"`
|
|
Task string `json:"task"`
|
|
Agent string `json:"agent,omitempty"`
|
|
Template string `json:"template,omitempty"`
|
|
Persona string `json:"persona,omitempty"`
|
|
Org string `json:"org,omitempty"`
|
|
Variables map[string]string `json:"variables,omitempty"`
|
|
}
|
|
|
|
// out := agentic.RemoteDispatchOutput{Success: true, Host: "charon", Repo: "go-io", Agent: "claude:opus"}
|
|
type RemoteDispatchOutput struct {
|
|
Success bool `json:"success"`
|
|
Host string `json:"host"`
|
|
Repo string `json:"repo"`
|
|
Agent string `json:"agent"`
|
|
WorkspaceDir string `json:"workspace_dir,omitempty"`
|
|
PID int `json:"pid,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
func (s *PrepSubsystem) registerRemoteDispatchTool(svc *coremcp.Service) {
|
|
coremcp.AddToolRecorded(svc, svc.Server(), "agentic", &mcp.Tool{
|
|
Name: "agentic_dispatch_remote",
|
|
Description: "Dispatch a task to a remote core-agent (e.g. Charon). The remote agent preps a workspace and spawns the task locally on its hardware.",
|
|
}, s.dispatchRemote)
|
|
}
|
|
|
|
func (s *PrepSubsystem) dispatchRemote(ctx context.Context, _ *mcp.CallToolRequest, input RemoteDispatchInput) (*mcp.CallToolResult, RemoteDispatchOutput, error) {
|
|
if input.Host == "" {
|
|
return nil, RemoteDispatchOutput{}, core.E("dispatchRemote", "host is required", nil)
|
|
}
|
|
if input.Repo == "" {
|
|
return nil, RemoteDispatchOutput{}, core.E("dispatchRemote", "repo is required", nil)
|
|
}
|
|
if input.Task == "" {
|
|
return nil, RemoteDispatchOutput{}, core.E("dispatchRemote", "task is required", nil)
|
|
}
|
|
|
|
client := NewRemoteClient(input.Host)
|
|
|
|
callParams := map[string]any{
|
|
"repo": input.Repo,
|
|
"task": input.Task,
|
|
}
|
|
if input.Agent != "" {
|
|
callParams["agent"] = input.Agent
|
|
}
|
|
if input.Template != "" {
|
|
callParams["template"] = input.Template
|
|
}
|
|
if input.Persona != "" {
|
|
callParams["persona"] = input.Persona
|
|
}
|
|
if input.Org != "" {
|
|
callParams["org"] = input.Org
|
|
}
|
|
if len(input.Variables) > 0 {
|
|
callParams["variables"] = input.Variables
|
|
}
|
|
|
|
output := RemoteDispatchOutput{
|
|
Success: true,
|
|
Host: input.Host,
|
|
Repo: input.Repo,
|
|
Agent: input.Agent,
|
|
}
|
|
|
|
sessionID, err := client.Initialize(ctx)
|
|
if err != nil {
|
|
return nil, RemoteDispatchOutput{
|
|
Host: input.Host,
|
|
Error: core.Sprintf("init failed: %v", err),
|
|
}, core.E("dispatchRemote", "MCP initialize failed", err)
|
|
}
|
|
|
|
result, err := client.Call(ctx, sessionID, client.ToolCallBody(1, "agentic_dispatch", callParams))
|
|
if err != nil {
|
|
return nil, RemoteDispatchOutput{
|
|
Host: input.Host,
|
|
Error: core.Sprintf("call failed: %v", err),
|
|
}, core.E("dispatchRemote", "tool call failed", err)
|
|
}
|
|
|
|
var rpcResponse struct {
|
|
Result struct {
|
|
Content []struct {
|
|
Text string `json:"text"`
|
|
} `json:"content"`
|
|
} `json:"result"`
|
|
Error *struct {
|
|
Message string `json:"message"`
|
|
} `json:"error"`
|
|
}
|
|
if r := core.JSONUnmarshal(result, &rpcResponse); r.OK {
|
|
if rpcResponse.Error != nil {
|
|
output.Success = false
|
|
output.Error = rpcResponse.Error.Message
|
|
} else if len(rpcResponse.Result.Content) > 0 {
|
|
var dispatchOut DispatchOutput
|
|
if r := core.JSONUnmarshalString(rpcResponse.Result.Content[0].Text, &dispatchOut); r.OK {
|
|
output.Success = dispatchOut.Success
|
|
output.WorkspaceDir = dispatchOut.WorkspaceDir
|
|
output.PID = dispatchOut.PID
|
|
output.Agent = dispatchOut.Agent
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, output, nil
|
|
}
|
|
|
|
// addr := resolveHost("charon") // "10.69.69.165:9101"
|
|
func resolveHost(host string) string {
|
|
host = core.Trim(host)
|
|
aliases := map[string]string{
|
|
"charon": "10.69.69.165:9101",
|
|
"cladius": "127.0.0.1:9101",
|
|
"local": "127.0.0.1:9101",
|
|
}
|
|
|
|
if addr, ok := aliases[core.Lower(host)]; ok {
|
|
return addr
|
|
}
|
|
|
|
if !core.Contains(host, ":") {
|
|
return core.Concat(host, ":9101")
|
|
}
|
|
|
|
return host
|
|
}
|
|
|
|
// token := remoteToken("charon")
|
|
func remoteToken(host string) string {
|
|
host = core.Trim(host)
|
|
envKey := core.Sprintf("AGENT_TOKEN_%s", core.Upper(host))
|
|
if token := core.Env(envKey); token != "" {
|
|
return token
|
|
}
|
|
|
|
if token := core.Env("MCP_AUTH_TOKEN"); token != "" {
|
|
return token
|
|
}
|
|
|
|
home := HomeDir()
|
|
tokenFiles := []string{
|
|
core.Sprintf("%s/.core/tokens/%s.token", home, core.Lower(host)),
|
|
core.Sprintf("%s/.core/agent-token", home),
|
|
}
|
|
for _, f := range tokenFiles {
|
|
if r := fs.Read(f); r.OK {
|
|
return core.Trim(r.Value.(string))
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|