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
9 changes: 0 additions & 9 deletions apps/daemon/internal/agent/claudesdk/declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,20 @@ const claudeSDKNodeEnv = "OAC_RUNTIME_CLAUDE_SDK_NODE"
// Declaration owns Claude SDK discovery, configuration and execution factories.
var Declaration = agent.Declaration{Info: proto.SupportedAgentKind{Kind: "claude_sdk", Capabilities: proto.AgentKindCapabilities{
SubagentObservations: proto.CapabilityUnsupported,
Streaming: proto.CapabilitySupported,
Usage: proto.CapabilitySupported,
Resume: proto.CapabilitySupported,
NativeSessionRecovery: proto.CapabilityUnsupported,
Steering: proto.CapabilitySupported,
MessageItems: proto.CapabilitySupported,
ToolObservations: proto.CapabilitySupported,
EnvironmentNone: proto.CapabilitySupported,
LocalEnvironment: proto.CapabilityUnsupported,
Preparation: proto.CapabilityUnsupported,
WorkspaceReadPreparation: proto.CapabilityUnsupported,
WorkspaceOutputExport: proto.CapabilityUnsupported,
ProgrammaticToolCallingDisable: proto.CapabilitySupported,
WebSearchControl: proto.CapabilityUnsupported,
ExecutionControls: proto.CapabilitySupported,
TextVerbosity: proto.CapabilityUnsupported,
StructuredOutput: proto.CapabilityUnsupported,
ToolSearch: proto.CapabilityUnsupported,
MessageImages: proto.CapabilityUnsupported,
FunctionResultImages: proto.CapabilityUnsupported,
SubagentControl: proto.CapabilitySupported,
DurableInputReceipts: proto.CapabilitySupported,
DurableTurns: proto.CapabilitySupported,
FunctionTools: proto.CapabilitySupported,
MCPHTTPTools: proto.CapabilityUnsupported,
MCPHTTPRequired: proto.CapabilityUnsupported,
Expand Down
8 changes: 1 addition & 7 deletions apps/daemon/internal/agent/claudesdk/declaration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func TestClaudeSDKFeatureDiscovery(t *testing.T) {

// The declaration must retain the complete baseline capability descriptor.
func TestDeclaredCapabilityBaseline(t *testing.T) {
expected := map[string]bool{"Streaming": true, "Usage": true, "Resume": true, "Steering": true, "MessageItems": true, "ToolObservations": true, "EnvironmentNone": true, "ProgrammaticToolCallingDisable": true, "ExecutionControls": true, "SubagentControl": true, "DurableInputReceipts": true, "DurableTurns": true, "FunctionTools": true}
expected := map[string]bool{"MessageItems": true, "EnvironmentNone": true, "ProgrammaticToolCallingDisable": true, "SubagentControl": true, "FunctionTools": true}
value := reflect.ValueOf(Declaration.Info.Capabilities)
for i := 0; i < value.NumField(); i++ {
name := value.Type().Field(i).Name
Expand Down Expand Up @@ -119,12 +119,6 @@ func TestRuntimeDiscoveryConfigurationAndRegistration(t *testing.T) {
if calls != 1 || runtime.Info.Available != ready || (runtime.Executor != nil) != ready || runtime.Info.Capabilities.LocalEnvironment.IsSupported() {
t.Fatalf("runtime: %+v", runtime)
}
registry := agent.NewRegistry()
registry.Register(Declaration, *runtime)
info := registry.SupportedAgentKinds()[0]
if info.Capabilities.Preparation.IsSupported() != ready {
t.Fatal(info)
}
}
}
}
14 changes: 5 additions & 9 deletions apps/daemon/internal/agent/codex/declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,20 @@ var Declaration = agent.Declaration{Info: proto.SupportedAgentKind{
Kind: "codex",
Capabilities: proto.AgentKindCapabilities{
SubagentObservations: proto.CapabilitySupported,
Streaming: proto.CapabilitySupported,
Usage: proto.CapabilitySupported,
Resume: proto.CapabilitySupported,
NativeSessionRecovery: proto.CapabilityUnsupported,
Steering: proto.CapabilitySupported,
MessageItems: proto.CapabilitySupported,
ToolObservations: proto.CapabilitySupported,
EnvironmentNone: proto.CapabilitySupported,
LocalEnvironment: proto.CapabilityUnsupported,
Preparation: proto.CapabilityUnsupported,
WorkspaceReadPreparation: proto.CapabilityUnsupported,
WorkspaceOutputExport: proto.CapabilityUnsupported,
ProgrammaticToolCallingDisable: proto.CapabilitySupported,
WebSearchControl: proto.CapabilitySupported,
ExecutionControls: proto.CapabilityFromBool(SupportsTextVerbosity),
TextVerbosity: proto.CapabilityFromBool(SupportsTextVerbosity),
StructuredOutput: proto.CapabilityUnsupported,
ToolSearch: proto.CapabilityUnsupported,
MessageImages: proto.CapabilitySupported,
FunctionResultImages: proto.CapabilitySupported,
SubagentControl: proto.CapabilitySupported,
DurableInputReceipts: proto.CapabilitySupported,
DurableTurns: proto.CapabilitySupported,
FunctionTools: proto.CapabilitySupported,
MCPHTTPTools: proto.CapabilitySupported,
MCPHTTPRequired: proto.CapabilityUnsupported,
Expand All @@ -50,6 +41,11 @@ func discover(ctx context.Context, options agent.DiscoveryOptions, info proto.Su
}
func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, info proto.SupportedAgentKind, check func(context.Context, string) (string, error)) *agent.Runtime {
runtime := &agent.Runtime{Info: info}
if !SupportsTextVerbosity {
// Every execution carries a text verbosity, which needs the bounded catalog probe.
fmt.Fprintln(options.Stderr, "oac-daemon: codex unavailable: text verbosity requires Unix process-group cancellation support")
return runtime
}
ctx, cancel := context.WithTimeout(parent, 15*time.Second)
defer cancel()
version, err := check(ctx, "")
Expand Down
3 changes: 1 addition & 2 deletions apps/daemon/internal/agent/codex/declaration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ func TestMCPRequiredDiscoveryRequiresPinnedNative(t *testing.T) {

// The declaration must retain the complete baseline capability descriptor.
func TestDeclaredCapabilityBaseline(t *testing.T) {
expected := map[string]bool{"SubagentObservations": true, "Streaming": true, "Usage": true, "Resume": true, "Steering": true, "MessageItems": true, "ToolObservations": true, "EnvironmentNone": true, "ProgrammaticToolCallingDisable": true, "WebSearchControl": true, "MessageImages": true, "FunctionResultImages": true, "SubagentControl": true, "DurableInputReceipts": true, "DurableTurns": true, "FunctionTools": true, "MCPHTTPTools": true, "MCPHTTPBearerAuth": true}
expected["ExecutionControls"], expected["TextVerbosity"] = SupportsTextVerbosity, SupportsTextVerbosity
expected := map[string]bool{"SubagentObservations": true, "MessageItems": true, "EnvironmentNone": true, "ProgrammaticToolCallingDisable": true, "WebSearchControl": true, "TextVerbosity": SupportsTextVerbosity, "MessageImages": true, "FunctionResultImages": true, "SubagentControl": true, "FunctionTools": true, "MCPHTTPTools": true, "MCPHTTPBearerAuth": true}
value := reflect.ValueOf(Declaration.Info.Capabilities)
for i := 0; i < value.NumField(); i++ {
name := value.Type().Field(i).Name
Expand Down
14 changes: 5 additions & 9 deletions apps/daemon/internal/agent/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
// Registration: each adapter exports one Declaration. The Runtime discovers the
// static declaration list and installs each resulting Runtime through Register.
// Availability and factory selection belong to the adapter. RegisterKind resets
// the factories, so Register installs it first. RegisterExecutor derives the
// Preparation capability. The Runtime's Environment owner, not the adapter,
// serves and declares workspace operations.
// the factories, so Register installs it first. The Runtime's Environment
// owner, not the adapter, serves and declares workspace operations.
//
// Runtime registration and Core service qualification remain separate. A public
// Harness also needs a profile in services/core/internal/engine; advertising
Expand Down Expand Up @@ -627,16 +626,15 @@ func (r *Registry) RegisterKind(info proto.SupportedAgentKind, configuration har
r.configurations[kind] = configuration
delete(r.executors, kind)
delete(r.views, kind)
info.Capabilities.Preparation = proto.CapabilityUnsupported
r.kinds[kind] = info
}

// RegisterExecutor installs the shared lifecycle after RegisterKind and derives
// the Preparation capability. It does not enable other public operations.
// RegisterExecutor installs the shared lifecycle after RegisterKind. It does
// not enable other public operations.
func (r *Registry) RegisterExecutor(kind string, factory ExecutorFactory) {
r.mu.Lock()
defer r.mu.Unlock()
info, exists := r.kinds[kind]
_, exists := r.kinds[kind]
if !exists || factory == nil {
panic("agent.Registry.RegisterExecutor: registered kind and factory required")
}
Expand All @@ -650,6 +648,4 @@ func (r *Registry) RegisterExecutor(kind string, factory ExecutorFactory) {
}
return factory(ctx, req)
}
info.Capabilities.Preparation = proto.CapabilitySupported
r.kinds[kind] = info
}
14 changes: 0 additions & 14 deletions apps/daemon/internal/agent/mcode/declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,20 @@ import (
// Declaration owns MiniMax Code discovery, configuration and execution factories.
var Declaration = agent.Declaration{Info: proto.SupportedAgentKind{Kind: "mcode", Capabilities: proto.AgentKindCapabilities{
SubagentObservations: proto.CapabilityUnsupported,
Streaming: proto.CapabilitySupported,
Usage: proto.CapabilityUnsupported,
Resume: proto.CapabilitySupported,
NativeSessionRecovery: proto.CapabilityUnsupported,
Steering: proto.CapabilityUnsupported,
MessageItems: proto.CapabilityUnsupported,
ToolObservations: proto.CapabilityUnsupported,
EnvironmentNone: proto.CapabilityUnsupported,
LocalEnvironment: proto.CapabilityUnsupported,
Preparation: proto.CapabilityUnsupported,
WorkspaceReadPreparation: proto.CapabilityUnsupported,
WorkspaceOutputExport: proto.CapabilityUnsupported,
ProgrammaticToolCallingDisable: proto.CapabilityUnsupported,
WebSearchControl: proto.CapabilityUnsupported,
ExecutionControls: proto.CapabilityUnsupported,
TextVerbosity: proto.CapabilityUnsupported,
StructuredOutput: proto.CapabilityUnsupported,
ToolSearch: proto.CapabilityUnsupported,
MessageImages: proto.CapabilityUnsupported,
FunctionResultImages: proto.CapabilityUnsupported,
SubagentControl: proto.CapabilityUnsupported,
DurableInputReceipts: proto.CapabilityUnsupported,
DurableTurns: proto.CapabilityUnsupported,
FunctionTools: proto.CapabilityUnsupported,
MCPHTTPTools: proto.CapabilityUnsupported,
MCPHTTPRequired: proto.CapabilityUnsupported,
Expand All @@ -56,12 +47,7 @@ func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, r
return runtime
}
result.Available, result.Version = true, version
result.Capabilities.Steering = proto.CapabilitySupported
result.Capabilities.DurableTurns = proto.CapabilitySupported
result.Capabilities.DurableInputReceipts = proto.CapabilitySupported
result.Capabilities.ExecutionControls = proto.CapabilitySupported
result.Capabilities.ProgrammaticToolCallingDisable = proto.CapabilitySupported
result.Capabilities.ToolObservations = proto.CapabilitySupported
result.Capabilities.SubagentControl = proto.CapabilitySupported
// Native preparation verifies the applied admission/tool profile before input.
result.Capabilities.SubagentObservations = proto.CapabilitySupported
Expand Down
14 changes: 4 additions & 10 deletions apps/daemon/internal/agent/mcode/declaration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestMCodeExecutionFollowsAvailability(t *testing.T) {
if (runtime.Executor != nil) != available {
t.Fatalf("factories: %+v", runtime)
}
if info.Available != available || info.Capabilities.EnvironmentNone.IsSupported() != available || info.Capabilities.DurableInputReceipts.IsSupported() != available || info.Capabilities.SubagentObservations.IsSupported() != available {
if info.Available != available || info.Capabilities.EnvironmentNone.IsSupported() != available || info.Capabilities.SubagentObservations.IsSupported() != available {
t.Fatalf("capabilities=%+v", info.Capabilities)
}
if info.Capabilities.NativeSessionRecovery.IsSupported() || info.Capabilities.LocalEnvironment.IsSupported() || info.Capabilities.FunctionTools.IsSupported() {
Expand All @@ -34,18 +34,12 @@ func TestMCodeExecutionFollowsAvailability(t *testing.T) {
}
}

// The declaration must retain the complete baseline capability descriptor.
// The static declaration supports nothing until discovery finds the CLI.
func TestDeclaredCapabilityBaseline(t *testing.T) {
expected := map[string]bool{"Streaming": true, "Resume": true}
value := reflect.ValueOf(Declaration.Info.Capabilities)
for i := 0; i < value.NumField(); i++ {
name := value.Type().Field(i).Name
want := proto.CapabilityUnsupported
if expected[name] {
want = proto.CapabilitySupported
}
if got := value.Field(i).Interface(); got != want {
t.Errorf("%s = %v, want %v", name, got, want)
if got := value.Field(i).Interface(); got != proto.CapabilityUnsupported {
t.Errorf("%s = %v, want unsupported", value.Type().Field(i).Name, got)
}
}
if err := Declaration.Info.ValidateDeclaration(); err != nil {
Expand Down
12 changes: 5 additions & 7 deletions apps/daemon/internal/agent/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,16 @@ func TestRegistrySupportedAgentKindsReportsDescriptors(t *testing.T) {
Available: false,
Version: "missing",
Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{
Streaming: proto.CapabilitySupported,
MessageItems: proto.CapabilitySupported,
}),
}, harnessconfig.Configuration{})
reg.RegisterKind(proto.SupportedAgentKind{
Kind: "fake_alpha",
Available: true,
Version: "1.2.3",
Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{
Streaming: proto.CapabilitySupported,
Usage: proto.CapabilitySupported,
Resume: proto.CapabilitySupported,
MessageItems: proto.CapabilitySupported,
EnvironmentNone: proto.CapabilitySupported,
}),
}, harnessconfig.Configuration{})

Expand All @@ -76,10 +75,10 @@ func TestRegistrySupportedAgentKindsReportsDescriptors(t *testing.T) {
if got[0].Kind != "fake_alpha" || got[1].Kind != "fake_beta" {
t.Fatalf("SupportedAgentKinds sort = %#v, want fake_alpha then fake_beta", got)
}
if !got[0].Available || got[0].Version != "1.2.3" || !got[0].Capabilities.Usage.IsSupported() || !got[0].Capabilities.Resume.IsSupported() {
if !got[0].Available || got[0].Version != "1.2.3" || !got[0].Capabilities.MessageItems.IsSupported() || !got[0].Capabilities.EnvironmentNone.IsSupported() {
t.Fatalf("fake_alpha descriptor not preserved: %#v", got[0])
}
if got[1].Available || got[1].Version != "missing" || !got[1].Capabilities.Streaming.IsSupported() {
if got[1].Available || got[1].Version != "missing" || !got[1].Capabilities.MessageItems.IsSupported() {
t.Fatalf("fake_beta descriptor not preserved: %#v", got[1])
}
}
Expand Down Expand Up @@ -113,7 +112,6 @@ func TestRegistryRejectsEveryOmittedCapabilityBeforeReplacement(t *testing.T) {
original := proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: valid}
registry.RegisterKind(original, harnessconfig.Configuration{})
registry.RegisterExecutor("fixture", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { return nil, nil })
original.Capabilities.Preparation = proto.CapabilitySupported
missing := valid
reflect.ValueOf(&missing).Elem().Field(i).Set(reflect.ValueOf(proto.CapabilityUnspecified))
func() {
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agenthost/admit_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func TestRegistryDescribesTheViewPath(t *testing.T) {
for _, info := range (&Host{cfg: f.cfg}).Registry(nil).SupportedAgentKinds() {
kinds = append(kinds, info.Kind)
c, declared := info.Capabilities, info.Kind == "supporting"
if !c.Preparation.IsSupported() || !c.LocalEnvironment.IsSupported() || !c.MCPHTTPTools.IsSupported() || c.EnvironmentNone.IsSupported() != declared ||
if !c.LocalEnvironment.IsSupported() || !c.MCPHTTPTools.IsSupported() || c.EnvironmentNone.IsSupported() != declared ||
c.FunctionTools.IsSupported() != declared || c.FunctionResultImages.IsSupported() != declared || c.ToolSearch.IsSupported() != declared ||
c.WorkspaceOutputExport.IsSupported() || c.WorkspaceReadPreparation.IsSupported() {
t.Errorf("%s: capabilities %+v do not describe the view path", info.Kind, c)
Expand Down
61 changes: 22 additions & 39 deletions apps/daemon/internal/dispatch/capability_admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,27 @@ import (
"github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto/prototest"
)

func TestSteeringUsesAdmittedDeclarationAndDoesNotReplayUnsupportedImplementation(t *testing.T) {
for _, supported := range []bool{false, true} {
t.Run(fmt.Sprint(supported), func(t *testing.T) {
h := newHarness(t)
defer h.router.Shutdown(context.Background())
var calls atomic.Int32
factory := func(_ context.Context, _ proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Session, error) {
return &steeringSession{fakeSession: &fakeSession{out: out, closeOutOnCancel: true}, steer: func(context.Context, proto.PromptSteerPayload) error {
calls.Add(1)
return fmt.Errorf("%w: fixture has no active input", agent.ErrUnsupportedOperation)
}}, nil
}
info := proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{Steering: proto.CapabilityFromBool(supported)})}
registerSession(h.reg, info, factory)
startRun(t, h.router, h.sender, "fixture", "run")
// A new registration cannot rewrite the already admitted owner's contract.
info.Capabilities.Steering = proto.CapabilityFromBool(!supported)
registerSession(h.reg, info, factory)
for range 2 {
if err := handleSteeringAndWait(t, h, scoped(t, "run", proto.TypePromptSteer, "run", proto.PromptSteerPayload{InputID: "input", Input: proto.TextInput("hello")})); err != nil {
t.Fatal(err)
}
ack := lastSteeringAck(t, h.sender, "run", "input")
code := "unsupported"
if supported {
code = "contract_violation"
}
if ack.Accepted || ack.Written || ack.ErrorCode != code {
t.Fatal(ack)
}
}
want := int32(0)
if supported {
want = 1
}
if calls.Load() != want {
t.Fatalf("native calls %d, want %d", calls.Load(), want)
}
})
func TestSteeringDoesNotReplayUnsupportedImplementation(t *testing.T) {
h := newHarness(t)
defer h.router.Shutdown(context.Background())
var calls atomic.Int32
factory := func(_ context.Context, _ proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Session, error) {
return &steeringSession{fakeSession: &fakeSession{out: out, closeOutOnCancel: true}, steer: func(context.Context, proto.PromptSteerPayload) error {
calls.Add(1)
return fmt.Errorf("%w: fixture has no active input", agent.ErrUnsupportedOperation)
}}, nil
}
registerSession(h.reg, proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, factory)
startRun(t, h.router, h.sender, "fixture", "run")
for range 2 {
if err := handleSteeringAndWait(t, h, scoped(t, "run", proto.TypePromptSteer, "run", proto.PromptSteerPayload{InputID: "input", Input: proto.TextInput("hello")})); err != nil {
t.Fatal(err)
}
if ack := lastSteeringAck(t, h.sender, "run", "input"); ack.Accepted || ack.Written || ack.ErrorCode != "contract_violation" {
t.Fatal(ack)
}
}
if calls.Load() != 1 {
t.Fatalf("native calls %d, want 1", calls.Load())
}
}
2 changes: 1 addition & 1 deletion apps/daemon/internal/dispatch/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (r *Router) handleExecutorPrepare(ctx context.Context, env proto.Envelope,
return r.rejectPreparation(env, "resource_unavailable")
}
factory, err := r.registry.ResolveExecutor(req.AgentKind)
if err != nil || !caps.Preparation.IsSupported() {
if err != nil {
return r.rejectPreparation(env, "unsupported_preparation")
}
environment, code := r.admittedEnvironment(env.Assignment, input.SessionID)
Expand Down
Loading
Loading