fix(ax): use typed workspace status parsing
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
c22c63edd2
commit
d005f881b7
3 changed files with 23 additions and 140 deletions
|
|
@ -18,21 +18,18 @@ func (s *PrepSubsystem) registerWorkspaceCommands() {
|
|||
c.Command("workspace/dispatch", core.Command{Description: "Dispatch an agent to work on a repo task", Action: s.cmdWorkspaceDispatch})
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdWorkspaceList(opts core.Options) core.Result {
|
||||
fsys := s.Core().Fs()
|
||||
|
||||
func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result {
|
||||
statusFiles := WorkspaceStatusPaths()
|
||||
count := 0
|
||||
for _, sf := range statusFiles {
|
||||
wsName := WorkspaceName(core.PathDir(sf))
|
||||
if sr := fsys.Read(sf); sr.OK {
|
||||
content := sr.Value.(string)
|
||||
status := extractField(content, "status")
|
||||
repo := extractField(content, "repo")
|
||||
agent := extractField(content, "agent")
|
||||
core.Print(nil, " %-8s %-8s %-10s %s", status, agent, repo, wsName)
|
||||
count++
|
||||
wsDir := core.PathDir(sf)
|
||||
wsName := WorkspaceName(wsDir)
|
||||
st, err := ReadStatus(wsDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
core.Print(nil, " %-8s %-8s %-10s %s", st.Status, st.Agent, st.Repo, wsName)
|
||||
count++
|
||||
}
|
||||
if count == 0 {
|
||||
core.Print(nil, " no workspaces")
|
||||
|
|
@ -52,12 +49,13 @@ func (s *PrepSubsystem) cmdWorkspaceClean(opts core.Options) core.Result {
|
|||
var toRemove []string
|
||||
|
||||
for _, sf := range statusFiles {
|
||||
wsName := WorkspaceName(core.PathDir(sf))
|
||||
sr := fsys.Read(sf)
|
||||
if !sr.OK {
|
||||
wsDir := core.PathDir(sf)
|
||||
wsName := WorkspaceName(wsDir)
|
||||
st, err := ReadStatus(wsDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
status := extractField(sr.Value.(string), "status")
|
||||
status := st.Status
|
||||
|
||||
switch filter {
|
||||
case "all":
|
||||
|
|
@ -130,30 +128,3 @@ func (s *PrepSubsystem) cmdWorkspaceDispatch(opts core.Options) core.Result {
|
|||
}
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// extractField does a quick JSON field extraction without full unmarshal.
|
||||
func extractField(jsonStr, field string) string {
|
||||
needle := core.Concat("\"", field, "\"")
|
||||
idx := -1
|
||||
for i := 0; i <= len(jsonStr)-len(needle); i++ {
|
||||
if jsonStr[i:i+len(needle)] == needle {
|
||||
idx = i + len(needle)
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
return ""
|
||||
}
|
||||
for idx < len(jsonStr) && (jsonStr[idx] == ':' || jsonStr[idx] == ' ' || jsonStr[idx] == '\t') {
|
||||
idx++
|
||||
}
|
||||
if idx >= len(jsonStr) || jsonStr[idx] != '"' {
|
||||
return ""
|
||||
}
|
||||
idx++
|
||||
end := idx
|
||||
for end < len(jsonStr) && jsonStr[end] != '"' {
|
||||
end++
|
||||
}
|
||||
return jsonStr[idx:end]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package agentic
|
||||
|
||||
import core "dappco.re/go/core"
|
||||
|
||||
func Example_extractField() {
|
||||
json := `{"status":"completed","repo":"go-io"}`
|
||||
core.Println(extractField(json, "status"))
|
||||
core.Println(extractField(json, "repo"))
|
||||
// Output:
|
||||
// completed
|
||||
// go-io
|
||||
}
|
||||
|
|
@ -10,61 +10,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// --- extractField ---
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Good_SimpleJSON(t *testing.T) {
|
||||
json := `{"status":"running","repo":"go-io","agent":"codex"}`
|
||||
assert.Equal(t, "running", extractField(json, "status"))
|
||||
assert.Equal(t, "go-io", extractField(json, "repo"))
|
||||
assert.Equal(t, "codex", extractField(json, "agent"))
|
||||
}
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Good_PrettyPrinted(t *testing.T) {
|
||||
json := `{
|
||||
"status": "completed",
|
||||
"repo": "go-crypt"
|
||||
}`
|
||||
assert.Equal(t, "completed", extractField(json, "status"))
|
||||
assert.Equal(t, "go-crypt", extractField(json, "repo"))
|
||||
}
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Good_TabSeparated(t *testing.T) {
|
||||
json := `{"status": "blocked"}`
|
||||
assert.Equal(t, "blocked", extractField(json, "status"))
|
||||
}
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Bad_MissingField(t *testing.T) {
|
||||
json := `{"status":"running"}`
|
||||
assert.Empty(t, extractField(json, "nonexistent"))
|
||||
}
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Bad_EmptyJSON(t *testing.T) {
|
||||
assert.Empty(t, extractField("", "status"))
|
||||
assert.Empty(t, extractField("{}", "status"))
|
||||
}
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Bad_NoValue(t *testing.T) {
|
||||
// Field key exists but no quoted value after colon
|
||||
json := `{"status": 42}`
|
||||
assert.Empty(t, extractField(json, "status"))
|
||||
}
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Bad_TruncatedJSON(t *testing.T) {
|
||||
// Field key exists but string is truncated
|
||||
json := `{"status":`
|
||||
assert.Empty(t, extractField(json, "status"))
|
||||
}
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Good_EmptyValue(t *testing.T) {
|
||||
json := `{"status":""}`
|
||||
assert.Equal(t, "", extractField(json, "status"))
|
||||
}
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Good_ValueWithSpaces(t *testing.T) {
|
||||
json := `{"task":"fix the failing tests"}`
|
||||
assert.Equal(t, "fix the failing tests", extractField(json, "task"))
|
||||
}
|
||||
|
||||
// --- CmdWorkspaceList Bad/Ugly ---
|
||||
|
||||
func TestCommandsworkspace_CmdWorkspaceList_Bad_NoWorkspaceRootDir(t *testing.T) {
|
||||
|
|
@ -75,8 +20,8 @@ func TestCommandsworkspace_CmdWorkspaceList_Bad_NoWorkspaceRootDir(t *testing.T)
|
|||
c := core.New()
|
||||
s := &PrepSubsystem{
|
||||
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
r := s.cmdWorkspaceList(core.NewOptions())
|
||||
|
|
@ -105,8 +50,8 @@ func TestCommandsworkspace_CmdWorkspaceList_Ugly_NonDirAndCorruptStatus(t *testi
|
|||
c := core.New()
|
||||
s := &PrepSubsystem{
|
||||
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
r := s.cmdWorkspaceList(core.NewOptions())
|
||||
|
|
@ -134,8 +79,8 @@ func TestCommandsworkspace_CmdWorkspaceClean_Bad_UnknownFilterLeavesEverything(t
|
|||
c := core.New()
|
||||
s := &PrepSubsystem{
|
||||
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
// Filter "unknown" matches no switch case — nothing gets removed
|
||||
|
|
@ -169,8 +114,8 @@ func TestCommandsworkspace_CmdWorkspaceClean_Ugly_MixedStatuses(t *testing.T) {
|
|||
c := core.New()
|
||||
s := &PrepSubsystem{
|
||||
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
// "all" filter removes completed, failed, blocked, merged, ready-for-review but NOT running/queued
|
||||
|
|
@ -196,8 +141,8 @@ func TestCommandsworkspace_CmdWorkspaceDispatch_Ugly_AllFieldsSet(t *testing.T)
|
|||
c := core.New()
|
||||
s := &PrepSubsystem{
|
||||
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
backoff: make(map[string]time.Time),
|
||||
failCount: make(map[string]int),
|
||||
}
|
||||
|
||||
r := s.cmdWorkspaceDispatch(core.NewOptions(
|
||||
|
|
@ -212,22 +157,3 @@ func TestCommandsworkspace_CmdWorkspaceDispatch_Ugly_AllFieldsSet(t *testing.T)
|
|||
// The test verifies the CLI correctly passes all fields through to dispatch.
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
// --- ExtractField Ugly ---
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Ugly_NestedJSON(t *testing.T) {
|
||||
// Nested JSON — extractField only finds top-level keys (simple scan)
|
||||
j := `{"outer":{"inner":"value"},"status":"ok"}`
|
||||
assert.Equal(t, "ok", extractField(j, "status"))
|
||||
// "inner" is inside the nested object — extractField should still find it
|
||||
assert.Equal(t, "value", extractField(j, "inner"))
|
||||
}
|
||||
|
||||
func TestCommandsworkspace_ExtractField_Ugly_EscapedQuotes(t *testing.T) {
|
||||
// Value with escaped quotes — extractField stops at the first unescaped quote
|
||||
j := `{"msg":"hello \"world\"","status":"done"}`
|
||||
// extractField will return "hello \" because it stops at first quote after open
|
||||
// The important thing is it doesn't panic
|
||||
_ = extractField(j, "msg")
|
||||
assert.Equal(t, "done", extractField(j, "status"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue