* Execute idle compaction for a workspace directly from the backend. * * This path is frontend-independent: compaction still runs even if no UI is open. * Throws on failure so IdleCompactionService can log and continue with the next workspace.
(workspaceId: string)
| 9028 | * Throws on failure so IdleCompactionService can log and continue with the next workspace. |
| 9029 | */ |
| 9030 | async executeIdleCompaction(workspaceId: string): Promise<void> { |
| 9031 | assert(workspaceId.trim().length > 0, "executeIdleCompaction requires a non-empty workspaceId"); |
| 9032 | |
| 9033 | const sendOptions = await this.buildIdleCompactionSendOptions(workspaceId); |
| 9034 | |
| 9035 | const muxMetadata: MuxMessageMetadata = { |
| 9036 | type: "compaction-request", |
| 9037 | rawCommand: "/compact", |
| 9038 | commandPrefix: "/compact", |
| 9039 | parsed: { |
| 9040 | model: sendOptions.model, |
| 9041 | }, |
| 9042 | requestedModel: sendOptions.model, |
| 9043 | source: "idle-compaction", |
| 9044 | displayStatus: { emoji: "💤", message: "Compacting idle workspace..." }, |
| 9045 | }; |
| 9046 | |
| 9047 | const session = this.getOrCreateSession(workspaceId); |
| 9048 | if (session.isBusy()) { |
| 9049 | // Expected race (workspace became active), not a failure — do not report an outcome. |
| 9050 | throw new Error(`Failed to execute idle compaction: ${IDLE_ONLY_BUSY_SKIP_MESSAGE}`); |
| 9051 | } |
| 9052 | |
| 9053 | const sendResult = await this.sendMessage( |
| 9054 | workspaceId, |
| 9055 | buildCompactionMessageText({}), |
| 9056 | { |
| 9057 | ...sendOptions, |
| 9058 | muxMetadata, |
| 9059 | }, |
| 9060 | { |
| 9061 | // Idle compaction runs in background; avoid mutating auto-resume counters. |
| 9062 | skipAutoResumeReset: true, |
| 9063 | // Backend-initiated maintenance turn: do not treat as explicit user re-engagement. |
| 9064 | synthetic: true, |
| 9065 | // If the workspace became active after eligibility checks, skip instead of queueing |
| 9066 | // stale maintenance work for later. |
| 9067 | requireIdle: true, |
| 9068 | } |
| 9069 | ); |
| 9070 | |
| 9071 | if (!sendResult.success) { |
| 9072 | const rawError = sendResult.error; |
| 9073 | const formattedError = |
| 9074 | typeof rawError === "object" && rawError !== null |
| 9075 | ? "raw" in rawError && typeof rawError.raw === "string" |
| 9076 | ? rawError.raw |
| 9077 | : "message" in rawError && typeof rawError.message === "string" |
| 9078 | ? rawError.message |
| 9079 | : "type" in rawError && typeof rawError.type === "string" |
| 9080 | ? rawError.type |
| 9081 | : JSON.stringify(rawError) |
| 9082 | : String(rawError); |
| 9083 | // Report genuine pre-stream failures (e.g. invalid/unavailable compaction model) so the |
| 9084 | // idle loop can stop re-attempting this workspace. Mid-stream failures are reported |
| 9085 | // separately from the stream error/completion listeners. The requireIdle busy-skip is an |
| 9086 | // expected race (workspace became active), so it must NOT count toward suppression. |
| 9087 | const isBusySkip = |
no test coverage detected