aitoearn-dsh-plugin-test 0.0.1 → 0.0.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 CHANGED
@@ -16,12 +16,17 @@ Generated artifacts live only under `dist/` (gitignored).
16
16
  ## Build
17
17
 
18
18
  ```bash
19
- pnpm nx build agent-plugin # dsh distribution → dist/tools/agent-plugin/dsh
20
- pnpm nx pack agent-plugin # workbuddy distribution → dist/tools/agent-plugin/workbuddy
19
+ pnpm nx build agent-plugin # dsh distribution → dist/tools/agent-plugin/dsh
20
+ pnpm nx pack agent-plugin # workbuddy distribution (prod) → dist/tools/agent-plugin/workbuddy
21
+ pnpm nx run agent-plugin:pack:dev # workbuddy distribution (dev) → dist/tools/agent-plugin/workbuddy-dev
21
22
  pnpm nx test agent-plugin
22
23
  pnpm nx lint agent-plugin
23
24
  ```
24
25
 
26
+ WorkBuddy MCP connectors are packaged per environment: prod packs
27
+ `aitoearn` (aitoearn.ai) + `aitoearn-cn` (aitoearn.cn); dev packs
28
+ `aitoearn-dev` (dev endpoint).
29
+
25
30
  ## Install (DeepSeek Harness)
26
31
 
27
32
  ```bash
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "aitoearn-dsh-plugin-test",
3
3
  "type": "module",
4
- "version": "0.0.1",
5
- "description": "[TEST] Aitoearn agent preset, skills, and MCP tools for DeepSeek Harness",
4
+ "version": "0.0.3",
5
+ "description": "Aitoearn agent preset, skills, and MCP tools for DeepSeek Harness",
6
6
  "exports": {
7
7
  ".": {
8
8
  "types": "./src/dsh/index.d.ts",
@@ -135,4 +135,4 @@
135
135
  "zustand": "~4.4.7"
136
136
  },
137
137
  "module": "./src/dsh/index.js"
138
- }
138
+ }
@@ -102,45 +102,105 @@ async function apply(ctx, config = {}) {
102
102
  let current = () => config;
103
103
  let restoreNames;
104
104
  let fiber;
105
- const unmount = () => {
105
+ let generation = 0;
106
+ let mountChain = Promise.resolve();
107
+ let remountTimer;
108
+ let mountedFingerprint;
109
+ const unmountAsync = async () => {
106
110
  restoreNames?.();
107
111
  restoreNames = void 0;
108
- void fiber?.dispose();
112
+ const previous = fiber;
109
113
  fiber = void 0;
114
+ if (previous) {
115
+ try {
116
+ await previous.dispose();
117
+ } catch (error) {
118
+ ctx.logger.warn(error, "aitoearn-mcp: failed to dispose previous MCP client");
119
+ }
120
+ }
110
121
  };
111
- const mount = async () => {
112
- unmount();
122
+ const doMount = async (gen) => {
113
123
  const auth = resolveAuth(current());
114
124
  if (auth.apiKey.length === 0 || auth.mcpUrl.length === 0) {
125
+ await unmountAsync();
126
+ mountedFingerprint = void 0;
115
127
  ctx.logger.warn("aitoearn-mcp: skip mounting; API key or MCP URL is not configured yet");
116
128
  return;
117
129
  }
130
+ const fingerprint = `${auth.mcpUrl}
131
+ ${auth.apiKey}`;
132
+ if (fingerprint === mountedFingerprint && fiber) {
133
+ return;
134
+ }
135
+ await unmountAsync();
136
+ if (gen !== generation) {
137
+ return;
138
+ }
118
139
  restoreNames = patchRawToolNames(ctx.tools, MCP_SERVER_NAME, (publicName) => {
119
140
  ctx.logger.info(`kept qualified MCP tool name ${publicName}`);
120
141
  });
121
- const mcpClient = runtimeRequire("@deepseek-ai/dsh-mcp-client");
122
- const mcpConfig = {
123
- serverName: MCP_SERVER_NAME,
124
- transport: "streamable-http",
125
- url: auth.mcpUrl,
126
- headers: authorizationHeader(auth.apiKey),
127
- failOnStartupError: false,
128
- toolCallTimeoutMs: 12e4
129
- };
130
- fiber = ctx.plugin(mcpClient, mcpConfig);
131
- await fiber;
142
+ try {
143
+ const mcpClient = runtimeRequire("@deepseek-ai/dsh-mcp-client");
144
+ const mcpConfig = {
145
+ serverName: MCP_SERVER_NAME,
146
+ transport: "streamable-http",
147
+ url: auth.mcpUrl,
148
+ headers: authorizationHeader(auth.apiKey),
149
+ failOnStartupError: false,
150
+ toolCallTimeoutMs: 12e4
151
+ };
152
+ const next = ctx.plugin(mcpClient, mcpConfig);
153
+ fiber = next;
154
+ await next;
155
+ } catch (error) {
156
+ restoreNames?.();
157
+ restoreNames = void 0;
158
+ fiber = void 0;
159
+ ctx.logger.warn(error, "aitoearn-mcp: mount failed; will retry on next settings change");
160
+ return;
161
+ }
162
+ if (gen !== generation) {
163
+ await unmountAsync();
164
+ mountedFingerprint = void 0;
165
+ return;
166
+ }
167
+ mountedFingerprint = fingerprint;
132
168
  ctx.logger.info(`aitoearn-mcp: mounted MCP tools from ${auth.mcpUrl}`);
133
169
  };
170
+ const scheduleMount = () => {
171
+ generation += 1;
172
+ const gen = generation;
173
+ mountChain = mountChain.then(() => doMount(gen)).catch((error) => {
174
+ ctx.logger.warn(error, "aitoearn-mcp: mount failed");
175
+ });
176
+ return mountChain;
177
+ };
178
+ const scheduleRemountSoon = () => {
179
+ if (remountTimer) {
180
+ clearTimeout(remountTimer);
181
+ }
182
+ remountTimer = setTimeout(() => {
183
+ remountTimer = void 0;
184
+ void scheduleMount();
185
+ }, 50);
186
+ };
134
187
  ctx.inject(["settings"], (scoped) => {
135
188
  current = () => pluginConfigFromSettings(scoped.settings.get(SETTINGS_NAMESPACE), config);
136
189
  ctx.on("settings/document-updated", (namespace) => {
137
- if (String(namespace) === SETTINGS_NAMESPACE)
138
- void mount();
190
+ if (String(namespace) === SETTINGS_NAMESPACE) {
191
+ scheduleRemountSoon();
192
+ }
139
193
  });
140
- void mount();
194
+ void scheduleMount();
141
195
  });
142
- ctx.effect(() => unmount, "aitoearn-mcp.client");
143
- await mount();
196
+ ctx.effect(() => () => {
197
+ if (remountTimer) {
198
+ clearTimeout(remountTimer);
199
+ remountTimer = void 0;
200
+ }
201
+ void unmountAsync();
202
+ }, "aitoearn-mcp.client");
203
+ await scheduleMount();
144
204
  }
145
205
  export {
146
206
  apply,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../tools/agent-plugin/src/dsh/mcp.ts", "../../../../../../../tools/agent-plugin/src/dsh/auth.ts", "../../../../../../../tools/agent-plugin/src/dsh/tool-name.ts"],
4
- "sourcesContent": ["import { createRequire } from 'node:module'\nimport { Context, Fiber } from '@deepseek-ai/cordis'\nimport { StreamableHttpConfig } from '@deepseek-ai/dsh-mcp-client'\nimport {} from '@deepseek-ai/dsh-settings'\nimport {} from '@deepseek-ai/dsh-settings/types'\nimport {} from '@deepseek-ai/dsh-tools'\nimport { AitoearnPluginConfig, authorizationHeader, pluginConfigFromSettings, resolveAuth, SETTINGS_NAMESPACE } from './auth.js'\nimport { MCP_SERVER_NAME, patchRawToolNames } from './tool-name.js'\n\nconst runtimeRequire = createRequire(import.meta.url)\n\nexport const name = 'aitoearn-mcp'\nexport const inject = ['tools']\n\nexport async function apply(ctx: Context, config: AitoearnPluginConfig = {}): Promise<void> {\n let current = () => config\n let restoreNames: (() => void) | undefined\n let fiber: Fiber | undefined\n\n const unmount = () => {\n restoreNames?.()\n restoreNames = undefined\n void fiber?.dispose()\n fiber = undefined\n }\n\n const mount = async () => {\n unmount()\n const auth = resolveAuth(current())\n if (auth.apiKey.length === 0 || auth.mcpUrl.length === 0) {\n ctx.logger.warn('aitoearn-mcp: skip mounting; API key or MCP URL is not configured yet')\n return\n }\n restoreNames = patchRawToolNames(ctx.tools, MCP_SERVER_NAME, (publicName) => {\n ctx.logger.info(`kept qualified MCP tool name ${publicName}`)\n })\n const mcpClient: typeof import('@deepseek-ai/dsh-mcp-client') = runtimeRequire('@deepseek-ai/dsh-mcp-client')\n const mcpConfig: StreamableHttpConfig = {\n serverName: MCP_SERVER_NAME,\n transport: 'streamable-http',\n url: auth.mcpUrl,\n headers: authorizationHeader(auth.apiKey),\n failOnStartupError: false,\n toolCallTimeoutMs: 120000,\n }\n fiber = ctx.plugin(mcpClient, mcpConfig)\n await fiber\n ctx.logger.info(`aitoearn-mcp: mounted MCP tools from ${auth.mcpUrl}`)\n }\n\n ctx.inject(['settings'], (scoped) => {\n current = () => pluginConfigFromSettings(scoped.settings.get(SETTINGS_NAMESPACE), config)\n ctx.on('settings/document-updated', (namespace) => {\n if (String(namespace) === SETTINGS_NAMESPACE)\n void mount()\n })\n // The initial apply-time mount may run before the settings section is\n // readable; mount again once it is, so a stored key takes effect without\n // requiring a manual settings save.\n void mount()\n })\n\n ctx.effect(() => unmount, 'aitoearn-mcp.client')\n await mount()\n}\n", "export const MCP_PATH = '/api/ai/agent/mcp'\nexport const API_KEY_ENV = 'AITOEARN_API_KEY'\nexport const DEFAULT_ENVIRONMENT = 'global'\nexport const SETTINGS_NAMESPACE = 'aitoearn'\n\nexport const ENVIRONMENT_ORIGINS = {\n global: 'https://aitoearn.ai',\n cn: 'https://aitoearn.cn',\n} as const\n\nexport const DEFAULT_MCP_URL = `${ENVIRONMENT_ORIGINS.global}${MCP_PATH}`\n\nexport type AitoearnEnvironment = 'global' | 'cn' | 'custom'\n\nexport interface AitoearnAuth {\n mcpUrl: string\n apiKey: string\n}\n\nexport interface AitoearnPluginConfig {\n environment?: string\n baseUrl?: string\n apiKey?: string\n}\n\nexport function pluginConfigFromSettings(\n section: unknown,\n fallback: AitoearnPluginConfig = {},\n): AitoearnPluginConfig {\n if (section === null || typeof section !== 'object' || Array.isArray(section))\n return fallback\n const record = section as Record<string, unknown>\n return {\n ...fallback,\n ...(typeof record['environment'] === 'string' ? { environment: record['environment'] } : {}),\n ...(typeof record['baseUrl'] === 'string' ? { baseUrl: record['baseUrl'] } : {}),\n ...(typeof record['apiKey'] === 'string' ? { apiKey: record['apiKey'] } : {}),\n }\n}\n\nexport function resolveEnvironment(value: string | undefined): AitoearnEnvironment {\n if (value === 'cn' || value === 'custom' || value === 'global')\n return value\n return DEFAULT_ENVIRONMENT\n}\n\nexport function resolveOrigin(environment: AitoearnEnvironment, baseUrl?: string): string {\n if (environment !== 'custom')\n return ENVIRONMENT_ORIGINS[environment]\n const trimmed = baseUrl?.trim() ?? ''\n if (trimmed.length === 0)\n return ''\n try {\n const url = new URL(trimmed)\n if (url.protocol !== 'http:' && url.protocol !== 'https:')\n return ''\n return url.origin\n }\n catch {\n return ''\n }\n}\n\nexport function resolveMcpUrl(config: AitoearnPluginConfig): string {\n const environment = resolveEnvironment(config.environment)\n const origin = resolveOrigin(environment, config.baseUrl)\n if (origin.length === 0)\n return ''\n return `${origin}${MCP_PATH}`\n}\n\nexport function resolveAuth(\n config: AitoearnPluginConfig,\n env: NodeJS.ProcessEnv = process.env,\n): AitoearnAuth {\n return {\n mcpUrl: resolveMcpUrl(config),\n apiKey: config.apiKey?.trim() || env[API_KEY_ENV]?.trim() || '',\n }\n}\n\nexport function authorizationHeader(apiKey: string): Record<string, string> {\n if (apiKey.length === 0)\n return {}\n return { Authorization: `Bearer ${apiKey}` }\n}\n", "import { ToolRuntime } from '@deepseek-ai/dsh-tools'\n\nexport const MCP_SERVER_NAME = 'aitoearn'\n\nconst HASHED_PUBLIC_NAME = /_[0-9a-f]{12}$/\n\nexport interface RawPublicName {\n modelName: string\n stripped: boolean\n}\n\nexport function qualifiedPrefix(serverName = MCP_SERVER_NAME): string {\n return `mcp__${serverName}__`\n}\n\nexport function toRawPublicName(publicName: string, serverName = MCP_SERVER_NAME): RawPublicName {\n const prefix = qualifiedPrefix(serverName)\n if (!publicName.startsWith(prefix))\n return { modelName: publicName, stripped: false }\n if (publicName.length === 64 && HASHED_PUBLIC_NAME.test(publicName))\n return { modelName: publicName, stripped: false }\n const raw = publicName.slice(prefix.length)\n if (raw.length === 0)\n return { modelName: publicName, stripped: false }\n return { modelName: raw, stripped: true }\n}\n\nexport function patchRawToolNames(\n tools: Pick<ToolRuntime, 'register'>,\n serverName = MCP_SERVER_NAME,\n onKeepQualified?: (publicName: string) => void,\n): () => void {\n const original = tools.register.bind(tools)\n tools.register = (definition) => {\n const next = toRawPublicName(definition.name, serverName)\n if (!next.stripped) {\n if (definition.name.startsWith(qualifiedPrefix(serverName)))\n onKeepQualified?.(definition.name)\n return original(definition)\n }\n return original({ ...definition, name: next.modelName })\n }\n return () => {\n tools.register = original\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createRequire } from 'node:module'\nimport { Context, Fiber } from '@deepseek-ai/cordis'\nimport { StreamableHttpConfig } from '@deepseek-ai/dsh-mcp-client'\nimport {} from '@deepseek-ai/dsh-settings'\nimport {} from '@deepseek-ai/dsh-settings/types'\nimport {} from '@deepseek-ai/dsh-tools'\nimport { AitoearnPluginConfig, authorizationHeader, pluginConfigFromSettings, resolveAuth, SETTINGS_NAMESPACE } from './auth.js'\nimport { MCP_SERVER_NAME, patchRawToolNames } from './tool-name.js'\n\nconst runtimeRequire = createRequire(import.meta.url)\n\nexport const name = 'aitoearn-mcp'\nexport const inject = ['tools']\n\nexport async function apply(ctx: Context, config: AitoearnPluginConfig = {}): Promise<void> {\n let current = () => config\n let restoreNames: (() => void) | undefined\n let fiber: Fiber | undefined\n let generation = 0\n let mountChain: Promise<void> = Promise.resolve()\n let remountTimer: ReturnType<typeof setTimeout> | undefined\n let mountedFingerprint: string | undefined\n\n const unmountAsync = async () => {\n restoreNames?.()\n restoreNames = undefined\n const previous = fiber\n fiber = undefined\n if (previous) {\n try {\n await previous.dispose()\n }\n catch (error) {\n ctx.logger.warn(error, 'aitoearn-mcp: failed to dispose previous MCP client')\n }\n }\n }\n\n const doMount = async (gen: number) => {\n const auth = resolveAuth(current())\n if (auth.apiKey.length === 0 || auth.mcpUrl.length === 0) {\n await unmountAsync()\n mountedFingerprint = undefined\n ctx.logger.warn('aitoearn-mcp: skip mounting; API key or MCP URL is not configured yet')\n return\n }\n const fingerprint = `${auth.mcpUrl}\\n${auth.apiKey}`\n if (fingerprint === mountedFingerprint && fiber) {\n return\n }\n await unmountAsync()\n if (gen !== generation) {\n return\n }\n restoreNames = patchRawToolNames(ctx.tools, MCP_SERVER_NAME, (publicName) => {\n ctx.logger.info(`kept qualified MCP tool name ${publicName}`)\n })\n try {\n const mcpClient: typeof import('@deepseek-ai/dsh-mcp-client') = runtimeRequire('@deepseek-ai/dsh-mcp-client')\n const mcpConfig: StreamableHttpConfig = {\n serverName: MCP_SERVER_NAME,\n transport: 'streamable-http',\n url: auth.mcpUrl,\n headers: authorizationHeader(auth.apiKey),\n failOnStartupError: false,\n toolCallTimeoutMs: 120000,\n }\n const next = ctx.plugin(mcpClient, mcpConfig)\n fiber = next\n await next\n }\n catch (error) {\n restoreNames?.()\n restoreNames = undefined\n fiber = undefined\n ctx.logger.warn(error, 'aitoearn-mcp: mount failed; will retry on next settings change')\n return\n }\n if (gen !== generation) {\n await unmountAsync()\n mountedFingerprint = undefined\n return\n }\n mountedFingerprint = fingerprint\n ctx.logger.info(`aitoearn-mcp: mounted MCP tools from ${auth.mcpUrl}`)\n }\n\n const scheduleMount = () => {\n generation += 1\n const gen = generation\n mountChain = mountChain\n .then(() => doMount(gen))\n .catch((error: Error) => {\n ctx.logger.warn(error, 'aitoearn-mcp: mount failed')\n })\n return mountChain\n }\n\n const scheduleRemountSoon = () => {\n if (remountTimer) {\n clearTimeout(remountTimer)\n }\n remountTimer = setTimeout(() => {\n remountTimer = undefined\n void scheduleMount()\n }, 50)\n }\n\n ctx.inject(['settings'], (scoped) => {\n current = () => pluginConfigFromSettings(scoped.settings.get(SETTINGS_NAMESPACE), config)\n ctx.on('settings/document-updated', (namespace) => {\n if (String(namespace) === SETTINGS_NAMESPACE) {\n scheduleRemountSoon()\n }\n })\n // The initial apply-time mount may run before the settings section is\n // readable; mount again once it is, so a stored key takes effect without\n // requiring a manual settings save.\n void scheduleMount()\n })\n\n ctx.effect(() => () => {\n if (remountTimer) {\n clearTimeout(remountTimer)\n remountTimer = undefined\n }\n void unmountAsync()\n }, 'aitoearn-mcp.client')\n await scheduleMount()\n}\n", "export const MCP_PATH = '/api/ai/agent/mcp'\nexport const API_KEY_ENV = 'AITOEARN_API_KEY'\nexport const DEFAULT_ENVIRONMENT = 'global'\nexport const SETTINGS_NAMESPACE = 'aitoearn'\n\nexport const ENVIRONMENT_ORIGINS = {\n global: 'https://aitoearn.ai',\n cn: 'https://aitoearn.cn',\n} as const\n\nexport const DEFAULT_MCP_URL = `${ENVIRONMENT_ORIGINS.global}${MCP_PATH}`\n\nexport type AitoearnEnvironment = 'global' | 'cn' | 'custom'\n\nexport interface AitoearnAuth {\n mcpUrl: string\n apiKey: string\n}\n\nexport interface AitoearnPluginConfig {\n environment?: string\n baseUrl?: string\n apiKey?: string\n}\n\nexport function pluginConfigFromSettings(\n section: unknown,\n fallback: AitoearnPluginConfig = {},\n): AitoearnPluginConfig {\n if (section === null || typeof section !== 'object' || Array.isArray(section))\n return fallback\n const record = section as Record<string, unknown>\n return {\n ...fallback,\n ...(typeof record['environment'] === 'string' ? { environment: record['environment'] } : {}),\n ...(typeof record['baseUrl'] === 'string' ? { baseUrl: record['baseUrl'] } : {}),\n ...(typeof record['apiKey'] === 'string' ? { apiKey: record['apiKey'] } : {}),\n }\n}\n\nexport function resolveEnvironment(value: string | undefined): AitoearnEnvironment {\n if (value === 'cn' || value === 'custom' || value === 'global')\n return value\n return DEFAULT_ENVIRONMENT\n}\n\nexport function resolveOrigin(environment: AitoearnEnvironment, baseUrl?: string): string {\n if (environment !== 'custom')\n return ENVIRONMENT_ORIGINS[environment]\n const trimmed = baseUrl?.trim() ?? ''\n if (trimmed.length === 0)\n return ''\n try {\n const url = new URL(trimmed)\n if (url.protocol !== 'http:' && url.protocol !== 'https:')\n return ''\n return url.origin\n }\n catch {\n return ''\n }\n}\n\nexport function resolveMcpUrl(config: AitoearnPluginConfig): string {\n const environment = resolveEnvironment(config.environment)\n const origin = resolveOrigin(environment, config.baseUrl)\n if (origin.length === 0)\n return ''\n return `${origin}${MCP_PATH}`\n}\n\nexport function resolveAuth(\n config: AitoearnPluginConfig,\n env: NodeJS.ProcessEnv = process.env,\n): AitoearnAuth {\n return {\n mcpUrl: resolveMcpUrl(config),\n apiKey: config.apiKey?.trim() || env[API_KEY_ENV]?.trim() || '',\n }\n}\n\nexport function authorizationHeader(apiKey: string): Record<string, string> {\n if (apiKey.length === 0)\n return {}\n return { Authorization: `Bearer ${apiKey}` }\n}\n", "import { ToolRuntime } from '@deepseek-ai/dsh-tools'\n\nexport const MCP_SERVER_NAME = 'aitoearn'\n\nconst HASHED_PUBLIC_NAME = /_[0-9a-f]{12}$/\n\nexport interface RawPublicName {\n modelName: string\n stripped: boolean\n}\n\nexport function qualifiedPrefix(serverName = MCP_SERVER_NAME): string {\n return `mcp__${serverName}__`\n}\n\nexport function toRawPublicName(publicName: string, serverName = MCP_SERVER_NAME): RawPublicName {\n const prefix = qualifiedPrefix(serverName)\n if (!publicName.startsWith(prefix))\n return { modelName: publicName, stripped: false }\n if (publicName.length === 64 && HASHED_PUBLIC_NAME.test(publicName))\n return { modelName: publicName, stripped: false }\n const raw = publicName.slice(prefix.length)\n if (raw.length === 0)\n return { modelName: publicName, stripped: false }\n return { modelName: raw, stripped: true }\n}\n\nexport function patchRawToolNames(\n tools: Pick<ToolRuntime, 'register'>,\n serverName = MCP_SERVER_NAME,\n onKeepQualified?: (publicName: string) => void,\n): () => void {\n const original = tools.register.bind(tools)\n tools.register = (definition) => {\n const next = toRawPublicName(definition.name, serverName)\n if (!next.stripped) {\n if (definition.name.startsWith(qualifiedPrefix(serverName)))\n onKeepQualified?.(definition.name)\n return original(definition)\n }\n return original({ ...definition, name: next.modelName })\n }\n return () => {\n tools.register = original\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/src/dsh/mcp.js CHANGED
@@ -8,47 +8,110 @@ export async function apply(ctx, config = {}) {
8
8
  let current = () => config;
9
9
  let restoreNames;
10
10
  let fiber;
11
- const unmount = () => {
11
+ let generation = 0;
12
+ let mountChain = Promise.resolve();
13
+ let remountTimer;
14
+ let mountedFingerprint;
15
+ const unmountAsync = async () => {
12
16
  restoreNames?.();
13
17
  restoreNames = undefined;
14
- void fiber?.dispose();
18
+ const previous = fiber;
15
19
  fiber = undefined;
20
+ if (previous) {
21
+ try {
22
+ await previous.dispose();
23
+ }
24
+ catch (error) {
25
+ ctx.logger.warn(error, 'aitoearn-mcp: failed to dispose previous MCP client');
26
+ }
27
+ }
16
28
  };
17
- const mount = async () => {
18
- unmount();
29
+ const doMount = async (gen) => {
19
30
  const auth = resolveAuth(current());
20
31
  if (auth.apiKey.length === 0 || auth.mcpUrl.length === 0) {
32
+ await unmountAsync();
33
+ mountedFingerprint = undefined;
21
34
  ctx.logger.warn('aitoearn-mcp: skip mounting; API key or MCP URL is not configured yet');
22
35
  return;
23
36
  }
37
+ const fingerprint = `${auth.mcpUrl}\n${auth.apiKey}`;
38
+ if (fingerprint === mountedFingerprint && fiber) {
39
+ return;
40
+ }
41
+ await unmountAsync();
42
+ if (gen !== generation) {
43
+ return;
44
+ }
24
45
  restoreNames = patchRawToolNames(ctx.tools, MCP_SERVER_NAME, (publicName) => {
25
46
  ctx.logger.info(`kept qualified MCP tool name ${publicName}`);
26
47
  });
27
- const mcpClient = runtimeRequire('@deepseek-ai/dsh-mcp-client');
28
- const mcpConfig = {
29
- serverName: MCP_SERVER_NAME,
30
- transport: 'streamable-http',
31
- url: auth.mcpUrl,
32
- headers: authorizationHeader(auth.apiKey),
33
- failOnStartupError: false,
34
- toolCallTimeoutMs: 120000,
35
- };
36
- fiber = ctx.plugin(mcpClient, mcpConfig);
37
- await fiber;
48
+ try {
49
+ const mcpClient = runtimeRequire('@deepseek-ai/dsh-mcp-client');
50
+ const mcpConfig = {
51
+ serverName: MCP_SERVER_NAME,
52
+ transport: 'streamable-http',
53
+ url: auth.mcpUrl,
54
+ headers: authorizationHeader(auth.apiKey),
55
+ failOnStartupError: false,
56
+ toolCallTimeoutMs: 120000,
57
+ };
58
+ const next = ctx.plugin(mcpClient, mcpConfig);
59
+ fiber = next;
60
+ await next;
61
+ }
62
+ catch (error) {
63
+ restoreNames?.();
64
+ restoreNames = undefined;
65
+ fiber = undefined;
66
+ ctx.logger.warn(error, 'aitoearn-mcp: mount failed; will retry on next settings change');
67
+ return;
68
+ }
69
+ if (gen !== generation) {
70
+ await unmountAsync();
71
+ mountedFingerprint = undefined;
72
+ return;
73
+ }
74
+ mountedFingerprint = fingerprint;
38
75
  ctx.logger.info(`aitoearn-mcp: mounted MCP tools from ${auth.mcpUrl}`);
39
76
  };
77
+ const scheduleMount = () => {
78
+ generation += 1;
79
+ const gen = generation;
80
+ mountChain = mountChain
81
+ .then(() => doMount(gen))
82
+ .catch((error) => {
83
+ ctx.logger.warn(error, 'aitoearn-mcp: mount failed');
84
+ });
85
+ return mountChain;
86
+ };
87
+ const scheduleRemountSoon = () => {
88
+ if (remountTimer) {
89
+ clearTimeout(remountTimer);
90
+ }
91
+ remountTimer = setTimeout(() => {
92
+ remountTimer = undefined;
93
+ void scheduleMount();
94
+ }, 50);
95
+ };
40
96
  ctx.inject(['settings'], (scoped) => {
41
97
  current = () => pluginConfigFromSettings(scoped.settings.get(SETTINGS_NAMESPACE), config);
42
98
  ctx.on('settings/document-updated', (namespace) => {
43
- if (String(namespace) === SETTINGS_NAMESPACE)
44
- void mount();
99
+ if (String(namespace) === SETTINGS_NAMESPACE) {
100
+ scheduleRemountSoon();
101
+ }
45
102
  });
46
103
  // The initial apply-time mount may run before the settings section is
47
104
  // readable; mount again once it is, so a stored key takes effect without
48
105
  // requiring a manual settings save.
49
- void mount();
106
+ void scheduleMount();
50
107
  });
51
- ctx.effect(() => unmount, 'aitoearn-mcp.client');
52
- await mount();
108
+ ctx.effect(() => () => {
109
+ if (remountTimer) {
110
+ clearTimeout(remountTimer);
111
+ remountTimer = undefined;
112
+ }
113
+ void unmountAsync();
114
+ }, 'aitoearn-mcp.client');
115
+ await scheduleMount();
53
116
  }
54
117
  //# sourceMappingURL=mcp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../../../../../tools/agent-plugin/src/dsh/mcp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../../../../../tools/agent-plugin/src/dsh/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAM3C,OAAO,EAAwB,mBAAmB,EAAE,wBAAwB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAChI,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEnE,MAAM,cAAc,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAErD,MAAM,CAAC,MAAM,IAAI,GAAG,cAAc,CAAA;AAClC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,CAAA;AAE/B,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,GAAY,EAAE,SAA+B,EAAE;IACzE,IAAI,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAA;IAC1B,IAAI,YAAsC,CAAA;IAC1C,IAAI,KAAwB,CAAA;IAC5B,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,UAAU,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAA;IACjD,IAAI,YAAuD,CAAA;IAC3D,IAAI,kBAAsC,CAAA;IAE1C,MAAM,YAAY,GAAG,KAAK,IAAI,EAAE;QAC9B,YAAY,EAAE,EAAE,CAAA;QAChB,YAAY,GAAG,SAAS,CAAA;QACxB,MAAM,QAAQ,GAAG,KAAK,CAAA;QACtB,KAAK,GAAG,SAAS,CAAA;QACjB,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAA;YAC1B,CAAC;YACD,OAAO,KAAK,EAAE,CAAC;gBACb,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,qDAAqD,CAAC,CAAA;YAC/E,CAAC;QACH,CAAC;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,KAAK,EAAE,GAAW,EAAE,EAAE;QACpC,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;QACnC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzD,MAAM,YAAY,EAAE,CAAA;YACpB,kBAAkB,GAAG,SAAS,CAAA;YAC9B,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,uEAAuE,CAAC,CAAA;YACxF,OAAM;QACR,CAAC;QACD,MAAM,WAAW,GAAG,GAAG,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAA;QACpD,IAAI,WAAW,KAAK,kBAAkB,IAAI,KAAK,EAAE,CAAC;YAChD,OAAM;QACR,CAAC;QACD,MAAM,YAAY,EAAE,CAAA;QACpB,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,OAAM;QACR,CAAC;QACD,YAAY,GAAG,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,eAAe,EAAE,CAAC,UAAU,EAAE,EAAE;YAC1E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,UAAU,EAAE,CAAC,CAAA;QAC/D,CAAC,CAAC,CAAA;QACF,IAAI,CAAC;YACH,MAAM,SAAS,GAAiD,cAAc,CAAC,6BAA6B,CAAC,CAAA;YAC7G,MAAM,SAAS,GAAyB;gBACtC,UAAU,EAAE,eAAe;gBAC3B,SAAS,EAAE,iBAAiB;gBAC5B,GAAG,EAAE,IAAI,CAAC,MAAM;gBAChB,OAAO,EAAE,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC;gBACzC,kBAAkB,EAAE,KAAK;gBACzB,iBAAiB,EAAE,MAAM;aAC1B,CAAA;YACD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;YAC7C,KAAK,GAAG,IAAI,CAAA;YACZ,MAAM,IAAI,CAAA;QACZ,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACb,YAAY,EAAE,EAAE,CAAA;YAChB,YAAY,GAAG,SAAS,CAAA;YACxB,KAAK,GAAG,SAAS,CAAA;YACjB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,gEAAgE,CAAC,CAAA;YACxF,OAAM;QACR,CAAC;QACD,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,MAAM,YAAY,EAAE,CAAA;YACpB,kBAAkB,GAAG,SAAS,CAAA;YAC9B,OAAM;QACR,CAAC;QACD,kBAAkB,GAAG,WAAW,CAAA;QAChC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,wCAAwC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;IACxE,CAAC,CAAA;IAED,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,UAAU,IAAI,CAAC,CAAA;QACf,MAAM,GAAG,GAAG,UAAU,CAAA;QACtB,UAAU,GAAG,UAAU;aACpB,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;aACxB,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;YACtB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,4BAA4B,CAAC,CAAA;QACtD,CAAC,CAAC,CAAA;QACJ,OAAO,UAAU,CAAA;IACnB,CAAC,CAAA;IAED,MAAM,mBAAmB,GAAG,GAAG,EAAE;QAC/B,IAAI,YAAY,EAAE,CAAC;YACjB,YAAY,CAAC,YAAY,CAAC,CAAA;QAC5B,CAAC;QACD,YAAY,GAAG,UAAU,CAAC,GAAG,EAAE;YAC7B,YAAY,GAAG,SAAS,CAAA;YACxB,KAAK,aAAa,EAAE,CAAA;QACtB,CAAC,EAAE,EAAE,CAAC,CAAA;IACR,CAAC,CAAA;IAED,GAAG,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE;QAClC,OAAO,GAAG,GAAG,EAAE,CAAC,wBAAwB,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,CAAA;QACzF,GAAG,CAAC,EAAE,CAAC,2BAA2B,EAAE,CAAC,SAAS,EAAE,EAAE;YAChD,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,kBAAkB,EAAE,CAAC;gBAC7C,mBAAmB,EAAE,CAAA;YACvB,CAAC;QACH,CAAC,CAAC,CAAA;QACF,sEAAsE;QACtE,yEAAyE;QACzE,oCAAoC;QACpC,KAAK,aAAa,EAAE,CAAA;IACtB,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE;QACpB,IAAI,YAAY,EAAE,CAAC;YACjB,YAAY,CAAC,YAAY,CAAC,CAAA;YAC1B,YAAY,GAAG,SAAS,CAAA;QAC1B,CAAC;QACD,KAAK,YAAY,EAAE,CAAA;IACrB,CAAC,EAAE,qBAAqB,CAAC,CAAA;IACzB,MAAM,aAAa,EAAE,CAAA;AACvB,CAAC"}
@@ -8,11 +8,14 @@ const MCP_CLIENT_PACKAGE = '@deepseek-ai/dsh-mcp-client';
8
8
  * Make one bare package resolvable by plain Node resolution from the preset
9
9
  * directory. Module resolution from `<dshHome>/.agent-presets/aitoearn` walks
10
10
  * `<dshHome>/node_modules` — which nothing populates — so every bare runtime
11
- * import a bundled preset module makes needs its package linked there. An
12
- * existing real directory at the link path is left untouched.
11
+ * import a bundled preset module makes needs its package linked there. The
12
+ * link target is re-resolved on every sync, so a dangling or stale link (e.g.
13
+ * after the plugin moved or was reinstalled from a different source) heals
14
+ * itself; an existing real directory at the link path is left untouched.
13
15
  */
14
16
  function linkRuntimeDependency(packageName, dshHome) {
15
17
  const link = join(dshHome, 'node_modules', packageName);
18
+ const target = resolvePackageDir(packageName);
16
19
  let existing;
17
20
  try {
18
21
  existing = readlinkSync(link);
@@ -20,23 +23,28 @@ function linkRuntimeDependency(packageName, dshHome) {
20
23
  catch {
21
24
  existing = undefined;
22
25
  }
23
- if (existing !== undefined)
26
+ if (existing === target)
24
27
  return;
25
- if (existsSync(link))
26
- return;
27
- const hostRequire = createRequire(join(packageRoot(), 'package.json'));
28
- const target = nearestPackageDir(hostRequire, packageName);
29
- mkdirSync(dirname(link), { recursive: true });
30
- symlinkSync(target, link, process.platform === 'win32' ? 'junction' : 'dir');
28
+ if (existing !== undefined || !existsSync(link)) {
29
+ mkdirSync(dirname(link), { recursive: true });
30
+ rmSync(link, { force: true });
31
+ symlinkSync(target, link, process.platform === 'win32' ? 'junction' : 'dir');
32
+ }
31
33
  }
32
- /** Locate the installed package directory for one bare specifier. */
33
- function nearestPackageDir(hostRequire, packageName) {
34
- for (const searchPath of hostRequire.resolve.paths(packageName) ?? []) {
35
- const manifest = join(searchPath, packageName, 'package.json');
36
- if (existsSync(manifest))
37
- return dirname(manifest);
34
+ /** Resolve one bare specifier through the plugin's own graph, then walk up from the resolved entry to its package root. */
35
+ function resolvePackageDir(packageName) {
36
+ const hostRequire = createRequire(join(packageRoot(), 'package.json'));
37
+ const entry = hostRequire.resolve(packageName);
38
+ let dir = dirname(entry);
39
+ while (true) {
40
+ if (existsSync(join(dir, 'package.json')))
41
+ return dir;
42
+ const parent = dirname(dir);
43
+ if (parent === dir) {
44
+ throw new Error(`Aitoearn preset: cannot locate the package root of ${JSON.stringify(packageName)} (resolved to ${entry})`);
45
+ }
46
+ dir = parent;
38
47
  }
39
- throw new Error(`Aitoearn preset: cannot resolve installed package ${JSON.stringify(packageName)} for preset linking`);
40
48
  }
41
49
  export function syncAitoearnPreset(sourceDir = bundledPresetDir(), targetDir = userPresetDir(), dshHome = dshHomeDir()) {
42
50
  if (!existsSync(sourceDir)) {
@@ -1 +1 @@
1
- {"version":3,"file":"preset-sync.js","sourceRoot":"","sources":["../../../../../../tools/agent-plugin/src/dsh/preset-sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"preset-sync.js","sourceRoot":"","sources":["../../../../../../tools/agent-plugin/src/dsh/preset-sync.ts"],"names":[],"mappings":"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"}