2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import { dirname } from '../../../../base/common/path.js';
7
>
import type { IMcpServerDefinition, IParsedPlugin } from '../../../agentPlugins/common/pluginParsers.js';
8
>
import type { ISyncedCustomization } from '../../common/agentPluginManager.js';
9
>
import { type ChildCustomization, type PluginCustomization } from '../../common/state/sessionState.js';
10
>
import { toCodexMcpServerJson, type ICodexMcpServerConfigJson } from './codexMcpServers.js';
11
>
12
>
/**
13
>
* Codex ingests **client-pushed** plugin customizations (the "Open Plugins"
14
>
* the workbench syncs via {@link IActiveClient.customizations}) differently
15
>
* from the `.agents`/`.codex` files it discovers itself. This module holds the
16
>
* per-session store for those synced+parsed plugins plus the pure mappers that
17
>
* project them into (a) the AHP {@link PluginCustomization} surface, (b) codex
18
>
* per-thread `thread/start.config.mcp_servers`, and (c) process-global
19
>
* `skills/extraRoots/set` roots.
20
>
*
21
>
* Feeding strategy (see the phase investigation):
22
>
* - MCP servers are attached **per session** via `thread/start.config`
23
>
* (verified: codex starts the server for that thread only), so a plugin's
24
>
* server only runs for sessions that enable it.
25
>
* - Skills are process-global in codex (`skills/extraRoots/set` replaces a
26
>
* single shared root list), so the store exposes the union of enabled skill
27
>
* roots and the agent sets it across all live sessions. This matches the
28
>
* semantics of client customizations, which are global user choices.
29
>
*/
30
>
31
>
/** A single client-pushed plugin: its sync result plus the parsed components (when the sync succeeded). */
32
>
export interface ICodexClientPlugin {
33
>
readonly synced: ISyncedCustomization;
34
>
readonly parsed: IParsedPlugin | undefined;
35
>
}
36
>
37
>
/**
38
>
* Per-session store of client-pushed plugin customizations, keyed by the
39
>
* contributing client id, with a per-customization enablement overlay
40
>
* (absent = enabled, `false` = disabled). Merges every client's contribution
41
>
* deduplicated by customization id (first client wins). Pure state holder —
42
>
* the agent reads the projections below and drives codex.
43
>
*/
44
>
export class CodexClientCustomizationStore {
45
46
private readonly _byClient = new Map<string, readonly ICodexClientPlugin[]>();
47
private readonly _enablement = new Map<string, boolean>();
49
>
/** Replace one client's synced+parsed plugin set. */
50
>
setClient(clientId: string, plugins: readonly ICodexClientPlugin[]): void {
51
this._byClient.set(clientId, plugins);
52
}
54
>
/** Drop a client's contribution. Returns whether anything was removed. */
55
>
removeClient(clientId: string): boolean {
56
return this._byClient.delete(clientId);
57
}
59
>
/**
60
>
* Toggle a client-pushed customization on/off. Returns whether the
61
>
* enablement actually changed (so callers can skip a no-op refresh).
62
>
*/
63
>
setEnabled(id: string, enabled: boolean): boolean {
64
const current = this._enablement.get(id);
65
const effective = current !== false; // absent counts as enabled