dsh-gpt-adapter 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -7
- package/README.zh-CN.md +26 -7
- package/dist/src/codex-config.d.ts +2 -1
- package/dist/src/codex-mcp-metadata.d.ts +12 -0
- package/lib/index.js +171 -28
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -22,9 +22,19 @@ Reasoning and speed presets are requests to the configured gateway, not guarante
|
|
|
22
22
|
|
|
23
23
|
## Compatibility and installation
|
|
24
24
|
|
|
25
|
-
The native integration targets DSH `0.2.2-rc.1`, Cordis `4.1.0`, and the pinned adapter source hash. The development dependency uses Pi AI `1.3.0`. Installed third-party package files are never edited. The
|
|
25
|
+
The native integration targets DSH `0.2.2-rc.1`, Cordis `4.1.0`, and the pinned adapter source hash. The development dependency uses Pi AI `1.3.0`. Installed third-party package files are never edited. The published npm package is `dsh-gpt-adapter@0.2.3`.
|
|
26
26
|
|
|
27
|
-
Requires Node.js `>=24.9.0` and pnpm.
|
|
27
|
+
Requires Node.js `>=24.9.0` and DSH. Source builds also require pnpm.
|
|
28
|
+
|
|
29
|
+
Install the published package in the web profile:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
dsh plugin --profile web add dsh-gpt-adapter@0.2.3
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Restart the DSH host after installing or upgrading. Configure GPT connections under **Settings → Models**; Codex environment reuse is enabled by default. The plugin extends the existing native settings and conversation controls.
|
|
36
|
+
|
|
37
|
+
To install from source for development or verification:
|
|
28
38
|
|
|
29
39
|
```sh
|
|
30
40
|
git clone https://github.com/lxjmaster/dsh-gpt-adapter.git
|
|
@@ -32,17 +42,16 @@ cd dsh-gpt-adapter
|
|
|
32
42
|
pnpm install --frozen-lockfile
|
|
33
43
|
mkdir -p artifacts
|
|
34
44
|
pnpm pack --pack-destination artifacts
|
|
35
|
-
dsh plugin --profile web add ./artifacts/dsh-gpt-adapter-0.2.
|
|
45
|
+
dsh plugin --profile web add ./artifacts/dsh-gpt-adapter-0.2.3.tgz
|
|
36
46
|
```
|
|
37
47
|
|
|
38
|
-
`pnpm pack` runs the production build.
|
|
48
|
+
`pnpm pack` runs the production build.
|
|
39
49
|
|
|
40
50
|
## Automated releases
|
|
41
51
|
|
|
42
52
|
The `Release` workflow runs on pushes to `main` and manual dispatches. For a formal `X.Y.Z` version, it runs the test suite, creates `vX.Y.Z`, creates the matching GitHub Release, and publishes the package to npm. Prerelease versions are skipped, and existing tags or npm versions are reused safely.
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
Configure GPT connections as usual under **Settings → Models**. The plugin extends the existing native settings and conversation controls.
|
|
54
|
+
Maintainers must provide the npm access token as the repository secret `NPM_TOKEN` for this workflow.
|
|
46
55
|
|
|
47
56
|
## Codex environment reuse
|
|
48
57
|
|
|
@@ -58,12 +67,14 @@ Codex compatibility is enabled by default. The root is the plugin's `codexHome`
|
|
|
58
67
|
| Codex skills | Discover `skills/` and `skills/.system/`; bodies load on demand |
|
|
59
68
|
| Shared/project skills | Retain native discovery and include `.agents/skills` at every directory from cwd to repository root; nearer project skills take precedence |
|
|
60
69
|
| Skill enablement | Honor ordered `skills.config` name/path selectors and `skills.bundled.enabled` |
|
|
61
|
-
| Enabled plugins |
|
|
70
|
+
| Enabled plugins | Support portable root `plugin.json` / `mcp.json` and legacy declarations; disabled plugins stay disabled |
|
|
62
71
|
|
|
63
72
|
The compatibility layer reads project instructions through DSH's filesystem, selects candidates using Codex precedence, and replaces native messages only for covered scopes. Additional nested instruction updates remain applicable. It does not import Codex's built-in system prompt. Configured fallback filenames such as `CLAUDE.md` are used only when supplied in Codex configuration. Instructions are recorded as ordinary user context with a bounded byte budget, including an explicit zero budget; they do not override system, developer, or direct user instructions. Existing conversation history is retained when the plugin is removed.
|
|
64
73
|
|
|
65
74
|
Plugin resources are resolved from the configured local marketplace, or the newest locally cached version in the matching marketplace. Nothing is downloaded automatically. Plugin skills use `<plugin>-<skill>` names to fit DSH's skill-name format and avoid cross-plugin collisions. Original skill paths and relative resources remain intact.
|
|
66
75
|
|
|
76
|
+
Portable Agent Plugins use fixed `skills/` and `mcp.json` components, regardless of legacy `skills` or `mcpServers` declarations. OpenAI settings come from the root manifest's `extensions.com.openai` object, including an empty object; only when absent does `.codex-plugin/plugin.json` supply the overlay. Plugin identity always comes from the root manifest.
|
|
77
|
+
|
|
67
78
|
## MCP compatibility
|
|
68
79
|
|
|
69
80
|
The plugin automatically starts enabled MCP commands and connects configured HTTP endpoints for each Agent session. It imports `mcp_servers` and the MCP resources declared by enabled plugins. Trusted project overrides apply within that Agent's scope; concurrent sessions do not share imported tool registrations. Untrusted project configuration is skipped.
|
|
@@ -77,6 +88,14 @@ Supported configuration includes:
|
|
|
77
88
|
|
|
78
89
|
Global server names are retained where DSH permits them. Plugin servers use a plugin prefix. Unchanged connections are preserved. Changed or removed connections are disposed before replacement. Invalid configuration retains the last working snapshot, including individually invalid server entries. With automatic refresh enabled, failed startup retries with exponential backoff for up to ten attempts; a manual refresh resets that budget. The default two-second refresh detects configuration and plugin-resource changes; native skill watchers detect skill file changes.
|
|
79
90
|
|
|
91
|
+
Plugin-scoped `plugins.<plugin>.mcp_servers.<server>` policies apply `enabled`, `enabled_tools`, `disabled_tools`, and per-tool disabling without overriding the plugin's executable, transport, or credentials. When plugin files are invalid, retained connections still receive current disabling and filtering restrictions; old restrictions are not relaxed until a valid declaration is available again.
|
|
92
|
+
|
|
93
|
+
Stdio servers default to their Agent's project working directory and reconnect when that directory changes. An explicit `cwd` takes precedence. Without an explicit `cwd`, relative executable paths still resolve from the config or plugin root, independently of the process working directory. Plugin paths support `${PLUGIN_ROOT}`, `${CODEX_PLUGIN_ROOT}`, and `${CLAUDE_PLUGIN_ROOT}`.
|
|
94
|
+
|
|
95
|
+
Imported MCP tools carry the current DSH session and turn in `tools/call` metadata under `_meta["x-codex-turn-metadata"]`. `session_id` is the Agent session ID; `turn_id` is `<session ID>:<turn/start event sequence>`. Calls in one turn share the same identity, new turns read a fresh boundary, and asynchronous sessions remain isolated. Diagnostic calls outside an active turn do not invent identities. These fields are transport metadata, never model-supplied tool arguments.
|
|
96
|
+
|
|
97
|
+
The path is DSH Agent → imported tool execution context → MCP `tools/call` metadata → `node_repl` → browser service. This bridge addresses `Missing required Codex turn metadata: session_id, turn_id`; the browser extension, authentication, and App-specific services must still be available on the host. Unloading the plugin removes the bridge, and ordinary DSH MCP calls do not receive these fields.
|
|
98
|
+
|
|
80
99
|
Credentials are not copied into DSH settings, plugin caches, or this repository. Status responses contain only resource names, states, and sanitized reason codes. Missing environment variables must exist in the DSH host environment to be inherited.
|
|
81
100
|
|
|
82
101
|
This is environment compatibility, not a replacement Codex runtime. Codex OAuth/keychain sessions, Codex App IPC services, hooks, app components, permission policies, and proprietary commands are not automatically reproduced. Standard MCPs using configured headers/environment can be reused; servers requiring unavailable runtime authentication or services report a connection failure. Plugin hooks/apps/commands are reported as unsupported rather than executed. DSH retains its own approval and sandbox behavior.
|
package/README.zh-CN.md
CHANGED
|
@@ -22,9 +22,19 @@
|
|
|
22
22
|
|
|
23
23
|
## 兼容性与安装
|
|
24
24
|
|
|
25
|
-
原生集成面向 DSH `0.2.2-rc.1`、Cordis `4.1.0`,并校验适配器源码指纹。开发依赖固定 Pi AI `1.3.0
|
|
25
|
+
原生集成面向 DSH `0.2.2-rc.1`、Cordis `4.1.0`,并校验适配器源码指纹。开发依赖固定 Pi AI `1.3.0`。插件不会修改已安装的第三方包文件。当前 npm 发布版本为 `dsh-gpt-adapter@0.2.3`。
|
|
26
26
|
|
|
27
|
-
需要 Node.js `>=24.9.0` 和 pnpm。
|
|
27
|
+
需要 Node.js `>=24.9.0` 和 DSH。源码构建另外需要 pnpm。
|
|
28
|
+
|
|
29
|
+
在 `web` 配置档(profile)中安装已发布的 npm 包:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
dsh plugin --profile web add dsh-gpt-adapter@0.2.3
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
安装或升级后重启 DSH 宿主。在「设置 → 模型」中配置 GPT 连接;Codex 环境复用默认开启。插件扩展现有原生设置与对话控件。
|
|
36
|
+
|
|
37
|
+
如需从源码安装进行开发或验证:
|
|
28
38
|
|
|
29
39
|
```sh
|
|
30
40
|
git clone https://github.com/lxjmaster/dsh-gpt-adapter.git
|
|
@@ -32,17 +42,16 @@ cd dsh-gpt-adapter
|
|
|
32
42
|
pnpm install --frozen-lockfile
|
|
33
43
|
mkdir -p artifacts
|
|
34
44
|
pnpm pack --pack-destination artifacts
|
|
35
|
-
dsh plugin --profile web add ./artifacts/dsh-gpt-adapter-0.2.
|
|
45
|
+
dsh plugin --profile web add ./artifacts/dsh-gpt-adapter-0.2.3.tgz
|
|
36
46
|
```
|
|
37
47
|
|
|
38
|
-
`pnpm pack`
|
|
48
|
+
`pnpm pack` 会自动执行生产构建。
|
|
39
49
|
|
|
40
50
|
## 自动发布
|
|
41
51
|
|
|
42
52
|
`Release` 工作流会在推送到 `main` 或手动运行时执行。版本号为正式的 `X.Y.Z` 时,工作流会运行测试、创建 `vX.Y.Z` 标签、创建对应的 GitHub Release,并将包发布到 npm。预发布版本会跳过;已有标签或 npm 版本会安全复用。
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
在「设置 → 模型」中照常配置 GPT 连接;插件扩展现有原生设置与对话控件。
|
|
54
|
+
维护者需要在仓库 Secrets 中提供 npm 访问令牌 `NPM_TOKEN`,工作流才能发布。
|
|
46
55
|
|
|
47
56
|
## 复用 Codex 环境
|
|
48
57
|
|
|
@@ -58,12 +67,14 @@ dsh plugin --profile web add ./artifacts/dsh-gpt-adapter-0.2.2.tgz
|
|
|
58
67
|
| Codex 技能 | 发现 `skills/` 和 `skills/.system/`,正文按需加载 |
|
|
59
68
|
| 共享与项目技能 | 保留原生发现机制,并补齐当前目录到仓库根每一级 `.agents/skills`;更近的项目技能优先 |
|
|
60
69
|
| 技能开关 | 遵循有序的 `skills.config` 名称/路径选择器,以及 `skills.bundled.enabled` |
|
|
61
|
-
| 已启用插件 |
|
|
70
|
+
| 已启用插件 | 支持新版根目录 `plugin.json`、`mcp.json` 和旧版声明;禁用插件不加载 |
|
|
62
71
|
|
|
63
72
|
兼容层通过 DSH 文件系统读取项目指令,按 Codex 优先级选择候选文件,只替换已覆盖作用域内的原生指令消息;额外发现的子目录指令更新仍然保留。它不会导入 Codex 内置系统提示词。`CLAUDE.md` 等后备文件仅在 Codex 配置明确声明时使用。指令以有字节预算的普通用户上下文写入会话,明确配置的零预算也会生效;指令不覆盖系统、开发者或用户直接指令。移除插件不会删除已有会话历史。
|
|
64
73
|
|
|
65
74
|
插件资源优先来自已配置的本地市场目录,否则使用匹配市场中最新的本地缓存版本,不会自动下载。插件技能使用 `<插件名>-<技能名>`,以符合 DSH 命名格式并避免跨插件冲突。技能原始路径和相对资源引用保持不变。
|
|
66
75
|
|
|
76
|
+
新版 Agent Plugins 使用固定的 `skills/` 与 `mcp.json`,不受旧版 `skills`、`mcpServers` 声明改写。OpenAI 扩展优先使用根清单的 `extensions.com.openai` 对象(包括空对象),缺省时才使用 `.codex-plugin/plugin.json`;插件名称仍取自根清单。
|
|
77
|
+
|
|
67
78
|
## MCP 兼容
|
|
68
79
|
|
|
69
80
|
插件会为每个会话代理(Agent)自动启动已启用的 MCP 命令并连接已配置的 HTTP 端点。导入来源包括 `mcp_servers` 和已启用插件声明的 MCP 资源。受信任项目的覆盖配置只在该会话代理的作用域内生效,并行会话不会共用导入工具的注册;未受信任项目的配置会跳过。
|
|
@@ -77,6 +88,14 @@ dsh plugin --profile web add ./artifacts/dsh-gpt-adapter-0.2.2.tgz
|
|
|
77
88
|
|
|
78
89
|
全局服务器名称在 DSH 允许时原样保留,插件服务器增加插件名前缀。未变化的连接继续复用;变更或删除的连接先卸载再替换。配置损坏时保留上一份有效快照,单个服务器配置无效时也会保留其可用连接。开启自动刷新时,启动失败会按指数退避间隔重试,最多尝试十次;手动刷新会重置次数。默认每两秒检查配置与插件资源变化,技能文件变化由原生监视器处理。
|
|
79
90
|
|
|
91
|
+
插件级 `plugins.<插件>.mcp_servers.<服务器>` 的 `enabled`、`enabled_tools`、`disabled_tools` 和单工具禁用设置会覆盖声明中的策略,但不能改写插件的命令、传输或凭证。插件文件损坏时,保留的旧连接仍应用最新禁用和过滤限制;有效声明恢复前,不会放宽旧快照已有的限制。
|
|
92
|
+
|
|
93
|
+
stdio 服务器默认从所属 Agent 的项目工作目录启动,切换项目时重建连接;显式 `cwd` 配置优先。未显式配置 `cwd` 时,相对可执行文件路径仍按配置或插件根目录解析,避免与运行目录混淆。插件根目录替换同时支持 `${PLUGIN_ROOT}`、`${CODEX_PLUGIN_ROOT}` 和 `${CLAUDE_PLUGIN_ROOT}`。
|
|
94
|
+
|
|
95
|
+
导入的 MCP 工具会在 `tools/call` 的 `_meta["x-codex-turn-metadata"]` 中携带当前 DSH 会话和轮次信息。`session_id` 使用 Agent 的会话 ID,`turn_id` 使用 `<会话 ID>:<turn/start 事件序号>`;同一轮的调用保持一致,新轮次重新读取,异步并发会话相互隔离。没有活动轮次的诊断调用不生成虚假标识。这些字段不进入工具参数,也不要求模型填写。
|
|
96
|
+
|
|
97
|
+
调用链为:DSH Agent → 导入工具的异步调用上下文 → MCP `tools/call` 元数据 → `node_repl` → 浏览器服务。此桥接修复浏览器服务因缺少 `session_id`、`turn_id` 抛出的 `Missing required Codex turn metadata`;浏览器扩展、认证和 App 专属服务仍需在宿主可用。插件卸载时撤销桥接,普通 DSH MCP 调用不注入这些字段。
|
|
98
|
+
|
|
80
99
|
凭证不会复制到 DSH 设置、插件缓存或本仓库。状态接口只返回资源名、状态和经过脱敏的原因代码。需要继承的环境变量必须存在于 DSH 宿主环境中。
|
|
81
100
|
|
|
82
101
|
本插件提供环境兼容,不会重新实现 Codex 运行时。Codex 的 OAuth/钥匙串会话、App 专属进程通信服务、Hooks、应用组件、权限策略和专用命令不会自动重现。使用配置中的请求头或环境变量认证的标准 MCP 可以复用;依赖缺失认证或运行时服务的服务器会报告连接失败。插件的 Hooks、应用和命令明确标为不支持,不会自动执行。审批和沙箱仍由 DSH 自身管理。
|
|
@@ -8,6 +8,7 @@ export type CompatibilityItem = {
|
|
|
8
8
|
export type CodexMcpConfig = {
|
|
9
9
|
serverName: string;
|
|
10
10
|
source: string;
|
|
11
|
+
originalName?: string;
|
|
11
12
|
transport: 'stdio' | 'streamable-http';
|
|
12
13
|
command?: string;
|
|
13
14
|
args?: string[];
|
|
@@ -47,7 +48,7 @@ export declare function projectText(path: string, fs?: ProjectFileSystem, signal
|
|
|
47
48
|
/** Repository root to cwd; without a repository only cwd participates. */
|
|
48
49
|
export declare function projectDirectories(cwd: string, fs?: ProjectFileSystem, signal?: AbortSignal): Promise<string[]>;
|
|
49
50
|
export declare function readCodexConfig(home: string, cwd?: string, fs?: ProjectFileSystem, signal?: AbortSignal): Promise<JsonObject>;
|
|
50
|
-
export declare function normalizeCodexMcp(name: string, value: unknown, source: string, base: string, environment?: NodeJS.ProcessEnv): CodexMcpConfig | undefined;
|
|
51
|
+
export declare function normalizeCodexMcp(name: string, value: unknown, source: string, base: string, environment?: NodeJS.ProcessEnv, defaultCwd?: string): CodexMcpConfig | undefined;
|
|
51
52
|
export declare function discoverCodexSnapshot(home: string, options: {
|
|
52
53
|
importPluginResources: boolean;
|
|
53
54
|
mcpAllowlist: string[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type CodexTurnMetadata = {
|
|
2
|
+
session_id: string;
|
|
3
|
+
turn_id: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* DSH 0.2.2-rc.1 has no per-call MCP metadata hook. Scope the SDK seam to
|
|
7
|
+
* imported tool executions so its native lifecycle and result handling remain intact.
|
|
8
|
+
*/
|
|
9
|
+
export declare function installCodexMcpMetadata(): {
|
|
10
|
+
run<T>(metadata: CodexTurnMetadata | undefined, operation: () => T): T;
|
|
11
|
+
dispose(): void;
|
|
12
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -1082,7 +1082,7 @@ function serverName(name2) {
|
|
|
1082
1082
|
const hash = createHash3("sha256").update(name2).digest("hex").slice(0, 8);
|
|
1083
1083
|
return `${name2.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 23)}-${hash}`;
|
|
1084
1084
|
}
|
|
1085
|
-
function normalizeCodexMcp(name2, value, source, base, environment = process.env) {
|
|
1085
|
+
function normalizeCodexMcp(name2, value, source, base, environment = process.env, defaultCwd = base) {
|
|
1086
1086
|
const raw = object(value);
|
|
1087
1087
|
if (raw.enabled === false) return void 0;
|
|
1088
1088
|
const headers = record2(raw.http_headers ?? raw.headers);
|
|
@@ -1131,8 +1131,8 @@ function normalizeCodexMcp(name2, value, source, base, environment = process.env
|
|
|
1131
1131
|
if (environment[variable] !== void 0 && env[variable] === void 0)
|
|
1132
1132
|
env[variable] = environment[variable];
|
|
1133
1133
|
}
|
|
1134
|
-
const cwd = typeof raw.cwd === "string" ? resolve(base, raw.cwd) :
|
|
1135
|
-
const command = raw.command.includes("/") && !isAbsolute(raw.command) ? resolve(cwd, raw.command) : raw.command;
|
|
1134
|
+
const cwd = typeof raw.cwd === "string" ? resolve(base, raw.cwd) : defaultCwd;
|
|
1135
|
+
const command = raw.command.includes("/") && !isAbsolute(raw.command) ? resolve(typeof raw.cwd === "string" ? cwd : base, raw.command) : raw.command;
|
|
1136
1136
|
return {
|
|
1137
1137
|
...common,
|
|
1138
1138
|
transport: "stdio",
|
|
@@ -1151,9 +1151,29 @@ async function directories(path) {
|
|
|
1151
1151
|
}
|
|
1152
1152
|
}
|
|
1153
1153
|
async function manifestAt(root) {
|
|
1154
|
+
const text = await optionalText(join2(root, "plugin.json"));
|
|
1155
|
+
if (text !== void 0) {
|
|
1156
|
+
const manifest = object(JSON.parse(text));
|
|
1157
|
+
if (manifest.$schema === "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json") {
|
|
1158
|
+
const inline = manifest.extensions?.["com.openai"];
|
|
1159
|
+
const overlay = inline !== null && typeof inline === "object" && !Array.isArray(inline) ? inline : object(
|
|
1160
|
+
JSON.parse(
|
|
1161
|
+
await optionalText(
|
|
1162
|
+
join2(root, ".codex-plugin", "plugin.json")
|
|
1163
|
+
) ?? "{}"
|
|
1164
|
+
)
|
|
1165
|
+
);
|
|
1166
|
+
return {
|
|
1167
|
+
root,
|
|
1168
|
+
manifest: { ...manifest, ...overlay, name: manifest.name },
|
|
1169
|
+
portable: true
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1154
1173
|
for (const name2 of [".codex-plugin", ".claude-plugin"]) {
|
|
1155
|
-
const
|
|
1156
|
-
if (
|
|
1174
|
+
const text2 = await optionalText(join2(root, name2, "plugin.json"));
|
|
1175
|
+
if (text2 !== void 0)
|
|
1176
|
+
return { root, manifest: object(JSON.parse(text2)), portable: false };
|
|
1157
1177
|
}
|
|
1158
1178
|
return void 0;
|
|
1159
1179
|
}
|
|
@@ -1182,7 +1202,7 @@ async function resolvePlugin(home, id, config) {
|
|
|
1182
1202
|
}
|
|
1183
1203
|
function expandPlugin(value, root, home) {
|
|
1184
1204
|
if (typeof value === "string")
|
|
1185
|
-
return value.replaceAll("${CLAUDE_PLUGIN_ROOT}", root).replaceAll("${CODEX_PLUGIN_ROOT}", root).replaceAll("${CODEX_HOME}", home);
|
|
1205
|
+
return value.replaceAll("${PLUGIN_ROOT}", root).replaceAll("${CLAUDE_PLUGIN_ROOT}", root).replaceAll("${CODEX_PLUGIN_ROOT}", root).replaceAll("${CODEX_HOME}", home);
|
|
1186
1206
|
if (Array.isArray(value)) return value.map((v) => expandPlugin(v, root, home));
|
|
1187
1207
|
if (value && typeof value === "object")
|
|
1188
1208
|
return Object.fromEntries(
|
|
@@ -1190,6 +1210,22 @@ function expandPlugin(value, root, home) {
|
|
|
1190
1210
|
);
|
|
1191
1211
|
return value;
|
|
1192
1212
|
}
|
|
1213
|
+
function restrictMcpFallback(entry, policy) {
|
|
1214
|
+
if (policy.enabled === false) return void 0;
|
|
1215
|
+
const requested = Array.isArray(policy.enabled_tools) ? strings(policy.enabled_tools) : void 0;
|
|
1216
|
+
const enabledTools = requested && entry.enabledTools ? entry.enabledTools.filter((name2) => requested.includes(name2)) : requested ?? entry.enabledTools;
|
|
1217
|
+
return {
|
|
1218
|
+
...entry,
|
|
1219
|
+
...enabledTools ? { enabledTools } : {},
|
|
1220
|
+
disabledTools: [
|
|
1221
|
+
.../* @__PURE__ */ new Set([
|
|
1222
|
+
...entry.disabledTools,
|
|
1223
|
+
...strings(policy.disabled_tools),
|
|
1224
|
+
...Object.entries(object(policy.tools)).filter(([, tool]) => object(tool).enabled === false).map(([name2]) => name2)
|
|
1225
|
+
])
|
|
1226
|
+
]
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1193
1229
|
async function discoverCodexSnapshot(home, options, context = {}) {
|
|
1194
1230
|
const config = await readCodexConfig(
|
|
1195
1231
|
home,
|
|
@@ -1207,24 +1243,38 @@ async function discoverCodexSnapshot(home, options, context = {}) {
|
|
|
1207
1243
|
items: []
|
|
1208
1244
|
};
|
|
1209
1245
|
const names = /* @__PURE__ */ new Set();
|
|
1210
|
-
const addServers = (servers, source, base, plugin = "") => {
|
|
1211
|
-
for (const [name2,
|
|
1246
|
+
const addServers = (servers, source, base, plugin = "", policies = {}) => {
|
|
1247
|
+
for (const [name2, declaration] of Object.entries(servers)) {
|
|
1248
|
+
const policy = Object.fromEntries(
|
|
1249
|
+
Object.entries(object(policies[name2])).filter(
|
|
1250
|
+
([key]) => ["enabled", "enabled_tools", "disabled_tools", "tools"].includes(key)
|
|
1251
|
+
)
|
|
1252
|
+
);
|
|
1253
|
+
const raw = mergeConfig(object(declaration), policy);
|
|
1212
1254
|
const publicName = plugin ? `${plugin}-${name2}` : name2;
|
|
1213
1255
|
if (object(raw).enabled === false || options.mcpAllowlist.length && !options.mcpAllowlist.includes(name2)) {
|
|
1214
1256
|
result.items.push({ name: publicName, kind: "mcp", state: "disabled" });
|
|
1215
1257
|
continue;
|
|
1216
1258
|
}
|
|
1217
1259
|
try {
|
|
1218
|
-
const value = normalizeCodexMcp(
|
|
1260
|
+
const value = normalizeCodexMcp(
|
|
1261
|
+
publicName,
|
|
1262
|
+
raw,
|
|
1263
|
+
source,
|
|
1264
|
+
base,
|
|
1265
|
+
process.env,
|
|
1266
|
+
context.cwd ?? base
|
|
1267
|
+
);
|
|
1219
1268
|
if (!value) continue;
|
|
1220
1269
|
if (names.has(value.serverName)) throw new Error("namespace-conflict");
|
|
1221
1270
|
names.add(value.serverName);
|
|
1222
|
-
result.mcp.push(value);
|
|
1271
|
+
result.mcp.push({ ...value, originalName: name2 });
|
|
1223
1272
|
} catch (error) {
|
|
1224
1273
|
const previous = context.previous?.mcp.find(
|
|
1225
1274
|
(entry) => entry.serverName === serverName(publicName)
|
|
1226
1275
|
);
|
|
1227
|
-
|
|
1276
|
+
const retained = previous && restrictMcpFallback(previous, raw);
|
|
1277
|
+
if (retained) result.mcp.push(retained);
|
|
1228
1278
|
const reason = [
|
|
1229
1279
|
"missing-environment",
|
|
1230
1280
|
"unsupported-transport",
|
|
@@ -1258,9 +1308,9 @@ async function discoverCodexSnapshot(home, options, context = {}) {
|
|
|
1258
1308
|
});
|
|
1259
1309
|
continue;
|
|
1260
1310
|
}
|
|
1261
|
-
const { root, manifest } = plugin;
|
|
1311
|
+
const { root, manifest, portable } = plugin;
|
|
1262
1312
|
const name2 = String(manifest.name ?? id.split("@")[0]);
|
|
1263
|
-
const declared = manifest.skills ?? "./skills";
|
|
1313
|
+
const declared = portable ? "./skills" : manifest.skills ?? "./skills";
|
|
1264
1314
|
const skillRoots = [];
|
|
1265
1315
|
for (const entry of Array.isArray(declared) ? declared : [declared]) {
|
|
1266
1316
|
if (typeof entry === "string")
|
|
@@ -1271,7 +1321,10 @@ async function discoverCodexSnapshot(home, options, context = {}) {
|
|
|
1271
1321
|
});
|
|
1272
1322
|
}
|
|
1273
1323
|
let servers = manifest.mcpServers;
|
|
1274
|
-
if (
|
|
1324
|
+
if (portable) {
|
|
1325
|
+
const text = await optionalText(join2(root, "mcp.json"));
|
|
1326
|
+
servers = text === void 0 ? void 0 : JSON.parse(text);
|
|
1327
|
+
} else if (typeof servers === "string") {
|
|
1275
1328
|
const text = await optionalText(resolve(root, servers));
|
|
1276
1329
|
if (text === void 0) throw new Error("missing-plugin-mcp-file");
|
|
1277
1330
|
servers = JSON.parse(text);
|
|
@@ -1285,7 +1338,13 @@ async function discoverCodexSnapshot(home, options, context = {}) {
|
|
|
1285
1338
|
home
|
|
1286
1339
|
);
|
|
1287
1340
|
result.skillRoots.push(...skillRoots);
|
|
1288
|
-
addServers(
|
|
1341
|
+
addServers(
|
|
1342
|
+
object(expanded),
|
|
1343
|
+
`plugin:${id}`,
|
|
1344
|
+
root,
|
|
1345
|
+
name2,
|
|
1346
|
+
object(object(setting).mcp_servers)
|
|
1347
|
+
);
|
|
1289
1348
|
result.items.push({ name: id, kind: "plugin", state: "loaded" });
|
|
1290
1349
|
for (const field of ["hooks", "apps", "commands"]) {
|
|
1291
1350
|
if (manifest[field])
|
|
@@ -1297,11 +1356,16 @@ async function discoverCodexSnapshot(home, options, context = {}) {
|
|
|
1297
1356
|
});
|
|
1298
1357
|
}
|
|
1299
1358
|
} catch {
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
)
|
|
1304
|
-
|
|
1359
|
+
const policies = object(object(setting).mcp_servers);
|
|
1360
|
+
for (const entry of context.previous?.mcp ?? []) {
|
|
1361
|
+
if (entry.source !== `plugin:${id}`) continue;
|
|
1362
|
+
if (!entry.originalName && Object.keys(policies).length) continue;
|
|
1363
|
+
const retained = restrictMcpFallback(
|
|
1364
|
+
entry,
|
|
1365
|
+
object(policies[entry.originalName ?? ""])
|
|
1366
|
+
);
|
|
1367
|
+
if (retained) result.mcp.push(retained);
|
|
1368
|
+
}
|
|
1305
1369
|
result.skillRoots.push(
|
|
1306
1370
|
...context.previous?.skillRoots.filter(
|
|
1307
1371
|
(entry) => entry.pluginId === id
|
|
@@ -1548,6 +1612,49 @@ function installCodexInstructions(ctx, home) {
|
|
|
1548
1612
|
);
|
|
1549
1613
|
}
|
|
1550
1614
|
|
|
1615
|
+
// src/codex-mcp-metadata.ts
|
|
1616
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1617
|
+
import { Client } from "@modelcontextprotocol/client";
|
|
1618
|
+
var scope = new AsyncLocalStorage();
|
|
1619
|
+
var users = 0;
|
|
1620
|
+
var restore;
|
|
1621
|
+
function installCodexMcpMetadata() {
|
|
1622
|
+
if (users++ === 0) {
|
|
1623
|
+
const original = Client.prototype.callTool;
|
|
1624
|
+
const callTool = function(params, options) {
|
|
1625
|
+
const call = scope.getStore();
|
|
1626
|
+
const metadata = call?.active() ? call.metadata : void 0;
|
|
1627
|
+
return original.call(
|
|
1628
|
+
this,
|
|
1629
|
+
metadata ? {
|
|
1630
|
+
...params,
|
|
1631
|
+
_meta: { ...params._meta, "x-codex-turn-metadata": metadata }
|
|
1632
|
+
} : params,
|
|
1633
|
+
options
|
|
1634
|
+
);
|
|
1635
|
+
};
|
|
1636
|
+
Client.prototype.callTool = callTool;
|
|
1637
|
+
restore = () => {
|
|
1638
|
+
if (Client.prototype.callTool === callTool)
|
|
1639
|
+
Client.prototype.callTool = original;
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
let active = true;
|
|
1643
|
+
return {
|
|
1644
|
+
run(metadata, operation) {
|
|
1645
|
+
return scope.run({ active: () => active, metadata }, operation);
|
|
1646
|
+
},
|
|
1647
|
+
dispose() {
|
|
1648
|
+
if (!active) return;
|
|
1649
|
+
active = false;
|
|
1650
|
+
if (--users === 0) {
|
|
1651
|
+
restore?.();
|
|
1652
|
+
restore = void 0;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1551
1658
|
// src/codex.ts
|
|
1552
1659
|
var fingerprint = (v) => createHash5("sha256").update(JSON.stringify(v)).digest("hex");
|
|
1553
1660
|
function publicToolName(server, name2) {
|
|
@@ -1629,6 +1736,7 @@ function installCodexCompatibility(ctx, config) {
|
|
|
1629
1736
|
dispose: async () => {
|
|
1630
1737
|
}
|
|
1631
1738
|
};
|
|
1739
|
+
const metadata = config.importMcpServers ? installCodexMcpMetadata() : void 0;
|
|
1632
1740
|
const scopedSnapshot = async (cwd, signal, fallback) => {
|
|
1633
1741
|
const key = resolve3(cwd);
|
|
1634
1742
|
const previous = scopedSnapshots.get(key) ?? fallback ?? latest;
|
|
@@ -1841,7 +1949,22 @@ function installCodexCompatibility(ctx, config) {
|
|
|
1841
1949
|
if (enabledTools && !enabledTools.includes(definition.name) || disabledTools.includes(definition.name))
|
|
1842
1950
|
return () => {
|
|
1843
1951
|
};
|
|
1844
|
-
return target.register(
|
|
1952
|
+
return target.register({
|
|
1953
|
+
...definition,
|
|
1954
|
+
execute(args, execution) {
|
|
1955
|
+
const agent = execution.agent ?? owner.agent;
|
|
1956
|
+
const boundary = agent.session ? child.agents.turnBoundaryOf?.(agent.session) : void 0;
|
|
1957
|
+
const turn = boundary?.openTurnStartSeq;
|
|
1958
|
+
const turnMetadata = typeof agent.id === "string" && agent.id && turn != null ? {
|
|
1959
|
+
session_id: agent.id,
|
|
1960
|
+
turn_id: `${agent.id}:${turn}`
|
|
1961
|
+
} : void 0;
|
|
1962
|
+
return metadata.run(
|
|
1963
|
+
turnMetadata,
|
|
1964
|
+
() => definition.execute(args, execution)
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
});
|
|
1845
1968
|
};
|
|
1846
1969
|
const value = Reflect.get(target, key);
|
|
1847
1970
|
return typeof value === "function" ? value.bind(target) : value;
|
|
@@ -1984,9 +2107,15 @@ function installCodexCompatibility(ctx, config) {
|
|
|
1984
2107
|
agents.delete(agent);
|
|
1985
2108
|
await owner.tail.catch(() => {
|
|
1986
2109
|
});
|
|
1987
|
-
await Promise.
|
|
1988
|
-
[...owner.clients.values()].map((client) => client.fiber.dispose())
|
|
2110
|
+
const results = await Promise.allSettled(
|
|
2111
|
+
[...owner.clients.values()].map(async (client) => client.fiber.dispose())
|
|
1989
2112
|
);
|
|
2113
|
+
const errors = results.flatMap(
|
|
2114
|
+
(result) => result.status === "rejected" ? [result.reason] : []
|
|
2115
|
+
);
|
|
2116
|
+
if (errors.length === 1) throw errors[0];
|
|
2117
|
+
if (errors.length > 1)
|
|
2118
|
+
throw new AggregateError(errors, "Codex MCP client cleanup failed");
|
|
1990
2119
|
};
|
|
1991
2120
|
const reconcile = async () => {
|
|
1992
2121
|
if (closed) return;
|
|
@@ -2082,11 +2211,25 @@ function installCodexCompatibility(ctx, config) {
|
|
|
2082
2211
|
const dispose = () => stopping ??= (async () => {
|
|
2083
2212
|
closed = true;
|
|
2084
2213
|
if (timer) clearInterval(timer);
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2214
|
+
const errors = [];
|
|
2215
|
+
const settle = async (actions) => {
|
|
2216
|
+
const results = await Promise.allSettled(
|
|
2217
|
+
actions.map(async (action) => action())
|
|
2218
|
+
);
|
|
2219
|
+
for (const result of results)
|
|
2220
|
+
if (result.status === "rejected") errors.push(result.reason);
|
|
2221
|
+
};
|
|
2222
|
+
await settle(injections.map((fiber) => () => fiber.dispose()));
|
|
2223
|
+
await settle([() => tail]);
|
|
2224
|
+
await settle([...agents.keys()].map((agent) => () => disposeAgent(agent)));
|
|
2225
|
+
await settle([
|
|
2226
|
+
() => metadata?.dispose(),
|
|
2227
|
+
() => skillDispose?.(),
|
|
2228
|
+
...skills.map((provider) => () => provider.dispose())
|
|
2229
|
+
]);
|
|
2230
|
+
if (errors.length === 1) throw errors[0];
|
|
2231
|
+
if (errors.length > 1)
|
|
2232
|
+
throw new AggregateError(errors, "Codex compatibility cleanup failed");
|
|
2090
2233
|
})();
|
|
2091
2234
|
ctx.effect(() => dispose, "codex-compatibility");
|
|
2092
2235
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-gpt-adapter",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -122,7 +122,8 @@
|
|
|
122
122
|
"react-dom": "18.3.1",
|
|
123
123
|
"typescript": "^5.8.2",
|
|
124
124
|
"vitest": "^3.0.8",
|
|
125
|
-
"yaml": "^2.8.1"
|
|
125
|
+
"yaml": "^2.8.1",
|
|
126
|
+
"@modelcontextprotocol/client": "2.2.0"
|
|
126
127
|
},
|
|
127
128
|
"peerDependencies": {
|
|
128
129
|
"@deepseek-ai/cordis": "4.1.0",
|
|
@@ -141,7 +142,8 @@
|
|
|
141
142
|
"@deepseek-ai/dsh-typert-protocol": "0.2.2-rc.1",
|
|
142
143
|
"@deepseek-ai/dsh-util-values": "0.2.2-rc.1",
|
|
143
144
|
"@deepseek-ai/schemastery": "3.19.0",
|
|
144
|
-
"@earendil-works/pi-ai": "1.3.0"
|
|
145
|
+
"@earendil-works/pi-ai": "1.3.0",
|
|
146
|
+
"@modelcontextprotocol/client": "2.2.0"
|
|
145
147
|
},
|
|
146
148
|
"dependencies": {
|
|
147
149
|
"smol-toml": "1.9.1"
|