diff --git a/src/status-box.js b/src/status-box.js index d372192..9faf2d0 100644 --- a/src/status-box.js +++ b/src/status-box.js @@ -163,40 +163,13 @@ class StatusBox extends EventEmitter { this.metrics.queueDepth = Math.max(0, this.metrics.queueDepth - 1); try { - // Delete + recreate to keep status post at the bottom of the thread - // Mattermost clears pin on PUT, and edited posts stay at their original position - const postInfo = this._postInfo.get(postId); - if (postInfo) { - try { - await this._httpRequest('DELETE', `/api/v4/posts/${postId}`, null); - } catch (_delErr) { - // If delete fails, fall back to PUT - } - const body = { channel_id: postInfo.channelId, message: this._truncate(text) }; - if (postInfo.rootId) body.root_id = postInfo.rootId; - const newPost = await this._httpRequest('POST', '/api/v4/posts', body); - // Update tracking - this._postInfo.set(newPost.id, postInfo); - this._postInfo.delete(postId); - // Update throttle state key - const newState = this._throttleState.get(postId); - if (newState) { - this._throttleState.delete(postId); - this._throttleState.set(newPost.id, { pending: null, timer: null, lastFiredAt: Date.now(), resolvers: [] }); - } - // Notify caller of new post ID - this.emit('post-replaced', postId, newPost.id); - this.metrics.updatesSent++; - resolvers.forEach(({ resolve }) => resolve()); - } else { - // Fallback: regular PUT update - await this._apiCallWithRetry('PUT', `/api/v4/posts/${postId}`, { - id: postId, - message: this._truncate(text), - }); - this.metrics.updatesSent++; - resolvers.forEach(({ resolve }) => resolve()); - } + // In-place PUT update — no delete+recreate, no flicker + await this._apiCallWithRetry('PUT', `/api/v4/posts/${postId}`, { + id: postId, + message: this._truncate(text), + }); + this.metrics.updatesSent++; + resolvers.forEach(({ resolve }) => resolve()); } catch (err) { this.metrics.updatesFailed++; resolvers.forEach(({ reject }) => reject(err)); diff --git a/src/watcher-manager.js b/src/watcher-manager.js index bb0cdd8..371e8bb 100644 --- a/src/watcher-manager.js +++ b/src/watcher-manager.js @@ -240,6 +240,14 @@ async function startDaemon() { const initialText = buildInitialText(agentId, sessionKey); postId = await sharedStatusBox.createPost(channelId, initialText, rootPostId); logger.info({ sessionKey, postId, channelId }, 'Created status box'); + + // Auto-pin the status post so it's always visible + try { + await sharedStatusBox._apiCall('POST', '/api/v4/posts/' + postId + '/pin', {}); + logger.debug({ sessionKey, postId }, 'Status post pinned'); + } catch (pinErr) { + logger.warn({ sessionKey, err: pinErr }, 'Failed to pin status post'); + } } catch (err) { logger.error({ sessionKey, err }, 'Failed to create status post'); globalMetrics.lastError = err.message; @@ -282,18 +290,6 @@ async function startDaemon() { logger.debug({ sessionKey }, 'Session removed from sessions.json'); }); - // ---- Post Replaced (delete+recreate) ---- - sharedStatusBox.on('post-replaced', (oldPostId, newPostId) => { - // Update activeBoxes to point to new post ID - for (var entry of activeBoxes.entries()) { - if (entry[1].postId === oldPostId) { - entry[1].postId = newPostId; - logger.debug({ sessionKey: entry[0], oldPostId, newPostId }, 'Post replaced (delete+recreate)'); - break; - } - } - }); - // ---- Session Update (from watcher) ---- watcher.on('session-update', (sessionKey, state) => { const box = activeBoxes.get(sessionKey); @@ -345,7 +341,7 @@ async function startDaemon() { // Unpin the status post when session is done try { - await sharedStatusBox._apiCall('POST', `/api/v4/posts/${box.postId}/unpin`, null); + await sharedStatusBox._apiCall('POST', `/api/v4/posts/${box.postId}/unpin`, {}); logger.debug({ sessionKey, postId: box.postId }, 'Status post unpinned'); } catch (unpinErr) { logger.warn({ sessionKey, err: unpinErr }, 'Failed to unpin status post');