Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e725c62
automations: feat: migrate execution to Agent Host Protocol
ulugbekna Aug 20, 2026
4292d1e
automations: guard Agent Host migration cutover
benvillalobos Aug 21, 2026
741fde0
automations: optimize sparse cron evaluation
benvillalobos Aug 21, 2026
55790ef
automations: gate initial Run advertisement on enabled state
benvillalobos Aug 21, 2026
5bbad44
automations/ahp: gate SessionWorkingDirectoryReplaced action
benvillalobos Aug 21, 2026
e2c2657
automations/ahp: enforce single-run invariant in schedule claim loop
benvillalobos Aug 21, 2026
7747158
automations/ahp: coalesce simultaneously-due schedule triggers into o…
benvillalobos Aug 21, 2026
8d14a90
automations/ahp: gate Run authority on legacy import until source is …
benvillalobos Aug 21, 2026
b24e000
Merge origin/main into automations AHP migration
Copilot Aug 21, 2026
dda2f29
automations: finish Agent Host merge integration
Copilot Aug 22, 2026
3ff46a4
test: mirror host automation migration authority
Copilot Aug 22, 2026
4fe6244
agentHost: restore main's reject of SessionWorkingDirectoryReplaced
benvillalobos Aug 22, 2026
1b4c629
Merge branch 'main' into agents/vs-code-ahp-migration-strategy
benvillalobos Aug 22, 2026
1494f64
signing commit
benvillalobos Aug 22, 2026
d6f906c
signing commit
benvillalobos Aug 22, 2026
79b4542
automations: use family guards for dispatch, matching existing pattern
benvillalobos Aug 24, 2026
ae94bb8
automations: drop reducer-helpers sync patch, no longer needed
benvillalobos Aug 24, 2026
5ebf08a
agentHost: separate subscription resources and channels
benvillalobos Aug 24, 2026
d670771
Merge latest main into AHP migration branch
benvillalobos Aug 25, 2026
aae1d21
Merge latest main into AHP migration branch
benvillalobos Aug 25, 2026
dd8a259
automations: give the catalogue channel a round-trippable authority
benvillalobos Aug 25, 2026
e03a498
automations: key the catalogue channel like every other channel
benvillalobos Aug 25, 2026
c1bc4cb
automations: use shared action family routing
benvillalobos Aug 25, 2026
2b781de
automations: inject the automation service as a collaborator
benvillalobos Aug 25, 2026
3d11faa
automations: key subscriptions by URI through ResourceMap
benvillalobos Aug 25, 2026
87fcdc1
automations: revert subscribe callbacks to URI
benvillalobos Aug 25, 2026
3ad68d5
Merge latest main into AHP migration branch
benvillalobos Aug 26, 2026
9e3af11
automations: migrate legacy definitions to native AHP state
benvillalobos Aug 26, 2026
aa8be26
Merge origin/main into agents/vs-code-ahp-migration-strategy
benvillalobos Aug 26, 2026
cfff2d6
automations: stabilize legacy target serialization for AHP migration
benvillalobos Aug 27, 2026
030a452
automations: ignore rejected chat actions when finalizing runs
benvillalobos Aug 27, 2026
c825b10
automations: salvage valid legacy ledger entries
benvillalobos Aug 27, 2026
a45312d
automations: recover corrupt legacy run archives
benvillalobos Aug 27, 2026
f71b4a1
automations: wait for provider migration before wakeup
benvillalobos Aug 27, 2026
4e5201d
automations: reject inactive catalog subscriptions
benvillalobos Aug 27, 2026
dfb5aa6
automations: surface pending import drain failures
benvillalobos Aug 27, 2026
e81cc64
Merge remote-tracking branch 'origin/main' into agents/vs-code-ahp-mi…
benvillalobos Aug 27, 2026
096f973
Merge remote-tracking branch 'origin/main' into agents/vs-code-ahp-mi…
benvillalobos Aug 27, 2026
cde4af1
signing commit
benvillalobos Aug 27, 2026
85f59c8
signing commit
benvillalobos Aug 27, 2026
675c765
automations: acknowledge migrated snapshots
benvillalobos Aug 27, 2026
b2214fe
signing commit
benvillalobos Aug 27, 2026
7f93c1e
Merge branch 'main' into agents/vs-code-ahp-migration-strategy
benvillalobos Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions scripts/sync-agent-host-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { execSync } from 'child_process';
import * as ts from 'typescript';

const ROOT = path.resolve(__dirname, '..');
const PROTOCOL_REPO = path.resolve(ROOT, '../agent-host-protocol');
const PROTOCOL_REPO = process.env['AHP_PROTOCOL_REPO'] ?? path.resolve(ROOT, '../agent-host-protocol');
const TYPES_DIR = path.join(PROTOCOL_REPO, 'types');
const DEST_DIR = path.join(ROOT, 'src/vs/platform/agentHost/common/state/protocol');

Expand Down Expand Up @@ -219,16 +219,30 @@ function mergeDuplicateImports(content: string): string {
}).join('\n');
}




function applyGeneratedSourceFixes(content: string, dest: string): string {
const replaceRequired = (search: string | RegExp, replacement: string): void => {
const next = content.replace(search, replacement);
if (next === content) {
throw new Error(`Required generated-source compatibility fix no longer matches ${dest}`);
}
content = next;
};
if (dest === 'channels-automation/state.ts') {
replaceRequired(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we weren't going to need this anymore?

@benvillalobos Ben Villalobos (benvillalobos) Aug 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this needs to exist while the version of AHP we're on is pre- this PR microsoft/agent-host-protocol#412 and this should be removed once that bump happens

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That PR is merged so why not just bump it now?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we definitely have that commit in main already. This is just kind of a weird thing to have in the script.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't realize .ahp-version was already bumped by this point. will follow up with some cleanup on this

'import type { AutomationCreateRequestedAction, AutomationRemovedAction, AutomationSetAction, AutomationUpdateRequestedAction } from \'./actions.js\';',
Comment thread
benvillalobos marked this conversation as resolved.
'import type { AutomationRemovedAction, AutomationSetAction, AutomationUpdateRequestedAction } from \'./actions.js\';',
);
}
return content;
}

function processFile(src: string, dest: string): void {
let content = fs.readFileSync(src, 'utf-8');
content = stripExistingHeader(content);

// Merge duplicate imports from the same module
content = mergeDuplicateImports(content);
content = applyGeneratedSourceFixes(content, dest);

content = convertIndentation(content);
content = content.split('\n').map(line => line.trimEnd()).join('\n');
Expand Down
55 changes: 40 additions & 15 deletions src/vs/platform/agentHost/browser/agentHostProtocolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { AgentSubscriptionManager, type IActiveSubscriptionInfo, type IAgentSubs
import { agentHostAuthority, createAgentHostResourceUriMapper, fromAgentHostUri, identityAgentHostResourceUriMapper, type IAgentHostResourceUriMapper, toAgentHostUri } from '../common/agentHostUri.js';
import { AgentHostResourceIdentity, AgentHostResourcePermissionError, IAgentHostResourceService, LOCAL_AGENT_HOST_RESOURCE_IDENTITY } from '../common/agentHostResourceService.js';
import type { ClientNotificationMap, CommandMap, JsonRpcErrorResponse, JsonRpcRequest } from '../common/state/protocol/messages.js';
import { ActionType, type ActionEnvelope, type ChatAction, type ClientAnnotationsAction, type ClientChangesetAction, type INotification, type IRootConfigChangedAction, type SessionAction, type TerminalAction } from '../common/state/sessionActions.js';
import { ActionType, type ActionEnvelope, type ChatAction, type ClientAnnotationsAction, type ClientAutomationAction, type ClientAutomationRunAction, type ClientChangesetAction, type INotification, type IRootConfigChangedAction, type SessionAction, type TerminalAction } from '../common/state/sessionActions.js';
import { MessageAttachmentKind, SessionSummary, ROOT_STATE_URI, StateComponents, isAhpRootChannel, isDefaultChatUri, type ClientPluginCustomization, type Message, type RootState } from '../common/state/sessionState.js';
import { normalizeLegacyActionEnvelope } from '../common/state/legacyProtocolCompatibility.js';
import { SUPPORTED_PROTOCOL_VERSIONS } from '../common/state/protocol/version/registry.js';
Expand All @@ -36,6 +36,7 @@ import { AhpErrorCodes, JsonRpcErrorCodes } from '../common/state/protocol/error
import { ChatSourceKind, ContentEncoding, ResourceRequestParams, type CompletionsParams, type CompletionsResult, type CreateTerminalParams, type ResolveSessionConfigResult, type SessionConfigCompletionsResult } from '../common/state/protocol/commands.js';
import type { InvokeChangesetOperationParams, InvokeChangesetOperationResult } from '../common/state/protocol/channels-changeset/commands.js';
import { decodeBase64, encodeBase64 } from '../../../base/common/buffer.js';
import type { FetchAutomationRunsParams, FetchAutomationRunsResult, ListAutomationTriggerDefinitionsParams, ListAutomationTriggerDefinitionsResult, RunAutomationParams, RunAutomationResult } from '../common/state/protocol/channels-automation/commands.js';
import { ILoadEstimator, LoadEstimator } from '../../../base/parts/ipc/common/ipc.net.js';
import { ITelemetryService, TelemetryLevel, TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID } from '../../telemetry/common/telemetry.js';
import { getTelemetryLevel } from '../../telemetry/common/telemetryUtils.js';
Expand Down Expand Up @@ -649,7 +650,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
return;
}

const subscriptions = this._subscriptionManager.currentSubscriptionUris().map(u => u.toString());
const subscriptions = this._subscriptionManager.currentSubscriptionChannels();
// Always include the always-live root state alongside getSubscription-managed entries.
if (!subscriptions.includes(ROOT_STATE_URI)) {
subscriptions.unshift(ROOT_STATE_URI);
Expand Down Expand Up @@ -745,12 +746,12 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
private async _restoreSubscriptionsAfterFreshInitialize(initialSnapshots: readonly IStateSnapshot[]): Promise<void> {
const restored = new Set(initialSnapshots.map(snapshot => snapshot.resource));
const active = this._subscriptionManager.getActiveSubscriptions()
.filter(subscription => !restored.has(subscription.resource.toString()));
.filter(subscription => !restored.has(subscription.channel));
const restoreGroup = async (subscriptions: typeof active) => {
await Promise.all(subscriptions.map(async subscription => {
try {
const result = await this._dispatchRequest<CommandMap['subscribe']['result']>('subscribe', {
channel: subscription.resource.toString(),
channel: subscription.channel,
}, { bypassReconnectGate: true });
if (result.snapshot) {
this._subscriptionManager.applyReconnectSnapshot(
Expand All @@ -765,8 +766,8 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
if (error instanceof ProtocolError && error.code === AHP_CLIENT_CONNECTION_CLOSED) {
throw error;
}
this._logService.warn(`[AgentHostProtocolClient] Failed to restore subscription ${subscription.resource.toString()} after host restart: ${error instanceof Error ? error.message : String(error)}`);
this._subscriptionManager.markSubscriptionsMissing([subscription.resource]);
this._logService.warn(`[AgentHostProtocolClient] Failed to restore subscription ${subscription.channel} after host restart: ${error instanceof Error ? error.message : String(error)}`);
this._subscriptionManager.markSubscriptionsMissing([subscription.channel]);
}
}));
};
Expand Down Expand Up @@ -867,7 +868,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
this._serverSeq = maxSeq;
if (result.missing.length > 0) {
this._logService.info(`[RemoteAgentHostProtocol] Server cannot resume ${result.missing.length} subscription(s) after reconnect.`);
this._subscriptionManager.markSubscriptionsMissing(result.missing.map(u => URI.parse(u)));
this._subscriptionManager.markSubscriptionsMissing(result.missing);
}
} else {
let maxSeq = this._serverSeq;
Expand Down Expand Up @@ -944,6 +945,10 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
return this._subscriptionManager.getSubscription<T>(kind, resource, owner);
}

getSubscriptionByChannel<T>(kind: StateComponents, channel: string, owner: string): IReference<IAgentSubscription<T>> {
return this._subscriptionManager.getSubscriptionByChannel<T>(kind, channel, owner);
}

getSubscriptionUnmanaged<T>(_kind: StateComponents, resource: URI): IAgentSubscription<T> | undefined {
return this._subscriptionManager.getSubscriptionUnmanaged<T>(resource);
}
Expand All @@ -960,7 +965,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
return this._subscriptionManager.getActiveSubscriptions();
}

dispatch(channel: string, action: SessionAction | ChatAction | TerminalAction | ClientChangesetAction | ClientAnnotationsAction | IRootConfigChangedAction): void {
dispatch(channel: string, action: SessionAction | ChatAction | TerminalAction | ClientChangesetAction | ClientAnnotationsAction | ClientAutomationAction | ClientAutomationRunAction | IRootConfigChangedAction): void {
const seq = this._subscriptionManager.dispatchOptimistic(channel, action);
this.dispatchAction(channel, action, this._clientId, seq);
}
Expand All @@ -974,12 +979,16 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
* response.
*/
async subscribe(resource: URI): Promise<IStateSnapshot> {
this._logService.trace(`[RemoteAgentHostProtocol] subscribe start: ${resource.toString()}`);
const result = await this._sendRequest('subscribe', { channel: resource.toString() });
return this._subscribeChannel(resource.toString());
}

private async _subscribeChannel(channel: string): Promise<IStateSnapshot> {
this._logService.trace(`[RemoteAgentHostProtocol] subscribe start: ${channel}`);
const result = await this._sendRequest('subscribe', { channel });
if (!result.snapshot) {
throw new Error(`subscribe to ${resource.toString()} returned no snapshot`);
throw new Error(`subscribe to ${channel} returned no snapshot`);
}
this._logService.trace(`[RemoteAgentHostProtocol] subscribe done: ${resource.toString()}`);
this._logService.trace(`[RemoteAgentHostProtocol] subscribe done: ${channel}`);
return result.snapshot;
}

Expand All @@ -1001,13 +1010,17 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
* Unsubscribe from state at a URI.
*/
unsubscribe(resource: URI): void {
this._sendNotification('unsubscribe', { channel: resource.toString() });
this._unsubscribeChannel(resource.toString());
}

private _unsubscribeChannel(channel: string): void {
this._sendNotification('unsubscribe', { channel });
}

/**
* Dispatch a client action to the server. Returns the clientSeq used.
*/
private dispatchAction(channel: string, action: SessionAction | ChatAction | TerminalAction | ClientChangesetAction | ClientAnnotationsAction | IRootConfigChangedAction, _clientId: string, clientSeq: number): void {
private dispatchAction(channel: string, action: SessionAction | ChatAction | TerminalAction | ClientChangesetAction | ClientAnnotationsAction | ClientAutomationAction | ClientAutomationRunAction | IRootConfigChangedAction, _clientId: string, clientSeq: number): void {
this._grantImplicitReadsForOutgoingAction(action);
this._sendNotification('dispatchAction', { channel, clientSeq, action });
}
Expand Down Expand Up @@ -1063,6 +1076,18 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
return this._sendRequest('completions', params);
}

async listAutomationTriggerDefinitions(params: ListAutomationTriggerDefinitionsParams): Promise<ListAutomationTriggerDefinitionsResult> {
return this._sendRequest('listAutomationTriggerDefinitions', params);
}

async runAutomation(params: RunAutomationParams): Promise<RunAutomationResult> {
return this._sendRequest('runAutomation', params);
}

async fetchAutomationRuns(params: FetchAutomationRunsParams): Promise<FetchAutomationRunsResult> {
return this._sendRequest('fetchAutomationRuns', params);
}

/**
* Send an application-level ping and wait for the server's response.
* Used by {@link _watchdogTick} to keep idle connections under
Expand Down Expand Up @@ -1310,7 +1335,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
* Inspect an outgoing client-dispatched action and grant implicit reads for
* resources that the host will need to read after receiving the action.
*/
private _grantImplicitReadsForOutgoingAction(action: SessionAction | ChatAction | TerminalAction | ClientChangesetAction | ClientAnnotationsAction | IRootConfigChangedAction): void {
private _grantImplicitReadsForOutgoingAction(action: SessionAction | ChatAction | TerminalAction | ClientChangesetAction | ClientAnnotationsAction | ClientAutomationAction | ClientAutomationRunAction | IRootConfigChangedAction): void {
switch (action.type) {
case ActionType.SessionActiveClientSet:
if (action.activeClient.customizations) {
Expand Down
9 changes: 7 additions & 2 deletions src/vs/platform/agentHost/browser/nullAgentHostService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import type { IActiveSubscriptionInfo, IAgentSubscription } from '../common/stat
import type { CompletionsParams, CompletionsResult, CreateTerminalParams, ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../common/state/protocol/commands.js';
import type { InitializeResult } from '../common/state/protocol/common/commands.js';
import type { InvokeChangesetOperationParams, InvokeChangesetOperationResult } from '../common/state/protocol/channels-changeset/commands.js';
import type { ActionEnvelope, INotification, IRootConfigChangedAction, SessionAction, TerminalAction, ClientAnnotationsAction } from '../common/state/sessionActions.js';
import type { FetchAutomationRunsParams, FetchAutomationRunsResult, ListAutomationTriggerDefinitionsParams, ListAutomationTriggerDefinitionsResult, RunAutomationParams, RunAutomationResult } from '../common/state/protocol/channels-automation/commands.js';
import type { ActionEnvelope, ChatAction, ClientAnnotationsAction, ClientAutomationAction, ClientAutomationRunAction, ClientChangesetAction, INotification, IRootConfigChangedAction, SessionAction, TerminalAction } from '../common/state/sessionActions.js';
import type { IRemoteWatchHandle } from '../common/agentHostFileSystemProvider.js';
import type { CreateResourceWatchParams, CreateResourceWatchResult, ResourceCopyParams, ResourceCopyResult, ResourceDeleteParams, ResourceDeleteResult, ResourceListResult, ResourceMkdirParams, ResourceMkdirResult, ResourceMoveParams, ResourceMoveResult, ResourceReadResult, ResourceResolveParams, ResourceResolveResult, ResourceWriteParams, ResourceWriteResult } from '../common/state/sessionProtocol.js';
import type { ComponentToState, RootState, StateComponents } from '../common/state/sessionState.js';
Expand Down Expand Up @@ -45,10 +46,11 @@ export class NullAgentHostService implements IAgentHostService {
get rootState(): IAgentSubscription<RootState> { return notSupported(); }

getSubscription<T extends StateComponents>(_kind: T, _resource: URI, _owner: string): IReference<IAgentSubscription<ComponentToState[T]>> { return notSupported(); }
getSubscriptionByChannel<T extends StateComponents>(_kind: T, _channel: string, _owner: string): IReference<IAgentSubscription<ComponentToState[T]>> { return notSupported(); }
getSubscriptionUnmanaged<T extends StateComponents>(_kind: T, _resource: URI): IAgentSubscription<ComponentToState[T]> | undefined { return undefined; }
getInflightSessionCreate(_resource: URI): Promise<unknown> | undefined { return undefined; }
getActiveSubscriptions(): readonly IActiveSubscriptionInfo[] { return []; }
dispatch(_channel: string, _action: SessionAction | TerminalAction | ClientAnnotationsAction | IRootConfigChangedAction): void { notSupported(); }
dispatch(_channel: string, _action: SessionAction | ChatAction | TerminalAction | ClientChangesetAction | ClientAnnotationsAction | ClientAutomationAction | ClientAutomationRunAction | IRootConfigChangedAction): void { notSupported(); }

async restartAgentHost(): Promise<void> { notSupported(); }
async authenticate(_params: AuthenticateParams): Promise<AuthenticateResult> { return notSupported(); }
Expand All @@ -63,6 +65,9 @@ export class NullAgentHostService implements IAgentHostService {
async resolveSessionConfig(_params: IAgentResolveSessionConfigParams): Promise<ResolveSessionConfigResult> { return notSupported(); }
async sessionConfigCompletions(_params: IAgentSessionConfigCompletionsParams): Promise<SessionConfigCompletionsResult> { return notSupported(); }
async completions(_params: CompletionsParams): Promise<CompletionsResult> { return { items: [] }; }
async listAutomationTriggerDefinitions(_params: ListAutomationTriggerDefinitionsParams): Promise<ListAutomationTriggerDefinitionsResult> { return notSupported(); }
async runAutomation(_params: RunAutomationParams): Promise<RunAutomationResult> { return notSupported(); }
async fetchAutomationRuns(_params: FetchAutomationRunsParams): Promise<FetchAutomationRunsResult> { return notSupported(); }
async getCompletionTriggerCharacters(): Promise<readonly string[]> { return []; }
async startWebSocketServer(): Promise<IAgentHostSocketInfo> { return notSupported(); }
async getInspectInfo(_tryEnable: boolean): Promise<IAgentHostInspectInfo | undefined> { return undefined; }
Expand Down
Loading
Loading