fix: remove dead delete+recreate and pin code, add poll fallback test
Phase 1 cleanup: - Remove deletePost() method (dead code, replaced by PUT in-place updates) - Remove _postInfo Map tracking (no longer needed) - Remove pin/unpin API calls from watcher-manager.js (incompatible with PUT updates) - Add JSDoc note on (edited) label limitation in _flushUpdate() - Add integration test: test/integration/poll-fallback.test.js - Fix addSession() lastOffset===0 falsy bug (0 was treated as 'no offset') - Fix pre-existing test failures: add lastOffset:0 where tests expect backlog reads - Fix pre-existing session-monitor test: create stub transcript files - Fix pre-existing status-formatter test: update indent check for blockquote format - Format plugin/ files with Prettier (pre-existing formatting drift)
This commit is contained in:
44
plugin/plugin.json
Normal file
44
plugin/plugin.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"id": "com.openclaw.livestatus",
|
||||
"name": "OpenClaw Live Status",
|
||||
"description": "Real-time agent status streaming with custom post type rendering and WebSocket updates.",
|
||||
"homepage_url": "https://git.eeqj.de/ROOH/MATTERMOST_OPENCLAW_LIVESTATUS",
|
||||
"support_url": "https://git.eeqj.de/ROOH/MATTERMOST_OPENCLAW_LIVESTATUS/issues",
|
||||
"icon_path": "assets/icon.svg",
|
||||
"min_server_version": "7.0.0",
|
||||
"server": {
|
||||
"executables": {
|
||||
"linux-amd64": "server/dist/plugin-linux-amd64"
|
||||
}
|
||||
},
|
||||
"webapp": {
|
||||
"bundle_path": "webapp/dist/main.js"
|
||||
},
|
||||
"settings_schema": {
|
||||
"header": "Configure the OpenClaw Live Status plugin.",
|
||||
"footer": "",
|
||||
"settings": [
|
||||
{
|
||||
"key": "SharedSecret",
|
||||
"display_name": "Shared Secret",
|
||||
"type": "text",
|
||||
"help_text": "Shared secret for authenticating the watcher daemon. Must match the daemon's PLUGIN_SECRET env var.",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "MaxActiveSessions",
|
||||
"display_name": "Max Active Sessions",
|
||||
"type": "number",
|
||||
"help_text": "Maximum number of simultaneously tracked agent sessions.",
|
||||
"default": 20
|
||||
},
|
||||
{
|
||||
"key": "MaxStatusLines",
|
||||
"display_name": "Max Status Lines",
|
||||
"type": "number",
|
||||
"help_text": "Maximum number of status lines to display per session.",
|
||||
"default": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
234
plugin/server/api.go
Normal file
234
plugin/server/api.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
)
|
||||
|
||||
// ServeHTTP handles HTTP requests to the plugin.
|
||||
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// Auth middleware: validate shared secret
|
||||
config := p.getConfiguration()
|
||||
if config.SharedSecret != "" {
|
||||
auth := r.Header.Get("Authorization")
|
||||
expected := "Bearer " + config.SharedSecret
|
||||
if auth != expected {
|
||||
http.Error(w, `{"error": "unauthorized"}`, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
path := r.URL.Path
|
||||
|
||||
switch {
|
||||
case path == "/api/v1/health" && r.Method == http.MethodGet:
|
||||
p.handleHealth(w, r)
|
||||
case path == "/api/v1/sessions" && r.Method == http.MethodGet:
|
||||
p.handleListSessions(w, r)
|
||||
case path == "/api/v1/sessions" && r.Method == http.MethodPost:
|
||||
p.handleCreateSession(w, r)
|
||||
case strings.HasPrefix(path, "/api/v1/sessions/") && r.Method == http.MethodPut:
|
||||
sessionKey := strings.TrimPrefix(path, "/api/v1/sessions/")
|
||||
p.handleUpdateSession(w, r, sessionKey)
|
||||
case strings.HasPrefix(path, "/api/v1/sessions/") && r.Method == http.MethodDelete:
|
||||
sessionKey := strings.TrimPrefix(path, "/api/v1/sessions/")
|
||||
p.handleDeleteSession(w, r, sessionKey)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// handleHealth returns plugin health status.
|
||||
func (p *Plugin) handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
sessions, err := p.store.ListActiveSessions()
|
||||
count := 0
|
||||
if err == nil {
|
||||
count = len(sessions)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"status": "healthy",
|
||||
"active_sessions": count,
|
||||
"plugin_id": "com.openclaw.livestatus",
|
||||
})
|
||||
}
|
||||
|
||||
// handleListSessions returns all active sessions.
|
||||
func (p *Plugin) handleListSessions(w http.ResponseWriter, r *http.Request) {
|
||||
sessions, err := p.store.ListActiveSessions()
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, sessions)
|
||||
}
|
||||
|
||||
// CreateSessionRequest is the request body for creating a new session.
|
||||
type CreateSessionRequest struct {
|
||||
SessionKey string `json:"session_key"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
RootID string `json:"root_id,omitempty"`
|
||||
AgentID string `json:"agent_id"`
|
||||
}
|
||||
|
||||
// handleCreateSession creates a new custom_livestatus post and starts tracking.
|
||||
func (p *Plugin) handleCreateSession(w http.ResponseWriter, r *http.Request) {
|
||||
var req CreateSessionRequest
|
||||
if err := readJSON(r, &req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.SessionKey == "" || req.ChannelID == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "session_key and channel_id required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check max active sessions
|
||||
config := p.getConfiguration()
|
||||
sessions, _ := p.store.ListActiveSessions()
|
||||
if len(sessions) >= config.MaxActiveSessions {
|
||||
writeJSON(w, http.StatusTooManyRequests, map[string]string{"error": "max active sessions reached"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create the custom post
|
||||
post := &model.Post{
|
||||
ChannelId: req.ChannelID,
|
||||
RootId: req.RootID,
|
||||
Type: "custom_livestatus",
|
||||
Message: "", // Custom post types don't need a message
|
||||
}
|
||||
post.AddProp("session_key", req.SessionKey)
|
||||
post.AddProp("agent_id", req.AgentID)
|
||||
post.AddProp("status", "active")
|
||||
|
||||
createdPost, appErr := p.API.CreatePost(post)
|
||||
if appErr != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": appErr.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Store session data
|
||||
sessionData := SessionData{
|
||||
SessionKey: req.SessionKey,
|
||||
PostID: createdPost.Id,
|
||||
ChannelID: req.ChannelID,
|
||||
RootID: req.RootID,
|
||||
AgentID: req.AgentID,
|
||||
Status: "active",
|
||||
Lines: []string{},
|
||||
}
|
||||
if err := p.store.SaveSession(req.SessionKey, sessionData); err != nil {
|
||||
p.API.LogWarn("Failed to save session", "error", err.Error())
|
||||
}
|
||||
|
||||
// Broadcast initial state
|
||||
p.broadcastUpdate(req.ChannelID, sessionData)
|
||||
|
||||
writeJSON(w, http.StatusCreated, map[string]string{
|
||||
"post_id": createdPost.Id,
|
||||
"session_key": req.SessionKey,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSessionRequest is the request body for updating a session.
|
||||
type UpdateSessionRequest struct {
|
||||
Status string `json:"status"`
|
||||
Lines []string `json:"lines"`
|
||||
ElapsedMs int64 `json:"elapsed_ms"`
|
||||
TokenCount int `json:"token_count"`
|
||||
Children []SessionData `json:"children,omitempty"`
|
||||
StartTimeMs int64 `json:"start_time_ms"`
|
||||
}
|
||||
|
||||
// handleUpdateSession updates session data and broadcasts via WebSocket.
|
||||
// Critically: does NOT call any Mattermost post API — no "(edited)" label.
|
||||
func (p *Plugin) handleUpdateSession(w http.ResponseWriter, r *http.Request, sessionKey string) {
|
||||
var req UpdateSessionRequest
|
||||
if err := readJSON(r, &req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Get existing session
|
||||
existing, err := p.store.GetSession(sessionKey)
|
||||
if err != nil || existing == nil {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "session not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Update fields
|
||||
existing.Status = req.Status
|
||||
existing.Lines = req.Lines
|
||||
existing.ElapsedMs = req.ElapsedMs
|
||||
existing.TokenCount = req.TokenCount
|
||||
existing.Children = req.Children
|
||||
if req.StartTimeMs > 0 {
|
||||
existing.StartTimeMs = req.StartTimeMs
|
||||
}
|
||||
|
||||
// Save to KV store
|
||||
if err := p.store.SaveSession(sessionKey, *existing); err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Broadcast update via WebSocket (no Mattermost post API call!)
|
||||
p.broadcastUpdate(existing.ChannelID, *existing)
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// handleDeleteSession marks a session as complete.
|
||||
func (p *Plugin) handleDeleteSession(w http.ResponseWriter, r *http.Request, sessionKey string) {
|
||||
existing, err := p.store.GetSession(sessionKey)
|
||||
if err != nil || existing == nil {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "session not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Mark as done
|
||||
existing.Status = "done"
|
||||
|
||||
// Update the post props to reflect completion (one final API call)
|
||||
post, appErr := p.API.GetPost(existing.PostID)
|
||||
if appErr == nil && post != nil {
|
||||
post.AddProp("status", "done")
|
||||
post.AddProp("final_lines", existing.Lines)
|
||||
post.AddProp("elapsed_ms", existing.ElapsedMs)
|
||||
post.AddProp("token_count", existing.TokenCount)
|
||||
_, _ = p.API.UpdatePost(post)
|
||||
}
|
||||
|
||||
// Broadcast final state
|
||||
p.broadcastUpdate(existing.ChannelID, *existing)
|
||||
|
||||
// Clean up KV store
|
||||
_ = p.store.DeleteSession(sessionKey)
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "done"})
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func readJSON(r *http.Request, v interface{}) error {
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // 1MB limit
|
||||
if err != nil {
|
||||
return fmt.Errorf("read body: %w", err)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
return json.Unmarshal(body, v)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
71
plugin/server/configuration.go
Normal file
71
plugin/server/configuration.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Configuration holds the plugin settings from the Admin Console.
|
||||
type Configuration struct {
|
||||
SharedSecret string `json:"SharedSecret"`
|
||||
MaxActiveSessions int `json:"MaxActiveSessions"`
|
||||
MaxStatusLines int `json:"MaxStatusLines"`
|
||||
}
|
||||
|
||||
// Clone returns a shallow copy of the configuration.
|
||||
func (c *Configuration) Clone() *Configuration {
|
||||
var clone Configuration
|
||||
clone = *c
|
||||
return &clone
|
||||
}
|
||||
|
||||
// IsValid checks if the configuration is valid.
|
||||
func (c *Configuration) IsValid() error {
|
||||
if c.SharedSecret == "" {
|
||||
return fmt.Errorf("SharedSecret must not be empty")
|
||||
}
|
||||
if c.MaxActiveSessions <= 0 {
|
||||
c.MaxActiveSessions = 20
|
||||
}
|
||||
if c.MaxStatusLines <= 0 {
|
||||
c.MaxStatusLines = 30
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getConfiguration retrieves the active configuration under lock.
|
||||
func (p *Plugin) getConfiguration() *Configuration {
|
||||
p.configurationLock.RLock()
|
||||
defer p.configurationLock.RUnlock()
|
||||
|
||||
if p.configuration == nil {
|
||||
return &Configuration{
|
||||
MaxActiveSessions: 20,
|
||||
MaxStatusLines: 30,
|
||||
}
|
||||
}
|
||||
|
||||
return p.configuration
|
||||
}
|
||||
|
||||
// OnConfigurationChange is invoked when configuration changes may have been made.
|
||||
func (p *Plugin) OnConfigurationChange() error {
|
||||
var configuration = new(Configuration)
|
||||
|
||||
if err := p.API.LoadPluginConfiguration(configuration); err != nil {
|
||||
return fmt.Errorf("failed to load plugin configuration: %w", err)
|
||||
}
|
||||
|
||||
// Apply defaults
|
||||
if configuration.MaxActiveSessions <= 0 {
|
||||
configuration.MaxActiveSessions = 20
|
||||
}
|
||||
if configuration.MaxStatusLines <= 0 {
|
||||
configuration.MaxStatusLines = 30
|
||||
}
|
||||
|
||||
p.configurationLock.Lock()
|
||||
p.configuration = configuration
|
||||
p.configurationLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
BIN
plugin/server/dist/plugin-linux-amd64
vendored
Executable file
BIN
plugin/server/dist/plugin-linux-amd64
vendored
Executable file
Binary file not shown.
46
plugin/server/go.mod
Normal file
46
plugin/server/go.mod
Normal file
@@ -0,0 +1,46 @@
|
||||
module github.com/openclaw/mattermost-plugin-livestatus/server
|
||||
|
||||
go 1.22.12
|
||||
|
||||
require github.com/mattermost/mattermost/server/public v0.0.12
|
||||
|
||||
require (
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/hashicorp/go-hclog v1.6.2 // indirect
|
||||
github.com/hashicorp/go-plugin v1.6.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
|
||||
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect
|
||||
github.com/mattermost/logr/v2 v2.0.21 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/pborman/uuid v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/philhofer/fwd v1.1.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/tinylib/msgp v1.1.9 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/wiggin77/merror v1.0.5 // indirect
|
||||
github.com/wiggin77/srslog v1.0.1 // indirect
|
||||
golang.org/x/crypto v0.16.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect
|
||||
google.golang.org/grpc v1.60.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
288
plugin/server/go.sum
Normal file
288
plugin/server/go.sum
Normal file
@@ -0,0 +1,288 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64=
|
||||
github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I=
|
||||
github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
|
||||
github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8=
|
||||
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=
|
||||
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 h1:Y1Tu/swM31pVwwb2BTCsOdamENjjWCI6qmfHLbk6OZI=
|
||||
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956/go.mod h1:SRl30Lb7/QoYyohYeVBuqYvvmXSZJxZgiV3Zf6VbxjI=
|
||||
github.com/mattermost/logr/v2 v2.0.21 h1:CMHsP+nrbRlEC4g7BwOk1GAnMtHkniFhlSQPXy52be4=
|
||||
github.com/mattermost/logr/v2 v2.0.21/go.mod h1:kZkB/zqKL9e+RY5gB3vGpsyenC+TpuiOenjMkvJJbzc=
|
||||
github.com/mattermost/mattermost/server/public v0.0.12 h1:iunc9q4/XkArOrndEUn73uFw6v9TOEXEtp6Nm6Iv218=
|
||||
github.com/mattermost/mattermost/server/public v0.0.12/go.mod h1:Bk+atJcELCIk9Yeq5FoqTr+gra9704+X4amrlwlTgSc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
||||
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
|
||||
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU=
|
||||
github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/wiggin77/merror v1.0.5 h1:P+lzicsn4vPMycAf2mFf7Zk6G9eco5N+jB1qJ2XW3ME=
|
||||
github.com/wiggin77/merror v1.0.5/go.mod h1:H2ETSu7/bPE0Ymf4bEwdUoo73OOEkdClnoRisfw0Nm0=
|
||||
github.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8=
|
||||
github.com/wiggin77/srslog v1.0.1/go.mod h1:fehkyYDq1QfuYn60TDPu9YdY2bB85VUW2mvN1WynEls=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k=
|
||||
google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
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=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
9
plugin/server/main.go
Normal file
9
plugin/server/main.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&Plugin{})
|
||||
}
|
||||
47
plugin/server/plugin.go
Normal file
47
plugin/server/plugin.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
)
|
||||
|
||||
// Plugin implements the Mattermost plugin interface.
|
||||
type Plugin struct {
|
||||
plugin.MattermostPlugin
|
||||
|
||||
// configurationLock synchronizes access to the configuration.
|
||||
configurationLock sync.RWMutex
|
||||
|
||||
// configuration is the active plugin configuration.
|
||||
configuration *Configuration
|
||||
|
||||
// store wraps KV store operations for session persistence.
|
||||
store *Store
|
||||
}
|
||||
|
||||
// OnActivate is called when the plugin is activated.
|
||||
func (p *Plugin) OnActivate() error {
|
||||
p.store = NewStore(p.API)
|
||||
|
||||
p.API.LogInfo("OpenClaw Live Status plugin activated")
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnDeactivate is called when the plugin is deactivated.
|
||||
func (p *Plugin) OnDeactivate() error {
|
||||
// Mark all active sessions as interrupted
|
||||
sessions, err := p.store.ListActiveSessions()
|
||||
if err != nil {
|
||||
p.API.LogWarn("Failed to list sessions on deactivate", "error", err.Error())
|
||||
} else {
|
||||
for _, s := range sessions {
|
||||
s.Status = "interrupted"
|
||||
_ = p.store.SaveSession(s.SessionKey, s)
|
||||
p.broadcastUpdate(s.ChannelID, s)
|
||||
}
|
||||
}
|
||||
|
||||
p.API.LogInfo("OpenClaw Live Status plugin deactivated")
|
||||
return nil
|
||||
}
|
||||
117
plugin/server/store.go
Normal file
117
plugin/server/store.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
)
|
||||
|
||||
const kvPrefix = "ls_session_"
|
||||
|
||||
// SessionData represents a tracked agent session.
|
||||
type SessionData struct {
|
||||
SessionKey string `json:"session_key"`
|
||||
PostID string `json:"post_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
RootID string `json:"root_id,omitempty"`
|
||||
AgentID string `json:"agent_id"`
|
||||
Status string `json:"status"` // active, done, error, interrupted
|
||||
Lines []string `json:"lines"`
|
||||
ElapsedMs int64 `json:"elapsed_ms"`
|
||||
TokenCount int `json:"token_count"`
|
||||
Children []SessionData `json:"children,omitempty"`
|
||||
StartTimeMs int64 `json:"start_time_ms"`
|
||||
}
|
||||
|
||||
// Store wraps Mattermost KV store operations for session persistence.
|
||||
type Store struct {
|
||||
api plugin.API
|
||||
}
|
||||
|
||||
// NewStore creates a new Store instance.
|
||||
func NewStore(api plugin.API) *Store {
|
||||
return &Store{api: api}
|
||||
}
|
||||
|
||||
// encodeKey URL-encodes a session key for safe KV storage.
|
||||
func encodeKey(sessionKey string) string {
|
||||
return kvPrefix + url.PathEscape(sessionKey)
|
||||
}
|
||||
|
||||
// SaveSession stores a session in the KV store.
|
||||
func (s *Store) SaveSession(sessionKey string, data SessionData) error {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal session: %w", err)
|
||||
}
|
||||
appErr := s.api.KVSet(encodeKey(sessionKey), b)
|
||||
if appErr != nil {
|
||||
return fmt.Errorf("kv set: %s", appErr.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSession retrieves a session from the KV store.
|
||||
func (s *Store) GetSession(sessionKey string) (*SessionData, error) {
|
||||
b, appErr := s.api.KVGet(encodeKey(sessionKey))
|
||||
if appErr != nil {
|
||||
return nil, fmt.Errorf("kv get: %s", appErr.Error())
|
||||
}
|
||||
if b == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var data SessionData
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal session: %w", err)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
// DeleteSession removes a session from the KV store.
|
||||
func (s *Store) DeleteSession(sessionKey string) error {
|
||||
appErr := s.api.KVDelete(encodeKey(sessionKey))
|
||||
if appErr != nil {
|
||||
return fmt.Errorf("kv delete: %s", appErr.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListActiveSessions returns all active sessions from the KV store.
|
||||
func (s *Store) ListActiveSessions() ([]SessionData, error) {
|
||||
var sessions []SessionData
|
||||
page := 0
|
||||
perPage := 100
|
||||
|
||||
for {
|
||||
keys, appErr := s.api.KVList(page, perPage)
|
||||
if appErr != nil {
|
||||
return nil, fmt.Errorf("kv list: %s", appErr.Error())
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
if !strings.HasPrefix(key, kvPrefix) {
|
||||
continue
|
||||
}
|
||||
b, getErr := s.api.KVGet(key)
|
||||
if getErr != nil || b == nil {
|
||||
continue
|
||||
}
|
||||
var data SessionData
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
continue
|
||||
}
|
||||
if data.Status == "active" {
|
||||
sessions = append(sessions, data)
|
||||
}
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
return sessions, nil
|
||||
}
|
||||
25
plugin/server/websocket.go
Normal file
25
plugin/server/websocket.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
// broadcastUpdate sends a WebSocket event to all clients viewing the given channel.
|
||||
// The event is auto-prefixed by the SDK to "custom_com.openclaw.livestatus_update".
|
||||
func (p *Plugin) broadcastUpdate(channelID string, data SessionData) {
|
||||
payload := map[string]interface{}{
|
||||
"post_id": data.PostID,
|
||||
"session_key": data.SessionKey,
|
||||
"agent_id": data.AgentID,
|
||||
"status": data.Status,
|
||||
"lines": data.Lines,
|
||||
"elapsed_ms": data.ElapsedMs,
|
||||
"token_count": data.TokenCount,
|
||||
"children": data.Children,
|
||||
"start_time_ms": data.StartTimeMs,
|
||||
}
|
||||
|
||||
p.API.PublishWebSocketEvent("update", payload, &model.WebsocketBroadcast{
|
||||
ChannelId: channelID,
|
||||
})
|
||||
}
|
||||
1934
plugin/webapp/package-lock.json
generated
Normal file
1934
plugin/webapp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
plugin/webapp/package.json
Normal file
20
plugin/webapp/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "com.openclaw.livestatus-webapp",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack --mode=production",
|
||||
"dev": "webpack --mode=development --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"css-loader": "^6.11.0",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-loader": "^9.5.4",
|
||||
"typescript": "^5.9.3",
|
||||
"webpack": "^5.105.4",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
180
plugin/webapp/src/components/live_status_post.tsx
Normal file
180
plugin/webapp/src/components/live_status_post.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import TerminalView from './terminal_view';
|
||||
import { LiveStatusData } from '../types';
|
||||
|
||||
interface LiveStatusPostProps {
|
||||
post: {
|
||||
id: string;
|
||||
props: Record<string, any>;
|
||||
};
|
||||
theme: Record<string, string>;
|
||||
}
|
||||
|
||||
// Global store for WebSocket updates (set by the plugin index)
|
||||
declare global {
|
||||
interface Window {
|
||||
__livestatus_updates: Record<string, LiveStatusData>;
|
||||
__livestatus_listeners: Record<string, Array<(data: LiveStatusData) => void>>;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.__livestatus_updates = window.__livestatus_updates || {};
|
||||
window.__livestatus_listeners = window.__livestatus_listeners || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to live status updates for a given post ID.
|
||||
*/
|
||||
function useStatusUpdates(
|
||||
postId: string,
|
||||
initialData: LiveStatusData | null,
|
||||
): LiveStatusData | null {
|
||||
const [data, setData] = useState<LiveStatusData | null>(
|
||||
window.__livestatus_updates[postId] || initialData,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Register listener
|
||||
if (!window.__livestatus_listeners[postId]) {
|
||||
window.__livestatus_listeners[postId] = [];
|
||||
}
|
||||
const listener = (newData: LiveStatusData) => setData(newData);
|
||||
window.__livestatus_listeners[postId].push(listener);
|
||||
|
||||
// Check if we already have data
|
||||
if (window.__livestatus_updates[postId]) {
|
||||
setData(window.__livestatus_updates[postId]);
|
||||
}
|
||||
|
||||
return () => {
|
||||
const listeners = window.__livestatus_listeners[postId];
|
||||
if (listeners) {
|
||||
const idx = listeners.indexOf(listener);
|
||||
if (idx >= 0) listeners.splice(idx, 1);
|
||||
}
|
||||
};
|
||||
}, [postId]);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format elapsed time in human-readable form.
|
||||
*/
|
||||
function formatElapsed(ms: number): string {
|
||||
if (ms < 0) ms = 0;
|
||||
const s = Math.floor(ms / 1000);
|
||||
const m = Math.floor(s / 60);
|
||||
const h = Math.floor(m / 60);
|
||||
if (h > 0) return `${h}h${m % 60}m`;
|
||||
if (m > 0) return `${m}m${s % 60}s`;
|
||||
return `${s}s`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format token count compactly.
|
||||
*/
|
||||
function formatTokens(count: number): string {
|
||||
if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
|
||||
if (count >= 1000) return `${(count / 1000).toFixed(1)}k`;
|
||||
return String(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* LiveStatusPost — custom post type component.
|
||||
* Renders a terminal-style live status view with auto-updating content.
|
||||
*/
|
||||
const LiveStatusPost: React.FC<LiveStatusPostProps> = ({ post, theme }) => {
|
||||
const [elapsed, setElapsed] = useState(0);
|
||||
|
||||
// Build initial data from post props
|
||||
const initialData: LiveStatusData = {
|
||||
session_key: post.props.session_key || '',
|
||||
post_id: post.id,
|
||||
agent_id: post.props.agent_id || 'unknown',
|
||||
status: post.props.status || 'active',
|
||||
lines: post.props.final_lines || [],
|
||||
elapsed_ms: post.props.elapsed_ms || 0,
|
||||
token_count: post.props.token_count || 0,
|
||||
children: [],
|
||||
start_time_ms: post.props.start_time_ms || 0,
|
||||
};
|
||||
|
||||
const data = useStatusUpdates(post.id, initialData);
|
||||
const isActive = data?.status === 'active';
|
||||
|
||||
// Client-side elapsed time counter (ticks every 1s when active)
|
||||
useEffect(() => {
|
||||
if (!isActive || !data?.start_time_ms) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setElapsed(Date.now() - data.start_time_ms);
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [isActive, data?.start_time_ms]);
|
||||
|
||||
if (!data) {
|
||||
return <div className="ls-post ls-loading">Loading status...</div>;
|
||||
}
|
||||
|
||||
const displayElapsed =
|
||||
isActive && data.start_time_ms
|
||||
? formatElapsed(elapsed || Date.now() - data.start_time_ms)
|
||||
: formatElapsed(data.elapsed_ms);
|
||||
|
||||
const statusClass = `ls-status-${data.status}`;
|
||||
|
||||
return (
|
||||
<div className={`ls-post ${statusClass}`}>
|
||||
<div className="ls-header">
|
||||
<span className="ls-agent-badge">{data.agent_id}</span>
|
||||
{isActive && <span className="ls-live-dot" />}
|
||||
<span className={`ls-status-badge ${statusClass}`}>{data.status.toUpperCase()}</span>
|
||||
<span className="ls-elapsed">{displayElapsed}</span>
|
||||
</div>
|
||||
|
||||
<TerminalView lines={data.lines} maxLines={30} />
|
||||
|
||||
{data.children && data.children.length > 0 && (
|
||||
<div className="ls-children">
|
||||
{data.children.map((child, i) => (
|
||||
<ChildSession key={child.session_key || i} child={child} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isActive && data.token_count > 0 && (
|
||||
<div className="ls-footer">{formatTokens(data.token_count)} tokens</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a child/sub-agent session (collapsed by default).
|
||||
*/
|
||||
const ChildSession: React.FC<{ child: LiveStatusData }> = ({ child }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="ls-child">
|
||||
<div
|
||||
className="ls-child-header"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<span className="ls-expand-icon">{expanded ? '\u25BC' : '\u25B6'}</span>
|
||||
<span className="ls-agent-badge ls-child-badge">{child.agent_id}</span>
|
||||
<span className={`ls-status-badge ls-status-${child.status}`}>
|
||||
{child.status.toUpperCase()}
|
||||
</span>
|
||||
<span className="ls-elapsed">{formatElapsed(child.elapsed_ms)}</span>
|
||||
</div>
|
||||
{expanded && <TerminalView lines={child.lines} maxLines={15} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LiveStatusPost;
|
||||
41
plugin/webapp/src/components/status_line.tsx
Normal file
41
plugin/webapp/src/components/status_line.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
|
||||
interface StatusLineProps {
|
||||
line: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single status line with formatting:
|
||||
* - Tool calls: monospace tool name with colored result marker
|
||||
* - Thinking text: dimmed with box-drawing prefix
|
||||
*/
|
||||
const StatusLine: React.FC<StatusLineProps> = ({ line }) => {
|
||||
// Match tool call pattern: "toolName: arguments [OK]" or "toolName: arguments [ERR]"
|
||||
const toolMatch = line.match(/^(\S+?): (.+?)( \[OK\]| \[ERR\])?$/);
|
||||
|
||||
if (toolMatch) {
|
||||
const toolName = toolMatch[1];
|
||||
const args = toolMatch[2];
|
||||
const marker = (toolMatch[3] || '').trim();
|
||||
|
||||
return (
|
||||
<div className="ls-status-line ls-tool-call">
|
||||
<span className="ls-tool-name">{toolName}:</span>
|
||||
<span className="ls-tool-args"> {args}</span>
|
||||
{marker && (
|
||||
<span className={marker === '[OK]' ? 'ls-marker-ok' : 'ls-marker-err'}> {marker}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Thinking text
|
||||
return (
|
||||
<div className="ls-status-line ls-thinking">
|
||||
<span className="ls-thinking-prefix">{'\u2502'} </span>
|
||||
<span className="ls-thinking-text">{line}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusLine;
|
||||
46
plugin/webapp/src/components/terminal_view.tsx
Normal file
46
plugin/webapp/src/components/terminal_view.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, { useRef, useEffect, useState } from 'react';
|
||||
import StatusLine from './status_line';
|
||||
|
||||
interface TerminalViewProps {
|
||||
lines: string[];
|
||||
maxLines?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminal-style scrolling container for status lines.
|
||||
* Auto-scrolls to bottom on new content unless user has scrolled up.
|
||||
*/
|
||||
const TerminalView: React.FC<TerminalViewProps> = ({ lines, maxLines = 30 }) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [userScrolled, setUserScrolled] = useState(false);
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Auto-scroll to bottom when new lines arrive (unless user scrolled up)
|
||||
useEffect(() => {
|
||||
if (!userScrolled && bottomRef.current) {
|
||||
bottomRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}, [lines.length, userScrolled]);
|
||||
|
||||
// Detect user scroll
|
||||
const handleScroll = () => {
|
||||
if (!containerRef.current) return;
|
||||
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
||||
const isAtBottom = scrollHeight - scrollTop - clientHeight < 20;
|
||||
setUserScrolled(!isAtBottom);
|
||||
};
|
||||
|
||||
// Show only the most recent lines
|
||||
const visibleLines = lines.slice(-maxLines);
|
||||
|
||||
return (
|
||||
<div className="ls-terminal" ref={containerRef} onScroll={handleScroll}>
|
||||
{visibleLines.map((line, i) => (
|
||||
<StatusLine key={i} line={line} />
|
||||
))}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TerminalView;
|
||||
57
plugin/webapp/src/index.tsx
Normal file
57
plugin/webapp/src/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { PluginRegistry, WebSocketPayload, LiveStatusData } from './types';
|
||||
import LiveStatusPost from './components/live_status_post';
|
||||
import './styles/live_status.css';
|
||||
|
||||
const PLUGIN_ID = 'com.openclaw.livestatus';
|
||||
const WS_EVENT = `custom_${PLUGIN_ID}_update`;
|
||||
|
||||
class LiveStatusPlugin {
|
||||
private postTypeComponentId: string | null = null;
|
||||
|
||||
initialize(registry: PluginRegistry, store: any): void {
|
||||
// Register custom post type renderer
|
||||
this.postTypeComponentId = registry.registerPostTypeComponent(
|
||||
'custom_livestatus',
|
||||
LiveStatusPost,
|
||||
);
|
||||
|
||||
// Register WebSocket event handler
|
||||
registry.registerWebSocketEventHandler(WS_EVENT, (msg: any) => {
|
||||
const data = msg.data as WebSocketPayload;
|
||||
if (!data || !data.post_id) return;
|
||||
|
||||
// Update global store
|
||||
const update: LiveStatusData = {
|
||||
session_key: data.session_key,
|
||||
post_id: data.post_id,
|
||||
agent_id: data.agent_id,
|
||||
status: data.status as LiveStatusData['status'],
|
||||
lines: data.lines || [],
|
||||
elapsed_ms: data.elapsed_ms || 0,
|
||||
token_count: data.token_count || 0,
|
||||
children: data.children || [],
|
||||
start_time_ms: data.start_time_ms || 0,
|
||||
};
|
||||
|
||||
window.__livestatus_updates[data.post_id] = update;
|
||||
|
||||
// Notify listeners
|
||||
const listeners = window.__livestatus_listeners[data.post_id];
|
||||
if (listeners) {
|
||||
listeners.forEach((fn) => fn(update));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uninitialize(): void {
|
||||
// Cleanup handled by Mattermost plugin framework
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize global stores
|
||||
if (typeof window !== 'undefined') {
|
||||
window.__livestatus_updates = window.__livestatus_updates || {};
|
||||
window.__livestatus_listeners = window.__livestatus_listeners || {};
|
||||
}
|
||||
|
||||
(window as any).registerPlugin(PLUGIN_ID, new LiveStatusPlugin());
|
||||
197
plugin/webapp/src/styles/live_status.css
Normal file
197
plugin/webapp/src/styles/live_status.css
Normal file
@@ -0,0 +1,197 @@
|
||||
/* OpenClaw Live Status — Post Type Styles */
|
||||
|
||||
.ls-post {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.ls-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--center-channel-bg, #fff);
|
||||
border-bottom: 1px solid var(--center-channel-color-08, rgba(0, 0, 0, 0.08));
|
||||
}
|
||||
|
||||
.ls-agent-badge {
|
||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--button-bg, #166de0);
|
||||
color: var(--button-color, #fff);
|
||||
}
|
||||
|
||||
.ls-child-badge {
|
||||
font-size: 11px;
|
||||
background: var(--center-channel-color-24, rgba(0, 0, 0, 0.24));
|
||||
}
|
||||
|
||||
.ls-status-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ls-status-active .ls-status-badge {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.ls-status-done .ls-status-badge {
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.ls-status-error .ls-status-badge {
|
||||
background: #fbe9e7;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.ls-status-interrupted .ls-status-badge {
|
||||
background: #fff3e0;
|
||||
color: #e65100;
|
||||
}
|
||||
|
||||
.ls-elapsed {
|
||||
font-size: 12px;
|
||||
color: var(--center-channel-color-56, rgba(0, 0, 0, 0.56));
|
||||
margin-left: auto;
|
||||
font-family: 'SFMono-Regular', Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Live dot — pulsing green indicator */
|
||||
.ls-live-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #4caf50;
|
||||
animation: ls-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes ls-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
/* Terminal view */
|
||||
.ls-terminal {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
padding: 8px 12px;
|
||||
background: var(--center-channel-color-04, rgba(0, 0, 0, 0.04));
|
||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Status lines */
|
||||
.ls-status-line {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ls-tool-call {
|
||||
color: var(--center-channel-color, #3d3c40);
|
||||
}
|
||||
|
||||
.ls-tool-name {
|
||||
color: var(--link-color, #2389d7);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ls-tool-args {
|
||||
color: var(--center-channel-color-72, rgba(0, 0, 0, 0.72));
|
||||
}
|
||||
|
||||
.ls-marker-ok {
|
||||
color: #4caf50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ls-marker-err {
|
||||
color: #f44336;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ls-thinking {
|
||||
color: var(--center-channel-color-48, rgba(0, 0, 0, 0.48));
|
||||
}
|
||||
|
||||
.ls-thinking-prefix {
|
||||
color: var(--center-channel-color-24, rgba(0, 0, 0, 0.24));
|
||||
}
|
||||
|
||||
/* Children (sub-agents) */
|
||||
.ls-children {
|
||||
border-top: 1px solid var(--center-channel-color-08, rgba(0, 0, 0, 0.08));
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.ls-child {
|
||||
margin: 0 12px;
|
||||
border-left: 2px solid var(--center-channel-color-16, rgba(0, 0, 0, 0.16));
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.ls-child-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ls-expand-icon {
|
||||
font-size: 10px;
|
||||
color: var(--center-channel-color-48, rgba(0, 0, 0, 0.48));
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.ls-footer {
|
||||
padding: 4px 12px 8px;
|
||||
font-size: 11px;
|
||||
color: var(--center-channel-color-48, rgba(0, 0, 0, 0.48));
|
||||
font-family: 'SFMono-Regular', Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Loading state */
|
||||
.ls-loading {
|
||||
padding: 12px;
|
||||
color: var(--center-channel-color-48, rgba(0, 0, 0, 0.48));
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
.ls-terminal::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.ls-terminal::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ls-terminal::-webkit-scrollbar-thumb {
|
||||
background: var(--center-channel-color-16, rgba(0, 0, 0, 0.16));
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.ls-terminal::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--center-channel-color-32, rgba(0, 0, 0, 0.32));
|
||||
}
|
||||
39
plugin/webapp/src/types.ts
Normal file
39
plugin/webapp/src/types.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export interface LiveStatusData {
|
||||
session_key: string;
|
||||
post_id: string;
|
||||
agent_id: string;
|
||||
status: 'active' | 'done' | 'error' | 'interrupted';
|
||||
lines: string[];
|
||||
elapsed_ms: number;
|
||||
token_count: number;
|
||||
children: LiveStatusData[];
|
||||
start_time_ms: number;
|
||||
}
|
||||
|
||||
export interface WebSocketPayload {
|
||||
post_id: string;
|
||||
session_key: string;
|
||||
agent_id: string;
|
||||
status: string;
|
||||
lines: string[];
|
||||
elapsed_ms: number;
|
||||
token_count: number;
|
||||
children: LiveStatusData[];
|
||||
start_time_ms: number;
|
||||
}
|
||||
|
||||
// Mattermost plugin registry types (subset)
|
||||
export interface PluginRegistry {
|
||||
registerPostTypeComponent(typeName: string, component: any): string;
|
||||
registerWebSocketEventHandler(event: string, handler: (msg: any) => void): void;
|
||||
registerReducer(reducer: any): void;
|
||||
unregisterComponent(componentId: string): void;
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
id: string;
|
||||
type: string;
|
||||
props: Record<string, any>;
|
||||
channel_id: string;
|
||||
root_id: string;
|
||||
}
|
||||
18
plugin/webapp/tsconfig.json
Normal file
18
plugin/webapp/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"declaration": false,
|
||||
"sourceMap": false,
|
||||
"lib": ["ES6", "DOM"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
32
plugin/webapp/webpack.config.js
Normal file
32
plugin/webapp/webpack.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.tsx',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'main.js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user