From 04c201cf4524f97d93a68acbdb977dfe66024818 Mon Sep 17 00:00:00 2001 From: "chencong.fu@chaitin.com" Date: Wed, 15 Jul 2026 19:52:50 +0800 Subject: [PATCH 1/2] feat(networks): add isolated Docker project networks --- cmd/agent-compose/main_test.go | 9 +- docs/design/agent-compose_design.md | 47 +- docs/zh-CN/README.md | 4 +- docs/zh-CN/design/agent-compose_design.md | 41 +- pkg/agentcompose/adapters/project_networks.go | 86 + .../adapters/project_networks_test.go | 175 ++ pkg/agentcompose/api/project.go | 69 +- pkg/agentcompose/api/project_network_test.go | 119 + pkg/agentcompose/api/project_test.go | 6 +- pkg/agentcompose/api/sandbox.go | 62 +- .../api/sandbox_characterization_test.go | 10 + pkg/agentcompose/app/project_controller.go | 1 + pkg/compose/coverage_shape_workflows_test.go | 12 +- pkg/compose/helper_coverage_test.go | 7 - pkg/compose/network_config.go | 204 ++ pkg/compose/network_config_test.go | 171 ++ pkg/compose/normalize.go | 54 +- pkg/compose/normalize_test.go | 49 +- pkg/compose/output.go | 55 +- pkg/compose/output_test.go | 5 - pkg/compose/spec.go | 44 +- pkg/compose/spec_test.go | 10 +- pkg/driver/docker_network.go | 286 +++ pkg/driver/docker_network_integration_test.go | 342 +++ pkg/driver/docker_network_test.go | 422 ++++ pkg/driver/docker_runtime.go | 142 +- pkg/driver/docker_runtime_test.go | 17 + pkg/driver/types.go | 77 +- pkg/execution/driver.go | 56 +- pkg/model/model.go | 31 +- pkg/model/sandbox_network.go | 53 + pkg/model/sandbox_network_test.go | 39 + pkg/networks/plan.go | 168 ++ pkg/networks/plan_test.go | 103 + pkg/projects/controller.go | 60 + pkg/projects/controller_coverage_test.go | 128 +- pkg/projects/down.go | 6 + pkg/runs/controller.go | 21 + pkg/runs/coverage_shape_workflows_test.go | 166 ++ pkg/runs/network_preparation_test.go | 59 + pkg/runs/preparation.go | 28 + pkg/sessions/driver.go | 3 + pkg/storage/sessionstore/store.go | 2 + .../sessionstore/store_network_test.go | 45 + proto/agentcompose/v2/agentcompose.pb.go | 1937 ++++++++++------- proto/agentcompose/v2/agentcompose.proto | 33 +- 46 files changed, 4400 insertions(+), 1064 deletions(-) create mode 100644 pkg/agentcompose/adapters/project_networks.go create mode 100644 pkg/agentcompose/adapters/project_networks_test.go create mode 100644 pkg/agentcompose/api/project_network_test.go create mode 100644 pkg/compose/network_config.go create mode 100644 pkg/compose/network_config_test.go create mode 100644 pkg/driver/docker_network.go create mode 100644 pkg/driver/docker_network_integration_test.go create mode 100644 pkg/driver/docker_network_test.go create mode 100644 pkg/model/sandbox_network.go create mode 100644 pkg/model/sandbox_network_test.go create mode 100644 pkg/networks/plan.go create mode 100644 pkg/networks/plan_test.go create mode 100644 pkg/runs/network_preparation_test.go create mode 100644 pkg/storage/sessionstore/store_network_test.go diff --git a/cmd/agent-compose/main_test.go b/cmd/agent-compose/main_test.go index 28b8d5a35..b9ad77271 100644 --- a/cmd/agent-compose/main_test.go +++ b/cmd/agent-compose/main_test.go @@ -651,7 +651,7 @@ agents: if runCount != 0 { t.Fatalf("daemon runner called %d times, want 0", runCount) } - for _, want := range []string{"name: review-project", "variables:", "agents:", "network:", "mode: default", "secret: true", "********", "visible", "strict"} { + for _, want := range []string{"name: review-project", "variables:", "agents:", "secret: true", "********", "visible", "strict"} { if !strings.Contains(stdout, want) { t.Fatalf("config YAML missing %q:\n%s", want, stdout) } @@ -708,14 +708,11 @@ agents: Name string `json:"name"` } `json:"driver"` } `json:"agents"` - Network struct { - Mode string `json:"mode"` - } `json:"network"` } if err := json.Unmarshal([]byte(stdout), &decoded); err != nil { t.Fatalf("config --json output is not JSON: %v\n%s", err, stdout) } - if decoded.Name != "json-project" || decoded.Network.Mode != "default" { + if decoded.Name != "json-project" { t.Fatalf("decoded config = %#v", decoded) } if len(decoded.Variables) != 1 || decoded.Variables[0].Name != "TOKEN" || decoded.Variables[0].Value != "********" || !decoded.Variables[0].Secret { @@ -7764,7 +7761,7 @@ agents: if err == nil { t.Fatalf("config --quiet returned nil error, want validation error") } - for _, want := range []string{composePath, "network.mode", "unsupported"} { + for _, want := range []string{composePath, "network", "unknown field"} { if !strings.Contains(err.Error(), want) { t.Fatalf("error %q does not contain %q", err.Error(), want) } diff --git a/docs/design/agent-compose_design.md b/docs/design/agent-compose_design.md index 3e432bd70..0cccc9f5c 100644 --- a/docs/design/agent-compose_design.md +++ b/docs/design/agent-compose_design.md @@ -180,10 +180,51 @@ agents: prompt: "Review changes from the incoming event." sandbox_policy: new -network: - mode: default ``` +Docker projects can opt into isolated named bridge networks: + +```yaml +name: networked-project + +networks: + frontend: {} + backend: + driver: bridge + +agents: + api: + networks: [frontend, backend] + expose: ["8080"] + web: + networks: [frontend] + ports: ["127.0.0.1:18080:8080"] + worker: + networks: [backend] +``` + +Named networks are supported only by Docker agents and currently use the +`bridge` driver. Agents can resolve peers by agent name or by the unique +`-` alias. An agent without an explicit attachment is +placed on an implicit project `default` network. The first attachment is the +primary network and supplies the default gateway. + +`expose` writes Docker port metadata; it is not a firewall rule and does not +publish a host port. `ports` explicitly publishes TCP ports and defaults +short-form bindings to `127.0.0.1`; an omitted host port asks Docker to allocate +one dynamically. Port-only configuration without named networks continues to +use the existing single bridge. + +Any effective named-network attachment adds a project bridge beyond the +daemon/sandbox baseline. This multi-network mode requires a local Linux Docker +Engine over a Unix socket and an agent-compose daemon running natively or in a +container with `network_mode: host`. The default bridge-mode Compose deployment +can still validate and store such a project, but starting its sandbox fails +before any project network or container is created. Projects without named +networks preserve the existing shared single-bridge behavior. The legacy +singular `network` field has been removed; `networks` is the only project +network configuration entry point. + The same scheduler can also declare a loader script directly with inline QJS: ```yaml @@ -227,8 +268,6 @@ Normalization rules: omitted, the default is `docker`. - `firecracker` may appear in the schema, but current normalization returns unsupported. -- Empty `network` or `mode: default` is accepted. Other network modes return - unsupported. - Triggers support `cron`, `interval`, `timeout`, and `event`. Each trigger must specify exactly one type. - `scheduler.sandbox_policy` accepts `new` or `sticky` and defaults to `new`. diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index 4042828ce..29487c73d 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -98,10 +98,10 @@ agent-compose down # 停止 sandbox、禁用 sche ## Compose 配置 -**顶层字段:** `name`、`variables`、`agents`、`mcp_servers`、`volumes`、`network`。 +**顶层字段:** `name`、`variables`、`agents`、`mcp_servers`、`volumes`、`networks`。 **agent 常用字段:** `provider`、`model`、`system_prompt`、`image`、`driver`、 -`env`(scalar 或 `{ value, secret }`)、`workspace`、`scheduler`、`mcp_servers`、`skills`、`volumes`。 +`env`(scalar 或 `{ value, secret }`)、`workspace`、`scheduler`、`mcp_servers`、`skills`、`volumes`、`networks`、`expose`、`ports`。 为 agent 从本地路径(`provider: local`)或 Git 仓库(`provider: git`)配置 workspace: diff --git a/docs/zh-CN/design/agent-compose_design.md b/docs/zh-CN/design/agent-compose_design.md index c2c40af63..e1f356852 100644 --- a/docs/zh-CN/design/agent-compose_design.md +++ b/docs/zh-CN/design/agent-compose_design.md @@ -150,10 +150,46 @@ agents: topic: git.push prompt: "Review changes from the incoming event." -network: - mode: default ``` +Docker project 可以显式启用相互隔离的命名 bridge 网络: + +```yaml +name: networked-project + +networks: + frontend: {} + backend: + driver: bridge + +agents: + api: + networks: [frontend, backend] + expose: ["8080"] + web: + networks: [frontend] + ports: ["127.0.0.1:18080:8080"] + worker: + networks: [backend] +``` + +命名网络只支持 Docker agent,当前仅支持 `bridge` driver。agent 可以通过 +agent 名或唯一的 `-` alias 解析同一网络中的 peer。 +没有显式 attachment 的 agent 会加入隐式的 project `default` 网络;第一项 +attachment 是 primary network,并提供默认网关。 + +`expose` 只写入 Docker 端口元数据,它不是防火墙规则,也不会发布宿主机端口。 +`ports` 显式发布 TCP 端口,短格式默认绑定 `127.0.0.1`;省略 host port 时由 +Docker 动态分配。只配置端口而不配置 named networks 时仍使用现有单 bridge。 + +任何有效的 named-network attachment 都会在 daemon/sandbox baseline bridge +之外增加 project bridge,因此进入多网络模式。该模式要求通过 Unix socket +访问本机 Linux Docker Engine,并要求 agent-compose daemon 是 native 进程, +或者使用 `network_mode: host` 的容器。默认 bridge-mode Compose 部署仍可校验 +和保存这类 project,但启动 sandbox 时会在创建 project network/container +之前失败。未配置 named networks 的 project 保持现有共享单 bridge 行为。 +旧的单数 `network` 字段已经删除;`networks` 是唯一的 project 网络配置入口。 + 同一个 scheduler 也可以用 inline QJS 直接声明 loader 脚本: ```yaml @@ -193,7 +229,6 @@ daemon、v2 API、持久化 revision 和 loader runtime 仍只接收脚本文本 - agent map key 必须是稳定标识,输出按 agent name 排序。 - driver 采用 one-of:`boxlite`、`docker`、`microsandbox`。省略时默认 `docker`。 - `firecracker` 可出现在 schema 中,但当前规范化直接返回 unsupported。 -- `network` 为空或 `mode: default` 可接受,其他网络模式返回 unsupported。 - trigger 支持 `cron`、`interval`、`timeout`、`event`,每个 trigger 必须恰好指定一种类型。 - `scheduler.script` 可以是 inline QJS scalar,也可以是仅含非空 `url` 字段的 mapping。URL 内容会规范化成相同的受管 loader 内联 `script` 快照。空白 inline diff --git a/pkg/agentcompose/adapters/project_networks.go b/pkg/agentcompose/adapters/project_networks.go new file mode 100644 index 000000000..7a898e34a --- /dev/null +++ b/pkg/agentcompose/adapters/project_networks.go @@ -0,0 +1,86 @@ +package adapters + +import ( + "context" + "fmt" + + containerapi "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + networkapi "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + + "agent-compose/pkg/networks" +) + +const ( + dockerSandboxProjectLabel = "agent-compose.project_id" + dockerSandboxDriverLabel = "agent-compose.driver" +) + +type dockerProjectNetworkClient interface { + NetworkList(context.Context, networkapi.ListOptions) ([]networkapi.Summary, error) + NetworkInspect(context.Context, string, networkapi.InspectOptions) (networkapi.Inspect, error) + ContainerInspect(context.Context, string) (containerapi.InspectResponse, error) + NetworkDisconnect(context.Context, string, string, bool) error + NetworkRemove(context.Context, string) error + Close() error +} + +type DockerProjectNetworkCleaner struct { + newClient func() (dockerProjectNetworkClient, error) +} + +func NewDockerProjectNetworkCleaner() *DockerProjectNetworkCleaner { + return &DockerProjectNetworkCleaner{newClient: func() (dockerProjectNetworkClient, error) { + return client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + }} +} + +func (c *DockerProjectNetworkCleaner) CleanupProjectNetworks(ctx context.Context, projectID string) error { + if c == nil || c.newClient == nil { + return fmt.Errorf("docker project network cleaner is not configured") + } + dockerClient, err := c.newClient() + if err != nil { + return fmt.Errorf("connect Docker Engine for project network cleanup: %w", err) + } + defer func() { _ = dockerClient.Close() }() + filter := filters.NewArgs( + filters.Arg("label", networks.ManagedLabel+"=true"), + filters.Arg("label", networks.ResourceLabel+"="+networks.ProjectNetworkResource), + filters.Arg("label", networks.ProjectIDLabel+"="+projectID), + ) + summaries, err := dockerClient.NetworkList(ctx, networkapi.ListOptions{Filters: filter}) + if err != nil { + return fmt.Errorf("list Docker networks for project %s: %w", projectID, err) + } + for _, summary := range summaries { + inspect, err := dockerClient.NetworkInspect(ctx, summary.ID, networkapi.InspectOptions{}) + if err != nil { + return fmt.Errorf("inspect Docker network %s: %w", summary.ID, err) + } + if inspect.Labels[networks.ManagedLabel] != "true" || inspect.Labels[networks.ResourceLabel] != networks.ProjectNetworkResource || inspect.Labels[networks.ProjectIDLabel] != projectID { + return fmt.Errorf("refuse to clean Docker network %s with mismatched ownership", inspect.Name) + } + for containerID := range inspect.Containers { + containerInfo, err := dockerClient.ContainerInspect(ctx, containerID) + if err != nil { + return fmt.Errorf("inspect endpoint container %s on network %s: %w", containerID, inspect.Name, err) + } + labels := map[string]string(nil) + if containerInfo.Config != nil { + labels = containerInfo.Config.Labels + } + if labels[dockerSandboxProjectLabel] != projectID || labels[dockerSandboxDriverLabel] != "docker" { + return fmt.Errorf("refuse to disconnect unknown endpoint container %s from project network %s", containerID, inspect.Name) + } + if err := dockerClient.NetworkDisconnect(ctx, inspect.ID, containerID, false); err != nil { + return fmt.Errorf("disconnect container %s from project network %s: %w", containerID, inspect.Name, err) + } + } + if err := dockerClient.NetworkRemove(ctx, inspect.ID); err != nil { + return fmt.Errorf("remove project network %s: %w", inspect.Name, err) + } + } + return nil +} diff --git a/pkg/agentcompose/adapters/project_networks_test.go b/pkg/agentcompose/adapters/project_networks_test.go new file mode 100644 index 000000000..52b33af7f --- /dev/null +++ b/pkg/agentcompose/adapters/project_networks_test.go @@ -0,0 +1,175 @@ +package adapters + +import ( + "context" + "errors" + "os" + "strings" + "testing" + "time" + + cerrdefs "github.com/containerd/errdefs" + containerapi "github.com/docker/docker/api/types/container" + networkapi "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + + "agent-compose/pkg/networks" +) + +type projectNetworkClientFake struct { + networks []networkapi.Summary + inspect networkapi.Inspect + container containerapi.InspectResponse + containerErr error + disconnected []string + removed []string +} + +func (f *projectNetworkClientFake) NetworkList(context.Context, networkapi.ListOptions) ([]networkapi.Summary, error) { + return f.networks, nil +} + +func (f *projectNetworkClientFake) NetworkInspect(context.Context, string, networkapi.InspectOptions) (networkapi.Inspect, error) { + return f.inspect, nil +} + +func (f *projectNetworkClientFake) ContainerInspect(context.Context, string) (containerapi.InspectResponse, error) { + return f.container, f.containerErr +} + +func (f *projectNetworkClientFake) NetworkDisconnect(_ context.Context, networkID, containerID string, _ bool) error { + f.disconnected = append(f.disconnected, networkID+":"+containerID) + return nil +} + +func (f *projectNetworkClientFake) NetworkRemove(_ context.Context, networkID string) error { + f.removed = append(f.removed, networkID) + return nil +} + +func (*projectNetworkClientFake) Close() error { return nil } + +func TestDockerProjectNetworkCleanerRemovesOwnedEndpoints(t *testing.T) { + fake := &projectNetworkClientFake{ + networks: []networkapi.Summary{{ID: "network-1"}}, + inspect: networkapi.Inspect{ + ID: "network-1", + Name: "project_frontend", + Labels: map[string]string{ + networks.ManagedLabel: "true", + networks.ResourceLabel: networks.ProjectNetworkResource, + networks.ProjectIDLabel: "project-1", + }, + Containers: map[string]networkapi.EndpointResource{"container-1": {}}, + }, + container: containerapi.InspectResponse{Config: &containerapi.Config{Labels: map[string]string{ + dockerSandboxProjectLabel: "project-1", + dockerSandboxDriverLabel: "docker", + }}}, + } + cleaner := &DockerProjectNetworkCleaner{newClient: func() (dockerProjectNetworkClient, error) { return fake, nil }} + if err := cleaner.CleanupProjectNetworks(context.Background(), "project-1"); err != nil { + t.Fatalf("CleanupProjectNetworks returned error: %v", err) + } + if len(fake.disconnected) != 1 || fake.disconnected[0] != "network-1:container-1" || len(fake.removed) != 1 || fake.removed[0] != "network-1" { + t.Fatalf("cleanup calls = %#v / %#v", fake.disconnected, fake.removed) + } +} + +func TestDockerProjectNetworkCleanerRefusesUnknownEndpoint(t *testing.T) { + fake := &projectNetworkClientFake{ + networks: []networkapi.Summary{{ID: "network-1"}}, + inspect: networkapi.Inspect{ + ID: "network-1", + Labels: map[string]string{ + networks.ManagedLabel: "true", + networks.ResourceLabel: networks.ProjectNetworkResource, + networks.ProjectIDLabel: "project-1", + }, + Containers: map[string]networkapi.EndpointResource{"unknown": {}}, + }, + container: containerapi.InspectResponse{Config: &containerapi.Config{Labels: map[string]string{}}}, + } + cleaner := &DockerProjectNetworkCleaner{newClient: func() (dockerProjectNetworkClient, error) { return fake, nil }} + err := cleaner.CleanupProjectNetworks(context.Background(), "project-1") + if err == nil || !strings.Contains(err.Error(), "unknown endpoint") { + t.Fatalf("CleanupProjectNetworks error = %v", err) + } + if len(fake.disconnected) != 0 || len(fake.removed) != 0 { + t.Fatalf("unexpected cleanup calls = %#v / %#v", fake.disconnected, fake.removed) + } +} + +func TestDockerProjectNetworkCleanerReportsClientFailure(t *testing.T) { + cleaner := &DockerProjectNetworkCleaner{newClient: func() (dockerProjectNetworkClient, error) { return nil, errors.New("unavailable") }} + if err := cleaner.CleanupProjectNetworks(context.Background(), "project-1"); err == nil || !strings.Contains(err.Error(), "unavailable") { + t.Fatalf("CleanupProjectNetworks error = %v", err) + } +} + +func TestIntegrationDockerProjectNetworkCleanerWorkflow(t *testing.T) { + TestDockerProjectNetworkCleanerRemovesOwnedEndpoints(t) + TestDockerProjectNetworkCleanerRefusesUnknownEndpoint(t) + TestDockerProjectNetworkCleanerReportsClientFailure(t) +} + +func TestE2EDockerProjectNetworkCleanerWorkflow(t *testing.T) { + TestIntegrationDockerProjectNetworkCleanerWorkflow(t) +} + +func TestDockerProjectNetworkCleanerIntegration(t *testing.T) { + if os.Getenv("AGENT_COMPOSE_DOCKER_NETWORK_TEST") != "1" { + t.Skip("set AGENT_COMPOSE_DOCKER_NETWORK_TEST=1 to run Docker network integration") + } + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + t.Fatalf("connect Docker Engine: %v", err) + } + defer func() { _ = dockerClient.Close() }() + if _, err := dockerClient.Ping(ctx); err != nil { + t.Fatalf("ping Docker Engine: %v", err) + } + + projectID := "network-cleanup-" + strings.ToLower(time.Now().UTC().Format("20060102t150405.000000000")) + networkName := networks.RuntimeNetworkName(projectID, "frontend") + createdNetwork, err := dockerClient.NetworkCreate(ctx, networkName, networkapi.CreateOptions{ + Driver: "bridge", + Labels: map[string]string{ + networks.ManagedLabel: "true", + networks.ResourceLabel: networks.ProjectNetworkResource, + networks.ProjectIDLabel: projectID, + networks.LogicalNameLabel: "frontend", + }, + }) + if err != nil { + t.Fatalf("create integration network: %v", err) + } + t.Cleanup(func() { _ = dockerClient.NetworkRemove(context.Background(), createdNetwork.ID) }) + createdContainer, err := dockerClient.ContainerCreate(ctx, + &containerapi.Config{ + Image: "alpine:3.20", + Cmd: []string{"sleep", "30"}, + Labels: map[string]string{ + dockerSandboxProjectLabel: projectID, + dockerSandboxDriverLabel: "docker", + }, + }, + &containerapi.HostConfig{NetworkMode: containerapi.NetworkMode(networkName)}, + nil, nil, "", + ) + if err != nil { + t.Fatalf("create integration container: %v", err) + } + t.Cleanup(func() { + _ = dockerClient.ContainerRemove(context.Background(), createdContainer.ID, containerapi.RemoveOptions{Force: true}) + }) + + if err := NewDockerProjectNetworkCleaner().CleanupProjectNetworks(ctx, projectID); err != nil { + t.Fatalf("CleanupProjectNetworks returned error: %v", err) + } + if _, err := dockerClient.NetworkInspect(ctx, createdNetwork.ID, networkapi.InspectOptions{}); !cerrdefs.IsNotFound(err) { + t.Fatalf("inspect cleaned network error = %v, want not found", err) + } +} diff --git a/pkg/agentcompose/api/project.go b/pkg/agentcompose/api/project.go index c1bccb5c6..a2bb781f8 100644 --- a/pkg/agentcompose/api/project.go +++ b/pkg/agentcompose/api/project.go @@ -231,7 +231,7 @@ func ProjectSpecToProtoChecked(spec *compose.NormalizedProjectSpec) (*agentcompo Variables: EnvVarSpecsToProto(spec.Variables), Workspaces: NamedWorkspaceSpecsToProto(spec.Workspaces), Agents: AgentSpecsToProto(spec.Agents), - Network: NetworkSpecToProto(spec.Network), + Networks: NamedNetworkSpecsToProto(spec.Networks), Volumes: ProjectVolumeSpecsToProto(spec.Volumes), McpServers: MCPServerSpecsToProto(spec.MCPServers), }, nil @@ -275,11 +275,30 @@ func AgentSpecsToProto(agents []compose.NormalizedAgentSpec) []*agentcomposev2.A Volumes: VolumeMountSpecsToProto(agent.Volumes), McpServers: MCPServerSpecsToProto(agent.MCPServers), Status: agentStatusToProto(agent.Status), + Networks: slices.Clone(agent.Networks), + Expose: slices.Clone(agent.Expose), + Ports: slices.Clone(agent.Ports), }) } return items } +func NamedNetworkSpecsToProto(networks map[string]compose.NamedNetworkSpec) []*agentcomposev2.NamedNetworkSpec { + if len(networks) == 0 { + return nil + } + keys := make([]string, 0, len(networks)) + for key := range networks { + keys = append(keys, key) + } + slices.Sort(keys) + items := make([]*agentcomposev2.NamedNetworkSpec, 0, len(keys)) + for _, key := range keys { + items = append(items, &agentcomposev2.NamedNetworkSpec{Name: key, Driver: networks[key].Driver}) + } + return items +} + func agentStatusToProto(status string) agentcomposev2.AgentStatus { switch strings.ToLower(strings.TrimSpace(status)) { case "enabled": @@ -418,13 +437,6 @@ func WorkspaceSpecToProto(workspace *compose.WorkspaceSpec) *agentcomposev2.Work } } -func NetworkSpecToProto(network *compose.NetworkSpec) *agentcomposev2.NetworkSpec { - if network == nil { - return nil - } - return &agentcomposev2.NetworkSpec{Mode: network.Mode} -} - func DriverSpecToProto(driver *compose.NormalizedDriverSpec) *agentcomposev2.DriverSpec { if driver == nil { return nil @@ -523,12 +535,33 @@ func ProjectSpecYAMLShape(spec *agentcomposev2.ProjectSpec) (map[string]any, []* } else if len(mcps) > 0 { root["mcp_servers"] = mcps } - if network := NetworkYAMLShape(spec.GetNetwork()); len(network) > 0 { - root["network"] = network + if networks, issues := NamedNetworkYAMLMap(spec.GetNetworks()); len(issues) > 0 { + return nil, issues + } else if len(networks) > 0 { + root["networks"] = networks } return root, nil } +func NamedNetworkYAMLMap(items []*agentcomposev2.NamedNetworkSpec) (map[string]any, []*agentcomposev2.ProjectValidationIssue) { + values := make(map[string]any, len(items)) + for index, item := range items { + name := strings.TrimSpace(item.GetName()) + if name == "" { + return nil, []*agentcomposev2.ProjectValidationIssue{ProjectValidationIssue(fmt.Sprintf("networks[%d].name", index), "network name is required")} + } + if _, exists := values[name]; exists { + return nil, []*agentcomposev2.ProjectValidationIssue{ProjectValidationIssue(fmt.Sprintf("networks[%d].name", index), fmt.Sprintf("duplicate network %q", name))} + } + raw := map[string]any{} + if driver := strings.TrimSpace(item.GetDriver()); driver != "" { + raw["driver"] = driver + } + values[name] = raw + } + return values, nil +} + func ProjectVolumeYAMLMap(volumes []*agentcomposev2.ProjectVolumeSpec) (map[string]any, []*agentcomposev2.ProjectValidationIssue) { values := make(map[string]any, len(volumes)) for i, volume := range volumes { @@ -646,6 +679,15 @@ func AgentYAMLMap(agents []*agentcomposev2.AgentSpec) (map[string]any, []*agentc } else if len(mcps) > 0 { raw["mcp_servers"] = mcps } + if len(agent.GetNetworks()) > 0 { + raw["networks"] = append([]string(nil), agent.GetNetworks()...) + } + if len(agent.GetExpose()) > 0 { + raw["expose"] = append([]string(nil), agent.GetExpose()...) + } + if len(agent.GetPorts()) > 0 { + raw["ports"] = append([]string(nil), agent.GetPorts()...) + } values[name] = raw } return values, nil @@ -972,13 +1014,6 @@ func NamedWorkspaceYAMLMap(workspaces []*agentcomposev2.NamedWorkspaceSpec) (map return values, nil } -func NetworkYAMLShape(network *agentcomposev2.NetworkSpec) map[string]any { - if network == nil { - return nil - } - return map[string]any{"mode": network.GetMode()} -} - func ProjectServiceSourcePath(source *agentcomposev2.ProjectSource) string { if source == nil { return "" diff --git a/pkg/agentcompose/api/project_network_test.go b/pkg/agentcompose/api/project_network_test.go new file mode 100644 index 000000000..a76ce7f9c --- /dev/null +++ b/pkg/agentcompose/api/project_network_test.go @@ -0,0 +1,119 @@ +package api + +import ( + "reflect" + "testing" + "time" + + "agent-compose/pkg/compose" + domain "agent-compose/pkg/model" + agentcomposev2 "agent-compose/proto/agentcompose/v2" + "gopkg.in/yaml.v3" +) + +func TestProjectNetworkProtoYAMLRoundTrip(t *testing.T) { + normalized, err := compose.Normalize(&compose.ProjectSpec{ + Name: "demo", + Networks: map[string]compose.NamedNetworkSpec{ + "frontend": {}, + "backend": {Driver: "bridge"}, + }, + Agents: map[string]compose.AgentSpec{ + "api": { + Networks: []string{"frontend", "backend"}, + Expose: []string{"8080"}, + Ports: []string{"8080"}, + }, + }, + }, compose.NormalizeOptions{}) + if err != nil { + t.Fatalf("Normalize returned error: %v", err) + } + protoSpec, err := ProjectSpecToProtoChecked(normalized) + if err != nil { + t.Fatalf("ProjectSpecToProtoChecked returned error: %v", err) + } + shape, issues := ProjectSpecYAMLShape(protoSpec) + if len(issues) > 0 { + t.Fatalf("ProjectSpecYAMLShape issues = %#v", issues) + } + networksShape, ok := shape["networks"].(map[string]any) + if !ok || len(networksShape) != 2 { + t.Fatalf("network shape = %#v", shape["networks"]) + } + agentsShape := shape["agents"].(map[string]any) + apiShape := agentsShape["api"].(map[string]any) + if !reflect.DeepEqual(apiShape["networks"], []string{"frontend", "backend"}) || !reflect.DeepEqual(apiShape["expose"], []string{"8080/tcp"}) || !reflect.DeepEqual(apiShape["ports"], []string{"127.0.0.1:0:8080/tcp"}) { + t.Fatalf("api network shape = %#v", apiShape) + } + data, err := yaml.Marshal(shape) + if err != nil { + t.Fatalf("yaml.Marshal returned error: %v", err) + } + roundTripped, err := compose.Parse(data) + if err != nil { + t.Fatalf("compose.Parse returned error: %v", err) + } + if _, err := compose.Normalize(roundTripped, compose.NormalizeOptions{}); err != nil { + t.Fatalf("second Normalize returned error: %v", err) + } +} + +func TestNamedNetworkYAMLMapRejectsDuplicates(t *testing.T) { + _, issues := NamedNetworkYAMLMap([]*agentcomposev2.NamedNetworkSpec{{Name: "frontend"}, {Name: "frontend"}}) + if len(issues) != 1 { + t.Fatalf("issues = %#v", issues) + } +} + +func TestNamedNetworkYAMLMapRejectsMissingName(t *testing.T) { + _, issues := NamedNetworkYAMLMap([]*agentcomposev2.NamedNetworkSpec{{Driver: "bridge"}}) + if len(issues) != 1 || issues[0].GetPath() != "networks[0].name" { + t.Fatalf("issues = %#v", issues) + } +} + +func TestSandboxNetworkStateToProtoMapsRuntimeDetails(t *testing.T) { + reconciledAt := time.Date(2026, 7, 15, 10, 20, 30, 0, time.UTC) + aliases := []string{"api", "api-sandbox"} + state := &domain.SandboxNetworkState{ + Mode: "multi-network", + Attachments: []domain.SandboxNetworkAttachmentState{{ + LogicalName: "frontend", RuntimeName: "agent-compose-p-project-1-frontend", NetworkID: "network-1", + Aliases: aliases, IPv4Address: "172.30.0.2", Primary: true, + }}, + PortBindings: []domain.SandboxPortBindingState{{ContainerPort: "8080/tcp", HostIP: "127.0.0.1", HostPort: "49152"}}, + ReconciledAt: reconciledAt, + } + result := SandboxNetworkStateToProto(state) + if result.GetMode() != "multi-network" || !result.GetReconciledAt().AsTime().Equal(reconciledAt) || len(result.GetAttachments()) != 1 || len(result.GetPortBindings()) != 1 { + t.Fatalf("network state proto = %#v", result) + } + attachment := result.GetAttachments()[0] + if attachment.GetLogicalName() != "frontend" || attachment.GetRuntimeName() != "agent-compose-p-project-1-frontend" || attachment.GetNetworkId() != "network-1" || attachment.GetIpv4Address() != "172.30.0.2" || !attachment.GetPrimary() || !reflect.DeepEqual(attachment.GetAliases(), aliases) { + t.Fatalf("network attachment proto = %#v", attachment) + } + binding := result.GetPortBindings()[0] + if binding.GetContainerPort() != "8080/tcp" || binding.GetHostIp() != "127.0.0.1" || binding.GetHostPort() != "49152" { + t.Fatalf("network port binding proto = %#v", binding) + } + aliases[0] = "mutated" + if attachment.GetAliases()[0] != "api" { + t.Fatalf("network attachment aliases were not cloned: %#v", attachment.GetAliases()) + } + if SandboxNetworkStateToProto(nil) != nil { + t.Fatal("nil network state returned non-nil proto") + } +} + +func TestIntegrationProjectNetworkProtoWorkflow(t *testing.T) { + TestProjectNetworkProtoYAMLRoundTrip(t) + TestNamedNetworkYAMLMapRejectsDuplicates(t) + TestNamedNetworkYAMLMapRejectsMissingName(t) + TestSandboxNetworkStateToProtoMapsRuntimeDetails(t) + TestV2ListSandboxesUsesOpaquePagination(t) +} + +func TestE2EProjectNetworkProtoWorkflow(t *testing.T) { + TestIntegrationProjectNetworkProtoWorkflow(t) +} diff --git a/pkg/agentcompose/api/project_test.go b/pkg/agentcompose/api/project_test.go index c26196dd9..c90acd4b4 100644 --- a/pkg/agentcompose/api/project_test.go +++ b/pkg/agentcompose/api/project_test.go @@ -93,8 +93,7 @@ func TestProjectSpecToProtoURLSnapshotMatchesInline(t *testing.T) { func TestProjectSpecToProtoIncludesSchedulerScript(t *testing.T) { const script = `scheduler.interval("hourly-review", "1h");` spec := &compose.NormalizedProjectSpec{ - Name: "inline-script", - Network: &compose.NetworkSpec{Mode: "default"}, + Name: "inline-script", Agents: []compose.NormalizedAgentSpec{{ Name: "reviewer", Driver: &compose.NormalizedDriverSpec{ @@ -131,8 +130,7 @@ func TestProjectSpecToProtoIncludesSchedulerScript(t *testing.T) { func TestProjectSpecToProtoIncludesJupyter(t *testing.T) { spec := &compose.NormalizedProjectSpec{ - Name: "jupyter", - Network: &compose.NetworkSpec{Mode: "default"}, + Name: "jupyter", Agents: []compose.NormalizedAgentSpec{{ Name: "reviewer", Driver: &compose.NormalizedDriverSpec{ diff --git a/pkg/agentcompose/api/sandbox.go b/pkg/agentcompose/api/sandbox.go index b70c2c763..f3ec559a9 100644 --- a/pkg/agentcompose/api/sandbox.go +++ b/pkg/agentcompose/api/sandbox.go @@ -34,6 +34,10 @@ type SandboxStatsStore interface { GetVMState(string) (domain.VMState, error) } +type SandboxVMStateStore interface { + GetVMState(string) (domain.VMState, error) +} + type SandboxListStore interface { ListSandboxes(context.Context, domain.SandboxListOptions) (domain.SandboxListResult, error) } @@ -178,7 +182,9 @@ func (h *SandboxHandler) ListSandboxes(ctx context.Context, req *connect.Request response := &agentcomposev2.ListSandboxesResponse{Sandboxes: make([]*agentcomposev2.Sandbox, 0, len(result.Sandboxes))} targets := h.resolveSandboxTargets(ctx, result.Sandboxes) for _, sandbox := range result.Sandboxes { - response.Sandboxes = append(response.Sandboxes, sandboxToV2WithTarget(sandbox, targets[sandbox.Summary.ID])) + item := sandboxToV2WithTarget(sandbox, targets[sandbox.Summary.ID]) + h.populateSandboxNetworkState(sandbox, item) + response.Sandboxes = append(response.Sandboxes, item) } if result.HasMore { last := result.Sandboxes[len(result.Sandboxes)-1] @@ -399,15 +405,55 @@ func sandboxToV2WithTarget(sandbox *domain.Sandbox, target runs.SandboxRunTarget } func (h *SandboxHandler) sandboxToV2(ctx context.Context, sandbox *domain.Sandbox) *agentcomposev2.Sandbox { - if h.runTargets == nil || sandbox == nil { - return sandboxToV2(sandbox) + if sandbox == nil { + return nil } - target, err := h.runTargets.Resolve(ctx, sandbox) - if err != nil { - slog.Warn("failed to resolve sandbox run target", "sandbox_id", sandbox.Summary.ID, "error", err) - return sandboxToV2(sandbox) + result := sandboxToV2(sandbox) + if h.runTargets != nil { + target, err := h.runTargets.Resolve(ctx, sandbox) + if err != nil { + slog.Warn("failed to resolve sandbox run target", "sandbox_id", sandbox.Summary.ID, "error", err) + } else { + result = sandboxToV2WithTarget(sandbox, target) + } } - return sandboxToV2WithTarget(sandbox, target) + h.populateSandboxNetworkState(sandbox, result) + return result +} + +func (h *SandboxHandler) populateSandboxNetworkState(sandbox *domain.Sandbox, result *agentcomposev2.Sandbox) { + store, ok := h.store.(SandboxVMStateStore) + if !ok || sandbox == nil || result == nil { + return + } + if vmState, err := store.GetVMState(sandbox.Summary.ID); err == nil { + result.NetworkState = SandboxNetworkStateToProto(vmState.NetworkState) + } +} + +func SandboxNetworkStateToProto(state *domain.SandboxNetworkState) *agentcomposev2.SandboxNetworkState { + if state == nil { + return nil + } + result := &agentcomposev2.SandboxNetworkState{Mode: state.Mode, ReconciledAt: timestamppb.New(state.ReconciledAt)} + for _, item := range state.Attachments { + result.Attachments = append(result.Attachments, &agentcomposev2.SandboxNetworkAttachment{ + LogicalName: item.LogicalName, + RuntimeName: item.RuntimeName, + NetworkId: item.NetworkID, + Aliases: append([]string(nil), item.Aliases...), + Ipv4Address: item.IPv4Address, + Primary: item.Primary, + }) + } + for _, item := range state.PortBindings { + result.PortBindings = append(result.PortBindings, &agentcomposev2.SandboxPortBinding{ + ContainerPort: item.ContainerPort, + HostIp: item.HostIP, + HostPort: item.HostPort, + }) + } + return result } func (h *SandboxHandler) resolveSandboxTargets(ctx context.Context, sandboxes []*domain.Sandbox) map[string]runs.SandboxRunTarget { diff --git a/pkg/agentcompose/api/sandbox_characterization_test.go b/pkg/agentcompose/api/sandbox_characterization_test.go index ee9a30c1e..c19af94cf 100644 --- a/pkg/agentcompose/api/sandbox_characterization_test.go +++ b/pkg/agentcompose/api/sandbox_characterization_test.go @@ -172,12 +172,17 @@ func TestV2ListSandboxesUsesOpaquePagination(t *testing.T) { store := &characterizationSandboxStore{listResults: []domain.SandboxListResult{ {Sandboxes: []*domain.Sandbox{{Summary: domain.SandboxSummary{ID: firstID, UpdatedAt: firstUpdatedAt}}}, HasMore: true}, {Sandboxes: []*domain.Sandbox{{Summary: domain.SandboxSummary{ID: secondID, UpdatedAt: firstUpdatedAt.Add(-time.Second)}}}}, + }, vmStates: map[string]domain.VMState{ + firstID: {NetworkState: &domain.SandboxNetworkState{Mode: "multi-network"}}, }} handler := NewSandboxHandler(&characterizationSessionDelegate{}, store, &characterizationSandboxRemover{}, nil) first, err := handler.ListSandboxes(context.Background(), connect.NewRequest(&agentcomposev2.ListSandboxesRequest{Limit: 1})) if err != nil || first.Msg.GetSandboxes()[0].GetSandboxId() != firstID || first.Msg.GetNextCursor() == "" { t.Fatalf("first ListSandboxes() = %#v, err=%v", first, err) } + if first.Msg.GetSandboxes()[0].GetNetworkState().GetMode() != "multi-network" { + t.Fatalf("first sandbox network state = %#v", first.Msg.GetSandboxes()[0].GetNetworkState()) + } second, err := handler.ListSandboxes(context.Background(), connect.NewRequest(&agentcomposev2.ListSandboxesRequest{Limit: 1, Cursor: first.Msg.GetNextCursor()})) if err != nil || second.Msg.GetSandboxes()[0].GetSandboxId() != secondID || second.Msg.GetNextCursor() != "" { t.Fatalf("second ListSandboxes() = %#v, err=%v", second, err) @@ -319,6 +324,11 @@ type characterizationSandboxStore struct { cells []domain.NotebookCell events []domain.SandboxEvent proxyState domain.ProxyState + vmStates map[string]domain.VMState +} + +func (s *characterizationSandboxStore) GetVMState(id string) (domain.VMState, error) { + return s.vmStates[id], nil } func (s *characterizationSandboxStore) GetProxyState(string) (domain.ProxyState, error) { diff --git a/pkg/agentcompose/app/project_controller.go b/pkg/agentcompose/app/project_controller.go index 1e4237dfb..d28b9e85f 100644 --- a/pkg/agentcompose/app/project_controller.go +++ b/pkg/agentcompose/app/project_controller.go @@ -39,6 +39,7 @@ func NewProjectController(di do.Injector) (*projects.Controller, error) { Images: imageBackends.Auto, Loaders: do.MustInvoke[*loaders.Controller](di), Volumes: do.MustInvoke[*volumes.Manager](di), + Networks: adapters.NewDockerProjectNetworkCleaner(), StopSandbox: func(ctx context.Context, session *domain.Sandbox) error { return stopProjectSandbox(ctx, sessionStore, sandboxDriver, streams, session) }, diff --git a/pkg/compose/coverage_shape_workflows_test.go b/pkg/compose/coverage_shape_workflows_test.go index 8f979a0f6..26374995b 100644 --- a/pkg/compose/coverage_shape_workflows_test.go +++ b/pkg/compose/coverage_shape_workflows_test.go @@ -11,18 +11,16 @@ func TestIntegrationComposeParseNormalizeAndOutputWorkflows(t *testing.T) { t.Run("agent capsets", TestNormalizeAgentCapsetIDs) t.Run("valid agent names", TestNormalizePreservesValidAgentNames) t.Run("invalid agent name", TestNormalizeRejectsInvalidAgentName) - t.Run("driver and network defaults", TestNormalizeDefaultsDriverAndNetwork) + t.Run("driver default", TestNormalizeDefaultsDriver) t.Run("agent model interpolation", TestNormalizeInterpolatesAgentModelFromEnvironment) t.Run("missing model environment", TestNormalizeRequiresAgentModelEnvironmentReference) t.Run("empty driver", TestNormalizeRejectsEmptyDriver) t.Run("multiple drivers", TestNormalizeRejectsMultipleDrivers) t.Run("firecracker driver", TestNormalizeRejectsFirecrackerDriver) - t.Run("supported driver", TestNormalizeAcceptsSupportedDriverAndDefaultNetwork) - t.Run("empty network default", TestNormalizeAcceptsEmptyNetworkAsDefault) + t.Run("supported driver", TestNormalizeAcceptsSupportedDriver) t.Run("jupyter config", TestNormalizePreservesJupyterConfig) t.Run("jupyter defaults", TestNormalizeDropsDefaultJupyterConfig) t.Run("invalid jupyter port", TestNormalizeRejectsInvalidJupyterGuestPort) - t.Run("unsupported network", TestNormalizeRejectsUnsupportedNetwork) t.Run("invalid trigger", TestNormalizeRejectsInvalidTrigger) t.Run("scheduler script", TestNormalizePreservesSchedulerScript) t.Run("scheduler script URL snapshot", TestNormalizeResolvesSchedulerScriptURLSnapshot) @@ -37,6 +35,7 @@ func TestIntegrationComposeParseNormalizeAndOutputWorkflows(t *testing.T) { t.Run("duplicate agent keys", TestParseRejectsDuplicateAgentKeys) t.Run("parse minimal", TestParseMinimalSpec) t.Run("parse full", TestParseFullSpec) + t.Run("parse legacy network", TestParseRejectsLegacyNetworkField) t.Run("parse scheduler script", TestParseSchedulerScript) t.Run("parse scheduler script URL", TestParseSchedulerScriptURL) t.Run("parse invalid scheduler script URL", TestParseRejectsInvalidSchedulerScriptURLObject) @@ -73,6 +72,11 @@ func TestIntegrationComposeParseNormalizeAndOutputWorkflows(t *testing.T) { t.Run("normalize invalid mcp ref", TestNormalizeRejectsUndefinedMCPRef) t.Run("normalize invalid mcp shape", TestNormalizeRejectsInvalidMCPShape) t.Run("redacted mcp", TestRedactedOutputDoesNotLeakMCPSecrets) + t.Run("parse named networks", TestParseNamedNetworksAndAgentNetworkFields) + t.Run("normalize named networks", TestNormalizeNamedNetworksAndPorts) + t.Run("baseline without named networks", TestNormalizeWithoutNamedNetworksPreservesBaseline) + t.Run("dynamic port idempotence", TestNormalizePublishedPortIsIdempotentForDynamicHostPort) + t.Run("network validation", TestNormalizeNetworkValidation) } func TestE2EComposeParseNormalizeAndOutputWorkflows(t *testing.T) { diff --git a/pkg/compose/helper_coverage_test.go b/pkg/compose/helper_coverage_test.go index 0272eaba7..9f945a275 100644 --- a/pkg/compose/helper_coverage_test.go +++ b/pkg/compose/helper_coverage_test.go @@ -46,13 +46,6 @@ func TestComposePrivateHelperCoverage(t *testing.T) { t.Fatalf("cloneNormalizedBuildSpec nil returned non-nil") } - if cloneNetworkSpecForOutput(nil) != nil { - t.Fatalf("cloneNetworkSpecForOutput nil returned non-nil") - } - network := cloneNetworkSpecForOutput(&NetworkSpec{Mode: "bridge"}) - if network.Mode != "bridge" { - t.Fatalf("cloneNetworkSpecForOutput = %#v", network) - } if compareString("a", "b") >= 0 || compareString("b", "a") <= 0 || compareString("a", "a") != 0 { t.Fatalf("compareString returned unexpected ordering") } diff --git a/pkg/compose/network_config.go b/pkg/compose/network_config.go new file mode 100644 index 000000000..1a9e1b757 --- /dev/null +++ b/pkg/compose/network_config.go @@ -0,0 +1,204 @@ +package compose + +import ( + "fmt" + "net" + "strconv" + "strings" +) + +const defaultNamedNetwork = "default" + +func normalizeNamedNetworkSpecs(values map[string]NamedNetworkSpec) (map[string]NamedNetworkSpec, error) { + if len(values) == 0 { + return nil, nil + } + normalized := make(map[string]NamedNetworkSpec, len(values)) + for rawName, value := range values { + name := strings.TrimSpace(rawName) + if err := validateStableIdentifier(joinPath("networks", rawName), name, "network name"); err != nil { + return nil, err + } + if _, exists := normalized[name]; exists { + return nil, &ValidationError{Path: joinPath("networks", rawName), Message: fmt.Sprintf("duplicate network %q", name)} + } + driver := strings.ToLower(strings.TrimSpace(value.Driver)) + if driver == "" { + driver = "bridge" + } + if driver != "bridge" { + return nil, &ValidationError{Path: joinPath("networks", name) + ".driver", Message: fmt.Sprintf("unsupported network driver %q; only bridge is supported", driver)} + } + normalized[name] = NamedNetworkSpec{Driver: driver} + } + return normalized, nil +} + +func normalizeNetworkAttachments(path string, values []string) ([]string, error) { + if len(values) == 0 { + return nil, nil + } + seen := make(map[string]struct{}, len(values)) + normalized := make([]string, 0, len(values)) + for index, raw := range values { + name := strings.TrimSpace(raw) + itemPath := fmt.Sprintf("%s[%d]", path, index) + if err := validateStableIdentifier(itemPath, name, "network name"); err != nil { + return nil, err + } + if _, exists := seen[name]; exists { + return nil, &ValidationError{Path: itemPath, Message: fmt.Sprintf("duplicate network %q", name)} + } + seen[name] = struct{}{} + normalized = append(normalized, name) + } + return normalized, nil +} + +func normalizeExposedPorts(path string, values []string) ([]string, error) { + return normalizePortList(path, values, normalizeExposedPort) +} + +func normalizePublishedPorts(path string, values []string) ([]string, error) { + return normalizePortList(path, values, normalizePublishedPort) +} + +func normalizePortList(path string, values []string, normalize func(string) (string, error)) ([]string, error) { + if len(values) == 0 { + return nil, nil + } + seen := make(map[string]struct{}, len(values)) + items := make([]string, 0, len(values)) + for index, raw := range values { + itemPath := fmt.Sprintf("%s[%d]", path, index) + item, err := normalize(strings.TrimSpace(raw)) + if err != nil { + return nil, &ValidationError{Path: itemPath, Message: err.Error()} + } + if _, exists := seen[item]; exists { + return nil, &ValidationError{Path: itemPath, Message: fmt.Sprintf("duplicate port %q", item)} + } + seen[item] = struct{}{} + items = append(items, item) + } + return items, nil +} + +func normalizeExposedPort(raw string) (string, error) { + port, protocol, err := parseContainerPort(raw) + if err != nil { + return "", err + } + return strconv.Itoa(port) + "/" + protocol, nil +} + +func normalizePublishedPort(raw string) (string, error) { + parts := strings.Split(raw, ":") + var hostIP, hostPortRaw, containerPortRaw string + switch len(parts) { + case 1: + hostIP = "127.0.0.1" + containerPortRaw = parts[0] + case 2: + hostIP = "127.0.0.1" + hostPortRaw = parts[0] + containerPortRaw = parts[1] + case 3: + hostIP = strings.TrimSpace(parts[0]) + hostPortRaw = strings.TrimSpace(parts[1]) + containerPortRaw = parts[2] + default: + return "", fmt.Errorf("published port must be container, host:container, or host_ip:host:container") + } + if net.ParseIP(hostIP) == nil { + return "", fmt.Errorf("host IP %q is invalid", hostIP) + } + hostPort := 0 + if hostPortRaw != "" && hostPortRaw != "0" { + var err error + hostPort, err = parseTCPPort(hostPortRaw) + if err != nil { + return "", fmt.Errorf("invalid host port: %w", err) + } + } + containerPort, protocol, err := parseContainerPort(containerPortRaw) + if err != nil { + return "", fmt.Errorf("invalid container port: %w", err) + } + return fmt.Sprintf("%s:%d:%d/%s", hostIP, hostPort, containerPort, protocol), nil +} + +func parseContainerPort(raw string) (int, string, error) { + portRaw, protocol, found := strings.Cut(strings.TrimSpace(raw), "/") + if !found { + protocol = "tcp" + } + protocol = strings.ToLower(strings.TrimSpace(protocol)) + if protocol != "tcp" { + return 0, "", fmt.Errorf("unsupported port protocol %q; only tcp is supported", protocol) + } + port, err := parseTCPPort(portRaw) + if err != nil { + return 0, "", err + } + return port, protocol, nil +} + +func parseTCPPort(raw string) (int, error) { + port, err := strconv.Atoi(strings.TrimSpace(raw)) + if err != nil || port < 1 || port > 65535 { + return 0, fmt.Errorf("port must be between 1 and 65535") + } + return port, nil +} + +func validateProjectNetworkUsage(project *NormalizedProjectSpec) error { + if project == nil { + return nil + } + hasNamedNetworks := len(project.Networks) > 0 + needsDefault := false + if hasNamedNetworks { + for _, agent := range project.Agents { + if len(agent.Networks) == 0 { + needsDefault = true + break + } + } + if needsDefault { + if _, exists := project.Networks[defaultNamedNetwork]; !exists { + project.Networks[defaultNamedNetwork] = NamedNetworkSpec{Driver: "bridge"} + } + } + } + for index := range project.Agents { + agent := &project.Agents[index] + path := joinPath("agents", agent.Name) + if len(agent.Expose) > 0 || len(agent.Ports) > 0 { + if agent.Driver == nil || agent.Driver.Name != DriverDocker { + return &ValidationError{Path: path + ".driver", Message: "expose and ports require the docker driver"} + } + } + if !hasNamedNetworks { + if len(agent.Networks) > 0 { + return &ValidationError{Path: path + ".networks", Message: "agent networks require top-level networks"} + } + continue + } + if agent.Driver == nil || agent.Driver.Name != DriverDocker { + return &ValidationError{Path: path + ".driver", Message: "project named networks require every agent to use the docker driver"} + } + if len(agent.Networks) == 0 { + agent.Networks = []string{defaultNamedNetwork} + } + for attachmentIndex, name := range agent.Networks { + if _, exists := project.Networks[name]; !exists { + return &ValidationError{ + Path: fmt.Sprintf("%s.networks[%d]", path, attachmentIndex), + Message: fmt.Sprintf("network %q is not defined", name), + } + } + } + } + return nil +} diff --git a/pkg/compose/network_config_test.go b/pkg/compose/network_config_test.go new file mode 100644 index 000000000..d7ec957df --- /dev/null +++ b/pkg/compose/network_config_test.go @@ -0,0 +1,171 @@ +package compose + +import ( + "reflect" + "strings" + "testing" +) + +func TestParseNamedNetworksAndAgentNetworkFields(t *testing.T) { + spec, err := Parse([]byte(`name: demo +networks: + frontend: {} + backend: + driver: bridge +agents: + api: + networks: [frontend, backend] + expose: ["8080"] + ports: ["18080:8080"] +`)) + if err != nil { + t.Fatalf("Parse returned error: %v", err) + } + if len(spec.Networks) != 2 || spec.Networks["backend"].Driver != "bridge" { + t.Fatalf("networks = %#v", spec.Networks) + } + agent := spec.Agents["api"] + if !reflect.DeepEqual(agent.Networks, []string{"frontend", "backend"}) || + !reflect.DeepEqual(agent.Expose, []string{"8080"}) || + !reflect.DeepEqual(agent.Ports, []string{"18080:8080"}) { + t.Fatalf("agent network fields = %#v / %#v / %#v", agent.Networks, agent.Expose, agent.Ports) + } +} + +func TestNormalizeNamedNetworksAndPorts(t *testing.T) { + spec := &ProjectSpec{ + Name: "demo", + Networks: map[string]NamedNetworkSpec{ + "frontend": {}, + "backend": {Driver: "BRIDGE"}, + }, + Agents: map[string]AgentSpec{ + "api": { + Networks: []string{"frontend", "backend"}, + Expose: []string{"8080"}, + Ports: []string{"18080:8080", "0.0.0.0:19090:9090/tcp"}, + }, + "worker": {}, + }, + } + normalized, err := Normalize(spec, NormalizeOptions{}) + if err != nil { + t.Fatalf("Normalize returned error: %v", err) + } + if normalized.Networks["frontend"].Driver != "bridge" || normalized.Networks["backend"].Driver != "bridge" || normalized.Networks["default"].Driver != "bridge" { + t.Fatalf("normalized networks = %#v", normalized.Networks) + } + if len(normalized.Agents) != 2 || normalized.Agents[0].Name != "api" || normalized.Agents[1].Name != "worker" { + t.Fatalf("normalized agents = %#v", normalized.Agents) + } + api := normalized.Agents[0] + if strings.Join(api.Networks, ",") != "frontend,backend" { + t.Fatalf("api networks = %#v", api.Networks) + } + if strings.Join(api.Expose, ",") != "8080/tcp" { + t.Fatalf("api expose = %#v", api.Expose) + } + if strings.Join(api.Ports, ",") != "127.0.0.1:18080:8080/tcp,0.0.0.0:19090:9090/tcp" { + t.Fatalf("api ports = %#v", api.Ports) + } + if strings.Join(normalized.Agents[1].Networks, ",") != "default" { + t.Fatalf("worker networks = %#v", normalized.Agents[1].Networks) + } + canonical, err := normalized.MarshalCanonicalJSON(false) + if err != nil { + t.Fatalf("MarshalCanonicalJSON returned error: %v", err) + } + for _, want := range []string{`"name":"backend","driver":"bridge"`, `"networks":["frontend","backend"]`, `"ports":["127.0.0.1:18080:8080/tcp"`} { + if !strings.Contains(string(canonical), want) { + t.Fatalf("canonical JSON %s missing %s", canonical, want) + } + } +} + +func TestNormalizeWithoutNamedNetworksPreservesBaseline(t *testing.T) { + normalized, err := Normalize(&ProjectSpec{Name: "demo", Agents: map[string]AgentSpec{"worker": {}}}, NormalizeOptions{}) + if err != nil { + t.Fatalf("Normalize returned error: %v", err) + } + if normalized.Networks != nil || normalized.Agents[0].Networks != nil { + t.Fatalf("baseline project unexpectedly gained named networks: %#v / %#v", normalized.Networks, normalized.Agents[0].Networks) + } +} + +func TestNormalizePublishedPortIsIdempotentForDynamicHostPort(t *testing.T) { + first, err := normalizePublishedPorts("agents.api.ports", []string{"8080"}) + if err != nil { + t.Fatalf("first normalization returned error: %v", err) + } + second, err := normalizePublishedPorts("agents.api.ports", first) + if err != nil { + t.Fatalf("second normalization returned error: %v", err) + } + if !reflect.DeepEqual(first, []string{"127.0.0.1:0:8080/tcp"}) || !reflect.DeepEqual(second, first) { + t.Fatalf("published ports first=%#v second=%#v", first, second) + } +} + +func TestNormalizeNetworkValidation(t *testing.T) { + tests := []struct { + name string + spec *ProjectSpec + want string + }{ + { + name: "unsupported driver", + spec: &ProjectSpec{Name: "demo", Networks: map[string]NamedNetworkSpec{"frontend": {Driver: "overlay"}}}, + want: "only bridge is supported", + }, + { + name: "missing top level", + spec: &ProjectSpec{Name: "demo", Agents: map[string]AgentSpec{"worker": {Networks: []string{"frontend"}}}}, + want: "agent networks require top-level networks", + }, + { + name: "undefined attachment", + spec: &ProjectSpec{Name: "demo", Networks: map[string]NamedNetworkSpec{"frontend": {}}, Agents: map[string]AgentSpec{"worker": {Networks: []string{"backend"}}}}, + want: `network "backend" is not defined`, + }, + { + name: "duplicate attachment", + spec: &ProjectSpec{Name: "demo", Networks: map[string]NamedNetworkSpec{"frontend": {}}, Agents: map[string]AgentSpec{"worker": {Networks: []string{"frontend", "frontend"}}}}, + want: `duplicate network "frontend"`, + }, + { + name: "non docker agent", + spec: &ProjectSpec{Name: "demo", Networks: map[string]NamedNetworkSpec{"frontend": {}}, Agents: map[string]AgentSpec{"worker": {Driver: &DriverSpec{Boxlite: &BoxliteDriverSpec{}}}}}, + want: "require every agent to use the docker driver", + }, + { + name: "invalid exposed protocol", + spec: &ProjectSpec{Name: "demo", Agents: map[string]AgentSpec{"worker": {Expose: []string{"53/udp"}}}}, + want: "only tcp is supported", + }, + { + name: "invalid published host", + spec: &ProjectSpec{Name: "demo", Agents: map[string]AgentSpec{"worker": {Ports: []string{"localhost:8080:80"}}}}, + want: `host IP "localhost" is invalid`, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := Normalize(tc.spec, NormalizeOptions{}) + if err == nil || !strings.Contains(err.Error(), tc.want) { + t.Fatalf("Normalize error = %v, want containing %q", err, tc.want) + } + }) + } +} + +func TestIntegrationNamedNetworkConfigurationWorkflow(t *testing.T) { + TestParseNamedNetworksAndAgentNetworkFields(t) + TestNormalizeNamedNetworksAndPorts(t) + TestNormalizeWithoutNamedNetworksPreservesBaseline(t) + TestNormalizePublishedPortIsIdempotentForDynamicHostPort(t) + TestNormalizeNetworkValidation(t) +} + +func TestE2ENamedNetworkConfigurationWorkflow(t *testing.T) { + TestIntegrationNamedNetworkConfigurationWorkflow(t) +} diff --git a/pkg/compose/normalize.go b/pkg/compose/normalize.go index c6a435b84..f4801475f 100644 --- a/pkg/compose/normalize.go +++ b/pkg/compose/normalize.go @@ -43,7 +43,7 @@ type NormalizedProjectSpec struct { MCPServers map[string]NormalizedMCPServerSpec `yaml:"mcp_servers,omitempty" json:"mcp_servers,omitempty"` Volumes map[string]NormalizedVolumeSpec `yaml:"volumes,omitempty" json:"volumes,omitempty"` Agents []NormalizedAgentSpec `yaml:"agents,omitempty" json:"agents,omitempty"` - Network *NetworkSpec `yaml:"network,omitempty" json:"network,omitempty"` + Networks map[string]NamedNetworkSpec `yaml:"networks,omitempty" json:"networks,omitempty"` } type NormalizedAgentSpec struct { @@ -63,6 +63,9 @@ type NormalizedAgentSpec struct { Workspace *WorkspaceSpec `yaml:"workspace,omitempty" json:"workspace,omitempty"` Scheduler *NormalizedSchedulerSpec `yaml:"scheduler,omitempty" json:"scheduler,omitempty"` Jupyter *JupyterSpec `yaml:"jupyter,omitempty" json:"jupyter,omitempty"` + Networks []string `yaml:"networks,omitempty" json:"networks,omitempty"` + Expose []string `yaml:"expose,omitempty" json:"expose,omitempty"` + Ports []string `yaml:"ports,omitempty" json:"ports,omitempty"` } type NormalizedMCPServerSpec struct { @@ -165,7 +168,6 @@ func Normalize(spec *ProjectSpec, options NormalizeOptions) (*NormalizedProjectS if spec == nil { return nil, &ValidationError{Message: "spec is required"} } - name := strings.TrimSpace(spec.Name) if name == "" { name = defaultProjectName(options) @@ -177,8 +179,12 @@ func Normalize(spec *ProjectSpec, options NormalizeOptions) (*NormalizedProjectS normalized := &NormalizedProjectSpec{ Name: name, Variables: nil, - Network: normalizeNetworkDefault(spec.Network), } + networks, err := normalizeNamedNetworkSpecs(spec.Networks) + if err != nil { + return nil, err + } + normalized.Networks = networks variables, err := normalizeEnvVarMap("variables", spec.Variables, options) if err != nil { return nil, err @@ -194,9 +200,6 @@ func Normalize(spec *ProjectSpec, options NormalizeOptions) (*NormalizedProjectS return nil, err } normalized.MCPServers = mcps - if err := validateNetworkSpec(normalized.Network); err != nil { - return nil, err - } volumes, err := normalizeProjectVolumes(spec.Volumes) if err != nil { return nil, err @@ -220,6 +223,9 @@ func Normalize(spec *ProjectSpec, options NormalizeOptions) (*NormalizedProjectS } normalized.Agents = append(normalized.Agents, normalizedAgent) } + if err := validateProjectNetworkUsage(normalized); err != nil { + return nil, err + } return normalized, nil } @@ -281,6 +287,18 @@ func normalizeAgent(name string, agent AgentSpec, options NormalizeOptions, proj if err != nil { return NormalizedAgentSpec{}, err } + networks, err := normalizeNetworkAttachments(joinPath("agents", name)+".networks", agent.Networks) + if err != nil { + return NormalizedAgentSpec{}, err + } + expose, err := normalizeExposedPorts(joinPath("agents", name)+".expose", agent.Expose) + if err != nil { + return NormalizedAgentSpec{}, err + } + ports, err := normalizePublishedPorts(joinPath("agents", name)+".ports", agent.Ports) + if err != nil { + return NormalizedAgentSpec{}, err + } return NormalizedAgentSpec{ Name: name, Status: status, @@ -298,6 +316,9 @@ func normalizeAgent(name string, agent AgentSpec, options NormalizeOptions, proj Workspace: workspace, Scheduler: scheduler, Jupyter: jupyter, + Networks: networks, + Expose: expose, + Ports: ports, }, nil } @@ -1220,19 +1241,6 @@ func validatePositiveDuration(path string, value string) error { return nil } -func validateNetworkSpec(network *NetworkSpec) error { - if network == nil { - return nil - } - mode := strings.TrimSpace(network.Mode) - network.Mode = mode - if mode == "" || mode == "default" { - network.Mode = "default" - return nil - } - return &ValidationError{Path: "network.mode", Message: fmt.Sprintf("unsupported network mode %q; only default is supported", mode)} -} - func validateStableIdentifier(path string, value string, label string) error { if value == "" { return &ValidationError{Path: path, Message: label + " is required"} @@ -1261,14 +1269,6 @@ func defaultProjectName(options NormalizeOptions) string { return filepath.Base(filepath.Clean(dir)) } -func normalizeNetworkDefault(value *NetworkSpec) *NetworkSpec { - if value == nil { - return &NetworkSpec{Mode: "default"} - } - cloned := *value - return &cloned -} - func normalizeEnvVarMap(path string, values map[string]EnvVarSpec, options NormalizeOptions) (map[string]EnvVarSpec, error) { if len(values) == 0 { return nil, nil diff --git a/pkg/compose/normalize_test.go b/pkg/compose/normalize_test.go index 3191b8b54..7fb59c1f7 100644 --- a/pkg/compose/normalize_test.go +++ b/pkg/compose/normalize_test.go @@ -487,7 +487,7 @@ func TestNormalizeRejectsInvalidAgentName(t *testing.T) { } } -func TestNormalizeDefaultsDriverAndNetwork(t *testing.T) { +func TestNormalizeDefaultsDriver(t *testing.T) { spec := mustParseCompose(t, ` name: defaults agents: @@ -499,9 +499,6 @@ agents: if err != nil { t.Fatalf("Normalize returned error: %v", err) } - if normalized.Network == nil || normalized.Network.Mode != "default" { - t.Fatalf("network = %#v, want default", normalized.Network) - } if got := normalized.Agents[0].Driver; got == nil || got.Name != DriverDocker || got.Docker == nil { t.Fatalf("driver = %#v, want default docker", got) } @@ -598,11 +595,9 @@ agents: } } -func TestNormalizeAcceptsSupportedDriverAndDefaultNetwork(t *testing.T) { +func TestNormalizeAcceptsSupportedDriver(t *testing.T) { spec := mustParseCompose(t, ` name: supported-driver -network: - mode: default agents: reviewer: driver: @@ -614,32 +609,11 @@ agents: if err != nil { t.Fatalf("Normalize returned error: %v", err) } - if normalized.Network == nil || normalized.Network.Mode != "default" { - t.Fatalf("network = %#v, want default", normalized.Network) - } if got := normalized.Agents[0].Driver; got == nil || got.Name != DriverMicrosandbox || got.Microsandbox.Profile != "secure" { t.Fatalf("driver = %#v", got) } } -func TestNormalizeAcceptsEmptyNetworkAsDefault(t *testing.T) { - spec := mustParseCompose(t, ` -name: empty-network -network: {} -agents: - reviewer: - provider: codex -`) - - normalized, err := Normalize(spec, NormalizeOptions{}) - if err != nil { - t.Fatalf("Normalize returned error: %v", err) - } - if normalized.Network == nil || normalized.Network.Mode != "default" { - t.Fatalf("network = %#v, want default", normalized.Network) - } -} - func TestNormalizePreservesJupyterConfig(t *testing.T) { spec := mustParseCompose(t, ` name: jupyter @@ -712,25 +686,6 @@ func TestNormalizeRejectsInvalidJupyterGuestPort(t *testing.T) { } } -func TestNormalizeRejectsUnsupportedNetwork(t *testing.T) { - spec := mustParseCompose(t, ` -name: unsupported-network -network: - mode: bridge -agents: - reviewer: - provider: codex -`) - - _, err := Normalize(spec, NormalizeOptions{}) - if err == nil { - t.Fatalf("expected Normalize to fail") - } - if got := err.Error(); !strings.Contains(got, "network.mode") || !strings.Contains(got, "unsupported") { - t.Fatalf("error = %q, want network mode error", got) - } -} - func TestNormalizeRejectsInvalidTrigger(t *testing.T) { spec := mustParseCompose(t, ` name: invalid-trigger diff --git a/pkg/compose/output.go b/pkg/compose/output.go index 6a938d697..2ba981d8a 100644 --- a/pkg/compose/output.go +++ b/pkg/compose/output.go @@ -24,7 +24,12 @@ type orderedProjectSpec struct { MCPServers []orderedMCPServerSpec `yaml:"mcp_servers,omitempty" json:"mcp_servers,omitempty"` Volumes []orderedVolumeSpec `yaml:"volumes,omitempty" json:"volumes,omitempty"` Agents []orderedAgentSpec `yaml:"agents,omitempty" json:"agents,omitempty"` - Network *NetworkSpec `yaml:"network,omitempty" json:"network,omitempty"` + Networks []orderedNamedNetwork `yaml:"networks,omitempty" json:"networks,omitempty"` +} + +type orderedNamedNetwork struct { + Name string `yaml:"name" json:"name"` + Driver string `yaml:"driver" json:"driver"` } type orderedNamedWorkspace struct { @@ -53,6 +58,9 @@ type orderedAgentSpec struct { Workspace *WorkspaceSpec `yaml:"workspace,omitempty" json:"workspace,omitempty"` Scheduler *NormalizedSchedulerSpec `yaml:"scheduler,omitempty" json:"scheduler,omitempty"` Jupyter *JupyterSpec `yaml:"jupyter,omitempty" json:"jupyter,omitempty"` + Networks []string `yaml:"networks,omitempty" json:"networks,omitempty"` + Expose []string `yaml:"expose,omitempty" json:"expose,omitempty"` + Ports []string `yaml:"ports,omitempty" json:"ports,omitempty"` } type orderedMCPServerSpec struct { @@ -146,6 +154,9 @@ func (s *NormalizedProjectSpec) ordered(redactSecrets bool) orderedProjectSpec { Workspace: cloneWorkspaceSpec(agent.Workspace), Scheduler: cloneNormalizedSchedulerSpec(agent.Scheduler), Jupyter: cloneJupyterSpec(agent.Jupyter), + Networks: slices.Clone(agent.Networks), + Expose: slices.Clone(agent.Expose), + Ports: slices.Clone(agent.Ports), }) } slices.SortFunc(agents, func(a, b orderedAgentSpec) int { @@ -158,7 +169,7 @@ func (s *NormalizedProjectSpec) ordered(redactSecrets bool) orderedProjectSpec { MCPServers: mcps, Volumes: orderedVolumes(s.Volumes), Agents: agents, - Network: cloneNetworkSpecForOutput(s.Network), + Networks: orderedNamedNetworks(s.Networks), } } @@ -170,7 +181,7 @@ func (s *NormalizedProjectSpec) clone(redactSecrets bool) *NormalizedProjectSpec Workspaces: workspaceMapFromOrdered(ordered.Workspaces), MCPServers: mcpMapFromOrdered(ordered.MCPServers), Volumes: volumeMapFromOrdered(ordered.Volumes), - Network: ordered.Network, + Networks: namedNetworkMapFromOrdered(ordered.Networks), } for _, agent := range ordered.Agents { cloned.Agents = append(cloned.Agents, NormalizedAgentSpec{ @@ -190,11 +201,41 @@ func (s *NormalizedProjectSpec) clone(redactSecrets bool) *NormalizedProjectSpec Workspace: agent.Workspace, Scheduler: agent.Scheduler, Jupyter: agent.Jupyter, + Networks: slices.Clone(agent.Networks), + Expose: slices.Clone(agent.Expose), + Ports: slices.Clone(agent.Ports), }) } return cloned } +func orderedNamedNetworks(values map[string]NamedNetworkSpec) []orderedNamedNetwork { + if len(values) == 0 { + return nil + } + keys := make([]string, 0, len(values)) + for key := range values { + keys = append(keys, key) + } + slices.Sort(keys) + items := make([]orderedNamedNetwork, 0, len(keys)) + for _, key := range keys { + items = append(items, orderedNamedNetwork{Name: key, Driver: values[key].Driver}) + } + return items +} + +func namedNetworkMapFromOrdered(values []orderedNamedNetwork) map[string]NamedNetworkSpec { + if len(values) == 0 { + return nil + } + items := make(map[string]NamedNetworkSpec, len(values)) + for _, value := range values { + items[value.Name] = NamedNetworkSpec{Driver: value.Driver} + } + return items +} + func orderedWorkspaces(values map[string]WorkspaceSpec) []orderedNamedWorkspace { if len(values) == 0 { return nil @@ -436,14 +477,6 @@ func cloneNormalizedTriggerSpec(value NormalizedTriggerSpec) NormalizedTriggerSp return cloned } -func cloneNetworkSpecForOutput(value *NetworkSpec) *NetworkSpec { - if value == nil { - return nil - } - cloned := *value - return &cloned -} - func cloneJupyterSpec(value *JupyterSpec) *JupyterSpec { if value == nil { return nil diff --git a/pkg/compose/output_test.go b/pkg/compose/output_test.go index 0d4027d24..fbaeebd4c 100644 --- a/pkg/compose/output_test.go +++ b/pkg/compose/output_test.go @@ -377,11 +377,8 @@ agents: - cron: "@hourly" reviewer: provider: codex -network: {} `, nil) second := mustNormalizeCompose(t, ` -network: - mode: default agents: reviewer: provider: codex @@ -580,8 +577,6 @@ agents: `, nil) explicit := mustNormalizeCompose(t, ` name: defaults-hash -network: - mode: default agents: reviewer: provider: codex diff --git a/pkg/compose/spec.go b/pkg/compose/spec.go index c37d74b15..c8fb7bb23 100644 --- a/pkg/compose/spec.go +++ b/pkg/compose/spec.go @@ -9,14 +9,14 @@ import ( ) type ProjectSpec struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - EnvFiles EnvFileSpec `yaml:"env_file,omitempty" json:"env_file,omitempty"` - Variables map[string]EnvVarSpec `yaml:"variables,omitempty" json:"variables,omitempty"` - Workspaces map[string]WorkspaceSpec `yaml:"workspaces,omitempty" json:"workspaces,omitempty"` - MCPServers map[string]MCPServerSpec `yaml:"mcp_servers,omitempty" json:"mcp_servers,omitempty"` - Volumes map[string]VolumeSpec `yaml:"volumes,omitempty" json:"volumes,omitempty"` - Agents map[string]AgentSpec `yaml:"agents,omitempty" json:"agents,omitempty"` - Network *NetworkSpec `yaml:"network,omitempty" json:"network,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + EnvFiles EnvFileSpec `yaml:"env_file,omitempty" json:"env_file,omitempty"` + Variables map[string]EnvVarSpec `yaml:"variables,omitempty" json:"variables,omitempty"` + Workspaces map[string]WorkspaceSpec `yaml:"workspaces,omitempty" json:"workspaces,omitempty"` + MCPServers map[string]MCPServerSpec `yaml:"mcp_servers,omitempty" json:"mcp_servers,omitempty"` + Volumes map[string]VolumeSpec `yaml:"volumes,omitempty" json:"volumes,omitempty"` + Agents map[string]AgentSpec `yaml:"agents,omitempty" json:"agents,omitempty"` + Networks map[string]NamedNetworkSpec `yaml:"networks,omitempty" json:"networks,omitempty"` } // EnvFileSpec lists dotenv files used while loading a project configuration. @@ -59,6 +59,9 @@ type AgentSpec struct { Workspace *WorkspaceSpec `yaml:"workspace,omitempty" json:"workspace,omitempty"` Scheduler *SchedulerSpec `yaml:"scheduler,omitempty" json:"scheduler,omitempty"` Jupyter *JupyterSpec `yaml:"jupyter,omitempty" json:"jupyter,omitempty"` + Networks []string `yaml:"networks,omitempty" json:"networks,omitempty"` + Expose []string `yaml:"expose,omitempty" json:"expose,omitempty"` + Ports []string `yaml:"ports,omitempty" json:"ports,omitempty"` } type AgentMCPEntriesSpec []AgentMCPEntrySpec @@ -204,8 +207,8 @@ type WorkspaceSpec struct { Path string `yaml:"path,omitempty" json:"path,omitempty"` } -type NetworkSpec struct { - Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` +type NamedNetworkSpec struct { + Driver string `yaml:"driver,omitempty" json:"driver,omitempty"` } type DriverSpec struct { @@ -472,7 +475,7 @@ func validateProjectNode(node *yaml.Node) error { "mcp_servers": validateMCPMap, "volumes": validateVolumeMap, "agents": validateAgentMap, - "network": validateNetwork, + "networks": validateNamedNetworkMap, }) } @@ -508,6 +511,19 @@ func validateAgent(node *yaml.Node, path string) error { "workspace": validateWorkspace, "scheduler": validateScheduler, "jupyter": validateJupyter, + "networks": validateStringList, + "expose": validateStringList, + "ports": validateStringList, + }) +} + +func validateNamedNetworkMap(node *yaml.Node, path string) error { + return validateNamedMap(node, path, validateNamedNetwork) +} + +func validateNamedNetwork(node *yaml.Node, path string) error { + return validateMapping(node, path, map[string]nodeValidator{ + "driver": validateScalar, }) } @@ -820,12 +836,6 @@ func validateEnvVar(node *yaml.Node, path string) error { } } -func validateNetwork(node *yaml.Node, path string) error { - return validateMapping(node, path, map[string]nodeValidator{ - "mode": validateScalar, - }) -} - func validateNamedMap(node *yaml.Node, path string, validateValue nodeValidator) error { if err := requireKind(node, path, yaml.MappingNode, "mapping"); err != nil { return err diff --git a/pkg/compose/spec_test.go b/pkg/compose/spec_test.go index e3347c21f..f0c877646 100644 --- a/pkg/compose/spec_test.go +++ b/pkg/compose/spec_test.go @@ -118,8 +118,6 @@ agents: - event: topic: git.push prompt: "Review changes from the incoming event." -network: - mode: default `)) if err != nil { t.Fatalf("Parse returned error: %v", err) @@ -149,8 +147,12 @@ network: if agent.Scheduler.Triggers[1].Event == nil || agent.Scheduler.Triggers[1].Event.Topic != "git.push" { t.Fatalf("event trigger = %#v", agent.Scheduler.Triggers[1]) } - if spec.Network == nil || spec.Network.Mode != "default" { - t.Fatalf("network = %#v", spec.Network) +} + +func TestParseRejectsLegacyNetworkField(t *testing.T) { + _, err := Parse([]byte("name: legacy-network\nnetwork:\n mode: default\n")) + if err == nil || !strings.Contains(err.Error(), "network") || !strings.Contains(err.Error(), "unknown field") { + t.Fatalf("Parse error = %v, want legacy network unknown-field error", err) } } diff --git a/pkg/driver/docker_network.go b/pkg/driver/docker_network.go new file mode 100644 index 000000000..d9f745651 --- /dev/null +++ b/pkg/driver/docker_network.go @@ -0,0 +1,286 @@ +package driver + +import ( + "context" + "fmt" + "sort" + "strconv" + "strings" + "time" + + containerapi "github.com/docker/docker/api/types/container" + networkapi "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + + "agent-compose/pkg/networks" +) + +func (r *dockerRuntime) networkPlanForSandbox(sandbox *Sandbox, topology dockerDaemonTopology, dockerClient *client.Client) (networks.Plan, error) { + baseline := strings.TrimSpace(string(topology.networkMode)) + if sandbox == nil || sandbox.NetworkIntent == nil { + return networks.BuildPlan(networks.Intent{}, baseline) + } + intent := sandbox.NetworkIntent + definitions := make(map[string]networks.Definition, len(intent.Definitions)) + for _, definition := range intent.Definitions { + definitions[definition.Name] = networks.Definition{Name: definition.Name, Driver: definition.Driver} + } + plan, err := networks.BuildPlan(networks.Intent{ + ProjectID: intent.ProjectID, + AgentName: intent.AgentName, + SandboxID: sandbox.Summary.ID, + Definitions: definitions, + Attachments: intent.Attachments, + }, baseline) + if err != nil { + return networks.Plan{}, fmt.Errorf("plan docker networks for sandbox %s: %w", sandbox.Summary.ID, err) + } + if plan.Mode != networks.ModeMultiNetwork { + return plan, nil + } + if !strings.HasPrefix(strings.TrimSpace(dockerClient.DaemonHost()), "unix://") { + return networks.Plan{}, fmt.Errorf("project named networks require a local Docker Engine, got %s", dockerClient.DaemonHost()) + } + switch topology.kind { + case dockerDaemonNative, dockerDaemonContainerHost: + return plan, nil + case dockerDaemonContainerBridge: + return networks.Plan{}, fmt.Errorf("project named networks add bridges beyond baseline %q; the agent-compose daemon must run natively or with Docker host networking", baseline) + default: + detail := strings.TrimSpace(topology.detail) + if detail == "" { + detail = string(topology.kind) + } + return networks.Plan{}, fmt.Errorf("cannot verify daemon topology for project named networks: %s", detail) + } +} + +func ensureDockerManagedNetworks(ctx context.Context, dockerClient *client.Client, plan networks.Plan) error { + if plan.Mode != networks.ModeMultiNetwork { + return nil + } + for _, attachment := range plan.Attachments { + if !attachment.Managed { + continue + } + if err := ensureDockerManagedNetwork(ctx, dockerClient, attachment); err != nil { + return err + } + } + return nil +} + +func ensureDockerManagedNetwork(ctx context.Context, dockerClient *client.Client, attachment networks.Attachment) error { + inspect, err := dockerClient.NetworkInspect(ctx, attachment.RuntimeName, networkapi.InspectOptions{}) + if err == nil { + return validateDockerManagedNetwork(inspect, attachment) + } + if !isDockerNotFound(err) { + return fmt.Errorf("inspect docker network %s: %w", attachment.RuntimeName, err) + } + enableIPv4 := true + enableIPv6 := false + _, createErr := dockerClient.NetworkCreate(ctx, attachment.RuntimeName, networkapi.CreateOptions{ + Driver: "bridge", + EnableIPv4: &enableIPv4, + EnableIPv6: &enableIPv6, + Internal: false, + Attachable: false, + Labels: cloneStringMap(attachment.Labels), + }) + if createErr != nil { + inspect, err = dockerClient.NetworkInspect(ctx, attachment.RuntimeName, networkapi.InspectOptions{}) + if err != nil { + return fmt.Errorf("create docker network %s: %w", attachment.RuntimeName, createErr) + } + return validateDockerManagedNetwork(inspect, attachment) + } + inspect, err = dockerClient.NetworkInspect(ctx, attachment.RuntimeName, networkapi.InspectOptions{}) + if err != nil { + return fmt.Errorf("inspect created docker network %s: %w", attachment.RuntimeName, err) + } + return validateDockerManagedNetwork(inspect, attachment) +} + +func validateDockerManagedNetwork(inspect networkapi.Inspect, attachment networks.Attachment) error { + if inspect.Driver != "bridge" || inspect.Internal || inspect.Scope != "local" || !inspect.EnableIPv4 || inspect.EnableIPv6 || inspect.Attachable || inspect.Ingress || inspect.ConfigOnly { + return fmt.Errorf("docker network %s does not match managed bridge properties", attachment.RuntimeName) + } + for key, expected := range attachment.Labels { + if inspect.Labels[key] != expected { + return fmt.Errorf("docker network %s ownership label %s does not match", attachment.RuntimeName, key) + } + } + return nil +} + +func dockerContainerNetworkingConfig(plan networks.Plan) *networkapi.NetworkingConfig { + if plan.Mode != networks.ModeMultiNetwork || len(plan.Attachments) == 0 { + return nil + } + primary := plan.Attachments[0] + return &networkapi.NetworkingConfig{EndpointsConfig: map[string]*networkapi.EndpointSettings{ + primary.RuntimeName: { + Aliases: append([]string(nil), primary.Aliases...), + GwPriority: primary.GatewayPriority, + }, + }} +} + +func ensureDockerContainerAttachments(ctx context.Context, dockerClient *client.Client, containerInfo containerapi.InspectResponse, plan networks.Plan) error { + if plan.Mode != networks.ModeMultiNetwork { + return nil + } + existing := map[string]*networkapi.EndpointSettings{} + if containerInfo.NetworkSettings != nil { + for name, endpoint := range containerInfo.NetworkSettings.Networks { + existing[name] = endpoint + } + } + expected := make(map[string]struct{}, len(plan.Attachments)) + for _, attachment := range plan.Attachments { + expected[attachment.RuntimeName] = struct{}{} + } + for networkName, endpoint := range existing { + if _, ok := expected[networkName]; ok { + continue + } + lookup := networkName + if endpoint != nil && endpoint.NetworkID != "" { + lookup = endpoint.NetworkID + } + networkInfo, err := dockerClient.NetworkInspect(ctx, lookup, networkapi.InspectOptions{}) + if isDockerNotFound(err) { + continue + } + if err != nil { + return fmt.Errorf("inspect unexpected docker network %s on container %s: %w", networkName, containerInfo.ID, err) + } + if networkInfo.Labels[networks.ManagedLabel] == "true" && networkInfo.Labels[networks.ResourceLabel] == networks.ProjectNetworkResource { + return fmt.Errorf("container %s has unexpected managed project network %s", containerInfo.ID, networkName) + } + } + for _, attachment := range plan.Attachments { + if endpoint := existing[attachment.RuntimeName]; endpoint != nil && endpoint.NetworkID != "" { + networkInfo, err := dockerClient.NetworkInspect(ctx, attachment.RuntimeName, networkapi.InspectOptions{}) + if err != nil { + return fmt.Errorf("inspect project network %s for container %s: %w", attachment.RuntimeName, containerInfo.ID, err) + } + if endpoint.NetworkID == networkInfo.ID { + continue + } + } + if err := dockerClient.NetworkConnect(ctx, attachment.RuntimeName, containerInfo.ID, &networkapi.EndpointSettings{ + Aliases: append([]string(nil), attachment.Aliases...), + GwPriority: attachment.GatewayPriority, + }); err != nil { + return fmt.Errorf("connect docker container %s to project network %s: %w", containerInfo.ID, attachment.RuntimeName, err) + } + } + return nil +} + +func mergeDockerNetworkPortConfig(exposed nat.PortSet, bindings nat.PortMap, intent *SandboxNetworkIntent) (nat.PortSet, nat.PortMap, error) { + if intent == nil || (len(intent.Expose) == 0 && len(intent.Ports) == 0) { + return exposed, bindings, nil + } + if exposed == nil { + exposed = nat.PortSet{} + } + if bindings == nil { + bindings = nat.PortMap{} + } + for _, raw := range intent.Expose { + port, err := parseCanonicalContainerPort(raw) + if err != nil { + return nil, nil, err + } + exposed[port] = struct{}{} + } + for _, raw := range intent.Ports { + parts := strings.Split(raw, ":") + if len(parts) != 3 { + return nil, nil, fmt.Errorf("invalid canonical published port %q", raw) + } + hostPort, err := strconv.Atoi(parts[1]) + if err != nil || hostPort < 0 || hostPort > 65535 { + return nil, nil, fmt.Errorf("invalid canonical host port %q", parts[1]) + } + port, err := parseCanonicalContainerPort(parts[2]) + if err != nil { + return nil, nil, err + } + exposed[port] = struct{}{} + hostPortValue := "" + if hostPort > 0 { + hostPortValue = strconv.Itoa(hostPort) + } + bindings[port] = append(bindings[port], nat.PortBinding{HostIP: parts[0], HostPort: hostPortValue}) + } + return exposed, bindings, nil +} + +func parseCanonicalContainerPort(raw string) (nat.Port, error) { + portRaw, protocol, ok := strings.Cut(strings.TrimSpace(raw), "/") + if !ok || protocol != "tcp" { + return "", fmt.Errorf("invalid canonical container port %q", raw) + } + port, err := strconv.Atoi(portRaw) + if err != nil || port < 1 || port > 65535 { + return "", fmt.Errorf("invalid canonical container port %q", raw) + } + return nat.NewPort(protocol, portRaw) +} + +func cloneStringMap(values map[string]string) map[string]string { + if len(values) == 0 { + return nil + } + cloned := make(map[string]string, len(values)) + for key, value := range values { + cloned[key] = value + } + return cloned +} + +func dockerSandboxNetworkState(containerInfo containerapi.InspectResponse, plan networks.Plan, configured bool) *SandboxNetworkState { + if !configured { + return nil + } + state := &SandboxNetworkState{Mode: string(plan.Mode), ReconciledAt: time.Now().UTC()} + for index, attachment := range plan.Attachments { + item := SandboxNetworkAttachmentState{ + LogicalName: attachment.LogicalName, + RuntimeName: attachment.RuntimeName, + Aliases: append([]string(nil), attachment.Aliases...), + Primary: index == 0, + } + if containerInfo.NetworkSettings != nil { + if endpoint := containerInfo.NetworkSettings.Networks[attachment.RuntimeName]; endpoint != nil { + item.NetworkID = endpoint.NetworkID + item.IPv4Address = endpoint.IPAddress + } + } + state.Attachments = append(state.Attachments, item) + } + if containerInfo.NetworkSettings == nil { + return state + } + ports := make([]string, 0, len(containerInfo.NetworkSettings.Ports)) + for port := range containerInfo.NetworkSettings.Ports { + ports = append(ports, string(port)) + } + sort.Strings(ports) + for _, rawPort := range ports { + port := nat.Port(rawPort) + for _, binding := range containerInfo.NetworkSettings.Ports[port] { + state.PortBindings = append(state.PortBindings, SandboxPortBindingState{ + ContainerPort: rawPort, + HostIP: binding.HostIP, + HostPort: binding.HostPort, + }) + } + } + return state +} diff --git a/pkg/driver/docker_network_integration_test.go b/pkg/driver/docker_network_integration_test.go new file mode 100644 index 000000000..9c7c3adce --- /dev/null +++ b/pkg/driver/docker_network_integration_test.go @@ -0,0 +1,342 @@ +package driver + +import ( + "bytes" + "context" + "os" + "strings" + "testing" + "time" + + containerapi "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/docker/docker/pkg/stdcopy" + + "agent-compose/pkg/networks" +) + +func TestDockerManagedNetworkAttachmentIntegration(t *testing.T) { + ctx, dockerClient := dockerNetworkIntegrationClient(t) + projectID := "network-integration-" + strings.ToLower(time.Now().UTC().Format("20060102t150405.000000000")) + plan, err := networks.BuildPlan(networks.Intent{ + ProjectID: projectID, + AgentName: "api", + SandboxID: "sandbox-integration", + Definitions: map[string]networks.Definition{ + "frontend": {Name: "frontend", Driver: "bridge"}, + "backend": {Name: "backend", Driver: "bridge"}, + }, + Attachments: []string{"frontend", "backend"}, + }, "bridge") + if err != nil { + t.Fatalf("BuildPlan returned error: %v", err) + } + for _, attachment := range plan.Attachments { + attachment := attachment + t.Cleanup(func() { _ = dockerClient.NetworkRemove(context.Background(), attachment.RuntimeName) }) + } + if err := ensureDockerManagedNetworks(ctx, dockerClient, plan); err != nil { + t.Fatalf("ensureDockerManagedNetworks returned error: %v", err) + } + create, err := dockerClient.ContainerCreate(ctx, + &containerapi.Config{Image: "alpine:3.20", Cmd: []string{"sleep", "30"}}, + &containerapi.HostConfig{NetworkMode: containerapi.NetworkMode(plan.Attachments[0].RuntimeName)}, + dockerContainerNetworkingConfig(plan), nil, "", + ) + if err != nil { + t.Fatalf("create integration container: %v", err) + } + t.Cleanup(func() { + _ = dockerClient.ContainerRemove(context.Background(), create.ID, containerapi.RemoveOptions{Force: true}) + }) + inspect, err := dockerClient.ContainerInspect(ctx, create.ID) + if err != nil { + t.Fatalf("inspect integration container: %v", err) + } + if err := ensureDockerContainerAttachments(ctx, dockerClient, inspect, plan); err != nil { + t.Fatalf("ensureDockerContainerAttachments returned error: %v", err) + } + if err := dockerClient.ContainerStart(ctx, create.ID, containerapi.StartOptions{}); err != nil { + t.Fatalf("start integration container: %v", err) + } + inspect, err = dockerClient.ContainerInspect(ctx, create.ID) + if err != nil { + t.Fatalf("inspect started integration container: %v", err) + } + if inspect.NetworkSettings == nil || len(inspect.NetworkSettings.Networks) != 2 { + t.Fatalf("container networks = %#v", inspect.NetworkSettings) + } + for _, attachment := range plan.Attachments { + endpoint := inspect.NetworkSettings.Networks[attachment.RuntimeName] + if endpoint == nil || endpoint.NetworkID == "" || endpoint.IPAddress == "" { + t.Fatalf("endpoint %s = %#v", attachment.RuntimeName, endpoint) + } + } + state := dockerSandboxNetworkState(inspect, plan, true) + if state == nil || len(state.Attachments) != 2 || !state.Attachments[0].Primary || state.Attachments[1].Primary { + t.Fatalf("network state = %#v", state) + } +} + +func TestDockerManagedNetworkIsolationIntegration(t *testing.T) { + ctx, dockerClient := dockerNetworkIntegrationClient(t) + projectID := "network-isolation-" + strings.ToLower(time.Now().UTC().Format("20060102t150405.000000000")) + definitions := map[string]networks.Definition{ + "frontend": {Name: "frontend", Driver: "bridge"}, + "backend": {Name: "backend", Driver: "bridge"}, + } + buildPlan := func(agentName, sandboxID string, attachments ...string) networks.Plan { + t.Helper() + plan, err := networks.BuildPlan(networks.Intent{ + ProjectID: projectID, + AgentName: agentName, + SandboxID: sandboxID, + Definitions: definitions, + Attachments: attachments, + }, "bridge") + if err != nil { + t.Fatalf("BuildPlan(%s) returned error: %v", agentName, err) + } + return plan + } + + apiPlan := buildPlan("api", "sandbox-api", "frontend", "backend") + frontendPlan := buildPlan("frontend-client", "sandbox-frontend", "frontend") + backendPlan := buildPlan("backend-client", "sandbox-backend", "backend") + for _, attachment := range apiPlan.Attachments { + attachment := attachment + t.Cleanup(func() { _ = dockerClient.NetworkRemove(context.Background(), attachment.RuntimeName) }) + } + apiID := createDockerNetworkIntegrationContainer(t, ctx, dockerClient, apiPlan) + frontendID := createDockerNetworkIntegrationContainer(t, ctx, dockerClient, frontendPlan) + backendID := createDockerNetworkIntegrationContainer(t, ctx, dockerClient, backendPlan) + + assertDockerExecResult(t, ctx, dockerClient, frontendID, true, "ping", "-c", "1", "-W", "2", "api") + assertDockerExecResult(t, ctx, dockerClient, backendID, true, "ping", "-c", "1", "-W", "2", "api") + assertDockerExecResult(t, ctx, dockerClient, apiID, true, "ping", "-c", "1", "-W", "2", "frontend-client") + assertDockerExecResult(t, ctx, dockerClient, apiID, true, "ping", "-c", "1", "-W", "2", "backend-client") + assertDockerExecResult(t, ctx, dockerClient, frontendID, false, "ping", "-c", "1", "-W", "2", "backend-client") + assertDockerExecResult(t, ctx, dockerClient, backendID, false, "ping", "-c", "1", "-W", "2", "frontend-client") +} + +func TestDockerManagedNetworkReconcileAfterRecreationIntegration(t *testing.T) { + ctx, dockerClient := dockerNetworkIntegrationClient(t) + projectID := "network-reconcile-" + strings.ToLower(time.Now().UTC().Format("20060102t150405.000000000")) + plan, err := networks.BuildPlan(networks.Intent{ + ProjectID: projectID, + AgentName: "api", + SandboxID: "sandbox-reconcile", + Definitions: map[string]networks.Definition{ + "frontend": {Name: "frontend", Driver: "bridge"}, + "backend": {Name: "backend", Driver: "bridge"}, + }, + Attachments: []string{"frontend", "backend"}, + }, "bridge") + if err != nil { + t.Fatalf("BuildPlan returned error: %v", err) + } + for _, attachment := range plan.Attachments { + attachment := attachment + t.Cleanup(func() { _ = dockerClient.NetworkRemove(context.Background(), attachment.RuntimeName) }) + } + containerID := createDockerNetworkIntegrationContainer(t, ctx, dockerClient, plan) + stopTimeout := 1 + if err := dockerClient.ContainerStop(ctx, containerID, containerapi.StopOptions{Timeout: &stopTimeout}); err != nil { + t.Fatalf("stop integration container: %v", err) + } + for _, attachment := range plan.Attachments { + if err := dockerClient.NetworkDisconnect(ctx, attachment.RuntimeName, containerID, false); err != nil { + t.Fatalf("disconnect integration container from %s: %v", attachment.RuntimeName, err) + } + if err := dockerClient.NetworkRemove(ctx, attachment.RuntimeName); err != nil { + t.Fatalf("remove integration network %s: %v", attachment.RuntimeName, err) + } + } + if err := ensureDockerManagedNetworks(ctx, dockerClient, plan); err != nil { + t.Fatalf("recreate managed networks: %v", err) + } + containerInfo, err := dockerClient.ContainerInspect(ctx, containerID) + if err != nil { + t.Fatalf("inspect stopped integration container: %v", err) + } + if err := ensureDockerContainerAttachments(ctx, dockerClient, containerInfo, plan); err != nil { + t.Fatalf("reconcile recreated network attachments: %v", err) + } + if err := dockerClient.ContainerStart(ctx, containerID, containerapi.StartOptions{}); err != nil { + t.Fatalf("restart integration container: %v", err) + } + containerInfo, err = dockerClient.ContainerInspect(ctx, containerID) + if err != nil { + t.Fatalf("inspect restarted integration container: %v", err) + } + for _, attachment := range plan.Attachments { + endpoint := containerInfo.NetworkSettings.Networks[attachment.RuntimeName] + if endpoint == nil || endpoint.NetworkID == "" || endpoint.IPAddress == "" { + t.Fatalf("reconciled endpoint %s = %#v", attachment.RuntimeName, endpoint) + } + } +} + +func TestDockerManagedNetworkReconcileRejectsUnexpectedManagedAttachmentIntegration(t *testing.T) { + ctx, dockerClient := dockerNetworkIntegrationClient(t) + projectID := "network-drift-" + strings.ToLower(time.Now().UTC().Format("20060102t150405.000000000")) + definitions := map[string]networks.Definition{ + "frontend": {Name: "frontend", Driver: "bridge"}, + "backend": {Name: "backend", Driver: "bridge"}, + } + frontendPlan, err := networks.BuildPlan(networks.Intent{ + ProjectID: projectID, AgentName: "api", SandboxID: "sandbox-drift", + Definitions: definitions, Attachments: []string{"frontend"}, + }, "bridge") + if err != nil { + t.Fatalf("BuildPlan frontend returned error: %v", err) + } + backendPlan, err := networks.BuildPlan(networks.Intent{ + ProjectID: projectID, AgentName: "worker", SandboxID: "sandbox-worker", + Definitions: definitions, Attachments: []string{"backend"}, + }, "bridge") + if err != nil { + t.Fatalf("BuildPlan backend returned error: %v", err) + } + for _, plan := range []networks.Plan{frontendPlan, backendPlan} { + for _, attachment := range plan.Attachments { + attachment := attachment + t.Cleanup(func() { _ = dockerClient.NetworkRemove(context.Background(), attachment.RuntimeName) }) + } + } + containerID := createDockerNetworkIntegrationContainer(t, ctx, dockerClient, frontendPlan) + if err := ensureDockerManagedNetworks(ctx, dockerClient, backendPlan); err != nil { + t.Fatalf("ensure backend network: %v", err) + } + if err := dockerClient.NetworkConnect(ctx, backendPlan.Attachments[0].RuntimeName, containerID, nil); err != nil { + t.Fatalf("attach unexpected managed network: %v", err) + } + containerInfo, err := dockerClient.ContainerInspect(ctx, containerID) + if err != nil { + t.Fatalf("inspect drifted integration container: %v", err) + } + if err := ensureDockerContainerAttachments(ctx, dockerClient, containerInfo, frontendPlan); err == nil || !strings.Contains(err.Error(), "unexpected managed project network") { + t.Fatalf("reconcile drift error = %v", err) + } +} + +func TestDockerBaselinePortBindingIntegration(t *testing.T) { + ctx, dockerClient := dockerNetworkIntegrationClient(t) + plan, err := networks.BuildPlan(networks.Intent{}, "bridge") + if err != nil { + t.Fatalf("BuildPlan returned error: %v", err) + } + intent := &SandboxNetworkIntent{ + Expose: []string{"9090/tcp"}, + Ports: []string{"127.0.0.1:0:8080/tcp"}, + } + exposed, bindings, err := mergeDockerNetworkPortConfig(nil, nil, intent) + if err != nil { + t.Fatalf("mergeDockerNetworkPortConfig returned error: %v", err) + } + created, err := dockerClient.ContainerCreate(ctx, + &containerapi.Config{Image: "alpine:3.20", Cmd: []string{"sleep", "30"}, ExposedPorts: exposed}, + &containerapi.HostConfig{NetworkMode: "bridge", PortBindings: bindings}, + nil, nil, "", + ) + if err != nil { + t.Fatalf("create baseline port integration container: %v", err) + } + t.Cleanup(func() { + _ = dockerClient.ContainerRemove(context.Background(), created.ID, containerapi.RemoveOptions{Force: true}) + }) + if err := dockerClient.ContainerStart(ctx, created.ID, containerapi.StartOptions{}); err != nil { + t.Fatalf("start baseline port integration container: %v", err) + } + containerInfo, err := dockerClient.ContainerInspect(ctx, created.ID) + if err != nil { + t.Fatalf("inspect baseline port integration container: %v", err) + } + if _, ok := containerInfo.Config.ExposedPorts["9090/tcp"]; !ok { + t.Fatalf("exposed ports = %#v", containerInfo.Config.ExposedPorts) + } + state := dockerSandboxNetworkState(containerInfo, plan, true) + if state == nil || state.Mode != string(networks.ModeSingleBridge) || len(state.PortBindings) != 1 { + t.Fatalf("baseline network state = %#v", state) + } + binding := state.PortBindings[0] + if binding.ContainerPort != "8080/tcp" || binding.HostIP != "127.0.0.1" || binding.HostPort == "" { + t.Fatalf("dynamic port binding = %#v", binding) + } +} + +func dockerNetworkIntegrationClient(t *testing.T) (context.Context, *client.Client) { + t.Helper() + if os.Getenv("AGENT_COMPOSE_DOCKER_NETWORK_TEST") != "1" { + t.Skip("set AGENT_COMPOSE_DOCKER_NETWORK_TEST=1 to run Docker network integration") + } + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + t.Cleanup(cancel) + dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + t.Fatalf("connect Docker Engine: %v", err) + } + t.Cleanup(func() { _ = dockerClient.Close() }) + if _, err := dockerClient.Ping(ctx); err != nil { + t.Fatalf("ping Docker Engine: %v", err) + } + return ctx, dockerClient +} + +func createDockerNetworkIntegrationContainer(t *testing.T, ctx context.Context, dockerClient *client.Client, plan networks.Plan) string { + t.Helper() + if err := ensureDockerManagedNetworks(ctx, dockerClient, plan); err != nil { + t.Fatalf("ensureDockerManagedNetworks returned error: %v", err) + } + create, err := dockerClient.ContainerCreate(ctx, + &containerapi.Config{Image: "alpine:3.20", Cmd: []string{"sleep", "30"}}, + &containerapi.HostConfig{NetworkMode: containerapi.NetworkMode(plan.Attachments[0].RuntimeName)}, + dockerContainerNetworkingConfig(plan), nil, "", + ) + if err != nil { + t.Fatalf("create integration container: %v", err) + } + t.Cleanup(func() { + _ = dockerClient.ContainerRemove(context.Background(), create.ID, containerapi.RemoveOptions{Force: true}) + }) + inspect, err := dockerClient.ContainerInspect(ctx, create.ID) + if err != nil { + t.Fatalf("inspect integration container: %v", err) + } + if err := ensureDockerContainerAttachments(ctx, dockerClient, inspect, plan); err != nil { + t.Fatalf("ensureDockerContainerAttachments returned error: %v", err) + } + if err := dockerClient.ContainerStart(ctx, create.ID, containerapi.StartOptions{}); err != nil { + t.Fatalf("start integration container: %v", err) + } + return create.ID +} + +func assertDockerExecResult(t *testing.T, ctx context.Context, dockerClient *client.Client, containerID string, wantSuccess bool, command ...string) { + t.Helper() + execResponse, err := dockerClient.ContainerExecCreate(ctx, containerID, containerapi.ExecOptions{ + AttachStdout: true, + AttachStderr: true, + Cmd: command, + }) + if err != nil { + t.Fatalf("create Docker exec %q: %v", command, err) + } + attach, err := dockerClient.ContainerExecAttach(ctx, execResponse.ID, containerapi.ExecAttachOptions{}) + if err != nil { + t.Fatalf("attach Docker exec %q: %v", command, err) + } + defer attach.Close() + var stdout, stderr bytes.Buffer + if _, err := stdcopy.StdCopy(&stdout, &stderr, attach.Reader); err != nil { + t.Fatalf("read Docker exec %q: %v", command, err) + } + inspect, err := dockerClient.ContainerExecInspect(ctx, execResponse.ID) + if err != nil { + t.Fatalf("inspect Docker exec %q: %v", command, err) + } + succeeded := inspect.ExitCode == 0 + if succeeded != wantSuccess { + t.Fatalf("Docker exec %q exit code = %d, stdout = %q, stderr = %q, want success = %t", command, inspect.ExitCode, stdout.String(), stderr.String(), wantSuccess) + } +} diff --git a/pkg/driver/docker_network_test.go b/pkg/driver/docker_network_test.go new file mode 100644 index 000000000..7dfd9d9ed --- /dev/null +++ b/pkg/driver/docker_network_test.go @@ -0,0 +1,422 @@ +package driver + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "sync" + "testing" + + containerapi "github.com/docker/docker/api/types/container" + networkapi "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + + "agent-compose/pkg/networks" +) + +func TestDockerNetworkPlanKeepsBaselineWithoutIntent(t *testing.T) { + runtime := &dockerRuntime{} + dockerClient, err := client.NewClientWithOpts(client.WithHost("unix:///var/run/docker.sock")) + if err != nil { + t.Fatalf("NewClientWithOpts returned error: %v", err) + } + plan, err := runtime.networkPlanForSandbox(&Sandbox{}, dockerDaemonTopology{ + networkMode: containerapi.NetworkMode("compose_default"), + containerized: true, + kind: dockerDaemonContainerBridge, + }, dockerClient) + if err != nil { + t.Fatalf("networkPlanForSandbox returned error: %v", err) + } + if plan.RequiresDaemonHost || plan.BaselineNetwork != "compose_default" { + t.Fatalf("baseline plan = %#v", plan) + } +} + +func TestDockerNetworkPlanRejectsAdditionalBridgeForBridgeDaemon(t *testing.T) { + runtime := &dockerRuntime{} + dockerClient, err := client.NewClientWithOpts(client.WithHost("unix:///var/run/docker.sock")) + if err != nil { + t.Fatalf("NewClientWithOpts returned error: %v", err) + } + sandbox := &Sandbox{ + Summary: SandboxSummary{ID: "sandbox-1"}, + NetworkIntent: &SandboxNetworkIntent{ + ProjectID: "project-1", + AgentName: "api", + Definitions: []SandboxNetworkDefinition{{Name: "frontend", Driver: "bridge"}}, + Attachments: []string{"frontend"}, + }, + } + _, err = runtime.networkPlanForSandbox(sandbox, dockerDaemonTopology{ + networkMode: containerapi.NetworkMode("compose_default"), + containerized: true, + kind: dockerDaemonContainerBridge, + }, dockerClient) + if err == nil || !strings.Contains(err.Error(), "must run natively or with Docker host networking") { + t.Fatalf("networkPlanForSandbox error = %v", err) + } + for _, kind := range []dockerDaemonTopologyKind{dockerDaemonNative, dockerDaemonContainerHost} { + plan, planErr := runtime.networkPlanForSandbox(sandbox, dockerDaemonTopology{networkMode: "default", kind: kind}, dockerClient) + if planErr != nil || !plan.RequiresDaemonHost { + t.Fatalf("networkPlanForSandbox kind %s = %#v, %v", kind, plan, planErr) + } + } +} + +func TestDockerNetworkPlanRejectsRemoteAndUnknownDaemon(t *testing.T) { + runtime := &dockerRuntime{} + sandbox := &Sandbox{ + Summary: SandboxSummary{ID: "sandbox-1"}, + NetworkIntent: &SandboxNetworkIntent{ + ProjectID: "project-1", + AgentName: "api", + Definitions: []SandboxNetworkDefinition{{Name: "frontend", Driver: "bridge"}}, + Attachments: []string{"frontend"}, + }, + } + remoteClient, err := client.NewClientWithOpts(client.WithHost("tcp://docker.example.test:2376")) + if err != nil { + t.Fatalf("NewClientWithOpts remote returned error: %v", err) + } + if _, err := runtime.networkPlanForSandbox(sandbox, dockerDaemonTopology{networkMode: "default", kind: dockerDaemonNative}, remoteClient); err == nil || !strings.Contains(err.Error(), "require a local Docker Engine") { + t.Fatalf("remote Docker network plan error = %v", err) + } + + localClient, err := client.NewClientWithOpts(client.WithHost("unix:///var/run/docker.sock")) + if err != nil { + t.Fatalf("NewClientWithOpts local returned error: %v", err) + } + if _, err := runtime.networkPlanForSandbox(sandbox, dockerDaemonTopology{networkMode: "default", kind: dockerDaemonUnknown, detail: "self container unavailable"}, localClient); err == nil || !strings.Contains(err.Error(), "self container unavailable") { + t.Fatalf("unknown topology network plan error = %v", err) + } +} + +func TestMergeDockerNetworkPortConfig(t *testing.T) { + jupyterPort := nat.Port("8888/tcp") + exposed, bindings, err := mergeDockerNetworkPortConfig( + nat.PortSet{jupyterPort: {}}, + nat.PortMap{jupyterPort: []nat.PortBinding{{HostIP: "127.0.0.1", HostPort: "40000"}}}, + &SandboxNetworkIntent{ + Expose: []string{"8080/tcp"}, + Ports: []string{"127.0.0.1:18080:8080/tcp", "0.0.0.0:0:9090/tcp"}, + }, + ) + if err != nil { + t.Fatalf("mergeDockerNetworkPortConfig returned error: %v", err) + } + if len(exposed) != 3 || len(bindings[jupyterPort]) != 1 { + t.Fatalf("port config = %#v / %#v", exposed, bindings) + } + if got := bindings[nat.Port("8080/tcp")]; len(got) != 1 || got[0].HostIP != "127.0.0.1" || got[0].HostPort != "18080" { + t.Fatalf("8080 bindings = %#v", got) + } + if got := bindings[nat.Port("9090/tcp")]; len(got) != 1 || got[0].HostIP != "0.0.0.0" || got[0].HostPort != "" { + t.Fatalf("9090 bindings = %#v", got) + } +} + +func TestMergeDockerNetworkPortConfigRejectsInvalidCanonicalValues(t *testing.T) { + tests := []struct { + name string + intent *SandboxNetworkIntent + want string + }{ + {name: "expose protocol", intent: &SandboxNetworkIntent{Expose: []string{"53/udp"}}, want: "invalid canonical container port"}, + {name: "expose range", intent: &SandboxNetworkIntent{Expose: []string{"70000/tcp"}}, want: "invalid canonical container port"}, + {name: "published shape", intent: &SandboxNetworkIntent{Ports: []string{"8080/tcp"}}, want: "invalid canonical published port"}, + {name: "published host port", intent: &SandboxNetworkIntent{Ports: []string{"127.0.0.1:70000:8080/tcp"}}, want: "invalid canonical host port"}, + {name: "published container port", intent: &SandboxNetworkIntent{Ports: []string{"127.0.0.1:0:0/tcp"}}, want: "invalid canonical container port"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if _, _, err := mergeDockerNetworkPortConfig(nil, nil, tc.intent); err == nil || !strings.Contains(err.Error(), tc.want) { + t.Fatalf("mergeDockerNetworkPortConfig error = %v, want containing %q", err, tc.want) + } + }) + } + if exposed, bindings, err := mergeDockerNetworkPortConfig(nil, nil, nil); err != nil || exposed != nil || bindings != nil { + t.Fatalf("nil intent result = %#v / %#v / %v", exposed, bindings, err) + } +} + +func TestDockerSandboxNetworkStateIncludesPortOnlyBaseline(t *testing.T) { + plan, err := networks.BuildPlan(networks.Intent{}, "bridge") + if err != nil { + t.Fatalf("BuildPlan returned error: %v", err) + } + port := nat.Port("8080/tcp") + networkSettings := &containerapi.NetworkSettings{ + Networks: map[string]*networkapi.EndpointSettings{ + "bridge": {NetworkID: "network-1", IPAddress: "172.17.0.2"}, + }, + } + networkSettings.Ports = nat.PortMap{ + port: []nat.PortBinding{{HostIP: "127.0.0.1", HostPort: "49152"}}, + } + state := dockerSandboxNetworkState(containerapi.InspectResponse{ + NetworkSettings: networkSettings, + }, plan, true) + if state == nil || state.Mode != string(networks.ModeSingleBridge) || len(state.Attachments) != 1 || state.Attachments[0].NetworkID != "network-1" { + t.Fatalf("network state = %#v", state) + } + if len(state.PortBindings) != 1 || state.PortBindings[0].ContainerPort != "8080/tcp" || state.PortBindings[0].HostPort != "49152" { + t.Fatalf("port binding state = %#v", state.PortBindings) + } + if dockerSandboxNetworkState(containerapi.InspectResponse{}, plan, false) != nil { + t.Fatal("unconfigured baseline unexpectedly returned network state") + } +} + +func TestDockerContainerNetworkingConfigUsesPrimaryAttachment(t *testing.T) { + if config := dockerContainerNetworkingConfig(networks.Plan{Mode: networks.ModeSingleBridge}); config != nil { + t.Fatalf("single-bridge networking config = %#v, want nil", config) + } + plan := networks.Plan{Mode: networks.ModeMultiNetwork, Attachments: []networks.Attachment{ + {RuntimeName: "frontend", Aliases: []string{"api", "api-sandbox"}, GatewayPriority: 100}, + {RuntimeName: "backend", Aliases: []string{"api"}}, + }} + config := dockerContainerNetworkingConfig(plan) + if config == nil || len(config.EndpointsConfig) != 1 { + t.Fatalf("multi-network config = %#v", config) + } + endpoint := config.EndpointsConfig["frontend"] + if endpoint == nil || endpoint.GwPriority != 100 || len(endpoint.Aliases) != 2 || endpoint.Aliases[0] != "api" { + t.Fatalf("primary endpoint = %#v", endpoint) + } + endpoint.Aliases[0] = "mutated" + if plan.Attachments[0].Aliases[0] != "api" { + t.Fatalf("networking config aliases mutated plan: %#v", plan.Attachments[0].Aliases) + } +} + +func TestValidateDockerManagedNetworkRejectsPropertyAndOwnershipDrift(t *testing.T) { + attachment := networks.Attachment{ + RuntimeName: "project_frontend", + Labels: map[string]string{ + networks.ManagedLabel: "true", + networks.ProjectIDLabel: "project-1", + }, + } + valid := networkapi.Inspect{ + Driver: "bridge", + Scope: "local", + EnableIPv4: true, + Labels: map[string]string{ + networks.ManagedLabel: "true", + networks.ProjectIDLabel: "project-1", + }, + } + if err := validateDockerManagedNetwork(valid, attachment); err != nil { + t.Fatalf("validateDockerManagedNetwork valid error = %v", err) + } + drifted := valid + drifted.EnableIPv6 = true + if err := validateDockerManagedNetwork(drifted, attachment); err == nil { + t.Fatal("validateDockerManagedNetwork accepted IPv6 drift") + } + drifted = valid + drifted.Labels = map[string]string{ + networks.ManagedLabel: "true", + networks.ProjectIDLabel: "other-project", + } + if err := validateDockerManagedNetwork(drifted, attachment); err == nil { + t.Fatal("validateDockerManagedNetwork accepted ownership drift") + } +} + +func TestDockerManagedNetworkLifecycleViaAPI(t *testing.T) { + ctx := context.Background() + var mu sync.Mutex + inspectByName := map[string]networkapi.Inspect{} + createCalls := 0 + connectCalls := make([]string, 0) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + path := req.URL.Path + switch { + case req.Method == http.MethodGet && strings.Contains(path, "/networks/"): + name := path[strings.LastIndex(path, "/")+1:] + mu.Lock() + inspect, ok := inspectByName[name] + mu.Unlock() + if !ok { + w.WriteHeader(http.StatusNotFound) + _ = json.NewEncoder(w).Encode(map[string]string{"message": "network not found"}) + return + } + _ = json.NewEncoder(w).Encode(inspect) + case req.Method == http.MethodPost && strings.HasSuffix(path, "/networks/create"): + var options struct { + Name string `json:"Name"` + Driver string `json:"Driver"` + EnableIPv4 *bool `json:"EnableIPv4"` + Labels map[string]string `json:"Labels"` + } + if err := json.NewDecoder(req.Body).Decode(&options); err != nil { + t.Errorf("decode network create request: %v", err) + w.WriteHeader(http.StatusBadRequest) + return + } + mu.Lock() + createCalls++ + inspectByName[options.Name] = networkapi.Inspect{ + Name: options.Name, + ID: "id-" + options.Name, + Driver: options.Driver, + Scope: "local", + EnableIPv4: options.EnableIPv4 != nil && *options.EnableIPv4, + Labels: options.Labels, + } + mu.Unlock() + _ = json.NewEncoder(w).Encode(networkapi.CreateResponse{ID: "id-" + options.Name}) + case req.Method == http.MethodPost && strings.Contains(path, "/networks/") && strings.HasSuffix(path, "/connect"): + parts := strings.Split(path, "/") + name := parts[len(parts)-2] + mu.Lock() + connectCalls = append(connectCalls, name) + mu.Unlock() + w.WriteHeader(http.StatusOK) + default: + t.Errorf("unexpected Docker API request: %s %s", req.Method, path) + w.WriteHeader(http.StatusNotFound) + } + })) + defer server.Close() + + dockerClient, err := client.NewClientWithOpts(client.WithHost(server.URL), client.WithVersion("1.43")) + if err != nil { + t.Fatalf("NewClientWithOpts returned error: %v", err) + } + defer func() { _ = dockerClient.Close() }() + + labels := map[string]string{ + networks.ManagedLabel: "true", + networks.ResourceLabel: networks.ProjectNetworkResource, + networks.ProjectIDLabel: "project-1", + networks.LogicalNameLabel: "frontend", + } + frontend := networks.Attachment{LogicalName: "frontend", RuntimeName: "frontend", Driver: "bridge", Managed: true, Labels: labels} + plan := networks.Plan{Mode: networks.ModeMultiNetwork, Attachments: []networks.Attachment{frontend}} + if err := ensureDockerManagedNetworks(ctx, dockerClient, plan); err != nil { + t.Fatalf("ensureDockerManagedNetworks create returned error: %v", err) + } + if err := ensureDockerManagedNetworks(ctx, dockerClient, plan); err != nil { + t.Fatalf("ensureDockerManagedNetworks reuse returned error: %v", err) + } + mu.Lock() + gotCreateCalls := createCalls + mu.Unlock() + if gotCreateCalls != 1 { + t.Fatalf("network create calls = %d, want 1", gotCreateCalls) + } + + backend := frontend + backend.LogicalName = "backend" + backend.RuntimeName = "backend" + backend.Aliases = []string{"api"} + backend.Labels = cloneStringMap(labels) + backend.Labels[networks.LogicalNameLabel] = "backend" + mu.Lock() + inspectByName["backend"] = networkapi.Inspect{Name: "backend", ID: "id-backend", Driver: "bridge", Scope: "local", EnableIPv4: true, Labels: backend.Labels} + mu.Unlock() + plan.Attachments = append(plan.Attachments, backend) + containerInfo := containerapi.InspectResponse{ + ContainerJSONBase: &containerapi.ContainerJSONBase{ID: "container-1"}, + NetworkSettings: &containerapi.NetworkSettings{Networks: map[string]*networkapi.EndpointSettings{ + "frontend": {NetworkID: "id-frontend"}, + }}, + } + if err := ensureDockerContainerAttachments(ctx, dockerClient, containerInfo, plan); err != nil { + t.Fatalf("ensureDockerContainerAttachments returned error: %v", err) + } + mu.Lock() + gotConnectCalls := append([]string(nil), connectCalls...) + mu.Unlock() + if len(gotConnectCalls) != 1 || gotConnectCalls[0] != "backend" { + t.Fatalf("network connect calls = %#v, want backend", gotConnectCalls) + } + + mu.Lock() + inspectByName["id-rogue"] = networkapi.Inspect{ID: "id-rogue", Labels: map[string]string{ + networks.ManagedLabel: "true", + networks.ResourceLabel: networks.ProjectNetworkResource, + }} + mu.Unlock() + containerInfo.NetworkSettings.Networks["rogue"] = &networkapi.EndpointSettings{NetworkID: "id-rogue"} + if err := ensureDockerContainerAttachments(ctx, dockerClient, containerInfo, plan); err == nil || !strings.Contains(err.Error(), "unexpected managed project network") { + t.Fatalf("unexpected managed attachment error = %v", err) + } +} + +func TestDockerManagedNetworkCreateRaceViaAPI(t *testing.T) { + ctx := context.Background() + var inspectCalls int + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch { + case req.Method == http.MethodGet && strings.Contains(req.URL.Path, "/networks/frontend"): + inspectCalls++ + if inspectCalls == 1 { + w.WriteHeader(http.StatusNotFound) + _ = json.NewEncoder(w).Encode(map[string]string{"message": "network not found"}) + return + } + _ = json.NewEncoder(w).Encode(networkapi.Inspect{ + Name: "frontend", ID: "network-frontend", Driver: "bridge", Scope: "local", EnableIPv4: true, + Labels: map[string]string{networks.ManagedLabel: "true", networks.ResourceLabel: networks.ProjectNetworkResource}, + }) + case req.Method == http.MethodPost && strings.HasSuffix(req.URL.Path, "/networks/create"): + w.WriteHeader(http.StatusConflict) + _ = json.NewEncoder(w).Encode(map[string]string{"message": "network already exists"}) + default: + t.Errorf("unexpected Docker API request: %s %s", req.Method, req.URL.Path) + w.WriteHeader(http.StatusNotFound) + } + })) + defer server.Close() + + dockerClient, err := client.NewClientWithOpts(client.WithHost(server.URL), client.WithVersion("1.43")) + if err != nil { + t.Fatalf("NewClientWithOpts returned error: %v", err) + } + defer func() { _ = dockerClient.Close() }() + attachment := networks.Attachment{ + RuntimeName: "frontend", + Managed: true, + Labels: map[string]string{ + networks.ManagedLabel: "true", + networks.ResourceLabel: networks.ProjectNetworkResource, + }, + } + if err := ensureDockerManagedNetworks(ctx, dockerClient, networks.Plan{Mode: networks.ModeMultiNetwork, Attachments: []networks.Attachment{attachment}}); err != nil { + t.Fatalf("ensureDockerManagedNetworks race returned error: %v", err) + } + if inspectCalls != 2 { + t.Fatalf("network inspect calls = %d, want 2", inspectCalls) + } + if err := ensureDockerManagedNetworks(ctx, dockerClient, networks.Plan{Mode: networks.ModeSingleBridge}); err != nil { + t.Fatalf("single-bridge ensure returned error: %v", err) + } +} + +func TestIntegrationDockerNamedNetworkPlanningWorkflow(t *testing.T) { + TestDockerNetworkPlanKeepsBaselineWithoutIntent(t) + TestDockerNetworkPlanRejectsAdditionalBridgeForBridgeDaemon(t) + TestDockerNetworkPlanRejectsRemoteAndUnknownDaemon(t) + TestMergeDockerNetworkPortConfig(t) + TestMergeDockerNetworkPortConfigRejectsInvalidCanonicalValues(t) + TestDockerSandboxNetworkStateIncludesPortOnlyBaseline(t) + TestDockerContainerNetworkingConfigUsesPrimaryAttachment(t) + TestValidateDockerManagedNetworkRejectsPropertyAndOwnershipDrift(t) + TestDockerContainerIDFromMountInfo(t) + TestDockerManagedNetworkLifecycleViaAPI(t) + TestDockerManagedNetworkCreateRaceViaAPI(t) +} + +func TestE2EDockerNamedNetworkPlanningWorkflow(t *testing.T) { + TestIntegrationDockerNamedNetworkPlanningWorkflow(t) +} diff --git a/pkg/driver/docker_runtime.go b/pkg/driver/docker_runtime.go index 5f6e04cc5..3443a5580 100644 --- a/pkg/driver/docker_runtime.go +++ b/pkg/driver/docker_runtime.go @@ -18,6 +18,8 @@ import ( "sync" "time" + "agent-compose/pkg/networks" + cerrdefs "github.com/containerd/errdefs" dockertypes "github.com/docker/docker/api/types" containerapi "github.com/docker/docker/api/types/container" @@ -30,9 +32,10 @@ import ( ) const ( - dockerSandboxLabelPrefix = "agent-compose" - dockerSandboxLabelID = dockerSandboxLabelPrefix + ".sandbox_id" - dockerSandboxLabelDriver = dockerSandboxLabelPrefix + ".driver" + dockerSandboxLabelPrefix = "agent-compose" + dockerSandboxLabelID = dockerSandboxLabelPrefix + ".sandbox_id" + dockerSandboxLabelDriver = dockerSandboxLabelPrefix + ".driver" + dockerSandboxLabelProject = dockerSandboxLabelPrefix + ".project_id" dockerStopAPIMargin = 5 * time.Second dockerStopFallbackActionTimeout = 5 * time.Second @@ -45,8 +48,19 @@ type dockerRuntime struct { type dockerDaemonTopology struct { networkMode containerapi.NetworkMode containerized bool + kind dockerDaemonTopologyKind + detail string } +type dockerDaemonTopologyKind string + +const ( + dockerDaemonNative dockerDaemonTopologyKind = "native-host" + dockerDaemonContainerHost dockerDaemonTopologyKind = "container-host-network" + dockerDaemonContainerBridge dockerDaemonTopologyKind = "container-bridge-network" + dockerDaemonUnknown dockerDaemonTopologyKind = "remote-or-unknown" +) + type dockerExecCollector struct { stream ExecStreamWriter filter *execOutputFilter @@ -137,10 +151,24 @@ func (r *dockerRuntime) EnsureSandbox(ctx context.Context, sandbox *Sandbox, vmS defer func() { _ = dockerClient.Close() }() topology := r.dockerDaemonTopology(ctx, dockerClient) - containerInfo, _, err := r.getOrCreateContainer(ctx, dockerClient, sandbox, vmState, proxyState, topology.networkMode) + networkPlan, err := r.networkPlanForSandbox(sandbox, topology, dockerClient) if err != nil { return SandboxVMInfo{}, err } + if err := ensureDockerManagedNetworks(ctx, dockerClient, networkPlan); err != nil { + return SandboxVMInfo{}, err + } + networkMode := topology.networkMode + if networkPlan.Mode == networks.ModeMultiNetwork { + networkMode = containerapi.NetworkMode(networkPlan.Attachments[0].RuntimeName) + } + containerInfo, _, err := r.getOrCreateContainer(ctx, dockerClient, sandbox, vmState, proxyState, networkMode, networkPlan) + if err != nil { + return SandboxVMInfo{}, err + } + if err := ensureDockerContainerAttachments(ctx, dockerClient, containerInfo, networkPlan); err != nil { + return SandboxVMInfo{}, err + } if containerInfo.State == nil || !containerInfo.State.Running { if err := dockerClient.ContainerStart(ctx, containerInfo.ID, containerapi.StartOptions{}); err != nil { return SandboxVMInfo{}, fmt.Errorf("start docker container %s: %w", containerInfo.ID, err) @@ -159,8 +187,9 @@ func (r *dockerRuntime) EnsureSandbox(ctx context.Context, sandbox *Sandbox, vmS if err != nil { return SandboxVMInfo{}, err } + networkState := dockerSandboxNetworkState(containerInfo, networkPlan, sandbox.NetworkIntent != nil) if !jupyterEnabled(proxyState) { - return SandboxVMInfo{BoxID: containerInfo.ID, ProxyState: &proxyState}, nil + return SandboxVMInfo{BoxID: containerInfo.ID, ProxyState: &proxyState, NetworkState: networkState}, nil } readyCtx, cancel := context.WithTimeout(ctx, r.config.JupyterReadyTimeout) @@ -168,7 +197,7 @@ func (r *dockerRuntime) EnsureSandbox(ctx context.Context, sandbox *Sandbox, vmS cancel() if readyErr != nil { if logText := readSandboxJupyterLog(sandbox); jupyterLogIndicatesReady(logText) { - return SandboxVMInfo{BoxID: containerInfo.ID, JupyterURL: jupyterDirectURL(proxyState), ProxyState: &proxyState}, nil + return SandboxVMInfo{BoxID: containerInfo.ID, JupyterURL: jupyterDirectURL(proxyState), ProxyState: &proxyState, NetworkState: networkState}, nil } if logText := readSandboxJupyterLog(sandbox); logText != "" { return SandboxVMInfo{}, fmt.Errorf("%w\nGuest log:\n%s", readyErr, logText) @@ -179,7 +208,7 @@ func (r *dockerRuntime) EnsureSandbox(ctx context.Context, sandbox *Sandbox, vmS return SandboxVMInfo{}, readyErr } - return SandboxVMInfo{BoxID: containerInfo.ID, JupyterURL: jupyterDirectURL(proxyState), ProxyState: &proxyState}, nil + return SandboxVMInfo{BoxID: containerInfo.ID, JupyterURL: jupyterDirectURL(proxyState), ProxyState: &proxyState, NetworkState: networkState}, nil } func (r *dockerRuntime) IsSandboxAlive(ctx context.Context, sandbox *Sandbox, vmState VMState) (bool, error) { @@ -657,7 +686,7 @@ func (r *dockerRuntime) newClient() (*client.Client, error) { return dockerClient, nil } -func (r *dockerRuntime) getOrCreateContainer(ctx context.Context, dockerClient *client.Client, sandbox *Sandbox, vmState VMState, proxyState ProxyState, networkMode containerapi.NetworkMode) (containerapi.InspectResponse, bool, error) { +func (r *dockerRuntime) getOrCreateContainer(ctx context.Context, dockerClient *client.Client, sandbox *Sandbox, vmState VMState, proxyState ProxyState, networkMode containerapi.NetworkMode, networkPlan networks.Plan) (containerapi.InspectResponse, bool, error) { appconfig.ApplyDefaultGuestPaths(r.config) if containerInfo, ok, err := r.findContainer(ctx, dockerClient, sandbox, vmState); err != nil { return containerapi.InspectResponse{}, false, err @@ -683,6 +712,10 @@ func (r *dockerRuntime) getOrCreateContainer(ctx context.Context, dockerClient * exposedPorts, portBindings = dockerJupyterPortConfig(proxyState.GuestPort) cmdText = jupyterLaunchCommand(r.config, proxyState, false) } + exposedPorts, portBindings, err = mergeDockerNetworkPortConfig(exposedPorts, portBindings, sandbox.NetworkIntent) + if err != nil { + return containerapi.InspectResponse{}, false, fmt.Errorf("configure docker ports for sandbox %s: %w", sandbox.Summary.ID, err) + } containerConfig := &containerapi.Config{ Image: resolveSandboxGuestImage(vmState.Image, sandbox.Summary.GuestImage, defaultGuestImageForDriver(r.config, RuntimeDriverDocker)), WorkingDir: r.config.GuestWorkspacePath, @@ -695,8 +728,11 @@ func (r *dockerRuntime) getOrCreateContainer(ctx context.Context, dockerClient * dockerSandboxLabelDriver: RuntimeDriverDocker, }, } + if sandbox.NetworkIntent != nil { + containerConfig.Labels[dockerSandboxLabelProject] = strings.TrimSpace(sandbox.NetworkIntent.ProjectID) + } hostConfig := dockerSandboxHostConfig(mounts, portBindings, networkMode) - createResp, err := dockerClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, name) + createResp, err := dockerClient.ContainerCreate(ctx, containerConfig, hostConfig, dockerContainerNetworkingConfig(networkPlan), nil, name) if err != nil { return containerapi.InspectResponse{}, false, fmt.Errorf("create docker container for sandbox %s: %w", sandbox.Summary.ID, err) } @@ -807,37 +843,86 @@ func SandboxStopContextTimeout(driver string, stopTimeout time.Duration) time.Du } func (r *dockerRuntime) dockerDaemonTopology(ctx context.Context, dockerClient *client.Client) dockerDaemonTopology { - networkName, ok, err := r.dockerNetworkNameFromSelfContainer(ctx, dockerClient) + containerInfo, ok, err := r.dockerSelfContainer(ctx, dockerClient) if err != nil { slog.Warn("failed to inspect current docker container network; falling back to default docker network", "error", err) - return dockerDaemonTopology{networkMode: containerapi.NetworkMode("default")} + return dockerDaemonTopology{networkMode: containerapi.NetworkMode("default"), kind: dockerDaemonUnknown, detail: err.Error()} } if !ok { slog.Warn("current process is not running in an inspectable docker container; falling back to default docker network") - return dockerDaemonTopology{networkMode: containerapi.NetworkMode("default")} + if _, markerErr := os.Stat("/.dockerenv"); markerErr == nil { + return dockerDaemonTopology{networkMode: containerapi.NetworkMode("default"), kind: dockerDaemonUnknown, detail: "process is containerized but its Docker container cannot be inspected"} + } + return dockerDaemonTopology{networkMode: containerapi.NetworkMode("default"), kind: dockerDaemonNative} + } + if containerInfo.HostConfig != nil && containerInfo.HostConfig.NetworkMode.IsHost() { + return dockerDaemonTopology{networkMode: containerapi.NetworkMode("default"), kind: dockerDaemonContainerHost} + } + networkName, ok := selectDockerNetworkName(containerInfo) + if !ok { + return dockerDaemonTopology{networkMode: containerapi.NetworkMode("default"), containerized: true, kind: dockerDaemonUnknown, detail: "daemon container has no usable bridge network"} } - return dockerDaemonTopology{networkMode: containerapi.NetworkMode(networkName), containerized: true} + return dockerDaemonTopology{networkMode: containerapi.NetworkMode(networkName), containerized: true, kind: dockerDaemonContainerBridge} } -func (r *dockerRuntime) dockerNetworkNameFromSelfContainer(ctx context.Context, dockerClient *client.Client) (string, bool, error) { +func (r *dockerRuntime) dockerSelfContainer(ctx context.Context, dockerClient *client.Client) (containerapi.InspectResponse, bool, error) { hostname, err := os.Hostname() if err != nil { - return "", false, nil + return containerapi.InspectResponse{}, false, nil } hostname = strings.TrimSpace(hostname) if hostname == "" { - return "", false, nil + return containerapi.InspectResponse{}, false, nil } containerInfo, err := dockerClient.ContainerInspect(ctx, hostname) if err != nil { - if isDockerNotFound(err) { - return "", false, nil + if !isDockerNotFound(err) { + return containerapi.InspectResponse{}, false, fmt.Errorf("inspect current docker container %s: %w", hostname, err) + } + mountInfo, readErr := os.ReadFile("/proc/self/mountinfo") + if readErr != nil { + return containerapi.InspectResponse{}, false, nil + } + containerID := dockerContainerIDFromMountInfo(mountInfo) + if containerID == "" || containerID == hostname { + return containerapi.InspectResponse{}, false, nil + } + containerInfo, err = dockerClient.ContainerInspect(ctx, containerID) + if err != nil { + if isDockerNotFound(err) { + return containerapi.InspectResponse{}, false, nil + } + return containerapi.InspectResponse{}, false, fmt.Errorf("inspect current docker container %s from mountinfo: %w", containerID, err) } - return "", false, fmt.Errorf("inspect current docker container %s: %w", hostname, err) } - networkName, ok := selectDockerNetworkName(containerInfo) - return networkName, ok, nil + return containerInfo, true, nil +} + +func dockerContainerIDFromMountInfo(data []byte) string { + const marker = "/containers/" + for _, field := range strings.Fields(string(data)) { + markerIndex := strings.Index(field, marker) + if markerIndex < 0 { + continue + } + remainder := field[markerIndex+len(marker):] + containerID, _, _ := strings.Cut(remainder, "/") + if len(containerID) != 64 { + continue + } + valid := true + for _, char := range containerID { + if (char < '0' || char > '9') && (char < 'a' || char > 'f') { + valid = false + break + } + } + if valid { + return containerID + } + } + return "" } func selectDockerNetworkName(containerInfo containerapi.InspectResponse) (string, bool) { @@ -910,23 +995,14 @@ func (r *dockerRuntime) bindRuntimeMountSource(ctx context.Context, dockerClient } func (r *dockerRuntime) bindRuntimeMountSourceFromSelfContainer(ctx context.Context, dockerClient *client.Client, hostPath string) (string, bool, error) { - hostname, err := os.Hostname() + containerInfo, ok, err := r.dockerSelfContainer(ctx, dockerClient) if err != nil { - return "", false, nil + return "", false, err } - hostname = strings.TrimSpace(hostname) - if hostname == "" { + if !ok { return "", false, nil } - containerInfo, err := dockerClient.ContainerInspect(ctx, hostname) - if err != nil { - if isDockerNotFound(err) { - return "", false, nil - } - return "", false, fmt.Errorf("inspect current docker container %s: %w", hostname, err) - } - var bestSource string var bestDestination string for _, mount := range containerInfo.Mounts { diff --git a/pkg/driver/docker_runtime_test.go b/pkg/driver/docker_runtime_test.go index 5ff821d5e..b9dbd48b2 100644 --- a/pkg/driver/docker_runtime_test.go +++ b/pkg/driver/docker_runtime_test.go @@ -488,6 +488,23 @@ func TestSelectDockerNetworkNameReturnsFalseWithoutNetworks(t *testing.T) { } } +func TestDockerContainerIDFromMountInfo(t *testing.T) { + const containerID = "237ef17be5165bad266b37817772506de92550696c0c6a4526579ca7da24ce70" + mountInfo := []byte("4792 4782 252:3 /var/lib/docker/containers/" + containerID + "/hostname /etc/hostname rw,relatime - ext4 /dev/vda3 rw\n") + if got := dockerContainerIDFromMountInfo(mountInfo); got != containerID { + t.Fatalf("dockerContainerIDFromMountInfo = %q, want %q", got, containerID) + } + for _, invalid := range [][]byte{ + []byte("no docker container mount"), + []byte("/var/lib/docker/containers/short/hostname"), + []byte("/var/lib/docker/containers/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz/hostname"), + } { + if got := dockerContainerIDFromMountInfo(invalid); got != "" { + t.Fatalf("dockerContainerIDFromMountInfo(%q) = %q, want empty", invalid, got) + } + } +} + func TestDockerJupyterPortConfigRequestsAutomaticLoopbackBinding(t *testing.T) { exposedPorts, portBindings := dockerJupyterPortConfig(9999) port := nat.Port("9999/tcp") diff --git a/pkg/driver/types.go b/pkg/driver/types.go index 564bfcff0..9f945c7ec 100644 --- a/pkg/driver/types.go +++ b/pkg/driver/types.go @@ -25,10 +25,49 @@ type SandboxSummary struct { } type Sandbox struct { - Summary SandboxSummary `json:"summary"` - EnvItems []SandboxEnvVar `json:"env_items,omitempty"` - VolumeMounts []SandboxVolumeMount `json:"volume_mounts,omitempty"` - RuntimeEnvItems []SandboxEnvVar `json:"-"` + Summary SandboxSummary `json:"summary"` + EnvItems []SandboxEnvVar `json:"env_items,omitempty"` + VolumeMounts []SandboxVolumeMount `json:"volume_mounts,omitempty"` + RuntimeEnvItems []SandboxEnvVar `json:"-"` + NetworkIntent *SandboxNetworkIntent `json:"network_intent,omitempty"` +} + +type SandboxNetworkDefinition struct { + Name string `json:"name"` + Driver string `json:"driver"` +} + +type SandboxNetworkIntent struct { + Version int `json:"version"` + ProjectID string `json:"project_id"` + ProjectRevision int64 `json:"project_revision"` + AgentName string `json:"agent_name"` + Definitions []SandboxNetworkDefinition `json:"definitions"` + Attachments []string `json:"attachments"` + Expose []string `json:"expose,omitempty"` + Ports []string `json:"ports,omitempty"` +} + +type SandboxNetworkAttachmentState struct { + LogicalName string `json:"logical_name"` + RuntimeName string `json:"runtime_name"` + NetworkID string `json:"network_id,omitempty"` + Aliases []string `json:"aliases,omitempty"` + IPv4Address string `json:"ipv4_address,omitempty"` + Primary bool `json:"primary,omitempty"` +} + +type SandboxPortBindingState struct { + ContainerPort string `json:"container_port"` + HostIP string `json:"host_ip"` + HostPort string `json:"host_port"` +} + +type SandboxNetworkState struct { + Mode string `json:"mode"` + Attachments []SandboxNetworkAttachmentState `json:"attachments,omitempty"` + PortBindings []SandboxPortBindingState `json:"port_bindings,omitempty"` + ReconciledAt time.Time `json:"reconciled_at"` } type SandboxVolumeMount struct { @@ -43,17 +82,18 @@ type SandboxVolumeMount struct { } type VMState struct { - Driver string `json:"driver"` - Mode string `json:"mode,omitempty"` - BoxName string `json:"box_name,omitempty"` - BoxID string `json:"box_id,omitempty"` - Image string `json:"image,omitempty"` - Registry string `json:"registry,omitempty"` - RuntimeHome string `json:"runtime_home,omitempty"` - StartedAt time.Time `json:"started_at,omitempty"` - StoppedAt time.Time `json:"stopped_at,omitempty"` - LastError string `json:"last_error,omitempty"` - BootstrapRef string `json:"bootstrap_ref,omitempty"` + Driver string `json:"driver"` + Mode string `json:"mode,omitempty"` + BoxName string `json:"box_name,omitempty"` + BoxID string `json:"box_id,omitempty"` + Image string `json:"image,omitempty"` + Registry string `json:"registry,omitempty"` + RuntimeHome string `json:"runtime_home,omitempty"` + StartedAt time.Time `json:"started_at,omitempty"` + StoppedAt time.Time `json:"stopped_at,omitempty"` + LastError string `json:"last_error,omitempty"` + BootstrapRef string `json:"bootstrap_ref,omitempty"` + NetworkState *SandboxNetworkState `json:"network_state,omitempty"` } type ProxyState struct { @@ -132,9 +172,10 @@ type SandboxStats struct { type ExecStreamWriter func(ExecChunk) type SandboxVMInfo struct { - BoxID string - JupyterURL string - ProxyState *ProxyState + BoxID string + JupyterURL string + ProxyState *ProxyState + NetworkState *SandboxNetworkState } type SandboxRuntime interface { diff --git a/pkg/execution/driver.go b/pkg/execution/driver.go index 9e8e39878..7fda12926 100644 --- a/pkg/execution/driver.go +++ b/pkg/execution/driver.go @@ -30,6 +30,23 @@ func ToDriverSandbox(session *domain.Sandbox) *driverpkg.Sandbox { HostPath: item.HostPath, }) } + var networkIntent *driverpkg.SandboxNetworkIntent + if session.NetworkIntent != nil { + definitions := make([]driverpkg.SandboxNetworkDefinition, 0, len(session.NetworkIntent.Definitions)) + for _, definition := range session.NetworkIntent.Definitions { + definitions = append(definitions, driverpkg.SandboxNetworkDefinition{Name: definition.Name, Driver: definition.Driver}) + } + networkIntent = &driverpkg.SandboxNetworkIntent{ + Version: session.NetworkIntent.Version, + ProjectID: session.NetworkIntent.ProjectID, + ProjectRevision: session.NetworkIntent.ProjectRevision, + AgentName: session.NetworkIntent.AgentName, + Definitions: definitions, + Attachments: append([]string(nil), session.NetworkIntent.Attachments...), + Expose: append([]string(nil), session.NetworkIntent.Expose...), + Ports: append([]string(nil), session.NetworkIntent.Ports...), + } + } return &driverpkg.Sandbox{ Summary: driverpkg.SandboxSummary{ ID: session.Summary.ID, @@ -44,6 +61,7 @@ func ToDriverSandbox(session *domain.Sandbox) *driverpkg.Sandbox { EnvItems: envItems, VolumeMounts: volumeMounts, RuntimeEnvItems: runtimeEnvItems, + NetworkIntent: networkIntent, } } @@ -60,6 +78,7 @@ func ToDriverVMState(state domain.VMState) driverpkg.VMState { StoppedAt: state.StoppedAt, LastError: state.LastError, BootstrapRef: state.BootstrapRef, + NetworkState: toDriverNetworkState(state.NetworkState), } } @@ -76,6 +95,7 @@ func FromDriverVMState(state driverpkg.VMState) domain.VMState { StoppedAt: state.StoppedAt, LastError: state.LastError, BootstrapRef: state.BootstrapRef, + NetworkState: fromDriverNetworkState(state.NetworkState), } } @@ -102,7 +122,7 @@ func ToDriverExecSpec(spec domain.ExecSpec) driverpkg.ExecSpec { } func FromDriverSandboxVMInfo(info driverpkg.SandboxVMInfo) domain.SandboxVMInfo { - result := domain.SandboxVMInfo{BoxID: info.BoxID, JupyterURL: info.JupyterURL} + result := domain.SandboxVMInfo{BoxID: info.BoxID, JupyterURL: info.JupyterURL, NetworkState: fromDriverNetworkState(info.NetworkState)} if info.ProxyState != nil { proxyState := FromDriverProxyState(*info.ProxyState) result.ProxyState = &proxyState @@ -110,6 +130,40 @@ func FromDriverSandboxVMInfo(info driverpkg.SandboxVMInfo) domain.SandboxVMInfo return result } +func toDriverNetworkState(state *domain.SandboxNetworkState) *driverpkg.SandboxNetworkState { + if state == nil { + return nil + } + result := &driverpkg.SandboxNetworkState{Mode: state.Mode, ReconciledAt: state.ReconciledAt} + for _, item := range state.Attachments { + result.Attachments = append(result.Attachments, driverpkg.SandboxNetworkAttachmentState{ + LogicalName: item.LogicalName, RuntimeName: item.RuntimeName, NetworkID: item.NetworkID, + Aliases: append([]string(nil), item.Aliases...), IPv4Address: item.IPv4Address, Primary: item.Primary, + }) + } + for _, item := range state.PortBindings { + result.PortBindings = append(result.PortBindings, driverpkg.SandboxPortBindingState{ContainerPort: item.ContainerPort, HostIP: item.HostIP, HostPort: item.HostPort}) + } + return result +} + +func fromDriverNetworkState(state *driverpkg.SandboxNetworkState) *domain.SandboxNetworkState { + if state == nil { + return nil + } + result := &domain.SandboxNetworkState{Mode: state.Mode, ReconciledAt: state.ReconciledAt} + for _, item := range state.Attachments { + result.Attachments = append(result.Attachments, domain.SandboxNetworkAttachmentState{ + LogicalName: item.LogicalName, RuntimeName: item.RuntimeName, NetworkID: item.NetworkID, + Aliases: append([]string(nil), item.Aliases...), IPv4Address: item.IPv4Address, Primary: item.Primary, + }) + } + for _, item := range state.PortBindings { + result.PortBindings = append(result.PortBindings, domain.SandboxPortBindingState{ContainerPort: item.ContainerPort, HostIP: item.HostIP, HostPort: item.HostPort}) + } + return result +} + func FromDriverProxyState(state driverpkg.ProxyState) domain.ProxyState { return domain.ProxyState{ ProxyPath: state.ProxyPath, diff --git a/pkg/model/model.go b/pkg/model/model.go index a1b42364e..e15496036 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -174,6 +174,7 @@ type Sandbox struct { VolumeMounts []SandboxVolumeMount `json:"volume_mounts,omitempty"` RuntimeEnvItems []SandboxEnvVar `json:"-"` ProviderEnvItems []SandboxEnvVar `json:"-"` + NetworkIntent *SandboxNetworkIntent `json:"network_intent,omitempty"` } func ValidateSandboxWorkspaceProvisioning(provisioning *SandboxWorkspaceProvisioning) error { @@ -451,17 +452,18 @@ type RuntimeCommandResult struct { type ExecStreamWriter func(ExecChunk) type VMState struct { - Driver string `json:"driver"` - Mode string `json:"mode,omitempty"` - BoxName string `json:"box_name,omitempty"` - BoxID string `json:"box_id,omitempty"` - Image string `json:"image,omitempty"` - Registry string `json:"registry,omitempty"` - RuntimeHome string `json:"runtime_home,omitempty"` - StartedAt time.Time `json:"started_at,omitempty"` - StoppedAt time.Time `json:"stopped_at,omitempty"` - LastError string `json:"last_error,omitempty"` - BootstrapRef string `json:"bootstrap_ref,omitempty"` + Driver string `json:"driver"` + Mode string `json:"mode,omitempty"` + BoxName string `json:"box_name,omitempty"` + BoxID string `json:"box_id,omitempty"` + Image string `json:"image,omitempty"` + Registry string `json:"registry,omitempty"` + RuntimeHome string `json:"runtime_home,omitempty"` + StartedAt time.Time `json:"started_at,omitempty"` + StoppedAt time.Time `json:"stopped_at,omitempty"` + LastError string `json:"last_error,omitempty"` + BootstrapRef string `json:"bootstrap_ref,omitempty"` + NetworkState *SandboxNetworkState `json:"network_state,omitempty"` } type ProxyState struct { @@ -476,9 +478,10 @@ type ProxyState struct { } type SandboxVMInfo struct { - BoxID string - JupyterURL string - ProxyState *ProxyState + BoxID string + JupyterURL string + ProxyState *ProxyState + NetworkState *SandboxNetworkState } type ExecSpec struct { diff --git a/pkg/model/sandbox_network.go b/pkg/model/sandbox_network.go new file mode 100644 index 000000000..836e58a92 --- /dev/null +++ b/pkg/model/sandbox_network.go @@ -0,0 +1,53 @@ +package model + +import "time" + +type SandboxNetworkDefinition struct { + Name string `json:"name"` + Driver string `json:"driver"` +} + +type SandboxNetworkIntent struct { + Version int `json:"version"` + ProjectID string `json:"project_id"` + ProjectRevision int64 `json:"project_revision"` + AgentName string `json:"agent_name"` + Definitions []SandboxNetworkDefinition `json:"definitions"` + Attachments []string `json:"attachments"` + Expose []string `json:"expose,omitempty"` + Ports []string `json:"ports,omitempty"` +} + +func CloneSandboxNetworkIntent(intent *SandboxNetworkIntent) *SandboxNetworkIntent { + if intent == nil { + return nil + } + cloned := *intent + cloned.Definitions = append([]SandboxNetworkDefinition(nil), intent.Definitions...) + cloned.Attachments = append([]string(nil), intent.Attachments...) + cloned.Expose = append([]string(nil), intent.Expose...) + cloned.Ports = append([]string(nil), intent.Ports...) + return &cloned +} + +type SandboxNetworkAttachmentState struct { + LogicalName string `json:"logical_name"` + RuntimeName string `json:"runtime_name"` + NetworkID string `json:"network_id,omitempty"` + Aliases []string `json:"aliases,omitempty"` + IPv4Address string `json:"ipv4_address,omitempty"` + Primary bool `json:"primary,omitempty"` +} + +type SandboxPortBindingState struct { + ContainerPort string `json:"container_port"` + HostIP string `json:"host_ip"` + HostPort string `json:"host_port"` +} + +type SandboxNetworkState struct { + Mode string `json:"mode"` + Attachments []SandboxNetworkAttachmentState `json:"attachments,omitempty"` + PortBindings []SandboxPortBindingState `json:"port_bindings,omitempty"` + ReconciledAt time.Time `json:"reconciled_at"` +} diff --git a/pkg/model/sandbox_network_test.go b/pkg/model/sandbox_network_test.go new file mode 100644 index 000000000..7258a8bb3 --- /dev/null +++ b/pkg/model/sandbox_network_test.go @@ -0,0 +1,39 @@ +package model + +import ( + "reflect" + "testing" +) + +func TestCloneSandboxNetworkIntentDeepCopiesSlices(t *testing.T) { + if CloneSandboxNetworkIntent(nil) != nil { + t.Fatal("CloneSandboxNetworkIntent(nil) returned non-nil") + } + original := &SandboxNetworkIntent{ + Version: 1, + ProjectID: "project-1", + Definitions: []SandboxNetworkDefinition{{Name: "frontend", Driver: "bridge"}}, + Attachments: []string{"frontend"}, + Expose: []string{"8080/tcp"}, + Ports: []string{"127.0.0.1:0:8080/tcp"}, + } + cloned := CloneSandboxNetworkIntent(original) + if !reflect.DeepEqual(cloned, original) { + t.Fatalf("cloned intent = %#v, want %#v", cloned, original) + } + cloned.Definitions[0].Name = "backend" + cloned.Attachments[0] = "backend" + cloned.Expose[0] = "9090/tcp" + cloned.Ports[0] = "127.0.0.1:0:9090/tcp" + if original.Definitions[0].Name != "frontend" || original.Attachments[0] != "frontend" || original.Expose[0] != "8080/tcp" || original.Ports[0] != "127.0.0.1:0:8080/tcp" { + t.Fatalf("clone mutated original: %#v", original) + } +} + +func TestIntegrationSandboxNetworkIntentCloneWorkflow(t *testing.T) { + TestCloneSandboxNetworkIntentDeepCopiesSlices(t) +} + +func TestE2ESandboxNetworkIntentCloneWorkflow(t *testing.T) { + TestCloneSandboxNetworkIntentDeepCopiesSlices(t) +} diff --git a/pkg/networks/plan.go b/pkg/networks/plan.go new file mode 100644 index 000000000..b55036da4 --- /dev/null +++ b/pkg/networks/plan.go @@ -0,0 +1,168 @@ +package networks + +import ( + "crypto/sha256" + "fmt" + "regexp" + "strings" +) + +type Mode string + +const ( + ModeSingleBridge Mode = "single-bridge" + ModeMultiNetwork Mode = "multi-network" +) + +const ( + ManagedLabel = "agent-compose.managed" + ResourceLabel = "agent-compose.resource" + ProjectIDLabel = "agent-compose.project-id" + LogicalNameLabel = "agent-compose.network-name" + + ProjectNetworkResource = "project-network" +) + +var dockerNamePartPattern = regexp.MustCompile(`[^a-zA-Z0-9_.-]+`) + +type Definition struct { + Name string + Driver string +} + +type Intent struct { + ProjectID string + AgentName string + SandboxID string + Definitions map[string]Definition + Attachments []string +} + +type Plan struct { + Mode Mode + BaselineNetwork string + RequiresDaemonHost bool + Attachments []Attachment +} + +type Attachment struct { + LogicalName string + RuntimeName string + Driver string + Aliases []string + GatewayPriority int + Managed bool + Labels map[string]string +} + +func BuildPlan(intent Intent, baselineNetwork string) (Plan, error) { + baselineNetwork = strings.TrimSpace(baselineNetwork) + if len(intent.Attachments) == 0 { + if baselineNetwork == "" { + return Plan{}, fmt.Errorf("baseline docker network is required for single-bridge mode") + } + return Plan{ + Mode: ModeSingleBridge, + BaselineNetwork: baselineNetwork, + Attachments: []Attachment{{ + LogicalName: "default", + RuntimeName: baselineNetwork, + Driver: "bridge", + GatewayPriority: 100, + }}, + }, nil + } + projectID := strings.TrimSpace(intent.ProjectID) + if projectID == "" { + return Plan{}, fmt.Errorf("project id is required for multi-network mode") + } + seen := make(map[string]struct{}, len(intent.Attachments)) + attachments := make([]Attachment, 0, len(intent.Attachments)) + for index, logicalName := range intent.Attachments { + logicalName = strings.TrimSpace(logicalName) + if logicalName == "" { + return Plan{}, fmt.Errorf("network attachment %d is empty", index) + } + if _, exists := seen[logicalName]; exists { + return Plan{}, fmt.Errorf("network attachment %q is duplicated", logicalName) + } + seen[logicalName] = struct{}{} + definition, exists := intent.Definitions[logicalName] + if !exists { + return Plan{}, fmt.Errorf("network attachment %q is not defined", logicalName) + } + driver := strings.ToLower(strings.TrimSpace(definition.Driver)) + if driver == "" { + driver = "bridge" + } + if driver != "bridge" { + return Plan{}, fmt.Errorf("network attachment %q uses unsupported driver %q", logicalName, driver) + } + priority := 0 + if index == 0 { + priority = 100 + } + attachments = append(attachments, Attachment{ + LogicalName: logicalName, + RuntimeName: RuntimeNetworkName(projectID, logicalName), + Driver: driver, + Aliases: endpointAliases(intent.AgentName, intent.SandboxID), + GatewayPriority: priority, + Managed: true, + Labels: map[string]string{ + ManagedLabel: "true", + ResourceLabel: ProjectNetworkResource, + ProjectIDLabel: projectID, + LogicalNameLabel: logicalName, + }, + }) + } + return Plan{ + Mode: ModeMultiNetwork, + BaselineNetwork: baselineNetwork, + RequiresDaemonHost: true, + Attachments: attachments, + }, nil +} + +func RuntimeNetworkName(projectID, logicalName string) string { + projectPart := sanitizeNamePart(projectID) + logicalPart := sanitizeNamePart(logicalName) + name := "agent-compose-p-" + projectPart + "-" + logicalPart + if len(name) <= 63 { + return name + } + digest := sha256.Sum256([]byte(projectID + "\x00" + logicalName)) + suffix := fmt.Sprintf("%x", digest[:6]) + if len(projectPart) > 12 { + projectPart = projectPart[:12] + } + readable := "agent-compose-p-" + projectPart + "-" + logicalPart + maxReadableLength := 63 - 1 - len(suffix) + if len(readable) > maxReadableLength { + readable = strings.TrimRight(readable[:maxReadableLength], "-._") + } + return readable + "-" + suffix +} + +func endpointAliases(agentName, sandboxID string) []string { + agentName = sanitizeNamePart(agentName) + sandboxID = sanitizeNamePart(sandboxID) + aliases := make([]string, 0, 2) + if agentName != "" { + aliases = append(aliases, agentName) + } + if agentName != "" && sandboxID != "" { + shortID := sandboxID + if len(shortID) > 12 { + shortID = shortID[:12] + } + aliases = append(aliases, agentName+"-"+shortID) + } + return aliases +} + +func sanitizeNamePart(value string) string { + value = strings.Trim(dockerNamePartPattern.ReplaceAllString(strings.TrimSpace(value), "-"), "-._") + return value +} diff --git a/pkg/networks/plan_test.go b/pkg/networks/plan_test.go new file mode 100644 index 000000000..dc73d7fd9 --- /dev/null +++ b/pkg/networks/plan_test.go @@ -0,0 +1,103 @@ +package networks + +import ( + "reflect" + "strings" + "testing" +) + +func TestBuildPlanKeepsSingleBridgeBaseline(t *testing.T) { + plan, err := BuildPlan(Intent{}, "compose_default") + if err != nil { + t.Fatalf("BuildPlan returned error: %v", err) + } + if plan.Mode != ModeSingleBridge || plan.RequiresDaemonHost || len(plan.Attachments) != 1 || plan.Attachments[0].RuntimeName != "compose_default" || plan.Attachments[0].Managed { + t.Fatalf("single bridge plan = %#v", plan) + } +} + +func TestBuildPlanTreatsOneAdditionalBridgeAsMultiNetwork(t *testing.T) { + plan, err := BuildPlan(Intent{ + ProjectID: "project-123", + AgentName: "api", + SandboxID: "sandbox-abcdef1234567890", + Definitions: map[string]Definition{"frontend": {Name: "frontend", Driver: "bridge"}}, + Attachments: []string{"frontend"}, + }, "compose_default") + if err != nil { + t.Fatalf("BuildPlan returned error: %v", err) + } + if plan.Mode != ModeMultiNetwork || !plan.RequiresDaemonHost || len(plan.Attachments) != 1 { + t.Fatalf("multi-network plan = %#v", plan) + } + attachment := plan.Attachments[0] + if attachment.RuntimeName != "agent-compose-p-project-123-frontend" || attachment.GatewayPriority != 100 || !attachment.Managed { + t.Fatalf("attachment = %#v", attachment) + } + if !reflect.DeepEqual(attachment.Aliases, []string{"api", "api-sandbox-abcd"}) { + t.Fatalf("aliases = %#v", attachment.Aliases) + } + if attachment.Labels[ProjectIDLabel] != "project-123" || attachment.Labels[LogicalNameLabel] != "frontend" { + t.Fatalf("labels = %#v", attachment.Labels) + } +} + +func TestBuildPlanPreservesAttachmentOrder(t *testing.T) { + plan, err := BuildPlan(Intent{ + ProjectID: "p1", + Definitions: map[string]Definition{ + "frontend": {Driver: "bridge"}, + "backend": {Driver: "bridge"}, + }, + Attachments: []string{"backend", "frontend"}, + }, "bridge") + if err != nil { + t.Fatalf("BuildPlan returned error: %v", err) + } + if plan.Attachments[0].LogicalName != "backend" || plan.Attachments[0].GatewayPriority != 100 || plan.Attachments[1].LogicalName != "frontend" || plan.Attachments[1].GatewayPriority != 0 { + t.Fatalf("attachments = %#v", plan.Attachments) + } +} + +func TestBuildPlanRejectsInvalidIntent(t *testing.T) { + tests := []Intent{ + {Attachments: []string{"frontend"}, Definitions: map[string]Definition{"frontend": {Driver: "bridge"}}}, + {ProjectID: "p1", Attachments: []string{"missing"}, Definitions: map[string]Definition{}}, + {ProjectID: "p1", Attachments: []string{"frontend", "frontend"}, Definitions: map[string]Definition{"frontend": {Driver: "bridge"}}}, + {ProjectID: "p1", Attachments: []string{"frontend"}, Definitions: map[string]Definition{"frontend": {Driver: "overlay"}}}, + } + for _, intent := range tests { + if _, err := BuildPlan(intent, "bridge"); err == nil { + t.Fatalf("BuildPlan(%#v) returned nil error", intent) + } + } +} + +func TestRuntimeNetworkNameDistinguishesLongProjectNetworks(t *testing.T) { + projectID := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + frontend := RuntimeNetworkName(projectID, "frontend") + backend := RuntimeNetworkName(projectID, "backend") + if frontend == backend { + t.Fatalf("runtime network names collided: %q", frontend) + } + for _, name := range []string{frontend, backend} { + if len(name) > 63 { + t.Fatalf("runtime network name length = %d: %q", len(name), name) + } + if !strings.Contains(name, projectID[:12]) { + t.Fatalf("runtime network name %q does not contain project short ID", name) + } + } +} + +func TestIntegrationNamedNetworkPlanWorkflow(t *testing.T) { + TestBuildPlanKeepsSingleBridgeBaseline(t) + TestBuildPlanTreatsOneAdditionalBridgeAsMultiNetwork(t) + TestBuildPlanPreservesAttachmentOrder(t) + TestBuildPlanRejectsInvalidIntent(t) + TestRuntimeNetworkNameDistinguishesLongProjectNetworks(t) +} + +func TestE2ENamedNetworkPlanWorkflow(t *testing.T) { + TestIntegrationNamedNetworkPlanWorkflow(t) +} diff --git a/pkg/projects/controller.go b/pkg/projects/controller.go index 39a4ca782..d1c0e692a 100644 --- a/pkg/projects/controller.go +++ b/pkg/projects/controller.go @@ -3,6 +3,7 @@ package projects import ( "context" "database/sql" + "encoding/json" "errors" "fmt" "strings" @@ -75,6 +76,10 @@ type VolumeManager interface { RemoveProjectVolumes(ctx context.Context, projectID string) error } +type ProjectNetworkCleaner interface { + CleanupProjectNetworks(context.Context, string) error +} + type Controller struct { config *appconfig.Config store ControllerStore @@ -82,6 +87,7 @@ type Controller struct { images images.Backend loaders LoaderValidator volumes VolumeManager + networks ProjectNetworkCleaner stop func(context.Context, *domain.Sandbox) error defaultDR string } @@ -93,6 +99,7 @@ type ControllerDependencies struct { Images images.Backend Loaders LoaderValidator Volumes VolumeManager + Networks ProjectNetworkCleaner StopSandbox func(context.Context, *domain.Sandbox) error } @@ -108,6 +115,7 @@ func NewController(deps ControllerDependencies) *Controller { images: deps.Images, loaders: deps.Loaders, volumes: deps.Volumes, + networks: deps.Networks, stop: deps.StopSandbox, defaultDR: defaultDriver, } @@ -332,6 +340,7 @@ func (c *Controller) RemoveProject(ctx context.Context, req RemoveRequest) (Remo DisableManagedLoader: c.disableManagedLoaderIfOwned, RefreshLoaders: c.refreshLoaders, StopSandbox: c.stop, + CleanupNetworks: c.projectNetworkCleanup(project), }) changes := downChangesToChanges(downChanges) if err != nil { @@ -367,6 +376,57 @@ func (c *Controller) RemoveProject(ctx context.Context, req RemoveRequest) (Remo return RemoveResult{Project: project, Agents: agents, Schedulers: schedulers, Changes: changes}, nil } +type projectRevisionReader interface { + GetProjectRevision(context.Context, string, int64) (domain.ProjectRevisionRecord, error) +} + +func (c *Controller) projectNetworkCleanup(project domain.ProjectRecord) func(context.Context, string) error { + if c == nil || c.networks == nil || project.CurrentRevision <= 0 { + return nil + } + revisions, ok := c.store.(projectRevisionReader) + if !ok { + return nil + } + return func(ctx context.Context, projectID string) error { + hasNamedNetworks, err := projectRevisionHistoryHasNamedNetworks(ctx, revisions, projectID, project.CurrentRevision) + if err != nil { + return err + } + if !hasNamedNetworks { + return nil + } + return c.networks.CleanupProjectNetworks(ctx, projectID) + } +} + +func projectRevisionHistoryHasNamedNetworks(ctx context.Context, revisions projectRevisionReader, projectID string, currentRevision int64) (bool, error) { + for revisionNumber := currentRevision; revisionNumber > 0; revisionNumber-- { + revision, err := revisions.GetProjectRevision(ctx, projectID, revisionNumber) + if err != nil { + return false, fmt.Errorf("load project revision %d: %w", revisionNumber, err) + } + hasNamedNetworks, err := decodeProjectRevisionNetworkSpec(revision.SpecJSON) + if err != nil { + return false, fmt.Errorf("inspect project revision %d: %w", revisionNumber, err) + } + if hasNamedNetworks { + return true, nil + } + } + return false, nil +} + +func decodeProjectRevisionNetworkSpec(raw string) (bool, error) { + var spec struct { + Networks []json.RawMessage `json:"networks"` + } + if err := json.Unmarshal([]byte(raw), &spec); err != nil { + return false, fmt.Errorf("decode project network spec: %w", err) + } + return len(spec.Networks) > 0, nil +} + func (c *Controller) ResolveProjectRef(ctx context.Context, ref ProjectRef) (domain.ProjectRecord, error) { return c.resolveProjectRef(ctx, ref, false) } diff --git a/pkg/projects/controller_coverage_test.go b/pkg/projects/controller_coverage_test.go index b8a5b985c..93ed2ea19 100644 --- a/pkg/projects/controller_coverage_test.go +++ b/pkg/projects/controller_coverage_test.go @@ -133,6 +133,106 @@ func TestControllerRemoveProjectMarksProjectRemovedAndIsIdempotent(t *testing.T) } } +func TestProjectNetworkCleanupUsesNamedNetworkRevisionHistory(t *testing.T) { + ctx := context.Background() + store := &controllerCoverageStore{revisions: []domain.ProjectRevisionRecord{ + {ProjectID: "project-1", Revision: 1, SpecJSON: `{"networks":[{"name":"frontend","driver":"bridge"}]}`}, + {ProjectID: "project-1", Revision: 2, SpecJSON: `{"networks":[{"name":"backend","driver":"bridge"}]}`}, + }} + cleaner := &controllerCoverageNetworkCleaner{} + controller := NewController(ControllerDependencies{Store: store, Networks: cleaner}) + project := domain.ProjectRecord{ID: "project-1", CurrentRevision: 2} + cleanup := controller.projectNetworkCleanup(project) + if cleanup == nil { + t.Fatal("projectNetworkCleanup returned nil for named-network project") + } + if err := cleanup(ctx, project.ID); err != nil { + t.Fatalf("network cleanup returned error: %v", err) + } + if len(cleaner.projectIDs) != 1 || cleaner.projectIDs[0] != project.ID { + t.Fatalf("network cleanup project IDs = %#v", cleaner.projectIDs) + } + + store.revisions[1].SpecJSON = `{"networks":[]}` + if err := cleanup(ctx, project.ID); err != nil { + t.Fatalf("historical network cleanup returned error: %v", err) + } + if len(cleaner.projectIDs) != 2 { + t.Fatalf("historical network cleanup project IDs = %#v", cleaner.projectIDs) + } + + store.revisions[0].SpecJSON = `{"networks":[]}` + if err := cleanup(ctx, project.ID); err != nil { + t.Fatalf("baseline history cleanup returned error: %v", err) + } + if len(cleaner.projectIDs) != 2 { + t.Fatalf("baseline history unexpectedly cleaned networks: %#v", cleaner.projectIDs) + } + + store.revisions[0].SpecJSON = `{` + if err := cleanup(ctx, project.ID); err == nil || !strings.Contains(err.Error(), "decode project network spec") { + t.Fatalf("invalid revision cleanup error = %v", err) + } + store.revisions = store.revisions[1:] + if err := cleanup(ctx, project.ID); err == nil || !strings.Contains(err.Error(), "load project revision 1") { + t.Fatalf("missing historical revision cleanup error = %v", err) + } + if controller.projectNetworkCleanup(domain.ProjectRecord{ID: project.ID}) != nil { + t.Fatal("zero-revision project returned network cleanup") + } + if (&Controller{}).projectNetworkCleanup(project) != nil { + t.Fatal("controller without network cleaner returned cleanup") + } +} + +func TestControllerRemoveProjectCleansHistoricalNetworksBeforeRemoval(t *testing.T) { + ctx := context.Background() + newStore := func() *controllerCoverageStore { + return &controllerCoverageStore{ + projects: []domain.ProjectRecord{{ID: "project-1", Name: "networked-project", CurrentRevision: 2}}, + revisions: []domain.ProjectRevisionRecord{ + {ProjectID: "project-1", Revision: 1, SpecJSON: `{"networks":[{"name":"frontend","driver":"bridge"}]}`}, + {ProjectID: "project-1", Revision: 2, SpecJSON: `{"networks":[]}`}, + }, + } + } + + t.Run("cleanup succeeds", func(t *testing.T) { + store := newStore() + cleaner := &controllerCoverageNetworkCleaner{} + controller := NewController(ControllerDependencies{ + Store: store, + Sandboxes: controllerCoverageSessionStore{}, + Networks: cleaner, + }) + result, err := controller.RemoveProject(ctx, RemoveRequest{Project: ProjectRef{ProjectID: "project-1"}}) + if err != nil { + t.Fatalf("RemoveProject returned error: %v", err) + } + if result.Project.RemovedAt.IsZero() || len(cleaner.projectIDs) != 1 || cleaner.projectIDs[0] != "project-1" { + t.Fatalf("RemoveProject result=%#v cleanup=%#v", result.Project, cleaner.projectIDs) + } + }) + + t.Run("cleanup failure leaves project active", func(t *testing.T) { + store := newStore() + cleaner := &controllerCoverageNetworkCleaner{err: errors.New("docker network cleanup failed")} + controller := NewController(ControllerDependencies{ + Store: store, + Sandboxes: controllerCoverageSessionStore{}, + Networks: cleaner, + }) + result, err := controller.RemoveProject(ctx, RemoveRequest{Project: ProjectRef{ProjectID: "project-1"}}) + if !errors.Is(err, cleaner.err) || !result.Project.RemovedAt.IsZero() { + t.Fatalf("RemoveProject result=%#v err=%v", result.Project, err) + } + project, loadErr := store.GetProject(ctx, "project-1") + if loadErr != nil || !project.RemovedAt.IsZero() { + t.Fatalf("project after cleanup failure=%#v err=%v", project, loadErr) + } + }) +} + func TestManagedSchedulerErrorHelpersCoverage(t *testing.T) { plain := &managedSchedulerBuildError{message: "missing script"} if plain.Error() != "missing script" { @@ -248,12 +348,18 @@ func TestIntegrationControllerValidateApplyDryRunAndResolveWorkflows(t *testing. TestControllerValidateApplyDryRunAndResolveWorkflows(t) TestControllerRejectsUncompiledDriversBeforePersistence(t) TestDownProjectSandboxAndSchedulerWorkflows(t) + TestControllerRemoveProjectMarksProjectRemovedAndIsIdempotent(t) + TestProjectNetworkCleanupUsesNamedNetworkRevisionHistory(t) + TestControllerRemoveProjectCleansHistoricalNetworksBeforeRemoval(t) } func TestE2EControllerValidateApplyDryRunAndResolveWorkflows(t *testing.T) { TestControllerValidateApplyDryRunAndResolveWorkflows(t) TestControllerRejectsUncompiledDriversBeforePersistence(t) TestDownProjectSandboxAndSchedulerWorkflows(t) + TestControllerRemoveProjectMarksProjectRemovedAndIsIdempotent(t) + TestProjectNetworkCleanupUsesNamedNetworkRevisionHistory(t) + TestControllerRemoveProjectCleansHistoricalNetworksBeforeRemoval(t) } type controllerCoverageLoaderValidator struct{} @@ -267,7 +373,8 @@ func (controllerCoverageLoaderValidator) Refresh(context.Context) error { } type controllerCoverageStore struct { - projects []domain.ProjectRecord + projects []domain.ProjectRecord + revisions []domain.ProjectRevisionRecord } func (s *controllerCoverageStore) GetProject(_ context.Context, id string) (domain.ProjectRecord, error) { @@ -325,6 +432,15 @@ func (s *controllerCoverageStore) SaveProjectRevision(context.Context, domain.Pr return domain.ProjectRevisionRecord{}, false, nil } +func (s *controllerCoverageStore) GetProjectRevision(_ context.Context, projectID string, revision int64) (domain.ProjectRevisionRecord, error) { + for _, item := range s.revisions { + if item.ProjectID == projectID && item.Revision == revision { + return item, nil + } + } + return domain.ProjectRevisionRecord{}, sql.ErrNoRows +} + func (s *controllerCoverageStore) GetProjectAgent(context.Context, string, string) (domain.ProjectAgentRecord, error) { return domain.ProjectAgentRecord{}, sql.ErrNoRows } @@ -395,6 +511,16 @@ type controllerCoverageVolumeManager struct { removedProjects []string } +type controllerCoverageNetworkCleaner struct { + projectIDs []string + err error +} + +func (c *controllerCoverageNetworkCleaner) CleanupProjectNetworks(_ context.Context, projectID string) error { + c.projectIDs = append(c.projectIDs, projectID) + return c.err +} + func (m *controllerCoverageVolumeManager) Ensure(context.Context, domain.VolumeRecord) (domain.VolumeRecord, bool, error) { return domain.VolumeRecord{}, false, nil } diff --git a/pkg/projects/down.go b/pkg/projects/down.go index 9305310cc..254ad7a96 100644 --- a/pkg/projects/down.go +++ b/pkg/projects/down.go @@ -36,6 +36,7 @@ type DownOptions struct { DisableManagedLoader func(ctx context.Context, loaderID, projectID, schedulerID string) error RefreshLoaders func(ctx context.Context) error StopSandbox func(ctx context.Context, sandbox *domain.Sandbox) error + CleanupNetworks func(ctx context.Context, projectID string) error } func DownProject(ctx context.Context, project domain.ProjectRecord, options DownOptions) ([]DownChange, error) { @@ -50,6 +51,11 @@ func DownProject(ctx context.Context, project domain.ProjectRecord, options Down return changes, err } changes = append(changes, sandboxChanges...) + if options.CleanupNetworks != nil { + if err := options.CleanupNetworks(ctx, project.ID); err != nil { + return changes, fmt.Errorf("clean project networks for down %s: %w", project.Name, err) + } + } return changes, nil } diff --git a/pkg/runs/controller.go b/pkg/runs/controller.go index 970c20f37..efac27ffc 100644 --- a/pkg/runs/controller.go +++ b/pkg/runs/controller.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "path/filepath" + "reflect" "strings" "sync" "time" @@ -1961,6 +1962,11 @@ func (c *Controller) ensureProjectRunSandbox(ctx context.Context, run domain.Pro return SandboxResult{}, fmt.Errorf("load sandbox %s: %w", sandboxID, err) } warnings = append(warnings, fmt.Sprintf("sticky sandbox %s is unavailable; creating a replacement", sandboxID)) + } else if !sandboxNetworkIntentsEqual(sandbox.NetworkIntent, prepared.NetworkIntent) { + if !boundSandbox { + return SandboxResult{Sandbox: sandbox}, fmt.Errorf("%w: sandbox %s network intent does not match project run", ErrInvalidRequest, sandboxID) + } + warnings = append(warnings, fmt.Sprintf("sticky sandbox %s network intent no longer matches; creating a replacement", sandboxID)) } else { if sandbox.Summary.VMStatus == domain.VMStatusDeleting { return SandboxResult{Sandbox: sandbox}, fmt.Errorf("sandbox %s is being deleted", sandboxID) @@ -2006,6 +2012,9 @@ func (c *Controller) ensureProjectRunSandbox(ctx context.Context, run domain.Pro if err := c.validateSandboxRuntimeDriver(driver); err != nil { return SandboxResult{}, err } + if prepared.NetworkIntent != nil && driver != driverpkg.RuntimeDriverDocker { + return SandboxResult{}, fmt.Errorf("%w: project named networks require the docker driver", ErrInvalidRequest) + } guestImage := driverpkg.ResolveSandboxGuestImage(run.ImageRef, driverpkg.DefaultGuestImageForDriver(c.config, driver)) if err := images.EnsureDriverImage(ctx, c.config, c.images, images.EnsureRequest{ Driver: driver, @@ -2020,6 +2029,7 @@ func (c *Controller) ensureProjectRunSandbox(ctx context.Context, run domain.Pro return SandboxResult{}, err } jupyterOptions.VolumeMounts = volumeMounts + jupyterOptions.NetworkIntent = prepared.NetworkIntent sandbox, err := c.store.CreateSandboxWithOptions(ctx, SandboxTitle(run), "", @@ -2051,6 +2061,17 @@ func (c *Controller) ensureProjectRunSandbox(ctx context.Context, run domain.Pro return SandboxResult{Sandbox: sandbox, Created: true, Warnings: volumeWarnings}, nil } +func sandboxNetworkIntentsEqual(left, right *domain.SandboxNetworkIntent) bool { + if left == nil || right == nil { + return left == nil && right == nil + } + leftEffective := *left + rightEffective := *right + leftEffective.ProjectRevision = 0 + rightEffective.ProjectRevision = 0 + return reflect.DeepEqual(leftEffective, rightEffective) +} + func (c *Controller) validateSandboxRuntimeDriver(driver string) error { err := driverpkg.ValidateCompiledRuntimeDriver(driver) if errors.Is(err, driverpkg.ErrRuntimeDriverNotCompiled) { diff --git a/pkg/runs/coverage_shape_workflows_test.go b/pkg/runs/coverage_shape_workflows_test.go index ebc89b442..40128216a 100644 --- a/pkg/runs/coverage_shape_workflows_test.go +++ b/pkg/runs/coverage_shape_workflows_test.go @@ -494,6 +494,9 @@ func testRunsControllerPersistenceBoundaryWorkflows(t *testing.T) { t.Run("manual trigger prompt", TestRunsControllerRunProjectAgentManualTriggerPromptOverride) t.Run("manual trigger missing", TestRunsControllerRunProjectAgentManualTriggerMissingDoesNotCreateRun) t.Run("sticky binding scope", TestRunsControllerStickyBindingsAreScopedByTrigger) + t.Run("sticky reuse across project revisions", TestRunsControllerStickyNetworkIntentReusesAcrossProjectRevisions) + t.Run("failed sticky replacement preserves binding", TestRunsControllerFailedStickyNetworkReplacementPreservesBinding) + t.Run("network intent revision equality", TestSandboxNetworkIntentsEqualIgnoresProjectRevision) t.Run("unsupported persistence boundary", TestRunsControllerRejectsUncompiledScheduledSandboxBeforePersistence) t.Run("apply jupyter", TestRunsControllerApplyJupyterOptionsToSandbox) t.Run("docker host port", TestRunsControllerApplyJupyterOptionsLeavesDockerHostPortForRuntime) @@ -1784,6 +1787,46 @@ func TestRunsControllerStickyBindingsAreScopedByTrigger(t *testing.T) { if fixture.configDB.bindings["loader-1/trigger-a"].SandboxID != first.SandboxID || fixture.configDB.bindings["loader-1/trigger-b"].SandboxID != other.SandboxID { t.Fatalf("bindings = %#v", fixture.configDB.bindings) } + oldSticky, err := fixture.store.GetSandbox(fixture.ctx, first.SandboxID) + if err != nil { + t.Fatalf("load sticky sandbox: %v", err) + } + oldSticky.NetworkIntent = &domain.SandboxNetworkIntent{ + Version: 1, + ProjectID: "project-1", + ProjectRevision: 1, + AgentName: "worker", + Definitions: []domain.SandboxNetworkDefinition{{Name: "frontend", Driver: "bridge"}}, + Attachments: []string{"frontend"}, + } + if err := fixture.store.SaveSandbox(oldSticky); err != nil { + t.Fatalf("save mismatched sticky sandbox: %v", err) + } + _, explicitExecErr, explicitErr := fixture.controller.RunProjectAgent(fixture.ctx, RunAgentRequest{ + ProjectID: "project-1", + AgentName: "worker", + Prompt: "explicit reuse must fail", + SandboxID: first.SandboxID, + Source: domain.ProjectRunSourceAPI, + ClientRequestID: "explicit-network-mismatch", + CleanupPolicy: agentcomposev2.RunSandboxCleanupPolicy_RUN_SANDBOX_CLEANUP_POLICY_KEEP_RUNNING, + }, nil) + if explicitErr != nil || !errors.Is(explicitExecErr, ErrInvalidRequest) { + t.Fatalf("explicit mismatched reuse err=%v execErr=%v", explicitErr, explicitExecErr) + } + if got := fixture.configDB.bindings["loader-1/trigger-a"].SandboxID; got != first.SandboxID { + t.Fatalf("explicit mismatch changed sticky binding to %q", got) + } + intentReplacement := runSticky("trigger-a", "sticky-a-intent-replacement") + if intentReplacement.SandboxID == first.SandboxID || fixture.configDB.bindings["loader-1/trigger-a"].SandboxID != intentReplacement.SandboxID { + t.Fatalf("intent replacement run=%#v bindings=%#v", intentReplacement, fixture.configDB.bindings) + } + if len(intentReplacement.Warnings) == 0 || !strings.Contains(intentReplacement.Warnings[0], "network intent") { + t.Fatalf("intent replacement warnings = %#v", intentReplacement.Warnings) + } + if reusedReplacement := runSticky("trigger-a", "sticky-a-replacement-reuse"); reusedReplacement.SandboxID != intentReplacement.SandboxID { + t.Fatalf("replacement was not reused: first=%q second=%q", intentReplacement.SandboxID, reusedReplacement.SandboxID) + } fixture.configDB.bindings["loader-1/stale"] = domain.LoaderBinding{LoaderID: "loader-1", TriggerID: "stale", SandboxID: "missing-sandbox"} replacement := runSticky("stale", "sticky-stale-1") @@ -1795,6 +1838,129 @@ func TestRunsControllerStickyBindingsAreScopedByTrigger(t *testing.T) { } } +func TestRunsControllerStickyNetworkIntentReusesAcrossProjectRevisions(t *testing.T) { + fixture := newControllerRunFixture(t) + const specJSON = `{"networks":[{"name":"frontend","driver":"bridge"}],"agents":[{"name":"worker","networks":["frontend"],"expose":["8080/tcp"],"ports":["127.0.0.1:0:8080/tcp"]}]}` + fixture.configDB.revision.SpecJSON = specJSON + + runSticky := func(requestID string) domain.ProjectRunRecord { + t.Helper() + run, execErr, err := fixture.controller.RunProjectAgent(fixture.ctx, RunAgentRequest{ + ProjectID: "project-1", + AgentName: "worker", + Prompt: "reuse networked sandbox", + Source: domain.ProjectRunSourceScheduler, + SchedulerID: "scheduler-1", + TriggerID: "trigger-revision", + ClientRequestID: requestID, + CleanupPolicy: agentcomposev2.RunSandboxCleanupPolicy_RUN_SANDBOX_CLEANUP_POLICY_KEEP_RUNNING, + StickyBindingLoaderID: "loader-1", + StickyBindingTriggerID: "trigger-revision", + }, nil) + if err != nil || execErr != nil { + t.Fatalf("RunProjectAgent err=%v execErr=%v run=%#v", err, execErr, run) + } + return run + } + + first := runSticky("sticky-network-revision-1") + firstSandbox, err := fixture.store.GetSandbox(fixture.ctx, first.SandboxID) + if err != nil { + t.Fatalf("load first sticky sandbox: %v", err) + } + if firstSandbox.NetworkIntent == nil || firstSandbox.NetworkIntent.ProjectRevision != 1 { + t.Fatalf("first network intent = %#v", firstSandbox.NetworkIntent) + } + + fixture.configDB.project.CurrentRevision = 2 + fixture.configDB.revision.Revision = 2 + second := runSticky("sticky-network-revision-2") + if second.SandboxID != first.SandboxID { + t.Fatalf("revision-only project update replaced sandbox: first=%q second=%q", first.SandboxID, second.SandboxID) + } + if len(second.Warnings) != 0 { + t.Fatalf("revision-only reuse warnings = %#v", second.Warnings) + } + if got := fixture.configDB.bindings["loader-1/trigger-revision"].SandboxID; got != first.SandboxID { + t.Fatalf("revision-only reuse binding = %q, want %q", got, first.SandboxID) + } +} + +func TestRunsControllerFailedStickyNetworkReplacementPreservesBinding(t *testing.T) { + fixture := newControllerRunFixture(t) + runSticky := func(requestID string) (domain.ProjectRunRecord, error, error) { + t.Helper() + return fixture.controller.RunProjectAgent(fixture.ctx, RunAgentRequest{ + ProjectID: "project-1", + AgentName: "worker", + Prompt: "replace networked sandbox", + Source: domain.ProjectRunSourceScheduler, + SchedulerID: "scheduler-1", + TriggerID: "trigger-failed-replacement", + ClientRequestID: requestID, + CleanupPolicy: agentcomposev2.RunSandboxCleanupPolicy_RUN_SANDBOX_CLEANUP_POLICY_KEEP_RUNNING, + StickyBindingLoaderID: "loader-1", + StickyBindingTriggerID: "trigger-failed-replacement", + }, nil) + } + + first, execErr, err := runSticky("sticky-network-before-failure") + if err != nil || execErr != nil { + t.Fatalf("first RunProjectAgent err=%v execErr=%v run=%#v", err, execErr, first) + } + oldSandbox, err := fixture.store.GetSandbox(fixture.ctx, first.SandboxID) + if err != nil { + t.Fatalf("load first sticky sandbox: %v", err) + } + oldSandbox.NetworkIntent = &domain.SandboxNetworkIntent{ + Version: 1, + ProjectID: "project-1", + AgentName: "worker", + Definitions: []domain.SandboxNetworkDefinition{{Name: "frontend", Driver: "bridge"}}, + Attachments: []string{"frontend"}, + } + if err := fixture.store.SaveSandbox(oldSandbox); err != nil { + t.Fatalf("save mismatched sticky sandbox: %v", err) + } + + fixture.driver.startErr = errors.New("replacement runtime start failed") + failed, execErr, err := runSticky("sticky-network-failed-replacement") + if err != nil || !errors.Is(execErr, fixture.driver.startErr) { + t.Fatalf("replacement RunProjectAgent err=%v execErr=%v run=%#v", err, execErr, failed) + } + if failed.SandboxID == "" || failed.SandboxID == first.SandboxID { + t.Fatalf("failed replacement sandbox ID = %q, old = %q", failed.SandboxID, first.SandboxID) + } + if got := fixture.configDB.bindings["loader-1/trigger-failed-replacement"].SandboxID; got != first.SandboxID { + t.Fatalf("failed replacement changed binding to %q, want %q", got, first.SandboxID) + } +} + +func TestSandboxNetworkIntentsEqualIgnoresProjectRevision(t *testing.T) { + left := &domain.SandboxNetworkIntent{ + Version: 1, + ProjectID: "project-1", + ProjectRevision: 1, + AgentName: "worker", + Definitions: []domain.SandboxNetworkDefinition{{Name: "frontend", Driver: "bridge"}}, + Attachments: []string{"frontend"}, + Expose: []string{"8080/tcp"}, + Ports: []string{"127.0.0.1:0:8080/tcp"}, + } + right := domain.CloneSandboxNetworkIntent(left) + right.ProjectRevision = 9 + if !sandboxNetworkIntentsEqual(left, right) { + t.Fatal("equal effective network intents differed only by project revision") + } + right.Attachments = []string{"backend"} + if sandboxNetworkIntentsEqual(left, right) { + t.Fatal("different network attachments compared equal") + } + if sandboxNetworkIntentsEqual(nil, left) || !sandboxNetworkIntentsEqual(nil, nil) { + t.Fatal("nil intent equality returned unexpected result") + } +} + func TestRunsControllerRejectsUncompiledScheduledSandboxBeforePersistence(t *testing.T) { for _, runtimeDriver := range []string{driverpkg.RuntimeDriverBoxlite, driverpkg.RuntimeDriverMicrosandbox} { t.Run(runtimeDriver, func(t *testing.T) { diff --git a/pkg/runs/network_preparation_test.go b/pkg/runs/network_preparation_test.go new file mode 100644 index 000000000..bd789cd11 --- /dev/null +++ b/pkg/runs/network_preparation_test.go @@ -0,0 +1,59 @@ +package runs + +import ( + "reflect" + "testing" + + agentcomposev2 "agent-compose/proto/agentcompose/v2" +) + +func TestNetworkIntentFromProjectSpec(t *testing.T) { + agent := &agentcomposev2.AgentSpec{ + Name: "api", + Networks: []string{"frontend", "backend"}, + Expose: []string{"8080/tcp"}, + Ports: []string{"127.0.0.1:18080:8080/tcp"}, + } + intent := networkIntentFromProjectSpec("project-1", 7, "api", &agentcomposev2.ProjectSpec{ + Networks: []*agentcomposev2.NamedNetworkSpec{ + {Name: "backend", Driver: "bridge"}, + {Name: "frontend", Driver: "bridge"}, + }, + }, agent) + if intent == nil || intent.Version != 1 || intent.ProjectID != "project-1" || intent.ProjectRevision != 7 || intent.AgentName != "api" { + t.Fatalf("intent = %#v", intent) + } + if !reflect.DeepEqual(intent.Attachments, agent.Networks) || !reflect.DeepEqual(intent.Expose, agent.Expose) || !reflect.DeepEqual(intent.Ports, agent.Ports) { + t.Fatalf("intent lists = %#v", intent) + } + if len(intent.Definitions) != 2 || intent.Definitions[0].Name != "backend" || intent.Definitions[1].Name != "frontend" { + t.Fatalf("definitions = %#v", intent.Definitions) + } +} + +func TestNetworkIntentFromProjectSpecKeepsBaselineWithoutNamedNetworks(t *testing.T) { + if intent := networkIntentFromProjectSpec("project-1", 1, "api", &agentcomposev2.ProjectSpec{}, &agentcomposev2.AgentSpec{Name: "api"}); intent != nil { + t.Fatalf("baseline intent = %#v, want nil", intent) + } +} + +func TestNetworkIntentFromProjectSpecKeepsPortOnlyConfigurationOnBaseline(t *testing.T) { + intent := networkIntentFromProjectSpec("project-1", 3, "api", &agentcomposev2.ProjectSpec{}, &agentcomposev2.AgentSpec{ + Name: "api", + Expose: []string{"8080/tcp"}, + Ports: []string{"127.0.0.1:0:8080/tcp"}, + }) + if intent == nil || intent.ProjectRevision != 3 || len(intent.Attachments) != 0 || !reflect.DeepEqual(intent.Expose, []string{"8080/tcp"}) || !reflect.DeepEqual(intent.Ports, []string{"127.0.0.1:0:8080/tcp"}) { + t.Fatalf("port-only intent = %#v", intent) + } +} + +func TestIntegrationProjectRunNetworkIntentWorkflow(t *testing.T) { + TestNetworkIntentFromProjectSpec(t) + TestNetworkIntentFromProjectSpecKeepsBaselineWithoutNamedNetworks(t) + TestNetworkIntentFromProjectSpecKeepsPortOnlyConfigurationOnBaseline(t) +} + +func TestE2EProjectRunNetworkIntentWorkflow(t *testing.T) { + TestIntegrationProjectRunNetworkIntentWorkflow(t) +} diff --git a/pkg/runs/preparation.go b/pkg/runs/preparation.go index ef8d1cf8a..2bedf6981 100644 --- a/pkg/runs/preparation.go +++ b/pkg/runs/preparation.go @@ -38,6 +38,7 @@ type Preparation struct { ProjectRoot string ProjectVolumes map[string]domain.VolumeRecord Jupyter sessionstore.CreateSandboxOptions + NetworkIntent *domain.SandboxNetworkIntent } func PrepareProjectRun(ctx context.Context, store PreparationStore, resolver WorkspaceResolver, run domain.ProjectRunRecord, requestEnv []*agentcomposev2.EnvVarSpec) (Preparation, error) { @@ -83,6 +84,7 @@ func PrepareProjectRun(ctx context.Context, store PreparationStore, resolver Wor Volumes: agent.Volumes, ProjectRoot: ProjectRoot(project), Jupyter: jupyterOptionsFromAgentSpec(agentSpec), + NetworkIntent: networkIntentFromProjectSpec(run.ProjectID, run.ProjectRevision, run.AgentName, spec, agentSpec), } projectVolumes, err := store.ListProjectVolumes(ctx, project.ID) if err != nil { @@ -107,6 +109,32 @@ func PrepareProjectRun(ctx context.Context, store PreparationStore, resolver Wor return prepared, nil } +func networkIntentFromProjectSpec(projectID string, projectRevision int64, agentName string, project *agentcomposev2.ProjectSpec, agent *agentcomposev2.AgentSpec) *domain.SandboxNetworkIntent { + if project == nil || agent == nil { + return nil + } + if len(project.GetNetworks()) == 0 && len(agent.GetExpose()) == 0 && len(agent.GetPorts()) == 0 { + return nil + } + definitions := make([]domain.SandboxNetworkDefinition, 0, len(project.GetNetworks())) + for _, network := range project.GetNetworks() { + if network == nil { + continue + } + definitions = append(definitions, domain.SandboxNetworkDefinition{Name: network.GetName(), Driver: network.GetDriver()}) + } + return &domain.SandboxNetworkIntent{ + Version: 1, + ProjectID: strings.TrimSpace(projectID), + ProjectRevision: projectRevision, + AgentName: strings.TrimSpace(agentName), + Definitions: definitions, + Attachments: append([]string(nil), agent.GetNetworks()...), + Expose: append([]string(nil), agent.GetExpose()...), + Ports: append([]string(nil), agent.GetPorts()...), + } +} + func ProjectRoot(project domain.ProjectRecord) string { sourcePath := strings.TrimSpace(project.SourcePath) if sourcePath == "" { diff --git a/pkg/sessions/driver.go b/pkg/sessions/driver.go index 96f5fd91c..e17bc08c3 100644 --- a/pkg/sessions/driver.go +++ b/pkg/sessions/driver.go @@ -13,6 +13,9 @@ func ApplySessionStartInfo(vmState domain.VMState, proxyState domain.ProxyState, vmState.StoppedAt = time.Time{} vmState.LastError = "" vmState.BootstrapRef = firstNonEmpty(info.JupyterURL, vmState.BootstrapRef) + if info.NetworkState != nil { + vmState.NetworkState = info.NetworkState + } if info.ProxyState != nil { proxyState = *info.ProxyState } diff --git a/pkg/storage/sessionstore/store.go b/pkg/storage/sessionstore/store.go index ef1e15735..56d4a774b 100644 --- a/pkg/storage/sessionstore/store.go +++ b/pkg/storage/sessionstore/store.go @@ -90,6 +90,7 @@ type CreateSandboxOptions struct { JupyterGuestPort int JupyterExpose bool VolumeMounts []domain.SandboxVolumeMount + NetworkIntent *domain.SandboxNetworkIntent } func (s *Store) CreateSandbox(ctx context.Context, title, baseWorkspace, driver, guestImage, workspaceID, triggerSource string, workspace *SandboxWorkspace, envItems []SandboxEnvVar, tags []SandboxTag) (*Sandbox, error) { @@ -156,6 +157,7 @@ func (s *Store) CreateSandboxWithOptions(_ context.Context, title, baseWorkspace WorkspaceProvisioning: workspaceProvisioning, EnvItems: append([]SandboxEnvVar(nil), envItems...), VolumeMounts: domain.NormalizeSandboxVolumeMounts(options.VolumeMounts), + NetworkIntent: domain.CloneSandboxNetworkIntent(options.NetworkIntent), } if session.Summary.Title == "" { diff --git a/pkg/storage/sessionstore/store_network_test.go b/pkg/storage/sessionstore/store_network_test.go new file mode 100644 index 000000000..6b5c0010e --- /dev/null +++ b/pkg/storage/sessionstore/store_network_test.go @@ -0,0 +1,45 @@ +package sessionstore + +import ( + "context" + "reflect" + "testing" + + driverpkg "agent-compose/pkg/driver" + domain "agent-compose/pkg/model" +) + +func TestStorePersistsSandboxNetworkIntent(t *testing.T) { + store := newCoverageStore(t) + intent := &domain.SandboxNetworkIntent{ + Version: 1, + ProjectID: "project-1", + ProjectRevision: 7, + AgentName: "api", + Definitions: []domain.SandboxNetworkDefinition{{Name: "frontend", Driver: "bridge"}}, + Attachments: []string{"frontend"}, + Expose: []string{"8080/tcp"}, + Ports: []string{"127.0.0.1:18080:8080/tcp"}, + } + sandbox, err := store.CreateSandboxWithOptions(context.Background(), "networked", "", driverpkg.RuntimeDriverDocker, "guest:latest", "", "test", nil, nil, nil, CreateSandboxOptions{NetworkIntent: intent}) + if err != nil { + t.Fatalf("CreateSandboxWithOptions returned error: %v", err) + } + intent.ProjectID = "mutated" + loaded, err := store.GetSandbox(context.Background(), sandbox.Summary.ID) + if err != nil { + t.Fatalf("GetSandbox returned error: %v", err) + } + want := "project-1" + if loaded.NetworkIntent == nil || loaded.NetworkIntent.ProjectID != want || loaded.NetworkIntent.ProjectRevision != 7 || !reflect.DeepEqual(loaded.NetworkIntent.Attachments, []string{"frontend"}) { + t.Fatalf("loaded network intent = %#v", loaded.NetworkIntent) + } +} + +func TestIntegrationStorePersistsSandboxNetworkIntent(t *testing.T) { + TestStorePersistsSandboxNetworkIntent(t) +} + +func TestE2EStorePersistsSandboxNetworkIntent(t *testing.T) { + TestStorePersistsSandboxNetworkIntent(t) +} diff --git a/proto/agentcompose/v2/agentcompose.pb.go b/proto/agentcompose/v2/agentcompose.pb.go index 795729eb2..87a9c7b37 100644 --- a/proto/agentcompose/v2/agentcompose.pb.go +++ b/proto/agentcompose/v2/agentcompose.pb.go @@ -3749,10 +3749,10 @@ type ProjectSpec struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Variables []*EnvVarSpec `protobuf:"bytes,2,rep,name=variables,proto3" json:"variables,omitempty"` Agents []*AgentSpec `protobuf:"bytes,4,rep,name=agents,proto3" json:"agents,omitempty"` - Network *NetworkSpec `protobuf:"bytes,5,opt,name=network,proto3" json:"network,omitempty"` Volumes []*ProjectVolumeSpec `protobuf:"bytes,6,rep,name=volumes,proto3" json:"volumes,omitempty"` Workspaces []*NamedWorkspaceSpec `protobuf:"bytes,7,rep,name=workspaces,proto3" json:"workspaces,omitempty"` McpServers []*MCPServerSpec `protobuf:"bytes,8,rep,name=mcp_servers,json=mcpServers,proto3" json:"mcp_servers,omitempty"` + Networks []*NamedNetworkSpec `protobuf:"bytes,9,rep,name=networks,proto3" json:"networks,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3808,13 +3808,6 @@ func (x *ProjectSpec) GetAgents() []*AgentSpec { return nil } -func (x *ProjectSpec) GetNetwork() *NetworkSpec { - if x != nil { - return x.Network - } - return nil -} - func (x *ProjectSpec) GetVolumes() []*ProjectVolumeSpec { if x != nil { return x.Volumes @@ -3836,6 +3829,13 @@ func (x *ProjectSpec) GetMcpServers() []*MCPServerSpec { return nil } +func (x *ProjectSpec) GetNetworks() []*NamedNetworkSpec { + if x != nil { + return x.Networks + } + return nil +} + type NamedWorkspaceSpec struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -3906,6 +3906,9 @@ type AgentSpec struct { McpServers []*MCPServerSpec `protobuf:"bytes,14,rep,name=mcp_servers,json=mcpServers,proto3" json:"mcp_servers,omitempty"` Skills []*SkillSpec `protobuf:"bytes,15,rep,name=skills,proto3" json:"skills,omitempty"` Status AgentStatus `protobuf:"varint,16,opt,name=status,proto3,enum=agentcompose.v2.AgentStatus" json:"status,omitempty"` + Networks []string `protobuf:"bytes,17,rep,name=networks,proto3" json:"networks,omitempty"` + Expose []string `protobuf:"bytes,18,rep,name=expose,proto3" json:"expose,omitempty"` + Ports []string `protobuf:"bytes,19,rep,name=ports,proto3" json:"ports,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4052,6 +4055,27 @@ func (x *AgentSpec) GetStatus() AgentStatus { return AgentStatus_AGENT_STATUS_UNSPECIFIED } +func (x *AgentSpec) GetNetworks() []string { + if x != nil { + return x.Networks + } + return nil +} + +func (x *AgentSpec) GetExpose() []string { + if x != nil { + return x.Expose + } + return nil +} + +func (x *AgentSpec) GetPorts() []string { + if x != nil { + return x.Ports + } + return nil +} + type MCPServerSpec struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -4600,27 +4624,28 @@ func (x *WorkspaceSpec) GetName() string { return "" } -type NetworkSpec struct { +type NamedNetworkSpec struct { state protoimpl.MessageState `protogen:"open.v1"` - Mode string `protobuf:"bytes,1,opt,name=mode,proto3" json:"mode,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Driver string `protobuf:"bytes,2,opt,name=driver,proto3" json:"driver,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *NetworkSpec) Reset() { - *x = NetworkSpec{} +func (x *NamedNetworkSpec) Reset() { + *x = NamedNetworkSpec{} mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *NetworkSpec) String() string { +func (x *NamedNetworkSpec) String() string { return protoimpl.X.MessageStringOf(x) } -func (*NetworkSpec) ProtoMessage() {} +func (*NamedNetworkSpec) ProtoMessage() {} -func (x *NetworkSpec) ProtoReflect() protoreflect.Message { +func (x *NamedNetworkSpec) ProtoReflect() protoreflect.Message { mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4632,14 +4657,21 @@ func (x *NetworkSpec) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use NetworkSpec.ProtoReflect.Descriptor instead. -func (*NetworkSpec) Descriptor() ([]byte, []int) { +// Deprecated: Use NamedNetworkSpec.ProtoReflect.Descriptor instead. +func (*NamedNetworkSpec) Descriptor() ([]byte, []int) { return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{46} } -func (x *NetworkSpec) GetMode() string { +func (x *NamedNetworkSpec) GetName() string { if x != nil { - return x.Mode + return x.Name + } + return "" +} + +func (x *NamedNetworkSpec) GetDriver() string { + if x != nil { + return x.Driver } return "" } @@ -7294,6 +7326,7 @@ type Sandbox struct { CellCount uint32 `protobuf:"varint,14,opt,name=cell_count,json=cellCount,proto3" json:"cell_count,omitempty"` EventCount uint32 `protobuf:"varint,15,opt,name=event_count,json=eventCount,proto3" json:"event_count,omitempty"` NotebookUrl string `protobuf:"bytes,16,opt,name=notebook_url,json=notebookUrl,proto3" json:"notebook_url,omitempty"` + NetworkState *SandboxNetworkState `protobuf:"bytes,17,opt,name=network_state,json=networkState,proto3" json:"network_state,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -7440,6 +7473,225 @@ func (x *Sandbox) GetNotebookUrl() string { return "" } +func (x *Sandbox) GetNetworkState() *SandboxNetworkState { + if x != nil { + return x.NetworkState + } + return nil +} + +type SandboxNetworkState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mode string `protobuf:"bytes,1,opt,name=mode,proto3" json:"mode,omitempty"` + Attachments []*SandboxNetworkAttachment `protobuf:"bytes,2,rep,name=attachments,proto3" json:"attachments,omitempty"` + PortBindings []*SandboxPortBinding `protobuf:"bytes,3,rep,name=port_bindings,json=portBindings,proto3" json:"port_bindings,omitempty"` + ReconciledAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=reconciled_at,json=reconciledAt,proto3" json:"reconciled_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxNetworkState) Reset() { + *x = SandboxNetworkState{} + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[83] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxNetworkState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxNetworkState) ProtoMessage() {} + +func (x *SandboxNetworkState) ProtoReflect() protoreflect.Message { + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[83] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxNetworkState.ProtoReflect.Descriptor instead. +func (*SandboxNetworkState) Descriptor() ([]byte, []int) { + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{83} +} + +func (x *SandboxNetworkState) GetMode() string { + if x != nil { + return x.Mode + } + return "" +} + +func (x *SandboxNetworkState) GetAttachments() []*SandboxNetworkAttachment { + if x != nil { + return x.Attachments + } + return nil +} + +func (x *SandboxNetworkState) GetPortBindings() []*SandboxPortBinding { + if x != nil { + return x.PortBindings + } + return nil +} + +func (x *SandboxNetworkState) GetReconciledAt() *timestamppb.Timestamp { + if x != nil { + return x.ReconciledAt + } + return nil +} + +type SandboxNetworkAttachment struct { + state protoimpl.MessageState `protogen:"open.v1"` + LogicalName string `protobuf:"bytes,1,opt,name=logical_name,json=logicalName,proto3" json:"logical_name,omitempty"` + RuntimeName string `protobuf:"bytes,2,opt,name=runtime_name,json=runtimeName,proto3" json:"runtime_name,omitempty"` + NetworkId string `protobuf:"bytes,3,opt,name=network_id,json=networkId,proto3" json:"network_id,omitempty"` + Aliases []string `protobuf:"bytes,4,rep,name=aliases,proto3" json:"aliases,omitempty"` + Ipv4Address string `protobuf:"bytes,5,opt,name=ipv4_address,json=ipv4Address,proto3" json:"ipv4_address,omitempty"` + Primary bool `protobuf:"varint,6,opt,name=primary,proto3" json:"primary,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxNetworkAttachment) Reset() { + *x = SandboxNetworkAttachment{} + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[84] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxNetworkAttachment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxNetworkAttachment) ProtoMessage() {} + +func (x *SandboxNetworkAttachment) ProtoReflect() protoreflect.Message { + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[84] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxNetworkAttachment.ProtoReflect.Descriptor instead. +func (*SandboxNetworkAttachment) Descriptor() ([]byte, []int) { + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{84} +} + +func (x *SandboxNetworkAttachment) GetLogicalName() string { + if x != nil { + return x.LogicalName + } + return "" +} + +func (x *SandboxNetworkAttachment) GetRuntimeName() string { + if x != nil { + return x.RuntimeName + } + return "" +} + +func (x *SandboxNetworkAttachment) GetNetworkId() string { + if x != nil { + return x.NetworkId + } + return "" +} + +func (x *SandboxNetworkAttachment) GetAliases() []string { + if x != nil { + return x.Aliases + } + return nil +} + +func (x *SandboxNetworkAttachment) GetIpv4Address() string { + if x != nil { + return x.Ipv4Address + } + return "" +} + +func (x *SandboxNetworkAttachment) GetPrimary() bool { + if x != nil { + return x.Primary + } + return false +} + +type SandboxPortBinding struct { + state protoimpl.MessageState `protogen:"open.v1"` + ContainerPort string `protobuf:"bytes,1,opt,name=container_port,json=containerPort,proto3" json:"container_port,omitempty"` + HostIp string `protobuf:"bytes,2,opt,name=host_ip,json=hostIp,proto3" json:"host_ip,omitempty"` + HostPort string `protobuf:"bytes,3,opt,name=host_port,json=hostPort,proto3" json:"host_port,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxPortBinding) Reset() { + *x = SandboxPortBinding{} + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[85] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxPortBinding) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxPortBinding) ProtoMessage() {} + +func (x *SandboxPortBinding) ProtoReflect() protoreflect.Message { + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[85] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxPortBinding.ProtoReflect.Descriptor instead. +func (*SandboxPortBinding) Descriptor() ([]byte, []int) { + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{85} +} + +func (x *SandboxPortBinding) GetContainerPort() string { + if x != nil { + return x.ContainerPort + } + return "" +} + +func (x *SandboxPortBinding) GetHostIp() string { + if x != nil { + return x.HostIp + } + return "" +} + +func (x *SandboxPortBinding) GetHostPort() string { + if x != nil { + return x.HostPort + } + return "" +} + type SandboxTag struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -7450,7 +7702,7 @@ type SandboxTag struct { func (x *SandboxTag) Reset() { *x = SandboxTag{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[83] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7462,7 +7714,7 @@ func (x *SandboxTag) String() string { func (*SandboxTag) ProtoMessage() {} func (x *SandboxTag) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[83] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7475,7 +7727,7 @@ func (x *SandboxTag) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxTag.ProtoReflect.Descriptor instead. func (*SandboxTag) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{83} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{86} } func (x *SandboxTag) GetName() string { @@ -7502,7 +7754,7 @@ type ListSandboxesRequest struct { func (x *ListSandboxesRequest) Reset() { *x = ListSandboxesRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[84] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7514,7 +7766,7 @@ func (x *ListSandboxesRequest) String() string { func (*ListSandboxesRequest) ProtoMessage() {} func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[84] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7527,7 +7779,7 @@ func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxesRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{84} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{87} } func (x *ListSandboxesRequest) GetLimit() uint32 { @@ -7554,7 +7806,7 @@ type ListSandboxesResponse struct { func (x *ListSandboxesResponse) Reset() { *x = ListSandboxesResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[85] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7566,7 +7818,7 @@ func (x *ListSandboxesResponse) String() string { func (*ListSandboxesResponse) ProtoMessage() {} func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[85] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7579,7 +7831,7 @@ func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxesResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{85} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{88} } func (x *ListSandboxesResponse) GetSandboxes() []*Sandbox { @@ -7605,7 +7857,7 @@ type GetSandboxResponse struct { func (x *GetSandboxResponse) Reset() { *x = GetSandboxResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[86] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7617,7 +7869,7 @@ func (x *GetSandboxResponse) String() string { func (*GetSandboxResponse) ProtoMessage() {} func (x *GetSandboxResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[86] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7630,7 +7882,7 @@ func (x *GetSandboxResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxResponse.ProtoReflect.Descriptor instead. func (*GetSandboxResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{86} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{89} } func (x *GetSandboxResponse) GetSandbox() *Sandbox { @@ -7649,7 +7901,7 @@ type StopSandboxRequest struct { func (x *StopSandboxRequest) Reset() { *x = StopSandboxRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[87] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7661,7 +7913,7 @@ func (x *StopSandboxRequest) String() string { func (*StopSandboxRequest) ProtoMessage() {} func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[87] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7674,7 +7926,7 @@ func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopSandboxRequest.ProtoReflect.Descriptor instead. func (*StopSandboxRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{87} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{90} } func (x *StopSandboxRequest) GetSandboxId() string { @@ -7693,7 +7945,7 @@ type StopSandboxResponse struct { func (x *StopSandboxResponse) Reset() { *x = StopSandboxResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[88] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7705,7 +7957,7 @@ func (x *StopSandboxResponse) String() string { func (*StopSandboxResponse) ProtoMessage() {} func (x *StopSandboxResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[88] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7718,7 +7970,7 @@ func (x *StopSandboxResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StopSandboxResponse.ProtoReflect.Descriptor instead. func (*StopSandboxResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{88} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{91} } func (x *StopSandboxResponse) GetSandbox() *Sandbox { @@ -7737,7 +7989,7 @@ type ResumeSandboxRequest struct { func (x *ResumeSandboxRequest) Reset() { *x = ResumeSandboxRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[89] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7749,7 +8001,7 @@ func (x *ResumeSandboxRequest) String() string { func (*ResumeSandboxRequest) ProtoMessage() {} func (x *ResumeSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[89] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7762,7 +8014,7 @@ func (x *ResumeSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeSandboxRequest.ProtoReflect.Descriptor instead. func (*ResumeSandboxRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{89} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{92} } func (x *ResumeSandboxRequest) GetSandboxId() string { @@ -7781,7 +8033,7 @@ type ResumeSandboxResponse struct { func (x *ResumeSandboxResponse) Reset() { *x = ResumeSandboxResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[90] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7793,7 +8045,7 @@ func (x *ResumeSandboxResponse) String() string { func (*ResumeSandboxResponse) ProtoMessage() {} func (x *ResumeSandboxResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[90] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7806,7 +8058,7 @@ func (x *ResumeSandboxResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeSandboxResponse.ProtoReflect.Descriptor instead. func (*ResumeSandboxResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{90} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{93} } func (x *ResumeSandboxResponse) GetSandbox() *Sandbox { @@ -7828,7 +8080,7 @@ type MetricValue struct { func (x *MetricValue) Reset() { *x = MetricValue{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[91] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7840,7 +8092,7 @@ func (x *MetricValue) String() string { func (*MetricValue) ProtoMessage() {} func (x *MetricValue) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[91] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7853,7 +8105,7 @@ func (x *MetricValue) ProtoReflect() protoreflect.Message { // Deprecated: Use MetricValue.ProtoReflect.Descriptor instead. func (*MetricValue) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{91} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{94} } func (x *MetricValue) GetValue() float64 { @@ -7904,7 +8156,7 @@ type SandboxStats struct { func (x *SandboxStats) Reset() { *x = SandboxStats{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[92] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7916,7 +8168,7 @@ func (x *SandboxStats) String() string { func (*SandboxStats) ProtoMessage() {} func (x *SandboxStats) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[92] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7929,7 +8181,7 @@ func (x *SandboxStats) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStats.ProtoReflect.Descriptor instead. func (*SandboxStats) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{92} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{95} } func (x *SandboxStats) GetSandboxId() string { @@ -8045,7 +8297,7 @@ type RunSummary struct { func (x *RunSummary) Reset() { *x = RunSummary{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[93] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8057,7 +8309,7 @@ func (x *RunSummary) String() string { func (*RunSummary) ProtoMessage() {} func (x *RunSummary) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[93] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8070,7 +8322,7 @@ func (x *RunSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use RunSummary.ProtoReflect.Descriptor instead. func (*RunSummary) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{93} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{96} } func (x *RunSummary) GetRunId() string { @@ -8238,7 +8490,7 @@ type RunDetail struct { func (x *RunDetail) Reset() { *x = RunDetail{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[94] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8250,7 +8502,7 @@ func (x *RunDetail) String() string { func (*RunDetail) ProtoMessage() {} func (x *RunDetail) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[94] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8263,7 +8515,7 @@ func (x *RunDetail) ProtoReflect() protoreflect.Message { // Deprecated: Use RunDetail.ProtoReflect.Descriptor instead. func (*RunDetail) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{94} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{97} } func (x *RunDetail) GetSummary() *RunSummary { @@ -8355,7 +8607,7 @@ type ExecRequest struct { func (x *ExecRequest) Reset() { *x = ExecRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[95] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8367,7 +8619,7 @@ func (x *ExecRequest) String() string { func (*ExecRequest) ProtoMessage() {} func (x *ExecRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[95] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8380,7 +8632,7 @@ func (x *ExecRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecRequest.ProtoReflect.Descriptor instead. func (*ExecRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{95} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{98} } func (x *ExecRequest) GetTarget() isExecRequest_Target { @@ -8485,7 +8737,7 @@ type ExecSandboxSelector struct { func (x *ExecSandboxSelector) Reset() { *x = ExecSandboxSelector{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[96] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8497,7 +8749,7 @@ func (x *ExecSandboxSelector) String() string { func (*ExecSandboxSelector) ProtoMessage() {} func (x *ExecSandboxSelector) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[96] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8510,7 +8762,7 @@ func (x *ExecSandboxSelector) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxSelector.ProtoReflect.Descriptor instead. func (*ExecSandboxSelector) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{96} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{99} } func (x *ExecSandboxSelector) GetProjectId() string { @@ -8544,7 +8796,7 @@ type ExecCommand struct { func (x *ExecCommand) Reset() { *x = ExecCommand{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[97] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8556,7 +8808,7 @@ func (x *ExecCommand) String() string { func (*ExecCommand) ProtoMessage() {} func (x *ExecCommand) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[97] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8569,7 +8821,7 @@ func (x *ExecCommand) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecCommand.ProtoReflect.Descriptor instead. func (*ExecCommand) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{97} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{100} } func (x *ExecCommand) GetCommand() string { @@ -8595,7 +8847,7 @@ type ExecResponse struct { func (x *ExecResponse) Reset() { *x = ExecResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[98] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8607,7 +8859,7 @@ func (x *ExecResponse) String() string { func (*ExecResponse) ProtoMessage() {} func (x *ExecResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[98] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8620,7 +8872,7 @@ func (x *ExecResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecResponse.ProtoReflect.Descriptor instead. func (*ExecResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{98} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{101} } func (x *ExecResponse) GetResult() *ExecResult { @@ -8646,7 +8898,7 @@ type ExecStreamResponse struct { func (x *ExecStreamResponse) Reset() { *x = ExecStreamResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[99] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8658,7 +8910,7 @@ func (x *ExecStreamResponse) String() string { func (*ExecStreamResponse) ProtoMessage() {} func (x *ExecStreamResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[99] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8671,7 +8923,7 @@ func (x *ExecStreamResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecStreamResponse.ProtoReflect.Descriptor instead. func (*ExecStreamResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{99} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{102} } func (x *ExecStreamResponse) GetEventType() ExecStreamEventType { @@ -8749,7 +9001,7 @@ type ExecAttachRequest struct { func (x *ExecAttachRequest) Reset() { *x = ExecAttachRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[100] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8761,7 +9013,7 @@ func (x *ExecAttachRequest) String() string { func (*ExecAttachRequest) ProtoMessage() {} func (x *ExecAttachRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[100] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8774,7 +9026,7 @@ func (x *ExecAttachRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecAttachRequest.ProtoReflect.Descriptor instead. func (*ExecAttachRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{100} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{103} } func (x *ExecAttachRequest) GetClientFrameId() string { @@ -8919,7 +9171,7 @@ type ExecAttachResponse struct { func (x *ExecAttachResponse) Reset() { *x = ExecAttachResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[101] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8931,7 +9183,7 @@ func (x *ExecAttachResponse) String() string { func (*ExecAttachResponse) ProtoMessage() {} func (x *ExecAttachResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[101] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8944,7 +9196,7 @@ func (x *ExecAttachResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecAttachResponse.ProtoReflect.Descriptor instead. func (*ExecAttachResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{101} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{104} } func (x *ExecAttachResponse) GetServerFrameId() string { @@ -9076,7 +9328,7 @@ type ExecAttachStart struct { func (x *ExecAttachStart) Reset() { *x = ExecAttachStart{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[102] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9088,7 +9340,7 @@ func (x *ExecAttachStart) String() string { func (*ExecAttachStart) ProtoMessage() {} func (x *ExecAttachStart) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[102] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9101,7 +9353,7 @@ func (x *ExecAttachStart) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecAttachStart.ProtoReflect.Descriptor instead. func (*ExecAttachStart) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{102} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{105} } func (x *ExecAttachStart) GetRequest() *ExecRequest { @@ -9156,7 +9408,7 @@ type AttachTerminalSize struct { func (x *AttachTerminalSize) Reset() { *x = AttachTerminalSize{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[103] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9168,7 +9420,7 @@ func (x *AttachTerminalSize) String() string { func (*AttachTerminalSize) ProtoMessage() {} func (x *AttachTerminalSize) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[103] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9181,7 +9433,7 @@ func (x *AttachTerminalSize) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachTerminalSize.ProtoReflect.Descriptor instead. func (*AttachTerminalSize) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{103} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{106} } func (x *AttachTerminalSize) GetRows() uint32 { @@ -9207,7 +9459,7 @@ type AttachStdin struct { func (x *AttachStdin) Reset() { *x = AttachStdin{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[104] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9219,7 +9471,7 @@ func (x *AttachStdin) String() string { func (*AttachStdin) ProtoMessage() {} func (x *AttachStdin) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[104] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9232,7 +9484,7 @@ func (x *AttachStdin) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachStdin.ProtoReflect.Descriptor instead. func (*AttachStdin) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{104} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{107} } func (x *AttachStdin) GetData() []byte { @@ -9250,7 +9502,7 @@ type AttachStdinEOF struct { func (x *AttachStdinEOF) Reset() { *x = AttachStdinEOF{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[105] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9262,7 +9514,7 @@ func (x *AttachStdinEOF) String() string { func (*AttachStdinEOF) ProtoMessage() {} func (x *AttachStdinEOF) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[105] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9275,7 +9527,7 @@ func (x *AttachStdinEOF) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachStdinEOF.ProtoReflect.Descriptor instead. func (*AttachStdinEOF) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{105} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{108} } type AttachResize struct { @@ -9287,7 +9539,7 @@ type AttachResize struct { func (x *AttachResize) Reset() { *x = AttachResize{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[106] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9299,7 +9551,7 @@ func (x *AttachResize) String() string { func (*AttachResize) ProtoMessage() {} func (x *AttachResize) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[106] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9312,7 +9564,7 @@ func (x *AttachResize) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachResize.ProtoReflect.Descriptor instead. func (*AttachResize) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{106} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{109} } func (x *AttachResize) GetTerminalSize() *AttachTerminalSize { @@ -9331,7 +9583,7 @@ type AttachSignal struct { func (x *AttachSignal) Reset() { *x = AttachSignal{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[107] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9343,7 +9595,7 @@ func (x *AttachSignal) String() string { func (*AttachSignal) ProtoMessage() {} func (x *AttachSignal) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[107] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9356,7 +9608,7 @@ func (x *AttachSignal) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachSignal.ProtoReflect.Descriptor instead. func (*AttachSignal) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{107} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{110} } func (x *AttachSignal) GetSignal() string { @@ -9376,7 +9628,7 @@ type AttachHumanMessage struct { func (x *AttachHumanMessage) Reset() { *x = AttachHumanMessage{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[108] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9388,7 +9640,7 @@ func (x *AttachHumanMessage) String() string { func (*AttachHumanMessage) ProtoMessage() {} func (x *AttachHumanMessage) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[108] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9401,7 +9653,7 @@ func (x *AttachHumanMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachHumanMessage.ProtoReflect.Descriptor instead. func (*AttachHumanMessage) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{108} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{111} } func (x *AttachHumanMessage) GetText() string { @@ -9427,7 +9679,7 @@ type AttachCancel struct { func (x *AttachCancel) Reset() { *x = AttachCancel{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[109] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9439,7 +9691,7 @@ func (x *AttachCancel) String() string { func (*AttachCancel) ProtoMessage() {} func (x *AttachCancel) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[109] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9452,7 +9704,7 @@ func (x *AttachCancel) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachCancel.ProtoReflect.Descriptor instead. func (*AttachCancel) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{109} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{112} } func (x *AttachCancel) GetReason() string { @@ -9476,7 +9728,7 @@ type AttachStarted struct { func (x *AttachStarted) Reset() { *x = AttachStarted{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[110] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9488,7 +9740,7 @@ func (x *AttachStarted) String() string { func (*AttachStarted) ProtoMessage() {} func (x *AttachStarted) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[110] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9501,7 +9753,7 @@ func (x *AttachStarted) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachStarted.ProtoReflect.Descriptor instead. func (*AttachStarted) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{110} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{113} } func (x *AttachStarted) GetOperationId() string { @@ -9558,7 +9810,7 @@ type AttachOutput struct { func (x *AttachOutput) Reset() { *x = AttachOutput{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[111] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9570,7 +9822,7 @@ func (x *AttachOutput) String() string { func (*AttachOutput) ProtoMessage() {} func (x *AttachOutput) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[111] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9583,7 +9835,7 @@ func (x *AttachOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachOutput.ProtoReflect.Descriptor instead. func (*AttachOutput) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{111} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{114} } func (x *AttachOutput) GetData() []byte { @@ -9626,7 +9878,7 @@ type AttachAgentEvent struct { func (x *AttachAgentEvent) Reset() { *x = AttachAgentEvent{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[112] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9638,7 +9890,7 @@ func (x *AttachAgentEvent) String() string { func (*AttachAgentEvent) ProtoMessage() {} func (x *AttachAgentEvent) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[112] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9651,7 +9903,7 @@ func (x *AttachAgentEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachAgentEvent.ProtoReflect.Descriptor instead. func (*AttachAgentEvent) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{112} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{115} } func (x *AttachAgentEvent) GetName() string { @@ -9693,7 +9945,7 @@ type AttachAgentTurnCompleted struct { func (x *AttachAgentTurnCompleted) Reset() { *x = AttachAgentTurnCompleted{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[113] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9705,7 +9957,7 @@ func (x *AttachAgentTurnCompleted) String() string { func (*AttachAgentTurnCompleted) ProtoMessage() {} func (x *AttachAgentTurnCompleted) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[113] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9718,7 +9970,7 @@ func (x *AttachAgentTurnCompleted) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachAgentTurnCompleted.ProtoReflect.Descriptor instead. func (*AttachAgentTurnCompleted) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{113} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{116} } func (x *AttachAgentTurnCompleted) GetRunId() string { @@ -9757,7 +10009,7 @@ type AttachResult struct { func (x *AttachResult) Reset() { *x = AttachResult{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[114] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9769,7 +10021,7 @@ func (x *AttachResult) String() string { func (*AttachResult) ProtoMessage() {} func (x *AttachResult) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[114] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9782,7 +10034,7 @@ func (x *AttachResult) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachResult.ProtoReflect.Descriptor instead. func (*AttachResult) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{114} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{117} } func (x *AttachResult) GetExitCode() int32 { @@ -9846,7 +10098,7 @@ type AttachError struct { func (x *AttachError) Reset() { *x = AttachError{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[115] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9858,7 +10110,7 @@ func (x *AttachError) String() string { func (*AttachError) ProtoMessage() {} func (x *AttachError) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[115] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9871,7 +10123,7 @@ func (x *AttachError) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachError.ProtoReflect.Descriptor instead. func (*AttachError) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{115} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{118} } func (x *AttachError) GetCode() string { @@ -9924,7 +10176,7 @@ type ExecResult struct { func (x *ExecResult) Reset() { *x = ExecResult{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[116] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9936,7 +10188,7 @@ func (x *ExecResult) String() string { func (*ExecResult) ProtoMessage() {} func (x *ExecResult) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[116] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9949,7 +10201,7 @@ func (x *ExecResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecResult.ProtoReflect.Descriptor instead. func (*ExecResult) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{116} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{119} } func (x *ExecResult) GetExecId() string { @@ -10064,7 +10316,7 @@ type ListImagesRequest struct { func (x *ListImagesRequest) Reset() { *x = ListImagesRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[117] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10076,7 +10328,7 @@ func (x *ListImagesRequest) String() string { func (*ListImagesRequest) ProtoMessage() {} func (x *ListImagesRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[117] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10089,7 +10341,7 @@ func (x *ListImagesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListImagesRequest.ProtoReflect.Descriptor instead. func (*ListImagesRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{117} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{120} } func (x *ListImagesRequest) GetStore() ImageStoreKind { @@ -10147,7 +10399,7 @@ type ListImagesResponse struct { func (x *ListImagesResponse) Reset() { *x = ListImagesResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[118] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10159,7 +10411,7 @@ func (x *ListImagesResponse) String() string { func (*ListImagesResponse) ProtoMessage() {} func (x *ListImagesResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[118] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10172,7 +10424,7 @@ func (x *ListImagesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListImagesResponse.ProtoReflect.Descriptor instead. func (*ListImagesResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{118} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{121} } func (x *ListImagesResponse) GetImages() []*Image { @@ -10221,7 +10473,7 @@ type PullImageRequest struct { func (x *PullImageRequest) Reset() { *x = PullImageRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[119] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10233,7 +10485,7 @@ func (x *PullImageRequest) String() string { func (*PullImageRequest) ProtoMessage() {} func (x *PullImageRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[119] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10246,7 +10498,7 @@ func (x *PullImageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PullImageRequest.ProtoReflect.Descriptor instead. func (*PullImageRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{119} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{122} } func (x *PullImageRequest) GetImageRef() string { @@ -10283,7 +10535,7 @@ type PullImageResponse struct { func (x *PullImageResponse) Reset() { *x = PullImageResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[120] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10295,7 +10547,7 @@ func (x *PullImageResponse) String() string { func (*PullImageResponse) ProtoMessage() {} func (x *PullImageResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[120] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10308,7 +10560,7 @@ func (x *PullImageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PullImageResponse.ProtoReflect.Descriptor instead. func (*PullImageResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{120} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{123} } func (x *PullImageResponse) GetImage() *Image { @@ -10357,7 +10609,7 @@ type InspectImageRequest struct { func (x *InspectImageRequest) Reset() { *x = InspectImageRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[121] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10369,7 +10621,7 @@ func (x *InspectImageRequest) String() string { func (*InspectImageRequest) ProtoMessage() {} func (x *InspectImageRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[121] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10382,7 +10634,7 @@ func (x *InspectImageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectImageRequest.ProtoReflect.Descriptor instead. func (*InspectImageRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{121} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{124} } func (x *InspectImageRequest) GetImageRef() string { @@ -10416,7 +10668,7 @@ type InspectImageResponse struct { func (x *InspectImageResponse) Reset() { *x = InspectImageResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[122] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10428,7 +10680,7 @@ func (x *InspectImageResponse) String() string { func (*InspectImageResponse) ProtoMessage() {} func (x *InspectImageResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[122] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10441,7 +10693,7 @@ func (x *InspectImageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectImageResponse.ProtoReflect.Descriptor instead. func (*InspectImageResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{122} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{125} } func (x *InspectImageResponse) GetImage() *Image { @@ -10470,7 +10722,7 @@ type RemoveImageRequest struct { func (x *RemoveImageRequest) Reset() { *x = RemoveImageRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[123] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10482,7 +10734,7 @@ func (x *RemoveImageRequest) String() string { func (*RemoveImageRequest) ProtoMessage() {} func (x *RemoveImageRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[123] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10495,7 +10747,7 @@ func (x *RemoveImageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveImageRequest.ProtoReflect.Descriptor instead. func (*RemoveImageRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{123} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{126} } func (x *RemoveImageRequest) GetImageRef() string { @@ -10538,7 +10790,7 @@ type RemoveImageResponse struct { func (x *RemoveImageResponse) Reset() { *x = RemoveImageResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[124] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10550,7 +10802,7 @@ func (x *RemoveImageResponse) String() string { func (*RemoveImageResponse) ProtoMessage() {} func (x *RemoveImageResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[124] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10563,7 +10815,7 @@ func (x *RemoveImageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveImageResponse.ProtoReflect.Descriptor instead. func (*RemoveImageResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{124} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{127} } func (x *RemoveImageResponse) GetImageRef() string { @@ -10611,7 +10863,7 @@ type BuildImageRequest struct { func (x *BuildImageRequest) Reset() { *x = BuildImageRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[125] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10623,7 +10875,7 @@ func (x *BuildImageRequest) String() string { func (*BuildImageRequest) ProtoMessage() {} func (x *BuildImageRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[125] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10636,7 +10888,7 @@ func (x *BuildImageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildImageRequest.ProtoReflect.Descriptor instead. func (*BuildImageRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{125} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{128} } func (x *BuildImageRequest) GetContextDir() string { @@ -10717,7 +10969,7 @@ type BuildImageEvent struct { func (x *BuildImageEvent) Reset() { *x = BuildImageEvent{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[126] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10729,7 +10981,7 @@ func (x *BuildImageEvent) String() string { func (*BuildImageEvent) ProtoMessage() {} func (x *BuildImageEvent) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[126] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10742,7 +10994,7 @@ func (x *BuildImageEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildImageEvent.ProtoReflect.Descriptor instead. func (*BuildImageEvent) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{126} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{129} } func (x *BuildImageEvent) GetStatus() ImageOperationStatus { @@ -10808,7 +11060,7 @@ type CacheFilter struct { func (x *CacheFilter) Reset() { *x = CacheFilter{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[127] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10820,7 +11072,7 @@ func (x *CacheFilter) String() string { func (*CacheFilter) ProtoMessage() {} func (x *CacheFilter) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[127] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10833,7 +11085,7 @@ func (x *CacheFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use CacheFilter.ProtoReflect.Descriptor instead. func (*CacheFilter) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{127} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{130} } func (x *CacheFilter) GetDriver() string { @@ -10887,7 +11139,7 @@ type ListCachesRequest struct { func (x *ListCachesRequest) Reset() { *x = ListCachesRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[128] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10899,7 +11151,7 @@ func (x *ListCachesRequest) String() string { func (*ListCachesRequest) ProtoMessage() {} func (x *ListCachesRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[128] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10912,7 +11164,7 @@ func (x *ListCachesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCachesRequest.ProtoReflect.Descriptor instead. func (*ListCachesRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{128} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{131} } func (x *ListCachesRequest) GetFilter() *CacheFilter { @@ -10932,7 +11184,7 @@ type ListCachesResponse struct { func (x *ListCachesResponse) Reset() { *x = ListCachesResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[129] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10944,7 +11196,7 @@ func (x *ListCachesResponse) String() string { func (*ListCachesResponse) ProtoMessage() {} func (x *ListCachesResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[129] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10957,7 +11209,7 @@ func (x *ListCachesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCachesResponse.ProtoReflect.Descriptor instead. func (*ListCachesResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{129} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{132} } func (x *ListCachesResponse) GetCaches() []*CacheItem { @@ -10983,7 +11235,7 @@ type InspectCacheRequest struct { func (x *InspectCacheRequest) Reset() { *x = InspectCacheRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[130] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10995,7 +11247,7 @@ func (x *InspectCacheRequest) String() string { func (*InspectCacheRequest) ProtoMessage() {} func (x *InspectCacheRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[130] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11008,7 +11260,7 @@ func (x *InspectCacheRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectCacheRequest.ProtoReflect.Descriptor instead. func (*InspectCacheRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{130} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{133} } func (x *InspectCacheRequest) GetCacheId() string { @@ -11028,7 +11280,7 @@ type InspectCacheResponse struct { func (x *InspectCacheResponse) Reset() { *x = InspectCacheResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[131] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11040,7 +11292,7 @@ func (x *InspectCacheResponse) String() string { func (*InspectCacheResponse) ProtoMessage() {} func (x *InspectCacheResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[131] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11053,7 +11305,7 @@ func (x *InspectCacheResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectCacheResponse.ProtoReflect.Descriptor instead. func (*InspectCacheResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{131} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{134} } func (x *InspectCacheResponse) GetCache() *CacheItem { @@ -11080,7 +11332,7 @@ type PruneCachesRequest struct { func (x *PruneCachesRequest) Reset() { *x = PruneCachesRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[132] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11092,7 +11344,7 @@ func (x *PruneCachesRequest) String() string { func (*PruneCachesRequest) ProtoMessage() {} func (x *PruneCachesRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[132] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11105,7 +11357,7 @@ func (x *PruneCachesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PruneCachesRequest.ProtoReflect.Descriptor instead. func (*PruneCachesRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{132} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{135} } func (x *PruneCachesRequest) GetFilter() *CacheFilter { @@ -11135,7 +11387,7 @@ type PruneCachesResponse struct { func (x *PruneCachesResponse) Reset() { *x = PruneCachesResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[133] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11147,7 +11399,7 @@ func (x *PruneCachesResponse) String() string { func (*PruneCachesResponse) ProtoMessage() {} func (x *PruneCachesResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[133] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11160,7 +11412,7 @@ func (x *PruneCachesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PruneCachesResponse.ProtoReflect.Descriptor instead. func (*PruneCachesResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{133} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{136} } func (x *PruneCachesResponse) GetDryRun() bool { @@ -11208,7 +11460,7 @@ type RemoveCacheRequest struct { func (x *RemoveCacheRequest) Reset() { *x = RemoveCacheRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[134] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11220,7 +11472,7 @@ func (x *RemoveCacheRequest) String() string { func (*RemoveCacheRequest) ProtoMessage() {} func (x *RemoveCacheRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[134] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11233,7 +11485,7 @@ func (x *RemoveCacheRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveCacheRequest.ProtoReflect.Descriptor instead. func (*RemoveCacheRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{134} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{137} } func (x *RemoveCacheRequest) GetCacheId() string { @@ -11263,7 +11515,7 @@ type RemoveCacheResponse struct { func (x *RemoveCacheResponse) Reset() { *x = RemoveCacheResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[135] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11275,7 +11527,7 @@ func (x *RemoveCacheResponse) String() string { func (*RemoveCacheResponse) ProtoMessage() {} func (x *RemoveCacheResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[135] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11288,7 +11540,7 @@ func (x *RemoveCacheResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveCacheResponse.ProtoReflect.Descriptor instead. func (*RemoveCacheResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{135} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{138} } func (x *RemoveCacheResponse) GetDryRun() bool { @@ -11350,7 +11602,7 @@ type CacheItem struct { func (x *CacheItem) Reset() { *x = CacheItem{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[136] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11362,7 +11614,7 @@ func (x *CacheItem) String() string { func (*CacheItem) ProtoMessage() {} func (x *CacheItem) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[136] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11375,7 +11627,7 @@ func (x *CacheItem) ProtoReflect() protoreflect.Message { // Deprecated: Use CacheItem.ProtoReflect.Descriptor instead. func (*CacheItem) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{136} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{139} } func (x *CacheItem) GetCacheId() string { @@ -11505,7 +11757,7 @@ type CacheReference struct { func (x *CacheReference) Reset() { *x = CacheReference{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[137] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11517,7 +11769,7 @@ func (x *CacheReference) String() string { func (*CacheReference) ProtoMessage() {} func (x *CacheReference) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[137] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11530,7 +11782,7 @@ func (x *CacheReference) ProtoReflect() protoreflect.Message { // Deprecated: Use CacheReference.ProtoReflect.Descriptor instead. func (*CacheReference) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{137} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{140} } func (x *CacheReference) GetType() string { @@ -11593,7 +11845,7 @@ type ListVolumesRequest struct { func (x *ListVolumesRequest) Reset() { *x = ListVolumesRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[138] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11605,7 +11857,7 @@ func (x *ListVolumesRequest) String() string { func (*ListVolumesRequest) ProtoMessage() {} func (x *ListVolumesRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[138] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11618,7 +11870,7 @@ func (x *ListVolumesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVolumesRequest.ProtoReflect.Descriptor instead. func (*ListVolumesRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{138} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{141} } func (x *ListVolumesRequest) GetQuery() string { @@ -11651,7 +11903,7 @@ type ListVolumesResponse struct { func (x *ListVolumesResponse) Reset() { *x = ListVolumesResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[139] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11663,7 +11915,7 @@ func (x *ListVolumesResponse) String() string { func (*ListVolumesResponse) ProtoMessage() {} func (x *ListVolumesResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[139] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11676,7 +11928,7 @@ func (x *ListVolumesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVolumesResponse.ProtoReflect.Descriptor instead. func (*ListVolumesResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{139} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{142} } func (x *ListVolumesResponse) GetVolumes() []*Volume { @@ -11698,7 +11950,7 @@ type CreateVolumeRequest struct { func (x *CreateVolumeRequest) Reset() { *x = CreateVolumeRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[140] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11710,7 +11962,7 @@ func (x *CreateVolumeRequest) String() string { func (*CreateVolumeRequest) ProtoMessage() {} func (x *CreateVolumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[140] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11723,7 +11975,7 @@ func (x *CreateVolumeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateVolumeRequest.ProtoReflect.Descriptor instead. func (*CreateVolumeRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{140} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{143} } func (x *CreateVolumeRequest) GetName() string { @@ -11764,7 +12016,7 @@ type CreateVolumeResponse struct { func (x *CreateVolumeResponse) Reset() { *x = CreateVolumeResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[141] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11776,7 +12028,7 @@ func (x *CreateVolumeResponse) String() string { func (*CreateVolumeResponse) ProtoMessage() {} func (x *CreateVolumeResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[141] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11789,7 +12041,7 @@ func (x *CreateVolumeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateVolumeResponse.ProtoReflect.Descriptor instead. func (*CreateVolumeResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{141} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{144} } func (x *CreateVolumeResponse) GetVolume() *Volume { @@ -11815,7 +12067,7 @@ type InspectVolumeRequest struct { func (x *InspectVolumeRequest) Reset() { *x = InspectVolumeRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[142] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11827,7 +12079,7 @@ func (x *InspectVolumeRequest) String() string { func (*InspectVolumeRequest) ProtoMessage() {} func (x *InspectVolumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[142] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11840,7 +12092,7 @@ func (x *InspectVolumeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectVolumeRequest.ProtoReflect.Descriptor instead. func (*InspectVolumeRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{142} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{145} } func (x *InspectVolumeRequest) GetName() string { @@ -11859,7 +12111,7 @@ type InspectVolumeResponse struct { func (x *InspectVolumeResponse) Reset() { *x = InspectVolumeResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[143] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11871,7 +12123,7 @@ func (x *InspectVolumeResponse) String() string { func (*InspectVolumeResponse) ProtoMessage() {} func (x *InspectVolumeResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[143] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11884,7 +12136,7 @@ func (x *InspectVolumeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectVolumeResponse.ProtoReflect.Descriptor instead. func (*InspectVolumeResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{143} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{146} } func (x *InspectVolumeResponse) GetVolume() *Volume { @@ -11904,7 +12156,7 @@ type RemoveVolumeRequest struct { func (x *RemoveVolumeRequest) Reset() { *x = RemoveVolumeRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[144] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11916,7 +12168,7 @@ func (x *RemoveVolumeRequest) String() string { func (*RemoveVolumeRequest) ProtoMessage() {} func (x *RemoveVolumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[144] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11929,7 +12181,7 @@ func (x *RemoveVolumeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveVolumeRequest.ProtoReflect.Descriptor instead. func (*RemoveVolumeRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{144} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{147} } func (x *RemoveVolumeRequest) GetName() string { @@ -11956,7 +12208,7 @@ type RemoveVolumeResponse struct { func (x *RemoveVolumeResponse) Reset() { *x = RemoveVolumeResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[145] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11968,7 +12220,7 @@ func (x *RemoveVolumeResponse) String() string { func (*RemoveVolumeResponse) ProtoMessage() {} func (x *RemoveVolumeResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[145] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11981,7 +12233,7 @@ func (x *RemoveVolumeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveVolumeResponse.ProtoReflect.Descriptor instead. func (*RemoveVolumeResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{145} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{148} } func (x *RemoveVolumeResponse) GetName() string { @@ -12010,7 +12262,7 @@ type PruneVolumesRequest struct { func (x *PruneVolumesRequest) Reset() { *x = PruneVolumesRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[146] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12022,7 +12274,7 @@ func (x *PruneVolumesRequest) String() string { func (*PruneVolumesRequest) ProtoMessage() {} func (x *PruneVolumesRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[146] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12035,7 +12287,7 @@ func (x *PruneVolumesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PruneVolumesRequest.ProtoReflect.Descriptor instead. func (*PruneVolumesRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{146} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{149} } func (x *PruneVolumesRequest) GetQuery() string { @@ -12078,7 +12330,7 @@ type PruneVolumesResponse struct { func (x *PruneVolumesResponse) Reset() { *x = PruneVolumesResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[147] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12090,7 +12342,7 @@ func (x *PruneVolumesResponse) String() string { func (*PruneVolumesResponse) ProtoMessage() {} func (x *PruneVolumesResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[147] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12103,7 +12355,7 @@ func (x *PruneVolumesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PruneVolumesResponse.ProtoReflect.Descriptor instead. func (*PruneVolumesResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{147} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{150} } func (x *PruneVolumesResponse) GetDryRun() bool { @@ -12150,7 +12402,7 @@ type Volume struct { func (x *Volume) Reset() { *x = Volume{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[148] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12162,7 +12414,7 @@ func (x *Volume) String() string { func (*Volume) ProtoMessage() {} func (x *Volume) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[148] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12175,7 +12427,7 @@ func (x *Volume) ProtoReflect() protoreflect.Message { // Deprecated: Use Volume.ProtoReflect.Descriptor instead. func (*Volume) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{148} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{151} } func (x *Volume) GetName() string { @@ -12259,7 +12511,7 @@ type Image struct { func (x *Image) Reset() { *x = Image{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[149] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12271,7 +12523,7 @@ func (x *Image) String() string { func (*Image) ProtoMessage() {} func (x *Image) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[149] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12284,7 +12536,7 @@ func (x *Image) ProtoReflect() protoreflect.Message { // Deprecated: Use Image.ProtoReflect.Descriptor instead. func (*Image) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{149} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{152} } func (x *Image) GetImageId() string { @@ -12418,7 +12670,7 @@ type ImagePlatform struct { func (x *ImagePlatform) Reset() { *x = ImagePlatform{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[150] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12430,7 +12682,7 @@ func (x *ImagePlatform) String() string { func (*ImagePlatform) ProtoMessage() {} func (x *ImagePlatform) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[150] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12443,7 +12695,7 @@ func (x *ImagePlatform) ProtoReflect() protoreflect.Message { // Deprecated: Use ImagePlatform.ProtoReflect.Descriptor instead. func (*ImagePlatform) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{150} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{153} } func (x *ImagePlatform) GetOs() string { @@ -12486,7 +12738,7 @@ type ImageStoreStatus struct { func (x *ImageStoreStatus) Reset() { *x = ImageStoreStatus{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[151] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12498,7 +12750,7 @@ func (x *ImageStoreStatus) String() string { func (*ImageStoreStatus) ProtoMessage() {} func (x *ImageStoreStatus) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[151] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12511,7 +12763,7 @@ func (x *ImageStoreStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ImageStoreStatus.ProtoReflect.Descriptor instead. func (*ImageStoreStatus) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{151} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{154} } func (x *ImageStoreStatus) GetStore() ImageStoreKind { @@ -12553,7 +12805,7 @@ type DockerImageStatus struct { func (x *DockerImageStatus) Reset() { *x = DockerImageStatus{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[152] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12565,7 +12817,7 @@ func (x *DockerImageStatus) String() string { func (*DockerImageStatus) ProtoMessage() {} func (x *DockerImageStatus) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[152] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12578,7 +12830,7 @@ func (x *DockerImageStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use DockerImageStatus.ProtoReflect.Descriptor instead. func (*DockerImageStatus) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{152} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{155} } func (x *DockerImageStatus) GetLocal() bool { @@ -12616,7 +12868,7 @@ type OCIImageStatus struct { func (x *OCIImageStatus) Reset() { *x = OCIImageStatus{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[153] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12628,7 +12880,7 @@ func (x *OCIImageStatus) String() string { func (*OCIImageStatus) ProtoMessage() {} func (x *OCIImageStatus) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[153] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12641,7 +12893,7 @@ func (x *OCIImageStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use OCIImageStatus.ProtoReflect.Descriptor instead. func (*OCIImageStatus) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{153} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{156} } func (x *OCIImageStatus) GetLayoutCached() bool { @@ -12699,7 +12951,7 @@ type ImagePullProgress struct { func (x *ImagePullProgress) Reset() { *x = ImagePullProgress{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[154] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12711,7 +12963,7 @@ func (x *ImagePullProgress) String() string { func (*ImagePullProgress) ProtoMessage() {} func (x *ImagePullProgress) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[154] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12724,7 +12976,7 @@ func (x *ImagePullProgress) ProtoReflect() protoreflect.Message { // Deprecated: Use ImagePullProgress.ProtoReflect.Descriptor instead. func (*ImagePullProgress) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{154} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{157} } func (x *ImagePullProgress) GetId() string { @@ -12772,7 +13024,7 @@ type JupyterSpec struct { func (x *JupyterSpec) Reset() { *x = JupyterSpec{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[155] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12784,7 +13036,7 @@ func (x *JupyterSpec) String() string { func (*JupyterSpec) ProtoMessage() {} func (x *JupyterSpec) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[155] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12797,7 +13049,7 @@ func (x *JupyterSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use JupyterSpec.ProtoReflect.Descriptor instead. func (*JupyterSpec) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{155} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{158} } func (x *JupyterSpec) GetEnabled() bool { @@ -12825,7 +13077,7 @@ type RunJupyterSpec struct { func (x *RunJupyterSpec) Reset() { *x = RunJupyterSpec{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[156] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12837,7 +13089,7 @@ func (x *RunJupyterSpec) String() string { func (*RunJupyterSpec) ProtoMessage() {} func (x *RunJupyterSpec) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[156] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12850,7 +13102,7 @@ func (x *RunJupyterSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use RunJupyterSpec.ProtoReflect.Descriptor instead. func (*RunJupyterSpec) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{156} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{159} } func (x *RunJupyterSpec) GetEnabled() bool { @@ -12883,7 +13135,7 @@ type StartRunRequest struct { func (x *StartRunRequest) Reset() { *x = StartRunRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[157] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12895,7 +13147,7 @@ func (x *StartRunRequest) String() string { func (*StartRunRequest) ProtoMessage() {} func (x *StartRunRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[157] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12908,7 +13160,7 @@ func (x *StartRunRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StartRunRequest.ProtoReflect.Descriptor instead. func (*StartRunRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{157} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{160} } func (x *StartRunRequest) GetRun() *RunAgentRequest { @@ -12929,7 +13181,7 @@ type StartRunResponse struct { func (x *StartRunResponse) Reset() { *x = StartRunResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[158] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12941,7 +13193,7 @@ func (x *StartRunResponse) String() string { func (*StartRunResponse) ProtoMessage() {} func (x *StartRunResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[158] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12954,7 +13206,7 @@ func (x *StartRunResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StartRunResponse.ProtoReflect.Descriptor instead. func (*StartRunResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{158} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{161} } func (x *StartRunResponse) GetRun() *RunSummary { @@ -12994,7 +13246,7 @@ type SkillSpec struct { func (x *SkillSpec) Reset() { *x = SkillSpec{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[159] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13006,7 +13258,7 @@ func (x *SkillSpec) String() string { func (*SkillSpec) ProtoMessage() {} func (x *SkillSpec) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[159] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13019,7 +13271,7 @@ func (x *SkillSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillSpec.ProtoReflect.Descriptor instead. func (*SkillSpec) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{159} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{162} } func (x *SkillSpec) GetName() string { @@ -13088,7 +13340,7 @@ type ResolveResourceIDRequest struct { func (x *ResolveResourceIDRequest) Reset() { *x = ResolveResourceIDRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[160] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13100,7 +13352,7 @@ func (x *ResolveResourceIDRequest) String() string { func (*ResolveResourceIDRequest) ProtoMessage() {} func (x *ResolveResourceIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[160] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13113,7 +13365,7 @@ func (x *ResolveResourceIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveResourceIDRequest.ProtoReflect.Descriptor instead. func (*ResolveResourceIDRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{160} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{163} } func (x *ResolveResourceIDRequest) GetId() string { @@ -13140,7 +13392,7 @@ type ResolveResourceIDResponse struct { func (x *ResolveResourceIDResponse) Reset() { *x = ResolveResourceIDResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[161] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13152,7 +13404,7 @@ func (x *ResolveResourceIDResponse) String() string { func (*ResolveResourceIDResponse) ProtoMessage() {} func (x *ResolveResourceIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[161] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13165,7 +13417,7 @@ func (x *ResolveResourceIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveResourceIDResponse.ProtoReflect.Descriptor instead. func (*ResolveResourceIDResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{161} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{164} } func (x *ResolveResourceIDResponse) GetTargets() []*ResourceTarget { @@ -13196,7 +13448,7 @@ type ResourceTarget struct { func (x *ResourceTarget) Reset() { *x = ResourceTarget{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[162] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13208,7 +13460,7 @@ func (x *ResourceTarget) String() string { func (*ResourceTarget) ProtoMessage() {} func (x *ResourceTarget) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[162] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13221,7 +13473,7 @@ func (x *ResourceTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceTarget.ProtoReflect.Descriptor instead. func (*ResourceTarget) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{162} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{165} } func (x *ResourceTarget) GetKind() ResourceKind { @@ -13274,7 +13526,7 @@ type GetDashboardOverviewRequest struct { func (x *GetDashboardOverviewRequest) Reset() { *x = GetDashboardOverviewRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[163] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13286,7 +13538,7 @@ func (x *GetDashboardOverviewRequest) String() string { func (*GetDashboardOverviewRequest) ProtoMessage() {} func (x *GetDashboardOverviewRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[163] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13299,7 +13551,7 @@ func (x *GetDashboardOverviewRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDashboardOverviewRequest.ProtoReflect.Descriptor instead. func (*GetDashboardOverviewRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{163} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{166} } type WatchDashboardOverviewRequest struct { @@ -13310,7 +13562,7 @@ type WatchDashboardOverviewRequest struct { func (x *WatchDashboardOverviewRequest) Reset() { *x = WatchDashboardOverviewRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[164] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13322,7 +13574,7 @@ func (x *WatchDashboardOverviewRequest) String() string { func (*WatchDashboardOverviewRequest) ProtoMessage() {} func (x *WatchDashboardOverviewRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[164] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13335,7 +13587,7 @@ func (x *WatchDashboardOverviewRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchDashboardOverviewRequest.ProtoReflect.Descriptor instead. func (*WatchDashboardOverviewRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{164} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{167} } type RunOverview struct { @@ -13349,7 +13601,7 @@ type RunOverview struct { func (x *RunOverview) Reset() { *x = RunOverview{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[165] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13361,7 +13613,7 @@ func (x *RunOverview) String() string { func (*RunOverview) ProtoMessage() {} func (x *RunOverview) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[165] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13374,7 +13626,7 @@ func (x *RunOverview) ProtoReflect() protoreflect.Message { // Deprecated: Use RunOverview.ProtoReflect.Descriptor instead. func (*RunOverview) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{165} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{168} } func (x *RunOverview) GetRunningCount() uint32 { @@ -13408,7 +13660,7 @@ type DashboardOverview struct { func (x *DashboardOverview) Reset() { *x = DashboardOverview{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[166] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13420,7 +13672,7 @@ func (x *DashboardOverview) String() string { func (*DashboardOverview) ProtoMessage() {} func (x *DashboardOverview) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[166] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13433,7 +13685,7 @@ func (x *DashboardOverview) ProtoReflect() protoreflect.Message { // Deprecated: Use DashboardOverview.ProtoReflect.Descriptor instead. func (*DashboardOverview) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{166} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{169} } func (x *DashboardOverview) GetRuns() *RunOverview { @@ -13459,7 +13711,7 @@ type GetDashboardOverviewResponse struct { func (x *GetDashboardOverviewResponse) Reset() { *x = GetDashboardOverviewResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[167] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13471,7 +13723,7 @@ func (x *GetDashboardOverviewResponse) String() string { func (*GetDashboardOverviewResponse) ProtoMessage() {} func (x *GetDashboardOverviewResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[167] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13484,7 +13736,7 @@ func (x *GetDashboardOverviewResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDashboardOverviewResponse.ProtoReflect.Descriptor instead. func (*GetDashboardOverviewResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{167} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{170} } func (x *GetDashboardOverviewResponse) GetOverview() *DashboardOverview { @@ -13504,7 +13756,7 @@ type WatchDashboardOverviewResponse struct { func (x *WatchDashboardOverviewResponse) Reset() { *x = WatchDashboardOverviewResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[168] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13516,7 +13768,7 @@ func (x *WatchDashboardOverviewResponse) String() string { func (*WatchDashboardOverviewResponse) ProtoMessage() {} func (x *WatchDashboardOverviewResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[168] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13529,7 +13781,7 @@ func (x *WatchDashboardOverviewResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchDashboardOverviewResponse.ProtoReflect.Descriptor instead. func (*WatchDashboardOverviewResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{168} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{171} } func (x *WatchDashboardOverviewResponse) GetOverview() *DashboardOverview { @@ -13554,7 +13806,7 @@ type GetGlobalEnvRequest struct { func (x *GetGlobalEnvRequest) Reset() { *x = GetGlobalEnvRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[169] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13566,7 +13818,7 @@ func (x *GetGlobalEnvRequest) String() string { func (*GetGlobalEnvRequest) ProtoMessage() {} func (x *GetGlobalEnvRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[169] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13579,7 +13831,7 @@ func (x *GetGlobalEnvRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGlobalEnvRequest.ProtoReflect.Descriptor instead. func (*GetGlobalEnvRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{169} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{172} } type GetGlobalEnvResponse struct { @@ -13591,7 +13843,7 @@ type GetGlobalEnvResponse struct { func (x *GetGlobalEnvResponse) Reset() { *x = GetGlobalEnvResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[170] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13603,7 +13855,7 @@ func (x *GetGlobalEnvResponse) String() string { func (*GetGlobalEnvResponse) ProtoMessage() {} func (x *GetGlobalEnvResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[170] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13616,7 +13868,7 @@ func (x *GetGlobalEnvResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGlobalEnvResponse.ProtoReflect.Descriptor instead. func (*GetGlobalEnvResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{170} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{173} } func (x *GetGlobalEnvResponse) GetEnv() []*EnvVarSpec { @@ -13635,7 +13887,7 @@ type UpdateGlobalEnvRequest struct { func (x *UpdateGlobalEnvRequest) Reset() { *x = UpdateGlobalEnvRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[171] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13647,7 +13899,7 @@ func (x *UpdateGlobalEnvRequest) String() string { func (*UpdateGlobalEnvRequest) ProtoMessage() {} func (x *UpdateGlobalEnvRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[171] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13660,7 +13912,7 @@ func (x *UpdateGlobalEnvRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateGlobalEnvRequest.ProtoReflect.Descriptor instead. func (*UpdateGlobalEnvRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{171} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{174} } func (x *UpdateGlobalEnvRequest) GetEnv() []*EnvVarUpdateSpec { @@ -13679,7 +13931,7 @@ type UpdateGlobalEnvResponse struct { func (x *UpdateGlobalEnvResponse) Reset() { *x = UpdateGlobalEnvResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[172] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13691,7 +13943,7 @@ func (x *UpdateGlobalEnvResponse) String() string { func (*UpdateGlobalEnvResponse) ProtoMessage() {} func (x *UpdateGlobalEnvResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[172] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13704,7 +13956,7 @@ func (x *UpdateGlobalEnvResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateGlobalEnvResponse.ProtoReflect.Descriptor instead. func (*UpdateGlobalEnvResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{172} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{175} } func (x *UpdateGlobalEnvResponse) GetEnv() []*EnvVarSpec { @@ -13722,7 +13974,7 @@ type GetCapabilityGatewayConfigRequest struct { func (x *GetCapabilityGatewayConfigRequest) Reset() { *x = GetCapabilityGatewayConfigRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[173] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13734,7 +13986,7 @@ func (x *GetCapabilityGatewayConfigRequest) String() string { func (*GetCapabilityGatewayConfigRequest) ProtoMessage() {} func (x *GetCapabilityGatewayConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[173] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13747,7 +13999,7 @@ func (x *GetCapabilityGatewayConfigRequest) ProtoReflect() protoreflect.Message // Deprecated: Use GetCapabilityGatewayConfigRequest.ProtoReflect.Descriptor instead. func (*GetCapabilityGatewayConfigRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{173} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{176} } type CapabilityGatewayConfig struct { @@ -13760,7 +14012,7 @@ type CapabilityGatewayConfig struct { func (x *CapabilityGatewayConfig) Reset() { *x = CapabilityGatewayConfig{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[174] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13772,7 +14024,7 @@ func (x *CapabilityGatewayConfig) String() string { func (*CapabilityGatewayConfig) ProtoMessage() {} func (x *CapabilityGatewayConfig) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[174] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13785,7 +14037,7 @@ func (x *CapabilityGatewayConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use CapabilityGatewayConfig.ProtoReflect.Descriptor instead. func (*CapabilityGatewayConfig) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{174} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{177} } func (x *CapabilityGatewayConfig) GetAddr() string { @@ -13811,7 +14063,7 @@ type GetCapabilityGatewayConfigResponse struct { func (x *GetCapabilityGatewayConfigResponse) Reset() { *x = GetCapabilityGatewayConfigResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[175] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13823,7 +14075,7 @@ func (x *GetCapabilityGatewayConfigResponse) String() string { func (*GetCapabilityGatewayConfigResponse) ProtoMessage() {} func (x *GetCapabilityGatewayConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[175] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13836,7 +14088,7 @@ func (x *GetCapabilityGatewayConfigResponse) ProtoReflect() protoreflect.Message // Deprecated: Use GetCapabilityGatewayConfigResponse.ProtoReflect.Descriptor instead. func (*GetCapabilityGatewayConfigResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{175} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{178} } func (x *GetCapabilityGatewayConfigResponse) GetConfig() *CapabilityGatewayConfig { @@ -13856,7 +14108,7 @@ type UpdateCapabilityGatewayConfigRequest struct { func (x *UpdateCapabilityGatewayConfigRequest) Reset() { *x = UpdateCapabilityGatewayConfigRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[176] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13868,7 +14120,7 @@ func (x *UpdateCapabilityGatewayConfigRequest) String() string { func (*UpdateCapabilityGatewayConfigRequest) ProtoMessage() {} func (x *UpdateCapabilityGatewayConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[176] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13881,7 +14133,7 @@ func (x *UpdateCapabilityGatewayConfigRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use UpdateCapabilityGatewayConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateCapabilityGatewayConfigRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{176} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{179} } func (x *UpdateCapabilityGatewayConfigRequest) GetAddr() string { @@ -13907,7 +14159,7 @@ type UpdateCapabilityGatewayConfigResponse struct { func (x *UpdateCapabilityGatewayConfigResponse) Reset() { *x = UpdateCapabilityGatewayConfigResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[177] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13919,7 +14171,7 @@ func (x *UpdateCapabilityGatewayConfigResponse) String() string { func (*UpdateCapabilityGatewayConfigResponse) ProtoMessage() {} func (x *UpdateCapabilityGatewayConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[177] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13932,7 +14184,7 @@ func (x *UpdateCapabilityGatewayConfigResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use UpdateCapabilityGatewayConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateCapabilityGatewayConfigResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{177} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{180} } func (x *UpdateCapabilityGatewayConfigResponse) GetConfig() *CapabilityGatewayConfig { @@ -13957,7 +14209,7 @@ type WorkspacePreset struct { func (x *WorkspacePreset) Reset() { *x = WorkspacePreset{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[178] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13969,7 +14221,7 @@ func (x *WorkspacePreset) String() string { func (*WorkspacePreset) ProtoMessage() {} func (x *WorkspacePreset) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[178] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13982,7 +14234,7 @@ func (x *WorkspacePreset) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspacePreset.ProtoReflect.Descriptor instead. func (*WorkspacePreset) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{178} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{181} } func (x *WorkspacePreset) GetId() string { @@ -14042,7 +14294,7 @@ type ListWorkspacePresetsRequest struct { func (x *ListWorkspacePresetsRequest) Reset() { *x = ListWorkspacePresetsRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[179] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14054,7 +14306,7 @@ func (x *ListWorkspacePresetsRequest) String() string { func (*ListWorkspacePresetsRequest) ProtoMessage() {} func (x *ListWorkspacePresetsRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[179] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14067,7 +14319,7 @@ func (x *ListWorkspacePresetsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacePresetsRequest.ProtoReflect.Descriptor instead. func (*ListWorkspacePresetsRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{179} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{182} } type ListWorkspacePresetsResponse struct { @@ -14079,7 +14331,7 @@ type ListWorkspacePresetsResponse struct { func (x *ListWorkspacePresetsResponse) Reset() { *x = ListWorkspacePresetsResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[180] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14091,7 +14343,7 @@ func (x *ListWorkspacePresetsResponse) String() string { func (*ListWorkspacePresetsResponse) ProtoMessage() {} func (x *ListWorkspacePresetsResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[180] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14104,7 +14356,7 @@ func (x *ListWorkspacePresetsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacePresetsResponse.ProtoReflect.Descriptor instead. func (*ListWorkspacePresetsResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{180} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{183} } func (x *ListWorkspacePresetsResponse) GetPresets() []*WorkspacePreset { @@ -14126,7 +14378,7 @@ type CreateWorkspacePresetRequest struct { func (x *CreateWorkspacePresetRequest) Reset() { *x = CreateWorkspacePresetRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[181] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14138,7 +14390,7 @@ func (x *CreateWorkspacePresetRequest) String() string { func (*CreateWorkspacePresetRequest) ProtoMessage() {} func (x *CreateWorkspacePresetRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[181] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14151,7 +14403,7 @@ func (x *CreateWorkspacePresetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspacePresetRequest.ProtoReflect.Descriptor instead. func (*CreateWorkspacePresetRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{181} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{184} } func (x *CreateWorkspacePresetRequest) GetName() string { @@ -14195,7 +14447,7 @@ type UpdateWorkspacePresetRequest struct { func (x *UpdateWorkspacePresetRequest) Reset() { *x = UpdateWorkspacePresetRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[182] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14207,7 +14459,7 @@ func (x *UpdateWorkspacePresetRequest) String() string { func (*UpdateWorkspacePresetRequest) ProtoMessage() {} func (x *UpdateWorkspacePresetRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[182] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14220,7 +14472,7 @@ func (x *UpdateWorkspacePresetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateWorkspacePresetRequest.ProtoReflect.Descriptor instead. func (*UpdateWorkspacePresetRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{182} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{185} } func (x *UpdateWorkspacePresetRequest) GetPresetId() string { @@ -14267,7 +14519,7 @@ type DeleteWorkspacePresetRequest struct { func (x *DeleteWorkspacePresetRequest) Reset() { *x = DeleteWorkspacePresetRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[183] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14279,7 +14531,7 @@ func (x *DeleteWorkspacePresetRequest) String() string { func (*DeleteWorkspacePresetRequest) ProtoMessage() {} func (x *DeleteWorkspacePresetRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[183] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14292,7 +14544,7 @@ func (x *DeleteWorkspacePresetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspacePresetRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkspacePresetRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{183} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{186} } func (x *DeleteWorkspacePresetRequest) GetPresetId() string { @@ -14310,7 +14562,7 @@ type DeleteWorkspacePresetResponse struct { func (x *DeleteWorkspacePresetResponse) Reset() { *x = DeleteWorkspacePresetResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[184] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14322,7 +14574,7 @@ func (x *DeleteWorkspacePresetResponse) String() string { func (*DeleteWorkspacePresetResponse) ProtoMessage() {} func (x *DeleteWorkspacePresetResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[184] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[187] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14335,7 +14587,7 @@ func (x *DeleteWorkspacePresetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspacePresetResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkspacePresetResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{184} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{187} } type WorkspacePresetResponse struct { @@ -14347,7 +14599,7 @@ type WorkspacePresetResponse struct { func (x *WorkspacePresetResponse) Reset() { *x = WorkspacePresetResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[185] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[188] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14359,7 +14611,7 @@ func (x *WorkspacePresetResponse) String() string { func (*WorkspacePresetResponse) ProtoMessage() {} func (x *WorkspacePresetResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[185] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[188] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14372,7 +14624,7 @@ func (x *WorkspacePresetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspacePresetResponse.ProtoReflect.Descriptor instead. func (*WorkspacePresetResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{185} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{188} } func (x *WorkspacePresetResponse) GetPreset() *WorkspacePreset { @@ -14390,7 +14642,7 @@ type GetCapabilityStatusRequest struct { func (x *GetCapabilityStatusRequest) Reset() { *x = GetCapabilityStatusRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[186] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14402,7 +14654,7 @@ func (x *GetCapabilityStatusRequest) String() string { func (*GetCapabilityStatusRequest) ProtoMessage() {} func (x *GetCapabilityStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[186] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[189] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14415,7 +14667,7 @@ func (x *GetCapabilityStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCapabilityStatusRequest.ProtoReflect.Descriptor instead. func (*GetCapabilityStatusRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{186} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{189} } type CapabilityStatusResponse struct { @@ -14434,7 +14686,7 @@ type CapabilityStatusResponse struct { func (x *CapabilityStatusResponse) Reset() { *x = CapabilityStatusResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[187] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14446,7 +14698,7 @@ func (x *CapabilityStatusResponse) String() string { func (*CapabilityStatusResponse) ProtoMessage() {} func (x *CapabilityStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[187] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[190] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14459,7 +14711,7 @@ func (x *CapabilityStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CapabilityStatusResponse.ProtoReflect.Descriptor instead. func (*CapabilityStatusResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{187} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{190} } func (x *CapabilityStatusResponse) GetConfigured() bool { @@ -14526,7 +14778,7 @@ type ListCapabilitySetsRequest struct { func (x *ListCapabilitySetsRequest) Reset() { *x = ListCapabilitySetsRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[188] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14538,7 +14790,7 @@ func (x *ListCapabilitySetsRequest) String() string { func (*ListCapabilitySetsRequest) ProtoMessage() {} func (x *ListCapabilitySetsRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[188] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[191] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14551,7 +14803,7 @@ func (x *ListCapabilitySetsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCapabilitySetsRequest.ProtoReflect.Descriptor instead. func (*ListCapabilitySetsRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{188} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{191} } type CapabilitySet struct { @@ -14566,7 +14818,7 @@ type CapabilitySet struct { func (x *CapabilitySet) Reset() { *x = CapabilitySet{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[189] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14578,7 +14830,7 @@ func (x *CapabilitySet) String() string { func (*CapabilitySet) ProtoMessage() {} func (x *CapabilitySet) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[189] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[192] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14591,7 +14843,7 @@ func (x *CapabilitySet) ProtoReflect() protoreflect.Message { // Deprecated: Use CapabilitySet.ProtoReflect.Descriptor instead. func (*CapabilitySet) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{189} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{192} } func (x *CapabilitySet) GetId() string { @@ -14631,7 +14883,7 @@ type ListCapabilitySetsResponse struct { func (x *ListCapabilitySetsResponse) Reset() { *x = ListCapabilitySetsResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[190] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14643,7 +14895,7 @@ func (x *ListCapabilitySetsResponse) String() string { func (*ListCapabilitySetsResponse) ProtoMessage() {} func (x *ListCapabilitySetsResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[190] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[193] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14656,7 +14908,7 @@ func (x *ListCapabilitySetsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCapabilitySetsResponse.ProtoReflect.Descriptor instead. func (*ListCapabilitySetsResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{190} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{193} } func (x *ListCapabilitySetsResponse) GetCapsets() []*CapabilitySet { @@ -14675,7 +14927,7 @@ type GetCapabilityCatalogRequest struct { func (x *GetCapabilityCatalogRequest) Reset() { *x = GetCapabilityCatalogRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[191] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14687,7 +14939,7 @@ func (x *GetCapabilityCatalogRequest) String() string { func (*GetCapabilityCatalogRequest) ProtoMessage() {} func (x *GetCapabilityCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[191] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14700,7 +14952,7 @@ func (x *GetCapabilityCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCapabilityCatalogRequest.ProtoReflect.Descriptor instead. func (*GetCapabilityCatalogRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{191} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{194} } func (x *GetCapabilityCatalogRequest) GetCapsetId() string { @@ -14726,7 +14978,7 @@ type CapabilityEndpoint struct { func (x *CapabilityEndpoint) Reset() { *x = CapabilityEndpoint{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[192] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14738,7 +14990,7 @@ func (x *CapabilityEndpoint) String() string { func (*CapabilityEndpoint) ProtoMessage() {} func (x *CapabilityEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[192] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14751,7 +15003,7 @@ func (x *CapabilityEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use CapabilityEndpoint.ProtoReflect.Descriptor instead. func (*CapabilityEndpoint) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{192} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{195} } func (x *CapabilityEndpoint) GetProtocol() string { @@ -14826,7 +15078,7 @@ type CapabilityMethod struct { func (x *CapabilityMethod) Reset() { *x = CapabilityMethod{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[193] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14838,7 +15090,7 @@ func (x *CapabilityMethod) String() string { func (*CapabilityMethod) ProtoMessage() {} func (x *CapabilityMethod) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[193] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14851,7 +15103,7 @@ func (x *CapabilityMethod) ProtoReflect() protoreflect.Message { // Deprecated: Use CapabilityMethod.ProtoReflect.Descriptor instead. func (*CapabilityMethod) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{193} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{196} } func (x *CapabilityMethod) GetServiceId() string { @@ -14922,7 +15174,7 @@ type GetCapabilityCatalogResponse struct { func (x *GetCapabilityCatalogResponse) Reset() { *x = GetCapabilityCatalogResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[194] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14934,7 +15186,7 @@ func (x *GetCapabilityCatalogResponse) String() string { func (*GetCapabilityCatalogResponse) ProtoMessage() {} func (x *GetCapabilityCatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[194] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14947,7 +15199,7 @@ func (x *GetCapabilityCatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCapabilityCatalogResponse.ProtoReflect.Descriptor instead. func (*GetCapabilityCatalogResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{194} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{197} } func (x *GetCapabilityCatalogResponse) GetCapsetId() string { @@ -14987,7 +15239,7 @@ type ListSandboxHistoryRequest struct { func (x *ListSandboxHistoryRequest) Reset() { *x = ListSandboxHistoryRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[195] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14999,7 +15251,7 @@ func (x *ListSandboxHistoryRequest) String() string { func (*ListSandboxHistoryRequest) ProtoMessage() {} func (x *ListSandboxHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[195] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15012,7 +15264,7 @@ func (x *ListSandboxHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxHistoryRequest.ProtoReflect.Descriptor instead. func (*ListSandboxHistoryRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{195} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{198} } func (x *ListSandboxHistoryRequest) GetSandboxId() string { @@ -15043,7 +15295,7 @@ type SandboxHistoryCell struct { func (x *SandboxHistoryCell) Reset() { *x = SandboxHistoryCell{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[196] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15055,7 +15307,7 @@ func (x *SandboxHistoryCell) String() string { func (*SandboxHistoryCell) ProtoMessage() {} func (x *SandboxHistoryCell) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[196] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15068,7 +15320,7 @@ func (x *SandboxHistoryCell) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxHistoryCell.ProtoReflect.Descriptor instead. func (*SandboxHistoryCell) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{196} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{199} } func (x *SandboxHistoryCell) GetId() string { @@ -15175,7 +15427,7 @@ type SandboxHistoryEvent struct { func (x *SandboxHistoryEvent) Reset() { *x = SandboxHistoryEvent{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[197] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15187,7 +15439,7 @@ func (x *SandboxHistoryEvent) String() string { func (*SandboxHistoryEvent) ProtoMessage() {} func (x *SandboxHistoryEvent) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[197] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15200,7 +15452,7 @@ func (x *SandboxHistoryEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxHistoryEvent.ProtoReflect.Descriptor instead. func (*SandboxHistoryEvent) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{197} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{200} } func (x *SandboxHistoryEvent) GetId() string { @@ -15249,7 +15501,7 @@ type ListSandboxHistoryResponse struct { func (x *ListSandboxHistoryResponse) Reset() { *x = ListSandboxHistoryResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[198] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15261,7 +15513,7 @@ func (x *ListSandboxHistoryResponse) String() string { func (*ListSandboxHistoryResponse) ProtoMessage() {} func (x *ListSandboxHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[198] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15274,7 +15526,7 @@ func (x *ListSandboxHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxHistoryResponse.ProtoReflect.Descriptor instead. func (*ListSandboxHistoryResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{198} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{201} } func (x *ListSandboxHistoryResponse) GetCells() []*SandboxHistoryCell { @@ -15307,7 +15559,7 @@ type WatchSandboxRequest struct { func (x *WatchSandboxRequest) Reset() { *x = WatchSandboxRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[199] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15319,7 +15571,7 @@ func (x *WatchSandboxRequest) String() string { func (*WatchSandboxRequest) ProtoMessage() {} func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[199] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15332,7 +15584,7 @@ func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSandboxRequest.ProtoReflect.Descriptor instead. func (*WatchSandboxRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{199} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{202} } func (x *WatchSandboxRequest) GetSandboxId() string { @@ -15357,7 +15609,7 @@ type WatchSandboxResponse struct { func (x *WatchSandboxResponse) Reset() { *x = WatchSandboxResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[200] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15369,7 +15621,7 @@ func (x *WatchSandboxResponse) String() string { func (*WatchSandboxResponse) ProtoMessage() {} func (x *WatchSandboxResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[200] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15382,7 +15634,7 @@ func (x *WatchSandboxResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSandboxResponse.ProtoReflect.Descriptor instead. func (*WatchSandboxResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{200} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{203} } func (x *WatchSandboxResponse) GetEventType() SandboxWatchEventType { @@ -15445,7 +15697,7 @@ type GenerateLLMRequest struct { func (x *GenerateLLMRequest) Reset() { *x = GenerateLLMRequest{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[201] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15457,7 +15709,7 @@ func (x *GenerateLLMRequest) String() string { func (*GenerateLLMRequest) ProtoMessage() {} func (x *GenerateLLMRequest) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[201] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15470,7 +15722,7 @@ func (x *GenerateLLMRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateLLMRequest.ProtoReflect.Descriptor instead. func (*GenerateLLMRequest) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{201} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{204} } func (x *GenerateLLMRequest) GetPrompt() string { @@ -15507,7 +15759,7 @@ type GenerateLLMResponse struct { func (x *GenerateLLMResponse) Reset() { *x = GenerateLLMResponse{} - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[202] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15519,7 +15771,7 @@ func (x *GenerateLLMResponse) String() string { func (*GenerateLLMResponse) ProtoMessage() {} func (x *GenerateLLMResponse) ProtoReflect() protoreflect.Message { - mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[202] + mi := &file_agentcompose_v2_agentcompose_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15532,7 +15784,7 @@ func (x *GenerateLLMResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateLLMResponse.ProtoReflect.Descriptor instead. func (*GenerateLLMResponse) Descriptor() ([]byte, []int) { - return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{202} + return file_agentcompose_v2_agentcompose_proto_rawDescGZIP(), []int{205} } func (x *GenerateLLMResponse) GetText() string { @@ -15802,21 +16054,21 @@ const file_agentcompose_v2_agentcompose_proto_rawDesc = "" + "\vresource_id\x18\x03 \x01(\tR\n" + "resourceId\x12\x12\n" + "\x04name\x18\x04 \x01(\tR\x04name\x12\x18\n" + - "\amessage\x18\x05 \x01(\tR\amessage\"\x9d\x03\n" + + "\amessage\x18\x05 \x01(\tR\amessage\"\xa4\x03\n" + "\vProjectSpec\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x129\n" + "\tvariables\x18\x02 \x03(\v2\x1b.agentcompose.v2.EnvVarSpecR\tvariables\x122\n" + - "\x06agents\x18\x04 \x03(\v2\x1a.agentcompose.v2.AgentSpecR\x06agents\x126\n" + - "\anetwork\x18\x05 \x01(\v2\x1c.agentcompose.v2.NetworkSpecR\anetwork\x12<\n" + + "\x06agents\x18\x04 \x03(\v2\x1a.agentcompose.v2.AgentSpecR\x06agents\x12<\n" + "\avolumes\x18\x06 \x03(\v2\".agentcompose.v2.ProjectVolumeSpecR\avolumes\x12C\n" + "\n" + "workspaces\x18\a \x03(\v2#.agentcompose.v2.NamedWorkspaceSpecR\n" + "workspaces\x12?\n" + "\vmcp_servers\x18\b \x03(\v2\x1e.agentcompose.v2.MCPServerSpecR\n" + - "mcpServersJ\x04\b\x03\x10\x04R\tworkspace\"f\n" + + "mcpServers\x12=\n" + + "\bnetworks\x18\t \x03(\v2!.agentcompose.v2.NamedNetworkSpecR\bnetworksJ\x04\b\x03\x10\x04R\tworkspace\"f\n" + "\x12NamedWorkspaceSpec\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12<\n" + - "\tworkspace\x18\x02 \x01(\v2\x1e.agentcompose.v2.WorkspaceSpecR\tworkspace\"\xdc\x05\n" + + "\tworkspace\x18\x02 \x01(\v2\x1e.agentcompose.v2.WorkspaceSpecR\tworkspace\"\xa6\x06\n" + "\tAgentSpec\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1a\n" + "\bprovider\x18\x02 \x01(\tR\bprovider\x12\x14\n" + @@ -15836,7 +16088,10 @@ const file_agentcompose_v2_agentcompose_proto_rawDesc = "" + "\vmcp_servers\x18\x0e \x03(\v2\x1e.agentcompose.v2.MCPServerSpecR\n" + "mcpServers\x122\n" + "\x06skills\x18\x0f \x03(\v2\x1a.agentcompose.v2.SkillSpecR\x06skills\x124\n" + - "\x06status\x18\x10 \x01(\x0e2\x1c.agentcompose.v2.AgentStatusR\x06status\"\xfb\x01\n" + + "\x06status\x18\x10 \x01(\x0e2\x1c.agentcompose.v2.AgentStatusR\x06status\x12\x1a\n" + + "\bnetworks\x18\x11 \x03(\tR\bnetworks\x12\x16\n" + + "\x06expose\x18\x12 \x03(\tR\x06expose\x12\x14\n" + + "\x05ports\x18\x13 \x03(\tR\x05ports\"\xfb\x01\n" + "\rMCPServerSpec\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + "\x04type\x18\x02 \x01(\tR\x04type\x12\x1c\n" + @@ -15893,9 +16148,10 @@ const file_agentcompose_v2_agentcompose_proto_rawDesc = "" + "\x03url\x18\x02 \x01(\tR\x03url\x12\x16\n" + "\x06branch\x18\x03 \x01(\tR\x06branch\x12\x12\n" + "\x04path\x18\x04 \x01(\tR\x04path\x12\x12\n" + - "\x04name\x18\x05 \x01(\tR\x04name\"!\n" + - "\vNetworkSpec\x12\x12\n" + - "\x04mode\x18\x01 \x01(\tR\x04mode\"\xa2\x01\n" + + "\x04name\x18\x05 \x01(\tR\x04name\">\n" + + "\x10NamedNetworkSpec\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + + "\x06driver\x18\x02 \x01(\tR\x06driver\"\xa2\x01\n" + "\rSchedulerSpec\x12\x18\n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x128\n" + "\btriggers\x18\x02 \x03(\v2\x1c.agentcompose.v2.TriggerSpecR\btriggers\x12\x16\n" + @@ -16130,7 +16386,7 @@ const file_agentcompose_v2_agentcompose_proto_rawDesc = "" + "\x05stats\x18\x01 \x01(\v2\x1d.agentcompose.v2.SandboxStatsR\x05stats\"2\n" + "\x11GetSandboxRequest\x12\x1d\n" + "\n" + - "sandbox_id\x18\x01 \x01(\tR\tsandboxId\"\xb9\x04\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\"\x84\x05\n" + "\aSandbox\x12\x1d\n" + "\n" + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x16\n" + @@ -16156,7 +16412,25 @@ const file_agentcompose_v2_agentcompose_proto_rawDesc = "" + "cell_count\x18\x0e \x01(\rR\tcellCount\x12\x1f\n" + "\vevent_count\x18\x0f \x01(\rR\n" + "eventCount\x12!\n" + - "\fnotebook_url\x18\x10 \x01(\tR\vnotebookUrl\"6\n" + + "\fnotebook_url\x18\x10 \x01(\tR\vnotebookUrl\x12I\n" + + "\rnetwork_state\x18\x11 \x01(\v2$.agentcompose.v2.SandboxNetworkStateR\fnetworkState\"\x81\x02\n" + + "\x13SandboxNetworkState\x12\x12\n" + + "\x04mode\x18\x01 \x01(\tR\x04mode\x12K\n" + + "\vattachments\x18\x02 \x03(\v2).agentcompose.v2.SandboxNetworkAttachmentR\vattachments\x12H\n" + + "\rport_bindings\x18\x03 \x03(\v2#.agentcompose.v2.SandboxPortBindingR\fportBindings\x12?\n" + + "\rreconciled_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\freconciledAt\"\xd6\x01\n" + + "\x18SandboxNetworkAttachment\x12!\n" + + "\flogical_name\x18\x01 \x01(\tR\vlogicalName\x12!\n" + + "\fruntime_name\x18\x02 \x01(\tR\vruntimeName\x12\x1d\n" + + "\n" + + "network_id\x18\x03 \x01(\tR\tnetworkId\x12\x18\n" + + "\aaliases\x18\x04 \x03(\tR\aaliases\x12!\n" + + "\fipv4_address\x18\x05 \x01(\tR\vipv4Address\x12\x18\n" + + "\aprimary\x18\x06 \x01(\bR\aprimary\"q\n" + + "\x12SandboxPortBinding\x12%\n" + + "\x0econtainer_port\x18\x01 \x01(\tR\rcontainerPort\x12\x17\n" + + "\ahost_ip\x18\x02 \x01(\tR\x06hostIp\x12\x1b\n" + + "\thost_port\x18\x03 \x01(\tR\bhostPort\"6\n" + "\n" + "SandboxTag\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + @@ -17081,7 +17355,7 @@ func file_agentcompose_v2_agentcompose_proto_rawDescGZIP() []byte { } var file_agentcompose_v2_agentcompose_proto_enumTypes = make([]protoimpl.EnumInfo, 24) -var file_agentcompose_v2_agentcompose_proto_msgTypes = make([]protoimpl.MessageInfo, 215) +var file_agentcompose_v2_agentcompose_proto_msgTypes = make([]protoimpl.MessageInfo, 218) var file_agentcompose_v2_agentcompose_proto_goTypes = []any{ (ProjectValidationSeverity)(0), // 0: agentcompose.v2.ProjectValidationSeverity (ProjectChangeAction)(0), // 1: agentcompose.v2.ProjectChangeAction @@ -17153,7 +17427,7 @@ var file_agentcompose_v2_agentcompose_proto_goTypes = []any{ (*EnvVarSpec)(nil), // 67: agentcompose.v2.EnvVarSpec (*EnvVarUpdateSpec)(nil), // 68: agentcompose.v2.EnvVarUpdateSpec (*WorkspaceSpec)(nil), // 69: agentcompose.v2.WorkspaceSpec - (*NetworkSpec)(nil), // 70: agentcompose.v2.NetworkSpec + (*NamedNetworkSpec)(nil), // 70: agentcompose.v2.NamedNetworkSpec (*SchedulerSpec)(nil), // 71: agentcompose.v2.SchedulerSpec (*TriggerSpec)(nil), // 72: agentcompose.v2.TriggerSpec (*EventTriggerSpec)(nil), // 73: agentcompose.v2.EventTriggerSpec @@ -17190,139 +17464,142 @@ var file_agentcompose_v2_agentcompose_proto_goTypes = []any{ (*GetSandboxStatsResponse)(nil), // 104: agentcompose.v2.GetSandboxStatsResponse (*GetSandboxRequest)(nil), // 105: agentcompose.v2.GetSandboxRequest (*Sandbox)(nil), // 106: agentcompose.v2.Sandbox - (*SandboxTag)(nil), // 107: agentcompose.v2.SandboxTag - (*ListSandboxesRequest)(nil), // 108: agentcompose.v2.ListSandboxesRequest - (*ListSandboxesResponse)(nil), // 109: agentcompose.v2.ListSandboxesResponse - (*GetSandboxResponse)(nil), // 110: agentcompose.v2.GetSandboxResponse - (*StopSandboxRequest)(nil), // 111: agentcompose.v2.StopSandboxRequest - (*StopSandboxResponse)(nil), // 112: agentcompose.v2.StopSandboxResponse - (*ResumeSandboxRequest)(nil), // 113: agentcompose.v2.ResumeSandboxRequest - (*ResumeSandboxResponse)(nil), // 114: agentcompose.v2.ResumeSandboxResponse - (*MetricValue)(nil), // 115: agentcompose.v2.MetricValue - (*SandboxStats)(nil), // 116: agentcompose.v2.SandboxStats - (*RunSummary)(nil), // 117: agentcompose.v2.RunSummary - (*RunDetail)(nil), // 118: agentcompose.v2.RunDetail - (*ExecRequest)(nil), // 119: agentcompose.v2.ExecRequest - (*ExecSandboxSelector)(nil), // 120: agentcompose.v2.ExecSandboxSelector - (*ExecCommand)(nil), // 121: agentcompose.v2.ExecCommand - (*ExecResponse)(nil), // 122: agentcompose.v2.ExecResponse - (*ExecStreamResponse)(nil), // 123: agentcompose.v2.ExecStreamResponse - (*ExecAttachRequest)(nil), // 124: agentcompose.v2.ExecAttachRequest - (*ExecAttachResponse)(nil), // 125: agentcompose.v2.ExecAttachResponse - (*ExecAttachStart)(nil), // 126: agentcompose.v2.ExecAttachStart - (*AttachTerminalSize)(nil), // 127: agentcompose.v2.AttachTerminalSize - (*AttachStdin)(nil), // 128: agentcompose.v2.AttachStdin - (*AttachStdinEOF)(nil), // 129: agentcompose.v2.AttachStdinEOF - (*AttachResize)(nil), // 130: agentcompose.v2.AttachResize - (*AttachSignal)(nil), // 131: agentcompose.v2.AttachSignal - (*AttachHumanMessage)(nil), // 132: agentcompose.v2.AttachHumanMessage - (*AttachCancel)(nil), // 133: agentcompose.v2.AttachCancel - (*AttachStarted)(nil), // 134: agentcompose.v2.AttachStarted - (*AttachOutput)(nil), // 135: agentcompose.v2.AttachOutput - (*AttachAgentEvent)(nil), // 136: agentcompose.v2.AttachAgentEvent - (*AttachAgentTurnCompleted)(nil), // 137: agentcompose.v2.AttachAgentTurnCompleted - (*AttachResult)(nil), // 138: agentcompose.v2.AttachResult - (*AttachError)(nil), // 139: agentcompose.v2.AttachError - (*ExecResult)(nil), // 140: agentcompose.v2.ExecResult - (*ListImagesRequest)(nil), // 141: agentcompose.v2.ListImagesRequest - (*ListImagesResponse)(nil), // 142: agentcompose.v2.ListImagesResponse - (*PullImageRequest)(nil), // 143: agentcompose.v2.PullImageRequest - (*PullImageResponse)(nil), // 144: agentcompose.v2.PullImageResponse - (*InspectImageRequest)(nil), // 145: agentcompose.v2.InspectImageRequest - (*InspectImageResponse)(nil), // 146: agentcompose.v2.InspectImageResponse - (*RemoveImageRequest)(nil), // 147: agentcompose.v2.RemoveImageRequest - (*RemoveImageResponse)(nil), // 148: agentcompose.v2.RemoveImageResponse - (*BuildImageRequest)(nil), // 149: agentcompose.v2.BuildImageRequest - (*BuildImageEvent)(nil), // 150: agentcompose.v2.BuildImageEvent - (*CacheFilter)(nil), // 151: agentcompose.v2.CacheFilter - (*ListCachesRequest)(nil), // 152: agentcompose.v2.ListCachesRequest - (*ListCachesResponse)(nil), // 153: agentcompose.v2.ListCachesResponse - (*InspectCacheRequest)(nil), // 154: agentcompose.v2.InspectCacheRequest - (*InspectCacheResponse)(nil), // 155: agentcompose.v2.InspectCacheResponse - (*PruneCachesRequest)(nil), // 156: agentcompose.v2.PruneCachesRequest - (*PruneCachesResponse)(nil), // 157: agentcompose.v2.PruneCachesResponse - (*RemoveCacheRequest)(nil), // 158: agentcompose.v2.RemoveCacheRequest - (*RemoveCacheResponse)(nil), // 159: agentcompose.v2.RemoveCacheResponse - (*CacheItem)(nil), // 160: agentcompose.v2.CacheItem - (*CacheReference)(nil), // 161: agentcompose.v2.CacheReference - (*ListVolumesRequest)(nil), // 162: agentcompose.v2.ListVolumesRequest - (*ListVolumesResponse)(nil), // 163: agentcompose.v2.ListVolumesResponse - (*CreateVolumeRequest)(nil), // 164: agentcompose.v2.CreateVolumeRequest - (*CreateVolumeResponse)(nil), // 165: agentcompose.v2.CreateVolumeResponse - (*InspectVolumeRequest)(nil), // 166: agentcompose.v2.InspectVolumeRequest - (*InspectVolumeResponse)(nil), // 167: agentcompose.v2.InspectVolumeResponse - (*RemoveVolumeRequest)(nil), // 168: agentcompose.v2.RemoveVolumeRequest - (*RemoveVolumeResponse)(nil), // 169: agentcompose.v2.RemoveVolumeResponse - (*PruneVolumesRequest)(nil), // 170: agentcompose.v2.PruneVolumesRequest - (*PruneVolumesResponse)(nil), // 171: agentcompose.v2.PruneVolumesResponse - (*Volume)(nil), // 172: agentcompose.v2.Volume - (*Image)(nil), // 173: agentcompose.v2.Image - (*ImagePlatform)(nil), // 174: agentcompose.v2.ImagePlatform - (*ImageStoreStatus)(nil), // 175: agentcompose.v2.ImageStoreStatus - (*DockerImageStatus)(nil), // 176: agentcompose.v2.DockerImageStatus - (*OCIImageStatus)(nil), // 177: agentcompose.v2.OCIImageStatus - (*ImagePullProgress)(nil), // 178: agentcompose.v2.ImagePullProgress - (*JupyterSpec)(nil), // 179: agentcompose.v2.JupyterSpec - (*RunJupyterSpec)(nil), // 180: agentcompose.v2.RunJupyterSpec - (*StartRunRequest)(nil), // 181: agentcompose.v2.StartRunRequest - (*StartRunResponse)(nil), // 182: agentcompose.v2.StartRunResponse - (*SkillSpec)(nil), // 183: agentcompose.v2.SkillSpec - (*ResolveResourceIDRequest)(nil), // 184: agentcompose.v2.ResolveResourceIDRequest - (*ResolveResourceIDResponse)(nil), // 185: agentcompose.v2.ResolveResourceIDResponse - (*ResourceTarget)(nil), // 186: agentcompose.v2.ResourceTarget - (*GetDashboardOverviewRequest)(nil), // 187: agentcompose.v2.GetDashboardOverviewRequest - (*WatchDashboardOverviewRequest)(nil), // 188: agentcompose.v2.WatchDashboardOverviewRequest - (*RunOverview)(nil), // 189: agentcompose.v2.RunOverview - (*DashboardOverview)(nil), // 190: agentcompose.v2.DashboardOverview - (*GetDashboardOverviewResponse)(nil), // 191: agentcompose.v2.GetDashboardOverviewResponse - (*WatchDashboardOverviewResponse)(nil), // 192: agentcompose.v2.WatchDashboardOverviewResponse - (*GetGlobalEnvRequest)(nil), // 193: agentcompose.v2.GetGlobalEnvRequest - (*GetGlobalEnvResponse)(nil), // 194: agentcompose.v2.GetGlobalEnvResponse - (*UpdateGlobalEnvRequest)(nil), // 195: agentcompose.v2.UpdateGlobalEnvRequest - (*UpdateGlobalEnvResponse)(nil), // 196: agentcompose.v2.UpdateGlobalEnvResponse - (*GetCapabilityGatewayConfigRequest)(nil), // 197: agentcompose.v2.GetCapabilityGatewayConfigRequest - (*CapabilityGatewayConfig)(nil), // 198: agentcompose.v2.CapabilityGatewayConfig - (*GetCapabilityGatewayConfigResponse)(nil), // 199: agentcompose.v2.GetCapabilityGatewayConfigResponse - (*UpdateCapabilityGatewayConfigRequest)(nil), // 200: agentcompose.v2.UpdateCapabilityGatewayConfigRequest - (*UpdateCapabilityGatewayConfigResponse)(nil), // 201: agentcompose.v2.UpdateCapabilityGatewayConfigResponse - (*WorkspacePreset)(nil), // 202: agentcompose.v2.WorkspacePreset - (*ListWorkspacePresetsRequest)(nil), // 203: agentcompose.v2.ListWorkspacePresetsRequest - (*ListWorkspacePresetsResponse)(nil), // 204: agentcompose.v2.ListWorkspacePresetsResponse - (*CreateWorkspacePresetRequest)(nil), // 205: agentcompose.v2.CreateWorkspacePresetRequest - (*UpdateWorkspacePresetRequest)(nil), // 206: agentcompose.v2.UpdateWorkspacePresetRequest - (*DeleteWorkspacePresetRequest)(nil), // 207: agentcompose.v2.DeleteWorkspacePresetRequest - (*DeleteWorkspacePresetResponse)(nil), // 208: agentcompose.v2.DeleteWorkspacePresetResponse - (*WorkspacePresetResponse)(nil), // 209: agentcompose.v2.WorkspacePresetResponse - (*GetCapabilityStatusRequest)(nil), // 210: agentcompose.v2.GetCapabilityStatusRequest - (*CapabilityStatusResponse)(nil), // 211: agentcompose.v2.CapabilityStatusResponse - (*ListCapabilitySetsRequest)(nil), // 212: agentcompose.v2.ListCapabilitySetsRequest - (*CapabilitySet)(nil), // 213: agentcompose.v2.CapabilitySet - (*ListCapabilitySetsResponse)(nil), // 214: agentcompose.v2.ListCapabilitySetsResponse - (*GetCapabilityCatalogRequest)(nil), // 215: agentcompose.v2.GetCapabilityCatalogRequest - (*CapabilityEndpoint)(nil), // 216: agentcompose.v2.CapabilityEndpoint - (*CapabilityMethod)(nil), // 217: agentcompose.v2.CapabilityMethod - (*GetCapabilityCatalogResponse)(nil), // 218: agentcompose.v2.GetCapabilityCatalogResponse - (*ListSandboxHistoryRequest)(nil), // 219: agentcompose.v2.ListSandboxHistoryRequest - (*SandboxHistoryCell)(nil), // 220: agentcompose.v2.SandboxHistoryCell - (*SandboxHistoryEvent)(nil), // 221: agentcompose.v2.SandboxHistoryEvent - (*ListSandboxHistoryResponse)(nil), // 222: agentcompose.v2.ListSandboxHistoryResponse - (*WatchSandboxRequest)(nil), // 223: agentcompose.v2.WatchSandboxRequest - (*WatchSandboxResponse)(nil), // 224: agentcompose.v2.WatchSandboxResponse - (*GenerateLLMRequest)(nil), // 225: agentcompose.v2.GenerateLLMRequest - (*GenerateLLMResponse)(nil), // 226: agentcompose.v2.GenerateLLMResponse - nil, // 227: agentcompose.v2.ProjectVolumeSpec.LabelsEntry - nil, // 228: agentcompose.v2.ProjectVolumeSpec.OptionsEntry - nil, // 229: agentcompose.v2.BuildSpec.ArgsEntry - nil, // 230: agentcompose.v2.AttachHumanMessage.MetadataEntry - nil, // 231: agentcompose.v2.AttachError.DetailsEntry - nil, // 232: agentcompose.v2.BuildImageRequest.BuildArgsEntry - nil, // 233: agentcompose.v2.CreateVolumeRequest.LabelsEntry - nil, // 234: agentcompose.v2.CreateVolumeRequest.OptionsEntry - nil, // 235: agentcompose.v2.Volume.LabelsEntry - nil, // 236: agentcompose.v2.Volume.OptionsEntry - nil, // 237: agentcompose.v2.Image.LabelsEntry - nil, // 238: agentcompose.v2.CapabilityEndpoint.MetadataEntry - (*timestamppb.Timestamp)(nil), // 239: google.protobuf.Timestamp + (*SandboxNetworkState)(nil), // 107: agentcompose.v2.SandboxNetworkState + (*SandboxNetworkAttachment)(nil), // 108: agentcompose.v2.SandboxNetworkAttachment + (*SandboxPortBinding)(nil), // 109: agentcompose.v2.SandboxPortBinding + (*SandboxTag)(nil), // 110: agentcompose.v2.SandboxTag + (*ListSandboxesRequest)(nil), // 111: agentcompose.v2.ListSandboxesRequest + (*ListSandboxesResponse)(nil), // 112: agentcompose.v2.ListSandboxesResponse + (*GetSandboxResponse)(nil), // 113: agentcompose.v2.GetSandboxResponse + (*StopSandboxRequest)(nil), // 114: agentcompose.v2.StopSandboxRequest + (*StopSandboxResponse)(nil), // 115: agentcompose.v2.StopSandboxResponse + (*ResumeSandboxRequest)(nil), // 116: agentcompose.v2.ResumeSandboxRequest + (*ResumeSandboxResponse)(nil), // 117: agentcompose.v2.ResumeSandboxResponse + (*MetricValue)(nil), // 118: agentcompose.v2.MetricValue + (*SandboxStats)(nil), // 119: agentcompose.v2.SandboxStats + (*RunSummary)(nil), // 120: agentcompose.v2.RunSummary + (*RunDetail)(nil), // 121: agentcompose.v2.RunDetail + (*ExecRequest)(nil), // 122: agentcompose.v2.ExecRequest + (*ExecSandboxSelector)(nil), // 123: agentcompose.v2.ExecSandboxSelector + (*ExecCommand)(nil), // 124: agentcompose.v2.ExecCommand + (*ExecResponse)(nil), // 125: agentcompose.v2.ExecResponse + (*ExecStreamResponse)(nil), // 126: agentcompose.v2.ExecStreamResponse + (*ExecAttachRequest)(nil), // 127: agentcompose.v2.ExecAttachRequest + (*ExecAttachResponse)(nil), // 128: agentcompose.v2.ExecAttachResponse + (*ExecAttachStart)(nil), // 129: agentcompose.v2.ExecAttachStart + (*AttachTerminalSize)(nil), // 130: agentcompose.v2.AttachTerminalSize + (*AttachStdin)(nil), // 131: agentcompose.v2.AttachStdin + (*AttachStdinEOF)(nil), // 132: agentcompose.v2.AttachStdinEOF + (*AttachResize)(nil), // 133: agentcompose.v2.AttachResize + (*AttachSignal)(nil), // 134: agentcompose.v2.AttachSignal + (*AttachHumanMessage)(nil), // 135: agentcompose.v2.AttachHumanMessage + (*AttachCancel)(nil), // 136: agentcompose.v2.AttachCancel + (*AttachStarted)(nil), // 137: agentcompose.v2.AttachStarted + (*AttachOutput)(nil), // 138: agentcompose.v2.AttachOutput + (*AttachAgentEvent)(nil), // 139: agentcompose.v2.AttachAgentEvent + (*AttachAgentTurnCompleted)(nil), // 140: agentcompose.v2.AttachAgentTurnCompleted + (*AttachResult)(nil), // 141: agentcompose.v2.AttachResult + (*AttachError)(nil), // 142: agentcompose.v2.AttachError + (*ExecResult)(nil), // 143: agentcompose.v2.ExecResult + (*ListImagesRequest)(nil), // 144: agentcompose.v2.ListImagesRequest + (*ListImagesResponse)(nil), // 145: agentcompose.v2.ListImagesResponse + (*PullImageRequest)(nil), // 146: agentcompose.v2.PullImageRequest + (*PullImageResponse)(nil), // 147: agentcompose.v2.PullImageResponse + (*InspectImageRequest)(nil), // 148: agentcompose.v2.InspectImageRequest + (*InspectImageResponse)(nil), // 149: agentcompose.v2.InspectImageResponse + (*RemoveImageRequest)(nil), // 150: agentcompose.v2.RemoveImageRequest + (*RemoveImageResponse)(nil), // 151: agentcompose.v2.RemoveImageResponse + (*BuildImageRequest)(nil), // 152: agentcompose.v2.BuildImageRequest + (*BuildImageEvent)(nil), // 153: agentcompose.v2.BuildImageEvent + (*CacheFilter)(nil), // 154: agentcompose.v2.CacheFilter + (*ListCachesRequest)(nil), // 155: agentcompose.v2.ListCachesRequest + (*ListCachesResponse)(nil), // 156: agentcompose.v2.ListCachesResponse + (*InspectCacheRequest)(nil), // 157: agentcompose.v2.InspectCacheRequest + (*InspectCacheResponse)(nil), // 158: agentcompose.v2.InspectCacheResponse + (*PruneCachesRequest)(nil), // 159: agentcompose.v2.PruneCachesRequest + (*PruneCachesResponse)(nil), // 160: agentcompose.v2.PruneCachesResponse + (*RemoveCacheRequest)(nil), // 161: agentcompose.v2.RemoveCacheRequest + (*RemoveCacheResponse)(nil), // 162: agentcompose.v2.RemoveCacheResponse + (*CacheItem)(nil), // 163: agentcompose.v2.CacheItem + (*CacheReference)(nil), // 164: agentcompose.v2.CacheReference + (*ListVolumesRequest)(nil), // 165: agentcompose.v2.ListVolumesRequest + (*ListVolumesResponse)(nil), // 166: agentcompose.v2.ListVolumesResponse + (*CreateVolumeRequest)(nil), // 167: agentcompose.v2.CreateVolumeRequest + (*CreateVolumeResponse)(nil), // 168: agentcompose.v2.CreateVolumeResponse + (*InspectVolumeRequest)(nil), // 169: agentcompose.v2.InspectVolumeRequest + (*InspectVolumeResponse)(nil), // 170: agentcompose.v2.InspectVolumeResponse + (*RemoveVolumeRequest)(nil), // 171: agentcompose.v2.RemoveVolumeRequest + (*RemoveVolumeResponse)(nil), // 172: agentcompose.v2.RemoveVolumeResponse + (*PruneVolumesRequest)(nil), // 173: agentcompose.v2.PruneVolumesRequest + (*PruneVolumesResponse)(nil), // 174: agentcompose.v2.PruneVolumesResponse + (*Volume)(nil), // 175: agentcompose.v2.Volume + (*Image)(nil), // 176: agentcompose.v2.Image + (*ImagePlatform)(nil), // 177: agentcompose.v2.ImagePlatform + (*ImageStoreStatus)(nil), // 178: agentcompose.v2.ImageStoreStatus + (*DockerImageStatus)(nil), // 179: agentcompose.v2.DockerImageStatus + (*OCIImageStatus)(nil), // 180: agentcompose.v2.OCIImageStatus + (*ImagePullProgress)(nil), // 181: agentcompose.v2.ImagePullProgress + (*JupyterSpec)(nil), // 182: agentcompose.v2.JupyterSpec + (*RunJupyterSpec)(nil), // 183: agentcompose.v2.RunJupyterSpec + (*StartRunRequest)(nil), // 184: agentcompose.v2.StartRunRequest + (*StartRunResponse)(nil), // 185: agentcompose.v2.StartRunResponse + (*SkillSpec)(nil), // 186: agentcompose.v2.SkillSpec + (*ResolveResourceIDRequest)(nil), // 187: agentcompose.v2.ResolveResourceIDRequest + (*ResolveResourceIDResponse)(nil), // 188: agentcompose.v2.ResolveResourceIDResponse + (*ResourceTarget)(nil), // 189: agentcompose.v2.ResourceTarget + (*GetDashboardOverviewRequest)(nil), // 190: agentcompose.v2.GetDashboardOverviewRequest + (*WatchDashboardOverviewRequest)(nil), // 191: agentcompose.v2.WatchDashboardOverviewRequest + (*RunOverview)(nil), // 192: agentcompose.v2.RunOverview + (*DashboardOverview)(nil), // 193: agentcompose.v2.DashboardOverview + (*GetDashboardOverviewResponse)(nil), // 194: agentcompose.v2.GetDashboardOverviewResponse + (*WatchDashboardOverviewResponse)(nil), // 195: agentcompose.v2.WatchDashboardOverviewResponse + (*GetGlobalEnvRequest)(nil), // 196: agentcompose.v2.GetGlobalEnvRequest + (*GetGlobalEnvResponse)(nil), // 197: agentcompose.v2.GetGlobalEnvResponse + (*UpdateGlobalEnvRequest)(nil), // 198: agentcompose.v2.UpdateGlobalEnvRequest + (*UpdateGlobalEnvResponse)(nil), // 199: agentcompose.v2.UpdateGlobalEnvResponse + (*GetCapabilityGatewayConfigRequest)(nil), // 200: agentcompose.v2.GetCapabilityGatewayConfigRequest + (*CapabilityGatewayConfig)(nil), // 201: agentcompose.v2.CapabilityGatewayConfig + (*GetCapabilityGatewayConfigResponse)(nil), // 202: agentcompose.v2.GetCapabilityGatewayConfigResponse + (*UpdateCapabilityGatewayConfigRequest)(nil), // 203: agentcompose.v2.UpdateCapabilityGatewayConfigRequest + (*UpdateCapabilityGatewayConfigResponse)(nil), // 204: agentcompose.v2.UpdateCapabilityGatewayConfigResponse + (*WorkspacePreset)(nil), // 205: agentcompose.v2.WorkspacePreset + (*ListWorkspacePresetsRequest)(nil), // 206: agentcompose.v2.ListWorkspacePresetsRequest + (*ListWorkspacePresetsResponse)(nil), // 207: agentcompose.v2.ListWorkspacePresetsResponse + (*CreateWorkspacePresetRequest)(nil), // 208: agentcompose.v2.CreateWorkspacePresetRequest + (*UpdateWorkspacePresetRequest)(nil), // 209: agentcompose.v2.UpdateWorkspacePresetRequest + (*DeleteWorkspacePresetRequest)(nil), // 210: agentcompose.v2.DeleteWorkspacePresetRequest + (*DeleteWorkspacePresetResponse)(nil), // 211: agentcompose.v2.DeleteWorkspacePresetResponse + (*WorkspacePresetResponse)(nil), // 212: agentcompose.v2.WorkspacePresetResponse + (*GetCapabilityStatusRequest)(nil), // 213: agentcompose.v2.GetCapabilityStatusRequest + (*CapabilityStatusResponse)(nil), // 214: agentcompose.v2.CapabilityStatusResponse + (*ListCapabilitySetsRequest)(nil), // 215: agentcompose.v2.ListCapabilitySetsRequest + (*CapabilitySet)(nil), // 216: agentcompose.v2.CapabilitySet + (*ListCapabilitySetsResponse)(nil), // 217: agentcompose.v2.ListCapabilitySetsResponse + (*GetCapabilityCatalogRequest)(nil), // 218: agentcompose.v2.GetCapabilityCatalogRequest + (*CapabilityEndpoint)(nil), // 219: agentcompose.v2.CapabilityEndpoint + (*CapabilityMethod)(nil), // 220: agentcompose.v2.CapabilityMethod + (*GetCapabilityCatalogResponse)(nil), // 221: agentcompose.v2.GetCapabilityCatalogResponse + (*ListSandboxHistoryRequest)(nil), // 222: agentcompose.v2.ListSandboxHistoryRequest + (*SandboxHistoryCell)(nil), // 223: agentcompose.v2.SandboxHistoryCell + (*SandboxHistoryEvent)(nil), // 224: agentcompose.v2.SandboxHistoryEvent + (*ListSandboxHistoryResponse)(nil), // 225: agentcompose.v2.ListSandboxHistoryResponse + (*WatchSandboxRequest)(nil), // 226: agentcompose.v2.WatchSandboxRequest + (*WatchSandboxResponse)(nil), // 227: agentcompose.v2.WatchSandboxResponse + (*GenerateLLMRequest)(nil), // 228: agentcompose.v2.GenerateLLMRequest + (*GenerateLLMResponse)(nil), // 229: agentcompose.v2.GenerateLLMResponse + nil, // 230: agentcompose.v2.ProjectVolumeSpec.LabelsEntry + nil, // 231: agentcompose.v2.ProjectVolumeSpec.OptionsEntry + nil, // 232: agentcompose.v2.BuildSpec.ArgsEntry + nil, // 233: agentcompose.v2.AttachHumanMessage.MetadataEntry + nil, // 234: agentcompose.v2.AttachError.DetailsEntry + nil, // 235: agentcompose.v2.BuildImageRequest.BuildArgsEntry + nil, // 236: agentcompose.v2.CreateVolumeRequest.LabelsEntry + nil, // 237: agentcompose.v2.CreateVolumeRequest.OptionsEntry + nil, // 238: agentcompose.v2.Volume.LabelsEntry + nil, // 239: agentcompose.v2.Volume.OptionsEntry + nil, // 240: agentcompose.v2.Image.LabelsEntry + nil, // 241: agentcompose.v2.CapabilityEndpoint.MetadataEntry + (*timestamppb.Timestamp)(nil), // 242: google.protobuf.Timestamp } var file_agentcompose_v2_agentcompose_proto_depIdxs = []int32{ 60, // 0: agentcompose.v2.ValidateProjectRequest.spec:type_name -> agentcompose.v2.ProjectSpec @@ -17356,18 +17633,18 @@ var file_agentcompose_v2_agentcompose_proto_depIdxs = []int32{ 43, // 28: agentcompose.v2.ProjectAgent.latest_run:type_name -> agentcompose.v2.ProjectAgentLatestRun 3, // 29: agentcompose.v2.ProjectAgentLatestRun.status:type_name -> agentcompose.v2.RunStatus 4, // 30: agentcompose.v2.ProjectAgentLatestRun.source:type_name -> agentcompose.v2.RunSource - 239, // 31: agentcompose.v2.ProjectAgentLatestRun.at:type_name -> google.protobuf.Timestamp + 242, // 31: agentcompose.v2.ProjectAgentLatestRun.at:type_name -> google.protobuf.Timestamp 36, // 32: agentcompose.v2.GetSchedulerRequest.project:type_name -> agentcompose.v2.ProjectRef 44, // 33: agentcompose.v2.GetSchedulerResponse.scheduler:type_name -> agentcompose.v2.ProjectScheduler 71, // 34: agentcompose.v2.GetSchedulerResponse.spec:type_name -> agentcompose.v2.SchedulerSpec 47, // 35: agentcompose.v2.GetSchedulerResponse.triggers:type_name -> agentcompose.v2.ResolvedTrigger 72, // 36: agentcompose.v2.ResolvedTrigger.spec:type_name -> agentcompose.v2.TriggerSpec - 239, // 37: agentcompose.v2.ResolvedTrigger.next_fire_at:type_name -> google.protobuf.Timestamp - 239, // 38: agentcompose.v2.ResolvedTrigger.last_fired_at:type_name -> google.protobuf.Timestamp - 239, // 39: agentcompose.v2.SchedulerSummary.latest_run_at:type_name -> google.protobuf.Timestamp + 242, // 37: agentcompose.v2.ResolvedTrigger.next_fire_at:type_name -> google.protobuf.Timestamp + 242, // 38: agentcompose.v2.ResolvedTrigger.last_fired_at:type_name -> google.protobuf.Timestamp + 242, // 39: agentcompose.v2.SchedulerSummary.latest_run_at:type_name -> google.protobuf.Timestamp 49, // 40: agentcompose.v2.ListSchedulersResponse.schedulers:type_name -> agentcompose.v2.SchedulerSummary 36, // 41: agentcompose.v2.ListSchedulerEventsRequest.project:type_name -> agentcompose.v2.ProjectRef - 239, // 42: agentcompose.v2.SchedulerEvent.created_at:type_name -> google.protobuf.Timestamp + 242, // 42: agentcompose.v2.SchedulerEvent.created_at:type_name -> google.protobuf.Timestamp 52, // 43: agentcompose.v2.ListSchedulerEventsResponse.events:type_name -> agentcompose.v2.SchedulerEvent 36, // 44: agentcompose.v2.SetSchedulerEnabledRequest.project:type_name -> agentcompose.v2.ProjectRef 44, // 45: agentcompose.v2.SetSchedulerEnabledResponse.scheduler:type_name -> agentcompose.v2.ProjectScheduler @@ -17377,26 +17654,26 @@ var file_agentcompose_v2_agentcompose_proto_depIdxs = []int32{ 1, // 49: agentcompose.v2.ProjectChange.action:type_name -> agentcompose.v2.ProjectChangeAction 67, // 50: agentcompose.v2.ProjectSpec.variables:type_name -> agentcompose.v2.EnvVarSpec 62, // 51: agentcompose.v2.ProjectSpec.agents:type_name -> agentcompose.v2.AgentSpec - 70, // 52: agentcompose.v2.ProjectSpec.network:type_name -> agentcompose.v2.NetworkSpec - 64, // 53: agentcompose.v2.ProjectSpec.volumes:type_name -> agentcompose.v2.ProjectVolumeSpec - 61, // 54: agentcompose.v2.ProjectSpec.workspaces:type_name -> agentcompose.v2.NamedWorkspaceSpec - 63, // 55: agentcompose.v2.ProjectSpec.mcp_servers:type_name -> agentcompose.v2.MCPServerSpec + 64, // 52: agentcompose.v2.ProjectSpec.volumes:type_name -> agentcompose.v2.ProjectVolumeSpec + 61, // 53: agentcompose.v2.ProjectSpec.workspaces:type_name -> agentcompose.v2.NamedWorkspaceSpec + 63, // 54: agentcompose.v2.ProjectSpec.mcp_servers:type_name -> agentcompose.v2.MCPServerSpec + 70, // 55: agentcompose.v2.ProjectSpec.networks:type_name -> agentcompose.v2.NamedNetworkSpec 69, // 56: agentcompose.v2.NamedWorkspaceSpec.workspace:type_name -> agentcompose.v2.WorkspaceSpec 74, // 57: agentcompose.v2.AgentSpec.driver:type_name -> agentcompose.v2.DriverSpec 67, // 58: agentcompose.v2.AgentSpec.env:type_name -> agentcompose.v2.EnvVarSpec 69, // 59: agentcompose.v2.AgentSpec.workspace:type_name -> agentcompose.v2.WorkspaceSpec 71, // 60: agentcompose.v2.AgentSpec.scheduler:type_name -> agentcompose.v2.SchedulerSpec - 179, // 61: agentcompose.v2.AgentSpec.jupyter:type_name -> agentcompose.v2.JupyterSpec + 182, // 61: agentcompose.v2.AgentSpec.jupyter:type_name -> agentcompose.v2.JupyterSpec 66, // 62: agentcompose.v2.AgentSpec.build:type_name -> agentcompose.v2.BuildSpec 65, // 63: agentcompose.v2.AgentSpec.volumes:type_name -> agentcompose.v2.VolumeMountSpec 63, // 64: agentcompose.v2.AgentSpec.mcp_servers:type_name -> agentcompose.v2.MCPServerSpec - 183, // 65: agentcompose.v2.AgentSpec.skills:type_name -> agentcompose.v2.SkillSpec + 186, // 65: agentcompose.v2.AgentSpec.skills:type_name -> agentcompose.v2.SkillSpec 5, // 66: agentcompose.v2.AgentSpec.status:type_name -> agentcompose.v2.AgentStatus 67, // 67: agentcompose.v2.MCPServerSpec.env:type_name -> agentcompose.v2.EnvVarSpec 67, // 68: agentcompose.v2.MCPServerSpec.headers:type_name -> agentcompose.v2.EnvVarSpec - 227, // 69: agentcompose.v2.ProjectVolumeSpec.labels:type_name -> agentcompose.v2.ProjectVolumeSpec.LabelsEntry - 228, // 70: agentcompose.v2.ProjectVolumeSpec.options:type_name -> agentcompose.v2.ProjectVolumeSpec.OptionsEntry - 229, // 71: agentcompose.v2.BuildSpec.args:type_name -> agentcompose.v2.BuildSpec.ArgsEntry + 230, // 69: agentcompose.v2.ProjectVolumeSpec.labels:type_name -> agentcompose.v2.ProjectVolumeSpec.LabelsEntry + 231, // 70: agentcompose.v2.ProjectVolumeSpec.options:type_name -> agentcompose.v2.ProjectVolumeSpec.OptionsEntry + 232, // 71: agentcompose.v2.BuildSpec.args:type_name -> agentcompose.v2.BuildSpec.ArgsEntry 72, // 72: agentcompose.v2.SchedulerSpec.triggers:type_name -> agentcompose.v2.TriggerSpec 73, // 73: agentcompose.v2.TriggerSpec.event:type_name -> agentcompose.v2.EventTriggerSpec 75, // 74: agentcompose.v2.DriverSpec.boxlite:type_name -> agentcompose.v2.BoxliteDriverSpec @@ -17405,306 +17682,310 @@ var file_agentcompose_v2_agentcompose_proto_depIdxs = []int32{ 4, // 77: agentcompose.v2.RunAgentRequest.source:type_name -> agentcompose.v2.RunSource 67, // 78: agentcompose.v2.RunAgentRequest.env:type_name -> agentcompose.v2.EnvVarSpec 10, // 79: agentcompose.v2.RunAgentRequest.cleanup_policy:type_name -> agentcompose.v2.RunSandboxCleanupPolicy - 180, // 80: agentcompose.v2.RunAgentRequest.jupyter:type_name -> agentcompose.v2.RunJupyterSpec + 183, // 80: agentcompose.v2.RunAgentRequest.jupyter:type_name -> agentcompose.v2.RunJupyterSpec 65, // 81: agentcompose.v2.RunAgentRequest.volumes:type_name -> agentcompose.v2.VolumeMountSpec - 118, // 82: agentcompose.v2.RunAgentResponse.run:type_name -> agentcompose.v2.RunDetail + 121, // 82: agentcompose.v2.RunAgentResponse.run:type_name -> agentcompose.v2.RunDetail 9, // 83: agentcompose.v2.RunAgentStreamResponse.event_type:type_name -> agentcompose.v2.RunAgentStreamEventType - 117, // 84: agentcompose.v2.RunAgentStreamResponse.run:type_name -> agentcompose.v2.RunSummary + 120, // 84: agentcompose.v2.RunAgentStreamResponse.run:type_name -> agentcompose.v2.RunSummary 13, // 85: agentcompose.v2.RunAgentStreamResponse.stream:type_name -> agentcompose.v2.StdioStream 84, // 86: agentcompose.v2.RunAgentStreamResponse.transcript:type_name -> agentcompose.v2.TranscriptEvent 83, // 87: agentcompose.v2.RunAttachRequest.start:type_name -> agentcompose.v2.RunAttachStart - 128, // 88: agentcompose.v2.RunAttachRequest.stdin:type_name -> agentcompose.v2.AttachStdin - 129, // 89: agentcompose.v2.RunAttachRequest.stdin_eof:type_name -> agentcompose.v2.AttachStdinEOF - 130, // 90: agentcompose.v2.RunAttachRequest.resize:type_name -> agentcompose.v2.AttachResize - 131, // 91: agentcompose.v2.RunAttachRequest.signal:type_name -> agentcompose.v2.AttachSignal - 132, // 92: agentcompose.v2.RunAttachRequest.human_message:type_name -> agentcompose.v2.AttachHumanMessage - 133, // 93: agentcompose.v2.RunAttachRequest.cancel:type_name -> agentcompose.v2.AttachCancel - 134, // 94: agentcompose.v2.RunAttachResponse.started:type_name -> agentcompose.v2.AttachStarted - 135, // 95: agentcompose.v2.RunAttachResponse.output:type_name -> agentcompose.v2.AttachOutput - 136, // 96: agentcompose.v2.RunAttachResponse.agent_event:type_name -> agentcompose.v2.AttachAgentEvent - 137, // 97: agentcompose.v2.RunAttachResponse.agent_turn_completed:type_name -> agentcompose.v2.AttachAgentTurnCompleted - 138, // 98: agentcompose.v2.RunAttachResponse.result:type_name -> agentcompose.v2.AttachResult - 139, // 99: agentcompose.v2.RunAttachResponse.error:type_name -> agentcompose.v2.AttachError + 131, // 88: agentcompose.v2.RunAttachRequest.stdin:type_name -> agentcompose.v2.AttachStdin + 132, // 89: agentcompose.v2.RunAttachRequest.stdin_eof:type_name -> agentcompose.v2.AttachStdinEOF + 133, // 90: agentcompose.v2.RunAttachRequest.resize:type_name -> agentcompose.v2.AttachResize + 134, // 91: agentcompose.v2.RunAttachRequest.signal:type_name -> agentcompose.v2.AttachSignal + 135, // 92: agentcompose.v2.RunAttachRequest.human_message:type_name -> agentcompose.v2.AttachHumanMessage + 136, // 93: agentcompose.v2.RunAttachRequest.cancel:type_name -> agentcompose.v2.AttachCancel + 137, // 94: agentcompose.v2.RunAttachResponse.started:type_name -> agentcompose.v2.AttachStarted + 138, // 95: agentcompose.v2.RunAttachResponse.output:type_name -> agentcompose.v2.AttachOutput + 139, // 96: agentcompose.v2.RunAttachResponse.agent_event:type_name -> agentcompose.v2.AttachAgentEvent + 140, // 97: agentcompose.v2.RunAttachResponse.agent_turn_completed:type_name -> agentcompose.v2.AttachAgentTurnCompleted + 141, // 98: agentcompose.v2.RunAttachResponse.result:type_name -> agentcompose.v2.AttachResult + 142, // 99: agentcompose.v2.RunAttachResponse.error:type_name -> agentcompose.v2.AttachError 78, // 100: agentcompose.v2.RunAttachStart.request:type_name -> agentcompose.v2.RunAgentRequest 12, // 101: agentcompose.v2.RunAttachStart.mode:type_name -> agentcompose.v2.AttachRunMode - 127, // 102: agentcompose.v2.RunAttachStart.terminal_size:type_name -> agentcompose.v2.AttachTerminalSize + 130, // 102: agentcompose.v2.RunAttachStart.terminal_size:type_name -> agentcompose.v2.AttachTerminalSize 13, // 103: agentcompose.v2.TranscriptEvent.stream:type_name -> agentcompose.v2.StdioStream - 118, // 104: agentcompose.v2.GetRunResponse.run:type_name -> agentcompose.v2.RunDetail + 121, // 104: agentcompose.v2.GetRunResponse.run:type_name -> agentcompose.v2.RunDetail 3, // 105: agentcompose.v2.ListRunsRequest.status:type_name -> agentcompose.v2.RunStatus 4, // 106: agentcompose.v2.ListRunsRequest.source:type_name -> agentcompose.v2.RunSource - 117, // 107: agentcompose.v2.ListRunsResponse.runs:type_name -> agentcompose.v2.RunSummary + 120, // 107: agentcompose.v2.ListRunsResponse.runs:type_name -> agentcompose.v2.RunSummary 3, // 108: agentcompose.v2.RunLogChunk.run_status:type_name -> agentcompose.v2.RunStatus - 118, // 109: agentcompose.v2.StopRunResponse.run:type_name -> agentcompose.v2.RunDetail + 121, // 109: agentcompose.v2.StopRunResponse.run:type_name -> agentcompose.v2.RunDetail 8, // 110: agentcompose.v2.RunEvent.kind:type_name -> agentcompose.v2.RunEventKind - 239, // 111: agentcompose.v2.RunEvent.created_at:type_name -> google.protobuf.Timestamp + 242, // 111: agentcompose.v2.RunEvent.created_at:type_name -> google.protobuf.Timestamp 94, // 112: agentcompose.v2.ListRunEventsResponse.events:type_name -> agentcompose.v2.RunEvent 94, // 113: agentcompose.v2.ListSandboxRunEventsResponse.events:type_name -> agentcompose.v2.RunEvent 20, // 114: agentcompose.v2.SandboxPruneCandidate.kind:type_name -> agentcompose.v2.SandboxPruneCandidateKind - 239, // 115: agentcompose.v2.SandboxPruneCandidate.updated_at:type_name -> google.protobuf.Timestamp + 242, // 115: agentcompose.v2.SandboxPruneCandidate.updated_at:type_name -> google.protobuf.Timestamp 101, // 116: agentcompose.v2.PruneSandboxesResponse.matched:type_name -> agentcompose.v2.SandboxPruneCandidate 101, // 117: agentcompose.v2.PruneSandboxesResponse.skipped:type_name -> agentcompose.v2.SandboxPruneCandidate - 116, // 118: agentcompose.v2.GetSandboxStatsResponse.stats:type_name -> agentcompose.v2.SandboxStats - 239, // 119: agentcompose.v2.Sandbox.created_at:type_name -> google.protobuf.Timestamp - 239, // 120: agentcompose.v2.Sandbox.updated_at:type_name -> google.protobuf.Timestamp - 107, // 121: agentcompose.v2.Sandbox.tags:type_name -> agentcompose.v2.SandboxTag - 106, // 122: agentcompose.v2.ListSandboxesResponse.sandboxes:type_name -> agentcompose.v2.Sandbox - 106, // 123: agentcompose.v2.GetSandboxResponse.sandbox:type_name -> agentcompose.v2.Sandbox - 106, // 124: agentcompose.v2.StopSandboxResponse.sandbox:type_name -> agentcompose.v2.Sandbox - 106, // 125: agentcompose.v2.ResumeSandboxResponse.sandbox:type_name -> agentcompose.v2.Sandbox - 17, // 126: agentcompose.v2.MetricValue.status:type_name -> agentcompose.v2.MetricStatus - 115, // 127: agentcompose.v2.SandboxStats.cpu_percent:type_name -> agentcompose.v2.MetricValue - 115, // 128: agentcompose.v2.SandboxStats.memory_usage_bytes:type_name -> agentcompose.v2.MetricValue - 115, // 129: agentcompose.v2.SandboxStats.memory_limit_bytes:type_name -> agentcompose.v2.MetricValue - 115, // 130: agentcompose.v2.SandboxStats.memory_percent:type_name -> agentcompose.v2.MetricValue - 115, // 131: agentcompose.v2.SandboxStats.network_rx_bytes:type_name -> agentcompose.v2.MetricValue - 115, // 132: agentcompose.v2.SandboxStats.network_tx_bytes:type_name -> agentcompose.v2.MetricValue - 115, // 133: agentcompose.v2.SandboxStats.block_read_bytes:type_name -> agentcompose.v2.MetricValue - 115, // 134: agentcompose.v2.SandboxStats.block_write_bytes:type_name -> agentcompose.v2.MetricValue - 115, // 135: agentcompose.v2.SandboxStats.uptime_seconds:type_name -> agentcompose.v2.MetricValue - 4, // 136: agentcompose.v2.RunSummary.source:type_name -> agentcompose.v2.RunSource - 3, // 137: agentcompose.v2.RunSummary.status:type_name -> agentcompose.v2.RunStatus - 117, // 138: agentcompose.v2.RunDetail.summary:type_name -> agentcompose.v2.RunSummary - 120, // 139: agentcompose.v2.ExecRequest.selector:type_name -> agentcompose.v2.ExecSandboxSelector - 121, // 140: agentcompose.v2.ExecRequest.command:type_name -> agentcompose.v2.ExecCommand - 67, // 141: agentcompose.v2.ExecRequest.env:type_name -> agentcompose.v2.EnvVarSpec - 140, // 142: agentcompose.v2.ExecResponse.result:type_name -> agentcompose.v2.ExecResult - 11, // 143: agentcompose.v2.ExecStreamResponse.event_type:type_name -> agentcompose.v2.ExecStreamEventType - 13, // 144: agentcompose.v2.ExecStreamResponse.stream:type_name -> agentcompose.v2.StdioStream - 140, // 145: agentcompose.v2.ExecStreamResponse.result:type_name -> agentcompose.v2.ExecResult - 84, // 146: agentcompose.v2.ExecStreamResponse.transcript:type_name -> agentcompose.v2.TranscriptEvent - 126, // 147: agentcompose.v2.ExecAttachRequest.start:type_name -> agentcompose.v2.ExecAttachStart - 128, // 148: agentcompose.v2.ExecAttachRequest.stdin:type_name -> agentcompose.v2.AttachStdin - 129, // 149: agentcompose.v2.ExecAttachRequest.stdin_eof:type_name -> agentcompose.v2.AttachStdinEOF - 130, // 150: agentcompose.v2.ExecAttachRequest.resize:type_name -> agentcompose.v2.AttachResize - 131, // 151: agentcompose.v2.ExecAttachRequest.signal:type_name -> agentcompose.v2.AttachSignal - 133, // 152: agentcompose.v2.ExecAttachRequest.cancel:type_name -> agentcompose.v2.AttachCancel - 132, // 153: agentcompose.v2.ExecAttachRequest.human_message:type_name -> agentcompose.v2.AttachHumanMessage - 134, // 154: agentcompose.v2.ExecAttachResponse.started:type_name -> agentcompose.v2.AttachStarted - 135, // 155: agentcompose.v2.ExecAttachResponse.output:type_name -> agentcompose.v2.AttachOutput - 138, // 156: agentcompose.v2.ExecAttachResponse.result:type_name -> agentcompose.v2.AttachResult - 139, // 157: agentcompose.v2.ExecAttachResponse.error:type_name -> agentcompose.v2.AttachError - 136, // 158: agentcompose.v2.ExecAttachResponse.agent_event:type_name -> agentcompose.v2.AttachAgentEvent - 137, // 159: agentcompose.v2.ExecAttachResponse.agent_turn_completed:type_name -> agentcompose.v2.AttachAgentTurnCompleted - 119, // 160: agentcompose.v2.ExecAttachStart.request:type_name -> agentcompose.v2.ExecRequest - 127, // 161: agentcompose.v2.ExecAttachStart.terminal_size:type_name -> agentcompose.v2.AttachTerminalSize - 12, // 162: agentcompose.v2.ExecAttachStart.mode:type_name -> agentcompose.v2.AttachRunMode - 127, // 163: agentcompose.v2.AttachResize.terminal_size:type_name -> agentcompose.v2.AttachTerminalSize - 230, // 164: agentcompose.v2.AttachHumanMessage.metadata:type_name -> agentcompose.v2.AttachHumanMessage.MetadataEntry - 117, // 165: agentcompose.v2.AttachStarted.run:type_name -> agentcompose.v2.RunSummary - 13, // 166: agentcompose.v2.AttachOutput.stream:type_name -> agentcompose.v2.StdioStream - 84, // 167: agentcompose.v2.AttachOutput.transcript:type_name -> agentcompose.v2.TranscriptEvent - 140, // 168: agentcompose.v2.AttachResult.exec_result:type_name -> agentcompose.v2.ExecResult - 117, // 169: agentcompose.v2.AttachResult.run:type_name -> agentcompose.v2.RunSummary - 231, // 170: agentcompose.v2.AttachError.details:type_name -> agentcompose.v2.AttachError.DetailsEntry - 121, // 171: agentcompose.v2.ExecResult.command:type_name -> agentcompose.v2.ExecCommand - 14, // 172: agentcompose.v2.ListImagesRequest.store:type_name -> agentcompose.v2.ImageStoreKind - 173, // 173: agentcompose.v2.ListImagesResponse.images:type_name -> agentcompose.v2.Image - 175, // 174: agentcompose.v2.ListImagesResponse.store_status:type_name -> agentcompose.v2.ImageStoreStatus - 14, // 175: agentcompose.v2.PullImageRequest.store:type_name -> agentcompose.v2.ImageStoreKind - 174, // 176: agentcompose.v2.PullImageRequest.platform:type_name -> agentcompose.v2.ImagePlatform - 173, // 177: agentcompose.v2.PullImageResponse.image:type_name -> agentcompose.v2.Image - 16, // 178: agentcompose.v2.PullImageResponse.status:type_name -> agentcompose.v2.ImageOperationStatus - 178, // 179: agentcompose.v2.PullImageResponse.progress:type_name -> agentcompose.v2.ImagePullProgress - 14, // 180: agentcompose.v2.InspectImageRequest.store:type_name -> agentcompose.v2.ImageStoreKind - 173, // 181: agentcompose.v2.InspectImageResponse.image:type_name -> agentcompose.v2.Image - 175, // 182: agentcompose.v2.InspectImageResponse.store_status:type_name -> agentcompose.v2.ImageStoreStatus - 14, // 183: agentcompose.v2.RemoveImageRequest.store:type_name -> agentcompose.v2.ImageStoreKind - 232, // 184: agentcompose.v2.BuildImageRequest.build_args:type_name -> agentcompose.v2.BuildImageRequest.BuildArgsEntry - 14, // 185: agentcompose.v2.BuildImageRequest.store:type_name -> agentcompose.v2.ImageStoreKind - 174, // 186: agentcompose.v2.BuildImageRequest.platform:type_name -> agentcompose.v2.ImagePlatform - 16, // 187: agentcompose.v2.BuildImageEvent.status:type_name -> agentcompose.v2.ImageOperationStatus - 173, // 188: agentcompose.v2.BuildImageEvent.image:type_name -> agentcompose.v2.Image - 18, // 189: agentcompose.v2.CacheFilter.domain:type_name -> agentcompose.v2.CacheDomain - 21, // 190: agentcompose.v2.CacheFilter.status:type_name -> agentcompose.v2.CacheStatus - 151, // 191: agentcompose.v2.ListCachesRequest.filter:type_name -> agentcompose.v2.CacheFilter - 160, // 192: agentcompose.v2.ListCachesResponse.caches:type_name -> agentcompose.v2.CacheItem - 160, // 193: agentcompose.v2.InspectCacheResponse.cache:type_name -> agentcompose.v2.CacheItem - 151, // 194: agentcompose.v2.PruneCachesRequest.filter:type_name -> agentcompose.v2.CacheFilter - 160, // 195: agentcompose.v2.PruneCachesResponse.matched:type_name -> agentcompose.v2.CacheItem - 160, // 196: agentcompose.v2.PruneCachesResponse.skipped:type_name -> agentcompose.v2.CacheItem - 160, // 197: agentcompose.v2.RemoveCacheResponse.matched:type_name -> agentcompose.v2.CacheItem - 160, // 198: agentcompose.v2.RemoveCacheResponse.skipped:type_name -> agentcompose.v2.CacheItem - 18, // 199: agentcompose.v2.CacheItem.domain:type_name -> agentcompose.v2.CacheDomain - 21, // 200: agentcompose.v2.CacheItem.status:type_name -> agentcompose.v2.CacheStatus - 161, // 201: agentcompose.v2.CacheItem.references:type_name -> agentcompose.v2.CacheReference - 19, // 202: agentcompose.v2.CacheReference.policy:type_name -> agentcompose.v2.CacheReferencePolicy - 172, // 203: agentcompose.v2.ListVolumesResponse.volumes:type_name -> agentcompose.v2.Volume - 233, // 204: agentcompose.v2.CreateVolumeRequest.labels:type_name -> agentcompose.v2.CreateVolumeRequest.LabelsEntry - 234, // 205: agentcompose.v2.CreateVolumeRequest.options:type_name -> agentcompose.v2.CreateVolumeRequest.OptionsEntry - 172, // 206: agentcompose.v2.CreateVolumeResponse.volume:type_name -> agentcompose.v2.Volume - 172, // 207: agentcompose.v2.InspectVolumeResponse.volume:type_name -> agentcompose.v2.Volume - 172, // 208: agentcompose.v2.PruneVolumesResponse.matched:type_name -> agentcompose.v2.Volume - 172, // 209: agentcompose.v2.PruneVolumesResponse.removed:type_name -> agentcompose.v2.Volume - 172, // 210: agentcompose.v2.PruneVolumesResponse.skipped:type_name -> agentcompose.v2.Volume - 235, // 211: agentcompose.v2.Volume.labels:type_name -> agentcompose.v2.Volume.LabelsEntry - 236, // 212: agentcompose.v2.Volume.options:type_name -> agentcompose.v2.Volume.OptionsEntry - 14, // 213: agentcompose.v2.Image.store:type_name -> agentcompose.v2.ImageStoreKind - 15, // 214: agentcompose.v2.Image.availability_status:type_name -> agentcompose.v2.ImageAvailabilityStatus - 174, // 215: agentcompose.v2.Image.platform:type_name -> agentcompose.v2.ImagePlatform - 176, // 216: agentcompose.v2.Image.docker:type_name -> agentcompose.v2.DockerImageStatus - 177, // 217: agentcompose.v2.Image.oci:type_name -> agentcompose.v2.OCIImageStatus - 237, // 218: agentcompose.v2.Image.labels:type_name -> agentcompose.v2.Image.LabelsEntry - 14, // 219: agentcompose.v2.ImageStoreStatus.store:type_name -> agentcompose.v2.ImageStoreKind - 78, // 220: agentcompose.v2.StartRunRequest.run:type_name -> agentcompose.v2.RunAgentRequest - 117, // 221: agentcompose.v2.StartRunResponse.run:type_name -> agentcompose.v2.RunSummary - 22, // 222: agentcompose.v2.ResolveResourceIDRequest.kinds:type_name -> agentcompose.v2.ResourceKind - 186, // 223: agentcompose.v2.ResolveResourceIDResponse.targets:type_name -> agentcompose.v2.ResourceTarget - 22, // 224: agentcompose.v2.ResourceTarget.kind:type_name -> agentcompose.v2.ResourceKind - 189, // 225: agentcompose.v2.DashboardOverview.runs:type_name -> agentcompose.v2.RunOverview - 239, // 226: agentcompose.v2.DashboardOverview.updated_at:type_name -> google.protobuf.Timestamp - 190, // 227: agentcompose.v2.GetDashboardOverviewResponse.overview:type_name -> agentcompose.v2.DashboardOverview - 190, // 228: agentcompose.v2.WatchDashboardOverviewResponse.overview:type_name -> agentcompose.v2.DashboardOverview - 67, // 229: agentcompose.v2.GetGlobalEnvResponse.env:type_name -> agentcompose.v2.EnvVarSpec - 68, // 230: agentcompose.v2.UpdateGlobalEnvRequest.env:type_name -> agentcompose.v2.EnvVarUpdateSpec - 67, // 231: agentcompose.v2.UpdateGlobalEnvResponse.env:type_name -> agentcompose.v2.EnvVarSpec - 198, // 232: agentcompose.v2.GetCapabilityGatewayConfigResponse.config:type_name -> agentcompose.v2.CapabilityGatewayConfig - 198, // 233: agentcompose.v2.UpdateCapabilityGatewayConfigResponse.config:type_name -> agentcompose.v2.CapabilityGatewayConfig - 239, // 234: agentcompose.v2.WorkspacePreset.created_at:type_name -> google.protobuf.Timestamp - 239, // 235: agentcompose.v2.WorkspacePreset.updated_at:type_name -> google.protobuf.Timestamp - 202, // 236: agentcompose.v2.ListWorkspacePresetsResponse.presets:type_name -> agentcompose.v2.WorkspacePreset - 202, // 237: agentcompose.v2.WorkspacePresetResponse.preset:type_name -> agentcompose.v2.WorkspacePreset - 213, // 238: agentcompose.v2.ListCapabilitySetsResponse.capsets:type_name -> agentcompose.v2.CapabilitySet - 238, // 239: agentcompose.v2.CapabilityEndpoint.metadata:type_name -> agentcompose.v2.CapabilityEndpoint.MetadataEntry - 216, // 240: agentcompose.v2.CapabilityMethod.endpoints:type_name -> agentcompose.v2.CapabilityEndpoint - 217, // 241: agentcompose.v2.GetCapabilityCatalogResponse.methods:type_name -> agentcompose.v2.CapabilityMethod - 239, // 242: agentcompose.v2.SandboxHistoryCell.created_at:type_name -> google.protobuf.Timestamp - 239, // 243: agentcompose.v2.SandboxHistoryEvent.created_at:type_name -> google.protobuf.Timestamp - 220, // 244: agentcompose.v2.ListSandboxHistoryResponse.cells:type_name -> agentcompose.v2.SandboxHistoryCell - 221, // 245: agentcompose.v2.ListSandboxHistoryResponse.events:type_name -> agentcompose.v2.SandboxHistoryEvent - 23, // 246: agentcompose.v2.WatchSandboxResponse.event_type:type_name -> agentcompose.v2.SandboxWatchEventType - 106, // 247: agentcompose.v2.WatchSandboxResponse.sandbox:type_name -> agentcompose.v2.Sandbox - 220, // 248: agentcompose.v2.WatchSandboxResponse.cell:type_name -> agentcompose.v2.SandboxHistoryCell - 221, // 249: agentcompose.v2.WatchSandboxResponse.event:type_name -> agentcompose.v2.SandboxHistoryEvent - 13, // 250: agentcompose.v2.WatchSandboxResponse.stream:type_name -> agentcompose.v2.StdioStream - 24, // 251: agentcompose.v2.ProjectService.ValidateProject:input_type -> agentcompose.v2.ValidateProjectRequest - 26, // 252: agentcompose.v2.ProjectService.ApplyProject:input_type -> agentcompose.v2.ApplyProjectRequest - 28, // 253: agentcompose.v2.ProjectService.GetProject:input_type -> agentcompose.v2.GetProjectRequest - 30, // 254: agentcompose.v2.ProjectService.ListProjects:input_type -> agentcompose.v2.ListProjectsRequest - 32, // 255: agentcompose.v2.ProjectService.RemoveProject:input_type -> agentcompose.v2.RemoveProjectRequest - 34, // 256: agentcompose.v2.ProjectService.WatchProject:input_type -> agentcompose.v2.WatchProjectRequest - 45, // 257: agentcompose.v2.ProjectService.GetScheduler:input_type -> agentcompose.v2.GetSchedulerRequest - 48, // 258: agentcompose.v2.ProjectService.ListSchedulers:input_type -> agentcompose.v2.ListSchedulersRequest - 51, // 259: agentcompose.v2.ProjectService.ListSchedulerEvents:input_type -> agentcompose.v2.ListSchedulerEventsRequest - 54, // 260: agentcompose.v2.ProjectService.SetSchedulerEnabled:input_type -> agentcompose.v2.SetSchedulerEnabledRequest - 56, // 261: agentcompose.v2.ProjectService.SetSchedulerTriggerEnabled:input_type -> agentcompose.v2.SetSchedulerTriggerEnabledRequest - 78, // 262: agentcompose.v2.RunService.RunAgent:input_type -> agentcompose.v2.RunAgentRequest - 181, // 263: agentcompose.v2.RunService.StartRun:input_type -> agentcompose.v2.StartRunRequest - 78, // 264: agentcompose.v2.RunService.RunAgentStream:input_type -> agentcompose.v2.RunAgentRequest - 81, // 265: agentcompose.v2.RunService.RunAttach:input_type -> agentcompose.v2.RunAttachRequest - 85, // 266: agentcompose.v2.RunService.GetRun:input_type -> agentcompose.v2.GetRunRequest - 87, // 267: agentcompose.v2.RunService.ListRuns:input_type -> agentcompose.v2.ListRunsRequest - 89, // 268: agentcompose.v2.RunService.FollowRunLogs:input_type -> agentcompose.v2.FollowRunLogsRequest - 91, // 269: agentcompose.v2.RunService.StopRun:input_type -> agentcompose.v2.StopRunRequest - 93, // 270: agentcompose.v2.RunService.ListRunEvents:input_type -> agentcompose.v2.ListRunEventsRequest - 96, // 271: agentcompose.v2.RunService.ListSandboxRunEvents:input_type -> agentcompose.v2.ListSandboxRunEventsRequest - 119, // 272: agentcompose.v2.ExecService.Exec:input_type -> agentcompose.v2.ExecRequest - 119, // 273: agentcompose.v2.ExecService.ExecStream:input_type -> agentcompose.v2.ExecRequest - 124, // 274: agentcompose.v2.ExecService.ExecAttach:input_type -> agentcompose.v2.ExecAttachRequest - 141, // 275: agentcompose.v2.ImageService.ListImages:input_type -> agentcompose.v2.ListImagesRequest - 143, // 276: agentcompose.v2.ImageService.PullImage:input_type -> agentcompose.v2.PullImageRequest - 145, // 277: agentcompose.v2.ImageService.InspectImage:input_type -> agentcompose.v2.InspectImageRequest - 147, // 278: agentcompose.v2.ImageService.RemoveImage:input_type -> agentcompose.v2.RemoveImageRequest - 149, // 279: agentcompose.v2.ImageService.BuildImage:input_type -> agentcompose.v2.BuildImageRequest - 152, // 280: agentcompose.v2.CacheService.ListCaches:input_type -> agentcompose.v2.ListCachesRequest - 154, // 281: agentcompose.v2.CacheService.InspectCache:input_type -> agentcompose.v2.InspectCacheRequest - 156, // 282: agentcompose.v2.CacheService.PruneCaches:input_type -> agentcompose.v2.PruneCachesRequest - 158, // 283: agentcompose.v2.CacheService.RemoveCache:input_type -> agentcompose.v2.RemoveCacheRequest - 162, // 284: agentcompose.v2.VolumeService.ListVolumes:input_type -> agentcompose.v2.ListVolumesRequest - 164, // 285: agentcompose.v2.VolumeService.CreateVolume:input_type -> agentcompose.v2.CreateVolumeRequest - 166, // 286: agentcompose.v2.VolumeService.InspectVolume:input_type -> agentcompose.v2.InspectVolumeRequest - 168, // 287: agentcompose.v2.VolumeService.RemoveVolume:input_type -> agentcompose.v2.RemoveVolumeRequest - 170, // 288: agentcompose.v2.VolumeService.PruneVolumes:input_type -> agentcompose.v2.PruneVolumesRequest - 98, // 289: agentcompose.v2.SandboxService.RemoveSandbox:input_type -> agentcompose.v2.RemoveSandboxRequest - 100, // 290: agentcompose.v2.SandboxService.PruneSandboxes:input_type -> agentcompose.v2.PruneSandboxesRequest - 103, // 291: agentcompose.v2.SandboxService.GetSandboxStats:input_type -> agentcompose.v2.GetSandboxStatsRequest - 105, // 292: agentcompose.v2.SandboxService.GetSandbox:input_type -> agentcompose.v2.GetSandboxRequest - 111, // 293: agentcompose.v2.SandboxService.StopSandbox:input_type -> agentcompose.v2.StopSandboxRequest - 113, // 294: agentcompose.v2.SandboxService.ResumeSandbox:input_type -> agentcompose.v2.ResumeSandboxRequest - 108, // 295: agentcompose.v2.SandboxService.ListSandboxes:input_type -> agentcompose.v2.ListSandboxesRequest - 219, // 296: agentcompose.v2.SandboxService.ListSandboxHistory:input_type -> agentcompose.v2.ListSandboxHistoryRequest - 223, // 297: agentcompose.v2.SandboxService.WatchSandbox:input_type -> agentcompose.v2.WatchSandboxRequest - 187, // 298: agentcompose.v2.DashboardService.GetDashboardOverview:input_type -> agentcompose.v2.GetDashboardOverviewRequest - 188, // 299: agentcompose.v2.DashboardService.WatchDashboardOverview:input_type -> agentcompose.v2.WatchDashboardOverviewRequest - 193, // 300: agentcompose.v2.SettingsService.GetGlobalEnv:input_type -> agentcompose.v2.GetGlobalEnvRequest - 195, // 301: agentcompose.v2.SettingsService.UpdateGlobalEnv:input_type -> agentcompose.v2.UpdateGlobalEnvRequest - 197, // 302: agentcompose.v2.SettingsService.GetCapabilityGatewayConfig:input_type -> agentcompose.v2.GetCapabilityGatewayConfigRequest - 200, // 303: agentcompose.v2.SettingsService.UpdateCapabilityGatewayConfig:input_type -> agentcompose.v2.UpdateCapabilityGatewayConfigRequest - 203, // 304: agentcompose.v2.SettingsService.ListWorkspacePresets:input_type -> agentcompose.v2.ListWorkspacePresetsRequest - 205, // 305: agentcompose.v2.SettingsService.CreateWorkspacePreset:input_type -> agentcompose.v2.CreateWorkspacePresetRequest - 206, // 306: agentcompose.v2.SettingsService.UpdateWorkspacePreset:input_type -> agentcompose.v2.UpdateWorkspacePresetRequest - 207, // 307: agentcompose.v2.SettingsService.DeleteWorkspacePreset:input_type -> agentcompose.v2.DeleteWorkspacePresetRequest - 210, // 308: agentcompose.v2.CapabilityService.GetCapabilityStatus:input_type -> agentcompose.v2.GetCapabilityStatusRequest - 212, // 309: agentcompose.v2.CapabilityService.ListCapabilitySets:input_type -> agentcompose.v2.ListCapabilitySetsRequest - 215, // 310: agentcompose.v2.CapabilityService.GetCapabilityCatalog:input_type -> agentcompose.v2.GetCapabilityCatalogRequest - 225, // 311: agentcompose.v2.LLMService.Generate:input_type -> agentcompose.v2.GenerateLLMRequest - 184, // 312: agentcompose.v2.ResourceService.ResolveID:input_type -> agentcompose.v2.ResolveResourceIDRequest - 25, // 313: agentcompose.v2.ProjectService.ValidateProject:output_type -> agentcompose.v2.ValidateProjectResponse - 27, // 314: agentcompose.v2.ProjectService.ApplyProject:output_type -> agentcompose.v2.ApplyProjectResponse - 29, // 315: agentcompose.v2.ProjectService.GetProject:output_type -> agentcompose.v2.GetProjectResponse - 31, // 316: agentcompose.v2.ProjectService.ListProjects:output_type -> agentcompose.v2.ListProjectsResponse - 33, // 317: agentcompose.v2.ProjectService.RemoveProject:output_type -> agentcompose.v2.RemoveProjectResponse - 35, // 318: agentcompose.v2.ProjectService.WatchProject:output_type -> agentcompose.v2.WatchProjectResponse - 46, // 319: agentcompose.v2.ProjectService.GetScheduler:output_type -> agentcompose.v2.GetSchedulerResponse - 50, // 320: agentcompose.v2.ProjectService.ListSchedulers:output_type -> agentcompose.v2.ListSchedulersResponse - 53, // 321: agentcompose.v2.ProjectService.ListSchedulerEvents:output_type -> agentcompose.v2.ListSchedulerEventsResponse - 55, // 322: agentcompose.v2.ProjectService.SetSchedulerEnabled:output_type -> agentcompose.v2.SetSchedulerEnabledResponse - 57, // 323: agentcompose.v2.ProjectService.SetSchedulerTriggerEnabled:output_type -> agentcompose.v2.SetSchedulerTriggerEnabledResponse - 79, // 324: agentcompose.v2.RunService.RunAgent:output_type -> agentcompose.v2.RunAgentResponse - 182, // 325: agentcompose.v2.RunService.StartRun:output_type -> agentcompose.v2.StartRunResponse - 80, // 326: agentcompose.v2.RunService.RunAgentStream:output_type -> agentcompose.v2.RunAgentStreamResponse - 82, // 327: agentcompose.v2.RunService.RunAttach:output_type -> agentcompose.v2.RunAttachResponse - 86, // 328: agentcompose.v2.RunService.GetRun:output_type -> agentcompose.v2.GetRunResponse - 88, // 329: agentcompose.v2.RunService.ListRuns:output_type -> agentcompose.v2.ListRunsResponse - 90, // 330: agentcompose.v2.RunService.FollowRunLogs:output_type -> agentcompose.v2.RunLogChunk - 92, // 331: agentcompose.v2.RunService.StopRun:output_type -> agentcompose.v2.StopRunResponse - 95, // 332: agentcompose.v2.RunService.ListRunEvents:output_type -> agentcompose.v2.ListRunEventsResponse - 97, // 333: agentcompose.v2.RunService.ListSandboxRunEvents:output_type -> agentcompose.v2.ListSandboxRunEventsResponse - 122, // 334: agentcompose.v2.ExecService.Exec:output_type -> agentcompose.v2.ExecResponse - 123, // 335: agentcompose.v2.ExecService.ExecStream:output_type -> agentcompose.v2.ExecStreamResponse - 125, // 336: agentcompose.v2.ExecService.ExecAttach:output_type -> agentcompose.v2.ExecAttachResponse - 142, // 337: agentcompose.v2.ImageService.ListImages:output_type -> agentcompose.v2.ListImagesResponse - 144, // 338: agentcompose.v2.ImageService.PullImage:output_type -> agentcompose.v2.PullImageResponse - 146, // 339: agentcompose.v2.ImageService.InspectImage:output_type -> agentcompose.v2.InspectImageResponse - 148, // 340: agentcompose.v2.ImageService.RemoveImage:output_type -> agentcompose.v2.RemoveImageResponse - 150, // 341: agentcompose.v2.ImageService.BuildImage:output_type -> agentcompose.v2.BuildImageEvent - 153, // 342: agentcompose.v2.CacheService.ListCaches:output_type -> agentcompose.v2.ListCachesResponse - 155, // 343: agentcompose.v2.CacheService.InspectCache:output_type -> agentcompose.v2.InspectCacheResponse - 157, // 344: agentcompose.v2.CacheService.PruneCaches:output_type -> agentcompose.v2.PruneCachesResponse - 159, // 345: agentcompose.v2.CacheService.RemoveCache:output_type -> agentcompose.v2.RemoveCacheResponse - 163, // 346: agentcompose.v2.VolumeService.ListVolumes:output_type -> agentcompose.v2.ListVolumesResponse - 165, // 347: agentcompose.v2.VolumeService.CreateVolume:output_type -> agentcompose.v2.CreateVolumeResponse - 167, // 348: agentcompose.v2.VolumeService.InspectVolume:output_type -> agentcompose.v2.InspectVolumeResponse - 169, // 349: agentcompose.v2.VolumeService.RemoveVolume:output_type -> agentcompose.v2.RemoveVolumeResponse - 171, // 350: agentcompose.v2.VolumeService.PruneVolumes:output_type -> agentcompose.v2.PruneVolumesResponse - 99, // 351: agentcompose.v2.SandboxService.RemoveSandbox:output_type -> agentcompose.v2.RemoveSandboxResponse - 102, // 352: agentcompose.v2.SandboxService.PruneSandboxes:output_type -> agentcompose.v2.PruneSandboxesResponse - 104, // 353: agentcompose.v2.SandboxService.GetSandboxStats:output_type -> agentcompose.v2.GetSandboxStatsResponse - 110, // 354: agentcompose.v2.SandboxService.GetSandbox:output_type -> agentcompose.v2.GetSandboxResponse - 112, // 355: agentcompose.v2.SandboxService.StopSandbox:output_type -> agentcompose.v2.StopSandboxResponse - 114, // 356: agentcompose.v2.SandboxService.ResumeSandbox:output_type -> agentcompose.v2.ResumeSandboxResponse - 109, // 357: agentcompose.v2.SandboxService.ListSandboxes:output_type -> agentcompose.v2.ListSandboxesResponse - 222, // 358: agentcompose.v2.SandboxService.ListSandboxHistory:output_type -> agentcompose.v2.ListSandboxHistoryResponse - 224, // 359: agentcompose.v2.SandboxService.WatchSandbox:output_type -> agentcompose.v2.WatchSandboxResponse - 191, // 360: agentcompose.v2.DashboardService.GetDashboardOverview:output_type -> agentcompose.v2.GetDashboardOverviewResponse - 192, // 361: agentcompose.v2.DashboardService.WatchDashboardOverview:output_type -> agentcompose.v2.WatchDashboardOverviewResponse - 194, // 362: agentcompose.v2.SettingsService.GetGlobalEnv:output_type -> agentcompose.v2.GetGlobalEnvResponse - 196, // 363: agentcompose.v2.SettingsService.UpdateGlobalEnv:output_type -> agentcompose.v2.UpdateGlobalEnvResponse - 199, // 364: agentcompose.v2.SettingsService.GetCapabilityGatewayConfig:output_type -> agentcompose.v2.GetCapabilityGatewayConfigResponse - 201, // 365: agentcompose.v2.SettingsService.UpdateCapabilityGatewayConfig:output_type -> agentcompose.v2.UpdateCapabilityGatewayConfigResponse - 204, // 366: agentcompose.v2.SettingsService.ListWorkspacePresets:output_type -> agentcompose.v2.ListWorkspacePresetsResponse - 209, // 367: agentcompose.v2.SettingsService.CreateWorkspacePreset:output_type -> agentcompose.v2.WorkspacePresetResponse - 209, // 368: agentcompose.v2.SettingsService.UpdateWorkspacePreset:output_type -> agentcompose.v2.WorkspacePresetResponse - 208, // 369: agentcompose.v2.SettingsService.DeleteWorkspacePreset:output_type -> agentcompose.v2.DeleteWorkspacePresetResponse - 211, // 370: agentcompose.v2.CapabilityService.GetCapabilityStatus:output_type -> agentcompose.v2.CapabilityStatusResponse - 214, // 371: agentcompose.v2.CapabilityService.ListCapabilitySets:output_type -> agentcompose.v2.ListCapabilitySetsResponse - 218, // 372: agentcompose.v2.CapabilityService.GetCapabilityCatalog:output_type -> agentcompose.v2.GetCapabilityCatalogResponse - 226, // 373: agentcompose.v2.LLMService.Generate:output_type -> agentcompose.v2.GenerateLLMResponse - 185, // 374: agentcompose.v2.ResourceService.ResolveID:output_type -> agentcompose.v2.ResolveResourceIDResponse - 313, // [313:375] is the sub-list for method output_type - 251, // [251:313] is the sub-list for method input_type - 251, // [251:251] is the sub-list for extension type_name - 251, // [251:251] is the sub-list for extension extendee - 0, // [0:251] is the sub-list for field type_name + 119, // 118: agentcompose.v2.GetSandboxStatsResponse.stats:type_name -> agentcompose.v2.SandboxStats + 242, // 119: agentcompose.v2.Sandbox.created_at:type_name -> google.protobuf.Timestamp + 242, // 120: agentcompose.v2.Sandbox.updated_at:type_name -> google.protobuf.Timestamp + 110, // 121: agentcompose.v2.Sandbox.tags:type_name -> agentcompose.v2.SandboxTag + 107, // 122: agentcompose.v2.Sandbox.network_state:type_name -> agentcompose.v2.SandboxNetworkState + 108, // 123: agentcompose.v2.SandboxNetworkState.attachments:type_name -> agentcompose.v2.SandboxNetworkAttachment + 109, // 124: agentcompose.v2.SandboxNetworkState.port_bindings:type_name -> agentcompose.v2.SandboxPortBinding + 242, // 125: agentcompose.v2.SandboxNetworkState.reconciled_at:type_name -> google.protobuf.Timestamp + 106, // 126: agentcompose.v2.ListSandboxesResponse.sandboxes:type_name -> agentcompose.v2.Sandbox + 106, // 127: agentcompose.v2.GetSandboxResponse.sandbox:type_name -> agentcompose.v2.Sandbox + 106, // 128: agentcompose.v2.StopSandboxResponse.sandbox:type_name -> agentcompose.v2.Sandbox + 106, // 129: agentcompose.v2.ResumeSandboxResponse.sandbox:type_name -> agentcompose.v2.Sandbox + 17, // 130: agentcompose.v2.MetricValue.status:type_name -> agentcompose.v2.MetricStatus + 118, // 131: agentcompose.v2.SandboxStats.cpu_percent:type_name -> agentcompose.v2.MetricValue + 118, // 132: agentcompose.v2.SandboxStats.memory_usage_bytes:type_name -> agentcompose.v2.MetricValue + 118, // 133: agentcompose.v2.SandboxStats.memory_limit_bytes:type_name -> agentcompose.v2.MetricValue + 118, // 134: agentcompose.v2.SandboxStats.memory_percent:type_name -> agentcompose.v2.MetricValue + 118, // 135: agentcompose.v2.SandboxStats.network_rx_bytes:type_name -> agentcompose.v2.MetricValue + 118, // 136: agentcompose.v2.SandboxStats.network_tx_bytes:type_name -> agentcompose.v2.MetricValue + 118, // 137: agentcompose.v2.SandboxStats.block_read_bytes:type_name -> agentcompose.v2.MetricValue + 118, // 138: agentcompose.v2.SandboxStats.block_write_bytes:type_name -> agentcompose.v2.MetricValue + 118, // 139: agentcompose.v2.SandboxStats.uptime_seconds:type_name -> agentcompose.v2.MetricValue + 4, // 140: agentcompose.v2.RunSummary.source:type_name -> agentcompose.v2.RunSource + 3, // 141: agentcompose.v2.RunSummary.status:type_name -> agentcompose.v2.RunStatus + 120, // 142: agentcompose.v2.RunDetail.summary:type_name -> agentcompose.v2.RunSummary + 123, // 143: agentcompose.v2.ExecRequest.selector:type_name -> agentcompose.v2.ExecSandboxSelector + 124, // 144: agentcompose.v2.ExecRequest.command:type_name -> agentcompose.v2.ExecCommand + 67, // 145: agentcompose.v2.ExecRequest.env:type_name -> agentcompose.v2.EnvVarSpec + 143, // 146: agentcompose.v2.ExecResponse.result:type_name -> agentcompose.v2.ExecResult + 11, // 147: agentcompose.v2.ExecStreamResponse.event_type:type_name -> agentcompose.v2.ExecStreamEventType + 13, // 148: agentcompose.v2.ExecStreamResponse.stream:type_name -> agentcompose.v2.StdioStream + 143, // 149: agentcompose.v2.ExecStreamResponse.result:type_name -> agentcompose.v2.ExecResult + 84, // 150: agentcompose.v2.ExecStreamResponse.transcript:type_name -> agentcompose.v2.TranscriptEvent + 129, // 151: agentcompose.v2.ExecAttachRequest.start:type_name -> agentcompose.v2.ExecAttachStart + 131, // 152: agentcompose.v2.ExecAttachRequest.stdin:type_name -> agentcompose.v2.AttachStdin + 132, // 153: agentcompose.v2.ExecAttachRequest.stdin_eof:type_name -> agentcompose.v2.AttachStdinEOF + 133, // 154: agentcompose.v2.ExecAttachRequest.resize:type_name -> agentcompose.v2.AttachResize + 134, // 155: agentcompose.v2.ExecAttachRequest.signal:type_name -> agentcompose.v2.AttachSignal + 136, // 156: agentcompose.v2.ExecAttachRequest.cancel:type_name -> agentcompose.v2.AttachCancel + 135, // 157: agentcompose.v2.ExecAttachRequest.human_message:type_name -> agentcompose.v2.AttachHumanMessage + 137, // 158: agentcompose.v2.ExecAttachResponse.started:type_name -> agentcompose.v2.AttachStarted + 138, // 159: agentcompose.v2.ExecAttachResponse.output:type_name -> agentcompose.v2.AttachOutput + 141, // 160: agentcompose.v2.ExecAttachResponse.result:type_name -> agentcompose.v2.AttachResult + 142, // 161: agentcompose.v2.ExecAttachResponse.error:type_name -> agentcompose.v2.AttachError + 139, // 162: agentcompose.v2.ExecAttachResponse.agent_event:type_name -> agentcompose.v2.AttachAgentEvent + 140, // 163: agentcompose.v2.ExecAttachResponse.agent_turn_completed:type_name -> agentcompose.v2.AttachAgentTurnCompleted + 122, // 164: agentcompose.v2.ExecAttachStart.request:type_name -> agentcompose.v2.ExecRequest + 130, // 165: agentcompose.v2.ExecAttachStart.terminal_size:type_name -> agentcompose.v2.AttachTerminalSize + 12, // 166: agentcompose.v2.ExecAttachStart.mode:type_name -> agentcompose.v2.AttachRunMode + 130, // 167: agentcompose.v2.AttachResize.terminal_size:type_name -> agentcompose.v2.AttachTerminalSize + 233, // 168: agentcompose.v2.AttachHumanMessage.metadata:type_name -> agentcompose.v2.AttachHumanMessage.MetadataEntry + 120, // 169: agentcompose.v2.AttachStarted.run:type_name -> agentcompose.v2.RunSummary + 13, // 170: agentcompose.v2.AttachOutput.stream:type_name -> agentcompose.v2.StdioStream + 84, // 171: agentcompose.v2.AttachOutput.transcript:type_name -> agentcompose.v2.TranscriptEvent + 143, // 172: agentcompose.v2.AttachResult.exec_result:type_name -> agentcompose.v2.ExecResult + 120, // 173: agentcompose.v2.AttachResult.run:type_name -> agentcompose.v2.RunSummary + 234, // 174: agentcompose.v2.AttachError.details:type_name -> agentcompose.v2.AttachError.DetailsEntry + 124, // 175: agentcompose.v2.ExecResult.command:type_name -> agentcompose.v2.ExecCommand + 14, // 176: agentcompose.v2.ListImagesRequest.store:type_name -> agentcompose.v2.ImageStoreKind + 176, // 177: agentcompose.v2.ListImagesResponse.images:type_name -> agentcompose.v2.Image + 178, // 178: agentcompose.v2.ListImagesResponse.store_status:type_name -> agentcompose.v2.ImageStoreStatus + 14, // 179: agentcompose.v2.PullImageRequest.store:type_name -> agentcompose.v2.ImageStoreKind + 177, // 180: agentcompose.v2.PullImageRequest.platform:type_name -> agentcompose.v2.ImagePlatform + 176, // 181: agentcompose.v2.PullImageResponse.image:type_name -> agentcompose.v2.Image + 16, // 182: agentcompose.v2.PullImageResponse.status:type_name -> agentcompose.v2.ImageOperationStatus + 181, // 183: agentcompose.v2.PullImageResponse.progress:type_name -> agentcompose.v2.ImagePullProgress + 14, // 184: agentcompose.v2.InspectImageRequest.store:type_name -> agentcompose.v2.ImageStoreKind + 176, // 185: agentcompose.v2.InspectImageResponse.image:type_name -> agentcompose.v2.Image + 178, // 186: agentcompose.v2.InspectImageResponse.store_status:type_name -> agentcompose.v2.ImageStoreStatus + 14, // 187: agentcompose.v2.RemoveImageRequest.store:type_name -> agentcompose.v2.ImageStoreKind + 235, // 188: agentcompose.v2.BuildImageRequest.build_args:type_name -> agentcompose.v2.BuildImageRequest.BuildArgsEntry + 14, // 189: agentcompose.v2.BuildImageRequest.store:type_name -> agentcompose.v2.ImageStoreKind + 177, // 190: agentcompose.v2.BuildImageRequest.platform:type_name -> agentcompose.v2.ImagePlatform + 16, // 191: agentcompose.v2.BuildImageEvent.status:type_name -> agentcompose.v2.ImageOperationStatus + 176, // 192: agentcompose.v2.BuildImageEvent.image:type_name -> agentcompose.v2.Image + 18, // 193: agentcompose.v2.CacheFilter.domain:type_name -> agentcompose.v2.CacheDomain + 21, // 194: agentcompose.v2.CacheFilter.status:type_name -> agentcompose.v2.CacheStatus + 154, // 195: agentcompose.v2.ListCachesRequest.filter:type_name -> agentcompose.v2.CacheFilter + 163, // 196: agentcompose.v2.ListCachesResponse.caches:type_name -> agentcompose.v2.CacheItem + 163, // 197: agentcompose.v2.InspectCacheResponse.cache:type_name -> agentcompose.v2.CacheItem + 154, // 198: agentcompose.v2.PruneCachesRequest.filter:type_name -> agentcompose.v2.CacheFilter + 163, // 199: agentcompose.v2.PruneCachesResponse.matched:type_name -> agentcompose.v2.CacheItem + 163, // 200: agentcompose.v2.PruneCachesResponse.skipped:type_name -> agentcompose.v2.CacheItem + 163, // 201: agentcompose.v2.RemoveCacheResponse.matched:type_name -> agentcompose.v2.CacheItem + 163, // 202: agentcompose.v2.RemoveCacheResponse.skipped:type_name -> agentcompose.v2.CacheItem + 18, // 203: agentcompose.v2.CacheItem.domain:type_name -> agentcompose.v2.CacheDomain + 21, // 204: agentcompose.v2.CacheItem.status:type_name -> agentcompose.v2.CacheStatus + 164, // 205: agentcompose.v2.CacheItem.references:type_name -> agentcompose.v2.CacheReference + 19, // 206: agentcompose.v2.CacheReference.policy:type_name -> agentcompose.v2.CacheReferencePolicy + 175, // 207: agentcompose.v2.ListVolumesResponse.volumes:type_name -> agentcompose.v2.Volume + 236, // 208: agentcompose.v2.CreateVolumeRequest.labels:type_name -> agentcompose.v2.CreateVolumeRequest.LabelsEntry + 237, // 209: agentcompose.v2.CreateVolumeRequest.options:type_name -> agentcompose.v2.CreateVolumeRequest.OptionsEntry + 175, // 210: agentcompose.v2.CreateVolumeResponse.volume:type_name -> agentcompose.v2.Volume + 175, // 211: agentcompose.v2.InspectVolumeResponse.volume:type_name -> agentcompose.v2.Volume + 175, // 212: agentcompose.v2.PruneVolumesResponse.matched:type_name -> agentcompose.v2.Volume + 175, // 213: agentcompose.v2.PruneVolumesResponse.removed:type_name -> agentcompose.v2.Volume + 175, // 214: agentcompose.v2.PruneVolumesResponse.skipped:type_name -> agentcompose.v2.Volume + 238, // 215: agentcompose.v2.Volume.labels:type_name -> agentcompose.v2.Volume.LabelsEntry + 239, // 216: agentcompose.v2.Volume.options:type_name -> agentcompose.v2.Volume.OptionsEntry + 14, // 217: agentcompose.v2.Image.store:type_name -> agentcompose.v2.ImageStoreKind + 15, // 218: agentcompose.v2.Image.availability_status:type_name -> agentcompose.v2.ImageAvailabilityStatus + 177, // 219: agentcompose.v2.Image.platform:type_name -> agentcompose.v2.ImagePlatform + 179, // 220: agentcompose.v2.Image.docker:type_name -> agentcompose.v2.DockerImageStatus + 180, // 221: agentcompose.v2.Image.oci:type_name -> agentcompose.v2.OCIImageStatus + 240, // 222: agentcompose.v2.Image.labels:type_name -> agentcompose.v2.Image.LabelsEntry + 14, // 223: agentcompose.v2.ImageStoreStatus.store:type_name -> agentcompose.v2.ImageStoreKind + 78, // 224: agentcompose.v2.StartRunRequest.run:type_name -> agentcompose.v2.RunAgentRequest + 120, // 225: agentcompose.v2.StartRunResponse.run:type_name -> agentcompose.v2.RunSummary + 22, // 226: agentcompose.v2.ResolveResourceIDRequest.kinds:type_name -> agentcompose.v2.ResourceKind + 189, // 227: agentcompose.v2.ResolveResourceIDResponse.targets:type_name -> agentcompose.v2.ResourceTarget + 22, // 228: agentcompose.v2.ResourceTarget.kind:type_name -> agentcompose.v2.ResourceKind + 192, // 229: agentcompose.v2.DashboardOverview.runs:type_name -> agentcompose.v2.RunOverview + 242, // 230: agentcompose.v2.DashboardOverview.updated_at:type_name -> google.protobuf.Timestamp + 193, // 231: agentcompose.v2.GetDashboardOverviewResponse.overview:type_name -> agentcompose.v2.DashboardOverview + 193, // 232: agentcompose.v2.WatchDashboardOverviewResponse.overview:type_name -> agentcompose.v2.DashboardOverview + 67, // 233: agentcompose.v2.GetGlobalEnvResponse.env:type_name -> agentcompose.v2.EnvVarSpec + 68, // 234: agentcompose.v2.UpdateGlobalEnvRequest.env:type_name -> agentcompose.v2.EnvVarUpdateSpec + 67, // 235: agentcompose.v2.UpdateGlobalEnvResponse.env:type_name -> agentcompose.v2.EnvVarSpec + 201, // 236: agentcompose.v2.GetCapabilityGatewayConfigResponse.config:type_name -> agentcompose.v2.CapabilityGatewayConfig + 201, // 237: agentcompose.v2.UpdateCapabilityGatewayConfigResponse.config:type_name -> agentcompose.v2.CapabilityGatewayConfig + 242, // 238: agentcompose.v2.WorkspacePreset.created_at:type_name -> google.protobuf.Timestamp + 242, // 239: agentcompose.v2.WorkspacePreset.updated_at:type_name -> google.protobuf.Timestamp + 205, // 240: agentcompose.v2.ListWorkspacePresetsResponse.presets:type_name -> agentcompose.v2.WorkspacePreset + 205, // 241: agentcompose.v2.WorkspacePresetResponse.preset:type_name -> agentcompose.v2.WorkspacePreset + 216, // 242: agentcompose.v2.ListCapabilitySetsResponse.capsets:type_name -> agentcompose.v2.CapabilitySet + 241, // 243: agentcompose.v2.CapabilityEndpoint.metadata:type_name -> agentcompose.v2.CapabilityEndpoint.MetadataEntry + 219, // 244: agentcompose.v2.CapabilityMethod.endpoints:type_name -> agentcompose.v2.CapabilityEndpoint + 220, // 245: agentcompose.v2.GetCapabilityCatalogResponse.methods:type_name -> agentcompose.v2.CapabilityMethod + 242, // 246: agentcompose.v2.SandboxHistoryCell.created_at:type_name -> google.protobuf.Timestamp + 242, // 247: agentcompose.v2.SandboxHistoryEvent.created_at:type_name -> google.protobuf.Timestamp + 223, // 248: agentcompose.v2.ListSandboxHistoryResponse.cells:type_name -> agentcompose.v2.SandboxHistoryCell + 224, // 249: agentcompose.v2.ListSandboxHistoryResponse.events:type_name -> agentcompose.v2.SandboxHistoryEvent + 23, // 250: agentcompose.v2.WatchSandboxResponse.event_type:type_name -> agentcompose.v2.SandboxWatchEventType + 106, // 251: agentcompose.v2.WatchSandboxResponse.sandbox:type_name -> agentcompose.v2.Sandbox + 223, // 252: agentcompose.v2.WatchSandboxResponse.cell:type_name -> agentcompose.v2.SandboxHistoryCell + 224, // 253: agentcompose.v2.WatchSandboxResponse.event:type_name -> agentcompose.v2.SandboxHistoryEvent + 13, // 254: agentcompose.v2.WatchSandboxResponse.stream:type_name -> agentcompose.v2.StdioStream + 24, // 255: agentcompose.v2.ProjectService.ValidateProject:input_type -> agentcompose.v2.ValidateProjectRequest + 26, // 256: agentcompose.v2.ProjectService.ApplyProject:input_type -> agentcompose.v2.ApplyProjectRequest + 28, // 257: agentcompose.v2.ProjectService.GetProject:input_type -> agentcompose.v2.GetProjectRequest + 30, // 258: agentcompose.v2.ProjectService.ListProjects:input_type -> agentcompose.v2.ListProjectsRequest + 32, // 259: agentcompose.v2.ProjectService.RemoveProject:input_type -> agentcompose.v2.RemoveProjectRequest + 34, // 260: agentcompose.v2.ProjectService.WatchProject:input_type -> agentcompose.v2.WatchProjectRequest + 45, // 261: agentcompose.v2.ProjectService.GetScheduler:input_type -> agentcompose.v2.GetSchedulerRequest + 48, // 262: agentcompose.v2.ProjectService.ListSchedulers:input_type -> agentcompose.v2.ListSchedulersRequest + 51, // 263: agentcompose.v2.ProjectService.ListSchedulerEvents:input_type -> agentcompose.v2.ListSchedulerEventsRequest + 54, // 264: agentcompose.v2.ProjectService.SetSchedulerEnabled:input_type -> agentcompose.v2.SetSchedulerEnabledRequest + 56, // 265: agentcompose.v2.ProjectService.SetSchedulerTriggerEnabled:input_type -> agentcompose.v2.SetSchedulerTriggerEnabledRequest + 78, // 266: agentcompose.v2.RunService.RunAgent:input_type -> agentcompose.v2.RunAgentRequest + 184, // 267: agentcompose.v2.RunService.StartRun:input_type -> agentcompose.v2.StartRunRequest + 78, // 268: agentcompose.v2.RunService.RunAgentStream:input_type -> agentcompose.v2.RunAgentRequest + 81, // 269: agentcompose.v2.RunService.RunAttach:input_type -> agentcompose.v2.RunAttachRequest + 85, // 270: agentcompose.v2.RunService.GetRun:input_type -> agentcompose.v2.GetRunRequest + 87, // 271: agentcompose.v2.RunService.ListRuns:input_type -> agentcompose.v2.ListRunsRequest + 89, // 272: agentcompose.v2.RunService.FollowRunLogs:input_type -> agentcompose.v2.FollowRunLogsRequest + 91, // 273: agentcompose.v2.RunService.StopRun:input_type -> agentcompose.v2.StopRunRequest + 93, // 274: agentcompose.v2.RunService.ListRunEvents:input_type -> agentcompose.v2.ListRunEventsRequest + 96, // 275: agentcompose.v2.RunService.ListSandboxRunEvents:input_type -> agentcompose.v2.ListSandboxRunEventsRequest + 122, // 276: agentcompose.v2.ExecService.Exec:input_type -> agentcompose.v2.ExecRequest + 122, // 277: agentcompose.v2.ExecService.ExecStream:input_type -> agentcompose.v2.ExecRequest + 127, // 278: agentcompose.v2.ExecService.ExecAttach:input_type -> agentcompose.v2.ExecAttachRequest + 144, // 279: agentcompose.v2.ImageService.ListImages:input_type -> agentcompose.v2.ListImagesRequest + 146, // 280: agentcompose.v2.ImageService.PullImage:input_type -> agentcompose.v2.PullImageRequest + 148, // 281: agentcompose.v2.ImageService.InspectImage:input_type -> agentcompose.v2.InspectImageRequest + 150, // 282: agentcompose.v2.ImageService.RemoveImage:input_type -> agentcompose.v2.RemoveImageRequest + 152, // 283: agentcompose.v2.ImageService.BuildImage:input_type -> agentcompose.v2.BuildImageRequest + 155, // 284: agentcompose.v2.CacheService.ListCaches:input_type -> agentcompose.v2.ListCachesRequest + 157, // 285: agentcompose.v2.CacheService.InspectCache:input_type -> agentcompose.v2.InspectCacheRequest + 159, // 286: agentcompose.v2.CacheService.PruneCaches:input_type -> agentcompose.v2.PruneCachesRequest + 161, // 287: agentcompose.v2.CacheService.RemoveCache:input_type -> agentcompose.v2.RemoveCacheRequest + 165, // 288: agentcompose.v2.VolumeService.ListVolumes:input_type -> agentcompose.v2.ListVolumesRequest + 167, // 289: agentcompose.v2.VolumeService.CreateVolume:input_type -> agentcompose.v2.CreateVolumeRequest + 169, // 290: agentcompose.v2.VolumeService.InspectVolume:input_type -> agentcompose.v2.InspectVolumeRequest + 171, // 291: agentcompose.v2.VolumeService.RemoveVolume:input_type -> agentcompose.v2.RemoveVolumeRequest + 173, // 292: agentcompose.v2.VolumeService.PruneVolumes:input_type -> agentcompose.v2.PruneVolumesRequest + 98, // 293: agentcompose.v2.SandboxService.RemoveSandbox:input_type -> agentcompose.v2.RemoveSandboxRequest + 100, // 294: agentcompose.v2.SandboxService.PruneSandboxes:input_type -> agentcompose.v2.PruneSandboxesRequest + 103, // 295: agentcompose.v2.SandboxService.GetSandboxStats:input_type -> agentcompose.v2.GetSandboxStatsRequest + 105, // 296: agentcompose.v2.SandboxService.GetSandbox:input_type -> agentcompose.v2.GetSandboxRequest + 114, // 297: agentcompose.v2.SandboxService.StopSandbox:input_type -> agentcompose.v2.StopSandboxRequest + 116, // 298: agentcompose.v2.SandboxService.ResumeSandbox:input_type -> agentcompose.v2.ResumeSandboxRequest + 111, // 299: agentcompose.v2.SandboxService.ListSandboxes:input_type -> agentcompose.v2.ListSandboxesRequest + 222, // 300: agentcompose.v2.SandboxService.ListSandboxHistory:input_type -> agentcompose.v2.ListSandboxHistoryRequest + 226, // 301: agentcompose.v2.SandboxService.WatchSandbox:input_type -> agentcompose.v2.WatchSandboxRequest + 190, // 302: agentcompose.v2.DashboardService.GetDashboardOverview:input_type -> agentcompose.v2.GetDashboardOverviewRequest + 191, // 303: agentcompose.v2.DashboardService.WatchDashboardOverview:input_type -> agentcompose.v2.WatchDashboardOverviewRequest + 196, // 304: agentcompose.v2.SettingsService.GetGlobalEnv:input_type -> agentcompose.v2.GetGlobalEnvRequest + 198, // 305: agentcompose.v2.SettingsService.UpdateGlobalEnv:input_type -> agentcompose.v2.UpdateGlobalEnvRequest + 200, // 306: agentcompose.v2.SettingsService.GetCapabilityGatewayConfig:input_type -> agentcompose.v2.GetCapabilityGatewayConfigRequest + 203, // 307: agentcompose.v2.SettingsService.UpdateCapabilityGatewayConfig:input_type -> agentcompose.v2.UpdateCapabilityGatewayConfigRequest + 206, // 308: agentcompose.v2.SettingsService.ListWorkspacePresets:input_type -> agentcompose.v2.ListWorkspacePresetsRequest + 208, // 309: agentcompose.v2.SettingsService.CreateWorkspacePreset:input_type -> agentcompose.v2.CreateWorkspacePresetRequest + 209, // 310: agentcompose.v2.SettingsService.UpdateWorkspacePreset:input_type -> agentcompose.v2.UpdateWorkspacePresetRequest + 210, // 311: agentcompose.v2.SettingsService.DeleteWorkspacePreset:input_type -> agentcompose.v2.DeleteWorkspacePresetRequest + 213, // 312: agentcompose.v2.CapabilityService.GetCapabilityStatus:input_type -> agentcompose.v2.GetCapabilityStatusRequest + 215, // 313: agentcompose.v2.CapabilityService.ListCapabilitySets:input_type -> agentcompose.v2.ListCapabilitySetsRequest + 218, // 314: agentcompose.v2.CapabilityService.GetCapabilityCatalog:input_type -> agentcompose.v2.GetCapabilityCatalogRequest + 228, // 315: agentcompose.v2.LLMService.Generate:input_type -> agentcompose.v2.GenerateLLMRequest + 187, // 316: agentcompose.v2.ResourceService.ResolveID:input_type -> agentcompose.v2.ResolveResourceIDRequest + 25, // 317: agentcompose.v2.ProjectService.ValidateProject:output_type -> agentcompose.v2.ValidateProjectResponse + 27, // 318: agentcompose.v2.ProjectService.ApplyProject:output_type -> agentcompose.v2.ApplyProjectResponse + 29, // 319: agentcompose.v2.ProjectService.GetProject:output_type -> agentcompose.v2.GetProjectResponse + 31, // 320: agentcompose.v2.ProjectService.ListProjects:output_type -> agentcompose.v2.ListProjectsResponse + 33, // 321: agentcompose.v2.ProjectService.RemoveProject:output_type -> agentcompose.v2.RemoveProjectResponse + 35, // 322: agentcompose.v2.ProjectService.WatchProject:output_type -> agentcompose.v2.WatchProjectResponse + 46, // 323: agentcompose.v2.ProjectService.GetScheduler:output_type -> agentcompose.v2.GetSchedulerResponse + 50, // 324: agentcompose.v2.ProjectService.ListSchedulers:output_type -> agentcompose.v2.ListSchedulersResponse + 53, // 325: agentcompose.v2.ProjectService.ListSchedulerEvents:output_type -> agentcompose.v2.ListSchedulerEventsResponse + 55, // 326: agentcompose.v2.ProjectService.SetSchedulerEnabled:output_type -> agentcompose.v2.SetSchedulerEnabledResponse + 57, // 327: agentcompose.v2.ProjectService.SetSchedulerTriggerEnabled:output_type -> agentcompose.v2.SetSchedulerTriggerEnabledResponse + 79, // 328: agentcompose.v2.RunService.RunAgent:output_type -> agentcompose.v2.RunAgentResponse + 185, // 329: agentcompose.v2.RunService.StartRun:output_type -> agentcompose.v2.StartRunResponse + 80, // 330: agentcompose.v2.RunService.RunAgentStream:output_type -> agentcompose.v2.RunAgentStreamResponse + 82, // 331: agentcompose.v2.RunService.RunAttach:output_type -> agentcompose.v2.RunAttachResponse + 86, // 332: agentcompose.v2.RunService.GetRun:output_type -> agentcompose.v2.GetRunResponse + 88, // 333: agentcompose.v2.RunService.ListRuns:output_type -> agentcompose.v2.ListRunsResponse + 90, // 334: agentcompose.v2.RunService.FollowRunLogs:output_type -> agentcompose.v2.RunLogChunk + 92, // 335: agentcompose.v2.RunService.StopRun:output_type -> agentcompose.v2.StopRunResponse + 95, // 336: agentcompose.v2.RunService.ListRunEvents:output_type -> agentcompose.v2.ListRunEventsResponse + 97, // 337: agentcompose.v2.RunService.ListSandboxRunEvents:output_type -> agentcompose.v2.ListSandboxRunEventsResponse + 125, // 338: agentcompose.v2.ExecService.Exec:output_type -> agentcompose.v2.ExecResponse + 126, // 339: agentcompose.v2.ExecService.ExecStream:output_type -> agentcompose.v2.ExecStreamResponse + 128, // 340: agentcompose.v2.ExecService.ExecAttach:output_type -> agentcompose.v2.ExecAttachResponse + 145, // 341: agentcompose.v2.ImageService.ListImages:output_type -> agentcompose.v2.ListImagesResponse + 147, // 342: agentcompose.v2.ImageService.PullImage:output_type -> agentcompose.v2.PullImageResponse + 149, // 343: agentcompose.v2.ImageService.InspectImage:output_type -> agentcompose.v2.InspectImageResponse + 151, // 344: agentcompose.v2.ImageService.RemoveImage:output_type -> agentcompose.v2.RemoveImageResponse + 153, // 345: agentcompose.v2.ImageService.BuildImage:output_type -> agentcompose.v2.BuildImageEvent + 156, // 346: agentcompose.v2.CacheService.ListCaches:output_type -> agentcompose.v2.ListCachesResponse + 158, // 347: agentcompose.v2.CacheService.InspectCache:output_type -> agentcompose.v2.InspectCacheResponse + 160, // 348: agentcompose.v2.CacheService.PruneCaches:output_type -> agentcompose.v2.PruneCachesResponse + 162, // 349: agentcompose.v2.CacheService.RemoveCache:output_type -> agentcompose.v2.RemoveCacheResponse + 166, // 350: agentcompose.v2.VolumeService.ListVolumes:output_type -> agentcompose.v2.ListVolumesResponse + 168, // 351: agentcompose.v2.VolumeService.CreateVolume:output_type -> agentcompose.v2.CreateVolumeResponse + 170, // 352: agentcompose.v2.VolumeService.InspectVolume:output_type -> agentcompose.v2.InspectVolumeResponse + 172, // 353: agentcompose.v2.VolumeService.RemoveVolume:output_type -> agentcompose.v2.RemoveVolumeResponse + 174, // 354: agentcompose.v2.VolumeService.PruneVolumes:output_type -> agentcompose.v2.PruneVolumesResponse + 99, // 355: agentcompose.v2.SandboxService.RemoveSandbox:output_type -> agentcompose.v2.RemoveSandboxResponse + 102, // 356: agentcompose.v2.SandboxService.PruneSandboxes:output_type -> agentcompose.v2.PruneSandboxesResponse + 104, // 357: agentcompose.v2.SandboxService.GetSandboxStats:output_type -> agentcompose.v2.GetSandboxStatsResponse + 113, // 358: agentcompose.v2.SandboxService.GetSandbox:output_type -> agentcompose.v2.GetSandboxResponse + 115, // 359: agentcompose.v2.SandboxService.StopSandbox:output_type -> agentcompose.v2.StopSandboxResponse + 117, // 360: agentcompose.v2.SandboxService.ResumeSandbox:output_type -> agentcompose.v2.ResumeSandboxResponse + 112, // 361: agentcompose.v2.SandboxService.ListSandboxes:output_type -> agentcompose.v2.ListSandboxesResponse + 225, // 362: agentcompose.v2.SandboxService.ListSandboxHistory:output_type -> agentcompose.v2.ListSandboxHistoryResponse + 227, // 363: agentcompose.v2.SandboxService.WatchSandbox:output_type -> agentcompose.v2.WatchSandboxResponse + 194, // 364: agentcompose.v2.DashboardService.GetDashboardOverview:output_type -> agentcompose.v2.GetDashboardOverviewResponse + 195, // 365: agentcompose.v2.DashboardService.WatchDashboardOverview:output_type -> agentcompose.v2.WatchDashboardOverviewResponse + 197, // 366: agentcompose.v2.SettingsService.GetGlobalEnv:output_type -> agentcompose.v2.GetGlobalEnvResponse + 199, // 367: agentcompose.v2.SettingsService.UpdateGlobalEnv:output_type -> agentcompose.v2.UpdateGlobalEnvResponse + 202, // 368: agentcompose.v2.SettingsService.GetCapabilityGatewayConfig:output_type -> agentcompose.v2.GetCapabilityGatewayConfigResponse + 204, // 369: agentcompose.v2.SettingsService.UpdateCapabilityGatewayConfig:output_type -> agentcompose.v2.UpdateCapabilityGatewayConfigResponse + 207, // 370: agentcompose.v2.SettingsService.ListWorkspacePresets:output_type -> agentcompose.v2.ListWorkspacePresetsResponse + 212, // 371: agentcompose.v2.SettingsService.CreateWorkspacePreset:output_type -> agentcompose.v2.WorkspacePresetResponse + 212, // 372: agentcompose.v2.SettingsService.UpdateWorkspacePreset:output_type -> agentcompose.v2.WorkspacePresetResponse + 211, // 373: agentcompose.v2.SettingsService.DeleteWorkspacePreset:output_type -> agentcompose.v2.DeleteWorkspacePresetResponse + 214, // 374: agentcompose.v2.CapabilityService.GetCapabilityStatus:output_type -> agentcompose.v2.CapabilityStatusResponse + 217, // 375: agentcompose.v2.CapabilityService.ListCapabilitySets:output_type -> agentcompose.v2.ListCapabilitySetsResponse + 221, // 376: agentcompose.v2.CapabilityService.GetCapabilityCatalog:output_type -> agentcompose.v2.GetCapabilityCatalogResponse + 229, // 377: agentcompose.v2.LLMService.Generate:output_type -> agentcompose.v2.GenerateLLMResponse + 188, // 378: agentcompose.v2.ResourceService.ResolveID:output_type -> agentcompose.v2.ResolveResourceIDResponse + 317, // [317:379] is the sub-list for method output_type + 255, // [255:317] is the sub-list for method input_type + 255, // [255:255] is the sub-list for extension type_name + 255, // [255:255] is the sub-list for extension extendee + 0, // [0:255] is the sub-list for field type_name } func init() { file_agentcompose_v2_agentcompose_proto_init() } @@ -17730,13 +18011,13 @@ func file_agentcompose_v2_agentcompose_proto_init() { (*RunAttachResponse_Result)(nil), (*RunAttachResponse_Error)(nil), } - file_agentcompose_v2_agentcompose_proto_msgTypes[91].OneofWrappers = []any{} - file_agentcompose_v2_agentcompose_proto_msgTypes[95].OneofWrappers = []any{ + file_agentcompose_v2_agentcompose_proto_msgTypes[94].OneofWrappers = []any{} + file_agentcompose_v2_agentcompose_proto_msgTypes[98].OneofWrappers = []any{ (*ExecRequest_SandboxId)(nil), (*ExecRequest_RunId)(nil), (*ExecRequest_Selector)(nil), } - file_agentcompose_v2_agentcompose_proto_msgTypes[100].OneofWrappers = []any{ + file_agentcompose_v2_agentcompose_proto_msgTypes[103].OneofWrappers = []any{ (*ExecAttachRequest_Start)(nil), (*ExecAttachRequest_Stdin)(nil), (*ExecAttachRequest_StdinEof)(nil), @@ -17745,7 +18026,7 @@ func file_agentcompose_v2_agentcompose_proto_init() { (*ExecAttachRequest_Cancel)(nil), (*ExecAttachRequest_HumanMessage)(nil), } - file_agentcompose_v2_agentcompose_proto_msgTypes[101].OneofWrappers = []any{ + file_agentcompose_v2_agentcompose_proto_msgTypes[104].OneofWrappers = []any{ (*ExecAttachResponse_Started)(nil), (*ExecAttachResponse_Output)(nil), (*ExecAttachResponse_Result)(nil), @@ -17753,14 +18034,14 @@ func file_agentcompose_v2_agentcompose_proto_init() { (*ExecAttachResponse_AgentEvent)(nil), (*ExecAttachResponse_AgentTurnCompleted)(nil), } - file_agentcompose_v2_agentcompose_proto_msgTypes[176].OneofWrappers = []any{} + file_agentcompose_v2_agentcompose_proto_msgTypes[179].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_agentcompose_v2_agentcompose_proto_rawDesc), len(file_agentcompose_v2_agentcompose_proto_rawDesc)), NumEnums: 24, - NumMessages: 215, + NumMessages: 218, NumExtensions: 0, NumServices: 12, }, diff --git a/proto/agentcompose/v2/agentcompose.proto b/proto/agentcompose/v2/agentcompose.proto index 4aeae0c53..50e8c282f 100644 --- a/proto/agentcompose/v2/agentcompose.proto +++ b/proto/agentcompose/v2/agentcompose.proto @@ -547,10 +547,10 @@ message ProjectSpec { reserved 3; reserved "workspace"; repeated AgentSpec agents = 4; - NetworkSpec network = 5; repeated ProjectVolumeSpec volumes = 6; repeated NamedWorkspaceSpec workspaces = 7; repeated MCPServerSpec mcp_servers = 8; + repeated NamedNetworkSpec networks = 9; } message NamedWorkspaceSpec { @@ -575,6 +575,9 @@ message AgentSpec { repeated MCPServerSpec mcp_servers = 14; repeated SkillSpec skills = 15; AgentStatus status = 16; + repeated string networks = 17; + repeated string expose = 18; + repeated string ports = 19; } message MCPServerSpec { @@ -635,8 +638,9 @@ message WorkspaceSpec { string name = 5; } -message NetworkSpec { - string mode = 1; +message NamedNetworkSpec { + string name = 1; + string driver = 2; } message SchedulerSpec { @@ -928,6 +932,29 @@ message Sandbox { uint32 cell_count = 14; uint32 event_count = 15; string notebook_url = 16; + SandboxNetworkState network_state = 17; +} + +message SandboxNetworkState { + string mode = 1; + repeated SandboxNetworkAttachment attachments = 2; + repeated SandboxPortBinding port_bindings = 3; + google.protobuf.Timestamp reconciled_at = 4; +} + +message SandboxNetworkAttachment { + string logical_name = 1; + string runtime_name = 2; + string network_id = 3; + repeated string aliases = 4; + string ipv4_address = 5; + bool primary = 6; +} + +message SandboxPortBinding { + string container_port = 1; + string host_ip = 2; + string host_port = 3; } message SandboxTag { From 17cf62308ac0af32ed84d1574895de2111a316e5 Mon Sep 17 00:00:00 2001 From: "chencong.fu@chaitin.com" Date: Wed, 15 Jul 2026 20:29:35 +0800 Subject: [PATCH 2/2] fix(networks): tolerate concurrent Docker cleanup --- pkg/agentcompose/adapters/project_networks.go | 11 ++- .../adapters/project_networks_test.go | 68 ++++++++++++++++--- pkg/compose/network_config_test.go | 10 +++ 3 files changed, 78 insertions(+), 11 deletions(-) diff --git a/pkg/agentcompose/adapters/project_networks.go b/pkg/agentcompose/adapters/project_networks.go index 7a898e34a..f2cd60c6a 100644 --- a/pkg/agentcompose/adapters/project_networks.go +++ b/pkg/agentcompose/adapters/project_networks.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + cerrdefs "github.com/containerd/errdefs" containerapi "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" networkapi "github.com/docker/docker/api/types/network" @@ -57,6 +58,9 @@ func (c *DockerProjectNetworkCleaner) CleanupProjectNetworks(ctx context.Context for _, summary := range summaries { inspect, err := dockerClient.NetworkInspect(ctx, summary.ID, networkapi.InspectOptions{}) if err != nil { + if cerrdefs.IsNotFound(err) { + continue + } return fmt.Errorf("inspect Docker network %s: %w", summary.ID, err) } if inspect.Labels[networks.ManagedLabel] != "true" || inspect.Labels[networks.ResourceLabel] != networks.ProjectNetworkResource || inspect.Labels[networks.ProjectIDLabel] != projectID { @@ -65,6 +69,9 @@ func (c *DockerProjectNetworkCleaner) CleanupProjectNetworks(ctx context.Context for containerID := range inspect.Containers { containerInfo, err := dockerClient.ContainerInspect(ctx, containerID) if err != nil { + if cerrdefs.IsNotFound(err) { + continue + } return fmt.Errorf("inspect endpoint container %s on network %s: %w", containerID, inspect.Name, err) } labels := map[string]string(nil) @@ -74,11 +81,11 @@ func (c *DockerProjectNetworkCleaner) CleanupProjectNetworks(ctx context.Context if labels[dockerSandboxProjectLabel] != projectID || labels[dockerSandboxDriverLabel] != "docker" { return fmt.Errorf("refuse to disconnect unknown endpoint container %s from project network %s", containerID, inspect.Name) } - if err := dockerClient.NetworkDisconnect(ctx, inspect.ID, containerID, false); err != nil { + if err := dockerClient.NetworkDisconnect(ctx, inspect.ID, containerID, false); err != nil && !cerrdefs.IsNotFound(err) { return fmt.Errorf("disconnect container %s from project network %s: %w", containerID, inspect.Name, err) } } - if err := dockerClient.NetworkRemove(ctx, inspect.ID); err != nil { + if err := dockerClient.NetworkRemove(ctx, inspect.ID); err != nil && !cerrdefs.IsNotFound(err) { return fmt.Errorf("remove project network %s: %w", inspect.Name, err) } } diff --git a/pkg/agentcompose/adapters/project_networks_test.go b/pkg/agentcompose/adapters/project_networks_test.go index 52b33af7f..44f254bf9 100644 --- a/pkg/agentcompose/adapters/project_networks_test.go +++ b/pkg/agentcompose/adapters/project_networks_test.go @@ -17,12 +17,15 @@ import ( ) type projectNetworkClientFake struct { - networks []networkapi.Summary - inspect networkapi.Inspect - container containerapi.InspectResponse - containerErr error - disconnected []string - removed []string + networks []networkapi.Summary + inspect networkapi.Inspect + inspectErr error + container containerapi.InspectResponse + containerErr error + disconnectErr error + removeErr error + disconnected []string + removed []string } func (f *projectNetworkClientFake) NetworkList(context.Context, networkapi.ListOptions) ([]networkapi.Summary, error) { @@ -30,7 +33,7 @@ func (f *projectNetworkClientFake) NetworkList(context.Context, networkapi.ListO } func (f *projectNetworkClientFake) NetworkInspect(context.Context, string, networkapi.InspectOptions) (networkapi.Inspect, error) { - return f.inspect, nil + return f.inspect, f.inspectErr } func (f *projectNetworkClientFake) ContainerInspect(context.Context, string) (containerapi.InspectResponse, error) { @@ -39,12 +42,12 @@ func (f *projectNetworkClientFake) ContainerInspect(context.Context, string) (co func (f *projectNetworkClientFake) NetworkDisconnect(_ context.Context, networkID, containerID string, _ bool) error { f.disconnected = append(f.disconnected, networkID+":"+containerID) - return nil + return f.disconnectErr } func (f *projectNetworkClientFake) NetworkRemove(_ context.Context, networkID string) error { f.removed = append(f.removed, networkID) - return nil + return f.removeErr } func (*projectNetworkClientFake) Close() error { return nil } @@ -107,10 +110,57 @@ func TestDockerProjectNetworkCleanerReportsClientFailure(t *testing.T) { } } +func TestDockerProjectNetworkCleanerIgnoresConcurrentNotFound(t *testing.T) { + newFake := func() *projectNetworkClientFake { + return &projectNetworkClientFake{ + networks: []networkapi.Summary{{ID: "network-1"}}, + inspect: networkapi.Inspect{ + ID: "network-1", + Name: "project_frontend", + Labels: map[string]string{ + networks.ManagedLabel: "true", + networks.ResourceLabel: networks.ProjectNetworkResource, + networks.ProjectIDLabel: "project-1", + }, + Containers: map[string]networkapi.EndpointResource{"container-1": {}}, + }, + container: containerapi.InspectResponse{Config: &containerapi.Config{Labels: map[string]string{ + dockerSandboxProjectLabel: "project-1", + dockerSandboxDriverLabel: "docker", + }}}, + } + } + tests := []struct { + name string + configure func(*projectNetworkClientFake) + wantDisconnected int + wantRemoved int + }{ + {name: "network already removed", configure: func(fake *projectNetworkClientFake) { fake.inspectErr = cerrdefs.ErrNotFound }}, + {name: "container already removed", configure: func(fake *projectNetworkClientFake) { fake.containerErr = cerrdefs.ErrNotFound }, wantRemoved: 1}, + {name: "endpoint already disconnected", configure: func(fake *projectNetworkClientFake) { fake.disconnectErr = cerrdefs.ErrNotFound }, wantDisconnected: 1, wantRemoved: 1}, + {name: "network removed after inspect", configure: func(fake *projectNetworkClientFake) { fake.removeErr = cerrdefs.ErrNotFound }, wantDisconnected: 1, wantRemoved: 1}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + fake := newFake() + tc.configure(fake) + cleaner := &DockerProjectNetworkCleaner{newClient: func() (dockerProjectNetworkClient, error) { return fake, nil }} + if err := cleaner.CleanupProjectNetworks(context.Background(), "project-1"); err != nil { + t.Fatalf("CleanupProjectNetworks returned error: %v", err) + } + if len(fake.disconnected) != tc.wantDisconnected || len(fake.removed) != tc.wantRemoved { + t.Fatalf("cleanup calls = disconnected %#v removed %#v", fake.disconnected, fake.removed) + } + }) + } +} + func TestIntegrationDockerProjectNetworkCleanerWorkflow(t *testing.T) { TestDockerProjectNetworkCleanerRemovesOwnedEndpoints(t) TestDockerProjectNetworkCleanerRefusesUnknownEndpoint(t) TestDockerProjectNetworkCleanerReportsClientFailure(t) + TestDockerProjectNetworkCleanerIgnoresConcurrentNotFound(t) } func TestE2EDockerProjectNetworkCleanerWorkflow(t *testing.T) { diff --git a/pkg/compose/network_config_test.go b/pkg/compose/network_config_test.go index d7ec957df..b6f42da84 100644 --- a/pkg/compose/network_config_test.go +++ b/pkg/compose/network_config_test.go @@ -147,6 +147,16 @@ func TestNormalizeNetworkValidation(t *testing.T) { spec: &ProjectSpec{Name: "demo", Agents: map[string]AgentSpec{"worker": {Ports: []string{"localhost:8080:80"}}}}, want: `host IP "localhost" is invalid`, }, + { + name: "unbracketed IPv6 published host", + spec: &ProjectSpec{Name: "demo", Agents: map[string]AgentSpec{"worker": {Ports: []string{"::1:8080:80"}}}}, + want: "published port must be container, host:container, or host_ip:host:container", + }, + { + name: "bracketed IPv6 published host", + spec: &ProjectSpec{Name: "demo", Agents: map[string]AgentSpec{"worker": {Ports: []string{"[::1]:8080:80"}}}}, + want: "published port must be container, host:container, or host_ip:host:container", + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) {