fix(agentic): align workspace flow with AX design

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-29 20:15:58 +00:00
parent 6bb4fb8d57
commit 6ac195c2e6
37 changed files with 1156 additions and 335 deletions

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: EUPL-1.2
package main
import (
@ -7,7 +9,6 @@ import (
"dappco.re/go/agent/pkg/brain"
"dappco.re/go/agent/pkg/monitor"
"dappco.re/go/agent/pkg/runner"
"dappco.re/go/mcp/pkg/mcp"
)
func main() {
@ -22,7 +23,7 @@ func main() {
core.WithService(runner.Register),
core.WithService(monitor.Register),
core.WithService(brain.Register),
core.WithService(mcp.Register),
core.WithName("mcp", registerMCPService),
)
c.App().Version = appVersion()

View file

@ -0,0 +1,33 @@
// SPDX-License-Identifier: EUPL-1.2
package main
import (
"dappco.re/go/agent/pkg/agentic"
"dappco.re/go/agent/pkg/brain"
"dappco.re/go/agent/pkg/monitor"
core "dappco.re/go/core"
"forge.lthn.ai/core/mcp/pkg/mcp"
)
func registerMCPService(c *core.Core) core.Result {
var subsystems []mcp.Subsystem
if prep, ok := core.ServiceFor[*agentic.PrepSubsystem](c, "agentic"); ok {
subsystems = append(subsystems, prep)
}
if mon, ok := core.ServiceFor[*monitor.Subsystem](c, "monitor"); ok {
subsystems = append(subsystems, mon)
}
if brn, ok := core.ServiceFor[*brain.DirectSubsystem](c, "brain"); ok {
subsystems = append(subsystems, brn)
}
svc, err := mcp.New(mcp.Options{
Subsystems: subsystems,
})
if err != nil {
return core.Result{Value: core.E("main.registerMCPService", "create mcp service", err), OK: false}
}
return core.Result{Value: svc, OK: true}
}

14
go.mod
View file

@ -7,6 +7,7 @@ require (
dappco.re/go/core/api v0.2.0
dappco.re/go/core/process v0.3.0
dappco.re/go/core/ws v0.3.0
forge.lthn.ai/core/mcp v0.4.8
github.com/gin-gonic/gin v1.12.0
github.com/gorilla/websocket v1.5.3
github.com/modelcontextprotocol/go-sdk v1.4.1
@ -14,11 +15,22 @@ require (
gopkg.in/yaml.v3 v3.0.1
)
require dappco.re/go/core/forge v0.2.0 // indirect
require (
dappco.re/go/core/forge v0.2.0
forge.lthn.ai/core/go-ws v0.2.5
)
require (
dappco.re/go/core/io v0.2.0 // indirect
dappco.re/go/core/log v0.1.0 // indirect
forge.lthn.ai/core/api v0.1.5 // indirect
forge.lthn.ai/core/go v0.3.3 // indirect
forge.lthn.ai/core/go-ai v0.1.12 // indirect
forge.lthn.ai/core/go-io v0.1.7 // indirect
forge.lthn.ai/core/go-log v0.0.4 // indirect
forge.lthn.ai/core/go-process v0.2.9 // indirect
forge.lthn.ai/core/go-rag v0.1.11 // indirect
forge.lthn.ai/core/go-webview v0.1.6 // indirect
github.com/99designs/gqlgen v0.17.88 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect

506
go.sum Normal file
View file

@ -0,0 +1,506 @@
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk=
dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A=
dappco.re/go/core/api v0.2.0 h1:5OcN9nawpp18Jp6dB1OwI2CBfs0Tacb0y0zqxFB6TJ0=
dappco.re/go/core/api v0.2.0/go.mod h1:AtgNAx8lDY+qhVObFdNQOjSUQrHX1BeiDdMuA6RIfzo=
dappco.re/go/core/forge v0.2.0 h1:EBCHaUdzEAbYpDwRTXMmJoSfSrK30IJTOVBPRxxkJTg=
dappco.re/go/core/forge v0.2.0/go.mod h1:XMz9ZNVl9xane9Rg3AEBuVV5UNNBGWbPY9rSKbqYgnM=
dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4=
dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E=
dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc=
dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs=
dappco.re/go/core/process v0.3.0 h1:BPF9R79+8ZWe34qCIy/sZy+P4HwbaO95js2oPJL7IqM=
dappco.re/go/core/process v0.3.0/go.mod h1:qwx8kt6x+J9gn7fu8lavuess72Ye9jPBODqDZQ9K0as=
dappco.re/go/core/ws v0.3.0 h1:ZxR8y5pfrWvnCHVN7qExXz7fdP5a063uNqyqE0Ab8pQ=
dappco.re/go/core/ws v0.3.0/go.mod h1:aLyXrJnbCOGL0SW9rC1EHAAIS83w3djO374gHIz4Nic=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg=
forge.lthn.ai/core/api v0.1.5 h1:NwZrcOyBjaiz5/cn0n0tnlMUodi8Or6FHMx59C7Kv2o=
forge.lthn.ai/core/api v0.1.5/go.mod h1:PBnaWyOVXSOGy+0x2XAPUFMYJxQ2CNhppia/D06ZPII=
forge.lthn.ai/core/cli v0.3.7/go.mod h1:DBUppJkA9P45ZFGgI2B8VXw1rAZxamHoI/KG7fRvTNs=
forge.lthn.ai/core/go v0.3.2/go.mod h1:f7/zb3Labn4ARfwTq5Bi2AFHY+uxyPHozO+hLb54eFo=
forge.lthn.ai/core/go v0.3.3 h1:kYYZ2nRYy0/Be3cyuLJspRjLqTMxpckVyhb/7Sw2gd0=
forge.lthn.ai/core/go v0.3.3/go.mod h1:Cp4ac25pghvO2iqOu59t1GyngTKVOzKB5/VPdhRi9CQ=
forge.lthn.ai/core/go-ai v0.1.12 h1:OHt0bUABlyhvgxZxyMwueRoh8rS3YKWGFY6++zCAwC8=
forge.lthn.ai/core/go-ai v0.1.12/go.mod h1:5Pc9lszxgkO7Aj2Z3dtq4L9Xk9l/VNN+Baj1t///OCM=
forge.lthn.ai/core/go-crypt v0.1.6/go.mod h1:4VZAGqxlbadhSB66sJkdj54/HSJ+bSxVgwWK5kMMYDo=
forge.lthn.ai/core/go-i18n v0.1.7/go.mod h1:0VDjwtY99NSj2iqwrI09h5GUsJeM9s48MLkr+/Dn4G8=
forge.lthn.ai/core/go-inference v0.1.7/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw=
forge.lthn.ai/core/go-io v0.1.5/go.mod h1:FRtXSsi8W+U9vewCU+LBAqqbIj3wjXA4dBdSv3SAtWI=
forge.lthn.ai/core/go-io v0.1.7 h1:Tdb6sqh+zz1lsGJaNX9RFWM6MJ/RhSAyxfulLXrJsbk=
forge.lthn.ai/core/go-io v0.1.7/go.mod h1:8lRLFk4Dnp5cR/Cyzh9WclD5566TbpdRgwcH7UZLWn4=
forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw=
forge.lthn.ai/core/go-ml v0.1.0/go.mod h1:FPV9JhIUOZdLeJpX1ggC15BpmM740NPg6rycnOc5vss=
forge.lthn.ai/core/go-mlx v0.1.0/go.mod h1:b4BJX67nx9QZiyREl2lmYIPJ+Yp5amZug3y7vXaRy/Y=
forge.lthn.ai/core/go-process v0.2.9 h1:Wql+5TUF+lfU2oJ9I+S764MkTqJhBsuyMM0v1zsfZC4=
forge.lthn.ai/core/go-process v0.2.9/go.mod h1:NIzZOF5IVYYCjHkcNIGcg1mZH+bzGoie4SlZUDYOKIM=
forge.lthn.ai/core/go-rag v0.1.11 h1:KXTOtnOdrx8YKmvnj0EOi2EI/+cKjE8w2PpJCQIrSd8=
forge.lthn.ai/core/go-rag v0.1.11/go.mod h1:vIlOKVD1SdqqjkJ2XQyXPuKPtiajz/STPLCaDpqOzk8=
forge.lthn.ai/core/go-scm v0.2.0/go.mod h1:Q/PV2FbqDlWnAOsXAd1pgSiHOlRCPW4HcPmOt8Z9H+E=
forge.lthn.ai/core/go-webview v0.1.6 h1:szXQxRJf2bOZJKh3v1P01B1Vf9mgXaBCXzh0EZu9aoc=
forge.lthn.ai/core/go-webview v0.1.6/go.mod h1:5n1tECD1wBV/uFZRY9ZjfPFO5TYZrlaR3mQFwvO2nek=
forge.lthn.ai/core/go-ws v0.2.5 h1:ZIV7Yrv01R/xpJUogA5vrfP9yB9li1w7EV3eZFMt8h0=
forge.lthn.ai/core/go-ws v0.2.5/go.mod h1:C3riJyLLcV6QhLvYlq3P/XkGTsN598qQeGBoLdoHBU4=
forge.lthn.ai/core/mcp v0.4.8 h1:nd1x3AL8AkUfl0kziltoJUX96Nx1BeFWEbgHmfrkKz8=
forge.lthn.ai/core/mcp v0.4.8/go.mod h1:eU35WT/8Mc0oJDVWdKaXEtNp27+Hc8KvnTKPf4DAqXE=
github.com/99designs/gqlgen v0.17.88 h1:neMQDgehMwT1vYIOx/w5ZYPUU/iMNAJzRO44I5Intoc=
github.com/99designs/gqlgen v0.17.88/go.mod h1:qeqYFEgOeSKqWedOjogPizimp2iu4E23bdPvl4jTYic=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I=
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a/go.mod h1:Sdr/tmSOLEnncCuXS5TwZRxuk7deH1WXVY8cve3eVBM=
github.com/apache/arrow-go/v18 v18.5.2/go.mod h1:yNoizNTT4peTciJ7V01d2EgOkE1d0fQ1vZcFOsVtFsw=
github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21/go.mod h1:UUxgWxofmOdAMuqEsSppbDtGKLfR04HGsD0HXzvhI1k=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12/go.mod h1:v2pNpJbRNl4vEUWEh5ytQok0zACAKfdmKS51Hotc3pQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20/go.mod h1:4TLZCmVJDM3FOu5P5TJP0zOlu9zWgDWU7aUxWbr+rcw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1/go.mod h1:qXVal5H0ChqXP63t6jze5LmFalc7+ZE7wOdLtZ0LCP0=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/boj/redistore v1.4.1/go.mod h1:c0Tvw6aMjslog4jHIAcNv6EtJM849YoOAhMY7JBbWpI=
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk=
github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chewxy/hm v1.0.0/go.mod h1:qg9YI4q6Fkj/whwHR1D+bOGeF7SniIP40VweVepLjg0=
github.com/chewxy/math32 v1.11.0/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/d4l3k/go-bfloat16 v0.0.0-20211005043715-690c3bdd05f1/go.mod h1:uw2gLcxEuYUlAd/EXyjc/v55nd3+47YAgWbSXVxPrNI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/authz v1.0.6 h1:qAO4sSSzOPCwYRZI6YtubC+h2tZVwhwSJeyEZn2W+5k=
github.com/gin-contrib/authz v1.0.6/go.mod h1:A2B5Im1M/HIoHPjLc31j3RlENSE6j8euJY9NFdzZeYo=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
github.com/gin-contrib/expvar v1.0.3 h1:nIbUaokxZfUEC/35h+RyWCP1SMF/suV/ARbXL3H3jrw=
github.com/gin-contrib/expvar v1.0.3/go.mod h1:bwqqmhty1Zl2JYVLzBIL6CSHDWDbQoQoicalAnBvUnY=
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
github.com/gin-contrib/httpsign v1.0.3 h1:NpeDQjmUV0qFjGCm/rkXSp3HH0hU7r84q1v+VtTiI5I=
github.com/gin-contrib/httpsign v1.0.3/go.mod h1:n4GC7StmHNBhIzWzuW2njKbZMeEWh4tDbmn3bD1ab+k=
github.com/gin-contrib/location/v2 v2.0.0 h1:iLx5RatHQHSxgC0tm2AG0sIuQKecI7FhREessVd6RWY=
github.com/gin-contrib/location/v2 v2.0.0/go.mod h1:276TDNr25NENBA/NQZUuEIlwxy/I5CYVFIr/d2TgOdU=
github.com/gin-contrib/pprof v1.5.3 h1:Bj5SxJ3kQDVez/s/+f9+meedJIqLS+xlkIVDe/lcvgM=
github.com/gin-contrib/pprof v1.5.3/go.mod h1:0+LQSZ4SLO0B6+2n6JBzaEygpTBxe/nI+YEYpfQQ6xY=
github.com/gin-contrib/secure v1.1.2 h1:6G8/NCOTSywWY7TeaH/0Yfaa6bfkE5ukkqtIm7lK11U=
github.com/gin-contrib/secure v1.1.2/go.mod h1:xI3jI5/BpOYMCBtjgmIVrMA3kI7y9LwCFxs+eLf5S3w=
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
github.com/gin-contrib/slog v1.2.0 h1:vAxZfr7knD1ZYK5+pMJLP52sZXIkJXkcRPa/0dx9hSk=
github.com/gin-contrib/slog v1.2.0/go.mod h1:vYK6YltmpsEFkO0zfRMLTKHrWS3DwUSn0TMpT+kMagI=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-contrib/static v1.1.5 h1:bAPqT4KTZN+4uDY1b90eSrD1t8iNzod7Jj8njwmnzz4=
github.com/gin-contrib/static v1.1.5/go.mod h1:8JSEXwZHcQ0uCrLPcsvnAJ4g+ODxeupP8Zetl9fd8wM=
github.com/gin-contrib/timeout v1.1.0 h1:WAmWseo5gfBUbMrMJu5hJxDclehfSJUmK2wGwCC/EFw=
github.com/gin-contrib/timeout v1.1.0/go.mod h1:NpRo4gd1Ad8ZQ4T6bQLVFDqiplCmPRs2nvfckxS2Fw4=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e/go.mod h1:ZybsQk6DWyN5t7An1MuPm1gtSZ1xDaTXS9ZjIOxvQrk=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/laziness-coders/mongostore v0.0.14/go.mod h1:Rh+yJax2Vxc2QY62clIM/kRnLk+TxivgSLHOXENXPtk=
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728/go.mod h1:1fEHWurg7pvf5SG6XNE5Q8UZmOwex51Mkx3SLhrW5B4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M=
github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/marcboeker/go-duckdb v1.8.5/go.mod h1:6mK7+WQE4P4u5AFLvVBmhFxY5fvhymFptghgJX6B+/8=
github.com/matryer/moq v0.6.0/go.mod h1:iEVhY/XBwFG/nbRyEf0oV+SqnTHZJ5wectzx7yT+y98=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modelcontextprotocol/go-sdk v1.4.1 h1:M4x9GyIPj+HoIlHNGpK2hq5o3BFhC+78PkEaldQRphc=
github.com/modelcontextprotocol/go-sdk v1.4.1/go.mod h1:Bo/mS87hPQqHSRkMv4dQq1XCu6zv4INdXnFZabkNU6s=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/ollama/ollama v0.18.2 h1:RsOY8oZ6TufRiPgsSlKJp4/V/X+oBREscUlEHZfd554=
github.com/ollama/ollama v0.18.2/go.mod h1:tCX4IMV8DHjl3zY0THxuEkpWDZSOchJpzTuLACpMwFw=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/parquet-go/bitpack v1.0.0/go.mod h1:XnVk9TH+O40eOOmvpAVZ7K2ocQFrQwysLMnc6M/8lgs=
github.com/parquet-go/jsonlite v1.4.0/go.mod h1:nDjpkpL4EOtqs6NQugUsi0Rleq9sW/OtC1NnZEnxzF0=
github.com/parquet-go/parquet-go v0.29.0/go.mod h1:navtkAYr2LGoJVp141oXPlO/sxLvaOe3la2JEoD8+rg=
github.com/pdevine/tensor v0.0.0-20240510204454-f88f4562727c/go.mod h1:PSojXDXF7TbgQiD6kkd98IHOS0QqTyUEaWRiS8+BLu8=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/qdrant/go-client v1.17.1 h1:7QmPwDddrHL3hC4NfycwtQlraVKRLcRi++BX6TTm+3g=
github.com/qdrant/go-client v1.17.1/go.mod h1:n1h6GhkdAzcohoXt/5Z19I2yxbCkMA6Jejob3S6NZT8=
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE=
github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/tkrajina/typescriptify-golang-structs v0.2.0/go.mod h1:sjU00nti/PMEOZb07KljFlR+lJ+RotsC0GBQMv9EKls=
github.com/tree-sitter/go-tree-sitter v0.25.0/go.mod h1:r77ig7BikoZhHrrsjAnv8RqGti5rtSyvDHPzgTPsUuU=
github.com/tree-sitter/tree-sitter-cpp v0.23.4/go.mod h1:doqNW64BriC7WBCQ1klf0KmJpdEvfxyXtoEybnBo6v8=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/twpayne/go-geom v1.6.1/go.mod h1:Kr+Nly6BswFsKM5sd31YaoWS5PeDDH2NftJTK7Gd028=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc=
github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
github.com/wader/gormstore/v2 v2.0.3/go.mod h1:sr3N3a8F1+PBc3fHoKaphFqDXLRJ9Oe6Yow0HxKFbbg=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xtgo/set v1.0.0/go.mod h1:d3NHzGzSa0NmB2NhFyECA+QdRp29oEn2xbT+TpeFoM8=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.67.0 h1:E7DmskpIO7ZR6QI6zKSEKIDNUYoKw9oHXP23gzbCdU0=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.67.0/go.mod h1:WB2cS9y+AwqqKhoo9gw6/ZxlSjFBUQGZ8BQOaD3FVXM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
go.opentelemetry.io/contrib/propagators/b3 v1.42.0/go.mod h1:iPgUcSEF5DORW6+yNbdw/YevUy+QqJ508ncjhrRSCjc=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
golang.org/x/telemetry v0.0.0-20260312161427-1546bf4b83fe/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorgonia.org/vecf32 v0.9.0/go.mod h1:NCc+5D2oxddRL11hd+pCB1PEyXWOyiQxfZ/1wwhOXCA=
gorgonia.org/vecf64 v0.9.0/go.mod h1:hp7IOWCnRiVQKON73kkC/AUMtEXyf9kGlVrtPQ9ccVA=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

View file

@ -111,7 +111,13 @@ func (s *PrepSubsystem) handleScan(ctx context.Context, opts core.Options) core.
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
// ))
func (s *PrepSubsystem) handleWatch(ctx context.Context, opts core.Options) core.Result {
input := WatchInput{}
input := WatchInput{
PollInterval: opts.Int("poll_interval"),
Timeout: opts.Int("timeout"),
}
if workspace := opts.String("workspace"); workspace != "" {
input.Workspaces = []string{workspace}
}
_, out, err := s.watch(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
@ -151,7 +157,7 @@ func (s *PrepSubsystem) handleQA(ctx context.Context, opts core.Options) core.Re
repo = st.Repo
}
s.Core().ACTION(messages.QAResult{
Workspace: core.PathBase(wsDir),
Workspace: WorkspaceName(wsDir),
Repo: repo,
Passed: passed,
})

View file

@ -6,14 +6,13 @@ import (
"context"
core "dappco.re/go/core"
"dappco.re/go/core/process"
"dappco.re/go/agent/pkg/agentic"
)
func ExampleRegister() {
c := core.New(
core.WithService(process.Register),
core.WithService(agentic.ProcessRegister),
core.WithService(agentic.Register),
)
c.ServiceStartup(context.Background(), nil)
@ -30,7 +29,7 @@ func ExampleRegister() {
func ExampleRegister_actions() {
c := core.New(
core.WithService(process.Register),
core.WithService(agentic.ProcessRegister),
core.WithService(agentic.Register),
)
c.ServiceStartup(context.Background(), nil)
@ -42,7 +41,7 @@ func ExampleRegister_actions() {
func ExampleRegister_task() {
c := core.New(
core.WithService(process.Register),
core.WithService(agentic.ProcessRegister),
core.WithService(agentic.Register),
)
c.ServiceStartup(context.Background(), nil)

View file

@ -11,6 +11,66 @@ import (
forge_types "dappco.re/go/core/forge/types"
)
type issueView struct {
Index int64 `json:"index"`
Number int64 `json:"number"`
Title string `json:"title"`
State string `json:"state"`
HTMLURL string `json:"html_url"`
Body string `json:"body"`
}
type prBranchView struct {
Ref string `json:"ref"`
}
type prUserView struct {
Login string `json:"login"`
UserName string `json:"username"`
}
type prLabelView struct {
Name string `json:"name"`
}
type pullRequestView struct {
Index int64 `json:"index"`
Number int64 `json:"number"`
Title string `json:"title"`
State string `json:"state"`
Mergeable bool `json:"mergeable"`
HTMLURL string `json:"html_url"`
Body string `json:"body"`
Head prBranchView `json:"head"`
Base prBranchView `json:"base"`
User *prUserView `json:"user"`
Labels []prLabelView `json:"labels"`
}
func issueNumber(issue issueView) int64 {
if issue.Index != 0 {
return issue.Index
}
return issue.Number
}
func pullRequestNumber(pr pullRequestView) int64 {
if pr.Index != 0 {
return pr.Index
}
return pr.Number
}
func pullRequestAuthor(pr pullRequestView) string {
if pr.User == nil {
return ""
}
if pr.User.UserName != "" {
return pr.User.UserName
}
return pr.User.Login
}
// parseForgeArgs extracts org and repo from opts.
func parseForgeArgs(opts core.Options) (org, repo string, num int64) {
org = opts.String("org")
@ -47,12 +107,13 @@ func (s *PrepSubsystem) cmdIssueGet(opts core.Options) core.Result {
core.Print(nil, "usage: core-agent issue get <repo> --number=N [--org=core]")
return core.Result{OK: false}
}
issue, err := s.forge.Issues.Get(ctx, forge.Params{"owner": org, "repo": repo, "index": fmtIndex(num)})
var issue issueView
err := s.forge.Client().Get(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d", org, repo, num), &issue)
if err != nil {
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
core.Print(nil, "#%d %s", issue.Index, issue.Title)
core.Print(nil, "#%d %s", issueNumber(issue), issue.Title)
core.Print(nil, " state: %s", issue.State)
core.Print(nil, " url: %s", issue.HTMLURL)
if issue.Body != "" {
@ -69,13 +130,14 @@ func (s *PrepSubsystem) cmdIssueList(opts core.Options) core.Result {
core.Print(nil, "usage: core-agent issue list <repo> [--org=core]")
return core.Result{OK: false}
}
issues, err := s.forge.Issues.ListAll(ctx, forge.Params{"owner": org, "repo": repo})
var issues []issueView
err := s.forge.Client().Get(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues?limit=50&page=1", org, repo), &issues)
if err != nil {
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
for _, issue := range issues {
core.Print(nil, " #%-4d %-6s %s", issue.Index, issue.State, issue.Title)
core.Print(nil, " #%-4d %-6s %s", issueNumber(issue), issue.State, issue.Title)
}
if len(issues) == 0 {
core.Print(nil, " no issues")
@ -117,7 +179,8 @@ func (s *PrepSubsystem) cmdIssueCreate(opts core.Options) core.Result {
createOpts := &forge_types.CreateIssueOption{Title: title, Body: body, Ref: ref}
if milestone != "" {
milestones, err := s.forge.Milestones.ListAll(ctx, forge.Params{"owner": org, "repo": repo})
var milestones []forge_types.Milestone
err := s.forge.Client().Get(ctx, core.Sprintf("/api/v1/repos/%s/%s/milestones", org, repo), &milestones)
if err == nil {
for _, m := range milestones {
if m.Title == milestone {
@ -163,12 +226,13 @@ func (s *PrepSubsystem) cmdPRGet(opts core.Options) core.Result {
core.Print(nil, "usage: core-agent pr get <repo> --number=N [--org=core]")
return core.Result{OK: false}
}
pr, err := s.forge.Pulls.Get(ctx, forge.Params{"owner": org, "repo": repo, "index": fmtIndex(num)})
var pr pullRequestView
err := s.forge.Client().Get(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls/%d", org, repo, num), &pr)
if err != nil {
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
core.Print(nil, "#%d %s", pr.Index, pr.Title)
core.Print(nil, "#%d %s", pullRequestNumber(pr), pr.Title)
core.Print(nil, " state: %s", pr.State)
core.Print(nil, " head: %s", pr.Head.Ref)
core.Print(nil, " base: %s", pr.Base.Ref)
@ -188,13 +252,14 @@ func (s *PrepSubsystem) cmdPRList(opts core.Options) core.Result {
core.Print(nil, "usage: core-agent pr list <repo> [--org=core]")
return core.Result{OK: false}
}
prs, err := s.forge.Pulls.ListAll(ctx, forge.Params{"owner": org, "repo": repo})
var prs []pullRequestView
err := s.forge.Client().Get(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls?limit=50&page=1", org, repo), &prs)
if err != nil {
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
for _, pr := range prs {
core.Print(nil, " #%-4d %-6s %s → %s %s", pr.Index, pr.State, pr.Head.Ref, pr.Base.Ref, pr.Title)
core.Print(nil, " #%-4d %-6s %s → %s %s", pullRequestNumber(pr), pr.State, pr.Head.Ref, pr.Base.Ref, pr.Title)
}
if len(prs) == 0 {
core.Print(nil, " no PRs")

View file

@ -19,13 +19,12 @@ func (s *PrepSubsystem) registerWorkspaceCommands() {
}
func (s *PrepSubsystem) cmdWorkspaceList(opts core.Options) core.Result {
wsRoot := WorkspaceRoot()
fsys := s.Core().Fs()
statusFiles := core.PathGlob(core.JoinPath(wsRoot, "*", "status.json"))
statusFiles := WorkspaceStatusPaths()
count := 0
for _, sf := range statusFiles {
wsName := core.PathBase(core.PathDir(sf))
wsName := WorkspaceName(core.PathDir(sf))
if sr := fsys.Read(sf); sr.OK {
content := sr.Value.(string)
status := extractField(content, "status")
@ -49,11 +48,11 @@ func (s *PrepSubsystem) cmdWorkspaceClean(opts core.Options) core.Result {
filter = "all"
}
statusFiles := core.PathGlob(core.JoinPath(wsRoot, "*", "status.json"))
statusFiles := WorkspaceStatusPaths()
var toRemove []string
for _, sf := range statusFiles {
wsName := core.PathBase(core.PathDir(sf))
wsName := WorkspaceName(core.PathDir(sf))
sr := fsys.Read(sf)
if !sr.OK {
continue

View file

@ -288,6 +288,7 @@ func (s *PrepSubsystem) stopIssueTracking(wsDir string) {
// broadcastStart emits IPC + audit events for agent start.
func (s *PrepSubsystem) broadcastStart(agent, wsDir string) {
wsName := WorkspaceName(wsDir)
st, _ := ReadStatus(wsDir)
repo := ""
if st != nil {
@ -295,15 +296,16 @@ func (s *PrepSubsystem) broadcastStart(agent, wsDir string) {
}
if s.ServiceRuntime != nil {
s.Core().ACTION(messages.AgentStarted{
Agent: agent, Repo: repo, Workspace: core.PathBase(wsDir),
Agent: agent, Repo: repo, Workspace: wsName,
})
}
emitStartEvent(agent, core.PathBase(wsDir))
emitStartEvent(agent, wsName)
}
// broadcastComplete emits IPC + audit events for agent completion.
func (s *PrepSubsystem) broadcastComplete(agent, wsDir, finalStatus string) {
emitCompletionEvent(agent, core.PathBase(wsDir), finalStatus)
wsName := WorkspaceName(wsDir)
emitCompletionEvent(agent, wsName, finalStatus)
if s.ServiceRuntime != nil {
st, _ := ReadStatus(wsDir)
repo := ""
@ -312,7 +314,7 @@ func (s *PrepSubsystem) broadcastComplete(agent, wsDir, finalStatus string) {
}
s.Core().ACTION(messages.AgentCompleted{
Agent: agent, Repo: repo,
Workspace: core.PathBase(wsDir), Status: finalStatus,
Workspace: wsName, Status: finalStatus,
})
}
}
@ -383,16 +385,15 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir string) (int, string, er
if !ok {
return 0, "", core.E("dispatch.spawnAgent", "process service not registered", nil)
}
sr := procSvc.StartWithOptions(context.Background(), process.RunOptions{
proc, err := procSvc.StartWithOptions(context.Background(), process.RunOptions{
Command: command,
Args: args,
Dir: repoDir,
Detach: true,
})
if !sr.OK {
return 0, "", core.E("dispatch.spawnAgent", core.Concat("failed to spawn ", agent), nil)
if err != nil {
return 0, "", core.E("dispatch.spawnAgent", core.Concat("failed to spawn ", agent), err)
}
proc := sr.Value.(*process.Process)
proc.CloseStdin()
pid := proc.Info().PID
@ -402,7 +403,7 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir string) (int, string, er
// Register a one-shot Action that monitors this agent, then run it via PerformAsync.
// PerformAsync tracks it in Core's WaitGroup — ServiceShutdown waits for it.
monitorAction := core.Concat("agentic.monitor.", core.PathBase(wsDir))
monitorAction := core.Concat("agentic.monitor.", core.Replace(WorkspaceName(wsDir), "/", "."))
s.Core().Action(monitorAction, func(_ context.Context, _ core.Options) core.Result {
<-proc.Done()
s.onAgentComplete(agent, wsDir, outputFile,
@ -468,21 +469,6 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
input.Template = "coding"
}
// Concurrency check — ask the runner
r := s.Core().Action("runner.dispatch").Run(ctx, core.NewOptions(
core.Option{Key: "agent", Value: input.Agent},
core.Option{Key: "repo", Value: input.Repo},
))
if !r.OK {
reason, _ := r.Value.(string)
out := DispatchOutput{
Repo: input.Repo,
Success: true,
OutputFile: core.Concat("queued — ", reason),
}
return nil, out, nil
}
// Step 1: Prep workspace — clone + build prompt
prepInput := PrepInput{
Repo: input.Repo,

View file

@ -8,6 +8,13 @@ import (
core "dappco.re/go/core"
)
func agentHomeDir() string {
if home := core.Env("HOME"); home != "" {
return home
}
return core.Env("DIR_HOME")
}
// ingestFindings reads the agent output log and creates issues via the API
// for scan/audit results. Only runs for conventions and security templates.
func (s *PrepSubsystem) ingestFindings(wsDir string) {
@ -17,7 +24,7 @@ func (s *PrepSubsystem) ingestFindings(wsDir string) {
}
// Read the log file
logFiles := core.PathGlob(core.JoinPath(wsDir, "agent-*.log"))
logFiles := workspaceLogFiles(wsDir)
if len(logFiles) == 0 {
return
}
@ -88,7 +95,7 @@ func (s *PrepSubsystem) createIssueViaAPI(repo, title, description, issueType, p
}
// Read the agent API key from file
r := fs.Read(core.JoinPath(core.Env("DIR_HOME"), ".claude", "agent-api.key"))
r := fs.Read(core.JoinPath(agentHomeDir(), ".claude", "agent-api.key"))
if !r.OK {
return
}

View file

@ -45,20 +45,21 @@ func TestIngest_IngestFindings_Good_WithFindings(t *testing.T) {
"- `pkg/core/service.go:100` has a missing error check\n" +
"- `pkg/core/config.go:25` needs documentation\n" +
"This is padding to get past the 100 char minimum length requirement for the log file content parsing."
require.True(t, fs.Write(core.JoinPath(wsDir, "agent-codex.log"), logContent).OK)
require.True(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK)
require.True(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), logContent).OK)
// Set up HOME for the agent-api.key read
home := t.TempDir()
t.Setenv("DIR_HOME", home)
t.Setenv("HOME", home)
require.True(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK)
require.True(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-api-key").OK)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.ingestFindings(wsDir)
@ -74,8 +75,8 @@ func TestIngest_IngestFindings_Bad_NotCompleted(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Should return early — status is not "completed"
@ -93,8 +94,8 @@ func TestIngest_IngestFindings_Bad_NoLogFile(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Should return early — no log files
@ -112,12 +113,13 @@ func TestIngest_IngestFindings_Bad_TooFewFindings(t *testing.T) {
// Only 1 finding (need >= 2 to ingest)
logContent := "Found: `main.go:1` has an issue. This padding makes the content long enough to pass the 100 char minimum check."
require.True(t, fs.Write(core.JoinPath(wsDir, "agent-codex.log"), logContent).OK)
require.True(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK)
require.True(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), logContent).OK)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
assert.NotPanics(t, func() {
@ -134,12 +136,13 @@ func TestIngest_IngestFindings_Bad_QuotaExhausted(t *testing.T) {
// Log contains quota error — should skip
logContent := "QUOTA_EXHAUSTED: Rate limit exceeded. `main.go:1` `other.go:2` padding to ensure we pass length check and get past the threshold."
require.True(t, fs.Write(core.JoinPath(wsDir, "agent-codex.log"), logContent).OK)
require.True(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK)
require.True(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), logContent).OK)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
assert.NotPanics(t, func() {
@ -152,8 +155,8 @@ func TestIngest_IngestFindings_Bad_NoStatusFile(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
assert.NotPanics(t, func() {
@ -169,12 +172,13 @@ func TestIngest_IngestFindings_Bad_ShortLogFile(t *testing.T) {
}))
// Log content is less than 100 bytes — should skip
require.True(t, fs.Write(core.JoinPath(wsDir, "agent-codex.log"), "short").OK)
require.True(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK)
require.True(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), "short").OK)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
assert.NotPanics(t, func() {
@ -203,12 +207,17 @@ func TestIngest_CreateIssueViaAPI_Good_Success(t *testing.T) {
}))
t.Cleanup(srv.Close)
home := t.TempDir()
t.Setenv("HOME", home)
require.True(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK)
require.True(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-key").OK)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.createIssueViaAPI("go-io", "Test Issue", "Description", "bug", "high", "scan")
@ -218,9 +227,9 @@ func TestIngest_CreateIssueViaAPI_Good_Success(t *testing.T) {
func TestIngest_CreateIssueViaAPI_Bad_NoBrainKey(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainKey: "",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
brainKey: "",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Should return early without panic
@ -231,15 +240,15 @@ func TestIngest_CreateIssueViaAPI_Bad_NoBrainKey(t *testing.T) {
func TestIngest_CreateIssueViaAPI_Bad_NoAPIKey(t *testing.T) {
home := t.TempDir()
t.Setenv("DIR_HOME", home)
t.Setenv("HOME", home)
// No agent-api.key file
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainURL: "https://example.com",
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
brainURL: "https://example.com",
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Should return early — no API key file
@ -255,16 +264,16 @@ func TestIngest_CreateIssueViaAPI_Bad_ServerError(t *testing.T) {
t.Cleanup(srv.Close)
home := t.TempDir()
t.Setenv("DIR_HOME", home)
t.Setenv("HOME", home)
require.True(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK)
require.True(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-key").OK)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Should not panic even on server error
@ -296,8 +305,8 @@ func TestIngest_IngestFindings_Ugly(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Should return early without panic — no log files
@ -323,16 +332,16 @@ func TestIngest_CreateIssueViaAPI_Ugly(t *testing.T) {
t.Cleanup(srv.Close)
home := t.TempDir()
t.Setenv("DIR_HOME", home)
t.Setenv("HOME", home)
require.True(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK)
require.True(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-key").OK)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.createIssueViaAPI("go-io", "XSS Test", "<script>alert('xss')</script><b>bold</b>&amp;", "bug", "high", "scan")

View file

@ -29,6 +29,13 @@ func WorkspaceRoot() string {
return core.JoinPath(CoreRoot(), "workspace")
}
// WorkspaceStatusPaths returns all workspace status files across supported layouts.
//
// paths := agentic.WorkspaceStatusPaths()
func WorkspaceStatusPaths() []string {
return workspaceStatusPaths(WorkspaceRoot())
}
// WorkspaceName extracts the unique workspace name from a full path.
// Given /Users/snider/Code/.core/workspace/core/go-io/dev → core/go-io/dev
//
@ -54,6 +61,32 @@ func CoreRoot() string {
return core.JoinPath(core.Env("DIR_HOME"), "Code", ".core")
}
func workspaceStatusPaths(wsRoot string) []string {
old := core.PathGlob(core.JoinPath(wsRoot, "*", "status.json"))
deep := core.PathGlob(core.JoinPath(wsRoot, "*", "*", "*", "status.json"))
return append(old, deep...)
}
func workspaceRepoDir(wsDir string) string {
return core.JoinPath(wsDir, "repo")
}
func workspaceMetaDir(wsDir string) string {
return core.JoinPath(wsDir, ".meta")
}
func workspaceBlockedPath(wsDir string) string {
return core.JoinPath(workspaceRepoDir(wsDir), "BLOCKED.md")
}
func workspaceAnswerPath(wsDir string) string {
return core.JoinPath(workspaceRepoDir(wsDir), "ANSWER.md")
}
func workspaceLogFiles(wsDir string) []string {
return core.PathGlob(core.JoinPath(workspaceMetaDir(wsDir), "agent-*.log"))
}
// PlansRoot returns the root directory for agent plans.
//
// plansDir := agentic.PlansRoot()

View file

@ -6,7 +6,6 @@ import (
"context"
core "dappco.re/go/core"
"dappco.re/go/core/forge"
forge_types "dappco.re/go/core/forge/types"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -157,16 +156,17 @@ func (s *PrepSubsystem) buildPRBody(st *WorkspaceStatus) string {
}
func (s *PrepSubsystem) forgeCreatePR(ctx context.Context, org, repo, head, base, title, body string) (string, int, error) {
pr, err := s.forge.Pulls.Create(ctx, forge.Params{"owner": org, "repo": repo}, &forge_types.CreatePullRequestOption{
var pr pullRequestView
err := s.forge.Client().Post(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls", org, repo), &forge_types.CreatePullRequestOption{
Title: title,
Body: body,
Head: head,
Base: base,
})
}, &pr)
if err != nil {
return "", 0, core.E("forgeCreatePR", "create PR failed", err)
}
return pr.HTMLURL, int(pr.Index), nil
return pr.HTMLURL, int(pullRequestNumber(pr)), nil
}
func (s *PrepSubsystem) commentOnIssue(ctx context.Context, org, repo string, issue int, comment string) {
@ -269,30 +269,31 @@ func (s *PrepSubsystem) listPRs(ctx context.Context, _ *mcp.CallToolRequest, inp
}
func (s *PrepSubsystem) listRepoPRs(ctx context.Context, org, repo, state string) ([]PRInfo, error) {
prs, err := s.forge.Pulls.ListAll(ctx, forge.Params{"owner": org, "repo": repo})
var prs []pullRequestView
err := s.forge.Client().Get(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls?limit=50&page=1", org, repo), &prs)
if err != nil {
return nil, core.E("listRepoPRs", core.Concat("failed to list PRs for ", repo), err)
}
var result []PRInfo
for _, pr := range prs {
if state != "" && state != "all" && string(pr.State) != state {
prState := pr.State
if prState == "" {
prState = "open"
}
if state != "" && state != "all" && prState != state {
continue
}
var labels []string
for _, l := range pr.Labels {
labels = append(labels, l.Name)
}
author := ""
if pr.User != nil {
author = pr.User.UserName
}
result = append(result, PRInfo{
Repo: repo,
Number: int(pr.Index),
Number: int(pullRequestNumber(pr)),
Title: pr.Title,
State: string(pr.State),
Author: author,
State: prState,
Author: pullRequestAuthor(pr),
Branch: pr.Head.Ref,
Base: pr.Base.Ref,
Labels: labels,

View file

@ -13,7 +13,7 @@ import (
"dappco.re/go/agent/pkg/lib"
core "dappco.re/go/core"
"dappco.re/go/core/forge"
coremcp "dappco.re/go/mcp/pkg/mcp"
coremcp "forge.lthn.ai/core/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
"gopkg.in/yaml.v3"
)
@ -37,8 +37,8 @@ type PrepSubsystem struct {
drainMu sync.Mutex
pokeCh chan struct{}
frozen bool
backoff map[string]time.Time // pool → paused until
failCount map[string]int // pool → consecutive fast failures
backoff map[string]time.Time // pool → paused until
failCount map[string]int // pool → consecutive fast failures
workspaces *core.Registry[*WorkspaceStatus] // in-memory workspace state
}
@ -410,8 +410,8 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
return nil, PrepOutput{}, err
}
repoDir := core.JoinPath(wsDir, "repo")
metaDir := core.JoinPath(wsDir, ".meta")
repoDir := workspaceRepoDir(wsDir)
metaDir := workspaceMetaDir(wsDir)
out := PrepOutput{WorkspaceDir: wsDir, RepoDir: repoDir}
// Source repo path — sanitise to prevent path traversal
@ -431,9 +431,22 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
out.Resumed = resumed
if resumed {
// Pull latest from origin so the workspace has current code
s.gitCmd(ctx, repoDir, "checkout", "main")
s.gitCmd(ctx, repoDir, "pull", "origin", "main")
// Preserve the current branch on resume. Pull it only if it exists on
// origin; otherwise refresh the default branch refs without abandoning the
// workspace branch.
currentBranch := s.gitOutput(ctx, repoDir, "rev-parse", "--abbrev-ref", "HEAD")
defaultBranch := s.DefaultBranch(repoDir)
if currentBranch == "" || currentBranch == "HEAD" {
currentBranch = defaultBranch
}
if currentBranch != "" {
s.gitCmd(ctx, repoDir, "checkout", currentBranch)
if s.gitCmdOK(ctx, repoDir, "ls-remote", "--exit-code", "--heads", "origin", currentBranch) {
s.gitCmd(ctx, repoDir, "pull", "--ff-only", "origin", currentBranch)
} else if defaultBranch != "" {
s.gitCmd(ctx, repoDir, "fetch", "origin", defaultBranch)
}
}
}
// Extract default workspace template (go.work etc.)

View file

@ -10,7 +10,6 @@ import (
"time"
core "dappco.re/go/core"
"dappco.re/go/core/process"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -24,7 +23,7 @@ var testCore *core.Core
// TestMain sets up a PrepSubsystem with go-process registered for all tests in the package.
func TestMain(m *testing.M) {
testCore = core.New(
core.WithService(process.Register),
core.WithService(ProcessRegister),
)
testCore.ServiceStartup(context.Background(), nil)

View file

@ -3,17 +3,99 @@
package agentic
import (
"context"
core "dappco.re/go/core"
"dappco.re/go/core/process"
)
// ProcessRegister is the service factory for go-process.
// Delegates to process.Register — named Actions (process.run, process.start,
// process.kill, process.list, process.get) are registered during OnStartup.
// Registers the process service under the canonical "process" name and exposes
// the Core `process.*` Actions expected by `c.Process()`.
//
// core.New(
// core.WithService(agentic.ProcessRegister),
// )
func ProcessRegister(c *core.Core) core.Result {
return process.Register(c)
if c == nil {
return core.Result{Value: core.E("agentic.ProcessRegister", "core is required", nil), OK: false}
}
if _, ok := core.ServiceFor[*process.Service](c, "process"); ok {
return core.Result{OK: true}
}
factory := process.NewService(process.Options{})
instance, err := factory(c)
if err != nil {
return core.Result{Value: core.E("agentic.ProcessRegister", "create process service", err), OK: false}
}
svc, ok := instance.(*process.Service)
if !ok {
return core.Result{Value: core.E("agentic.ProcessRegister", "unexpected process service type", nil), OK: false}
}
if r := c.RegisterService("process", svc); !r.OK {
return r
}
c.Action("process.run", func(ctx context.Context, opts core.Options) core.Result {
output, err := svc.RunWithOptions(ctx, process.RunOptions{
Command: opts.String("command"),
Args: optionStrings(opts, "args"),
Dir: opts.String("dir"),
Env: optionStrings(opts, "env"),
})
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
})
c.Action("process.start", func(ctx context.Context, opts core.Options) core.Result {
proc, err := svc.StartWithOptions(ctx, process.RunOptions{
Command: opts.String("command"),
Args: optionStrings(opts, "args"),
Dir: opts.String("dir"),
Env: optionStrings(opts, "env"),
Detach: opts.Bool("detach"),
})
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: proc, OK: true}
})
c.Action("process.kill", func(_ context.Context, opts core.Options) core.Result {
id := opts.String("id")
if id == "" {
return core.Result{Value: core.E("agentic.ProcessRegister", "process id is required", nil), OK: false}
}
if err := svc.Kill(id); err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{OK: true}
})
return core.Result{OK: true}
}
func optionStrings(opts core.Options, key string) []string {
r := opts.Get(key)
if !r.OK {
return nil
}
switch values := r.Value.(type) {
case []string:
return values
case []any:
out := make([]string, 0, len(values))
for _, value := range values {
out = append(out, core.Sprint(value))
}
return out
default:
if text := core.Sprint(values); text != "" {
return []string{text}
}
}
return nil
}

View file

@ -63,7 +63,7 @@ func TestRegister_ProcessRegister_Good(t *testing.T) {
c := core.New()
result := ProcessRegister(c)
assert.True(t, result.OK, "ProcessRegister should succeed with a real Core instance")
assert.NotNil(t, result.Value)
assert.True(t, c.Process().Exists(), "ProcessRegister should register the process service")
}
func TestRegister_ProcessRegister_Bad(t *testing.T) {

View file

@ -69,7 +69,7 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
// Write ANSWER.md if answer provided
if input.Answer != "" {
answerPath := core.JoinPath(repoDir, "ANSWER.md")
answerPath := workspaceAnswerPath(wsDir)
content := core.Sprintf("# Answer\n\n%s\n", input.Answer)
if r := fs.Write(answerPath, content); !r.OK {
err, _ := r.Value.(error)
@ -111,6 +111,6 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
Workspace: input.Workspace,
Agent: agent,
PID: pid,
OutputFile: core.JoinPath(wsDir, core.Sprintf("agent-%s.log", agent)),
OutputFile: agentOutputFile(wsDir, agent),
}, nil
}

View file

@ -14,8 +14,9 @@ import (
// Workspace status file convention:
//
// {workspace}/status.json — current state of the workspace
// {workspace}/BLOCKED.md — question the agent needs answered (written by agent)
// {workspace}/ANSWER.md — response from human (written by reviewer)
// {workspace}/repo/BLOCKED.md — question the agent needs answered (written by agent)
// {workspace}/repo/ANSWER.md — response from human (written by reviewer)
// {workspace}/.meta/agent-*.log — captured agent output
//
// Status lifecycle:
// running → completed (normal finish)
@ -29,20 +30,20 @@ import (
// st, err := ReadStatus(wsDir)
// if err == nil && st.Status == "completed" { autoCreatePR(wsDir) }
type WorkspaceStatus struct {
Status string `json:"status"` // running, completed, blocked, failed
Agent string `json:"agent"` // gemini, claude, codex
Repo string `json:"repo"` // target repo
Org string `json:"org,omitempty"` // forge org (e.g. "core")
Task string `json:"task"` // task description
Branch string `json:"branch,omitempty"` // git branch name
Status string `json:"status"` // running, completed, blocked, failed
Agent string `json:"agent"` // gemini, claude, codex
Repo string `json:"repo"` // target repo
Org string `json:"org,omitempty"` // forge org (e.g. "core")
Task string `json:"task"` // task description
Branch string `json:"branch,omitempty"` // git branch name
Issue int `json:"issue,omitempty"` // forge issue number
PID int `json:"pid,omitempty"` // OS process ID (if running)
ProcessID string `json:"process_id,omitempty"` // go-process ID for managed lookup
StartedAt time.Time `json:"started_at"` // when dispatch started
UpdatedAt time.Time `json:"updated_at"` // last status change
Question string `json:"question,omitempty"` // from BLOCKED.md
Runs int `json:"runs"` // how many times dispatched/resumed
PRURL string `json:"pr_url,omitempty"` // pull request URL (after PR created)
UpdatedAt time.Time `json:"updated_at"` // last status change
Question string `json:"question,omitempty"` // from BLOCKED.md
Runs int `json:"runs"` // how many times dispatched/resumed
PRURL string `json:"pr_url,omitempty"` // pull request URL (after PR created)
}
// WorkspaceQuery is the QUERY type for workspace state lookups.
@ -98,12 +99,12 @@ type StatusInput struct {
//
// out := agentic.StatusOutput{Total: 42, Running: 3, Queued: 10, Completed: 25}
type StatusOutput struct {
Total int `json:"total"`
Running int `json:"running"`
Queued int `json:"queued"`
Completed int `json:"completed"`
Failed int `json:"failed"`
Blocked []BlockedInfo `json:"blocked,omitempty"`
Total int `json:"total"`
Running int `json:"running"`
Queued int `json:"queued"`
Completed int `json:"completed"`
Failed int `json:"failed"`
Blocked []BlockedInfo `json:"blocked,omitempty"`
}
// BlockedInfo shows a workspace that needs human input.
@ -126,10 +127,7 @@ func (s *PrepSubsystem) registerStatusTool(server *mcp.Server) {
func (s *PrepSubsystem) status(ctx context.Context, _ *mcp.CallToolRequest, input StatusInput) (*mcp.CallToolResult, StatusOutput, error) {
wsRoot := WorkspaceRoot()
// Scan both old (*/status.json) and new (*/*/*/status.json) layouts
old := core.PathGlob(core.JoinPath(wsRoot, "*", "status.json"))
deep := core.PathGlob(core.JoinPath(wsRoot, "*", "*", "*", "status.json"))
statusFiles := append(old, deep...)
statusFiles := WorkspaceStatusPaths()
var out StatusOutput
@ -147,13 +145,12 @@ func (s *PrepSubsystem) status(ctx context.Context, _ *mcp.CallToolRequest, inpu
// If status is "running", check if PID is still alive
if st.Status == "running" && st.PID > 0 {
if err := syscall.Kill(st.PID, 0); err != nil {
blockedPath := core.JoinPath(wsDir, "repo", "BLOCKED.md")
blockedPath := workspaceBlockedPath(wsDir)
if r := fs.Read(blockedPath); r.OK {
st.Status = "blocked"
st.Question = core.Trim(r.Value.(string))
} else {
logFile := core.JoinPath(wsDir, core.Sprintf("agent-%s.log", st.Agent))
if r := fs.Read(logFile); !r.OK {
if len(workspaceLogFiles(wsDir)) == 0 {
st.Status = "failed"
st.Question = "Agent process died (no output log)"
} else {

View file

@ -199,13 +199,14 @@ func TestStatus_Status_Ugly(t *testing.T) {
// Case 2: running + dead PID + agent log → completed
ws2 := core.JoinPath(wsRoot, "dead-completed")
require.True(t, fs.EnsureDir(core.JoinPath(ws2, "repo")).OK)
require.True(t, fs.EnsureDir(core.JoinPath(ws2, ".meta")).OK)
require.NoError(t, writeStatus(ws2, &WorkspaceStatus{
Status: "running",
Repo: "go-log",
Agent: "claude",
PID: 999999,
}))
require.True(t, fs.Write(core.JoinPath(ws2, "agent-claude.log"), "agent finished ok").OK)
require.True(t, fs.Write(core.JoinPath(ws2, ".meta", "agent-claude.log"), "agent finished ok").OK)
// Case 3: running + dead PID + no log + no BLOCKED.md → failed
ws3 := core.JoinPath(wsRoot, "dead-failed")
@ -219,8 +220,8 @@ func TestStatus_Status_Ugly(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, out, err := s.status(nil, nil, StatusInput{})
@ -336,8 +337,8 @@ func TestStatus_Status_Good_PopulatedWorkspaces(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, out, err := s.status(context.Background(), nil, StatusInput{})
@ -356,8 +357,8 @@ func TestStatus_Status_Bad_EmptyWorkspaceRoot(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, out, err := s.status(context.Background(), nil, StatusInput{})

View file

@ -191,6 +191,16 @@ type verifyResult struct {
testCmd string
}
func resultText(r core.Result) string {
if text, ok := r.Value.(string); ok {
return text
}
if r.Value != nil {
return core.Sprint(r.Value)
}
return ""
}
// runVerification detects the project type and runs the appropriate test suite.
func (s *PrepSubsystem) runVerification(repoDir string) verifyResult {
if fileExists(core.JoinPath(repoDir, "go.mod")) {
@ -208,7 +218,7 @@ func (s *PrepSubsystem) runVerification(repoDir string) verifyResult {
func (s *PrepSubsystem) runGoTests(repoDir string) verifyResult {
ctx := context.Background()
r := s.runCmdEnv(ctx, repoDir, []string{"GOWORK=off"}, "go", "test", "./...", "-count=1", "-timeout", "120s")
out := r.Value.(string)
out := resultText(r)
exitCode := 0
if !r.OK {
exitCode = 1
@ -225,9 +235,9 @@ func (s *PrepSubsystem) runPHPTests(repoDir string) verifyResult {
if !r2.OK {
return verifyResult{passed: false, testCmd: "none", output: "No PHP test runner found (composer test and vendor/bin/pest both unavailable)", exitCode: 1}
}
return verifyResult{passed: true, output: r2.Value.(string), exitCode: 0, testCmd: "vendor/bin/pest"}
return verifyResult{passed: true, output: resultText(r2), exitCode: 0, testCmd: "vendor/bin/pest"}
}
return verifyResult{passed: true, output: r.Value.(string), exitCode: 0, testCmd: "composer test"}
return verifyResult{passed: true, output: resultText(r), exitCode: 0, testCmd: "composer test"}
}
func (s *PrepSubsystem) runNodeTests(repoDir string) verifyResult {
@ -245,7 +255,7 @@ func (s *PrepSubsystem) runNodeTests(repoDir string) verifyResult {
ctx := context.Background()
r = s.runCmd(ctx, repoDir, "npm", "test")
out := r.Value.(string)
out := resultText(r)
exitCode := 0
if !r.OK {
exitCode = 1

View file

@ -190,18 +190,15 @@ func (s *PrepSubsystem) watch(ctx context.Context, req *mcp.CallToolRequest, inp
// findActiveWorkspaces returns workspace names that are running or queued.
func (s *PrepSubsystem) findActiveWorkspaces() []string {
wsRoot := WorkspaceRoot()
entries := core.PathGlob(core.JoinPath(wsRoot, "*/status.json"))
var active []string
for _, entry := range entries {
for _, entry := range WorkspaceStatusPaths() {
wsDir := core.PathDir(entry)
st, err := ReadStatus(wsDir)
if err != nil {
continue
}
if st.Status == "running" || st.Status == "queued" {
active = append(active, core.PathBase(wsDir))
active = append(active, WorkspaceName(wsDir))
}
}
return active

View file

@ -15,8 +15,8 @@ import (
func TestWatch_ResolveWorkspaceDir_Good_RelativeName(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
dir := s.resolveWorkspaceDir("go-io-abc123")
assert.Contains(t, dir, "go-io-abc123")
@ -26,8 +26,8 @@ func TestWatch_ResolveWorkspaceDir_Good_RelativeName(t *testing.T) {
func TestWatch_ResolveWorkspaceDir_Good_AbsolutePath(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
abs := "/some/absolute/path"
assert.Equal(t, abs, s.resolveWorkspaceDir(abs))
@ -58,8 +58,8 @@ func TestWatch_FindActiveWorkspaces_Good_WithActive(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
active := s.findActiveWorkspaces()
assert.Contains(t, active, "ws-running")
@ -67,6 +67,25 @@ func TestWatch_FindActiveWorkspaces_Good_WithActive(t *testing.T) {
assert.NotContains(t, active, "ws-done")
}
func TestWatch_FindActiveWorkspaces_Good_DeepLayout(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
ws := core.JoinPath(root, "workspace", "core", "go-io", "task-15")
fs.EnsureDir(ws)
fs.Write(core.JoinPath(ws, "status.json"), core.JSONMarshalString(WorkspaceStatus{
Status: "running", Repo: "go-io", Agent: "codex",
}))
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
active := s.findActiveWorkspaces()
assert.Contains(t, active, "core/go-io/task-15")
}
func TestWatch_FindActiveWorkspaces_Good_Empty(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
@ -76,8 +95,8 @@ func TestWatch_FindActiveWorkspaces_Good_Empty(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
active := s.findActiveWorkspaces()
assert.Empty(t, active)
@ -92,8 +111,8 @@ func TestWatch_FindActiveWorkspaces_Bad(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
assert.NotPanics(t, func() {
active := s.findActiveWorkspaces()
@ -119,8 +138,8 @@ func TestWatch_FindActiveWorkspaces_Ugly(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
active := s.findActiveWorkspaces()
@ -135,8 +154,8 @@ func TestWatch_ResolveWorkspaceDir_Bad(t *testing.T) {
// Empty name
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
dir := s.resolveWorkspaceDir("")
assert.NotEmpty(t, dir, "empty name should still resolve to workspace root")
@ -147,8 +166,8 @@ func TestWatch_ResolveWorkspaceDir_Ugly(t *testing.T) {
// Name with path traversal "../.."
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
assert.NotPanics(t, func() {
dir := s.resolveWorkspaceDir("../..")

View file

@ -9,7 +9,7 @@ import (
"dappco.re/go/agent/pkg/agentic"
core "dappco.re/go/core"
"dappco.re/go/mcp/pkg/mcp/ide"
"forge.lthn.ai/core/mcp/pkg/mcp/ide"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

View file

@ -11,9 +11,8 @@ import (
"time"
core "dappco.re/go/core"
providerws "dappco.re/go/core/ws"
bridgews "forge.lthn.ai/core/go-ws"
"dappco.re/go/mcp/pkg/mcp/ide"
providerws "forge.lthn.ai/core/go-ws"
"forge.lthn.ai/core/mcp/pkg/mcp/ide"
"github.com/gorilla/websocket"
mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
@ -46,7 +45,7 @@ func testBridge(t *testing.T) *ide.Bridge {
srv := testWSServer(t)
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
hub := bridgews.NewHub()
hub := providerws.NewHub()
bridge := ide.NewBridge(hub, ide.Config{
LaravelWSURL: wsURL,
ReconnectInterval: 100 * time.Millisecond,

View file

@ -8,7 +8,7 @@ import (
"dappco.re/go/agent/pkg/agentic"
core "dappco.re/go/core"
coremcp "dappco.re/go/mcp/pkg/mcp"
coremcp "forge.lthn.ai/core/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

View file

@ -8,8 +8,8 @@ import (
"dappco.re/go/core/api"
"dappco.re/go/core/api/pkg/provider"
"dappco.re/go/core/ws"
"dappco.re/go/mcp/pkg/mcp/ide"
"forge.lthn.ai/core/go-ws"
"forge.lthn.ai/core/mcp/pkg/mcp/ide"
"github.com/gin-gonic/gin"
)

View file

@ -7,7 +7,7 @@ import (
"time"
core "dappco.re/go/core"
"dappco.re/go/mcp/pkg/mcp/ide"
"forge.lthn.ai/core/mcp/pkg/mcp/ide"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

View file

@ -88,35 +88,35 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
return nil
}
srcDir := core.Concat(wsDir, "/src")
if !fs.IsDir(srcDir) {
repoDir := core.JoinPath(wsDir, "repo")
if !fs.IsDir(repoDir) {
return nil
}
// Check if there are commits to push
branch := st.Branch
if branch == "" {
branch = m.detectBranch(srcDir)
branch = m.detectBranch(repoDir)
}
base := m.defaultBranch(srcDir)
base := m.defaultBranch(repoDir)
if branch == "" || branch == base {
return nil
}
// Check for unpushed commits
unpushed := m.countUnpushed(srcDir, branch)
unpushed := m.countUnpushed(repoDir, branch)
if unpushed == 0 {
return nil // already pushed or no commits
}
// Safety checks before pushing
if reason := m.checkSafety(srcDir); reason != "" {
if reason := m.checkSafety(repoDir); reason != "" {
updateStatus(wsDir, "rejected", reason)
return &harvestResult{repo: st.Repo, branch: branch, rejected: reason}
}
// Count changed files
files := m.countChangedFiles(srcDir)
files := m.countChangedFiles(repoDir)
// Mark ready for review — do NOT auto-push.
// Pushing is a high-impact mutation that should happen during

View file

@ -6,9 +6,9 @@ import (
"context"
"testing"
"dappco.re/go/agent/pkg/agentic"
"dappco.re/go/agent/pkg/messages"
core "dappco.re/go/core"
"dappco.re/go/core/process"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -26,17 +26,17 @@ func initTestRepo(t *testing.T) (sourceDir, wsDir string) {
run(t, sourceDir, "git", "add", ".")
run(t, sourceDir, "git", "commit", "-m", "init")
// Create workspace dir with src/ clone
// Create workspace dir with repo/ clone
wsDir = core.JoinPath(t.TempDir(), "workspace")
srcDir := core.JoinPath(wsDir, "src")
repoDir := core.JoinPath(wsDir, "repo")
fs.EnsureDir(wsDir)
run(t, wsDir, "git", "clone", sourceDir, "src")
run(t, wsDir, "git", "clone", sourceDir, "repo")
// Create agent branch with a commit
run(t, srcDir, "git", "checkout", "-b", "agent/test-task")
fs.Write(core.JoinPath(srcDir, "new.go"), "package main\n")
run(t, srcDir, "git", "add", ".")
run(t, srcDir, "git", "commit", "-m", "agent work")
run(t, repoDir, "git", "checkout", "-b", "agent/test-task")
fs.Write(core.JoinPath(repoDir, "new.go"), "package main\n")
run(t, repoDir, "git", "add", ".")
run(t, repoDir, "git", "commit", "-m", "agent work")
return sourceDir, wsDir
}
@ -62,9 +62,9 @@ func writeStatus(t *testing.T, wsDir, status, repo, branch string) {
func TestHarvest_DetectBranch_Good(t *testing.T) {
_, wsDir := initTestRepo(t)
srcDir := core.JoinPath(wsDir, "src")
repoDir := core.JoinPath(wsDir, "repo")
branch := testMon.detectBranch(srcDir)
branch := testMon.detectBranch(repoDir)
assert.Equal(t, "agent/test-task", branch)
}
@ -75,53 +75,53 @@ func TestHarvest_DetectBranch_Bad_NoRepo(t *testing.T) {
func TestHarvest_CountUnpushed_Good(t *testing.T) {
_, wsDir := initTestRepo(t)
srcDir := core.JoinPath(wsDir, "src")
repoDir := core.JoinPath(wsDir, "repo")
count := testMon.countUnpushed(srcDir, "agent/test-task")
count := testMon.countUnpushed(repoDir, "agent/test-task")
assert.Equal(t, 1, count)
}
func TestHarvest_CountChangedFiles_Good(t *testing.T) {
_, wsDir := initTestRepo(t)
srcDir := core.JoinPath(wsDir, "src")
repoDir := core.JoinPath(wsDir, "repo")
count := testMon.countChangedFiles(srcDir)
count := testMon.countChangedFiles(repoDir)
assert.Equal(t, 1, count)
}
func TestHarvest_CheckSafety_Good_CleanWorkspace(t *testing.T) {
_, wsDir := initTestRepo(t)
srcDir := core.JoinPath(wsDir, "src")
repoDir := core.JoinPath(wsDir, "repo")
reason := testMon.checkSafety(srcDir)
reason := testMon.checkSafety(repoDir)
assert.Equal(t, "", reason)
}
func TestHarvest_CheckSafety_Bad_BinaryFile(t *testing.T) {
_, wsDir := initTestRepo(t)
srcDir := core.JoinPath(wsDir, "src")
repoDir := core.JoinPath(wsDir, "repo")
// Add a binary file
fs.Write(core.JoinPath(srcDir, "app.exe"), "binary")
run(t, srcDir, "git", "add", ".")
run(t, srcDir, "git", "commit", "-m", "add binary")
fs.Write(core.JoinPath(repoDir, "app.exe"), "binary")
run(t, repoDir, "git", "add", ".")
run(t, repoDir, "git", "commit", "-m", "add binary")
reason := testMon.checkSafety(srcDir)
reason := testMon.checkSafety(repoDir)
assert.Contains(t, reason, "binary file added")
assert.Contains(t, reason, "app.exe")
}
func TestHarvest_CheckSafety_Bad_LargeFile(t *testing.T) {
_, wsDir := initTestRepo(t)
srcDir := core.JoinPath(wsDir, "src")
repoDir := core.JoinPath(wsDir, "repo")
// Add a file > 1MB
bigData := make([]byte, 1024*1024+1)
fs.Write(core.JoinPath(srcDir, "huge.txt"), string(bigData))
run(t, srcDir, "git", "add", ".")
run(t, srcDir, "git", "commit", "-m", "add large file")
fs.Write(core.JoinPath(repoDir, "huge.txt"), string(bigData))
run(t, repoDir, "git", "add", ".")
run(t, repoDir, "git", "commit", "-m", "add large file")
reason := testMon.checkSafety(srcDir)
reason := testMon.checkSafety(repoDir)
assert.Contains(t, reason, "large file")
assert.Contains(t, reason, "huge.txt")
}
@ -160,8 +160,8 @@ func TestHarvest_HarvestWorkspace_Bad_MainBranch(t *testing.T) {
_, wsDir := initTestRepo(t)
// Switch back to main
srcDir := core.JoinPath(wsDir, "src")
run(t, srcDir, "git", "checkout", "main")
repoDir := core.JoinPath(wsDir, "repo")
run(t, repoDir, "git", "checkout", "main")
writeStatus(t, wsDir, "completed", "test-repo", "main")
@ -173,12 +173,12 @@ func TestHarvest_HarvestWorkspace_Bad_MainBranch(t *testing.T) {
func TestHarvest_HarvestWorkspace_Bad_BinaryRejected(t *testing.T) {
_, wsDir := initTestRepo(t)
srcDir := core.JoinPath(wsDir, "src")
repoDir := core.JoinPath(wsDir, "repo")
// Add binary
fs.Write(core.JoinPath(srcDir, "build.so"), "elf")
run(t, srcDir, "git", "add", ".")
run(t, srcDir, "git", "commit", "-m", "add binary")
fs.Write(core.JoinPath(repoDir, "build.so"), "elf")
run(t, repoDir, "git", "add", ".")
run(t, repoDir, "git", "commit", "-m", "add binary")
writeStatus(t, wsDir, "completed", "test-repo", "agent/test-task")
@ -201,7 +201,7 @@ func TestHarvest_HarvestCompleted_Good_ChannelEvents(t *testing.T) {
// Create a Core with process + IPC handler to capture HarvestComplete messages
var captured []messages.HarvestComplete
c := core.New(core.WithService(process.Register))
c := core.New(core.WithService(agentic.ProcessRegister))
c.ServiceStartup(context.Background(), nil)
c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
if ev, ok := msg.(messages.HarvestComplete); ok {
@ -251,4 +251,3 @@ func TestHarvest_UpdateStatus_Good_WithQuestion(t *testing.T) {
assert.Equal(t, "rejected", st["status"])
assert.Equal(t, "binary file: app.exe", st["question"])
}

View file

@ -18,7 +18,7 @@ import (
"dappco.re/go/agent/pkg/agentic"
"dappco.re/go/agent/pkg/messages"
core "dappco.re/go/core"
coremcp "dappco.re/go/mcp/pkg/mcp"
coremcp "forge.lthn.ai/core/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -28,6 +28,10 @@ import (
// if text, ok := resultString(r); ok { json.Unmarshal([]byte(text), &st) }
var fs = agentic.LocalFs()
type channelSender interface {
ChannelSend(ctx context.Context, channel string, data any)
}
// workspaceStatusPaths returns all status.json files across both old and new workspace layouts.
// Old: workspace/{name}/status.json (1 level)
// New: workspace/{org}/{repo}/{identifier}/status.json (3 levels)
@ -54,6 +58,9 @@ func monitorHomeDir() string {
if d := core.Env("CORE_HOME"); d != "" {
return d
}
if d := core.Env("HOME"); d != "" {
return d
}
return core.Env("DIR_HOME")
}
@ -200,7 +207,7 @@ func (m *Subsystem) Start(ctx context.Context) {
monCtx, cancel := context.WithCancel(ctx)
m.cancel = cancel
core.Info( "monitor: started (interval=%s)", m.interval)
core.Info("monitor: started (interval=%s)", m.interval)
m.wg.Add(1)
go func() {
@ -240,7 +247,6 @@ func (m *Subsystem) Poke() {
}
}
// checkIdleAfterDelay waits briefly then checks if the fleet is genuinely idle.
// Only emits queue.drained when there are truly zero running or queued agents,
// verified by checking PIDs are alive, not just trusting status files.
@ -447,26 +453,13 @@ func (m *Subsystem) checkCompletions() string {
// checkInbox checks for unread messages.
func (m *Subsystem) checkInbox() string {
apiKeyStr := core.Env("CORE_BRAIN_KEY")
apiKeyStr := monitorBrainKey()
if apiKeyStr == "" {
home := core.Env("DIR_HOME")
keyFile := brainKeyPath(home)
r := fs.Read(keyFile)
if !r.OK {
return ""
}
value, ok := resultString(r)
if !ok {
return ""
}
apiKeyStr = value
return ""
}
// Call the API to check inbox
apiURL := core.Env("CORE_API_URL")
if apiURL == "" {
apiURL = "https://api.lthn.sh"
}
apiURL := monitorAPIURL()
inboxURL := core.Concat(apiURL, "/v1/messages/inbox?agent=", core.Replace(agentic.AgentName(), " ", "%20"))
hr := agentic.HTTPGet(context.Background(), inboxURL, core.Trim(apiKeyStr), "Bearer")
if !hr.OK {
@ -539,15 +532,14 @@ func (m *Subsystem) checkInbox() string {
// Push each message as a channel event so it lands in the session
if m.ServiceRuntime != nil {
for _, msg := range newMessages {
m.Core().ACTION(coremcp.ChannelPush{
Channel: "inbox.message",
Data: map[string]any{
if notifier, ok := core.ServiceFor[channelSender](m.Core(), "mcp"); ok {
for _, msg := range newMessages {
notifier.ChannelSend(context.Background(), "inbox.message", map[string]any{
"from": msg.From,
"subject": msg.Subject,
"content": msg.Content,
},
})
})
}
}
}

View file

@ -10,15 +10,27 @@ import (
"testing"
"time"
"dappco.re/go/agent/pkg/agentic"
"dappco.re/go/agent/pkg/messages"
core "dappco.re/go/core"
coremcp "dappco.re/go/mcp/pkg/mcp"
"dappco.re/go/core/process"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type capturedChannelEvent struct {
Channel string
Data any
}
type captureNotifier struct {
events []capturedChannelEvent
}
func (n *captureNotifier) ChannelSend(_ context.Context, channel string, data any) {
n.events = append(n.events, capturedChannelEvent{Channel: channel, Data: data})
}
// setupBrainKey creates a ~/.claude/brain.key file for API auth tests.
func setupBrainKey(t *testing.T, key string) {
t.Helper()
@ -260,15 +272,10 @@ func TestMonitor_CheckInbox_Good_UnreadMessages(t *testing.T) {
t.Setenv("CORE_API_URL", srv.URL)
t.Setenv("AGENT_NAME", "test-agent")
// Create Core with IPC handler to capture ChannelPush
var captured []coremcp.ChannelPush
// Create Core with an MCP notifier to capture channel events.
c := core.New()
c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
if ev, ok := msg.(coremcp.ChannelPush); ok {
captured = append(captured, ev)
}
return core.Result{OK: true}
})
notifier := &captureNotifier{}
require.True(t, c.RegisterService("mcp", notifier).OK)
mon := New()
mon.ServiceRuntime = core.NewServiceRuntime(c, MonitorOptions{})
@ -277,10 +284,10 @@ func TestMonitor_CheckInbox_Good_UnreadMessages(t *testing.T) {
msg := mon.checkInbox()
assert.Contains(t, msg, "2 unread message(s) in inbox")
// 3 new messages (ids 1,2,3 all > prevMaxID=0), but only 2 unread
// Monitor sends one ChannelPush per NEW message (higher ID than last seen)
require.Len(t, captured, 3)
data := captured[0].Data.(map[string]any)
// 3 new messages (ids 1,2,3 all > prevMaxID=0), but only 2 unread.
// Monitor sends one channel notification per new message.
require.Len(t, notifier.events, 3)
data := notifier.events[0].Data.(map[string]any)
assert.Equal(t, "clotho", data["from"])
}
@ -376,15 +383,10 @@ func TestMonitor_CheckInbox_Good_MultipleSameSender(t *testing.T) {
setupAPIEnv(t, srv.URL)
// Create Core with IPC handler to capture ChannelPush
var captured []coremcp.ChannelPush
// Create Core with an MCP notifier to capture channel events.
c := core.New()
c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
if ev, ok := msg.(coremcp.ChannelPush); ok {
captured = append(captured, ev)
}
return core.Result{OK: true}
})
notifier := &captureNotifier{}
require.True(t, c.RegisterService("mcp", notifier).OK)
mon := New()
mon.ServiceRuntime = core.NewServiceRuntime(c, MonitorOptions{})
@ -393,7 +395,7 @@ func TestMonitor_CheckInbox_Good_MultipleSameSender(t *testing.T) {
msg := mon.checkInbox()
assert.Contains(t, msg, "3 unread message(s)")
require.True(t, len(captured) > 0, "should capture at least one ChannelPush")
require.True(t, len(notifier.events) > 0, "should capture at least one channel event")
}
// --- check (integration of sub-checks) ---
@ -808,19 +810,19 @@ func TestMonitor_HarvestCompleted_Good_MultipleWorkspaces(t *testing.T) {
run(t, sourceDir, "git", "commit", "-m", "init")
fs.EnsureDir(wsDir)
run(t, wsDir, "git", "clone", sourceDir, "src")
srcDir := core.JoinPath(wsDir, "src")
run(t, srcDir, "git", "checkout", "-b", "agent/test-task")
fs.Write(core.JoinPath(srcDir, "new.go"), "package main\n")
run(t, srcDir, "git", "add", ".")
run(t, srcDir, "git", "commit", "-m", "agent work")
run(t, wsDir, "git", "clone", sourceDir, "repo")
repoDir := core.JoinPath(wsDir, "repo")
run(t, repoDir, "git", "checkout", "-b", "agent/test-task")
fs.Write(core.JoinPath(repoDir, "new.go"), "package main\n")
run(t, repoDir, "git", "add", ".")
run(t, repoDir, "git", "commit", "-m", "agent work")
writeStatus(t, wsDir, "completed", fmt.Sprintf("repo-%d", i), "agent/test-task")
}
// Create Core with IPC handler to capture HarvestComplete messages
var harvests []messages.HarvestComplete
c := core.New(core.WithService(process.Register))
c := core.New(core.WithService(agentic.ProcessRegister))
c.ServiceStartup(context.Background(), nil)
c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
if ev, ok := msg.(messages.HarvestComplete); ok {
@ -865,23 +867,23 @@ func TestMonitor_HarvestCompleted_Good_RejectedWorkspace(t *testing.T) {
wsDir := core.JoinPath(wsRoot, "workspace", "ws-rej")
fs.EnsureDir(wsDir)
run(t, wsDir, "git", "clone", sourceDir, "src")
srcDir := core.JoinPath(wsDir, "src")
run(t, srcDir, "git", "checkout", "-b", "agent/test-task")
fs.Write(core.JoinPath(srcDir, "new.go"), "package main\n")
run(t, srcDir, "git", "add", ".")
run(t, srcDir, "git", "commit", "-m", "agent work")
run(t, wsDir, "git", "clone", sourceDir, "repo")
repoDir := core.JoinPath(wsDir, "repo")
run(t, repoDir, "git", "checkout", "-b", "agent/test-task")
fs.Write(core.JoinPath(repoDir, "new.go"), "package main\n")
run(t, repoDir, "git", "add", ".")
run(t, repoDir, "git", "commit", "-m", "agent work")
// Add binary to trigger rejection
fs.Write(core.JoinPath(srcDir, "app.exe"), "binary")
run(t, srcDir, "git", "add", ".")
run(t, srcDir, "git", "commit", "-m", "add binary")
fs.Write(core.JoinPath(repoDir, "app.exe"), "binary")
run(t, repoDir, "git", "add", ".")
run(t, repoDir, "git", "commit", "-m", "add binary")
writeStatus(t, wsDir, "completed", "rej-repo", "agent/test-task")
// Create Core with IPC handler to capture HarvestRejected messages
var rejections []messages.HarvestRejected
c := core.New(core.WithService(process.Register))
c := core.New(core.WithService(agentic.ProcessRegister))
c.ServiceStartup(context.Background(), nil)
c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
if ev, ok := msg.(messages.HarvestRejected); ok {

View file

@ -7,14 +7,14 @@ import (
"os"
"testing"
"dappco.re/go/agent/pkg/agentic"
core "dappco.re/go/core"
"dappco.re/go/core/process"
)
var testMon *Subsystem
func TestMain(m *testing.M) {
c := core.New(core.WithService(process.Register))
c := core.New(core.WithService(agentic.ProcessRegister))
c.ServiceStartup(context.Background(), nil)
testMon = New()
testMon.ServiceRuntime = core.NewServiceRuntime(c, MonitorOptions{})

View file

@ -4,6 +4,7 @@ package runner
import (
"strconv"
"syscall"
"time"
core "dappco.re/go/core"
@ -65,10 +66,10 @@ func (c *ConcurrencyLimit) UnmarshalYAML(value *yaml.Node) error {
// AgentsConfig is the root of agents.yaml.
type AgentsConfig struct {
Version int `yaml:"version"`
Dispatch DispatchConfig `yaml:"dispatch"`
Concurrency map[string]ConcurrencyLimit `yaml:"concurrency"`
Rates map[string]RateConfig `yaml:"rates"`
Version int `yaml:"version"`
Dispatch DispatchConfig `yaml:"dispatch"`
Concurrency map[string]ConcurrencyLimit `yaml:"concurrency"`
Rates map[string]RateConfig `yaml:"rates"`
}
// loadAgentsConfig reads agents.yaml from known paths.
@ -147,7 +148,13 @@ func (s *Service) canDispatchAgent(agent string) (bool, string) {
func (s *Service) countRunningByAgent(agent string) int {
count := 0
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
if st.Status == "running" && baseAgent(st.Agent) == agent {
if st.Status != "running" || baseAgent(st.Agent) != agent {
return
}
switch {
case st.PID < 0:
count++
case st.PID > 0 && syscall.Kill(st.PID, 0) == nil:
count++
}
})
@ -158,7 +165,13 @@ func (s *Service) countRunningByAgent(agent string) int {
func (s *Service) countRunningByModel(agent string) int {
count := 0
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
if st.Status == "running" && st.Agent == agent {
if st.Status != "running" || st.Agent != agent {
return
}
switch {
case st.PID < 0:
count++
case st.PID > 0 && syscall.Kill(st.PID, 0) == nil:
count++
}
})

View file

@ -97,12 +97,14 @@ func TestQueue_ModelVariant_Ugly_Empty(t *testing.T) {
func TestQueue_CanDispatchAgent_Good_NoConfig(t *testing.T) {
svc := New()
assert.True(t, svc.canDispatchAgent("codex"), "no config → unlimited")
can, _ := svc.canDispatchAgent("codex")
assert.True(t, can, "no config → unlimited")
}
func TestQueue_CanDispatchAgent_Good_UnknownAgent(t *testing.T) {
svc := New()
assert.True(t, svc.canDispatchAgent("unknown-agent"))
can, _ := svc.canDispatchAgent("unknown-agent")
assert.True(t, can)
}
func TestQueue_CanDispatchAgent_Bad_AtLimit(t *testing.T) {
@ -114,13 +116,15 @@ func TestQueue_CanDispatchAgent_Bad_AtLimit(t *testing.T) {
})
}
// Since PIDs aren't alive, count should be 0
assert.True(t, svc.canDispatchAgent("codex"), "dead PIDs don't count")
can, _ := svc.canDispatchAgent("codex")
assert.True(t, can, "dead PIDs don't count")
}
func TestQueue_CanDispatchAgent_Ugly_ZeroLimit(t *testing.T) {
svc := New()
// Zero total means unlimited
assert.True(t, svc.canDispatchAgent("codex"))
can, _ := svc.canDispatchAgent("codex")
assert.True(t, can)
}
// --- countRunningByAgent ---

View file

@ -15,7 +15,6 @@ import (
"dappco.re/go/agent/pkg/messages"
core "dappco.re/go/core"
coremcp "dappco.re/go/mcp/pkg/mcp"
)
// Options configures the runner service.
@ -38,6 +37,10 @@ type Service struct {
workspaces *core.Registry[*WorkspaceStatus]
}
type channelSender interface {
ChannelSend(ctx context.Context, channel string, data any)
}
// New creates a runner service.
//
// svc := runner.New()
@ -140,26 +143,35 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
}
}
}
c.ACTION(coremcp.ChannelPush{
Channel: "agent.status",
Data: &AgentNotification{
Status: "started",
Repo: ev.Repo,
Agent: ev.Agent,
Workspace: ev.Workspace,
Running: running,
Limit: limit,
},
})
notification := &AgentNotification{
Status: "started",
Repo: ev.Repo,
Agent: ev.Agent,
Workspace: ev.Workspace,
Running: running,
Limit: limit,
}
if notifier, ok := core.ServiceFor[channelSender](c, "mcp"); ok {
notifier.ChannelSend(context.Background(), "agent.status", notification)
}
case messages.AgentCompleted:
// Update workspace status in Registry so concurrency count drops
s.workspaces.Each(func(name string, st *WorkspaceStatus) {
if st.Repo == ev.Repo && st.Status == "running" {
st.Status = ev.Status
st.PID = 0
if ev.Workspace != "" {
if r := s.workspaces.Get(ev.Workspace); r.OK {
if st, ok := r.Value.(*WorkspaceStatus); ok && st.Status == "running" {
st.Status = ev.Status
st.PID = 0
}
}
})
} else {
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
if st.Repo == ev.Repo && st.Status == "running" {
st.Status = ev.Status
st.PID = 0
}
})
}
cBase := baseAgent(ev.Agent)
cRunning := s.countRunningByAgent(cBase)
var cLimit int
@ -171,17 +183,17 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
}
}
}
c.ACTION(coremcp.ChannelPush{
Channel: "agent.status",
Data: &AgentNotification{
Status: ev.Status,
Repo: ev.Repo,
Agent: ev.Agent,
Workspace: ev.Workspace,
Running: cRunning,
Limit: cLimit,
},
})
notification := &AgentNotification{
Status: ev.Status,
Repo: ev.Repo,
Agent: ev.Agent,
Workspace: ev.Workspace,
Running: cRunning,
Limit: cLimit,
}
if notifier, ok := core.ServiceFor[channelSender](c, "mcp"); ok {
notifier.ChannelSend(context.Background(), "agent.status", notification)
}
s.Poke()
case messages.PokeQueue:

View file

@ -6,6 +6,7 @@ import (
"context"
"testing"
"dappco.re/go/agent/pkg/messages"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -304,6 +305,30 @@ func TestRunner_HandleIPCEvents_Good_UnknownMessage(t *testing.T) {
assert.True(t, r.OK)
}
func TestRunner_HandleIPCEvents_Good_UpdatesMatchingWorkspaceOnly(t *testing.T) {
c := core.New(core.WithOption("name", "test"))
svc := New()
svc.ServiceRuntime = core.NewServiceRuntime(c, Options{})
svc.TrackWorkspace("core/go-io/task-1", &WorkspaceStatus{
Status: "running", Agent: "codex", Repo: "go-io", PID: 111,
})
svc.TrackWorkspace("core/go-io/task-2", &WorkspaceStatus{
Status: "running", Agent: "codex", Repo: "go-io", PID: 222,
})
r := svc.HandleIPCEvents(c, messages.AgentCompleted{
Agent: "codex", Repo: "go-io", Workspace: "core/go-io/task-1", Status: "completed",
})
assert.True(t, r.OK)
first := svc.workspaces.Get("core/go-io/task-1").Value.(*WorkspaceStatus)
second := svc.workspaces.Get("core/go-io/task-2").Value.(*WorkspaceStatus)
assert.Equal(t, "completed", first.Status)
assert.Equal(t, 0, first.PID)
assert.Equal(t, "running", second.Status)
assert.Equal(t, 222, second.PID)
}
// --- WriteStatus / ReadStatus ---
func TestRunner_WriteReadStatus_Good(t *testing.T) {