From 85f3a0236148e4b1797b9ab70dac1d3ad5e1d4b5 Mon Sep 17 00:00:00 2001 From: Virgil Date: Tue, 31 Mar 2026 11:02:11 +0000 Subject: [PATCH] feat(agentic): add fleet platform actions and commands Co-Authored-By: Virgil --- go.sum | 183 ---- pkg/agentic/commands_platform.go | 500 +++++++++++ pkg/agentic/commands_platform_example_test.go | 17 + pkg/agentic/commands_platform_test.go | 65 ++ pkg/agentic/platform.go | 784 ++++++++++++++++++ pkg/agentic/platform_example_test.go | 18 + pkg/agentic/platform_test.go | 224 +++++ pkg/agentic/prep.go | 16 + 8 files changed, 1624 insertions(+), 183 deletions(-) create mode 100644 pkg/agentic/commands_platform.go create mode 100644 pkg/agentic/commands_platform_example_test.go create mode 100644 pkg/agentic/commands_platform_test.go create mode 100644 pkg/agentic/platform.go create mode 100644 pkg/agentic/platform_example_test.go create mode 100644 pkg/agentic/platform_test.go diff --git a/go.sum b/go.sum index bf9d149..37763b3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -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= @@ -12,30 +10,20 @@ 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.2.4/go.mod h1:C3riJyLLcV6QhLvYlq3P/XkGTsN598qQeGBoLdoHBU4= -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.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.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= @@ -44,17 +32,10 @@ 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 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw= 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 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= @@ -63,31 +44,13 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= 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 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= 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 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -105,36 +68,12 @@ github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xs 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 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= 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= @@ -143,20 +82,6 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= 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= @@ -187,7 +112,6 @@ github.com/gin-contrib/timeout v1.1.0 h1:WAmWseo5gfBUbMrMJu5hJxDclehfSJUmK2wGwCC 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= @@ -195,7 +119,6 @@ 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= @@ -203,7 +126,6 @@ github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZL 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 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -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= @@ -238,17 +160,12 @@ github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= 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.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= 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 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 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 v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 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= @@ -268,50 +185,20 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN 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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 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 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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= @@ -319,60 +206,31 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 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 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 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 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= 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= @@ -390,54 +248,31 @@ github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs 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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 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 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 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 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= 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 h1:B2Pew5ufEtgkjLF+tSkXjgYZXQr9m7aCm1wLKB0URbU= 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 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= 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= @@ -456,15 +291,12 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= 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 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE= 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-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= @@ -490,7 +322,6 @@ 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-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= @@ -502,7 +333,6 @@ 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= @@ -510,10 +340,8 @@ 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-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= 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= @@ -523,17 +351,6 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 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= diff --git a/pkg/agentic/commands_platform.go b/pkg/agentic/commands_platform.go new file mode 100644 index 0000000..5c72eba --- /dev/null +++ b/pkg/agentic/commands_platform.go @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + core "dappco.re/go/core" +) + +func (s *PrepSubsystem) registerPlatformCommands() { + c := s.Core() + c.Command("sync/push", core.Command{Description: "Push completed dispatch state to the platform API", Action: s.cmdSyncPush}) + c.Command("sync/pull", core.Command{Description: "Pull shared fleet context from the platform API", Action: s.cmdSyncPull}) + c.Command("sync/status", core.Command{Description: "Show platform sync status for the current or named agent", Action: s.cmdSyncStatus}) + + c.Command("fleet/register", core.Command{Description: "Register a fleet node with the platform API", Action: s.cmdFleetRegister}) + c.Command("fleet/heartbeat", core.Command{Description: "Send a heartbeat for a registered fleet node", Action: s.cmdFleetHeartbeat}) + c.Command("fleet/deregister", core.Command{Description: "Deregister a fleet node from the platform API", Action: s.cmdFleetDeregister}) + c.Command("fleet/nodes", core.Command{Description: "List registered fleet nodes", Action: s.cmdFleetNodes}) + c.Command("fleet/task/assign", core.Command{Description: "Assign a task to a fleet node", Action: s.cmdFleetTaskAssign}) + c.Command("fleet/task/complete", core.Command{Description: "Complete a fleet task and report findings", Action: s.cmdFleetTaskComplete}) + c.Command("fleet/task/next", core.Command{Description: "Ask the platform for the next fleet task", Action: s.cmdFleetTaskNext}) + c.Command("fleet/stats", core.Command{Description: "Show fleet activity statistics", Action: s.cmdFleetStats}) + + c.Command("credits/award", core.Command{Description: "Award credits to a fleet node", Action: s.cmdCreditsAward}) + c.Command("credits/balance", core.Command{Description: "Show credit balance for a fleet node", Action: s.cmdCreditsBalance}) + c.Command("credits/history", core.Command{Description: "Show credit history for a fleet node", Action: s.cmdCreditsHistory}) + + c.Command("subscription/detect", core.Command{Description: "Detect available provider capabilities", Action: s.cmdSubscriptionDetect}) + c.Command("subscription/budget", core.Command{Description: "Show compute budget for a fleet node", Action: s.cmdSubscriptionBudget}) + c.Command("subscription/update-budget", core.Command{Description: "Update compute budget for a fleet node", Action: s.cmdSubscriptionUpdateBudget}) +} + +func (s *PrepSubsystem) cmdSyncPush(options core.Options) core.Result { + result := s.handleSyncPush(s.commandContext(), options) + if !result.OK { + err := commandResultError("agentic.cmdSyncPush", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + output, ok := result.Value.(SyncPushOutput) + if !ok { + err := core.E("agentic.cmdSyncPush", "invalid sync push output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "synced: %d", output.Count) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdSyncPull(options core.Options) core.Result { + result := s.handleSyncPull(s.commandContext(), options) + if !result.OK { + err := commandResultError("agentic.cmdSyncPull", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + output, ok := result.Value.(SyncPullOutput) + if !ok { + err := core.E("agentic.cmdSyncPull", "invalid sync pull output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "context items: %d", output.Count) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdSyncStatus(options core.Options) core.Result { + result := s.handleSyncStatus(s.commandContext(), options) + if !result.OK { + err := commandResultError("agentic.cmdSyncStatus", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + output, ok := result.Value.(SyncStatusOutput) + if !ok { + err := core.E("agentic.cmdSyncStatus", "invalid sync status output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "agent: %s", output.AgentID) + core.Print(nil, "status: %s", output.Status) + core.Print(nil, "queued: %d", output.Queued) + core.Print(nil, "context items: %d", output.ContextCount) + if output.LastPushAt != "" { + core.Print(nil, "last push: %s", output.LastPushAt) + } + if output.LastPullAt != "" { + core.Print(nil, "last pull: %s", output.LastPullAt) + } + if output.RemoteError != "" { + core.Print(nil, "remote error: %s", output.RemoteError) + } + + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdFleetRegister(options core.Options) core.Result { + if optionStringValue(options, "agent_id", "agent-id", "_arg") == "" { + core.Print(nil, "usage: core-agent fleet register --platform=linux [--models=codex,gpt-5.4] [--capabilities=go,review]") + return core.Result{Value: core.E("agentic.cmdFleetRegister", "agent_id is required", nil), OK: false} + } + + result := s.handleFleetRegister(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdFleetRegister", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + node, ok := result.Value.(FleetNode) + if !ok { + err := core.E("agentic.cmdFleetRegister", "invalid fleet register output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "registered: %s", node.AgentID) + core.Print(nil, "status: %s", node.Status) + core.Print(nil, "platform: %s", node.Platform) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdFleetHeartbeat(options core.Options) core.Result { + if optionStringValue(options, "agent_id", "agent-id", "_arg") == "" || optionStringValue(options, "status") == "" { + core.Print(nil, "usage: core-agent fleet heartbeat --status=online [--compute-budget='{\"max_daily_hours\":2}']") + return core.Result{Value: core.E("agentic.cmdFleetHeartbeat", "agent_id and status are required", nil), OK: false} + } + + result := s.handleFleetHeartbeat(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdFleetHeartbeat", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + node, ok := result.Value.(FleetNode) + if !ok { + err := core.E("agentic.cmdFleetHeartbeat", "invalid fleet heartbeat output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "heartbeat: %s", node.AgentID) + core.Print(nil, "status: %s", node.Status) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdFleetDeregister(options core.Options) core.Result { + if optionStringValue(options, "agent_id", "agent-id", "_arg") == "" { + core.Print(nil, "usage: core-agent fleet deregister ") + return core.Result{Value: core.E("agentic.cmdFleetDeregister", "agent_id is required", nil), OK: false} + } + + result := s.handleFleetDeregister(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdFleetDeregister", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + data, ok := result.Value.(map[string]any) + if !ok { + err := core.E("agentic.cmdFleetDeregister", "invalid fleet deregister output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "deregistered: %s", stringValue(data["agent_id"])) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdFleetNodes(options core.Options) core.Result { + result := s.handleFleetNodes(s.commandContext(), options) + if !result.OK { + err := commandResultError("agentic.cmdFleetNodes", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + output, ok := result.Value.(FleetNodesOutput) + if !ok { + err := core.E("agentic.cmdFleetNodes", "invalid fleet nodes output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + if len(output.Nodes) == 0 { + core.Print(nil, "no fleet nodes") + return core.Result{OK: true} + } + + for _, node := range output.Nodes { + core.Print(nil, " %-10s %-8s %-10s %s", node.Status, node.Platform, node.AgentID, core.Join(",", node.Models...)) + } + core.Print(nil, "") + core.Print(nil, "total: %d", output.Total) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdFleetTaskAssign(options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + if agentID == "" || optionStringValue(options, "repo") == "" || optionStringValue(options, "task") == "" { + core.Print(nil, "usage: core-agent fleet task assign --repo=core/go-io --task=\"...\" [--branch=dev] [--template=coding] [--agent-model=codex:gpt-5.4]") + return core.Result{Value: core.E("agentic.cmdFleetTaskAssign", "agent_id, repo, and task are required", nil), OK: false} + } + + result := s.handleFleetAssignTask(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdFleetTaskAssign", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + task, ok := result.Value.(FleetTask) + if !ok { + err := core.E("agentic.cmdFleetTaskAssign", "invalid fleet task output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + printFleetTask(task) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdFleetTaskComplete(options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + taskID := optionIntValue(options, "task_id", "task-id") + if agentID == "" || taskID == 0 { + core.Print(nil, "usage: core-agent fleet task complete --task-id=N [--result='{\"status\":\"completed\"}'] [--findings='[{\"file\":\"x.go\"}]']") + return core.Result{Value: core.E("agentic.cmdFleetTaskComplete", "agent_id and task_id are required", nil), OK: false} + } + + result := s.handleFleetCompleteTask(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdFleetTaskComplete", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + task, ok := result.Value.(FleetTask) + if !ok { + err := core.E("agentic.cmdFleetTaskComplete", "invalid fleet task output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + printFleetTask(task) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdFleetTaskNext(options core.Options) core.Result { + if optionStringValue(options, "agent_id", "agent-id", "_arg") == "" { + core.Print(nil, "usage: core-agent fleet task next [--capabilities=go,review]") + return core.Result{Value: core.E("agentic.cmdFleetTaskNext", "agent_id is required", nil), OK: false} + } + + result := s.handleFleetNextTask(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdFleetTaskNext", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + task, ok := result.Value.(*FleetTask) + if !ok { + err := core.E("agentic.cmdFleetTaskNext", "invalid fleet next-task output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + if task == nil { + core.Print(nil, "no task available") + return core.Result{OK: true} + } + + printFleetTask(*task) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdFleetStats(options core.Options) core.Result { + result := s.handleFleetStats(s.commandContext(), options) + if !result.OK { + err := commandResultError("agentic.cmdFleetStats", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + stats, ok := result.Value.(FleetStats) + if !ok { + err := core.E("agentic.cmdFleetStats", "invalid fleet stats output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "nodes online: %d", stats.NodesOnline) + core.Print(nil, "tasks today: %d", stats.TasksToday) + core.Print(nil, "tasks week: %d", stats.TasksWeek) + core.Print(nil, "repos touched: %d", stats.ReposTouched) + core.Print(nil, "findings total: %d", stats.FindingsTotal) + core.Print(nil, "compute hours: %d", stats.ComputeHours) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdCreditsAward(options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + taskType := optionStringValue(options, "task_type", "task-type") + amount := optionIntValue(options, "amount") + if agentID == "" || taskType == "" || amount == 0 { + core.Print(nil, "usage: core-agent credits award --task-type=fleet-task --amount=2 [--description=\"...\"]") + return core.Result{Value: core.E("agentic.cmdCreditsAward", "agent_id, task_type, and amount are required", nil), OK: false} + } + + result := s.handleCreditsAward(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdCreditsAward", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + entry, ok := result.Value.(CreditEntry) + if !ok { + err := core.E("agentic.cmdCreditsAward", "invalid credit award output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "entry: %d", entry.ID) + core.Print(nil, "task type: %s", entry.TaskType) + core.Print(nil, "amount: %d", entry.Amount) + core.Print(nil, "balance after: %d", entry.BalanceAfter) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdCreditsBalance(options core.Options) core.Result { + if optionStringValue(options, "agent_id", "agent-id", "_arg") == "" { + core.Print(nil, "usage: core-agent credits balance ") + return core.Result{Value: core.E("agentic.cmdCreditsBalance", "agent_id is required", nil), OK: false} + } + + result := s.handleCreditsBalance(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdCreditsBalance", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + balance, ok := result.Value.(CreditBalance) + if !ok { + err := core.E("agentic.cmdCreditsBalance", "invalid credit balance output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "agent: %s", balance.AgentID) + core.Print(nil, "balance: %d", balance.Balance) + core.Print(nil, "entries: %d", balance.Entries) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdCreditsHistory(options core.Options) core.Result { + if optionStringValue(options, "agent_id", "agent-id", "_arg") == "" { + core.Print(nil, "usage: core-agent credits history [--limit=50]") + return core.Result{Value: core.E("agentic.cmdCreditsHistory", "agent_id is required", nil), OK: false} + } + + result := s.handleCreditsHistory(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdCreditsHistory", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + output, ok := result.Value.(CreditsHistoryOutput) + if !ok { + err := core.E("agentic.cmdCreditsHistory", "invalid credit history output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + if len(output.Entries) == 0 { + core.Print(nil, "no credit entries") + return core.Result{OK: true} + } + + for _, entry := range output.Entries { + core.Print(nil, " #%-4d %-12s %+d balance=%d", entry.ID, entry.TaskType, entry.Amount, entry.BalanceAfter) + } + core.Print(nil, "") + core.Print(nil, "total: %d", output.Total) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdSubscriptionDetect(options core.Options) core.Result { + result := s.handleSubscriptionDetect(s.commandContext(), options) + if !result.OK { + err := commandResultError("agentic.cmdSubscriptionDetect", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + capabilities, ok := result.Value.(SubscriptionCapabilities) + if !ok { + err := core.E("agentic.cmdSubscriptionDetect", "invalid capability output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "available: %s", core.Join(",", capabilities.Available...)) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdSubscriptionBudget(options core.Options) core.Result { + if optionStringValue(options, "agent_id", "agent-id", "_arg") == "" { + core.Print(nil, "usage: core-agent subscription budget ") + return core.Result{Value: core.E("agentic.cmdSubscriptionBudget", "agent_id is required", nil), OK: false} + } + + result := s.handleSubscriptionBudget(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdSubscriptionBudget", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + budget, ok := result.Value.(map[string]any) + if !ok { + err := core.E("agentic.cmdSubscriptionBudget", "invalid budget output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "%s", core.JSONMarshalString(budget)) + return core.Result{OK: true} +} + +func (s *PrepSubsystem) cmdSubscriptionUpdateBudget(options core.Options) core.Result { + if optionStringValue(options, "agent_id", "agent-id", "_arg") == "" || len(optionAnyMapValue(options, "limits")) == 0 { + core.Print(nil, "usage: core-agent subscription update-budget --limits='{\"max_daily_hours\":2}'") + return core.Result{Value: core.E("agentic.cmdSubscriptionUpdateBudget", "agent_id and limits are required", nil), OK: false} + } + + result := s.handleSubscriptionBudgetUpdate(s.commandContext(), normalisePlatformCommandOptions(options)) + if !result.OK { + err := commandResultError("agentic.cmdSubscriptionUpdateBudget", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + budget, ok := result.Value.(map[string]any) + if !ok { + err := core.E("agentic.cmdSubscriptionUpdateBudget", "invalid updated budget output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "%s", core.JSONMarshalString(budget)) + return core.Result{OK: true} +} + +func normalisePlatformCommandOptions(options core.Options) core.Options { + normalised := core.NewOptions(options.Items()...) + if normalised.String("agent_id") == "" { + if agentID := optionStringValue(options, "_arg", "agent-id"); agentID != "" { + normalised.Set("agent_id", agentID) + } + } + return normalised +} + +func commandResultError(action string, result core.Result) error { + if err, ok := result.Value.(error); ok && err != nil { + return err + } + + message := stringValue(result.Value) + if message != "" { + return core.E(action, message, nil) + } + + return core.E(action, "command failed", nil) +} + +func printFleetTask(task FleetTask) { + core.Print(nil, "task: %d", task.ID) + core.Print(nil, "repo: %s", task.Repo) + core.Print(nil, "status: %s", task.Status) + if task.Branch != "" { + core.Print(nil, "branch: %s", task.Branch) + } + if task.AgentModel != "" { + core.Print(nil, "agent model: %s", task.AgentModel) + } + core.Print(nil, "summary: %s", task.Task) +} diff --git a/pkg/agentic/commands_platform_example_test.go b/pkg/agentic/commands_platform_example_test.go new file mode 100644 index 0000000..fab633f --- /dev/null +++ b/pkg/agentic/commands_platform_example_test.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import core "dappco.re/go/core" + +func ExamplePrepSubsystem_cmdFleetRegister() { + s := &PrepSubsystem{ + ServiceRuntime: core.NewServiceRuntime(core.New(), AgentOptions{}), + } + + result := s.cmdFleetRegister(core.NewOptions()) + core.Println(result.OK) + // Output: + // usage: core-agent fleet register --platform=linux [--models=codex,gpt-5.4] [--capabilities=go,review] + // false +} diff --git a/pkg/agentic/commands_platform_test.go b/pkg/agentic/commands_platform_test.go new file mode 100644 index 0000000..ab969d0 --- /dev/null +++ b/pkg/agentic/commands_platform_test.go @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "net/http" + "net/http/httptest" + "testing" + + core "dappco.re/go/core" + "github.com/stretchr/testify/assert" +) + +func TestCommandsplatform_CmdFleetRegister_Bad(t *testing.T) { + subsystem := testPrepWithPlatformServer(t, nil, "") + result := subsystem.cmdFleetRegister(core.NewOptions()) + assert.False(t, result.OK) +} + +func TestCommandsplatform_CmdFleetNodes_Good(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"data":[{"id":1,"agent_id":"charon","platform":"linux","models":["codex"],"status":"online"}],"total":1}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + output := captureStdout(t, func() { + result := subsystem.cmdFleetNodes(core.NewOptions()) + assert.True(t, result.OK) + }) + + assert.Contains(t, output, "charon") + assert.Contains(t, output, "total: 1") +} + +func TestCommandsplatform_CmdSyncStatus_Good(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"data":{"agent_id":"charon","status":"online","last_push_at":"2026-03-31T08:00:00Z"}}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + output := captureStdout(t, func() { + result := subsystem.cmdSyncStatus(core.NewOptions(core.Option{Key: "_arg", Value: "charon"})) + assert.True(t, result.OK) + }) + + assert.Contains(t, output, "agent: charon") + assert.Contains(t, output, "status: online") +} + +func TestCommandsplatform_CmdSubscriptionDetect_Good(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"data":{"providers":{"claude":true},"available":["claude"]}}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + output := captureStdout(t, func() { + result := subsystem.cmdSubscriptionDetect(core.NewOptions()) + assert.True(t, result.OK) + }) + + assert.Contains(t, output, "available: claude") +} diff --git a/pkg/agentic/platform.go b/pkg/agentic/platform.go new file mode 100644 index 0000000..9675033 --- /dev/null +++ b/pkg/agentic/platform.go @@ -0,0 +1,784 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "context" + + core "dappco.re/go/core" +) + +// node := agentic.FleetNode{AgentID: "charon", Platform: "linux", Status: "online"} +type FleetNode struct { + ID int `json:"id"` + AgentID string `json:"agent_id"` + Platform string `json:"platform"` + Models []string `json:"models,omitempty"` + Capabilities []string `json:"capabilities,omitempty"` + Status string `json:"status"` + ComputeBudget map[string]any `json:"compute_budget,omitempty"` + CurrentTaskID int `json:"current_task_id,omitempty"` + LastHeartbeatAt string `json:"last_heartbeat_at,omitempty"` + RegisteredAt string `json:"registered_at,omitempty"` +} + +// task := agentic.FleetTask{ID: 7, Repo: "go-io", Task: "Fix tests", Status: "assigned"} +type FleetTask struct { + ID int `json:"id"` + Repo string `json:"repo"` + Branch string `json:"branch,omitempty"` + Task string `json:"task"` + Template string `json:"template,omitempty"` + AgentModel string `json:"agent_model,omitempty"` + Status string `json:"status"` + Result map[string]any `json:"result,omitempty"` + Findings []map[string]any `json:"findings,omitempty"` + Changes map[string]any `json:"changes,omitempty"` + Report map[string]any `json:"report,omitempty"` + StartedAt string `json:"started_at,omitempty"` + CompletedAt string `json:"completed_at,omitempty"` +} + +// out := agentic.FleetNodesOutput{Total: 2, Nodes: []agentic.FleetNode{{AgentID: "charon"}}} +type FleetNodesOutput struct { + Total int `json:"total"` + Nodes []FleetNode `json:"nodes"` +} + +// stats := agentic.FleetStats{NodesOnline: 2, TasksToday: 5} +type FleetStats struct { + NodesOnline int `json:"nodes_online"` + TasksToday int `json:"tasks_today"` + TasksWeek int `json:"tasks_week"` + ReposTouched int `json:"repos_touched"` + FindingsTotal int `json:"findings_total"` + ComputeHours int `json:"compute_hours"` +} + +// status := agentic.SyncStatusOutput{AgentID: "charon", Status: "online"} +type SyncStatusOutput struct { + AgentID string `json:"agent_id"` + Status string `json:"status"` + LastPushAt string `json:"last_push_at,omitempty"` + LastPullAt string `json:"last_pull_at,omitempty"` + Queued int `json:"queued"` + ContextCount int `json:"context_count"` + RemoteError string `json:"remote_error,omitempty"` +} + +// balance := agentic.CreditBalance{AgentID: "charon", Balance: 12} +type CreditBalance struct { + AgentID string `json:"agent_id"` + Balance int `json:"balance"` + Entries int `json:"entries"` +} + +// entry := agentic.CreditEntry{ID: 4, TaskType: "fleet-task", Amount: 2} +type CreditEntry struct { + ID int `json:"id"` + TaskType string `json:"task_type"` + Amount int `json:"amount"` + BalanceAfter int `json:"balance_after"` + Description string `json:"description,omitempty"` + CreatedAt string `json:"created_at,omitempty"` +} + +// out := agentic.CreditsHistoryOutput{Total: 1, Entries: []agentic.CreditEntry{{ID: 1}}} +type CreditsHistoryOutput struct { + Total int `json:"total"` + Entries []CreditEntry `json:"entries"` +} + +// caps := agentic.SubscriptionCapabilities{Available: []string{"claude", "openai"}} +type SubscriptionCapabilities struct { + Providers map[string]bool `json:"providers,omitempty"` + Available []string `json:"available,omitempty"` +} + +// result := c.Action("agent.sync.status").Run(ctx, core.NewOptions()) +func (s *PrepSubsystem) handleSyncStatus(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + if agentID == "" { + agentID = AgentName() + } + + output := SyncStatusOutput{ + AgentID: agentID, + Status: "offline", + Queued: len(readSyncQueue()), + ContextCount: len(readSyncContext()), + } + + if s.syncToken() == "" { + return core.Result{Value: output, OK: true} + } + + path := appendQueryParam("/v1/agent/status", "agent_id", agentID) + result := s.platformPayload(ctx, "agent.sync.status", "GET", path, nil) + if !result.OK { + err, _ := result.Value.(error) + if err != nil { + output.RemoteError = err.Error() + } + return core.Result{Value: output, OK: true} + } + + data := payloadDataMap(result.Value.(map[string]any)) + if len(data) == 0 { + return core.Result{Value: output, OK: true} + } + + output.Status = stringValue(data["status"]) + output.LastPushAt = stringValue(data["last_push_at"]) + output.LastPullAt = stringValue(data["last_pull_at"]) + if output.Status == "" { + output.Status = "online" + } + + return core.Result{Value: output, OK: true} +} + +// result := c.Action("agent.fleet.register").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleFleetRegister(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + if agentID == "" { + return core.Result{Value: core.E("agent.fleet.register", "agent_id is required", nil), OK: false} + } + + platform := optionStringValue(options, "platform") + if platform == "" { + platform = "unknown" + } + + body := map[string]any{ + "agent_id": agentID, + "platform": platform, + } + if models := optionStringSliceValue(options, "models"); len(models) > 0 { + body["models"] = models + } + if capabilities := optionStringSliceValue(options, "capabilities"); len(capabilities) > 0 { + body["capabilities"] = capabilities + } + + result := s.platformPayload(ctx, "agent.fleet.register", "POST", "/v1/fleet/register", body) + if !result.OK { + return result + } + + return core.Result{Value: parseFleetNode(payloadDataMap(result.Value.(map[string]any))), OK: true} +} + +// result := c.Action("agent.fleet.heartbeat").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleFleetHeartbeat(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + status := optionStringValue(options, "status") + if agentID == "" || status == "" { + return core.Result{Value: core.E("agent.fleet.heartbeat", "agent_id and status are required", nil), OK: false} + } + + body := map[string]any{ + "agent_id": agentID, + "status": status, + } + if budget := optionAnyMapValue(options, "compute_budget", "compute-budget"); len(budget) > 0 { + body["compute_budget"] = budget + } + + result := s.platformPayload(ctx, "agent.fleet.heartbeat", "POST", "/v1/fleet/heartbeat", body) + if !result.OK { + return result + } + + return core.Result{Value: parseFleetNode(payloadDataMap(result.Value.(map[string]any))), OK: true} +} + +// result := c.Action("agent.fleet.deregister").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleFleetDeregister(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + if agentID == "" { + return core.Result{Value: core.E("agent.fleet.deregister", "agent_id is required", nil), OK: false} + } + + result := s.platformPayload(ctx, "agent.fleet.deregister", "POST", "/v1/fleet/deregister", map[string]any{ + "agent_id": agentID, + }) + if !result.OK { + return result + } + + return core.Result{Value: map[string]any{ + "agent_id": agentID, + "deregistered": true, + }, OK: true} +} + +// result := c.Action("agent.fleet.nodes").Run(ctx, core.NewOptions()) +func (s *PrepSubsystem) handleFleetNodes(ctx context.Context, options core.Options) core.Result { + path := "/v1/fleet/nodes" + path = appendQueryParam(path, "status", optionStringValue(options, "status")) + path = appendQueryParam(path, "platform", optionStringValue(options, "platform")) + + result := s.platformPayload(ctx, "agent.fleet.nodes", "GET", path, nil) + if !result.OK { + return result + } + + return core.Result{Value: parseFleetNodesOutput(result.Value.(map[string]any)), OK: true} +} + +// result := c.Action("agent.fleet.task.assign").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleFleetAssignTask(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + repo := optionStringValue(options, "repo") + task := optionStringValue(options, "task") + if agentID == "" || repo == "" || task == "" { + return core.Result{Value: core.E("agent.fleet.task.assign", "agent_id, repo, and task are required", nil), OK: false} + } + + body := map[string]any{ + "agent_id": agentID, + "repo": repo, + "task": task, + } + if branch := optionStringValue(options, "branch"); branch != "" { + body["branch"] = branch + } + if template := optionStringValue(options, "template"); template != "" { + body["template"] = template + } + if agentModel := optionStringValue(options, "agent_model", "agent-model"); agentModel != "" { + body["agent_model"] = agentModel + } + + result := s.platformPayload(ctx, "agent.fleet.task.assign", "POST", "/v1/fleet/task/assign", body) + if !result.OK { + return result + } + + return core.Result{Value: parseFleetTask(payloadDataMap(result.Value.(map[string]any))), OK: true} +} + +// result := c.Action("agent.fleet.task.complete").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleFleetCompleteTask(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + taskID := optionIntValue(options, "task_id", "task-id") + if agentID == "" || taskID == 0 { + return core.Result{Value: core.E("agent.fleet.task.complete", "agent_id and task_id are required", nil), OK: false} + } + + body := map[string]any{ + "agent_id": agentID, + "task_id": taskID, + } + if resultMap := optionAnyMapValue(options, "result"); len(resultMap) > 0 { + body["result"] = resultMap + } + if findings := optionAnyMapSliceValue(options, "findings"); len(findings) > 0 { + body["findings"] = findings + } + if changes := optionAnyMapValue(options, "changes"); len(changes) > 0 { + body["changes"] = changes + } + if report := optionAnyMapValue(options, "report"); len(report) > 0 { + body["report"] = report + } + + result := s.platformPayload(ctx, "agent.fleet.task.complete", "POST", "/v1/fleet/task/complete", body) + if !result.OK { + return result + } + + return core.Result{Value: parseFleetTask(payloadDataMap(result.Value.(map[string]any))), OK: true} +} + +// result := c.Action("agent.fleet.task.next").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleFleetNextTask(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + if agentID == "" { + return core.Result{Value: core.E("agent.fleet.task.next", "agent_id is required", nil), OK: false} + } + + path := appendQueryParam("/v1/fleet/task/next", "agent_id", agentID) + path = appendQuerySlice(path, "capabilities[]", optionStringSliceValue(options, "capabilities")) + + result := s.platformPayload(ctx, "agent.fleet.task.next", "GET", path, nil) + if !result.OK { + return result + } + + data := payloadDataMap(result.Value.(map[string]any)) + if len(data) == 0 { + var task *FleetTask + return core.Result{Value: task, OK: true} + } + + task := parseFleetTask(data) + return core.Result{Value: &task, OK: true} +} + +// result := c.Action("agent.fleet.stats").Run(ctx, core.NewOptions()) +func (s *PrepSubsystem) handleFleetStats(ctx context.Context, options core.Options) core.Result { + result := s.platformPayload(ctx, "agent.fleet.stats", "GET", "/v1/fleet/stats", nil) + if !result.OK { + return result + } + + return core.Result{Value: parseFleetStats(payloadDataMap(result.Value.(map[string]any))), OK: true} +} + +// result := c.Action("agent.credits.award").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleCreditsAward(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + taskType := optionStringValue(options, "task_type", "task-type") + amount := optionIntValue(options, "amount") + if agentID == "" || taskType == "" || amount == 0 { + return core.Result{Value: core.E("agent.credits.award", "agent_id, task_type, and amount are required", nil), OK: false} + } + + body := map[string]any{ + "agent_id": agentID, + "task_type": taskType, + "amount": amount, + } + if fleetNodeID := optionIntValue(options, "fleet_node_id", "fleet-node-id"); fleetNodeID > 0 { + body["fleet_node_id"] = fleetNodeID + } + if description := optionStringValue(options, "description"); description != "" { + body["description"] = description + } + + result := s.platformPayload(ctx, "agent.credits.award", "POST", "/v1/credits/award", body) + if !result.OK { + return result + } + + return core.Result{Value: parseCreditEntry(payloadDataMap(result.Value.(map[string]any))), OK: true} +} + +// result := c.Action("agent.credits.balance").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleCreditsBalance(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + if agentID == "" { + return core.Result{Value: core.E("agent.credits.balance", "agent_id is required", nil), OK: false} + } + + path := core.Concat("/v1/credits/balance/", agentID) + result := s.platformPayload(ctx, "agent.credits.balance", "GET", path, nil) + if !result.OK { + return result + } + + return core.Result{Value: parseCreditBalance(payloadDataMap(result.Value.(map[string]any))), OK: true} +} + +// result := c.Action("agent.credits.history").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleCreditsHistory(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + if agentID == "" { + return core.Result{Value: core.E("agent.credits.history", "agent_id is required", nil), OK: false} + } + + path := core.Concat("/v1/credits/history/", agentID) + if limit := optionIntValue(options, "limit"); limit > 0 { + path = appendQueryParam(path, "limit", core.Sprint(limit)) + } + + result := s.platformPayload(ctx, "agent.credits.history", "GET", path, nil) + if !result.OK { + return result + } + + return core.Result{Value: parseCreditsHistoryOutput(result.Value.(map[string]any)), OK: true} +} + +// result := c.Action("agent.subscription.detect").Run(ctx, core.NewOptions()) +func (s *PrepSubsystem) handleSubscriptionDetect(ctx context.Context, options core.Options) core.Result { + body := map[string]any{} + if apiKeys := optionStringMapValue(options, "api_keys", "api-keys"); len(apiKeys) > 0 { + body["api_keys"] = apiKeys + } + + result := s.platformPayload(ctx, "agent.subscription.detect", "POST", "/v1/subscription/detect", body) + if !result.OK { + return result + } + + return core.Result{Value: parseSubscriptionCapabilities(payloadDataMap(result.Value.(map[string]any))), OK: true} +} + +// result := c.Action("agent.subscription.budget").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleSubscriptionBudget(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + if agentID == "" { + return core.Result{Value: core.E("agent.subscription.budget", "agent_id is required", nil), OK: false} + } + + path := core.Concat("/v1/subscription/budget/", agentID) + result := s.platformPayload(ctx, "agent.subscription.budget", "GET", path, nil) + if !result.OK { + return result + } + + return core.Result{Value: payloadDataMap(result.Value.(map[string]any)), OK: true} +} + +// result := c.Action("agent.subscription.budget.update").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) +func (s *PrepSubsystem) handleSubscriptionBudgetUpdate(ctx context.Context, options core.Options) core.Result { + agentID := optionStringValue(options, "agent_id", "agent-id", "_arg") + limits := optionAnyMapValue(options, "limits") + if agentID == "" || len(limits) == 0 { + return core.Result{Value: core.E("agent.subscription.budget.update", "agent_id and limits are required", nil), OK: false} + } + + path := core.Concat("/v1/subscription/budget/", agentID) + result := s.platformPayload(ctx, "agent.subscription.budget.update", "PUT", path, map[string]any{ + "limits": limits, + }) + if !result.OK { + return result + } + + return core.Result{Value: payloadDataMap(result.Value.(map[string]any)), OK: true} +} + +func (s *PrepSubsystem) platformPayload(ctx context.Context, action, method, path string, body any) core.Result { + token := s.syncToken() + if token == "" { + return core.Result{Value: core.E(action, "no platform API key configured", nil), OK: false} + } + + bodyString := "" + if body != nil { + bodyString = core.JSONMarshalString(body) + } + + requestResult := HTTPDo(ctx, method, core.Concat(s.syncAPIURL(), path), bodyString, token, "Bearer") + if !requestResult.OK { + return core.Result{Value: platformResultError(action, requestResult), OK: false} + } + + var payload map[string]any + parseResult := core.JSONUnmarshalString(requestResult.Value.(string), &payload) + if !parseResult.OK { + err, _ := parseResult.Value.(error) + return core.Result{Value: core.E(action, "failed to parse platform response", err), OK: false} + } + + return core.Result{Value: payload, OK: true} +} + +func platformResultError(action string, result core.Result) error { + if err, ok := result.Value.(error); ok && err != nil { + return core.E(action, "platform request failed", err) + } + + body := core.Trim(stringValue(result.Value)) + if body == "" { + return core.E(action, "platform request failed", nil) + } + + var payload map[string]any + if parseResult := core.JSONUnmarshalString(body, &payload); parseResult.OK { + if message := stringValue(payload["error"]); message != "" { + return core.E(action, message, nil) + } + } + + return core.E(action, body, nil) +} + +func payloadDataMap(payload map[string]any) map[string]any { + return anyMapValue(payload["data"]) +} + +func payloadDataSlice(payload map[string]any) []map[string]any { + return anyMapSliceValue(payload["data"]) +} + +func parseFleetNode(values map[string]any) FleetNode { + return FleetNode{ + ID: intValue(values["id"]), + AgentID: stringValue(values["agent_id"]), + Platform: stringValue(values["platform"]), + Models: listValue(values["models"]), + Capabilities: listValue(values["capabilities"]), + Status: stringValue(values["status"]), + ComputeBudget: anyMapValue(values["compute_budget"]), + CurrentTaskID: intValue(values["current_task_id"]), + LastHeartbeatAt: stringValue(values["last_heartbeat_at"]), + RegisteredAt: stringValue(values["registered_at"]), + } +} + +func parseFleetTask(values map[string]any) FleetTask { + return FleetTask{ + ID: intValue(values["id"]), + Repo: stringValue(values["repo"]), + Branch: stringValue(values["branch"]), + Task: stringValue(values["task"]), + Template: stringValue(values["template"]), + AgentModel: stringValue(values["agent_model"]), + Status: stringValue(values["status"]), + Result: anyMapValue(values["result"]), + Findings: anyMapSliceValue(values["findings"]), + Changes: anyMapValue(values["changes"]), + Report: anyMapValue(values["report"]), + StartedAt: stringValue(values["started_at"]), + CompletedAt: stringValue(values["completed_at"]), + } +} + +func parseFleetNodesOutput(payload map[string]any) FleetNodesOutput { + nodesData := payloadDataSlice(payload) + nodes := make([]FleetNode, 0, len(nodesData)) + for _, values := range nodesData { + nodes = append(nodes, parseFleetNode(values)) + } + + total := intValue(payload["total"]) + if total == 0 { + total = len(nodes) + } + + return FleetNodesOutput{ + Total: total, + Nodes: nodes, + } +} + +func parseFleetStats(values map[string]any) FleetStats { + return FleetStats{ + NodesOnline: intValue(values["nodes_online"]), + TasksToday: intValue(values["tasks_today"]), + TasksWeek: intValue(values["tasks_week"]), + ReposTouched: intValue(values["repos_touched"]), + FindingsTotal: intValue(values["findings_total"]), + ComputeHours: intValue(values["compute_hours"]), + } +} + +func parseCreditEntry(values map[string]any) CreditEntry { + return CreditEntry{ + ID: intValue(values["id"]), + TaskType: stringValue(values["task_type"]), + Amount: intValue(values["amount"]), + BalanceAfter: intValue(values["balance_after"]), + Description: stringValue(values["description"]), + CreatedAt: stringValue(values["created_at"]), + } +} + +func parseCreditBalance(values map[string]any) CreditBalance { + return CreditBalance{ + AgentID: stringValue(values["agent_id"]), + Balance: intValue(values["balance"]), + Entries: intValue(values["entries"]), + } +} + +func parseCreditsHistoryOutput(payload map[string]any) CreditsHistoryOutput { + entriesData := payloadDataSlice(payload) + entries := make([]CreditEntry, 0, len(entriesData)) + for _, values := range entriesData { + entries = append(entries, parseCreditEntry(values)) + } + + total := intValue(payload["total"]) + if total == 0 { + total = len(entries) + } + + return CreditsHistoryOutput{ + Total: total, + Entries: entries, + } +} + +func parseSubscriptionCapabilities(values map[string]any) SubscriptionCapabilities { + return SubscriptionCapabilities{ + Providers: boolMapValue(values["providers"]), + Available: listValue(values["available"]), + } +} + +func appendQueryParam(path, key, value string) string { + value = core.Trim(value) + if value == "" { + return path + } + + separator := "?" + if core.Contains(path, "?") { + separator = "&" + } + + return core.Concat(path, separator, key, "=", value) +} + +func appendQuerySlice(path, key string, values []string) string { + for _, value := range values { + path = appendQueryParam(path, key, value) + } + return path +} + +func optionAnyMapValue(options core.Options, keys ...string) map[string]any { + for _, key := range keys { + result := options.Get(key) + if !result.OK { + continue + } + values := anyMapValue(result.Value) + if len(values) > 0 { + return values + } + } + return nil +} + +func optionAnyMapSliceValue(options core.Options, keys ...string) []map[string]any { + for _, key := range keys { + result := options.Get(key) + if !result.OK { + continue + } + values := anyMapSliceValue(result.Value) + if len(values) > 0 { + return values + } + } + return nil +} + +func anyMapValue(value any) map[string]any { + switch typed := value.(type) { + case map[string]any: + return typed + case map[string]string: + values := make(map[string]any, len(typed)) + for key, item := range typed { + values[key] = item + } + return values + case string: + trimmed := core.Trim(typed) + if trimmed == "" { + return nil + } + if core.HasPrefix(trimmed, "{") { + var values map[string]any + if result := core.JSONUnmarshalString(trimmed, &values); result.OK { + return values + } + var strings map[string]string + if result := core.JSONUnmarshalString(trimmed, &strings); result.OK { + return anyMapValue(strings) + } + } + values := stringMapValue(trimmed) + if len(values) > 0 { + return anyMapValue(values) + } + } + return nil +} + +func anyMapSliceValue(value any) []map[string]any { + switch typed := value.(type) { + case []map[string]any: + return typed + case []any: + values := make([]map[string]any, 0, len(typed)) + for _, item := range typed { + if mapValue := anyMapValue(item); len(mapValue) > 0 { + values = append(values, mapValue) + } + } + return values + case string: + trimmed := core.Trim(typed) + if trimmed == "" { + return nil + } + if core.HasPrefix(trimmed, "[") { + var values []map[string]any + if result := core.JSONUnmarshalString(trimmed, &values); result.OK { + return values + } + var generic []any + if result := core.JSONUnmarshalString(trimmed, &generic); result.OK { + return anyMapSliceValue(generic) + } + } + } + return nil +} + +func boolMapValue(value any) map[string]bool { + switch typed := value.(type) { + case map[string]bool: + return typed + case map[string]any: + values := make(map[string]bool, len(typed)) + for key, item := range typed { + switch resolved := item.(type) { + case bool: + values[key] = resolved + case string: + values[key] = core.Lower(core.Trim(resolved)) == "true" + default: + values[key] = intValue(resolved) > 0 + } + } + return values + case string: + trimmed := core.Trim(typed) + if trimmed == "" { + return nil + } + if core.HasPrefix(trimmed, "{") { + var values map[string]bool + if result := core.JSONUnmarshalString(trimmed, &values); result.OK { + return values + } + var generic map[string]any + if result := core.JSONUnmarshalString(trimmed, &generic); result.OK { + return boolMapValue(generic) + } + } + } + return nil +} + +func listValue(value any) []string { + switch typed := value.(type) { + case map[string]any: + values := make([]string, 0, len(typed)) + for key, item := range typed { + if item == true || core.Trim(stringValue(item)) != "" { + values = append(values, key) + } + } + return cleanStrings(values) + default: + return stringSliceValue(value) + } +} + +func intValue(value any) int { + switch typed := value.(type) { + case int: + return typed + case int64: + return int(typed) + case float64: + return int(typed) + case string: + parsed := parseInt(typed) + if parsed != 0 || core.Trim(typed) == "0" { + return parsed + } + } + return 0 +} diff --git a/pkg/agentic/platform_example_test.go b/pkg/agentic/platform_example_test.go new file mode 100644 index 0000000..98ef760 --- /dev/null +++ b/pkg/agentic/platform_example_test.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import "fmt" + +func Example_parseFleetNode() { + node := parseFleetNode(map[string]any{ + "agent_id": "charon", + "platform": "linux", + "models": []any{"codex", "gpt-5.4"}, + "capabilities": map[string]any{"go": true, "review": true}, + "status": "online", + }) + + fmt.Println(node.AgentID, node.Platform, len(node.Models), len(node.Capabilities)) + // Output: charon linux 2 2 +} diff --git a/pkg/agentic/platform_test.go b/pkg/agentic/platform_test.go new file mode 100644 index 0000000..1f8f700 --- /dev/null +++ b/pkg/agentic/platform_test.go @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + core "dappco.re/go/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func testPrepWithPlatformServer(t *testing.T, srv *httptest.Server, token string) *PrepSubsystem { + t.Helper() + + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + t.Setenv("CORE_AGENT_API_KEY", token) + t.Setenv("CORE_BRAIN_KEY", "") + + c := core.New() + subsystem := &PrepSubsystem{ + ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}), + brainKey: token, + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + if srv != nil { + subsystem.brainURL = srv.URL + } + + return subsystem +} + +func TestPlatform_HandleFleetRegister_Good(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/v1/fleet/register", r.URL.Path) + require.Equal(t, "Bearer secret-token", r.Header.Get("Authorization")) + + bodyResult := core.ReadAll(r.Body) + require.True(t, bodyResult.OK) + + var payload map[string]any + parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) + require.True(t, parseResult.OK) + require.Equal(t, "charon", payload["agent_id"]) + require.Equal(t, "linux", payload["platform"]) + + _, _ = w.Write([]byte(`{"data":{"id":1,"agent_id":"charon","platform":"linux","models":["codex"],"capabilities":["go","review"],"status":"online"}}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + result := subsystem.handleFleetRegister(context.Background(), core.NewOptions( + core.Option{Key: "agent_id", Value: "charon"}, + core.Option{Key: "platform", Value: "linux"}, + core.Option{Key: "models", Value: "codex"}, + core.Option{Key: "capabilities", Value: "go,review"}, + )) + require.True(t, result.OK) + + node, ok := result.Value.(FleetNode) + require.True(t, ok) + assert.Equal(t, "charon", node.AgentID) + assert.Equal(t, "linux", node.Platform) + assert.Equal(t, []string{"codex"}, node.Models) + assert.Equal(t, []string{"go", "review"}, node.Capabilities) +} + +func TestPlatform_HandleFleetRegister_Bad(t *testing.T) { + subsystem := testPrepWithPlatformServer(t, nil, "") + + result := subsystem.handleFleetRegister(context.Background(), core.NewOptions( + core.Option{Key: "agent_id", Value: "charon"}, + core.Option{Key: "platform", Value: "linux"}, + )) + assert.False(t, result.OK) +} + +func TestPlatform_HandleFleetRegister_Ugly(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{broken json`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + result := subsystem.handleFleetRegister(context.Background(), core.NewOptions( + core.Option{Key: "agent_id", Value: "charon"}, + core.Option{Key: "platform", Value: "linux"}, + )) + assert.False(t, result.OK) +} + +func TestPlatform_HandleFleetNextTask_Good(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/v1/fleet/task/next", r.URL.Path) + require.Equal(t, "charon", r.URL.Query().Get("agent_id")) + require.Equal(t, []string{"go", "review"}, r.URL.Query()["capabilities[]"]) + _, _ = w.Write([]byte(`{"data":{"id":9,"repo":"core/go-io","branch":"dev","task":"Fix tests","template":"coding","agent_model":"codex:gpt-5.4","status":"in_progress","findings":[{"file":"main.go"}]}}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + result := subsystem.handleFleetNextTask(context.Background(), core.NewOptions( + core.Option{Key: "agent_id", Value: "charon"}, + core.Option{Key: "capabilities", Value: `["go","review"]`}, + )) + require.True(t, result.OK) + + task, ok := result.Value.(*FleetTask) + require.True(t, ok) + require.NotNil(t, task) + assert.Equal(t, 9, task.ID) + assert.Equal(t, "core/go-io", task.Repo) + assert.Len(t, task.Findings, 1) +} + +func TestPlatform_HandleFleetNextTask_Bad(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + _, _ = w.Write([]byte(`{"error":"queue unavailable"}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + result := subsystem.handleFleetNextTask(context.Background(), core.NewOptions( + core.Option{Key: "agent_id", Value: "charon"}, + )) + assert.False(t, result.OK) +} + +func TestPlatform_HandleFleetNextTask_Ugly(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"data":null}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + result := subsystem.handleFleetNextTask(context.Background(), core.NewOptions( + core.Option{Key: "agent_id", Value: "charon"}, + )) + require.True(t, result.OK) + + task, ok := result.Value.(*FleetTask) + require.True(t, ok) + assert.Nil(t, task) +} + +func TestPlatform_HandleSyncStatus_Good(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/v1/agent/status", r.URL.Path) + require.Equal(t, "charon", r.URL.Query().Get("agent_id")) + _, _ = w.Write([]byte(`{"data":{"agent_id":"charon","status":"online","last_push_at":"2026-03-31T08:00:00Z","last_pull_at":"2026-03-31T08:05:00Z"}}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + result := subsystem.handleSyncStatus(context.Background(), core.NewOptions( + core.Option{Key: "agent_id", Value: "charon"}, + )) + require.True(t, result.OK) + + status, ok := result.Value.(SyncStatusOutput) + require.True(t, ok) + assert.Equal(t, "charon", status.AgentID) + assert.Equal(t, "online", status.Status) + assert.Equal(t, "2026-03-31T08:00:00Z", status.LastPushAt) + assert.Empty(t, status.RemoteError) +} + +func TestPlatform_HandleCreditsHistory_Good(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/v1/credits/history/charon", r.URL.Path) + require.Equal(t, "5", r.URL.Query().Get("limit")) + _, _ = w.Write([]byte(`{"data":[{"id":1,"task_type":"fleet-task","amount":2,"balance_after":7,"description":"Fleet task completed"}],"total":1}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + result := subsystem.handleCreditsHistory(context.Background(), core.NewOptions( + core.Option{Key: "agent_id", Value: "charon"}, + core.Option{Key: "limit", Value: 5}, + )) + require.True(t, result.OK) + + output, ok := result.Value.(CreditsHistoryOutput) + require.True(t, ok) + assert.Equal(t, 1, output.Total) + require.Len(t, output.Entries, 1) + assert.Equal(t, 7, output.Entries[0].BalanceAfter) +} + +func TestPlatform_HandleSubscriptionDetect_Good(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/v1/subscription/detect", r.URL.Path) + bodyResult := core.ReadAll(r.Body) + require.True(t, bodyResult.OK) + + var payload map[string]any + parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) + require.True(t, parseResult.OK) + apiKeys, ok := payload["api_keys"].(map[string]any) + require.True(t, ok) + require.Equal(t, "k", apiKeys["openai"]) + + _, _ = w.Write([]byte(`{"data":{"providers":{"claude":true,"openai":true},"available":["claude","openai"]}}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + result := subsystem.handleSubscriptionDetect(context.Background(), core.NewOptions( + core.Option{Key: "api_keys", Value: `{"openai":"k"}`}, + )) + require.True(t, result.OK) + + capabilities, ok := result.Value.(SubscriptionCapabilities) + require.True(t, ok) + assert.True(t, capabilities.Providers["claude"]) + assert.Equal(t, []string{"claude", "openai"}, capabilities.Available) +} diff --git a/pkg/agentic/prep.go b/pkg/agentic/prep.go index cd0e986..eb321be 100644 --- a/pkg/agentic/prep.go +++ b/pkg/agentic/prep.go @@ -113,6 +113,21 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result { c.Action("agent.sync.push", s.handleSyncPush).Description = "Push completed dispatch state to the platform API" c.Action("agent.sync.pull", s.handleSyncPull).Description = "Pull fleet context from the platform API" + c.Action("agent.sync.status", s.handleSyncStatus).Description = "Get fleet sync status from the platform API" + c.Action("agent.fleet.register", s.handleFleetRegister).Description = "Register a fleet node with the platform API" + c.Action("agent.fleet.heartbeat", s.handleFleetHeartbeat).Description = "Send a heartbeat for a fleet node" + c.Action("agent.fleet.deregister", s.handleFleetDeregister).Description = "Deregister a fleet node from the platform API" + c.Action("agent.fleet.nodes", s.handleFleetNodes).Description = "List registered fleet nodes" + c.Action("agent.fleet.task.assign", s.handleFleetAssignTask).Description = "Assign a fleet task to an agent" + c.Action("agent.fleet.task.complete", s.handleFleetCompleteTask).Description = "Complete a fleet task and report results" + c.Action("agent.fleet.task.next", s.handleFleetNextTask).Description = "Ask the platform for the next fleet task" + c.Action("agent.fleet.stats", s.handleFleetStats).Description = "Get fleet activity statistics" + c.Action("agent.credits.award", s.handleCreditsAward).Description = "Award credits to a fleet node" + c.Action("agent.credits.balance", s.handleCreditsBalance).Description = "Get credit balance for a fleet node" + c.Action("agent.credits.history", s.handleCreditsHistory).Description = "List credit entries for a fleet node" + c.Action("agent.subscription.detect", s.handleSubscriptionDetect).Description = "Detect available provider capabilities" + c.Action("agent.subscription.budget", s.handleSubscriptionBudget).Description = "Get the compute budget for a fleet node" + c.Action("agent.subscription.budget.update", s.handleSubscriptionBudgetUpdate).Description = "Update the compute budget for a fleet node" c.Action("agentic.dispatch", s.handleDispatch).Description = "Prep workspace and spawn a subagent" c.Action("agentic.prep", s.handlePrep).Description = "Clone repo and build agent prompt" @@ -165,6 +180,7 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result { s.registerCommands(ctx) s.registerWorkspaceCommands() s.registerForgeCommands() + s.registerPlatformCommands() return core.Result{OK: true} }