Skip to content

Commit db39664

Browse files
authored
🤖 refactor: usage, goal and timing services take the workspace FIFO from SessionLocks (#7379)
## Summary The session usage, goal and timing services take the workspace FIFO from `SessionLocks`. It is PR 7 of phase 4. Lock keys and order do not change: the Node layer gives today's `workspaceFileLocks`. ## Order - Plan order: PR 7 comes after PR 6, with PR 8 (#7354), and before PR 9. - Landing order: PR 7 lands on main as its own PR, after PR 9 (#7372), before PR 8 (#7354) and PR 5 (#7355) (recorded as a phase 4 plan note). - Why: PR 7 needs PR 9's `historyService.locks`, which has merged. It uses no code from PR 8 or PR 5. ## Implementation - `sessionUsageService.ts` (8 calls) and `workspaceGoalService.ts` (29 calls): `fileLocks` is a getter that returns `historyService.locks.workspaceFiles`. A memory or foreign-holder layer reaches these writes too. The getter reads at each use, not at construction, so tests that pass a partial `HistoryService` need no edit. - `sessionTimingService.ts` (4 calls): `new SessionTimingService(config, telemetry, locks = nodeSessionLocks)`. `desktop.ts` keeps its call. - `desktop.ts` and `core.ts` do not change. ## Validation All on the head (`c7cacdb5db`, this diff on main `4770fa63e0`): - `bun test` of `sessionUsageService*.test.ts`, `workspaceGoalService*.test.ts`, `sessionTimingService.test.ts` and `sessionLocks/`: 324 pass, 0 fail. The test files pass with no edit. The call record fixture is unchanged. - `make static-check`, `make lint-eslint`: pass. - `make test`: 27753 pass, 0 fail in the main run, and every other run passes. - Tripwire (`scripts/perf/loopTestBaseline.ts --runs 3 --base origin/main`): 0 trips. - `make crap-check`: PASS, 0 violations. `make mutate-check`: PASS, 0 mutants in scope. ## Risks - None for behavior: in the app, `historyService.locks.workspaceFiles` and the default are the same `workspaceFileLocks` map. --- _Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high` • Cost: `$122.03`_ <!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high costs=122.03 -->
1 parent 06fe1fb commit db39664

3 files changed

Lines changed: 28 additions & 7 deletions

File tree

‎src/node/services/sessionTimingService.ts‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import * as fs from "fs/promises";
44
import { EventEmitter } from "events";
55
import writeFileAtomic from "@/node/utils/writeFileAtomic";
66
import type { Config } from "@/node/config";
7-
import { workspaceFileLocks } from "@/node/utils/concurrency/workspaceFileLocks";
7+
import { nodeSessionLocks } from "@/node/services/sessionLocks/nodeSessionLocks";
8+
import type { SessionLocks } from "@/node/services/sessionLocks/sessionLocks";
89
import { normalizeToCanonical } from "@/common/utils/ai/models";
910
import type { AgentMode } from "@/common/types/mode";
1011
import {
@@ -163,7 +164,8 @@ function validateTiming(params: {
163164
export class SessionTimingService {
164165
private readonly config: Config;
165166
private readonly telemetryService: TelemetryService;
166-
private readonly fileLocks = workspaceFileLocks;
167+
/** The one workspace FIFO (phase 4: from SessionLocks; desktop.ts keeps the Node default). */
168+
private readonly fileLocks: SessionLocks["workspaceFiles"];
167169

168170
private readonly activeStreams = new Map<string, ActiveStreamState>();
169171
private readonly timingFileCache = new Map<string, SessionTimingFile>();
@@ -181,9 +183,14 @@ export class SessionTimingService {
181183
{ lastEmitTimeMs: number; timer?: ReturnType<typeof setTimeout> }
182184
>();
183185

184-
constructor(config: Config, telemetryService: TelemetryService) {
186+
constructor(
187+
config: Config,
188+
telemetryService: TelemetryService,
189+
locks: SessionLocks = nodeSessionLocks
190+
) {
185191
this.config = config;
186192
this.telemetryService = telemetryService;
193+
this.fileLocks = locks.workspaceFiles;
187194
}
188195

189196
addSubscriber(workspaceId: string): void {

‎src/node/services/sessionUsageService.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { Config } from "@/node/config";
88
import type { HistoryService } from "./historyService";
99
import { isWorkspaceRemovalTombstoned } from "@/node/services/workspaceRemoval";
1010
import { withTargetMutationLock } from "@/node/services/refinement/targetMutationLocks";
11-
import { workspaceFileLocks } from "@/node/utils/concurrency/workspaceFileLocks";
11+
import type { SessionLocks } from "@/node/services/sessionLocks/sessionLocks";
1212
import type { ChatUsageDisplay } from "@/common/utils/tokens/usageAggregator";
1313
import { sumUsageHistory } from "@/common/utils/tokens/usageAggregator";
1414
import { createDisplayUsage } from "@/common/utils/tokens/displayUsage";
@@ -227,7 +227,14 @@ function pruneSettledInFlightKeys(
227227
*/
228228
export class SessionUsageService {
229229
private readonly SESSION_USAGE_FILE = "session-usage.json";
230-
private readonly fileLocks = workspaceFileLocks;
230+
/**
231+
* The one workspace FIFO, from the HistoryService's SessionLocks (phase 4), so a
232+
* memory or foreign-holder layer reaches these writes too. Read at each use, not
233+
* at construction: tests that pass a partial HistoryService keep working.
234+
*/
235+
private get fileLocks(): SessionLocks["workspaceFiles"] {
236+
return this.historyService.locks.workspaceFiles;
237+
}
231238
private readonly config: Config;
232239
private readonly historyService: HistoryService;
233240
private readonly getProvidersConfig: () => ProvidersConfigMap | null;

‎src/node/services/workspaceGoalService.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import type { SendMessageError } from "@/common/types/errors";
4444
import { ProvidersConfigStore, type Config } from "@/node/config";
4545
import type { HistoryService } from "@/node/services/historyService";
4646
import type { ExtensionMetadataService } from "@/node/services/ExtensionMetadataService";
47-
import { workspaceFileLocks } from "@/node/utils/concurrency/workspaceFileLocks";
47+
import type { SessionLocks } from "@/node/services/sessionLocks/sessionLocks";
4848
import {
4949
DEFAULT_GOAL_CONTINUATION_COOLDOWN_MS,
5050
GOAL_STREAM_ERROR_RESUME_MAX_ATTEMPTS,
@@ -609,7 +609,14 @@ export interface WorkspaceGoalServiceOptions {
609609
}
610610

611611
export class WorkspaceGoalService {
612-
private readonly fileLocks = workspaceFileLocks;
612+
/**
613+
* The one workspace FIFO, from the HistoryService's SessionLocks (phase 4), so a
614+
* memory or foreign-holder layer reaches these writes too. Read at each use, not
615+
* at construction: tests that pass a partial HistoryService keep working.
616+
*/
617+
private get fileLocks(): SessionLocks["workspaceFiles"] {
618+
return this.historyService.locks.workspaceFiles;
619+
}
613620
private readonly continuationCooldownMs: number;
614621
private readonly allowUserOriginBudgetWrapup: boolean;
615622
private readonly suppressKickoffContinuation: boolean;

0 commit comments

Comments
 (0)