feat: add seq cursor, work_items, provider fallback, and delivery reliability
Major reliability improvements to the message processing pipeline: - Add messages.seq monotonic cursor replacing timestamp-based cursors, preventing message loss from timestamp collisions with LIMIT queries - Add work_items table separating agent production from delivery, enabling delivery retry without re-running the agent - Propagate Discord send failures instead of silently swallowing them - Add Claude 429 → Kimi K2.5 automatic provider fallback with cooldown - Fix follow-up turn state reset in live index.ts path (not just message-runtime.ts) to prevent final output from being lost - Add restart context tracking for graceful restart announcements - Lazy migration from timestamp cursors to seq cursors for existing data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,12 @@ interface QueuedTask {
|
||||
fn: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface GroupActivityTouchMeta {
|
||||
source: 'follow-up';
|
||||
textLength: number;
|
||||
filename: string;
|
||||
}
|
||||
|
||||
export interface GroupRunContext {
|
||||
runId: string;
|
||||
reason: 'messages' | 'drain';
|
||||
@@ -35,6 +41,7 @@ interface GroupState {
|
||||
retryTimer: ReturnType<typeof setTimeout> | null;
|
||||
retryScheduledAt: number | null;
|
||||
startedAt: number | null;
|
||||
activityTouch: ((meta?: GroupActivityTouchMeta) => void) | null;
|
||||
}
|
||||
|
||||
export interface GroupStatus {
|
||||
@@ -73,6 +80,7 @@ export class GroupQueue {
|
||||
retryTimer: null,
|
||||
retryScheduledAt: null,
|
||||
startedAt: null,
|
||||
activityTouch: null,
|
||||
};
|
||||
this.groups.set(groupJid, state);
|
||||
}
|
||||
@@ -85,6 +93,14 @@ export class GroupQueue {
|
||||
this.processMessagesFn = fn;
|
||||
}
|
||||
|
||||
setActivityTouch(
|
||||
groupJid: string,
|
||||
touch: ((meta?: GroupActivityTouchMeta) => void) | null,
|
||||
): void {
|
||||
const state = this.getGroup(groupJid);
|
||||
state.activityTouch = touch;
|
||||
}
|
||||
|
||||
private createRunId(): string {
|
||||
return `run-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
@@ -262,6 +278,11 @@ export class GroupQueue {
|
||||
const tempPath = `${filepath}.tmp`;
|
||||
fs.writeFileSync(tempPath, JSON.stringify({ type: 'message', text }));
|
||||
fs.renameSync(tempPath, filepath);
|
||||
state.activityTouch?.({
|
||||
source: 'follow-up',
|
||||
textLength: text.length,
|
||||
filename,
|
||||
});
|
||||
logger.info(
|
||||
{
|
||||
groupJid,
|
||||
@@ -390,6 +411,7 @@ export class GroupQueue {
|
||||
state.processName = null;
|
||||
state.groupFolder = null;
|
||||
state.currentRunId = null;
|
||||
state.activityTouch = null;
|
||||
this.activeCount--;
|
||||
this.drainGroup(groupJid);
|
||||
}
|
||||
@@ -424,6 +446,7 @@ export class GroupQueue {
|
||||
state.process = null;
|
||||
state.processName = null;
|
||||
state.groupFolder = null;
|
||||
state.activityTouch = null;
|
||||
this.activeCount--;
|
||||
this.drainGroup(groupJid);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user