feat(agentic): expose workspace state model

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 12:50:10 +00:00
parent c01a694455
commit c27c531805
2 changed files with 22 additions and 19 deletions

View file

@ -10,14 +10,17 @@ import (
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// state := agentic.PlanState{Key: "pattern", Value: "observer", Category: "general"}
type PlanState struct {
// state := agentic.WorkspaceState{Key: "pattern", Value: "observer", Category: "general"}
type WorkspaceState struct {
Key string `json:"key"`
Value any `json:"value"`
Category string `json:"category,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
// PlanState is kept as a compatibility alias for older callers.
type PlanState = WorkspaceState
// input := agentic.StateSetInput{PlanSlug: "ax-follow-up", Key: "pattern", Value: "observer"}
type StateSetInput struct {
PlanSlug string `json:"plan_slug"`
@ -38,17 +41,17 @@ type StateListInput struct {
Category string `json:"category,omitempty"`
}
// out := agentic.StateOutput{Success: true, State: agentic.PlanState{Key: "pattern"}}
// out := agentic.StateOutput{Success: true, State: agentic.WorkspaceState{Key: "pattern"}}
type StateOutput struct {
Success bool `json:"success"`
State PlanState `json:"state"`
Success bool `json:"success"`
State WorkspaceState `json:"state"`
}
// out := agentic.StateListOutput{Success: true, Total: 1, States: []agentic.PlanState{{Key: "pattern"}}}
// out := agentic.StateListOutput{Success: true, Total: 1, States: []agentic.WorkspaceState{{Key: "pattern"}}}
type StateListOutput struct {
Success bool `json:"success"`
Total int `json:"total"`
States []PlanState `json:"states"`
Success bool `json:"success"`
Total int `json:"total"`
States []WorkspaceState `json:"states"`
}
// result := c.Action("state.set").Run(ctx, core.NewOptions(
@ -134,7 +137,7 @@ func (s *PrepSubsystem) stateSet(_ context.Context, _ *mcp.CallToolRequest, inpu
}
now := time.Now().Format(time.RFC3339)
state := PlanState{
state := WorkspaceState{
Key: input.Key,
Value: input.Value,
Category: input.Category,
@ -204,7 +207,7 @@ func (s *PrepSubsystem) stateList(_ context.Context, _ *mcp.CallToolRequest, inp
return nil, StateListOutput{}, err
}
filtered := make([]PlanState, 0, len(states))
filtered := make([]WorkspaceState, 0, len(states))
for _, state := range states {
if state.Category == "" {
state.Category = "general"
@ -230,25 +233,25 @@ func statePath(planSlug string) string {
return core.JoinPath(stateRoot(), core.Concat(core.SanitisePath(planSlug), ".json"))
}
func readPlanStates(planSlug string) ([]PlanState, error) {
func readPlanStates(planSlug string) ([]WorkspaceState, error) {
result := fs.Read(statePath(planSlug))
if !result.OK {
err, _ := result.Value.(error)
if err == nil {
return []PlanState{}, nil
return []WorkspaceState{}, nil
}
if core.Contains(err.Error(), "no such file") {
return []PlanState{}, nil
return []WorkspaceState{}, nil
}
return nil, core.E("readPlanStates", "failed to read state file", err)
}
content := core.Trim(result.Value.(string))
if content == "" {
return []PlanState{}, nil
return []WorkspaceState{}, nil
}
var states []PlanState
var states []WorkspaceState
if parseResult := core.JSONUnmarshalString(content, &states); !parseResult.OK {
err, _ := parseResult.Value.(error)
return nil, core.E("readPlanStates", "failed to parse state file", err)
@ -257,7 +260,7 @@ func readPlanStates(planSlug string) ([]PlanState, error) {
return states, nil
}
func writePlanStates(planSlug string, states []PlanState) error {
func writePlanStates(planSlug string, states []WorkspaceState) error {
if ensureDirResult := fs.EnsureDir(stateRoot()); !ensureDirResult.OK {
err, _ := ensureDirResult.Value.(error)
return core.E("writePlanStates", "failed to create state directory", err)

View file

@ -62,7 +62,7 @@ func TestState_HandleStateSet_Ugly_Upsert(t *testing.T) {
func TestState_HandleStateGet_Good(t *testing.T) {
subsystem := testPrepWithPlatformServer(t, nil, "")
require.NoError(t, writePlanStates("ax-follow-up", []PlanState{{
require.NoError(t, writePlanStates("ax-follow-up", []WorkspaceState{{
Key: "pattern",
Value: "observer",
Category: "general",
@ -101,7 +101,7 @@ func TestState_HandleStateGet_Ugly_CorruptStateFile(t *testing.T) {
func TestState_HandleStateList_Good(t *testing.T) {
subsystem := testPrepWithPlatformServer(t, nil, "")
require.NoError(t, writePlanStates("ax-follow-up", []PlanState{
require.NoError(t, writePlanStates("ax-follow-up", []WorkspaceState{
{Key: "pattern", Value: "observer", Category: "general"},
{Key: "risk", Value: "auth", Category: "security"},
}))