Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.worktrees/
data/
dist/
plugin.json.bak
22 changes: 17 additions & 5 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
version: 2
project_name: workflow-plugin-agent

before:
hooks:
- go mod tidy
- "sed -i.bak 's/\"version\": \".*\"/\"version\": \"{{ .Version }}\"/' plugin.json && rm -f plugin.json.bak"
# Rewrite plugin.json: bump version sentinel + refresh download URLs to the
# release being cut. Mirrors workflow-plugin-infra/.goreleaser.yml before-hook.
- "sed -i.bak -e 's/\"version\": \".*\"/\"version\": \"{{ .Version }}\"/' -e 's#/releases/download/v[^/]*/#/releases/download/v{{ .Version }}/#g' -e 's/workflow-plugin-agent_[0-9][0-9A-Za-z.~-]*/workflow-plugin-agent_{{ .Version }}/g' plugin.json && rm -f plugin.json.bak"

builds:
- id: workflow-plugin-agent
Expand All @@ -12,23 +15,29 @@ builds:
env:
- CGO_ENABLED=0
- GOPRIVATE=github.com/GoCodeAlone/*
ldflags:
- "-s -w -X github.com/GoCodeAlone/workflow-plugin-agent.Version={{ .Version }}"
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
ldflags:
- -s -w -X github.com/GoCodeAlone/workflow-plugin-agent.Version={{.Version}}

archives:
- formats: [tar.gz]
name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
- id: archive
formats:
- tar.gz
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
files:
- plugin.json
- plugin.contracts.json
- LICENSE

checksum:
name_template: "checksums.txt"

changelog:
sort: asc
filters:
Expand All @@ -39,3 +48,6 @@ changelog:

release:
draft: true
github:
owner: GoCodeAlone
name: workflow-plugin-agent
12 changes: 11 additions & 1 deletion cmd/workflow-plugin-agent/main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Command workflow-plugin-agent hosts the agent plugin over the workflow
// external-plugin protocol.
//
// ServePluginFull is used (rather than the bare sdk.Serve) so the binary can
// later gain CLI and/or build-hook dispatch without a re-architecture, mirroring
// workflow-plugin-infra/cmd/workflow-plugin-infra. The agent plugin exposes no
// CLI or hooks yet, so the cli and hooks arguments are nil.
package main

import (
Expand All @@ -8,5 +13,10 @@ import (
)

func main() {
sdk.Serve(agent.New(), sdk.WithBuildVersion(sdk.ResolveBuildVersion(agent.Version)))
sdk.ServePluginFull(
agent.New(),
nil, // no CLI provider yet
nil, // no hook handler yet
sdk.WithBuildVersion(sdk.ResolveBuildVersion(agent.Version)),
)
}
16 changes: 16 additions & 0 deletions plugin.contracts.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@
"mode": "strict",
"config": "workflow.plugins.agent.v1.ProviderConfig"
},
{
"kind": "step",
"type": "step.agent_execute",
"mode": "strict",
"config": "workflow.plugins.agent.v1.AgentExecuteConfig",
"input": "workflow.plugins.agent.v1.AgentExecuteInput",
"output": "workflow.plugins.agent.v1.AgentExecuteOutput"
},
{
"kind": "step",
"type": "step.provider_test",
"mode": "strict",
"config": "workflow.plugins.agent.v1.ProviderTestConfig",
"input": "workflow.plugins.agent.v1.ProviderTestInput",
"output": "workflow.plugins.agent.v1.ProviderTestOutput"
},
{
"kind": "step",
"type": "step.provider_models",
Expand Down
38 changes: 37 additions & 1 deletion plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,52 @@
"description": "AI agent primitives for workflow apps",
"author": "GoCodeAlone",
"license": "Proprietary",
"type": "builtin",
"type": "external",
"tier": "core",
"minEngineVersion": "0.53.0",
"private": true,
"homepage": "https://github.com/GoCodeAlone/workflow-plugin-agent",
"repository": "https://github.com/GoCodeAlone/workflow-plugin-agent",
"downloads": [
{
"os": "linux",
"arch": "amd64",
"url": "https://github.com/GoCodeAlone/workflow-plugin-agent/releases/download/v0.0.0/workflow-plugin-agent_0.0.0_linux_amd64.tar.gz"
},
{
"os": "linux",
"arch": "arm64",
"url": "https://github.com/GoCodeAlone/workflow-plugin-agent/releases/download/v0.0.0/workflow-plugin-agent_0.0.0_linux_arm64.tar.gz"
},
{
"os": "darwin",
"arch": "amd64",
"url": "https://github.com/GoCodeAlone/workflow-plugin-agent/releases/download/v0.0.0/workflow-plugin-agent_0.0.0_darwin_amd64.tar.gz"
},
{
"os": "darwin",
"arch": "arm64",
"url": "https://github.com/GoCodeAlone/workflow-plugin-agent/releases/download/v0.0.0/workflow-plugin-agent_0.0.0_darwin_arm64.tar.gz"
},
{
"os": "windows",
"arch": "amd64",
"url": "https://github.com/GoCodeAlone/workflow-plugin-agent/releases/download/v0.0.0/workflow-plugin-agent_0.0.0_windows_amd64.tar.gz"
},
{
"os": "windows",
"arch": "arm64",
"url": "https://github.com/GoCodeAlone/workflow-plugin-agent/releases/download/v0.0.0/workflow-plugin-agent_0.0.0_windows_arm64.tar.gz"
}
],
"capabilities": {
"configProvider": false,
"moduleTypes": [
"agent.provider"
],
"stepTypes": [
"step.agent_execute",
"step.provider_test",
"step.provider_models",
"step.model_pull"
],
Expand Down
76 changes: 65 additions & 11 deletions plugin_contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package agent
import (
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
"runtime"
Expand Down Expand Up @@ -37,6 +38,18 @@ func TestContractRegistryDeclaresStrictContracts(t *testing.T) {
kind: pb.ContractKind_CONTRACT_KIND_MODULE,
config: "workflow.plugins.agent.v1.ProviderConfig",
},
"step:step.agent_execute": {
kind: pb.ContractKind_CONTRACT_KIND_STEP,
config: "workflow.plugins.agent.v1.AgentExecuteConfig",
input: "workflow.plugins.agent.v1.AgentExecuteInput",
output: "workflow.plugins.agent.v1.AgentExecuteOutput",
},
"step:step.provider_test": {
kind: pb.ContractKind_CONTRACT_KIND_STEP,
config: "workflow.plugins.agent.v1.ProviderTestConfig",
input: "workflow.plugins.agent.v1.ProviderTestInput",
output: "workflow.plugins.agent.v1.ProviderTestOutput",
},
"step:step.provider_models": {
kind: pb.ContractKind_CONTRACT_KIND_STEP,
config: "workflow.plugins.agent.v1.ProviderModelsConfig",
Expand Down Expand Up @@ -79,12 +92,33 @@ func TestContractRegistryDeclaresStrictContracts(t *testing.T) {
}
}

func TestAppBoundStepsAreNotAdvertisedAsStrict(t *testing.T) {
// TestAppBoundStepsAdvertisedStrictButLegacyExecuted documents the hybrid
// contract for step.agent_execute + step.provider_test: they carry strict
// contract DESCRIPTORS (so consumers know their config/input/output shape and
// the strict-contracts coverage gate passes) but remain on the legacy map
// execution path because their implementations lazy-lookup services from
// modular.Application, which the typed gRPC handler signature cannot access.
// CreateTypedStep therefore declines them (ErrTypedContractNotHandled) and the
// engine falls back to the legacy StepProvider path.
func TestAppBoundStepsAdvertisedStrictButLegacyExecuted(t *testing.T) {
registry := New().ContractRegistry()
descriptors := map[string]*pb.ContractDescriptor{}
for _, descriptor := range registry.Contracts {
switch descriptor.StepType {
case "step.agent_execute", "step.provider_test":
t.Fatalf("%s requires application services and must not be advertised as strict", descriptor.StepType)
if descriptor.Kind == pb.ContractKind_CONTRACT_KIND_STEP {
descriptors[descriptor.StepType] = descriptor
}
}

for _, stepType := range []string{"step.agent_execute", "step.provider_test"} {
desc, ok := descriptors[stepType]
if !ok {
t.Fatalf("%s missing strict contract descriptor", stepType)
}
if desc.Mode != pb.ContractMode_CONTRACT_MODE_STRICT_PROTO {
t.Fatalf("%s mode = %s, want strict proto", stepType, desc.Mode)
}
if desc.ConfigMessage == "" || desc.InputMessage == "" || desc.OutputMessage == "" {
t.Fatalf("%s descriptor must carry config/input/output messages", stepType)
}
}

Expand All @@ -97,8 +131,24 @@ func TestAppBoundStepsAreNotAdvertisedAsStrict(t *testing.T) {
if err != nil {
t.Fatalf("pack config: %v", err)
}
if _, err := typedProvider.CreateTypedStep(stepType, "app-bound", config); err == nil {
t.Fatalf("CreateTypedStep(%q) succeeded; app-bound step must use legacy execution", stepType)
_, err = typedProvider.CreateTypedStep(stepType, "app-bound", config)
if err == nil {
t.Fatalf("CreateTypedStep(%q) succeeded; app-bound step must decline typed execution so the engine uses the legacy path", stepType)
}
if !errors.Is(err, sdk.ErrTypedContractNotHandled) {
t.Fatalf("CreateTypedStep(%q) error = %v, want ErrTypedContractNotHandled", stepType, err)
}
}

// TypedStepTypes advertises only the two fully-typed steps; the app-bound
// pair is intentionally absent because they cannot execute via the typed
// handler signature. StepTypes() (legacy) still lists all four.
typedStepTypes := typedProvider.TypedStepTypes()
for _, stepType := range []string{"step.agent_execute", "step.provider_test"} {
for _, got := range typedStepTypes {
if got == stepType {
t.Fatalf("TypedStepTypes must not list app-bound %s (legacy-execution only)", stepType)
}
}
}
}
Expand Down Expand Up @@ -170,11 +220,15 @@ func TestPluginJSONDeclaresStrictContractMetadata(t *testing.T) {
if manifest.StrictContracts.Registry != "plugin.contracts.json" {
t.Fatalf("strictContracts.registry = %q, want plugin.contracts.json", manifest.StrictContracts.Registry)
}
for _, stepType := range manifest.Capabilities.StepTypes {
switch stepType {
case "step.agent_execute", "step.provider_test":
t.Fatalf("plugin.json advertises legacy-only step %q while strict contracts are enabled", stepType)
}
// plugin.json capabilities.stepTypes lists ALL runtime steps. All four
// carry strict contract descriptors in plugin.contracts.json (asserted by
// TestPluginContractsManifestMatchesRegistry); step.agent_execute and
// step.provider_test remain legacy-execution despite having descriptors
// (asserted by TestAppBoundStepsAdvertisedStrictButLegacyExecuted).
// The drift gate that plugin.json stepTypes == runtime StepTypes() lives
// in provider_manifest_test.go (TestPluginJSONStepTypesMatchRuntimeTruth).
if len(manifest.Capabilities.StepTypes) == 0 {
t.Fatal("plugin.json capabilities.stepTypes is empty; expected runtime-truth advertisement")
}
}

Expand Down
Loading
Loading