🤖 refactor: auto-cleanup - #3695
Conversation
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
|
Root cause: The Verification: Ran Recommendation: Re-run the failed CI jobs. No code change needed. |
|
@codex review Latest push rebases onto |
|
To use Codex here, create a Codex account and connect to github. |
|
@codex review New in this run: cleanup #3 — deduped the identical blockquote line-prefixing ( |
|
To use Codex here, create a Codex account and connect to github. |
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
78cd7b2 to
a676f79
Compare
|
@codex review Added auto-cleanup #5: |
|
To use Codex here, create a Codex account and connect to github. |
a676f79 to
32928e3
Compare
|
To use Codex here, create a Codex account and connect to github. |
32928e3 to
8afce4c
Compare
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
8afce4c to
990576b
Compare
|
To use Codex here, create a Codex account and connect to github. |
cd778d7 to
08734b9
Compare
|
@codex review This run adds one behavior-preserving cleanup (#8 in the branch list): dedupes the two identical |
|
To use Codex here, create a Codex account and connect to github. |
08734b9 to
b42facd
Compare
Extract the byte-identical 'reload under lock and bail unless the record is still the exact one we reconciled (same status AND updatedAt)' guard shared by persistRepairedSettledWorkspaceTurn and reviveRetryingWorkspaceTurn (both added in #3738) into a module-level isReconciledWorkspaceTurnUnchanged type guard. The guard narrows current to non-null for the revive path, and the generic 'compare updatedAt too' rationale now lives in one doc comment while each call site keeps its situational note. Behavior-preserving.
Both the archive keyboard-shortcut path and the context-menu item inlined
the byte-identical props.onArchiveAll(...).catch(() => { /* same comment */ })
fire-and-forget block (introduced by #3741). Extracted a local archiveAll
helper so the swallow-and-surface rationale lives in one place.
…descendant queries
…ions The isKimiK3Model docstring already explains that Kimi K3 always reasons and supports only the max reasoning effort, and that the provider-options branches key off the predicate. #3737 restated that same sentence verbatim in both the Moonshot and OpenRouter branches of buildProviderOptions, so trim the duplicated lead sentence and keep only each branch's site-specific "send it explicitly" rationale. Comment-only; behavior-preserving.
…ll sites formatSubagentReportUserMessage already omits structuredOutput from the envelope when it is undefined, so the two call sites re-implemented that exact guard. Forward report.structuredOutput directly instead. Behavior- preserving: the helper's internal !== undefined check yields byte-identical envelope output whether the key is absent or explicitly undefined.
The native/beta 1M-context pattern lists repeated the same
`new RegExp(`^<id>${OPTIONAL_VERSION_SUFFIX}$`, "i")` construction ten
times, so each new model entry had to restate the anchoring and flags.
Extract anthropicModelIdPattern() and map base model ids through it;
generated regex sources and flags are unchanged.
…ntId
normalizeAgentId's fallback parameter already defaults to
WORKSPACE_DEFAULTS.agentId ("exec"), so the four call sites that passed
"exec" explicitly were re-hardcoding the centralized default. Dropping the
literal also makes the workspaceModeAi wrapper (whose only job was supplying
that fallback) dead, so it and its aliased import are removed.
truncateDigest inlined both bounds as bare literals, where 117 silently encoded 120 - "...".length. Introduce DIGEST_MAX_LENGTH/DIGEST_ELLIPSIS and derive the slice from the ellipsis so the invariant that a truncated digest still totals DIGEST_MAX_LENGTH is explicit, matching the existing convention in truncateTimelineDigest. The 120-char bound is deliberately tighter than the 600-char schema-level boundTimelineTextFields safety net, so the helpers stay separate.
The `(max-width: 768px) and (pointer: coarse)` literal that gates Mux's mobile affordances was copied into seven `window.matchMedia` callsites across five renderer files, each independently responsible for staying in sync with the matching `@media` block in globals.css. Hoist it to `MOBILE_TOUCH_MEDIA_QUERY` in `src/constants/layout.ts`, alongside `MOBILE_TOUCH_TARGET_PX`, which already documents the same coarse-pointer environment. Behavior-preserving: every callsite passed a byte-identical string, so each `matchMedia` call receives exactly the value it did before.
Three toast hosts hard-coded the same absolute overlay box, with a doc comment asserting they stay identical. Hoist it into src/constants/layout.ts so the invariant is enforced by the shared constant instead of by copy-paste.
The composer dock focus handler added in #3759 repeated the bare `button !== 0` magic number already used by the diff review drag-select handler. Name the check once in browser/utils/events so both call sites read as intent.
The dropdown option row recomputed `value === model` four times and `index === highlightedIndex` twice across the option class, ARIA state, and accent styling. Extract them as `isSelected`/`isHighlighted` locals so the row's state is named once and can't drift apart, matching the naming already used by the sibling AgentModePicker. Behavior-preserving: identical expressions, same evaluation per row.
The repository link added in #3762 duplicated the 'Last prompt' pill styling verbatim; extract it so the two footer affordances cannot drift apart.
#3779 routes monitor wakes to a dedicated transcript component and excludes them from prev/next prompt navigation, but each site re-derived the check as an inline bashMonitorWake null test in a different file. Extract isBashMonitorWakeMessage into messageUtils so both consumers agree by construction.
Four call sites inlined the identical 'join every text part, then parse the subagent report envelope' projection. #3783 added two more copies in findProgressRespondedTaskIds and hasAcceptedSubagentProgressReport, joining the pre-existing copies in TaskService sibling-report discovery and AgentSession's isVisibleCompletedSubagentReportMessage. Extract the projection into parseSubagentReportFromMessage, co-located with the string parser it wraps, and route all four sites through it.
c1fd548 to
2e0ad05
Compare
|
@codex review Auto-cleanup run: rebased onto Extracted The helper body is a verbatim move; the |
|
To use Codex here, create a Codex account and connect to github. |
✅ CI green — Codex gate still blocked on connector setupAll non-visual-review checks pass on Codex could not review. Both
That is an account/connector configuration issue on the repo side, not a signal about this diff, and it is outside what this agent can fix. There are zero unresolved review threads and no other reviewer feedback outstanding on the PR. Local validation for this run's cleanup:
Per this PR's readiness rules I'm pausing here rather than churning further: the remaining gate needs a human to connect the Codex app. |
Summary
This is the long-lived auto-cleanup PR. Each run, the auto-cleanup agent reviews new commits merged to
main, rebases onto the latestmain, and applies at most one extremely low-risk, behavior-preserving cleanup. The branch accumulates a small stack of independent cleanups until it is merged.Cleanups in this branch
Cleanups 1–43 (older runs)
Dedupe memory sweep
recordUsagecallbacks (MemoryConsolidationService). The consolidation sweep and the harvest sweep each inlined the same 15-line callback that routes billed usage to the headless-usage sidecar and emitsanalyticsIngest. Extracted into a privatemakeSweepUsageRecorder(...)helper.Dedupe the "memory scope is full" cap check (
MemoryService). ThecreateandsaveFile(new-file) paths each inlined a byte-identical block that calledstore.listFiles(), compared the count againstMEMORY_MAX_FILES_PER_SCOPE, and threw aMemoryCommandErrorwith the same message. Extracted into a privateassertScopeHasRoom(store, scope)helper.Dedupe blockquote line formatting in the bash monitor wake prompt (
bashMonitorWakeStore.ts).buildBashMonitorWakePromptrendered both the matched-output lines and the lost-monitor script with the identical.map((line) => \> ${line}`).join("\n")blockquote pattern in two places. Extracted into a module-levelblockquoteLines(lines)` helper.Dedupe the
tool_searchremoval inprepareToolSearch(toolCatalog.ts). Both fallback branches (PTC enabled, and empty deferred catalog) inlined the identical{ [TOOL_SEARCH_TOOL_NAME]: _removed, ...rest }destructure to drop the built-intool_searchentry from the tool record. Extracted into a module-levelwithoutToolSearch(tools)helper.Dedupe the Anthropic cache-create token extraction in
accumulateProviderMetadata(usageHelpers.ts). The function inlined the same verbose(metadata.anthropic as { cacheCreationInputTokens?: number } | undefined)?.cacheCreationInputTokens ?? 0cast twice (once for the accumulated metadata, once for the current step). Extracted into a module-privategetAnthropicCacheCreateTokens(metadata)helper.Dedupe capability-model thinking-policy resolution (
thinking/policy.ts). After 🤖 feat: integrate GPT-5.6 Sol/Terra/Luna with native max effort and pro-mode toggle #3708 taught the thinking policy to resolvemappedToModelaliases, bothgetThinkingPolicyForModelandhasExplicitThinkingPolicyinlined the identicalgetExplicitThinkingPolicy(resolveModelForMetadata(modelString, providersConfig ?? null))call. Extracted into a privategetExplicitThinkingPolicyForModel(modelString, providersConfig)helper.Dedupe queue entry clear-callback projection (
messageQueue.ts). After 🤖 feat: queue messages behind special sends instead of erroring (FIFO message queue) #3696 rewroteMessageQueueinto FIFOQueueEntryitems, bothgetClearCallbacksandremoveWorkspaceTurninlined the identical spread that builds aQueueClearCallbacksobject from an entry's optionalonCanceled/onAcceptedPreStreamFailurefields. Extracted into a privateentryClearCallbacks(entry)helper.Dedupe the OpenAI-origin model check in
openaiExplicitPromptCachingAvailable(cacheStrategy.ts). After 🤖 feat: GPT-5.6 explicit prompt cache breakpoints for direct OpenAI #3712 added the GPT-5.6 explicit-prompt-caching eligibility gate, the function inlined the identicalsplit(":", 2)+origin !== "openai" || !modelNamecheck twice — once for the request model and once for the resolved capability target — and the destructuredorigin/modelNamelocals were unused past their guard in both places. Extracted into a module-privateisOpenAIOriginModel(canonical)helper.Dedupe the
tool-call-execution-startemit inStreamManager(streamManager.ts). 🤖 fix: start tool elapsed timers when execute() actually runs #3716 introduced theToolCallExecutionStartEvent, emitted from two places:applyToolExecutionStart(part already stored) and the"tool-call"case that consumes apendingExecutionStartrecorded before the part landed. Both inlined the byte-identicalthis.emit("tool-call-execution-start", { type, workspaceId, messageId, toolCallId, timestamp } satisfies ToolCallExecutionStartEvent)block, differing only in thetoolCallId/timestampsource. Extracted into a privateemitToolCallExecutionStart(workspaceId, streamInfo, toolCallId, timestamp)helper.Dedupe model-parameter extras merge (
aiService.ts). After 🤖 feat: apply mid-turn thinking-level changes at the next model step #3718 added mid-turn thinking-level rebuilds, the initial-model path and the fallback-model path each inlined a byte-identical closure (mergeModelParameterExtras/mergeNextModelParameterExtras) that folds providers.jsoncproviderExtrasUNDER the Mux-built provider-options namespace (short-circuiting when there are no extras, deep-merging viamergeProviderExtrasUnderMuxwhen the namespace is a plain object). The two differed only in the namespace key and the overrides source. Extracted into a module-levelmakeModelParameterExtrasMerger(namespaceKey, providerExtras)factory that returns the merger closure.Unify the legacy
tool_searchpart-rename helper (toolCatalog.ts). 🤖 fix: avoid OpenAI tool search name collision #3719 renamed the built-in tool-search tool totool_catalog_searchand added request-time rewriting of historicaltool_searchcall/result parts. It introduced two byte-identical helpers —renameLegacyToolSearchCallPart(part: ToolCallPart)andrenameLegacyToolSearchResultPart(part: ToolResultPart)— that differ only in the part type; the rename body is identical. Collapsed both into a single genericrenameLegacyToolSearchPart<T extends { toolName: string }>(part: T)and dropped the now-unusedToolCallPart/ToolResultPartimports.Trim duplicated context-cap rationale comment (
codexOAuth.ts). 🤖 fix: cap GPT-5.6 context over Codex OAuth #3724 added the GPT-5.6 family toCODEX_OAUTH_CONTEXT_WINDOW_OVERRIDESand rewrote the map's inline comment with a sentence that restated the rationale already given in the map's doc comment directly above it. Dropped the duplicated rationale sentence, keeping only the tier-specific explanation. Comment-only; no behavior change. (Re-applied on top of [openai] 🤖 fix: use 372K GPT-5.6 OAuth context #3730, which later rewrote the same inline comment and re-introduced the duplicate.)Dedupe flat-section pinned block resolution (
pinnedReorder.ts). 🤖 feat: project-less scratch chats #3723 added a scratch branch tolocatePinnedBlockthat renders scratch chats as one flat "Chats" section, mirroring the existing multi-project branch. Both branches inlined the byte-identicalcollectFlatSectionRows(...).filter(isWorkspacePinned).map((row) => row.id)projection, theif (!pinnedIds.includes(meta.id)) return nullguard, and thereturn { fullOrder: pinnedIds, blockIds: pinnedIds }shape — differing only in theincludeRowpredicate ((row) => row.kind === "scratch"vsisMultiProject). Extracted into a privatelocateFlatSectionPinnedBlock(meta, sortedWorkspacesByProject, includeRow)helper.Dedupe JSON-wrapped tool-output unwrap (
workflowRunMessages.ts). 🤖 fix: stop terminal workflow await loops #3725 addedisTerminalWorkflowRunToolOutput, which re-inlined the byte-identicaloutput.type === "json" && "value" in outputcontainer check already used bystripWorkflowRunRecordForModelto detect the{ type: "json", value }SDK/UI wrapper before recursing on the inner value. Extracted the check into a module-privateisJsonWrappedOutput(output)helper that both functions call, moving the shared rationale into the helper's doc comment. No control flow or return-shape change.Hoist
errorTypelocal infinalizeWorkspaceTurnFromStreamError(taskService.ts). 🤖 fix: keep workspace-turn handles running through auto-retryable stream errors #3729 reworked workspace-turn stream-error settlement, and the reworked function readevent.errorTypethree times and repeated theevent.errorType != nullguard once for theexplicitRecoverycomputation and once in the recoveryif. Hoisted a singleconst errorType = event.errorTypeand routed all uses through it, deduplicating the repeated member access and null guard.ErrorEventis a Zod-inferred plain data type, so the property read has no side effects; pure behavior-preserving simplification with no control-flow change.Extract
buildSkillDescriptorhelper for skill discovery (common/orpc/schemas/agentSkill.ts+agent_skill_list.ts+agentSkillsService.ts). 🤖 feat: skills refresh — invocation control, $ARGUMENTS, dynamic context, .claude compat #3728 (skills refresh) addeduser-invocable/argument-hint/when_to_usefrontmatter and normalized them viaresolveSkillAdvertise/resolveSkillUserInvocable/resolveSkillWhenToUse. Both descriptor-building sites —readSkillDescriptor(theagent_skill_listtool) andreadSkillDescriptorFromDir(agentSkillsService discovery) — then inlined the byte-identical 7-field object literal mappingparsed.frontmatter+scopeinto anAgentSkillDescriptorbeforeAgentSkillDescriptorSchema.safeParse. Extracted the mapping into a sharedbuildSkillDescriptor(frontmatter, scope)inagentSkill.ts(co-located with theresolveSkill*helpers it calls) and dropped the now-unusedresolveSkill*imports at both call sites. Callers still runsafeParsethemselves since they handle validation failure differently. No behavior change.Hoist duplicated
Date.parse(record.createdAt)in the bash monitor delivery gate (workspaceService.ts). 🤖 fix: defer bash monitor wakes during task_await #3732 (defer bash monitor wakes duringtask_await) reworked the delivery gate indrainBashMonitorWakesso a match is re-checked against the shown frontier while pinned to its originating process instance viaDate.parse(record.createdAt). The new non-blockinggetMonitorWakeDeliveryStatebranch and the fallbackgetSettledShownThroughOffsetbranch each inlined the identicalDate.parse(record.createdAt)call as theoriginNotAfterMsargument. Hoisted a singleconst originNotAfterMs = Date.parse(record.createdAt)before the branches (with a clarifying comment on why the origin timestamp pins the check) and routed both calls through it.Date.parseis pure, so the hoist is behavior-preserving.Dedupe the "wait for any in-flight load" block in
DevToolsService(devToolsService.ts). 🤖 fix: clean up devtools.jsonl on archive/remove and reap orphaned session dirs #3733 addedremoveWorkspaceData(archive/remove DevTools cleanup) directly beside the existingclear; both inlined the byte-identicalconst pendingLoad = this.loadingPromises.get(workspaceId); if (pendingLoad) { await pendingLoad; }guard that drains any in-flightloadFromDiskbefore mutating in-memory state so a late load cannot repopulate stale data after the mutation. Extracted into a privateawaitPendingLoad(workspaceId)helper with the shared rationale in its doc comment; both call sites keep their situational one-line comment. No control-flow change.Dedupe MCP OAuth redirect URI resolution (
router.ts). Both the global (mcpOauth.startServerFlow) and per-project (projects.mcpOauth.startServerFlow) handlers inlined the byte-identical block that derives the OAuth callbackredirectUrifrom request headers — preferring theOriginheader (used verbatim when it parses as a URL), then falling back tox-forwarded-host/hostwith the forwarded proto (defaulting tohttp), and returningErr("Missing Host header")when no usable Host header exists. Extracted into a module-levelresolveMcpOauthRedirectUri(headers)helper that returns the resolved URI orundefined; each handler now mapsundefinedto the sameErr.startServerFlowisasync(its returned promise is passed through unawaited), so moving the call out of the origin-branchtrycannot change behavior — thetryonly ever guarded the synchronousnew URL(...)construction. No header semantics or return-shape change.Extract
getTotalTokenshelper for total-token sums (usageAggregator.ts+ 6 call sites). 🤖 feat: show per-model cost breakdown in workspace Costs tab #3739 (per-model cost breakdown in the Costs tab) added a fifth+ copy of the "sum every usage component" expression —input + cached + cacheCreate + output + reasoning.tokens— already inlined byte-for-byte inCostsTab(session model rows),WorkspaceStore(sessiontotalTokens),tokenMeterUtils(calculateTokenMeterDatatotal),sessionUsageService(per-modeltotalTokensaccumulation), and twice incli/run.ts(budgethasTokensgates). Added agetTotalTokens(usage)helper inusageAggregator.ts, co-located with and mirroring the existinggetTotalCost(same five-component iteration;undefined→0), and routed all six sites through it. The four-component sum incli/debug/costs.ts(which omitscacheCreate) was intentionally left untouched to preserve its existing behavior.Hoist the duplicated
dedupeKeyssnapshot inremoveByDedupeKeyPrefix(messageQueue.ts). 🤖 refactor: support incremental subagent reports #3714 (incremental sub-agent reports) addedMessageQueue.removeByDedupeKeyPrefix, which spread the entry'sdedupeKeysSetinto an array once for thematchingKeysprefix filter and then re-spread the sameSetinside theentry.messages.filter(...)callback — once per message iteration — to map each message index back to its dedupe key. TheSetis not mutated until afterkeptMessagesis computed, so both reads observe the same ordered snapshot. Hoisted a singleconst dedupeKeyList = [...entry.dedupeKeys]before the filter and routed both reads through it, eliminating the per-message re-spread. Pure behavior-preserving simplification with no control-flow change.Dedupe the settled workspace-turn reconciliation guard (
taskService.ts). 🤖 fix: report live workspace-turn state from task_await instead of stale settlements #3738 (report live workspace-turn state fromtask_await) added two read-time reconciliation helpers —persistRepairedSettledWorkspaceTurnandreviveRetryingWorkspaceTurn— that each open their settlement-lock body with the byte-identical guard: reload the handle viagetWorkspaceTurnandreturn currentunless it is still the exact record we reconciled against (current != null && current.status === record.status && current.updatedAt === record.updatedAt). Extracted the condition into a module-levelisReconciledWorkspaceTurnUnchanged(current, record)type guard, co-located with 🤖 fix: report live workspace-turn state from task_await instead of stale settlements #3738's ownisSelfHealEligibleSettledWorkspaceTurn, so the generic "compareupdatedAttoo, not just status" rationale lives in one doc comment while each call site keeps its situational note. Thecurrent is WorkspaceTurnTaskHandleRecordreturn type preserves the non-null narrowing thatreviveRetryingWorkspaceTurnrelies on after the guard. No control-flow or return-shape change.Dedupe the fire-and-forget archive-all catch (
TaskGroupListItem.tsx). 🤖 fix: archive all sidebar variants #3741 (archive all sidebar variants) added anonArchiveAllprop invoked from two places — the archive keyboard-shortcut branch inonKeyDownand theArchive all variantscontext-menu item'sonClick. Both inlined the byte-identical fire-and-forgetprops.onArchiveAll(...).catch(() => { /* the sidebar owner surfaces archive failures through its shared error UI */ })block, differing only in optional-call syntax (inert because the prop is defined in both branches). Extracted a localarchiveAll(buttonElement)helper so the swallow-and-surface rationale lives in one place. No control flow, arguments, or error-handling change.Extract
someDescendantAgentTaskWorkspacehelper for sticky-descendant queries (taskService.ts). [tasks] 🤖 feat: support sticky subagents #3744 (sticky subagents) added two adjacent query methods —hasStickyDescendantsandhasUnarchivedStickyDescendants— that each rebuilt the agent-task index the same way (loadConfigOrDefault()→buildAgentTaskIndex(cfg)→listDescendantAgentTaskIdsFromIndex(index, workspaceId).some(...)) and differed only in the.some()predicate. Extracted a privatesomeDescendantAgentTaskWorkspace(workspaceId, predicate)helper that resolves each descendant entry and threads it through the predicate (keeping.some()short-circuiting); the two public methods now just supply their predicate and keep their ownassert. The helper'sdescendant != null && predicate(descendant)guard is equivalent to the priorindex.byId.get(descendantId)?.taskSticky === trueform, so no behavior changes.Drop the duplicated Kimi K3 max-effort rationale (
providerOptions.ts). 🤖 feat: add native Kimi K3 support via a new Moonshot AI provider #3737 (native Kimi K3 via a new Moonshot AI provider) added theisKimiK3Modelpredicate, whose docstring is the authoritative statement that K3 always reasons and supports only the max reasoning effort and that the provider-options branches key off it. Both the Moonshot and OpenRouter branches ofbuildProviderOptionsthen restated that same lead sentence verbatim, so the duplicated sentence was trimmed from each while keeping only the branch-specific "send it explicitly" rationale (Moonshot: don't rely on the API default; OpenRouter:enabled: truealone falls back to the unsupported default medium effort). Comment-only; behavior-preserving.Drop the redundant
structuredOutputguard at subagent report call sites (taskService.ts). 🤖 feat: present subagent reports in chat #3742 (present subagent reports in chat) extractedformatSubagentReportUserMessage, which already omitsstructuredOutputfrom the report envelope when it isundefined(its internal!== undefinedconditional spread). Both call sites — the incrementalin_progressprogress report in theagent_reportpath and the terminalcompletedreport indeliverReportToParentUnlocked— nonetheless re-implemented that exact...(report.structuredOutput !== undefined ? { structuredOutput: report.structuredOutput } : {})guard before handing the value to the helper. Each now forwardsreport.structuredOutputdirectly, and the helper documents that it owns the omission. Behavior-preserving: the helper's internal guard yields byte-identical envelope output whether the key is absent or passed explicitly asundefined.Extract
isZipMediaTypehelper for staged attachment media-type checks (supportedAttachmentMediaTypes.ts). 🤖 feat: stage arbitrary pasted/dropped files into the workspace from chat #3746 (stage arbitrary pasted/dropped files) generalized the ZIP-only staged-attachment pipeline to arbitrary files, and in doing so the cast-laden ZIP membership checkZIP_MEDIA_TYPES.includes(normalized as (typeof ZIP_MEDIA_TYPES)[number])was inlined byte-identically in bothisSupportedStagedAttachmentMediaTypeandgetSupportedStagedAttachmentMediaType. Extracted a module-privateisZipMediaType(normalized)helper so theas consttuple cast lives in one place; both call sites now readisZipMediaType(normalized). Behavior-preserving.Hoist the duplicated
/goal-bypass-for-attachments check in the ChatInput send handler (ChatInput/index.tsx). 🤖 feat: stage arbitrary files from the creation composer #3748 (stage arbitrary files from the creation composer) computedgoalCommandBypassedForAttachments(parsed?.type === "goal-set" && attachments.length > 0) verbatim in two mutually-exclusive branches of the send handler: the creation-variant route and the workspace send path (the latter carrying a "mirror the creation-composer bypass" comment). Both branches resolveparsedandattachmentsidentically, so the boolean is now computed once above the routing and both inline copies dropped. Pure, behavior-preserving.Dedupe the anchored Anthropic model-id regex construction (
ai/models.ts). TheANTHROPIC_NATIVE_1M_PATTERNS/ANTHROPIC_BETA_1M_PATTERNSlists that backgetAnthropic1MContextModeeach spelled outnew RegExp(`^<id>${OPTIONAL_VERSION_SUFFIX}$`, "i")per entry — ten near-identical copies restating the anchoring, the optional dated-snapshot suffix interpolation, and the case-insensitive flag, so every new model (Opus 5 in 🤖 feat: add support for Claude Opus 5 #3750 being the latest) had to repeat the whole construction. Extracted a module-privateanthropicModelIdPattern(baseModelId)helper and reduced both lists to base model-id strings mapped through it. Generated regex sources and flags are byte-identical to before, and base ids are literal model names with no regex metacharacters, so matching is unchanged.Dedupe MCP header telemetry flag derivation (
orpc/router.ts). Everymcp_server_config_changedcapture site recomputed thehas_headers/uses_secret_headerspayload flags inline — eight copies across the global and per-project MCP routers (add,remove,setEnabled,setToolAllowlist). Two variants existed: an input-based one (Boolean(input.headers && Object.keys(input.headers).length > 0)plus the"secret" in vscan) and a server-based one that prefixed both with aserver.transport !== "stdio"guard, needed becauseheadersonly exists on the HTTP-ish arm of theMCPServerInfounion. ExtracteddescribeMcpHeaderTelemetry(headers)and a thindescribeMcpServerHeaderTelemetry(server)wrapper that returns{ hasHeaders: false, usesSecretHeaders: false }for stdio — exactly whattransport !== "stdio" && …already evaluated to — so the "what counts as a secret header" rationale lives in one doc comment. Behavior-preserving; ~80 lines removed.Extract
_child_dirshelper for job folder discovery (benchmarks/terminal_bench/prepare_leaderboard_submission.py).find_job_folderswalked directory trees with three copies of the same "iterate a directory, keep only the subdirectories" pattern: two nestedfor item in <dir>.iterdir(): if item.is_dir(): job_folders.append(item)loops (the directjobs/branch and the per-artifact branch) plus afor artifact_dir in artifacts_dir.iterdir(): if not artifact_dir.is_dir(): continueskip-guard at the top of the per-artifact scan. Extracted a module-level_child_dirs(path)helper returning[child for child in path.iterdir() if child.is_dir()]and rewrote all three sites in terms of it.iterdir()ordering and the resultingjob_foldersordering are unchanged; behavior-preserving, 9 lines removed.Drop the redundant
"exec"fallback duplication fornormalizeAgentId(workspaceModeAi.ts,WorkspaceModeAISync.tsx,ChatInput/index.tsx).normalizeAgentId(value, fallback)incommon/utils/agentIds.tsalready declaresfallback: string = WORKSPACE_DEFAULTS.agentId, andWORKSPACE_DEFAULTS.agentIdis"exec". Four call sites in the per-agent workspace AI settings paths nonetheless passed the bare literal"exec", re-hardcoding the centralized default that the signature supplies — exactly the kind of duplicated constant that goes stale if the default agent ever changes (every other "workspace default agent" call site either omits the argument or passesWORKSPACE_DEFAULTS.agentId). All four now omit the argument. With the literal gone,workspaceModeAi.ts's module-privatenormalizeAgentId(agentId)wrapper existed only to supply that fallback, so it and its aliasednormalizeAgentId as normalizeWorkspaceAgentIdimport were removed in favor of importingnormalizeAgentIddirectly. Behavior-preserving: the omitted argument resolves to the identical string.Name the digest truncation bounds in the timeline mapper (
timelineMapper.ts). 🤖 feat: add a durable per-workspace timeline #3755 (durable per-workspace timeline) addedtruncateDigest, which condenses a user prompt's text parts into a single-line timeline row title, with both of its bounds inlined as bare literals:normalized.length <= 120 ? normalized : `${normalized.slice(0, 117)}...`. The117silently encodes120 - "...".length, an invariant a reader can only confirm by counting the ellipsis, and the sibling helper in the same feature (truncateTimelineDigestincommon/orpc/schemas/timeline.ts) already spells the identical normalize-then-ellipsize pattern asTIMELINE_TEXT_MAX_LENGTH/TIMELINE_TEXT_MAX_LENGTH - 3. Introduced module-privateDIGEST_MAX_LENGTH = 120andDIGEST_ELLIPSIS = "..."and derived the slice asDIGEST_MAX_LENGTH - DIGEST_ELLIPSIS.length, so the "a truncated digest still totalsDIGEST_MAX_LENGTH" invariant is stated rather than implied, and the tighter-than-schema bound is explained in a comment. The two helpers were deliberately not merged: the mapper's 120-char row-title bound is intentionally tighter than the 600-charboundTimelineTextFieldssafety net applied later, so collapsing them would change what gets persisted.Dedupe the defensive
unknown-field reads in the timeline mapper (timelineMapper.ts). 🤖 feat: classify machine-authored turns on the workspace timeline #3756 (machine-authored turn classification) addedreadMonitorWakeProcesses, which pullsrecordsoffmuxMetadataand then adisplayNameoff each record. BecausemuxMetadatacrosses the oRPC boundary asany, both reads spelled out the same defensive guard inline —typeof x === "object" && x !== null ? (x as Record<string, unknown>)[field] : undefined— and the pre-existingreadMuxMetadataFieldin the same file carried a third copy of it as an early return, so one file held three hand-rolled versions of "index a field off a value that might not be an object". Extracted a module-privatereadObjectField(value, field): unknownand rewrote all three sites in terms of it, following the precedent already set bygetWorkflowResultFieldincommon/utils/workflowRunMessages.tsandreadPreviewTextintimelineService.ts. Behavior-preserving: all three guards admitted exactly the same shapes (non-null objects, arrays included, functions excluded bytypeof), and each caller still applies its own narrowing afterwards (typeof value === "string"for the metadata fields,Array.isArrayforrecords), so every input maps to the same result as before. 14 lines removed, 11 added; no new exports.Share the mobile-touch media query constant (
constants/layout.ts,App.tsx,WorkspaceMenuBar.tsx,WorkspaceShell.tsx,ChatInput/index.tsx,UserMessage.tsx). 🤖 feat: redesign workspace chrome (footer info bar, title header, creation hero, composer) #3753 (workspace chrome redesign) leaned further on Mux's mobile-affordance gate, and the string that defines it —(max-width: 768px) and (pointer: coarse)— was copied verbatim into sevenwindow.matchMedia(...)call sites across five renderer files (the sidebar width override, bothhandleOpenTerminalpopout branches, the menu bar'sisTouchMobileScreen,UserMessage'sisMobileTouch, and the composer'suseStateinitializer plus itschange-listener effect). Each copy was independently responsible for staying in sync with the matching@mediablock inglobals.css, which is the actual source of truth for the styles these branches mirror. Hoisted to an exportedMOBILE_TOUCH_MEDIA_QUERYinsrc/constants/layout.ts— directly aboveMOBILE_TOUCH_TARGET_PX, which already documents this same coarse-pointer environment — and rewrote all seven call sites to use it. Behavior-preserving: every call site passed a byte-identical literal (verified by an exact-match grep oversrc/), so eachmatchMediacall receives precisely the value it did before; the only other diff is Prettier rejoining four now-shorter expressions onto single lines. Four of the five consumers already imported from@/constants/layout, so this adds just one new import statement.Share the docked toast overlay placement class (
constants/layout.ts,ConnectionStatusToast.tsx,ChatInputToast.tsx,ChatInput/index.tsx). Three toast hosts each hard-coded the same absolute overlay box —pointer-events-none absolute right-[15px] bottom-full left-[15px] z-[1000] mb-2 [&>*]:pointer-events-auto— as two identically-named localwrapperClassNameconstants plus one inlineclassNameon the composer's toast stack.ConnectionStatusToast's own doc comment asserts that it "uses the same overlay placement as ChatInputToast", so the invariant was real but enforced only by copy-paste, and the composer renders both components withwrap={false}under a third copy of the box — so a drifting inset would misalign a toast depending on which host happened to wrap it. Hoisted toCHAT_DOCK_TOAST_OVERLAY_CLASSinsrc/constants/layout.ts, directly belowCHAT_DOCK_GUTTER_CLASSwhose15pxinset it mirrors. Behavior-preserving: the twowrapperClassNamesites now reference the identical string they previously defined locally, and the composer's stack composes it ascn(CHAT_DOCK_TOAST_OVERLAY_CLASS, "flex flex-col gap-2")— the same utility set with no conflicting utilities, sotailwind-mergeyields the same computed styles (only the class attribute's token order shifts). The value stays a literal string inlayout.tsbecause Tailwind scans source text, the same constraint already documented onCHAT_DOCK_GUTTER_CLASS.Share the primary mouse button guard (
browser/utils/events.ts,ChatPane.tsx,DiffRenderer.tsx). 🤖 fix: keep iPad composer clicks from selecting the whole transcript #3759's new composer-dockmousedownhandler gated on the bare magic numberevent.button !== 0— the same primary-button guard the diff review drag-select handler already spelled out inline. Named the check once asisPrimaryMouseButton(event)inbrowser/utils/events.ts, next to the existingisEventFromDialogPortal/stopKeyboardPropagationevent helpers, and pointed both call sites at it so each reads as intent instead of a DOM constant. The helper accepts both React synthetic and native mouse events.Name the ModelSelector row's selection/highlight state (
ModelSelector.tsx). 🤖 fix: align composer pickers and size local workers by memory #3760 reworked the model dropdown option row to sharecomposerPickerOptionClasswithAgentModePickerand to accent the selected row. In the process the row grew to recomputevalue === modelfour separate times — for the option class'sisSelected, foraria-selected, for theProviderIcon'stext-accent/text-mutedternary, and for the model-name span's accent — plusindex === highlightedIndextwice (data-highlightedand the option class'sisHighlighted). Hoisted both intoisSelected/isHighlightedlocals at the top of themapcallback, matching the naming the siblingAgentModePickeralready uses for the same two states, so the row's state is named once and the four accent/ARIA consumers cannot drift apart.Share the workspace footer pill class (
WorkspaceFooterBar.tsx). 🤖 feat: link the footer GitHub slug to the repository #3762 turned the footer's GitHubowner/reposlug into a link and — per its own PR description — styled it "to match the siblingLast promptpill", which in practice meant copying that pill's 13-utility Tailwind string verbatim into the new<a>. The two strings then differed only by the three<button>resets (cursor-pointer,border-0,bg-transparent), so any future restyle of one pill would silently drift from the other. Extracted the shared styling into a module-levelFOOTER_PILL_CLASS: the anchor consumes it directly, and the button composes it ascn(FOOTER_PILL_CLASS, "cursor-pointer border-0 bg-transparent").Share the safe inactive-animation pause install (
browser/utils/inactiveAnimations.ts,main.tsx,terminal-window.tsx). 🤖 perf: reduce idle dev CPU usage #3768 (reduce idle dev CPU usage) addedinstallInactiveAnimationPauseand wired it into both renderer entrypoints. Because the pause is a pure optimization that must not be able to take startup down with it — AGENTS.md's "startup-time initialization must never crash the app" rule — each entrypoint wrapped the call in its own five-linetry/catch, and the two blocks were byte-identical down to the comment (// Animation throttling is an optimization and must never block renderer startup.). Both also discard the disposer the installer returns, so the duplication was pure ceremony repeated per entrypoint rather than anything either window customized. ExtractedinstallInactiveAnimationPauseSafely()into the installer's own module, directly beneathinstallInactiveAnimationPause, so the swallow policy is stated once where the risk lives and any future entrypoint (or a third window) inherits it by calling one function. The doc comment records both the "never fail startup" rationale and the deliberate disposer drop, which was previously implicit in the call sites.Share the bash monitor wake message predicate (
utils/messages/messageUtils.ts,ChatPane.tsx,MessageRenderer.tsx). 🤖 fix: quiet monitor wake events in chat #3779 gave background monitor wakes their own quiet transcript presentation, which split one concept — "this persisted user turn is a machine-authored monitor event, not a human prompt" — across two files that each re-derived it inline.MessageRendererroutes onmessage.bashMonitorWake != nullto pickBashMonitorWakeMessageoverUserMessage;ChatPane'suserMessageNavigationByHistoryIdmemo independently filters onmessage.bashMonitorWake == nullso the prev/next prompt arrows skip wakes. The two tests are the same classification written twice with opposite polarity, and they have to stay in agreement: if only one is updated when the wake representation changes, the transcript renders a quiet event that the navigation arrows still count as a prompt (or vice versa), which is a silent UX bug rather than a type error. ExtractedisBashMonitorWakeMessage(message: DisplayedUserMessage)intomessageUtils, alongside the existingDisplayedMessagepredicates (shouldShowInterruptedBarrier,computeBashOutputGroupInfos), and pointed both call sites at it. Both files already imported frommessageUtils, so this adds no new module edge.Dedupe the monitor disposition branch in
terminate()(backgroundProcessManager.ts). 🤖 fix: cancel stale background monitor wakes #3776 (cancel stale background monitor wakes) gaveBackgroundProcessManager.terminate()a newoptions.monitorDispositionparameter and, to honour it, inlined the same five-lineif (shouldFlushMonitor) { this.stopMonitor(proc, true); } else { this.cancelMonitor(proc); }block at both of the method's two monitor-retirement points: the idempotent already-terminated shortcut and the live-kill path inside thetry. The two copies are byte-identical and must stay that way — they answer the same question ("does this caller still want a wake?") for the same process. Replaced both with aresolveMonitorForTermination(proc, shouldFlush)private helper placed next tostopMonitor/cancelMonitor, so the disposition rule lives in one place and the two exit paths cannot drift apart.Share the queued-message action button classes (
QueuedMessage.tsx). 🤖 fix: restore queued message text hierarchy #3781 restored the queued draft's text hierarchy by dropping theEditandSend nowlabels fromtext-xstotext-[11px]— and had to make that one-token edit twice, because both buttons inlined a byte-identicalflex h-7 items-center gap-1.5 rounded-md px-2.5 text-[11px] font-medium transition-colorsrun of geometry/typography utilities and differed only in their colour treatment (text-muted+ hover forEdit;bg-pending/10+ disabled states forSend now). Hoisted the shared half into aQUEUED_ACTION_BUTTON_CLASSNAMEconstant and composed each button's colours on top withcn(...), so the next typography tweak lands in one place instead of drifting between the two.parseSubagentReportFromMessagehelper for report-envelope history scans (subagentReportEnvelope.ts+ 4 call sites). Subagent report envelopes reach history as synthetic user messages, so every scanner that wants the parsed envelope must first reconstruct the message text. Four sites inlined the byte-identical projection —message.parts.filter((part): part is Extract<typeof part, { type: "text" }> => part.type === "text").map((part) => part.text).join("\n")followed byparseSubagentReportEnvelope(text). 🤖 fix: avoid duplicate subagent completion responses #3783 (avoid duplicate subagent completion responses) added the third and fourth copies inTaskService.findProgressRespondedTaskIdsandTaskService.hasAcceptedSubagentProgressReport, joining the pre-existing copies in TaskService's sibling synthetic-report discovery andAgentSession.isVisibleCompletedSubagentReportMessage. Extracted the projection intoparseSubagentReportFromMessage(message)insubagentReportEnvelope.ts, co-located with the string parser it wraps, and routed all four sites through it. TheMuxMessageimport isimport type, so the module stays runtime-dependency-free;parseSubagentReportEnveloperemains exported for the callers that already hold a text string (timelineMapper.ts, tests).This run
Considered
origin/mainfrom the previous checkpointa33604b92throughf14eade0c(HEAD), covering the single commit merged since the last run:fix: avoid duplicate subagent completion responses(🤖 fix: avoid duplicate subagent completion responses #3783,f14eade0c)Cleanup taken (see 🤖 Optimize bundle size and split artifacts per platform #44 above). 🤖 fix: avoid duplicate subagent completion responses #3783 touches four files but only two production files, and both new
TaskServicemethods open by rebuilding message text from parts before parsing the report envelope. That the same eight lines appear twice within one commit — and twice more elsewhere in the codebase — is exactly the duplication signal this PR targets. Because the helper's body is a verbatim move, the refactor cannot change parsing behavior.Considered and rejected: unifying the two new history scanners themselves.
findProgressRespondedTaskIdsneeds ordered iteration (it tracks whether an assistant turn followed eachin_progressreport), whilehasAcceptedSubagentProgressReportis an order-independentsome(...). They also differ in failure semantics — the former returns an empty set and the latter returnsfalseon a history read error. Merging them would mean threading a mode flag through genuinely different control flow, which is not behavior-preserving-by-construction.Left alone: the duplicated
getHistoryFromLatestBoundary+log.warn+ fail-open preamble shared by those two methods. The log messages and fallback values differ per caller, and the fail-open behavior is load-bearing per 🤖 fix: avoid duplicate subagent completion responses #3783's own risk note, so collapsing it would obscure the intent for three saved lines.Left alone:
timelineMapper.ts'sparseSubagentReportEnvelopecall. It already receives atextstring derived earlier in the function for other purposes, so it has nothing to dedupe.Checkpoint advanced to
f14eade0c.Validation
make static-check— ESLint, both TypeScript projects (tsgo --noEmitfortsconfig.jsonandtsconfig.main.json), Prettier, and generated-source freshness all pass. The run stops atfmt-shell-checkbecauseshfmtis not installed in this environment; that step is unreachable for this diff, which contains no shell files (only three.tsfiles).bun test src/node/services/taskService.test.ts— 328 pass, 0 fail. This is the suite 🤖 fix: avoid duplicate subagent completion responses #3783 extended and it directly exercises both refactoredTaskServicescanners.bun test src/node/services/agentSession.startupAutoRetry.test.ts— 30 pass, 0 fail. CoversisVisibleCompletedSubagentReportMessagevia the retry-gating path.bun test src/common/utils/subagentReportEnvelope.test.ts— 4 pass, 0 fail.Risks
None expected. The extraction is a literal move: the filter predicate, the
\njoin, and the delegation toparseSubagentReportEnvelopeare unchanged, and each call site's surrounding guards (role/synthetic/uiVisiblechecks, status comparisons, earlycontinues) were left in place. The one non-mechanical detail is the newimport type { MuxMessage }in a module that previously had no imports; it is type-only and therefore erased at compile time, sosubagentReportEnvelope.tskeeps its zero-runtime-dependency profile and stays safe for browser bundles.src/common/types/message.tsdoes not import the envelope module, so no cycle is introduced. Blast radius is subagent report detection in terminal-attention suppression, sibling report discovery, and startup retry gating.Auto-cleanup checkpoint: f14eade
Generated with
mux• Model:anthropic:claude-opus-5• Thinking:xhigh• Cost:$0.00