mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
* feat(plugins): add remote MCP runtime Co-authored-by: multica-agent <[email protected]> * chore(plugins): add private Exa example Co-authored-by: multica-agent <[email protected]> * docs(plugins): remove private plugin guide Co-authored-by: multica-agent <[email protected]> * fix(plugins): stabilize remote MCP tool digests Co-authored-by: multica-agent <[email protected]> * fix(plugins): preserve remote MCP notification status Co-authored-by: multica-agent <[email protected]> * fix(plugins): make Exa example runnable end to end Co-authored-by: multica-agent <[email protected]> * fix(codex): record MCP tool calls in agent log Co-authored-by: multica-agent <[email protected]> * chore(plugins): add community skills example Co-authored-by: multica-agent <[email protected]> * feat(plugins): support multi-file skill bundles Co-authored-by: multica-agent <[email protected]> * feat(plugins): add Mobbin MCP example Co-authored-by: multica-agent <[email protected]> * chore(plugins): bundle full Matt Pocock skill set Co-authored-by: multica-agent <[email protected]> * feat(plugins): add remote MCP OAuth connections Co-authored-by: multica-agent <[email protected]> * fix(plugins): gate enable on remote MCP readiness Co-authored-by: multica-agent <[email protected]> * fix(plugin): authenticate remote mcp credential broker Co-authored-by: multica-agent <[email protected]> * fix(plugins): harden remote mcp oauth flow Co-authored-by: multica-agent <[email protected]> * refactor(plugins): use a single feature flag Co-authored-by: multica-agent <[email protected]> * fix(plugins): clean up remote MCP OAuth state Co-authored-by: multica-agent <[email protected]> * chore(plugins): remove Matt Pocock example Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]>
162 lines
7.1 KiB
Go
162 lines
7.1 KiB
Go
// Package pluginruntime contains the versioned, JSON-serializable data that
|
|
// connects a compiled plugin snapshot to a task execution manifest.
|
|
package pluginruntime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/multica-ai/multica/server/pkg/plugincontract"
|
|
)
|
|
|
|
const (
|
|
CompilerVersion = "plugin-compiler-v2"
|
|
ComposerVersion = "plugin-composer-v1"
|
|
SchemaVersion = 1
|
|
)
|
|
|
|
// CompiledEntry is one contribution made available at a workspace or agent
|
|
// scope. Execution manifests copy these entries verbatim at enqueue time.
|
|
type CompiledEntry struct {
|
|
PluginID string `json:"plugin_id"`
|
|
PluginKey string `json:"plugin_key"`
|
|
InstallationID string `json:"installation_id"`
|
|
ReleaseID string `json:"release_id"`
|
|
ReleaseVersion string `json:"release_version"`
|
|
ContributionID string `json:"contribution_id"`
|
|
ContributionKey string `json:"contribution_key"`
|
|
ContributionType string `json:"contribution_type"`
|
|
DisplayName string `json:"display_name"`
|
|
Description string `json:"description,omitempty"`
|
|
SourceKind string `json:"source_kind"`
|
|
ArtifactFileID string `json:"artifact_file_id"`
|
|
ArtifactRef string `json:"artifact_ref"`
|
|
ArtifactDigest string `json:"artifact_digest"`
|
|
EntryPath string `json:"entry_path"`
|
|
EntryDigest string `json:"entry_digest"`
|
|
RequiredDaemonFeatures []string `json:"required_daemon_features"`
|
|
Ordinal int32 `json:"ordinal"`
|
|
ScopeType string `json:"scope_type"`
|
|
ScopeID string `json:"scope_id"`
|
|
ExcludedAgentIDs []string `json:"excluded_agent_ids,omitempty"`
|
|
SkillBundleHash string `json:"skill_bundle_hash,omitempty"`
|
|
SkillSizeBytes int64 `json:"skill_size_bytes,omitempty"`
|
|
SkillFileCount int `json:"skill_file_count,omitempty"`
|
|
SkillFiles []SkillFile `json:"skill_files,omitempty"`
|
|
ConfigID string `json:"config_id,omitempty"`
|
|
ConfigRevision int64 `json:"config_revision,omitempty"`
|
|
Endpoint string `json:"endpoint,omitempty"`
|
|
PublicConfig json.RawMessage `json:"public_config,omitempty"`
|
|
SecretRef string `json:"secret_ref,omitempty"`
|
|
AuthType string `json:"auth_type,omitempty"`
|
|
AuthHeader string `json:"auth_header,omitempty"`
|
|
ApprovedTools []RemoteMCPTool `json:"approved_tools,omitempty"`
|
|
ToolSchemaDigest string `json:"tool_schema_digest,omitempty"`
|
|
FailurePolicy string `json:"failure_policy,omitempty"`
|
|
Transport string `json:"transport,omitempty"`
|
|
ProtocolVersions []string `json:"protocol_versions,omitempty"`
|
|
EndpointAllowedHosts []string `json:"endpoint_allowed_hosts,omitempty"`
|
|
}
|
|
|
|
// SkillFile pins one companion file from an agent.skill.v1 contribution.
|
|
// Path is relative to the contribution's skills/<key>/ root; SKILL.md remains
|
|
// pinned separately by ArtifactFileID, EntryPath, and EntryDigest.
|
|
type SkillFile struct {
|
|
ArtifactFileID string `json:"artifact_file_id"`
|
|
Path string `json:"path"`
|
|
Digest string `json:"digest"`
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
}
|
|
|
|
type RemoteMCPTool struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description,omitempty"`
|
|
InputSchema json.RawMessage `json:"input_schema"`
|
|
SchemaDigest string `json:"schema_digest"`
|
|
Risk string `json:"risk,omitempty"`
|
|
}
|
|
|
|
// RemoteMCPConnection is claim-time, task-scoped connection metadata resolved
|
|
// from a pinned entry. Credentials are intentionally absent from this wire
|
|
// type and are resolved just-in-time by the daemon broker.
|
|
type RemoteMCPConnection struct {
|
|
InstallationID string `json:"installation_id"`
|
|
ContributionID string `json:"contribution_id"`
|
|
ContributionKey string `json:"contribution_key"`
|
|
ConfigID string `json:"config_id"`
|
|
ConfigRevision int64 `json:"config_revision"`
|
|
Endpoint string `json:"endpoint"`
|
|
PublicConfig json.RawMessage `json:"public_config,omitempty"`
|
|
Transport string `json:"transport"`
|
|
ProtocolVersions []string `json:"protocol_versions"`
|
|
EndpointAllowedHosts []string `json:"endpoint_allowed_hosts,omitempty"`
|
|
CredentialHeader string `json:"credential_header,omitempty"`
|
|
ApprovedTools []RemoteMCPTool `json:"approved_tools"`
|
|
ToolSchemaDigest string `json:"tool_schema_digest"`
|
|
FailurePolicy string `json:"failure_policy"`
|
|
}
|
|
|
|
type SnapshotPayload struct {
|
|
SourceGenerations map[string]int64 `json:"source_generations"`
|
|
CompilerVersion string `json:"compiler_version"`
|
|
SchemaVersion int `json:"schema_version"`
|
|
Entries []CompiledEntry `json:"entries"`
|
|
}
|
|
|
|
func Canonicalize(entries []CompiledEntry) {
|
|
for i := range entries {
|
|
sort.Strings(entries[i].ExcludedAgentIDs)
|
|
sort.Strings(entries[i].RequiredDaemonFeatures)
|
|
sort.Strings(entries[i].ProtocolVersions)
|
|
sort.Strings(entries[i].EndpointAllowedHosts)
|
|
sort.Slice(entries[i].SkillFiles, func(left, right int) bool {
|
|
if entries[i].SkillFiles[left].Path != entries[i].SkillFiles[right].Path {
|
|
return entries[i].SkillFiles[left].Path < entries[i].SkillFiles[right].Path
|
|
}
|
|
return entries[i].SkillFiles[left].ArtifactFileID < entries[i].SkillFiles[right].ArtifactFileID
|
|
})
|
|
sort.Slice(entries[i].ApprovedTools, func(left, right int) bool {
|
|
return entries[i].ApprovedTools[left].Name < entries[i].ApprovedTools[right].Name
|
|
})
|
|
}
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
left, right := entries[i], entries[j]
|
|
if left.Ordinal != right.Ordinal {
|
|
return left.Ordinal < right.Ordinal
|
|
}
|
|
if left.PluginKey != right.PluginKey {
|
|
return left.PluginKey < right.PluginKey
|
|
}
|
|
if left.ContributionKey != right.ContributionKey {
|
|
return left.ContributionKey < right.ContributionKey
|
|
}
|
|
if left.ScopeType != right.ScopeType {
|
|
return left.ScopeType < right.ScopeType
|
|
}
|
|
return left.ScopeID < right.ScopeID
|
|
})
|
|
}
|
|
|
|
func SnapshotDigest(sourceGenerations map[string]int64, entries []CompiledEntry) (string, error) {
|
|
Canonicalize(entries)
|
|
content, err := json.Marshal(SnapshotPayload{
|
|
SourceGenerations: sourceGenerations,
|
|
CompilerVersion: CompilerVersion,
|
|
SchemaVersion: SchemaVersion,
|
|
Entries: entries,
|
|
})
|
|
if err != nil {
|
|
return "", fmt.Errorf("marshal plugin snapshot payload: %w", err)
|
|
}
|
|
return plugincontract.DigestBytes(content), nil
|
|
}
|
|
|
|
func ParseEntries(raw []byte) ([]CompiledEntry, error) {
|
|
var entries []CompiledEntry
|
|
if err := json.Unmarshal(raw, &entries); err != nil {
|
|
return nil, fmt.Errorf("decode plugin compiled entries: %w", err)
|
|
}
|
|
return entries, nil
|
|
}
|