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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/manifest/field_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ var writeOnlyFields = map[ResourceKind][]string{
"sourceAgent", "targetAgent", // manifest references resolved to UUIDs at create time
"settings", // per-user grants and rate limits — JSONB write-only
},
KindWorkstation: {
// SSH credential fields — stored AES-256-GCM encrypted, never returned by API.
"privateKey", "knownHostsFingerprint", "host", "port", "user", "connectTimeoutSec",
// Manifest-only orchestration fields managed via side-effects (permissions.add, linkAgent).
"allowlist", "agents",
},
}

// WriteOnlyFields returns the write-only fields for a resource kind.
Expand Down
2 changes: 2 additions & 0 deletions internal/manifest/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (
KindSecureCLI ResourceKind = "SecureCLI"
KindSecureCLIGrant ResourceKind = "SecureCLIGrant"
KindAgentLink ResourceKind = "AgentLink"
KindWorkstation ResourceKind = "Workstation"
)

// Resource is a generic managed resource with kind + name + arbitrary spec.
Expand Down Expand Up @@ -75,5 +76,6 @@ func ApplyOrder() []ResourceKind {
KindSecureCLIGrant, // depends on SecureCLI + Agent
KindAgentTeam, // no strict deps
KindAgentLink, // depends on Agent (source + target); placed after AgentTeam to coexist with team-managed links
KindWorkstation, // depends on Agent (for links); after AgentLink so agents exist
}
}
1 change: 1 addition & 0 deletions internal/manifest/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var validKinds = map[ResourceKind]bool{
KindSecureCLI: true,
KindSecureCLIGrant: true,
KindAgentLink: true,
KindWorkstation: true,
}

// Validate checks the manifest for structural errors.
Expand Down
26 changes: 26 additions & 0 deletions internal/provider/goclaw/list_all.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,32 @@ func (p *Provider) listAllAgentLinks(ctx context.Context) ([]reconciler.Resource
return infos, nil
}

// listAllWorkstations returns ResourceInfo for every workstation in GoClaw via WS RPC.
func (p *Provider) listAllWorkstations(ctx context.Context) ([]reconciler.ResourceInfo, error) {
if err := p.ensureWS(ctx); err != nil {
return nil, fmt.Errorf("ws connect for workstations: %w", err)
}
payload, err := p.ws.Call(ctx, "workstations.list", nil)
if err != nil {
return nil, fmt.Errorf("workstations.list: %w", err)
}
var resp struct {
Workstations []map[string]any `json:"workstations"`
}
if err := json.Unmarshal(payload, &resp); err != nil {
return nil, fmt.Errorf("parse workstations.list response: %w", err)
}
infos := make([]reconciler.ResourceInfo, 0, len(resp.Workstations))
for _, ws := range resp.Workstations {
infos = append(infos, reconciler.ResourceInfo{
Kind: manifest.KindWorkstation,
Name: strVal(ws, "workstationKey"),
CreatedBy: strVal(ws, "createdBy"),
})
}
return infos, nil
}

// listAllTenants returns ResourceInfo for every tenant in GoClaw.
func (p *Provider) listAllTenants(ctx context.Context) ([]reconciler.ResourceInfo, error) {
data, err := p.http.Get(ctx, "/v1/tenants")
Expand Down
10 changes: 10 additions & 0 deletions internal/provider/goclaw/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ func (p *Provider) Observe(ctx context.Context, kind manifest.ResourceKind, key
return p.observeSecureCLIGrant(ctx, key)
case manifest.KindAgentLink:
return p.observeAgentLink(ctx, key)
case manifest.KindWorkstation:
return p.observeWorkstation(ctx, key)
default:
return nil, fmt.Errorf("observe not implemented for kind %s", kind)
}
Expand Down Expand Up @@ -242,6 +244,8 @@ func (p *Provider) Create(ctx context.Context, kind manifest.ResourceKind, key s
return p.createSecureCLIGrant(ctx, key, spec)
case manifest.KindAgentLink:
return p.createAgentLink(ctx, key, spec)
case manifest.KindWorkstation:
return p.createWorkstation(ctx, key, spec)
default:
return fmt.Errorf("create not implemented for kind %s", kind)
}
Expand Down Expand Up @@ -280,6 +284,8 @@ func (p *Provider) Update(ctx context.Context, kind manifest.ResourceKind, key s
return p.updateSecureCLIGrant(ctx, key, spec)
case manifest.KindAgentLink:
return p.updateAgentLink(ctx, key, spec)
case manifest.KindWorkstation:
return p.updateWorkstation(ctx, key, spec)
default:
return fmt.Errorf("update not implemented for kind %s", kind)
}
Expand Down Expand Up @@ -318,6 +324,8 @@ func (p *Provider) Delete(ctx context.Context, kind manifest.ResourceKind, key s
return p.deleteAgentLink(ctx, key)
case manifest.KindSkill:
return p.deleteSkill(ctx, key)
case manifest.KindWorkstation:
return p.deleteWorkstation(ctx, key)
default:
return fmt.Errorf("delete not implemented for kind %s", kind)
}
Expand Down Expand Up @@ -346,6 +354,8 @@ func (p *Provider) ListAll(ctx context.Context, kind manifest.ResourceKind) ([]r
return p.listAllSecureCLIs(ctx)
case manifest.KindAgentLink:
return p.listAllAgentLinks(ctx)
case manifest.KindWorkstation:
return p.listAllWorkstations(ctx)
case manifest.KindBuiltinToolConfig, manifest.KindSkillConfig, manifest.KindSystemConfig, manifest.KindMCPCredentials, manifest.KindSecureCLIGrant:
return nil, nil // per-tenant configs and child resources not enumerable for prune
default:
Expand Down
Loading
Loading