From bd3763e439fa4be7acee1663381aec10bd6567ee Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:35:37 +0200 Subject: [PATCH] =?UTF-8?q?AgentHost=20-=20=F0=9F=92=84=20extract=20change?= =?UTF-8?q?set=20service=20interface=20(#321370)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * AgentHost - 💄 extract changeset service interface * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../common/agentHostChangesetService.ts | 241 ++++++++++++++++++ .../node/agentHostChangesetCoordinator.ts | 11 +- ...gentHostChangesetFileMonitorCoordinator.ts | 2 +- .../node/agentHostChangesetService.ts | 237 +---------------- .../node/agentHostCommitOperationHandler.ts | 2 +- .../platform/agentHost/node/agentService.ts | 3 +- .../agentHost/node/agentSideEffects.ts | 2 +- .../agentHostChangesetCoordinator.test.ts | 2 +- .../agentHostCommitOperationHandler.test.ts | 2 +- .../test/node/agentHostTurnTelemetry.test.ts | 2 +- .../test/node/agentSideEffects.test.ts | 2 +- 11 files changed, 254 insertions(+), 252 deletions(-) create mode 100644 src/vs/platform/agentHost/common/agentHostChangesetService.ts diff --git a/src/vs/platform/agentHost/common/agentHostChangesetService.ts b/src/vs/platform/agentHost/common/agentHostChangesetService.ts new file mode 100644 index 00000000000..c7c41cee9b2 --- /dev/null +++ b/src/vs/platform/agentHost/common/agentHostChangesetService.ts @@ -0,0 +1,241 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from '../../instantiation/common/instantiation.js'; +import type { ChangesSummary } from './state/protocol/state.js'; +import type { ISessionFileDiff, URI as ProtocolURI } from './state/sessionState.js'; + +/** Metadata key under which the branch changeset's diff list is persisted. */ +export const META_CHANGESET_BRANCH = 'agentHost.changeset.branch'; + +/** Metadata key under which the session-wide changeset's diff list is persisted. */ +export const META_CHANGESET_SESSION = 'agentHost.changeset.session'; + +/** + * Legacy metadata key used by older builds to persist the session-wide + * changeset's diff list. Read-only fallback for {@link META_CHANGESET_SESSION}. + */ +export const META_LEGACY_DIFFS = 'diffs'; + +/** + * Metadata key under which the session's changes is persisted. + */ +export const META_CHANGES_SUMMARY = 'agentHost.changes'; + +/** The two static changeset kinds we publish by default. */ +export type StaticChangesetKind = 'branch' | 'session'; + +/** + * Raw metadata values for the persisted changeset blobs, batch-read + * by the caller (`AgentService.listSessions` / `AgentService.restoreSession`). + * The caller owns the database read so multiple metadata keys can be + * fetched in a single round-trip; the service owns parsing, applying, + * and `seedIfEmpty` gating. + */ +export interface IPersistedChangesetMetadata { + readonly branchRaw?: string; + readonly sessionRaw?: string; + readonly legacyRaw?: string; +} + +/** + * The parsed diffs returned from {@link IAgentHostChangesetService.restorePersistedStaticChangesets}, + * suitable for passing into {@link computeChangesSummaryFromPersistedDiffs} + * when the caller needs to synthesise a `summary.changes` aggregate for + * the session-list overlay. + */ +export interface IRestoredChangesetDiffs { + readonly branch?: readonly ISessionFileDiff[]; + readonly session?: readonly ISessionFileDiff[]; +} + +export const IAgentHostChangesetService = createDecorator('agentHostChangesetService'); + +/** + * Owns the lifecycle of static and per-turn changesets for the agent host: + * registers the `/changeset/{uncommitted,session,turn/}` URIs + * on the state manager, runs git-driven and edit-tracker-driven diff + * computations, debounces mid-turn recomputes, publishes file lists + * (`changeset/fileSet` / `changeset/fileRemoved`) and aggregate counts + * (`session/summaryChanged`), and persists results to the session DB so + * restarts can rehydrate without recomputing. + * + * Created locally by `AgentService` (not via `registerSingleton`) and + * added to the local `ServiceCollection` so `AgentSideEffects` can + * resolve it via `@IAgentHostChangesetService`. `AgentHostStateManager` + * is passed as a plain ctor argument (it has no decorator today); the + * git / log / session-data services are DI-injected. + */ +export interface IAgentHostChangesetService { + readonly _serviceBrand: undefined; + + /** + * Registers the two static changeset URIs (`uncommitted`, `session`) + * on the state manager so client subscriptions resolve to a + * `status: computing` snapshot before the first compute pass + * completes. The catalogue itself (`state.changesets`) is seeded + * upstream by `_buildInitialSummary` / `restoreSession` — this only + * deals with the state-manager-side per-changeset entries. + * + * Idempotent; safe to call on every create and restore path. + */ + registerStaticChangesets(session: ProtocolURI): void; + + /** + * Re-seed a static changeset (`uncommitted` or `session`) from a + * previously persisted file list (e.g. read out of the session DB on + * restore / listSessions). Idempotently registers the changeset URI + * on the state manager, fans the persisted files out as + * `changeset/fileSet` actions, and transitions the status to `Ready`. + */ + restoreStaticChangeset(session: ProtocolURI, kind: StaticChangesetKind, diffs: readonly ISessionFileDiff[]): void; + + /** + * Parses the persisted changeset metadata blobs (`uncommitted`, + * `session`, and the legacy `diffs` fallback for `session`) without + * mutating live state. Intended for list overlays that only need + * aggregate catalogue counts and should not pin full changeset state in + * memory. + */ + parsePersistedStaticChangesets(sessionUri: ProtocolURI, metadata: IPersistedChangesetMetadata): IRestoredChangesetDiffs; + + /** + * Applies parsed persisted changeset diffs to live state via + * {@link restoreStaticChangeset}. This is the side-effectful half of + * persisted restore and should only be used on real restore/subscribe + * paths that need a subscribable changeset snapshot. + * + * Honours `seedIfEmpty`: when a live changeset state already has files + * for the same kind, persisted diffs are NOT applied (they would + * otherwise overwrite the live state). + */ + applyPersistedStaticChangesets(sessionUri: ProtocolURI, diffs: IRestoredChangesetDiffs): void; + + /** + * Compatibility wrapper that parses persisted changeset metadata and then + * applies it to live state. New list-overlay callers should prefer + * {@link parsePersistedStaticChangesets}; restore/subscribe callers can + * use this method when they intentionally want both parse and seed. + * + * The `AgentService` orchestration boundary batches the metadata read + * (custom title + read / archive flags + config values + these three + * blobs) in a single database round-trip, then hands the raw values + * here; the service does not open the database itself for this method. + */ + restorePersistedStaticChangesets(sessionUri: ProtocolURI, metadata: IPersistedChangesetMetadata): IRestoredChangesetDiffs; + + /** + * Fire-and-forget persistence of the `summary.changes` aggregate to the + * session DB under {@link META_CHANGES_SUMMARY}. Used both by the + * happy-path turn-complete write and by the {@link ChangesetSessionCoordinator} + * one-shot migration that reads the old `META_CHANGESET_SESSION` / + * `META_LEGACY_DIFFS` blobs and projects them into the new key on + * sessions written by older builds. Errors are logged, not thrown. + */ + persistChangesSummary(sessionUri: ProtocolURI, summary: ChangesSummary): void; + + /** + * Returns true when the static changeset identified by `changesetUri` is + * currently being recomputed. Used by cache eviction to avoid dropping a + * slot while its producer is mid-flight. + */ + isStaticChangesetComputeActive(changesetUri: ProtocolURI): boolean; + + /** + * Lazy refresh of the branch changeset, kicked off when a client + * first subscribes to `/changeset/branch`. + */ + refreshBranchChangeset(session: ProtocolURI): void; + + /** + * Lazy refresh of the session changeset, kicked off when a + * client first subscribes to `/changeset/session` or the + * session URI itself (e.g. Agents Window observing the session). The + * recompute keeps the catalogue chip fresh across session opens even + * when no turn has run since process start. + */ + refreshSessionChangeset(session: ProtocolURI): void; + + /** + * Computes and publishes the per-turn changeset for `turnId` on `session`. + * Per-turn changesets are not persisted. + */ + computeTurnChangeset(session: ProtocolURI, turnId: string): Promise; + + /** + * Computes and publishes the compare-turns changeset between + * `originalTurnId` (the "from" endpoint) and `modifiedTurnId` (the + * "to" endpoint) on `session`. Diff direction is + * `originalTurnId.current → modifiedTurnId.current` — endpoint-to- + * endpoint, so it captures what differs between the two turn states. + * + * Implemented via git: both refs come from the per-turn checkpoint + * captured at the end of each turn. When either checkpoint is missing + * (non-git session, baseline never captured, capture failure), the + * changeset transitions to `status: Error` instead of rejecting; no + * SDK edit-tracker fallback exists. + * + * Compare-turns changesets are not persisted and are computed once + * on subscribe (no live recompute). + */ + computeCompareTurnsChangeset(session: ProtocolURI, originalTurnId: string, modifiedTurnId: string): Promise; + + /** + * Computes and publishes the uncommitted changeset for `session` + * directly via git (`git status` against HEAD). The uncommitted slot + * has no SDK edit-tracker fallback — the aggregator answers a different + * question than `git status` and would silently rebrand SDK-tracked + * edits as uncommitted git changes. When the session has no working + * directory, the working directory isn't a git work tree, or the git + * command fails, the changeset transitions to `status: Error`. + * + * Uncommitted changesets are not persisted; callers schedule recomputes + * (e.g. on turn complete, post-commit, working-tree watcher event) + * directly via this method. + */ + computeUncommittedChangeset(session: ProtocolURI): Promise; + + /** + * Hook called by `AgentSideEffects` after a tool call that produced + * file edits completes. Schedules a debounced session-changeset recompute. + */ + onToolCallEditsApplied(session: ProtocolURI, turnId: string): void; + + /** + * Hook called by `AgentSideEffects` when a turn completes. Cancels any + * pending mid-turn debounce, then schedules a final session + uncommitted + * recompute. Ordering matters — see implementation. + */ + onTurnComplete(session: ProtocolURI, turnId: string | undefined): void; + + /** + * Hook called by `AgentSideEffects` when a session is truncated (turns + * removed). Recomputes the session changeset from scratch (no + * `changedTurnId`, no incremental reuse). + */ + onSessionTruncated(session: ProtocolURI): void; + + /** + * Installs a predicate the service consults before scheduling a + * per-turn changeset recompute. Owned by {@link ChangesetSessionCoordinator}, + * which tracks per-turn subscribers via `onFirstSubscriber` / + * `onLastSubscriber`. Called exactly once at coordinator construction. + */ + setTurnSubscriberProbe(probe: (session: ProtocolURI, turnId: string) => boolean): void; + + /** + * Installs a predicate the service consults before scheduling an + * uncommitted-changeset recompute on turn complete. Owned by + * {@link ChangesetSessionCoordinator}, which tracks per-session + * uncommitted subscribers via `onFirstSubscriber` / `onLastSubscriber`. + * Called exactly once at coordinator construction. + * + * Uncommitted computes hit git on every recompute and produce no + * catalogue-chip aggregate, so the cost of recomputing for an + * unobserved session has no upside; the next subscriber will get a + * fresh snapshot from the coordinator's `_triggerUncommittedRefresh`. + */ + setUncommittedSubscriberProbe(probe: (session: ProtocolURI) => boolean): void; +} diff --git a/src/vs/platform/agentHost/node/agentHostChangesetCoordinator.ts b/src/vs/platform/agentHost/node/agentHostChangesetCoordinator.ts index b1a59e8c21f..d0d6bc8dceb 100644 --- a/src/vs/platform/agentHost/node/agentHostChangesetCoordinator.ts +++ b/src/vs/platform/agentHost/node/agentHostChangesetCoordinator.ts @@ -18,16 +18,9 @@ import { IAgentHostFileMonitorService } from './agentHostFileMonitorService.js'; import { IAgentHostGitService } from './agentHostGitService.js'; import { AgentHostStateManager } from './agentHostStateManager.js'; import { ILogService } from '../../log/common/log.js'; -import { - computeChangesSummaryFromLiveState, - computeChangesSummaryFromPersistedDiffs, - IAgentHostChangesetService, - META_CHANGES_SUMMARY, - META_CHANGESET_BRANCH, - META_CHANGESET_SESSION, - META_LEGACY_DIFFS, -} from './agentHostChangesetService.js'; +import { computeChangesSummaryFromLiveState, computeChangesSummaryFromPersistedDiffs } from './agentHostChangesetService.js'; import { ChangesSummary } from '../common/state/protocol/state.js'; +import { IAgentHostChangesetService, META_CHANGES_SUMMARY, META_CHANGESET_BRANCH, META_CHANGESET_SESSION, META_LEGACY_DIFFS } from '../common/agentHostChangesetService.js'; /** * Raw metadata blob values for the session DB, batch-read by the caller. diff --git a/src/vs/platform/agentHost/node/agentHostChangesetFileMonitorCoordinator.ts b/src/vs/platform/agentHost/node/agentHostChangesetFileMonitorCoordinator.ts index 7a7c3216dc5..b9995232e1b 100644 --- a/src/vs/platform/agentHost/node/agentHostChangesetFileMonitorCoordinator.ts +++ b/src/vs/platform/agentHost/node/agentHostChangesetFileMonitorCoordinator.ts @@ -9,7 +9,7 @@ import { URI } from '../../../base/common/uri.js'; import { buildBranchChangesetUri, buildSessionChangesetUri, buildUncommittedChangesetUri } from '../common/changesetUri.js'; import { parseSubagentSessionUri } from '../common/state/sessionState.js'; import { IAgentConfigurationService } from './agentConfigurationService.js'; -import { IAgentHostChangesetService } from './agentHostChangesetService.js'; +import { IAgentHostChangesetService } from '../common/agentHostChangesetService.js'; import { DEFAULT_AGENT_HOST_WATCH_EXCLUDES, IAgentHostFileMonitorService } from './agentHostFileMonitorService.js'; import { IAgentHostGitService } from './agentHostGitService.js'; import { AgentHostStateManager } from './agentHostStateManager.js'; diff --git a/src/vs/platform/agentHost/node/agentHostChangesetService.ts b/src/vs/platform/agentHost/node/agentHostChangesetService.ts index eacb0992df6..a5b0dfcb071 100644 --- a/src/vs/platform/agentHost/node/agentHostChangesetService.ts +++ b/src/vs/platform/agentHost/node/agentHostChangesetService.ts @@ -7,7 +7,6 @@ import { disposableTimeout, SequencerByKey } from '../../../base/common/async.js import { toErrorMessage } from '../../../base/common/errorMessage.js'; import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js'; import { URI } from '../../../base/common/uri.js'; -import { createDecorator } from '../../instantiation/common/instantiation.js'; import { ILogService } from '../../log/common/log.js'; import { buildBranchChangesetUri, @@ -33,26 +32,7 @@ import { IAgentHostCheckpointService } from '../common/agentHostCheckpointServic import { NodeWorkerDiffComputeService } from './diffComputeService.js'; import { computeSessionDiffs, computeTurnDiffs, type IIncrementalDiffOptions } from './sessionDiffAggregator.js'; import { META_CHECKPOINT_WORKING_DIR } from './agentHostCheckpointService.js'; - -/** Metadata key under which the branch changeset's diff list is persisted. */ -export const META_CHANGESET_BRANCH = 'agentHost.changeset.branch'; - -/** Metadata key under which the session-wide changeset's diff list is persisted. */ -export const META_CHANGESET_SESSION = 'agentHost.changeset.session'; - -/** - * Legacy metadata key used by older builds to persist the session-wide - * changeset's diff list. Read-only fallback for {@link META_CHANGESET_SESSION}. - */ -export const META_LEGACY_DIFFS = 'diffs'; - -/** - * Metadata key under which the session's changes is persisted. - */ -export const META_CHANGES_SUMMARY = 'agentHost.changes'; - -/** The two static changeset kinds we publish by default. */ -export type StaticChangesetKind = 'branch' | 'session'; +import { IAgentHostChangesetService, IPersistedChangesetMetadata, IRestoredChangesetDiffs, META_CHANGES_SUMMARY, META_CHANGESET_BRANCH, META_CHANGESET_SESSION, META_LEGACY_DIFFS, StaticChangesetKind } from '../common/agentHostChangesetService.js'; function staticChangesetUri(session: ProtocolURI, kind: StaticChangesetKind): ProtocolURI { return kind === 'branch' @@ -127,7 +107,7 @@ export function computeChangesSummaryFromPersistedDiffs( * warning that names `sessionUri` and `kind` so operators can correlate the * failure with a specific session/changeset slot. Never throws. */ -export function tryParsePersistedDiffs(raw: string | undefined, sessionUri: string, kind: string, log: ILogService): ISessionFileDiff[] | undefined { +function tryParsePersistedDiffs(raw: string | undefined, sessionUri: string, kind: string, log: ILogService): ISessionFileDiff[] | undefined { if (!raw) { return undefined; } @@ -139,219 +119,6 @@ export function tryParsePersistedDiffs(raw: string | undefined, sessionUri: stri } } -/** - * Raw metadata values for the persisted changeset blobs, batch-read - * by the caller (`AgentService.listSessions` / `AgentService.restoreSession`). - * The caller owns the database read so multiple metadata keys can be - * fetched in a single round-trip; the service owns parsing, applying, - * and `seedIfEmpty` gating. - */ -export interface IPersistedChangesetMetadata { - readonly branchRaw?: string; - readonly sessionRaw?: string; - readonly legacyRaw?: string; -} - -/** - * The parsed diffs returned from {@link IAgentHostChangesetService.restorePersistedStaticChangesets}, - * suitable for passing into {@link computeChangesSummaryFromPersistedDiffs} - * when the caller needs to synthesise a `summary.changes` aggregate for - * the session-list overlay. - */ -export interface IRestoredChangesetDiffs { - readonly branch?: readonly ISessionFileDiff[]; - readonly session?: readonly ISessionFileDiff[]; -} - -export const IAgentHostChangesetService = createDecorator('agentHostChangesetService'); - -/** - * Owns the lifecycle of static and per-turn changesets for the agent host: - * registers the `/changeset/{uncommitted,session,turn/}` URIs - * on the state manager, runs git-driven and edit-tracker-driven diff - * computations, debounces mid-turn recomputes, publishes file lists - * (`changeset/fileSet` / `changeset/fileRemoved`) and aggregate counts - * (`session/summaryChanged`), and persists results to the session DB so - * restarts can rehydrate without recomputing. - * - * Created locally by `AgentService` (not via `registerSingleton`) and - * added to the local `ServiceCollection` so `AgentSideEffects` can - * resolve it via `@IAgentHostChangesetService`. `AgentHostStateManager` - * is passed as a plain ctor argument (it has no decorator today); the - * git / log / session-data services are DI-injected. - */ -export interface IAgentHostChangesetService { - readonly _serviceBrand: undefined; - - /** - * Registers the two static changeset URIs (`uncommitted`, `session`) - * on the state manager so client subscriptions resolve to a - * `status: computing` snapshot before the first compute pass - * completes. The catalogue itself (`state.changesets`) is seeded - * upstream by `_buildInitialSummary` / `restoreSession` — this only - * deals with the state-manager-side per-changeset entries. - * - * Idempotent; safe to call on every create and restore path. - */ - registerStaticChangesets(session: ProtocolURI): void; - - /** - * Re-seed a static changeset (`uncommitted` or `session`) from a - * previously persisted file list (e.g. read out of the session DB on - * restore / listSessions). Idempotently registers the changeset URI - * on the state manager, fans the persisted files out as - * `changeset/fileSet` actions, and transitions the status to `Ready`. - */ - restoreStaticChangeset(session: ProtocolURI, kind: StaticChangesetKind, diffs: readonly ISessionFileDiff[]): void; - - /** - * Parses the persisted changeset metadata blobs (`uncommitted`, - * `session`, and the legacy `diffs` fallback for `session`) without - * mutating live state. Intended for list overlays that only need - * aggregate catalogue counts and should not pin full changeset state in - * memory. - */ - parsePersistedStaticChangesets(sessionUri: ProtocolURI, metadata: IPersistedChangesetMetadata): IRestoredChangesetDiffs; - - /** - * Applies parsed persisted changeset diffs to live state via - * {@link restoreStaticChangeset}. This is the side-effectful half of - * persisted restore and should only be used on real restore/subscribe - * paths that need a subscribable changeset snapshot. - * - * Honours `seedIfEmpty`: when a live changeset state already has files - * for the same kind, persisted diffs are NOT applied (they would - * otherwise overwrite the live state). - */ - applyPersistedStaticChangesets(sessionUri: ProtocolURI, diffs: IRestoredChangesetDiffs): void; - - /** - * Compatibility wrapper that parses persisted changeset metadata and then - * applies it to live state. New list-overlay callers should prefer - * {@link parsePersistedStaticChangesets}; restore/subscribe callers can - * use this method when they intentionally want both parse and seed. - * - * The `AgentService` orchestration boundary batches the metadata read - * (custom title + read / archive flags + config values + these three - * blobs) in a single database round-trip, then hands the raw values - * here; the service does not open the database itself for this method. - */ - restorePersistedStaticChangesets(sessionUri: ProtocolURI, metadata: IPersistedChangesetMetadata): IRestoredChangesetDiffs; - - /** - * Fire-and-forget persistence of the `summary.changes` aggregate to the - * session DB under {@link META_CHANGES_SUMMARY}. Used both by the - * happy-path turn-complete write and by the {@link ChangesetSessionCoordinator} - * one-shot migration that reads the old `META_CHANGESET_SESSION` / - * `META_LEGACY_DIFFS` blobs and projects them into the new key on - * sessions written by older builds. Errors are logged, not thrown. - */ - persistChangesSummary(sessionUri: ProtocolURI, summary: ChangesSummary): void; - - /** - * Returns true when the static changeset identified by `changesetUri` is - * currently being recomputed. Used by cache eviction to avoid dropping a - * slot while its producer is mid-flight. - */ - isStaticChangesetComputeActive(changesetUri: ProtocolURI): boolean; - - /** - * Lazy refresh of the branch changeset, kicked off when a client - * first subscribes to `/changeset/branch`. - */ - refreshBranchChangeset(session: ProtocolURI): void; - - /** - * Lazy refresh of the session changeset, kicked off when a - * client first subscribes to `/changeset/session` or the - * session URI itself (e.g. Agents Window observing the session). The - * recompute keeps the catalogue chip fresh across session opens even - * when no turn has run since process start. - */ - refreshSessionChangeset(session: ProtocolURI): void; - - /** - * Computes and publishes the per-turn changeset for `turnId` on `session`. - * Per-turn changesets are not persisted. - */ - computeTurnChangeset(session: ProtocolURI, turnId: string): Promise; - - /** - * Computes and publishes the compare-turns changeset between - * `originalTurnId` (the "from" endpoint) and `modifiedTurnId` (the - * "to" endpoint) on `session`. Diff direction is - * `originalTurnId.current → modifiedTurnId.current` — endpoint-to- - * endpoint, so it captures what differs between the two turn states. - * - * Implemented via git: both refs come from the per-turn checkpoint - * captured at the end of each turn. When either checkpoint is missing - * (non-git session, baseline never captured, capture failure), the - * changeset transitions to `status: Error` instead of rejecting; no - * SDK edit-tracker fallback exists. - * - * Compare-turns changesets are not persisted and are computed once - * on subscribe (no live recompute). - */ - computeCompareTurnsChangeset(session: ProtocolURI, originalTurnId: string, modifiedTurnId: string): Promise; - - /** - * Computes and publishes the uncommitted changeset for `session` - * directly via git (`git status` against HEAD). The uncommitted slot - * has no SDK edit-tracker fallback — the aggregator answers a different - * question than `git status` and would silently rebrand SDK-tracked - * edits as uncommitted git changes. When the session has no working - * directory, the working directory isn't a git work tree, or the git - * command fails, the changeset transitions to `status: Error`. - * - * Uncommitted changesets are not persisted; callers schedule recomputes - * (e.g. on turn complete, post-commit, working-tree watcher event) - * directly via this method. - */ - computeUncommittedChangeset(session: ProtocolURI): Promise; - - /** - * Hook called by `AgentSideEffects` after a tool call that produced - * file edits completes. Schedules a debounced session-changeset recompute. - */ - onToolCallEditsApplied(session: ProtocolURI, turnId: string): void; - - /** - * Hook called by `AgentSideEffects` when a turn completes. Cancels any - * pending mid-turn debounce, then schedules a final session + uncommitted - * recompute. Ordering matters — see implementation. - */ - onTurnComplete(session: ProtocolURI, turnId: string | undefined): void; - - /** - * Hook called by `AgentSideEffects` when a session is truncated (turns - * removed). Recomputes the session changeset from scratch (no - * `changedTurnId`, no incremental reuse). - */ - onSessionTruncated(session: ProtocolURI): void; - - /** - * Installs a predicate the service consults before scheduling a - * per-turn changeset recompute. Owned by {@link ChangesetSessionCoordinator}, - * which tracks per-turn subscribers via `onFirstSubscriber` / - * `onLastSubscriber`. Called exactly once at coordinator construction. - */ - setTurnSubscriberProbe(probe: (session: ProtocolURI, turnId: string) => boolean): void; - - /** - * Installs a predicate the service consults before scheduling an - * uncommitted-changeset recompute on turn complete. Owned by - * {@link ChangesetSessionCoordinator}, which tracks per-session - * uncommitted subscribers via `onFirstSubscriber` / `onLastSubscriber`. - * Called exactly once at coordinator construction. - * - * Uncommitted computes hit git on every recompute and produce no - * catalogue-chip aggregate, so the cost of recomputing for an - * unobserved session has no upside; the next subscriber will get a - * fresh snapshot from the coordinator's `_triggerUncommittedRefresh`. - */ - setUncommittedSubscriberProbe(probe: (session: ProtocolURI) => boolean): void; -} - export class AgentHostChangesetService extends Disposable implements IAgentHostChangesetService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/platform/agentHost/node/agentHostCommitOperationHandler.ts b/src/vs/platform/agentHost/node/agentHostCommitOperationHandler.ts index b5a35b3e4c5..cdc6a2ed6b5 100644 --- a/src/vs/platform/agentHost/node/agentHostCommitOperationHandler.ts +++ b/src/vs/platform/agentHost/node/agentHostCommitOperationHandler.ts @@ -15,7 +15,7 @@ import { AHP_AUTH_REQUIRED, AHP_SESSION_NOT_FOUND, JsonRpcErrorCodes, ProtocolEr import { readSessionGitState, type ISessionFileDiff, type SessionState } from '../common/state/sessionState.js'; import { ILogService } from '../../log/common/log.js'; import { IAgentHostGitService } from './agentHostGitService.js'; -import { IAgentHostChangesetService } from './agentHostChangesetService.js'; +import { IAgentHostChangesetService } from '../common/agentHostChangesetService.js'; import { CopilotApiError, ICopilotApiService } from './shared/copilotApiService.js'; const MAX_CHANGE_SUMMARY_PROMPT_CHARS = 20_000; diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index f62d07d358c..c7a8074b88f 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -40,7 +40,7 @@ import { IAgentHostGitService } from './agentHostGitService.js'; import { AgentSideEffects } from './agentSideEffects.js'; import { AgentServerToolHost } from './shared/agentServerToolHost.js'; import { feedbackServerToolGroup } from './shared/agentFeedbackServerTools.js'; -import { AgentHostChangesetService, IAgentHostChangesetService, META_CHANGES_SUMMARY } from './agentHostChangesetService.js'; +import { AgentHostChangesetService } from './agentHostChangesetService.js'; import { AgentHostFileMonitorService, IAgentHostFileMonitorService } from './agentHostFileMonitorService.js'; import { IAgentHostCheckpointService, NULL_CHECKPOINT_SERVICE } from '../common/agentHostCheckpointService.js'; import { CHANGESET_DB_METADATA_KEYS, ChangesetSessionCoordinator } from './agentHostChangesetCoordinator.js'; @@ -60,6 +60,7 @@ import { NullTelemetryService } from '../../telemetry/common/telemetryUtils.js'; import { AgentHostAuthenticationService } from './agentHostAuthenticationService.js'; import { updateAgentHostTelemetryLevelFromConfig } from './agentHostTelemetryService.js'; import { AgentHostOctoKitService, IAgentHostOctoKitService } from './shared/agentHostOctoKitService.js'; +import { IAgentHostChangesetService, META_CHANGES_SUMMARY } from '../common/agentHostChangesetService.js'; /** * Grace period before an empty, unsubscribed session is garbage-collected diff --git a/src/vs/platform/agentHost/node/agentSideEffects.ts b/src/vs/platform/agentHost/node/agentSideEffects.ts index aaabd51d0e4..06fde1a4313 100644 --- a/src/vs/platform/agentHost/node/agentSideEffects.ts +++ b/src/vs/platform/agentHost/node/agentSideEffects.ts @@ -13,7 +13,7 @@ import { localize } from '../../../nls.js'; import { IInstantiationService } from '../../instantiation/common/instantiation.js'; import { ILogService } from '../../log/common/log.js'; import { AgentSignal, IAgent, IAgentToolPendingConfirmationSignal } from '../common/agentService.js'; -import { IAgentHostChangesetService } from './agentHostChangesetService.js'; +import { IAgentHostChangesetService } from '../common/agentHostChangesetService.js'; import { IAgentHostCheckpointService } from '../common/agentHostCheckpointService.js'; import { ISessionDataService } from '../common/sessionDataService.js'; diff --git a/src/vs/platform/agentHost/test/node/agentHostChangesetCoordinator.test.ts b/src/vs/platform/agentHost/test/node/agentHostChangesetCoordinator.test.ts index 2d1ca432f2c..77488e2e27b 100644 --- a/src/vs/platform/agentHost/test/node/agentHostChangesetCoordinator.test.ts +++ b/src/vs/platform/agentHost/test/node/agentHostChangesetCoordinator.test.ts @@ -15,7 +15,7 @@ import { ActionType } from '../../common/state/sessionActions.js'; import { buildSubagentSessionUri, SessionStatus, type ISessionFileDiff } from '../../common/state/sessionState.js'; import { AgentConfigurationService } from '../../node/agentConfigurationService.js'; import { ChangesetSessionCoordinator, IChangesetSessionMetadata } from '../../node/agentHostChangesetCoordinator.js'; -import { IAgentHostChangesetService, IPersistedChangesetMetadata, IRestoredChangesetDiffs, StaticChangesetKind } from '../../node/agentHostChangesetService.js'; +import { IAgentHostChangesetService, IPersistedChangesetMetadata, IRestoredChangesetDiffs, StaticChangesetKind } from '../../common/agentHostChangesetService.js'; import { IAgentHostFileMonitorOptions, IAgentHostFileMonitorService } from '../../node/agentHostFileMonitorService.js'; import { IAgentHostGitService } from '../../node/agentHostGitService.js'; import { AgentHostStateManager } from '../../node/agentHostStateManager.js'; diff --git a/src/vs/platform/agentHost/test/node/agentHostCommitOperationHandler.test.ts b/src/vs/platform/agentHost/test/node/agentHostCommitOperationHandler.test.ts index 99ddda6c376..8c9b68e1119 100644 --- a/src/vs/platform/agentHost/test/node/agentHostCommitOperationHandler.test.ts +++ b/src/vs/platform/agentHost/test/node/agentHostCommitOperationHandler.test.ts @@ -16,11 +16,11 @@ import { SessionStatus, withSessionGitState, type ISessionFileDiff } from '../.. import type { IAgentHostGitService } from '../../node/agentHostGitService.js'; import { AgentHostCommitOperationHandler } from '../../node/agentHostCommitOperationHandler.js'; import { AgentHostStateManager } from '../../node/agentHostStateManager.js'; -import type { IAgentHostChangesetService, IPersistedChangesetMetadata, IRestoredChangesetDiffs, StaticChangesetKind } from '../../node/agentHostChangesetService.js'; import { CopilotApiError, type ICopilotApiService, type ICopilotApiServiceRequestOptions, type ICopilotUtilityChatCompletionRequest } from '../../node/shared/copilotApiService.js'; import { GITHUB_COPILOT_PROTECTED_RESOURCE, IAgentService } from '../../common/agentService.js'; import { AHP_AUTH_REQUIRED, ProtocolError } from '../../common/state/sessionProtocol.js'; import { ChangesSummary } from '../../common/state/protocol/state.js'; +import type { IAgentHostChangesetService, IPersistedChangesetMetadata, IRestoredChangesetDiffs, StaticChangesetKind } from '../../common/agentHostChangesetService.js'; class TestGitService implements IAgentHostGitService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/platform/agentHost/test/node/agentHostTurnTelemetry.test.ts b/src/vs/platform/agentHost/test/node/agentHostTurnTelemetry.test.ts index a76be016065..4fe40f9c469 100644 --- a/src/vs/platform/agentHost/test/node/agentHostTurnTelemetry.test.ts +++ b/src/vs/platform/agentHost/test/node/agentHostTurnTelemetry.test.ts @@ -17,7 +17,7 @@ import { MessageKind, PendingMessageKind, ResponsePartKind, SessionStatus } from import { IAgentHostCheckpointService, NULL_CHECKPOINT_SERVICE } from '../../common/agentHostCheckpointService.js'; import { AgentHostTelemetryService } from '../../node/agentHostTelemetryService.js'; import { AgentConfigurationService, IAgentConfigurationService } from '../../node/agentConfigurationService.js'; -import { IAgentHostChangesetService } from '../../node/agentHostChangesetService.js'; +import { IAgentHostChangesetService } from '../../common/agentHostChangesetService.js'; import { AgentSideEffects } from '../../node/agentSideEffects.js'; import { AgentHostStateManager } from '../../node/agentHostStateManager.js'; import { createNullSessionDataService } from '../common/sessionTestHelpers.js'; diff --git a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts index d45539dabd2..e27fb418ef6 100644 --- a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts +++ b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts @@ -31,7 +31,7 @@ import { AgentHostTelemetryLevelConfigKey, telemetryLevelToAgentHostConfigValue import { AgentConfigurationService, IAgentConfigurationService } from '../../node/agentConfigurationService.js'; import { AgentHostTelemetryService } from '../../node/agentHostTelemetryService.js'; import { IAgentHostCheckpointService, NULL_CHECKPOINT_SERVICE } from '../../common/agentHostCheckpointService.js'; -import { IAgentHostChangesetService, StaticChangesetKind } from '../../node/agentHostChangesetService.js'; +import { IAgentHostChangesetService, StaticChangesetKind } from '../../common/agentHostChangesetService.js'; import { IAgentHostGitService } from '../../node/agentHostGitService.js'; import { AgentService } from '../../node/agentService.js'; import { AgentSideEffects, IAgentSideEffectsOptions } from '../../node/agentSideEffects.js';