- background_output: snapshot read cursor before consuming, restore on /undo message removal so re-reads return data (fixes #2915) - MCP loader: preserve oauth field in transformMcpServer, add scope/ projectPath filtering so local-scoped MCPs only load in matching directories (fixes #2917) - runtime-fallback: add 'reached your usage limit' to retryable error patterns so quota exhaustion triggers model fallback (fixes #2918) Verified: bun test (4606 pass / 0 fail), tsc --noEmit clean
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { getMessageCursor, restoreMessageCursor, type CursorState } from "./session-cursor"
|
||||
|
||||
type MessageConsumptionKey = `${string}:${string}`
|
||||
|
||||
const cursorSnapshotsByMessage = new Map<MessageConsumptionKey, Map<string, CursorState | undefined>>()
|
||||
|
||||
function getMessageKey(sessionID: string, messageID: string): MessageConsumptionKey {
|
||||
return `${sessionID}:${messageID}`
|
||||
}
|
||||
|
||||
export function recordBackgroundOutputConsumption(
|
||||
parentSessionID: string | undefined,
|
||||
parentMessageID: string | undefined,
|
||||
taskSessionID: string | undefined
|
||||
): void {
|
||||
if (!parentSessionID || !parentMessageID || !taskSessionID) return
|
||||
|
||||
const messageKey = getMessageKey(parentSessionID, parentMessageID)
|
||||
const existing = cursorSnapshotsByMessage.get(messageKey) ?? new Map<string, CursorState | undefined>()
|
||||
|
||||
if (!cursorSnapshotsByMessage.has(messageKey)) {
|
||||
cursorSnapshotsByMessage.set(messageKey, existing)
|
||||
}
|
||||
|
||||
if (existing.has(taskSessionID)) return
|
||||
existing.set(taskSessionID, getMessageCursor(taskSessionID))
|
||||
}
|
||||
|
||||
export function restoreBackgroundOutputConsumption(
|
||||
parentSessionID: string | undefined,
|
||||
parentMessageID: string | undefined
|
||||
): void {
|
||||
if (!parentSessionID || !parentMessageID) return
|
||||
|
||||
const messageKey = getMessageKey(parentSessionID, parentMessageID)
|
||||
const snapshots = cursorSnapshotsByMessage.get(messageKey)
|
||||
if (!snapshots) return
|
||||
|
||||
cursorSnapshotsByMessage.delete(messageKey)
|
||||
for (const [taskSessionID, cursor] of snapshots) {
|
||||
restoreMessageCursor(taskSessionID, cursor)
|
||||
}
|
||||
}
|
||||
|
||||
export function clearBackgroundOutputConsumptionsForParentSession(sessionID: string | undefined): void {
|
||||
if (!sessionID) return
|
||||
|
||||
const prefix = `${sessionID}:`
|
||||
for (const messageKey of cursorSnapshotsByMessage.keys()) {
|
||||
if (messageKey.startsWith(prefix)) {
|
||||
cursorSnapshotsByMessage.delete(messageKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function clearBackgroundOutputConsumptionsForTaskSession(taskSessionID: string | undefined): void {
|
||||
if (!taskSessionID) return
|
||||
|
||||
for (const [messageKey, snapshots] of cursorSnapshotsByMessage) {
|
||||
snapshots.delete(taskSessionID)
|
||||
if (snapshots.size === 0) {
|
||||
cursorSnapshotsByMessage.delete(messageKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function clearBackgroundOutputConsumptionState(): void {
|
||||
cursorSnapshotsByMessage.clear()
|
||||
}
|
||||
@@ -13,13 +13,21 @@ export type CursorMessage = {
|
||||
info?: MessageInfo
|
||||
}
|
||||
|
||||
interface CursorState {
|
||||
export interface CursorState {
|
||||
lastKey?: string
|
||||
lastCount: number
|
||||
}
|
||||
|
||||
const sessionCursors = new Map<string, CursorState>()
|
||||
|
||||
function cloneCursorState(state: CursorState | undefined): CursorState | undefined {
|
||||
if (!state) return undefined
|
||||
return {
|
||||
lastKey: state.lastKey,
|
||||
lastCount: state.lastCount,
|
||||
}
|
||||
}
|
||||
|
||||
function buildMessageKey(message: CursorMessage, index: number): string {
|
||||
const id = message.info?.id
|
||||
if (id) return `id:${id}`
|
||||
@@ -83,3 +91,18 @@ export function resetMessageCursor(sessionID?: string): void {
|
||||
}
|
||||
sessionCursors.clear()
|
||||
}
|
||||
|
||||
export function getMessageCursor(sessionID: string | undefined): CursorState | undefined {
|
||||
if (!sessionID) return undefined
|
||||
return cloneCursorState(sessionCursors.get(sessionID))
|
||||
}
|
||||
|
||||
export function restoreMessageCursor(sessionID: string | undefined, cursor: CursorState | undefined): void {
|
||||
if (!sessionID) return
|
||||
if (!cursor) {
|
||||
sessionCursors.delete(sessionID)
|
||||
return
|
||||
}
|
||||
|
||||
sessionCursors.set(sessionID, cloneCursorState(cursor)!)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user