fix: plugin bot user + await plugin detection before session scan

- Add EnsureBotUser on plugin activate (fixes 'Unable to find user' error)
- Accept bot_user_id in create session request
- Await plugin health check before starting session monitor
  (prevents race where sessions detect before plugin flag is set)
- Plugin now creates custom_livestatus posts with proper bot user
This commit is contained in:
sol
2026-03-07 22:25:59 +00:00
parent 42755e73ad
commit 7aebebf193
4 changed files with 56 additions and 6 deletions

View File

@@ -75,6 +75,7 @@ type CreateSessionRequest struct {
ChannelID string `json:"channel_id"` ChannelID string `json:"channel_id"`
RootID string `json:"root_id,omitempty"` RootID string `json:"root_id,omitempty"`
AgentID string `json:"agent_id"` AgentID string `json:"agent_id"`
BotUserID string `json:"bot_user_id,omitempty"`
} }
// handleCreateSession creates a new custom_livestatus post and starts tracking. // handleCreateSession creates a new custom_livestatus post and starts tracking.
@@ -98,8 +99,20 @@ func (p *Plugin) handleCreateSession(w http.ResponseWriter, r *http.Request) {
return return
} }
// Create the custom post // Create the custom post — UserId is required
// Use the bot user ID passed in the request, or fall back to plugin bot
userID := req.BotUserID
if userID == "" {
// Try to get plugin's own bot
userID = p.getBotUserID()
}
if userID == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "bot_user_id required (no plugin bot available)"})
return
}
post := &model.Post{ post := &model.Post{
UserId: userID,
ChannelId: req.ChannelID, ChannelId: req.ChannelID,
RootId: req.RootID, RootId: req.RootID,
Type: "custom_livestatus", Type: "custom_livestatus",

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"sync" "sync"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/plugin"
) )
@@ -18,16 +19,37 @@ type Plugin struct {
// store wraps KV store operations for session persistence. // store wraps KV store operations for session persistence.
store *Store store *Store
// botUserID is the plugin's bot user ID (created on activation).
botUserID string
} }
// OnActivate is called when the plugin is activated. // OnActivate is called when the plugin is activated.
func (p *Plugin) OnActivate() error { func (p *Plugin) OnActivate() error {
p.store = NewStore(p.API) p.store = NewStore(p.API)
// Ensure plugin bot user exists
botID, appErr := p.API.EnsureBotUser(&model.Bot{
Username: "livestatus",
DisplayName: "Live Status",
Description: "OpenClaw Live Status plugin bot",
})
if appErr != nil {
p.API.LogWarn("Failed to ensure bot user", "error", appErr.Error())
} else {
p.botUserID = botID
p.API.LogInfo("Plugin bot user ensured", "botUserID", botID)
}
p.API.LogInfo("OpenClaw Live Status plugin activated") p.API.LogInfo("OpenClaw Live Status plugin activated")
return nil return nil
} }
// getBotUserID returns the plugin's bot user ID.
func (p *Plugin) getBotUserID() string {
return p.botUserID
}
// OnDeactivate is called when the plugin is deactivated. // OnDeactivate is called when the plugin is deactivated.
func (p *Plugin) OnDeactivate() error { func (p *Plugin) OnDeactivate() error {
// Mark all active sessions as interrupted // Mark all active sessions as interrupted

View File

@@ -60,13 +60,24 @@ PluginClient.prototype.isHealthy = function () {
* @param {string} agentId * @param {string} agentId
* @returns {Promise<string>} post_id * @returns {Promise<string>} post_id
*/ */
/**
* @param {string} botUserId - Mattermost bot user ID to author the post
*/
PluginClient.prototype.setBotUserId = function (botUserId) {
this.botUserId = botUserId;
};
PluginClient.prototype.createSession = function (sessionKey, channelId, rootId, agentId) { PluginClient.prototype.createSession = function (sessionKey, channelId, rootId, agentId) {
return this._request('POST', '/api/v1/sessions', { var body = {
session_key: sessionKey, session_key: sessionKey,
channel_id: channelId, channel_id: channelId,
root_id: rootId || '', root_id: rootId || '',
agent_id: agentId, agent_id: agentId,
}).then(function (data) { };
if (this.botUserId) {
body.bot_user_id = this.botUserId;
}
return this._request('POST', '/api/v1/sessions', body).then(function (data) {
return data.post_id; return data.post_id;
}); });
}; };

View File

@@ -171,13 +171,17 @@ async function startDaemon() {
logger: logger.child({ module: 'plugin-client' }), logger: logger.child({ module: 'plugin-client' }),
}); });
// Initial plugin detection // Initial plugin detection (awaited before monitor starts — see below)
pluginClient.isHealthy().then(function (healthy) { try {
var healthy = await pluginClient.isHealthy();
usePlugin = healthy; usePlugin = healthy;
logger.info({ usePlugin, url: config.plugin.url }, healthy logger.info({ usePlugin, url: config.plugin.url }, healthy
? 'Plugin detected — using WebSocket rendering mode' ? 'Plugin detected — using WebSocket rendering mode'
: 'Plugin not available — using REST API fallback'); : 'Plugin not available — using REST API fallback');
}); } catch (_detectErr) {
usePlugin = false;
logger.warn('Plugin detection failed — using REST API fallback');
}
// Periodic re-detection // Periodic re-detection
setInterval(function () { setInterval(function () {