Files
multica/server/pkg/pluginruntime/runtime.go
T
d467cc9069 MUL-6075: complete Plugin V1 execution chain (#6853)
* feat(server): add Plugin V1 lifecycle foundation

Co-authored-by: multica-agent <[email protected]>

* feat(server): complete Plugin V1 execution chain

Co-authored-by: multica-agent <[email protected]>

* feat(web): add temporary plugin settings demo

Co-authored-by: multica-agent <[email protected]>

* refactor(plugin): keep foundation free of demo content

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Lambda <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-13 02:29:49 +08:00

99 lines
3.5 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-v1"
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"`
SkillSizeBytes int64 `json:"skill_size_bytes"`
SkillFileCount int `json:"skill_file_count"`
}
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.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
}