Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
e50ad39
extract shared compaction prompt builder
ThomasK33 Feb 17, 2026
ad5b2bd
run idle compaction directly in backend
ThomasK33 Feb 17, 2026
226a79c
remove frontend idle compaction orchestration hook
ThomasK33 Feb 17, 2026
120c1d2
extract retry utilities to src/common
ThomasK33 Feb 17, 2026
fe9801c
add RetryManager and auto-retry event schemas
ThomasK33 Feb 17, 2026
b4b2548
integrate RetryManager into AgentSession
ThomasK33 Feb 17, 2026
cf2c82f
add setAutoRetryEnabled IPC route
ThomasK33 Feb 17, 2026
64949f3
remove frontend auto-retry orchestration
ThomasK33 Feb 17, 2026
1467aff
extract compaction threshold utilities to src/common
ThomasK33 Feb 17, 2026
3853cec
add CompactionMonitor and auto-compaction event schemas
ThomasK33 Feb 17, 2026
a3b67cb
integrate CompactionMonitor into AgentSession
ThomasK33 Feb 17, 2026
2935ff6
add setAutoCompactionThreshold IPC route
ThomasK33 Feb 17, 2026
7340d2d
remove frontend on-send and mid-stream compaction orchestration
ThomasK33 Feb 17, 2026
2f32760
clean up dead code and stale references after backend migration
ThomasK33 Feb 17, 2026
4752149
fix formatting
ThomasK33 Feb 17, 2026
964c080
fix lint errors in retry and compaction migration
ThomasK33 Feb 17, 2026
a079aad
fix integration tests for backend-driven compaction
ThomasK33 Feb 17, 2026
e19e0c9
fix: emit retry-abandoned on disable + handle duplicate failures grac…
ThomasK33 Feb 17, 2026
c18b594
fix: cancel pending retry on non-retryable error + fix mock abort event
ThomasK33 Feb 17, 2026
4a582dd
fix: address Codex review - compaction before persist, cancel timers …
ThomasK33 Feb 17, 2026
7881cc2
fix: seed usage state from persisted history before pre-send compacti…
ThomasK33 Feb 18, 2026
5cf1f9d
fix: make seedUsageStateFromHistory defensive + fix dispose race test…
ThomasK33 Feb 18, 2026
7a3dde5
fix: address Codex review - skip duplicate user emit, clear retry on …
ThomasK33 Feb 18, 2026
d6ead7e
fix: defer snapshot persistence when on-send auto-compaction triggers
ThomasK33 Feb 18, 2026
5f91c43
fix: thread provider model config into backend compaction checks
ThomasK33 Feb 18, 2026
c7aedb6
fix: avoid cached token double-count in mid-stream compaction checks
ThomasK33 Feb 18, 2026
f993df5
fix: preserve retry opt-out on synthetic sends and surface manual res…
ThomasK33 Feb 18, 2026
0986c9f
fix: mark idle compaction dispatch as synthetic send
ThomasK33 Feb 18, 2026
5770342
fix: preserve synthetic context for queued sends and handle ask-user …
ThomasK33 Feb 18, 2026
be47482
fix: scope queued synthetic sends and roll back ask-user retry on exc…
ThomasK33 Feb 18, 2026
3c009ec
fix: order ask-user retry rollback and guard pre-enable failures
ThomasK33 Feb 18, 2026
d90b03a
fix: serialize RetryBarrier auto-retry rollback after failed resume
ThomasK33 Feb 18, 2026
fff17fc
fix: guard retry callback against disable races in starting phase
ThomasK33 Feb 18, 2026
585ddee
fix: ignore stale retry rejection events after disable/generation change
ThomasK33 Feb 18, 2026
a764881
fix: recover interrupted streams on startup
ThomasK33 Feb 18, 2026
295ec72
fix: preserve tool policy in startup auto-retry
ThomasK33 Feb 18, 2026
3b4fdf5
fix: persist startup retry preferences and flags
ThomasK33 Feb 18, 2026
3b9e16e
fix: harden startup retry option reconstruction
ThomasK33 Feb 18, 2026
dd0f31d
fix: run full startup recovery and honor disabled midstream compaction
ThomasK33 Feb 18, 2026
e07bdbf
fix: serialize startup recovery dispatches
ThomasK33 Feb 18, 2026
e170ad6
fix startup auto-retry candidate selection
ThomasK33 Feb 18, 2026
8fb57e0
fix replay auto-retry countdown on reconnect
ThomasK33 Feb 18, 2026
0afe72e
fix startup retry option restore and preference rollbacks
ThomasK33 Feb 18, 2026
a653162
fix ask-user retry preference and idle compaction busy race
ThomasK33 Feb 18, 2026
3bdb358
πŸ€– fix: recover startup auto-retry for pre-stream failures
ThomasK33 Feb 18, 2026
3bb4650
fix: clear stale retry ui and seed compaction usage by epoch
ThomasK33 Feb 19, 2026
363c373
fix: guard ask_user workspace state lookup
ThomasK33 Feb 19, 2026
d669922
fix: preserve ask-user retry rollback across workspace switches
ThomasK33 Feb 19, 2026
b97bbfe
fix: trigger on-send compaction at configured threshold
ThomasK33 Feb 19, 2026
57943f6
fix: restore retry preference after manual retry success
ThomasK33 Feb 19, 2026
fd2e791
fix: rollback temporary ask-user retry on teardown
ThomasK33 Feb 19, 2026
3b2f15b
fix: harden temporary auto-retry rollback timing
ThomasK33 Feb 19, 2026
5441561
test: avoid global module mocks in retry/ask-user tests
ThomasK33 Feb 19, 2026
2d820f5
fix: handle terminal-state rollback races for temp auto-retry
ThomasK33 Feb 19, 2026
1f57bb6
fix: keep temporary retry toggles runtime-only
ThomasK33 Feb 19, 2026
71d64e0
fix: rollback temp retry on no-start resume success
ThomasK33 Feb 19, 2026
62b650b
fix: add deterministic resume-start signal for retry rollback
ThomasK33 Feb 19, 2026
3d3caf4
fix: persist pre-stream abort marker for startup retry
ThomasK33 Feb 19, 2026
e90d600
fix: honor preferred model for auto-compaction
ThomasK33 Feb 19, 2026
fd484c8
test: stabilize pre-stream abort persistence assertion
ThomasK33 Feb 19, 2026
0321200
fix: ignore startup aborts for startup retry abandon markers
ThomasK33 Feb 19, 2026
c256f92
fix: migrate legacy auto-retry opt-out on chat subscribe
ThomasK33 Feb 19, 2026
27ecd1c
fix: sync startup threshold with retry turn model
ThomasK33 Feb 19, 2026
6da94fa
test: stabilize workspace sync subscription assertion
ThomasK33 Feb 19, 2026
08b1269
fix: harden startup threshold sync and compaction dispatch errors
ThomasK33 Feb 19, 2026
ffe87e6
fix: skip compaction on invalid context-limit overrides
ThomasK33 Feb 19, 2026
b0bfafb
test: reduce pre-start interrupt message-id flake
ThomasK33 Feb 19, 2026
adc235f
fix: remove unnecessary stream-end type assertion
ThomasK33 Feb 20, 2026
41c9d12
fix: keep retry barrier mounted during manual retry stream
ThomasK33 Feb 20, 2026
ebd0d76
fix: emit idle compaction started only after dispatch
ThomasK33 Feb 20, 2026
e7adfc6
fix: retry startup follow-up dispatch after send failures
ThomasK33 Feb 20, 2026
e23d918
fix: retry startup recovery after history read failures
ThomasK33 Feb 20, 2026
673f684
fix: defer startup auto-retry after transient history failures
ThomasK33 Feb 20, 2026
66010e1
fix: back off startup auto-retry history read retries
ThomasK33 Feb 20, 2026
331cec7
Honor persisted auto-retry opt-out before retry scheduling
ThomasK33 Feb 20, 2026
05acab3
Reschedule auto-retry when resumeStream defers
ThomasK33 Feb 20, 2026
79b22c2
Ignore desktop retry/compaction events in mobile expander
ThomasK33 Feb 20, 2026
d1f6f16
Harden auto-retry migration and send acceptance reset
ThomasK33 Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions mobile/src/messages/normalizeChatEvent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@ describe("createChatEventExpander", () => {
});
});

it("ignores desktop-only compaction and retry status events", () => {
const expander = createChatEventExpander();

const events = expander.expand([
{ type: "idle-compaction-needed" } as WorkspaceChatEvent,
{ type: "idle-compaction-started" } as WorkspaceChatEvent,
{ type: "auto-compaction-triggered" } as WorkspaceChatEvent,
{ type: "auto-compaction-completed" } as WorkspaceChatEvent,
{ type: "auto-retry-scheduled" } as WorkspaceChatEvent,
{ type: "auto-retry-starting" } as WorkspaceChatEvent,
{ type: "auto-retry-abandoned" } as WorkspaceChatEvent,
]);

expect(events).toEqual([]);
});

it("emits displayable entries for mux messages replayed from history", () => {
const expander = createChatEventExpander();

Expand Down
9 changes: 8 additions & 1 deletion mobile/src/messages/normalizeChatEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,15 @@ export function createChatEventExpander(): ChatEventExpander {
// Usage delta: mobile app doesn't display usage, silently ignore
"usage-delta": () => [],

// Idle compaction signal: desktop auto-triggers; mobile currently ignores.
// Compaction/retry status signals: desktop-only affordances for now.
// Mobile intentionally drops these to avoid noisy "unsupported event" rows.
"idle-compaction-needed": () => [],
"idle-compaction-started": () => [],
"auto-compaction-triggered": () => [],
"auto-compaction-completed": () => [],
"auto-retry-scheduled": () => [],
"auto-retry-starting": () => [],
"auto-retry-abandoned": () => [],

// Pass-through events: return unchanged
"caught-up": () => [payload as WorkspaceChatEvent],
Expand Down
4 changes: 0 additions & 4 deletions src/browser/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { matchesKeybind, KEYBINDS } from "./utils/ui/keybinds";
import { handleLayoutSlotHotkeys } from "./utils/ui/layoutSlotHotkeys";
import { buildSortedWorkspacesByProject } from "./utils/ui/workspaceFiltering";
import { getVisibleWorkspaceIds } from "./utils/ui/workspaceDomNav";
import { useResumeManager } from "./hooks/useResumeManager";
import { useUnreadTracking } from "./hooks/useUnreadTracking";
import { useWorkspaceStoreRaw, useWorkspaceRecency } from "./stores/WorkspaceStore";

Expand Down Expand Up @@ -244,9 +243,6 @@ function AppInner() {
}
}, [refreshWorkspaceMetadata, setSelectedWorkspace]);

// Auto-resume interrupted streams on app startup and when failures occur
useResumeManager();

// Update window title based on selected workspace
// URL syncing is now handled by RouterContext
useEffect(() => {
Expand Down
8 changes: 0 additions & 8 deletions src/browser/components/AppLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { ProjectProvider, useProjectContext } from "../contexts/ProjectContext";
import { PolicyProvider, usePolicy } from "@/browser/contexts/PolicyContext";
import { PolicyBlockedScreen } from "@/browser/components/PolicyBlockedScreen";
import { APIProvider, useAPI, type APIClient } from "@/browser/contexts/API";
import { useIdleCompactionHandler } from "@/browser/hooks/useIdleCompactionHandler";
import { WorkspaceProvider, useWorkspaceContext } from "../contexts/WorkspaceContext";
import { RouterProvider } from "../contexts/RouterContext";
import { TelemetryEnabledProvider } from "../contexts/TelemetryEnabledContext";
Expand Down Expand Up @@ -64,13 +63,6 @@ function AppLoaderInner() {
const apiState = useAPI();
const api = apiState.api;

// Idle compaction should be registered at the app level so it can serialize across
// all workspaces (and won't reset when switching workspaces).
//
// Avoid triggering background compactions when mux is blocked by policy.
const idleCompactionApi = policyState.status.state === "blocked" ? null : api;
useIdleCompactionHandler({ api: idleCompactionApi });

// Get store instances
const workspaceStoreInstance = useWorkspaceStoreRaw();
const gitStatusStore = useGitStatusStoreRaw();
Expand Down
91 changes: 2 additions & 89 deletions src/browser/components/ChatInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,11 @@ import {
getWorkspaceLastReadKey,
} from "@/common/constants/storage";
import {
executeCompaction,
prepareCompactionMessage,
processSlashCommand,
type SlashCommandContext,
} from "@/browser/utils/chatCommands";
import { Button } from "../ui/button";
import { shouldTriggerAutoCompaction } from "@/browser/utils/compaction/shouldTriggerAutoCompaction";
import { CUSTOM_EVENTS } from "@/common/constants/events";
import { findAtMentionAtCursor } from "@/common/utils/atMentions";
import {
Expand Down Expand Up @@ -98,7 +96,7 @@ import type { AgentAiDefaults } from "@/common/types/agentAiDefaults";
import { coerceThinkingLevel, type ThinkingLevel } from "@/common/types/thinking";
import { resolveThinkingInput } from "@/common/utils/thinking/policy";
import {
type MuxFrontendMetadata,
type MuxMessageMetadata,
type ReviewNoteDataForDisplay,
prepareUserMessageForSend,
} from "@/common/types/message";
Expand Down Expand Up @@ -181,8 +179,6 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
const editingMessage = variant === "workspace" ? props.editingMessage : undefined;
const isStreamStarting = variant === "workspace" ? (props.isStreamStarting ?? false) : false;
const isCompacting = variant === "workspace" ? (props.isCompacting ?? false) : false;
const hasQueuedCompaction =
variant === "workspace" ? (props.hasQueuedCompaction ?? false) : false;
const [isMobileTouch, setIsMobileTouch] = useState(
() =>
typeof window !== "undefined" &&
Expand Down Expand Up @@ -1868,89 +1864,6 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
const preSendDraft = getDraft();
const preSendReviews = draftReviews;

// Auto-compaction check (workspace variant only)
// Check if we should auto-compact before sending this message
// Result is computed in parent (AIView) and passed down to avoid duplicate calculation
if (
variant === "workspace" &&
shouldTriggerAutoCompaction(
props.autoCompactionCheck,
isCompacting || isStreamStarting,
!!editingMessage,
hasQueuedCompaction
)
) {
// Prepare file parts for the continue message
const fileParts = chatAttachmentsToFileParts(attachments);

// Prepare reviews data for the continue message
const reviewsData = reviewData;

// Capture review IDs for marking as checked on success
const sentReviewIds = reviewIdsForCheck;

// Clear input immediately for responsive UX
setInput("");
setDraftReviews(null);

const compactionSendMessageOptions: SendMessageOptions = {
...sendMessageOptions,
};

setAttachments([]);
setHideReviewsDuringSend(true);

try {
const result = await executeCompaction({
api,
workspaceId: props.workspaceId,
followUpContent: {
text: messageTextForSend,
fileParts,
reviews: reviewsData,
muxMetadata: skillMuxMetadata,
},
sendMessageOptions: compactionSendMessageOptions,
});

if (!result.success) {
// Restore on error
setDraft(preSendDraft);
setDraftReviews(preSendReviews);
pushToast({
type: "error",
title: "Auto-Compaction Failed",
message: result.error ?? "Failed to start auto-compaction",
});
} else {
// Mark reviews as checked on success
if (sentReviewIds.length > 0) {
props.onCheckReviews?.(sentReviewIds);
}
pushToast({
type: "success",
message: "Context threshold reached - auto-compacting...",
});
props.onMessageSent?.();
}
} catch (error) {
// Restore on unexpected error
setDraft(preSendDraft);
setDraftReviews(preSendReviews);
pushToast({
type: "error",
title: "Auto-Compaction Failed",
message:
error instanceof Error ? error.message : "Unexpected error during auto-compaction",
});
} finally {
setSendingCount((c) => c - 1);
setHideReviewsDuringSend(false);
}

return; // Skip normal send
}

try {
// Prepare file parts if any
const fileParts = chatAttachmentsToFileParts(attachments, { validate: true });
Expand All @@ -1965,7 +1878,7 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {

// When editing a /compact command, regenerate the actual summarization request
let actualMessageText = messageTextForSend;
let muxMetadata: MuxFrontendMetadata | undefined = skillMuxMetadata;
let muxMetadata: MuxMessageMetadata | undefined = skillMuxMetadata;
let compactionOptions: Partial<SendMessageOptions> = {};

if (editingMessage && actualMessageText.startsWith("/")) {
Expand Down
4 changes: 0 additions & 4 deletions src/browser/components/ChatInput/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { FrontendWorkspaceMetadata } from "@/common/types/workspace";
import type { TelemetryRuntimeType } from "@/common/telemetry/payload";
import type { AutoCompactionCheckResult } from "@/browser/utils/compaction/autoCompactionCheck";
import type { Review } from "@/common/types/review";
import type { EditingMessageState, PendingUserMessage } from "@/browser/utils/chatEditing";

Expand Down Expand Up @@ -37,9 +36,6 @@ export interface ChatInputWorkspaceVariant {
/** Optional explanation displayed when input is disabled */
disabledReason?: string;
onReady?: (api: ChatInputAPI) => void;
autoCompactionCheck?: AutoCompactionCheckResult; // Computed in parent (AIView) to avoid duplicate calculation
/** True if there's already a compaction request queued (prevents double-compaction) */
hasQueuedCompaction?: boolean;
/** Reviews currently attached to chat (from useReviews hook) */
attachedReviews?: Review[];
/** Detach a review from chat input (sets status to pending) */
Expand Down
4 changes: 2 additions & 2 deletions src/browser/components/ChatInput/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { APIClient } from "@/browser/contexts/API";
import type { AgentSkillDescriptor } from "@/common/types/agentSkill";
import type { SendMessageError } from "@/common/types/errors";
import type { ParsedRuntime } from "@/common/types/runtime";
import { buildAgentSkillMetadata, type MuxFrontendMetadata } from "@/common/types/message";
import { buildAgentSkillMetadata, type MuxMessageMetadata } from "@/common/types/message";
import type { FilePart } from "@/common/orpc/types";
import type { ChatAttachment } from "../ChatAttachments";
import type { Review } from "@/common/types/review";
Expand Down Expand Up @@ -50,7 +50,7 @@ function isUnknownSlashCommand(value: ParsedCommand): value is UnknownSlashComma
export function buildSkillInvocationMetadata(
rawCommand: string,
descriptor: AgentSkillDescriptor
): MuxFrontendMetadata {
): MuxMessageMetadata {
return buildAgentSkillMetadata({
rawCommand,
commandPrefix: `/${descriptor.name}`,
Expand Down
Loading
Loading