aws-runtime-bridge 1.7.45 → 1.7.46

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ai-sources.d.ts","sourceRoot":"","sources":["../../src/routes/ai-sources.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,eAAe,4CAAW,CAAC"}
1
+ {"version":3,"file":"ai-sources.d.ts","sourceRoot":"","sources":["../../src/routes/ai-sources.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,eAAe,4CAAW,CAAC"}
@@ -1,9 +1,11 @@
1
- import { Router } from 'express';
2
1
  import axios from 'axios';
2
+ import { Router } from 'express';
3
3
  import { validateToken } from '../middleware/auth.js';
4
+ import { applyCurrentAiConfigToInstalledTools } from '../services/ai-source-apply.js';
4
5
  import { getAiConfigService } from '../services/cc-switch-sdk.js';
5
- import { buildToolSpecificAiConfig, } from '@cc-switch/sdk';
6
+ import { detectToolStatuses } from '../services/tool-installer.js';
6
7
  import { createLogger } from '../utils/logger.js';
8
+ import { ensureEnabledTools } from '../utils/validation.js';
7
9
  const log = createLogger('ai-sources');
8
10
  export const aiSourcesRouter = Router();
9
11
  function normalizeListedModels(payload) {
@@ -57,48 +59,30 @@ async function fetchRemoteModelList(baseUrl, apiKey) {
57
59
  return normalizeListedModels(anthropicResponse.data);
58
60
  }
59
61
  aiSourcesRouter.post('/cc-switch/apply-ai-sources', validateToken, async (req, res) => {
60
- const { agentId, template, claudecodeTemplateId, codexTemplateId, opencodeTemplateId, claudecode, codex, opencode, } = req.body || {};
62
+ const { agentId, template, ccSwitchEnabledTools, claudecodeTemplateId, codexTemplateId, opencodeTemplateId, claudecode, codex, opencode, } = req.body || {};
61
63
  if (!agentId) {
62
64
  res.status(400).json({ error: 'agentId is required' });
63
65
  return;
64
66
  }
65
67
  try {
66
68
  const aiConfigService = getAiConfigService();
67
- const generatedConfig = template ? buildToolSpecificAiConfig({
68
- baseUrl: String(template?.baseUrl || ''),
69
- apiKey: String(template?.apiKey || ''),
70
- model: String(template?.model || ''),
71
- models: Array.isArray(template?.models)
72
- ? template.models.map((item) => String(item || '').trim()).filter(Boolean)
73
- : undefined,
74
- }) : null;
75
- const claudeSource = generatedConfig?.claudecode || claudecode;
76
- const codexSource = generatedConfig?.codex || codex;
77
- const opencodeSource = generatedConfig?.opencode || opencode;
78
- let mergedClaude = null;
79
- let mergedCodex = null;
80
- let mergedOpencode = null;
81
- if (claudeSource && typeof claudeSource === 'object') {
82
- mergedClaude = await aiConfigService.applyClaudeCodeConfig(claudeSource);
83
- }
84
- if (codexSource && typeof codexSource === 'object') {
85
- mergedCodex = await aiConfigService.applyCodexConfig(codexSource);
86
- }
87
- if (opencodeSource && typeof opencodeSource === 'object') {
88
- mergedOpencode = await aiConfigService.applyOpenCodeConfig(opencodeSource);
89
- }
69
+ const tools = ensureEnabledTools(ccSwitchEnabledTools);
70
+ const toolStatus = await detectToolStatuses(tools);
71
+ const appliedSummary = await applyCurrentAiConfigToInstalledTools({
72
+ template: template,
73
+ requestedSources: { claudecode, codex, opencode },
74
+ toolStatus,
75
+ aiConfigService,
76
+ });
90
77
  res.json({
91
78
  ok: true,
92
79
  agentId: String(agentId),
93
80
  claudecodeTemplateId: String(claudecodeTemplateId || ''),
94
81
  codexTemplateId: String(codexTemplateId || ''),
95
82
  opencodeTemplateId: String(opencodeTemplateId || ''),
96
- generatedFromUnifiedTemplate: Boolean(generatedConfig),
97
- applied: {
98
- claudecode: claudeSource && typeof claudeSource === 'object' ? Object.keys(claudeSource) : [],
99
- codex: codexSource && typeof codexSource === 'object' ? Object.keys(codexSource) : [],
100
- opencode: opencodeSource && typeof opencodeSource === 'object' ? Object.keys(opencodeSource) : [],
101
- },
83
+ generatedFromUnifiedTemplate: appliedSummary.generatedFromUnifiedTemplate,
84
+ applied: appliedSummary.applied,
85
+ skippedTools: appliedSummary.skippedTools,
102
86
  });
103
87
  }
104
88
  catch (error) {
@@ -0,0 +1,43 @@
1
+ import { type ClaudeCodeConfig, type CodexConfig, type OpenCodeConfig } from '@cc-switch/sdk';
2
+ import type { ToolInstallStatus } from '../types.js';
3
+ export interface AiSourceTemplateInput {
4
+ baseUrl?: unknown;
5
+ apiKey?: unknown;
6
+ model?: unknown;
7
+ models?: unknown;
8
+ }
9
+ export interface RequestedAiSources {
10
+ claudecode?: unknown;
11
+ codex?: unknown;
12
+ opencode?: unknown;
13
+ }
14
+ export interface AiConfigApplier {
15
+ applyClaudeCodeConfig(config: ClaudeCodeConfig): Promise<ClaudeCodeConfig>;
16
+ applyCodexConfig(config: CodexConfig): Promise<CodexConfig>;
17
+ applyOpenCodeConfig(config: OpenCodeConfig): Promise<OpenCodeConfig>;
18
+ }
19
+ export interface ApplyCurrentAiConfigOptions {
20
+ template: AiSourceTemplateInput | null | undefined;
21
+ requestedSources?: RequestedAiSources;
22
+ toolStatus: Record<string, ToolInstallStatus>;
23
+ aiConfigService: AiConfigApplier;
24
+ }
25
+ export interface AppliedAiConfigSummary {
26
+ generatedFromUnifiedTemplate: boolean;
27
+ applied: {
28
+ claudecode: string[];
29
+ codex: string[];
30
+ opencode: string[];
31
+ };
32
+ skippedTools: string[];
33
+ merged: {
34
+ claudecode: ClaudeCodeConfig | null;
35
+ codex: CodexConfig | null;
36
+ opencode: OpenCodeConfig | null;
37
+ };
38
+ }
39
+ /**
40
+ * 主流程:用当前统一 AI 配置覆盖各 Agent CLI 配置,并跳过未安装 CLI。
41
+ */
42
+ export declare function applyCurrentAiConfigToInstalledTools(options: ApplyCurrentAiConfigOptions): Promise<AppliedAiConfigSummary>;
43
+ //# sourceMappingURL=ai-source-apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-source-apply.d.ts","sourceRoot":"","sources":["../../src/services/ai-source-apply.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3E,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5D,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,CAAC;IACnD,gBAAgB,CAAC,EAAE,kBAAkB,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC9C,eAAe,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,4BAA4B,EAAE,OAAO,CAAC;IACtC,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE;QACN,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;QACpC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;KACjC,CAAC;CACH;AAqBD;;GAEG;AACH,wBAAsB,oCAAoC,CACxD,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAsCjC"}
@@ -0,0 +1,54 @@
1
+ import { buildToolSpecificAiConfig, } from '@cc-switch/sdk';
2
+ function normalizeTemplate(template) {
3
+ return {
4
+ baseUrl: String(template?.baseUrl || ''),
5
+ apiKey: String(template?.apiKey || ''),
6
+ model: String(template?.model || ''),
7
+ models: Array.isArray(template?.models)
8
+ ? template.models.map((item) => String(item || '').trim()).filter(Boolean)
9
+ : undefined,
10
+ };
11
+ }
12
+ function isInstalled(toolStatus, tool) {
13
+ return Boolean(toolStatus[tool]?.installed);
14
+ }
15
+ function isClaudeCodeInstalled(toolStatus) {
16
+ return isInstalled(toolStatus, 'claude') || isInstalled(toolStatus, 'claudecode');
17
+ }
18
+ /**
19
+ * 主流程:用当前统一 AI 配置覆盖各 Agent CLI 配置,并跳过未安装 CLI。
20
+ */
21
+ export async function applyCurrentAiConfigToInstalledTools(options) {
22
+ const generatedConfig = options.template
23
+ ? buildToolSpecificAiConfig(normalizeTemplate(options.template))
24
+ : null;
25
+ const skippedTools = Object.values(options.toolStatus)
26
+ .filter((item) => !item.installed)
27
+ .map((item) => item.tool);
28
+ let mergedClaude = null;
29
+ let mergedCodex = null;
30
+ let mergedOpencode = null;
31
+ if (generatedConfig?.claudecode && isClaudeCodeInstalled(options.toolStatus)) {
32
+ mergedClaude = await options.aiConfigService.applyClaudeCodeConfig(generatedConfig.claudecode);
33
+ }
34
+ if (generatedConfig?.codex && isInstalled(options.toolStatus, 'codex')) {
35
+ mergedCodex = await options.aiConfigService.applyCodexConfig(generatedConfig.codex);
36
+ }
37
+ if (generatedConfig?.opencode && isInstalled(options.toolStatus, 'opencode')) {
38
+ mergedOpencode = await options.aiConfigService.applyOpenCodeConfig(generatedConfig.opencode);
39
+ }
40
+ return {
41
+ generatedFromUnifiedTemplate: Boolean(generatedConfig),
42
+ applied: {
43
+ claudecode: generatedConfig?.claudecode && mergedClaude ? Object.keys(generatedConfig.claudecode) : [],
44
+ codex: generatedConfig?.codex && mergedCodex ? Object.keys(generatedConfig.codex) : [],
45
+ opencode: generatedConfig?.opencode && mergedOpencode ? Object.keys(generatedConfig.opencode) : [],
46
+ },
47
+ skippedTools,
48
+ merged: {
49
+ claudecode: mergedClaude,
50
+ codex: mergedCodex,
51
+ opencode: mergedOpencode,
52
+ },
53
+ };
54
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ai-source-apply.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-source-apply.test.d.ts","sourceRoot":"","sources":["../../src/services/ai-source-apply.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ function status(tool, installed) {
3
+ return {
4
+ tool,
5
+ installed,
6
+ executable: installed ? tool : null,
7
+ version: installed ? '1.0.0' : null,
8
+ installing: false,
9
+ error: installed ? null : 'not installed',
10
+ };
11
+ }
12
+ describe('applyCurrentAiConfigToInstalledTools', () => {
13
+ it('forces current unified AI config and skips uninstalled tools', async () => {
14
+ const applyClaudeCodeConfig = vi.fn(async (config) => config);
15
+ const applyCodexConfig = vi.fn(async (config) => config);
16
+ const applyOpenCodeConfig = vi.fn(async (config) => config);
17
+ const { applyCurrentAiConfigToInstalledTools } = await import('./ai-source-apply.js');
18
+ const result = await applyCurrentAiConfigToInstalledTools({
19
+ template: {
20
+ baseUrl: 'https://current.example.com',
21
+ apiKey: 'sk-current',
22
+ model: 'gpt-current',
23
+ },
24
+ requestedSources: {
25
+ claudecode: { env: { ANTHROPIC_BASE_URL: 'https://stale.example.com' } },
26
+ codex: { model: 'stale-codex' },
27
+ opencode: { provider: { default: { options: { baseURL: 'https://stale.example.com/v1' } } } },
28
+ },
29
+ toolStatus: {
30
+ claude: status('claude', true),
31
+ codex: status('codex', false),
32
+ opencode: status('opencode', true),
33
+ },
34
+ aiConfigService: {
35
+ applyClaudeCodeConfig,
36
+ applyCodexConfig,
37
+ applyOpenCodeConfig,
38
+ },
39
+ });
40
+ expect(applyClaudeCodeConfig).toHaveBeenCalledWith({
41
+ env: {
42
+ ANTHROPIC_BASE_URL: 'https://current.example.com',
43
+ ANTHROPIC_AUTH_TOKEN: 'sk-current',
44
+ ANTHROPIC_MODEL: 'gpt-current',
45
+ },
46
+ });
47
+ expect(applyCodexConfig).not.toHaveBeenCalled();
48
+ expect(applyOpenCodeConfig).toHaveBeenCalledWith(expect.objectContaining({
49
+ provider: expect.objectContaining({
50
+ default: expect.objectContaining({
51
+ options: expect.objectContaining({
52
+ baseURL: 'https://current.example.com/v1',
53
+ apiKey: 'sk-current',
54
+ }),
55
+ }),
56
+ }),
57
+ }));
58
+ expect(result.applied.codex).toEqual([]);
59
+ expect(result.skippedTools).toEqual(['codex']);
60
+ });
61
+ it('applies Claude Code config when the installed tool status uses the claudecode alias', async () => {
62
+ const applyClaudeCodeConfig = vi.fn(async (config) => config);
63
+ const { applyCurrentAiConfigToInstalledTools } = await import('./ai-source-apply.js');
64
+ await applyCurrentAiConfigToInstalledTools({
65
+ template: {
66
+ baseUrl: 'https://current.example.com',
67
+ apiKey: 'sk-current',
68
+ model: 'gpt-current',
69
+ },
70
+ toolStatus: {
71
+ claudecode: status('claudecode', true),
72
+ },
73
+ aiConfigService: {
74
+ applyClaudeCodeConfig,
75
+ applyCodexConfig: vi.fn(async (config) => config),
76
+ applyOpenCodeConfig: vi.fn(async (config) => config),
77
+ },
78
+ });
79
+ expect(applyClaudeCodeConfig).toHaveBeenCalledOnce();
80
+ });
81
+ });
@@ -1,4 +1,4 @@
1
- import { CcSwitchSdk } from '@cc-switch/sdk';
1
+ import type { CcSwitchSdk } from '@cc-switch/sdk';
2
2
  import type { AdapterResult, InitLogEntry, LegacyMcpStateItem, LegacySkillStateItem, McpPayload, SkillPackage, SyncStateResult } from '../types.js';
3
3
  export declare function syncLegacyStateFromSdk(agentId: string, sdk: CcSwitchSdk): Promise<SyncStateResult>;
4
4
  export declare function initInstance(agentId: string, workspacePath: string, options: {
@@ -1 +1 @@
1
- {"version":3,"file":"instance-init-service.d.ts","sourceRoot":"","sources":["../../src/services/instance-init-service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAK7C,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,UAAU,EAAS,YAAY,EAAE,eAAe,EAAqB,MAAM,aAAa,CAAC;AAK9K,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,eAAe,CAAC,CAyB1B;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE;IACP,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;CAC3B,GACA,OAAO,CAAC;IACT,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE;QACL,OAAO,EAAE,OAAO,CAAC;QACjB,cAAc,EAAE,OAAO,CAAC;QACxB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,oBAAoB,EAAE,CAAC;KACpC,CAAC;IACF,GAAG,EAAE;QACH,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,oBAAoB,EAAE,CAAC;QAC/B,UAAU,EAAE,kBAAkB,EAAE,CAAC;KAClC,CAAC;CACH,CAAC,CA+MD"}
1
+ {"version":3,"file":"instance-init-service.d.ts","sourceRoot":"","sources":["../../src/services/instance-init-service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,UAAU,EAAS,YAAY,EAAE,eAAe,EAAqB,MAAM,aAAa,CAAC;AAsB9K,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,eAAe,CAAC,CAyB1B;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE;IACP,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;CAC3B,GACA,OAAO,CAAC;IACT,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE;QACL,OAAO,EAAE,OAAO,CAAC;QACjB,cAAc,EAAE,OAAO,CAAC;QACxB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,oBAAoB,EAAE,CAAC;KACpC,CAAC;IACF,GAAG,EAAE;QACH,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,oBAAoB,EAAE,CAAC;QAC/B,UAAU,EAAE,kBAAkB,EAAE,CAAC;KAClC,CAAC;CACH,CAAC,CA4MD"}
@@ -1,12 +1,18 @@
1
- import path from 'node:path';
2
1
  import { promises as fs } from 'node:fs';
3
- import { getAgentInitRoot, loadInstanceState, saveInstanceState } from './instance-state.js';
4
- import { loadCcSwitchSdk, runCcSwitchAdapter, toSdkAppsFromEnabledTools } from './cc-switch-sdk.js';
2
+ import path from 'node:path';
5
3
  import { ensureDir, installSkillPackageToSdk } from '../utils/file-utils.js';
6
4
  import { buildMcpConfigContent, normalizeSdkMcpServerSpec, toLegacyMcpStateItem, toLegacySkillStateItem } from '../utils/mcp-utils.js';
7
- import { ensureEnabledTools, parseBoolean } from '../utils/validation.js';
8
5
  import { normalizeMcpPayload } from '../utils/mcp-utils.js';
9
- import { detectToolStatuses, ensureToolsInstalled } from './tool-installer.js';
6
+ import { ensureEnabledTools, parseBoolean } from '../utils/validation.js';
7
+ import { loadCcSwitchSdk, runCcSwitchAdapter, toSdkAppsFromEnabledTools } from './cc-switch-sdk.js';
8
+ import { getAgentInitRoot, loadInstanceState, saveInstanceState } from './instance-state.js';
9
+ import { detectToolStatuses } from './tool-installer.js';
10
+ function pickInstalledTools(requestedTools, toolStatus) {
11
+ const installed = new Set(Object.values(toolStatus)
12
+ .filter((item) => item.installed)
13
+ .map((item) => item.tool));
14
+ return requestedTools.filter((tool) => installed.has(tool));
15
+ }
10
16
  export async function syncLegacyStateFromSdk(agentId, sdk) {
11
17
  const state = await loadInstanceState(agentId);
12
18
  const mcpServers = sdk.getAllMcpServers().map((item) => toLegacyMcpStateItem(item));
@@ -51,12 +57,14 @@ export async function initInstance(agentId, workspacePath, options) {
51
57
  installedTools: Object.values(detectedToolStatus).filter((item) => item.installed).map((item) => item.tool),
52
58
  missingTools: Object.values(detectedToolStatus).filter((item) => !item.installed).map((item) => item.tool),
53
59
  });
60
+ const installedTools = pickInstalledTools(tools, detectedToolStatus);
61
+ const missingTools = Object.values(detectedToolStatus).filter((item) => !item.installed);
54
62
  const initResult = {
55
63
  ok: true,
56
64
  agentId: String(agentId),
57
65
  workspacePath: normalizedWorkspace,
58
66
  initRoot,
59
- enabledTools: tools,
67
+ enabledTools: installedTools,
60
68
  toolStatus: detectedToolStatus,
61
69
  logs,
62
70
  skill: {
@@ -69,23 +77,17 @@ export async function initInstance(agentId, workspacePath, options) {
69
77
  },
70
78
  };
71
79
  const state = await loadInstanceState(agentId);
72
- pushLog('install-tools', 'running', '开始自动安装缺失工具', { tools });
73
- const ensuredToolStatus = await ensureToolsInstalled(tools);
74
- initResult.toolStatus = ensuredToolStatus;
75
- const failedTools = Object.values(ensuredToolStatus).filter((item) => !item.installed);
76
- if (failedTools.length > 0) {
77
- pushLog('install-tools', 'error', '部分工具自动安装失败', {
78
- failedTools: failedTools.map((item) => ({ tool: item.tool, error: item.error })),
79
- });
80
- }
81
- else {
82
- pushLog('install-tools', 'success', '工具安装步骤完成', {
83
- installedTools: Object.values(ensuredToolStatus).map((item) => ({ tool: item.tool, version: item.version })),
80
+ if (missingTools.length > 0) {
81
+ pushLog('skip-uninstalled-tools', 'skipped', '已跳过未安装工具', {
82
+ skippedTools: missingTools.map((item) => ({ tool: item.tool, error: item.error })),
84
83
  });
85
84
  }
85
+ pushLog('select-installed-tools', 'success', '已选择可初始化工具', {
86
+ installedTools,
87
+ });
86
88
  pushLog('load-sdk', 'running', '开始加载 CC Switch SDK');
87
89
  const sdk = await loadCcSwitchSdk(agentId);
88
- const sdkApps = toSdkAppsFromEnabledTools(tools);
90
+ const sdkApps = toSdkAppsFromEnabledTools(installedTools);
89
91
  pushLog('load-sdk', 'success', 'CC Switch SDK 加载完成', { sdkApps });
90
92
  if (initResult.skill.enabled) {
91
93
  pushLog('install-skills', 'running', '开始安装技能包', {
@@ -125,7 +127,7 @@ export async function initInstance(agentId, workspacePath, options) {
125
127
  });
126
128
  const normalizedMcpServers = (Array.isArray(options.mcpServers) ? options.mcpServers : [])
127
129
  .map((item) => normalizeMcpPayload(item))
128
- .filter((item) => item != null);
130
+ .filter((item) => item !== null);
129
131
  const appliedMcp = [];
130
132
  for (const item of normalizedMcpServers) {
131
133
  const allMcps = sdk.getAllMcpServers();
@@ -152,8 +154,8 @@ export async function initInstance(agentId, workspacePath, options) {
152
154
  }
153
155
  pushLog('persist-state', 'running', '开始保存实例状态');
154
156
  state.initialized = true;
155
- state.enabledTools = tools;
156
- state.toolStatus = ensuredToolStatus;
157
+ state.enabledTools = installedTools;
158
+ state.toolStatus = detectedToolStatus;
157
159
  state.skillEnabled = initResult.skill.enabled;
158
160
  state.mcpEnabled = initResult.mcp.enabled;
159
161
  await saveInstanceState(agentId, state);
@@ -178,7 +180,7 @@ export async function initInstance(agentId, workspacePath, options) {
178
180
  pushLog('adapter', 'success', '适配器回写完成', {
179
181
  snapshot: adapterResult.snapshot,
180
182
  });
181
- pushLog('complete', failedTools.length > 0 ? 'error' : 'success', failedTools.length > 0 ? '实例初始化完成,但存在工具安装失败项' : '实例初始化完成');
183
+ pushLog('complete', 'success', missingTools.length > 0 ? '实例初始化完成,未安装工具已跳过' : '实例初始化完成');
182
184
  return {
183
185
  ...initResult,
184
186
  adapterResult,
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=instance-init-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-init-service.test.d.ts","sourceRoot":"","sources":["../../src/services/instance-init-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,139 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ const detectToolStatusesMock = vi.fn();
6
+ const ensureToolsInstalledMock = vi.fn();
7
+ const installSkillPackageToSdkMock = vi.fn();
8
+ const runCcSwitchAdapterMock = vi.fn();
9
+ vi.mock('./tool-installer.js', () => ({
10
+ detectToolStatuses: detectToolStatusesMock,
11
+ ensureToolsInstalled: ensureToolsInstalledMock,
12
+ }));
13
+ vi.mock('./cc-switch-sdk.js', () => ({
14
+ loadCcSwitchSdk: vi.fn(async () => activeSdk),
15
+ runCcSwitchAdapter: runCcSwitchAdapterMock,
16
+ toSdkAppsFromEnabledTools: (enabledTools) => {
17
+ const tools = Array.isArray(enabledTools) ? enabledTools : [];
18
+ return {
19
+ claude: false,
20
+ claudecode: tools.includes('claude') || tools.includes('claudecode'),
21
+ codex: tools.includes('codex'),
22
+ gemini: tools.includes('gemini'),
23
+ opencode: tools.includes('opencode'),
24
+ openclaw: tools.includes('openclaw'),
25
+ };
26
+ },
27
+ }));
28
+ vi.mock('../utils/file-utils.js', async (importOriginal) => {
29
+ const original = await importOriginal();
30
+ return {
31
+ ...original,
32
+ installSkillPackageToSdk: installSkillPackageToSdkMock,
33
+ };
34
+ });
35
+ class MockSdk {
36
+ constructor() {
37
+ this.createdMcpInputs = [];
38
+ this.installedSkills = [];
39
+ this.mcpServers = [];
40
+ }
41
+ getAllSkills() {
42
+ return this.installedSkills;
43
+ }
44
+ getAllMcpServers() {
45
+ return this.mcpServers;
46
+ }
47
+ async createMcpServer(input) {
48
+ this.createdMcpInputs.push({ name: input.name, apps: input.apps });
49
+ const server = {
50
+ id: `mcp-${this.mcpServers.length + 1}`,
51
+ name: input.name,
52
+ server: input.server,
53
+ apps: input.apps,
54
+ };
55
+ this.mcpServers.push(server);
56
+ return server;
57
+ }
58
+ async updateMcpServer(id, input) {
59
+ const existing = this.mcpServers.find((server) => server.id === id);
60
+ if (!existing) {
61
+ throw new Error(`missing mcp ${id}`);
62
+ }
63
+ if (input.apps) {
64
+ existing.apps = input.apps;
65
+ }
66
+ if (input.server) {
67
+ existing.server = input.server;
68
+ }
69
+ return existing;
70
+ }
71
+ }
72
+ let activeSdk;
73
+ function status(tool, installed) {
74
+ return {
75
+ tool,
76
+ installed,
77
+ executable: installed ? tool : null,
78
+ version: installed ? '1.0.0' : null,
79
+ installing: false,
80
+ error: installed ? null : 'not installed',
81
+ };
82
+ }
83
+ describe('initInstance installed-only tool initialization', () => {
84
+ const originalRuntimeHomeDir = process.env.AWS_RUNTIME_HOME_DIR;
85
+ let runtimeHomeDir;
86
+ beforeEach(async () => {
87
+ runtimeHomeDir = path.join(os.tmpdir(), `aws-init-installed-only-${Date.now()}`);
88
+ process.env.AWS_RUNTIME_HOME_DIR = runtimeHomeDir;
89
+ activeSdk = new MockSdk();
90
+ vi.clearAllMocks();
91
+ runCcSwitchAdapterMock.mockResolvedValue({ ok: true, snapshot: { skills: 0, mcpServers: 0 } });
92
+ installSkillPackageToSdkMock.mockImplementation(async (_sdk, skillPackage, _skillDir, apps) => {
93
+ const installed = {
94
+ id: 'skill-1',
95
+ name: String(skillPackage.skillName || 'demo-skill'),
96
+ directory: '/demo-skill',
97
+ apps,
98
+ installedAt: Date.now(),
99
+ };
100
+ activeSdk.installedSkills.push(installed);
101
+ return installed;
102
+ });
103
+ });
104
+ afterEach(async () => {
105
+ if (originalRuntimeHomeDir === undefined) {
106
+ delete process.env.AWS_RUNTIME_HOME_DIR;
107
+ }
108
+ else {
109
+ process.env.AWS_RUNTIME_HOME_DIR = originalRuntimeHomeDir;
110
+ }
111
+ await fs.rm(runtimeHomeDir, { recursive: true, force: true });
112
+ });
113
+ it('skips uninstalled tools and configures MCP and skills only for installed tools', async () => {
114
+ detectToolStatusesMock.mockResolvedValue({
115
+ claude: status('claude', true),
116
+ opencode: status('opencode', false),
117
+ codex: status('codex', false),
118
+ });
119
+ const { initInstance } = await import('./instance-init-service.js');
120
+ const result = await initInstance('agent-installed-only', runtimeHomeDir, {
121
+ ccSwitchEnabledTools: ['claude', 'opencode', 'codex'],
122
+ skillPackages: [{ skillName: 'demo-skill', downloadUrl: 'https://example.com/skill.zip' }],
123
+ mcpServers: [{ name: 'aws-mcp', command: 'node', args: ['server.js'] }],
124
+ });
125
+ expect(ensureToolsInstalledMock).not.toHaveBeenCalled();
126
+ expect(result.enabledTools).toEqual(['claude']);
127
+ expect(result.ok).toBe(true);
128
+ expect(installSkillPackageToSdkMock).toHaveBeenCalledWith(activeSdk, expect.objectContaining({ skillName: 'demo-skill' }), expect.any(String), expect.objectContaining({ claudecode: true, opencode: false, codex: false }));
129
+ expect(activeSdk.createdMcpInputs).toEqual([
130
+ {
131
+ name: 'aws-mcp',
132
+ apps: expect.objectContaining({ claudecode: true, opencode: false, codex: false }),
133
+ },
134
+ ]);
135
+ const statePath = path.join(runtimeHomeDir, '.aws-bridge', 'instances', 'agent-installed-only', 'state.json');
136
+ const state = JSON.parse(await fs.readFile(statePath, 'utf-8'));
137
+ expect(state.enabledTools).toEqual(['claude']);
138
+ });
139
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.7.45",
3
+ "version": "1.7.46",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",