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:
sol
2026-03-07 20:31:32 +00:00
parent cc485f0009
commit 868574d939
31 changed files with 3596 additions and 68 deletions

View File

@@ -276,7 +276,10 @@ class SessionMonitor extends EventEmitter {
} catch (_e) {
// File doesn't exist — skip silently
if (this.logger) {
this.logger.debug({ sessionKey, transcriptFile }, 'Skipping session (transcript not found)');
this.logger.debug(
{ sessionKey, transcriptFile },
'Skipping session (transcript not found)',
);
}
return;
}

View File

@@ -76,10 +76,6 @@ class StatusBox extends EventEmitter {
// Throttle state per postId
// Map<postId, { pending: string|null, timer: NodeJS.Timeout|null, lastFiredAt: number }>
this._throttleState = new Map();
// Track post metadata for delete+recreate
// Map<postId, { channelId, rootId }>
this._postInfo = new Map();
}
/**
@@ -97,9 +93,6 @@ class StatusBox extends EventEmitter {
if (this.logger) this.logger.debug({ postId: post.id, channelId }, 'Created status post');
this.metrics.updatesSent++;
// Track post info for delete+recreate
this._postInfo.set(post.id, { channelId, rootId: rootId || null });
return post.id;
}
@@ -145,6 +138,9 @@ class StatusBox extends EventEmitter {
/**
* Flush the pending update for a postId.
* @private
* Note: PUT updates cause Mattermost to show '(edited)' label on the post.
* This is a known API limitation. The Mattermost plugin (Phase 3) solves this
* via custom post type rendering.
*/
async _flushUpdate(postId) {
const state = this._throttleState.get(postId);
@@ -163,7 +159,7 @@ class StatusBox extends EventEmitter {
this.metrics.queueDepth = Math.max(0, this.metrics.queueDepth - 1);
try {
// In-place PUT update — no delete+recreate, no flicker
// In-place PUT update
await this._apiCallWithRetry('PUT', `/api/v4/posts/${postId}`, {
id: postId,
message: this._truncate(text),
@@ -202,15 +198,6 @@ class StatusBox extends EventEmitter {
await Promise.allSettled(postIds.map((id) => this.forceFlush(id)));
}
/**
* Delete a post.
* @param {string} postId
* @returns {Promise<void>}
*/
async deletePost(postId) {
await this._apiCall('DELETE', `/api/v4/posts/${postId}`, null);
}
/**
* Truncate text to maxMessageChars.
* @private

View File

@@ -70,7 +70,12 @@ function format(sessionState, opts = {}) {
// never collapses like code blocks do, supports inline markdown
var body = lines.join('\n');
if (depth === 0) {
body = body.split('\n').map(function (l) { return '> ' + l; }).join('\n');
body = body
.split('\n')
.map(function (l) {
return '> ' + l;
})
.join('\n');
}
return body;
}

View File

@@ -52,9 +52,12 @@ class StatusWatcher extends EventEmitter {
if (this.sessions.has(sessionKey)) return;
// For new sessions (no saved offset), start from current file position
// so we only show NEW content going forward — not the entire backlog
let startOffset = initialState.lastOffset || 0;
if (!initialState.lastOffset) {
// so we only show NEW content going forward — not the entire backlog.
// Pass lastOffset: 0 explicitly to read from the beginning.
var startOffset;
if (initialState.lastOffset !== undefined) {
startOffset = initialState.lastOffset;
} else {
try {
var stat = fs.statSync(transcriptFile);
startOffset = stat.size;
@@ -86,8 +89,8 @@ class StatusWatcher extends EventEmitter {
this.logger.debug({ sessionKey, transcriptFile, startOffset }, 'Session added to watcher');
}
// Only read if we have a saved offset (recovery) — new sessions start streaming from current position
if (initialState.lastOffset) {
// Only read if we have an explicit offset (recovery or explicit 0) — new sessions start streaming from current position
if (initialState.lastOffset !== undefined) {
this._readFile(sessionKey, state);
}
@@ -327,9 +330,7 @@ class StatusWatcher extends EventEmitter {
argStr = item.arguments.url.slice(0, 60);
}
}
var statusLine = argStr
? toolName + ': ' + argStr
: toolName + ': ' + label;
var statusLine = argStr ? toolName + ': ' + argStr : toolName + ': ' + label;
state.lines.push(statusLine);
} else if (item.type === 'text' && item.text) {
var text = item.text.trim();

View File

@@ -240,14 +240,6 @@ 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;
@@ -339,14 +331,6 @@ async function startDaemon() {
logger.error({ sessionKey, err }, 'Failed to update final status');
}
// Unpin the status post when session is done
try {
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');
}
// Clean up — remove from all tracking so session can be re-detected if it becomes active again
activeBoxes.delete(sessionKey);
watcher.removeSession(sessionKey);