aws-runtime-bridge 1.7.49 → 1.7.50

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.
Files changed (166) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +77 -0
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -0
  3. package/dist/adapter/AcodeSdkAdapter.js +322 -0
  4. package/dist/adapter/AcodeSdkAdapter.test.d.ts +2 -0
  5. package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +1 -0
  6. package/dist/adapter/AcodeSdkAdapter.test.js +273 -0
  7. package/dist/adapter/AdapterRegistry.d.ts +1 -1
  8. package/dist/adapter/AdapterRegistry.d.ts.map +1 -1
  9. package/dist/adapter/AdapterRegistry.js +2 -0
  10. package/dist/adapter/AdapterRegistry.test.js +10 -1
  11. package/dist/adapter/SdkProviderSpi.d.ts.map +1 -1
  12. package/dist/adapter/SdkProviderSpi.js +10 -1
  13. package/dist/adapter/index.d.ts +1 -0
  14. package/dist/adapter/index.d.ts.map +1 -1
  15. package/dist/adapter/index.js +1 -0
  16. package/dist/adapters/cc-switch/index.d.ts +2 -0
  17. package/dist/adapters/cc-switch/index.d.ts.map +1 -1
  18. package/dist/adapters/cc-switch/index.js +6 -0
  19. package/dist/adapters/cc-switch/mcp-acode.d.ts +17 -0
  20. package/dist/adapters/cc-switch/mcp-acode.d.ts.map +1 -0
  21. package/dist/adapters/cc-switch/mcp-acode.js +71 -0
  22. package/dist/adapters/cc-switch/mcp-acode.test.d.ts +2 -0
  23. package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +1 -0
  24. package/dist/adapters/cc-switch/mcp-acode.test.js +80 -0
  25. package/dist/adapters/cc-switch/mcp-claude.d.ts.map +1 -1
  26. package/dist/adapters/cc-switch/mcp-claude.js +1 -0
  27. package/dist/adapters/cc-switch/mcp-claudecode.d.ts.map +1 -1
  28. package/dist/adapters/cc-switch/mcp-claudecode.js +1 -0
  29. package/dist/adapters/cc-switch/mcp-claudecode.test.js +1 -0
  30. package/dist/adapters/cc-switch/mcp-codex.d.ts.map +1 -1
  31. package/dist/adapters/cc-switch/mcp-codex.js +1 -0
  32. package/dist/adapters/cc-switch/mcp-codex.test.js +1 -0
  33. package/dist/adapters/cc-switch/mcp-opencode.d.ts.map +1 -1
  34. package/dist/adapters/cc-switch/mcp-opencode.js +1 -0
  35. package/dist/adapters/cc-switch/mcp-opencode.test.js +1 -0
  36. package/dist/adapters/cc-switch/skill-acode.d.ts +11 -0
  37. package/dist/adapters/cc-switch/skill-acode.d.ts.map +1 -0
  38. package/dist/adapters/cc-switch/skill-acode.js +12 -0
  39. package/dist/adapters/cc-switch/skill-directory.test.js +1 -0
  40. package/dist/config.d.ts +4 -0
  41. package/dist/config.d.ts.map +1 -1
  42. package/dist/config.js +10 -0
  43. package/dist/index.js +2 -0
  44. package/dist/routes/ai-sources.js +3 -2
  45. package/dist/routes/instance.js +1 -1
  46. package/dist/routes/instance.test.js +2 -1
  47. package/dist/routes/mcp.js +2 -2
  48. package/dist/routes/mcp.test.js +2 -1
  49. package/dist/routes/runtime-binding.d.ts +3 -2
  50. package/dist/routes/runtime-binding.d.ts.map +1 -1
  51. package/dist/routes/runtime-binding.js +9 -56
  52. package/dist/routes/runtime-binding.test.js +4 -80
  53. package/dist/routes/sessions.d.ts +5 -0
  54. package/dist/routes/sessions.d.ts.map +1 -1
  55. package/dist/routes/sessions.js +36 -16
  56. package/dist/routes/sessions.test.js +13 -0
  57. package/dist/routes/skills.js +2 -2
  58. package/dist/routes/skills.test.js +2 -1
  59. package/dist/routes/terminal.d.ts +9 -4
  60. package/dist/routes/terminal.d.ts.map +1 -1
  61. package/dist/routes/terminal.js +26 -8
  62. package/dist/routes/terminal.test.js +24 -8
  63. package/dist/services/acode-package.d.ts +8 -0
  64. package/dist/services/acode-package.d.ts.map +1 -0
  65. package/dist/services/acode-package.js +82 -0
  66. package/dist/services/ai-source-apply.d.ts +4 -0
  67. package/dist/services/ai-source-apply.d.ts.map +1 -1
  68. package/dist/services/ai-source-apply.js +8 -0
  69. package/dist/services/ai-source-apply.test.js +8 -1
  70. package/dist/services/cc-switch-sdk.d.ts.map +1 -1
  71. package/dist/services/cc-switch-sdk.js +9 -1
  72. package/dist/services/instance-init-service.d.ts.map +1 -1
  73. package/dist/services/instance-init-service.js +3 -2
  74. package/dist/services/instance-init-service.test.js +6 -5
  75. package/dist/services/instance-service.d.ts.map +1 -1
  76. package/dist/services/instance-service.js +1 -0
  77. package/dist/services/instance-state.js +1 -1
  78. package/dist/services/instance-state.test.js +4 -4
  79. package/dist/services/tool-installer.d.ts.map +1 -1
  80. package/dist/services/tool-installer.js +10 -0
  81. package/dist/types.d.ts +5 -0
  82. package/dist/types.d.ts.map +1 -1
  83. package/dist/utils/file-utils.d.ts.map +1 -1
  84. package/dist/utils/file-utils.js +1 -0
  85. package/dist/utils/mcp-utils.d.ts.map +1 -1
  86. package/dist/utils/mcp-utils.js +4 -1
  87. package/dist/utils/validation.js +1 -1
  88. package/node_modules/@cc-switch/sdk/dist/constants.d.ts.map +1 -1
  89. package/node_modules/@cc-switch/sdk/dist/constants.js +7 -0
  90. package/node_modules/@cc-switch/sdk/dist/schemas.d.ts.map +1 -1
  91. package/node_modules/@cc-switch/sdk/dist/schemas.js +1 -0
  92. package/node_modules/@cc-switch/sdk/dist/sdk.d.ts.map +1 -1
  93. package/node_modules/@cc-switch/sdk/dist/sdk.js +7 -3
  94. package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts +6 -0
  95. package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts.map +1 -1
  96. package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.js +27 -1
  97. package/node_modules/@cc-switch/sdk/dist/types.d.ts +4 -1
  98. package/node_modules/@cc-switch/sdk/dist/types.d.ts.map +1 -1
  99. package/package/acode/README.md +88 -0
  100. package/package/acode/dist/built-in-file-tools.d.ts +16 -0
  101. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -0
  102. package/package/acode/dist/built-in-file-tools.js +231 -0
  103. package/package/acode/dist/cli.d.ts +3 -0
  104. package/package/acode/dist/cli.d.ts.map +1 -0
  105. package/package/acode/dist/cli.js +37 -0
  106. package/package/acode/dist/commands/commandLoader.d.ts +7 -0
  107. package/package/acode/dist/commands/commandLoader.d.ts.map +1 -0
  108. package/package/acode/dist/commands/commandLoader.js +33 -0
  109. package/package/acode/dist/commands/commandParser.d.ts +7 -0
  110. package/package/acode/dist/commands/commandParser.d.ts.map +1 -0
  111. package/package/acode/dist/commands/commandParser.js +77 -0
  112. package/package/acode/dist/commands/commandRegistry.d.ts +13 -0
  113. package/package/acode/dist/commands/commandRegistry.d.ts.map +1 -0
  114. package/package/acode/dist/commands/commandRegistry.js +30 -0
  115. package/package/acode/dist/config.d.ts +4 -0
  116. package/package/acode/dist/config.d.ts.map +1 -0
  117. package/package/acode/dist/config.js +80 -0
  118. package/package/acode/dist/hooks/hookBus.d.ts +19 -0
  119. package/package/acode/dist/hooks/hookBus.d.ts.map +1 -0
  120. package/package/acode/dist/hooks/hookBus.js +40 -0
  121. package/package/acode/dist/index.d.ts +8 -0
  122. package/package/acode/dist/index.d.ts.map +1 -0
  123. package/package/acode/dist/index.js +7 -0
  124. package/package/acode/dist/runtime.d.ts +34 -0
  125. package/package/acode/dist/runtime.d.ts.map +1 -0
  126. package/package/acode/dist/runtime.js +571 -0
  127. package/package/acode/dist/types.d.ts +99 -0
  128. package/package/acode/dist/types.d.ts.map +1 -0
  129. package/package/acode/dist/types.js +1 -0
  130. package/package/acode/package.json +24 -0
  131. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +4 -4
  132. package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
  133. package/package/aws-client-agent-mcp/dist/agent-client.js +9 -6
  134. package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
  135. package/package/aws-client-agent-mcp/dist/agent-client.test.js +21 -0
  136. package/package/aws-client-agent-mcp/dist/agent-client.test.js.map +1 -1
  137. package/package/aws-client-agent-mcp/dist/http-client.d.ts +1 -17
  138. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
  139. package/package/aws-client-agent-mcp/dist/http-client.js +7 -113
  140. package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
  141. package/package/aws-client-agent-mcp/dist/http-client.test.js +22 -2
  142. package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
  143. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +0 -5
  144. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  145. package/package/aws-client-agent-mcp/dist/mcp-server.js +1 -23
  146. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  147. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +89 -6
  148. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  149. package/package/aws-client-agent-mcp/dist/types.d.ts +3 -0
  150. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  151. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +0 -13
  152. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
  153. package/package/aws-client-agent-mcp/dist/websocket-client.js +3 -100
  154. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
  155. package/package/cc-switch-sdk/dist/constants.d.ts.map +1 -1
  156. package/package/cc-switch-sdk/dist/constants.js +7 -0
  157. package/package/cc-switch-sdk/dist/schemas.d.ts.map +1 -1
  158. package/package/cc-switch-sdk/dist/schemas.js +1 -0
  159. package/package/cc-switch-sdk/dist/sdk.d.ts.map +1 -1
  160. package/package/cc-switch-sdk/dist/sdk.js +7 -3
  161. package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts +6 -0
  162. package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts.map +1 -1
  163. package/package/cc-switch-sdk/dist/services/ai-config-service.js +27 -1
  164. package/package/cc-switch-sdk/dist/types.d.ts +4 -1
  165. package/package/cc-switch-sdk/dist/types.d.ts.map +1 -1
  166. package/package.json +8 -4
@@ -82,7 +82,8 @@ describe('app flags normalization', () => {
82
82
  expect(normalizeAppFlags(null)).toEqual([]);
83
83
  });
84
84
  it('allows supported per-tool toggle app ids', () => {
85
- const isSupportedToggleAppId = (appId) => ['claudecode', 'opencode', 'codex'].includes(appId);
85
+ const isSupportedToggleAppId = (appId) => ['acode', 'claudecode', 'opencode', 'codex'].includes(appId);
86
+ expect(isSupportedToggleAppId('acode')).toBe(true);
86
87
  expect(isSupportedToggleAppId('claudecode')).toBe(true);
87
88
  expect(isSupportedToggleAppId('opencode')).toBe(true);
88
89
  expect(isSupportedToggleAppId('codex')).toBe(true);
@@ -13,8 +13,9 @@ export interface RuntimeTokenRequestResult {
13
13
  error?: string;
14
14
  }
15
15
  /**
16
- * 主流程:读取当前 scoped token -> 必要时串行刷新 -> 锁内重读 token,避免多个 MCP 进程强刷时互相废弃新 token。
16
+ * 主流程:MCP Node token 必须通过 /runtime/binding/claim-agent 从启动绑定领取。
17
+ * 不再返回 bridge/user/server scoped runtime token,避免 MCP Node 降级使用旧 bridge 级凭据。
17
18
  */
18
- export declare function resolveRuntimeTokenRequest(input: RuntimeTokenRequestInput): Promise<RuntimeTokenRequestResult>;
19
+ export declare function resolveRuntimeTokenRequest(input: RuntimeTokenRequestInput): RuntimeTokenRequestResult;
19
20
  export declare function logRuntimeBindingStartupState(): void;
20
21
  //# sourceMappingURL=runtime-binding.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/routes/runtime-binding.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,oBAAoB,4CAAW,CAAC;AAE7C,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqCD;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,yBAAyB,CAAC,CAyCpC;AAqMD,wBAAgB,6BAA6B,IAAI,IAAI,CAapD"}
1
+ {"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/routes/runtime-binding.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,oBAAoB,4CAAW,CAAC;AAE7C,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,wBAAwB,GAC9B,yBAAyB,CAM3B;AAqMD,wBAAgB,6BAA6B,IAAI,IAAI,CAapD"}
@@ -1,67 +1,20 @@
1
1
  import { Router } from "express";
2
2
  import { validateToken } from "../middleware/auth.js";
3
- import { requestRuntimeAccessTokenRefresh, requestRuntimeAccessTokenRefreshForServer } from "../services/auto-register.js";
4
3
  import { claimMcpLaunchBinding, getMcpLaunchQueueSize } from "../services/mcp-launch-binding-queue.js";
5
4
  import { clearRuntimeBinding, getRuntimeAccessToken, getRuntimeBindingPublicState, getRuntimePairingCode, hasRuntimeBinding, saveRuntimeBinding, validateRuntimeBindingToken, validateRuntimePairingCode, } from "../services/runtime-binding.js";
6
5
  import { createLogger } from "../utils/logger.js";
7
6
  const log = createLogger("runtime-binding-route");
8
7
  export const runtimeBindingRouter = Router();
9
- const runtimeTokenRefreshLocks = new Map();
10
- function buildRuntimeTokenRefreshLockKey(userId, serverUrl) {
11
- return `${String(userId || "").trim()}\n${String(serverUrl || "").trim()}`;
12
- }
13
- async function withRuntimeTokenRefreshLock(userId, serverUrl, operation) {
14
- const lockKey = buildRuntimeTokenRefreshLockKey(userId, serverUrl);
15
- const previous = runtimeTokenRefreshLocks.get(lockKey) ?? Promise.resolve();
16
- let release;
17
- const current = new Promise((resolve) => {
18
- release = resolve;
19
- });
20
- const chained = previous.then(() => current);
21
- runtimeTokenRefreshLocks.set(lockKey, chained);
22
- await previous;
23
- try {
24
- return await operation();
25
- }
26
- finally {
27
- release();
28
- if (runtimeTokenRefreshLocks.get(lockKey) === chained) {
29
- runtimeTokenRefreshLocks.delete(lockKey);
30
- }
31
- }
32
- }
33
8
  /**
34
- * 主流程:读取当前 scoped token -> 必要时串行刷新 -> 锁内重读 token,避免多个 MCP 进程强刷时互相废弃新 token。
9
+ * 主流程:MCP Node token 必须通过 /runtime/binding/claim-agent 从启动绑定领取。
10
+ * 不再返回 bridge/user/server scoped runtime token,避免 MCP Node 降级使用旧 bridge 级凭据。
35
11
  */
36
- export async function resolveRuntimeTokenRequest(input) {
37
- const { userId, serverUrl, schedulerBaseUrl, forceRefresh } = input;
38
- const requestedServerUrl = serverUrl || schedulerBaseUrl;
39
- const scopedToken = getRuntimeAccessToken(userId, requestedServerUrl);
40
- if (scopedToken && forceRefresh !== true) {
41
- return {
42
- success: true,
43
- runtimeAccessToken: scopedToken,
44
- updated: false,
45
- };
46
- }
47
- return await withRuntimeTokenRefreshLock(userId, requestedServerUrl, async () => {
48
- const latestScopedToken = getRuntimeAccessToken(userId, requestedServerUrl);
49
- const rejectedRuntimeAccessToken = String(input.rejectedRuntimeAccessToken || "").trim();
50
- if (forceRefresh === true &&
51
- latestScopedToken &&
52
- rejectedRuntimeAccessToken &&
53
- latestScopedToken !== rejectedRuntimeAccessToken) {
54
- return {
55
- success: true,
56
- runtimeAccessToken: latestScopedToken,
57
- updated: false,
58
- };
59
- }
60
- if (requestedServerUrl) {
61
- return await requestRuntimeAccessTokenRefreshForServer(String(requestedServerUrl));
62
- }
63
- return await requestRuntimeAccessTokenRefresh();
64
- });
12
+ export function resolveRuntimeTokenRequest(input) {
13
+ void input;
14
+ return {
15
+ success: false,
16
+ error: "MCP runtime token must be claimed from launch binding; bridge-scoped token refresh is disabled",
17
+ };
65
18
  }
66
19
  function extractRuntimeToken(req) {
67
20
  const headerToken = req.header("X-Runtime-Token");
@@ -130,7 +83,7 @@ runtimeBindingRouter.post("/binding/request-token", async (req, res) => {
130
83
  res.status(403).json({ ok: false, error: "loopback request required" });
131
84
  return;
132
85
  }
133
- const result = await resolveRuntimeTokenRequest(req.body || {});
86
+ const result = resolveRuntimeTokenRequest(req.body || {});
134
87
  if (!result.success || !result.runtimeAccessToken) {
135
88
  res.status(400).json({
136
89
  ok: false,
@@ -1,10 +1,6 @@
1
1
  import { afterEach, describe, expect, it, vi } from "vitest";
2
- const getRuntimeAccessTokenMock = vi.hoisted(() => vi.fn());
3
- const requestRuntimeAccessTokenRefreshMock = vi.hoisted(() => vi.fn());
4
- const requestRuntimeAccessTokenRefreshForServerMock = vi.hoisted(() => vi.fn());
5
2
  vi.mock("../services/runtime-binding.js", () => ({
6
3
  clearRuntimeBinding: vi.fn(),
7
- getRuntimeAccessToken: getRuntimeAccessTokenMock,
8
4
  getRuntimeBindingPublicState: vi.fn(() => ({ paired: true })),
9
5
  getRuntimePairingCode: vi.fn(() => "000-000"),
10
6
  hasRuntimeBinding: vi.fn(() => true),
@@ -12,10 +8,6 @@ vi.mock("../services/runtime-binding.js", () => ({
12
8
  validateRuntimeBindingToken: vi.fn(() => true),
13
9
  validateRuntimePairingCode: vi.fn(() => true),
14
10
  }));
15
- vi.mock("../services/auto-register.js", () => ({
16
- requestRuntimeAccessTokenRefresh: requestRuntimeAccessTokenRefreshMock,
17
- requestRuntimeAccessTokenRefreshForServer: requestRuntimeAccessTokenRefreshForServerMock,
18
- }));
19
11
  vi.mock("../services/mcp-launch-binding-queue.js", () => ({
20
12
  claimMcpLaunchBinding: vi.fn(),
21
13
  getMcpLaunchQueueSize: vi.fn(() => 0),
@@ -24,85 +16,17 @@ afterEach(() => {
24
16
  vi.clearAllMocks();
25
17
  });
26
18
  describe("runtime binding token request", () => {
27
- it("reuses a newer scoped token instead of rotating again after another MCP process refreshed it", async () => {
28
- getRuntimeAccessTokenMock.mockReturnValue("fresh-token-from-other-process");
29
- const { resolveRuntimeTokenRequest } = await import("./runtime-binding.js");
30
- const result = await resolveRuntimeTokenRequest({
31
- userId: "user-1",
32
- serverUrl: "ws://localhost:7380/ws/agent",
33
- forceRefresh: true,
34
- rejectedRuntimeAccessToken: "stale-token",
35
- });
36
- expect(result).toEqual({
37
- success: true,
38
- runtimeAccessToken: "fresh-token-from-other-process",
39
- updated: false,
40
- });
41
- expect(requestRuntimeAccessTokenRefreshForServerMock).not.toHaveBeenCalled();
42
- expect(requestRuntimeAccessTokenRefreshMock).not.toHaveBeenCalled();
43
- });
44
- it("rotates the runtime token when the latest scoped token is the rejected token", async () => {
45
- getRuntimeAccessTokenMock.mockReturnValue("stale-token");
46
- requestRuntimeAccessTokenRefreshForServerMock.mockResolvedValue({
47
- success: true,
48
- runtimeAccessToken: "rotated-token",
49
- updated: true,
50
- });
19
+ it("rejects bridge-scoped token refresh for MCP nodes", async () => {
51
20
  const { resolveRuntimeTokenRequest } = await import("./runtime-binding.js");
52
- const result = await resolveRuntimeTokenRequest({
21
+ const result = resolveRuntimeTokenRequest({
53
22
  userId: "user-1",
54
23
  serverUrl: "ws://localhost:7380/ws/agent",
55
24
  forceRefresh: true,
56
25
  rejectedRuntimeAccessToken: "stale-token",
57
26
  });
58
27
  expect(result).toEqual({
59
- success: true,
60
- runtimeAccessToken: "rotated-token",
61
- updated: true,
62
- });
63
- expect(requestRuntimeAccessTokenRefreshForServerMock).toHaveBeenCalledWith("ws://localhost:7380/ws/agent");
64
- });
65
- it("serializes concurrent force refreshes and lets the loser reuse the winner token", async () => {
66
- let cachedToken = "stale-token";
67
- let releaseRefresh = () => undefined;
68
- const refreshGate = new Promise((resolve) => {
69
- releaseRefresh = resolve;
70
- });
71
- getRuntimeAccessTokenMock.mockImplementation(() => cachedToken);
72
- requestRuntimeAccessTokenRefreshForServerMock.mockImplementation(async () => {
73
- await refreshGate;
74
- cachedToken = "winner-token";
75
- return {
76
- success: true,
77
- runtimeAccessToken: "winner-token",
78
- updated: true,
79
- };
80
- });
81
- const { resolveRuntimeTokenRequest } = await import("./runtime-binding.js");
82
- const first = resolveRuntimeTokenRequest({
83
- userId: "user-1",
84
- serverUrl: "ws://localhost:7380/ws/agent",
85
- forceRefresh: true,
86
- rejectedRuntimeAccessToken: "stale-token",
87
- });
88
- const second = resolveRuntimeTokenRequest({
89
- userId: "user-1",
90
- serverUrl: "ws://localhost:7380/ws/agent",
91
- forceRefresh: true,
92
- rejectedRuntimeAccessToken: "stale-token",
93
- });
94
- releaseRefresh();
95
- const [firstResult, secondResult] = await Promise.all([first, second]);
96
- expect(firstResult).toEqual({
97
- success: true,
98
- runtimeAccessToken: "winner-token",
99
- updated: true,
100
- });
101
- expect(secondResult).toEqual({
102
- success: true,
103
- runtimeAccessToken: "winner-token",
104
- updated: false,
28
+ success: false,
29
+ error: "MCP runtime token must be claimed from launch binding; bridge-scoped token refresh is disabled",
105
30
  });
106
- expect(requestRuntimeAccessTokenRefreshForServerMock).toHaveBeenCalledTimes(1);
107
31
  });
108
32
  });
@@ -4,4 +4,9 @@
4
4
  * 提供 SDK Agent 会话的查询和恢复功能
5
5
  */
6
6
  export declare const sessionsRouter: import("express-serve-static-core").Router;
7
+ /**
8
+ * 解析 active SDK 会话快照状态。
9
+ * 主流程:优先使用 adapter 细粒度状态,其次使用 terminal 路由内存里的最近 runtimeStatus,最后兼容旧会话回退 running。
10
+ */
11
+ export declare function resolveActiveSdkSessionSnapshotStatus(adapterStatus?: string, currentRuntimeStatus?: string): string;
7
12
  //# sourceMappingURL=sessions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/routes/sessions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH,eAAO,MAAM,cAAc,4CAAW,CAAC"}
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/routes/sessions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH,eAAO,MAAM,cAAc,4CAAW,CAAC;AAEvC;;;GAGG;AACH,wBAAgB,qCAAqC,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,CAOnH"}
@@ -13,6 +13,18 @@ import { createLogger } from '../utils/logger.js';
13
13
  import { sdkSessions } from './terminal.js';
14
14
  const log = createLogger('sessions');
15
15
  export const sessionsRouter = Router();
16
+ /**
17
+ * 解析 active SDK 会话快照状态。
18
+ * 主流程:优先使用 adapter 细粒度状态,其次使用 terminal 路由内存里的最近 runtimeStatus,最后兼容旧会话回退 running。
19
+ */
20
+ export function resolveActiveSdkSessionSnapshotStatus(adapterStatus, currentRuntimeStatus) {
21
+ const normalizedAdapterStatus = String(adapterStatus || '').trim();
22
+ if (normalizedAdapterStatus) {
23
+ return normalizedAdapterStatus;
24
+ }
25
+ const normalizedRuntimeStatus = String(currentRuntimeStatus || '').trim();
26
+ return normalizedRuntimeStatus || 'running';
27
+ }
16
28
  /**
17
29
  * 获取所有 SDK Agent 会话
18
30
  * GET /runtime/sessions
@@ -20,18 +32,22 @@ export const sessionsRouter = Router();
20
32
  sessionsRouter.get('/sessions', validateToken, async (_req, res) => {
21
33
  try {
22
34
  // 1. 获取 SDK 会话
23
- const activeSdkSessions = Array.from(sdkSessions.entries()).map(([sessionId, entry]) => ({
24
- sessionId,
25
- agentId: entry.agentId,
26
- workspacePath: entry.workspacePath,
27
- status: 'running',
28
- runtimeStatus: entry.currentRuntimeStatus,
29
- runtimeActionType: entry.currentRuntimeActionType,
30
- runtimeActionLabel: entry.currentRuntimeActionLabel,
31
- runtimeActionDetail: entry.currentRuntimeActionDetail,
32
- mode: 'sdk',
33
- startedAt: null,
34
- }));
35
+ const activeSdkSessions = Array.from(sdkSessions.entries()).map(([sessionId, entry]) => {
36
+ const adapter = adapterRegistry.get(entry.providerId || 'claude-code');
37
+ const status = resolveActiveSdkSessionSnapshotStatus(adapter.getSessionStatus(sessionId), entry.currentRuntimeStatus);
38
+ return {
39
+ sessionId,
40
+ agentId: entry.agentId,
41
+ workspacePath: entry.workspacePath,
42
+ status,
43
+ runtimeStatus: status,
44
+ runtimeActionType: entry.currentRuntimeActionType,
45
+ runtimeActionLabel: entry.currentRuntimeActionLabel,
46
+ runtimeActionDetail: entry.currentRuntimeActionDetail,
47
+ mode: 'sdk',
48
+ startedAt: null,
49
+ };
50
+ });
35
51
  // 2. SDK-only:活跃会话全部来自 SDK adapter。
36
52
  const activeSessions = activeSdkSessions;
37
53
  const persistedSessions = await loadPersistedSessions();
@@ -66,14 +82,16 @@ sessionsRouter.post('/sessions/recover', validateToken, async (req, res) => {
66
82
  // 1. 检查是否有 SDK 活跃会话
67
83
  const existingSdkSession = Array.from(sdkSessions.entries()).find(([, s]) => s.agentId === agentId);
68
84
  if (existingSdkSession) {
85
+ const adapter = adapterRegistry.get(existingSdkSession[1].providerId || 'claude-code');
86
+ const status = resolveActiveSdkSessionSnapshotStatus(adapter.getSessionStatus(existingSdkSession[0]), existingSdkSession[1].currentRuntimeStatus);
69
87
  res.json({
70
88
  ok: true,
71
89
  sessionId: existingSdkSession[0],
72
90
  agentId,
73
91
  workspacePath: existingSdkSession[1].workspacePath,
74
92
  command: existingSdkSession[1].config.command,
75
- status: 'running',
76
- runtimeStatus: existingSdkSession[1].currentRuntimeStatus,
93
+ status,
94
+ runtimeStatus: status,
77
95
  runtimeActionType: existingSdkSession[1].currentRuntimeActionType,
78
96
  runtimeActionLabel: existingSdkSession[1].currentRuntimeActionLabel,
79
97
  runtimeActionDetail: existingSdkSession[1].currentRuntimeActionDetail,
@@ -103,7 +121,7 @@ sessionsRouter.post('/sessions/recover', validateToken, async (req, res) => {
103
121
  }
104
122
  }
105
123
  const resolvedWorkspacePath = workspacePath || persistedSession?.workspacePath;
106
- const resolvedCommand = command || persistedSession?.command || 'claude';
124
+ const resolvedCommand = command || persistedSession?.command || 'acode';
107
125
  const resolvedMode = persistedSession?.mode || 'sdk';
108
126
  if (!resolvedWorkspacePath) {
109
127
  res.status(400).json({ error: 'workspacePath is required (not found in persisted session)' });
@@ -343,12 +361,14 @@ sessionsRouter.get('/sessions/by-agent/:agentId', validateToken, async (req, res
343
361
  if (activeSdkSession) {
344
362
  const adapter = adapterRegistry.get(activeSdkSession[1].providerId || 'claude-code');
345
363
  const pid = adapter.getSessionPid?.(activeSdkSession[0]);
364
+ const status = resolveActiveSdkSessionSnapshotStatus(adapter.getSessionStatus(activeSdkSession[0]), activeSdkSession[1].currentRuntimeStatus);
346
365
  res.json({
347
366
  ok: true,
348
367
  sessionId: activeSdkSession[0],
349
368
  agentId,
350
369
  workspacePath: activeSdkSession[1].workspacePath,
351
- status: 'running',
370
+ status,
371
+ runtimeStatus: status,
352
372
  isActive: true,
353
373
  mode: 'sdk',
354
374
  pid,
@@ -2,6 +2,7 @@
2
2
  * Sessions 路由单元测试
3
3
  */
4
4
  import { describe, it, expect } from 'vitest';
5
+ import { resolveActiveSdkSessionSnapshotStatus } from './sessions.js';
5
6
  describe('sessions route validation', () => {
6
7
  it('requires agentId for recovery', () => {
7
8
  const validateRecoverRequest = (body) => {
@@ -48,6 +49,18 @@ describe('session persistence', () => {
48
49
  expect(result[0].sessionId).toBe('session-2');
49
50
  });
50
51
  });
52
+ describe('active SDK session snapshots', () => {
53
+ it('reports the adapter fine-grained status instead of hardcoded running', () => {
54
+ expect(resolveActiveSdkSessionSnapshotStatus('waiting_input', 'running')).toBe('waiting_input');
55
+ expect(resolveActiveSdkSessionSnapshotStatus('tool_using', undefined)).toBe('tool_using');
56
+ });
57
+ it('uses current runtime status when adapter status is unavailable', () => {
58
+ expect(resolveActiveSdkSessionSnapshotStatus(undefined, 'thinking')).toBe('thinking');
59
+ });
60
+ it('falls back to running only for legacy sessions without status details', () => {
61
+ expect(resolveActiveSdkSessionSnapshotStatus(undefined, undefined)).toBe('running');
62
+ });
63
+ });
51
64
  describe('terminal configuration', () => {
52
65
  it('selects correct shell for platform', () => {
53
66
  const getShellConfig = (platform) => {
@@ -5,7 +5,7 @@ import { syncLegacyStateFromSdk } from '../services/instance-init-service.js';
5
5
  import { resolveSkillSourceDir, ensureDirectoryExists } from '../utils/file-utils.js';
6
6
  import { normalizeSdkAppFlags } from '../utils/mcp-utils.js';
7
7
  export const skillsRouter = Router();
8
- const TOGGLEABLE_APP_IDS = new Set(['claudecode', 'opencode']);
8
+ const TOGGLEABLE_APP_IDS = new Set(['acode', 'claudecode', 'opencode']);
9
9
  skillsRouter.post('/cc-switch/skills/list', validateToken, async (req, res) => {
10
10
  const { agentId } = req.body || {};
11
11
  if (!agentId) {
@@ -100,7 +100,7 @@ skillsRouter.post('/cc-switch/skills/toggle-app', validateToken, async (req, res
100
100
  try {
101
101
  const normalizedAppId = String(appId).trim().toLowerCase();
102
102
  if (!TOGGLEABLE_APP_IDS.has(normalizedAppId)) {
103
- res.status(400).json({ error: 'appId must be one of claudecode, opencode' });
103
+ res.status(400).json({ error: 'appId must be one of acode, claudecode, opencode' });
104
104
  return;
105
105
  }
106
106
  const sdk = await loadCcSwitchSdk(agentId);
@@ -40,7 +40,8 @@ describe('skills route validation', () => {
40
40
  expect(normalizeApps(['claude', 'opencode'])).toEqual(['claude', 'opencode']);
41
41
  });
42
42
  it('allows supported per-tool toggle app ids', () => {
43
- const isSupportedToggleAppId = (appId) => ['claudecode', 'opencode'].includes(appId);
43
+ const isSupportedToggleAppId = (appId) => ['acode', 'claudecode', 'opencode'].includes(appId);
44
+ expect(isSupportedToggleAppId('acode')).toBe(true);
44
45
  expect(isSupportedToggleAppId('claudecode')).toBe(true);
45
46
  expect(isSupportedToggleAppId('opencode')).toBe(true);
46
47
  expect(isSupportedToggleAppId('codex')).toBe(false);
@@ -46,15 +46,20 @@ export declare function decodeTerminalOutputChunk(decoder: TextDecoder, chunk?:
46
46
  * 解析 cd 命令目标;返回 undefined 表示不是目录切换命令,null 表示仅查看当前目录。
47
47
  */
48
48
  export declare function parseTerminalDirectoryChangeTarget(command: string): string | null | undefined;
49
+ /**
50
+ * 解析 SDK 会话对外展示状态。
51
+ * 主流程:优先使用 adapter 当前细粒度状态,其次使用路由内存记录的最近状态,最后才兼容旧会话回退 running。
52
+ */
53
+ export declare function resolveSdkSessionDisplayStatus(adapterStatus?: string, currentRuntimeStatus?: string): string;
49
54
  export interface PersistedSessionReuseDecision {
50
55
  reusable: boolean;
51
- reason: 'active-pid' | 'missing-pid' | 'dead-pid';
56
+ reason: 'active-pid' | 'missing-pid' | 'dead-pid' | 'in-process-provider';
52
57
  }
53
58
  /**
54
- * 判断持久化 SDK 会话能否在显式启动请求中复用。
55
- * 主流程:必须存在 PID OS 仍能确认该进程存活;否则视为重启后遗留的陈旧会话。
59
+ * Decide whether a persisted SDK session can be reused for a start request.
60
+ * Main flow: require a live PID, then reject in-process providers whose adapter control channel cannot be restored.
56
61
  */
57
- export declare function evaluatePersistedSessionReuse(session: Pick<PersistedSession, 'pid'>, isPidRunning?: (pid: number) => boolean): PersistedSessionReuseDecision;
62
+ export declare function evaluatePersistedSessionReuse(session: Pick<PersistedSession, 'pid'> & Partial<Pick<PersistedSession, 'command'>>, isPidRunning?: (pid: number) => boolean): PersistedSessionReuseDecision;
58
63
  /**
59
64
  * 生成显示在终端输出流中的工作目录提示符。
60
65
  */
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/routes/terminal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/routes/terminal.ts"],"names":[],"mappings":"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"}
@@ -118,10 +118,11 @@ function findActiveSdkSessionByAgentId(agentId) {
118
118
  }
119
119
  async function buildExistingSessionResponse(entry) {
120
120
  const adapter = adapterRegistry.get(entry.providerId || 'claude-code');
121
+ const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(entry.sessionId), entry.currentRuntimeStatus);
121
122
  return {
122
123
  sessionId: entry.sessionId,
123
- status: adapter.getSessionStatus(entry.sessionId) || 'running',
124
- runtimeStatus: entry.currentRuntimeStatus,
124
+ status,
125
+ runtimeStatus: status,
125
126
  runtimeActionType: entry.currentRuntimeActionType,
126
127
  runtimeActionLabel: entry.currentRuntimeActionLabel,
127
128
  runtimeActionDetail: entry.currentRuntimeActionDetail,
@@ -134,8 +135,20 @@ async function buildExistingSessionResponse(entry) {
134
135
  };
135
136
  }
136
137
  /**
137
- * 判断持久化 SDK 会话能否在显式启动请求中复用。
138
- * 主流程:必须存在 PID OS 仍能确认该进程存活;否则视为重启后遗留的陈旧会话。
138
+ * 解析 SDK 会话对外展示状态。
139
+ * 主流程:优先使用 adapter 当前细粒度状态,其次使用路由内存记录的最近状态,最后才兼容旧会话回退 running。
140
+ */
141
+ export function resolveSdkSessionDisplayStatus(adapterStatus, currentRuntimeStatus) {
142
+ const normalizedAdapterStatus = String(adapterStatus || '').trim();
143
+ if (normalizedAdapterStatus) {
144
+ return normalizedAdapterStatus;
145
+ }
146
+ const normalizedRuntimeStatus = String(currentRuntimeStatus || '').trim();
147
+ return normalizedRuntimeStatus || 'running';
148
+ }
149
+ /**
150
+ * Decide whether a persisted SDK session can be reused for a start request.
151
+ * Main flow: require a live PID, then reject in-process providers whose adapter control channel cannot be restored.
139
152
  */
140
153
  export function evaluatePersistedSessionReuse(session, isPidRunning = isProcessRunning) {
141
154
  if (!session.pid) {
@@ -144,6 +157,9 @@ export function evaluatePersistedSessionReuse(session, isPidRunning = isProcessR
144
157
  if (!isPidRunning(session.pid)) {
145
158
  return { reusable: false, reason: 'dead-pid' };
146
159
  }
160
+ if (resolveSdkProviderId(session.command) === 'acode') {
161
+ return { reusable: false, reason: 'in-process-provider' };
162
+ }
147
163
  return { reusable: true, reason: 'active-pid' };
148
164
  }
149
165
  function stopTerminalCommandProcess(sessionId) {
@@ -570,7 +586,7 @@ async function startSdkSession(req, res) {
570
586
  }
571
587
  startingAgents.add(normalizedAgentId);
572
588
  // 根据命令选择对应的 adapter
573
- const normalizedCommand = (command || 'claude').toLowerCase();
589
+ const normalizedCommand = (command || 'acode').toLowerCase();
574
590
  const providerId = resolveSdkProviderId(command);
575
591
  // ★ 特征日志:明确输出选择的 provider
576
592
  console.log(`[Runtime] ★★★ SDK启动 ★★★ command="${command}", normalizedCommand="${normalizedCommand}", providerId="${providerId}"`);
@@ -584,7 +600,7 @@ async function startSdkSession(req, res) {
584
600
  : buildClaudeCodeLaunchConfig(null, envOverrides);
585
601
  // 构建SDK配置
586
602
  const config = {
587
- command: command || 'claude',
603
+ command: command || 'acode',
588
604
  workingDirectory: workspacePath,
589
605
  autoAccept: Boolean(autoAccept),
590
606
  initialPrompt,
@@ -630,6 +646,7 @@ async function startSdkSession(req, res) {
630
646
  console.warn(`[Runtime] Failed to get SDK PID for agentId=${agentId}:`, pidError);
631
647
  }
632
648
  // ★ 持久化会话信息(包含 PID)
649
+ const startedStatus = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(sessionId), sdkSessions.get(sessionId)?.currentRuntimeStatus);
633
650
  await upsertPersistedSession({
634
651
  sessionId,
635
652
  agentId,
@@ -637,7 +654,7 @@ async function startSdkSession(req, res) {
637
654
  command: config.command,
638
655
  startedAt: new Date().toISOString(),
639
656
  status: 'running',
640
- runtimeStatus: 'running',
657
+ runtimeStatus: startedStatus,
641
658
  mode: 'sdk',
642
659
  pid: sdkPid,
643
660
  providerSessionId: adapter.getProviderSessionId(sessionId),
@@ -663,7 +680,8 @@ async function startSdkSession(req, res) {
663
680
  }
664
681
  res.json({
665
682
  sessionId,
666
- status: 'running',
683
+ status: startedStatus,
684
+ runtimeStatus: startedStatus,
667
685
  agentId,
668
686
  workspacePath,
669
687
  command: config.command,
@@ -3,7 +3,7 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { describe, expect, it } from 'vitest';
5
5
  import { SDK_PROVIDER_DEFINITIONS } from '../adapter/SdkProviderSpi.js';
6
- import { buildClaudeCodeLaunchConfig, buildRuntimeEnv, buildToolResultTimelineActionInfo, createTerminalOutputDecoder, decodeTerminalOutputChunk, evaluatePersistedSessionReuse, formatTerminalPrompt, formatSdkOutputEvent, normalizeTerminalCommandInput, parseTerminalDirectoryChangeTarget, resolveSdkInputMessage, resolveTerminalOutputEncoding, resolveSdkProviderId, resolveStatusChangeUsage, } from './terminal.js';
6
+ import { buildClaudeCodeLaunchConfig, buildRuntimeEnv, buildToolResultTimelineActionInfo, createTerminalOutputDecoder, decodeTerminalOutputChunk, evaluatePersistedSessionReuse, formatTerminalPrompt, formatSdkOutputEvent, normalizeTerminalCommandInput, parseTerminalDirectoryChangeTarget, resolveSdkInputMessage, resolveTerminalOutputEncoding, resolveSdkProviderId, resolveSdkSessionDisplayStatus, resolveStatusChangeUsage, } from './terminal.js';
7
7
  describe('terminal route validation', () => {
8
8
  it('requires agentId and workspacePath for start', () => {
9
9
  const validateStartRequest = (body) => {
@@ -15,8 +15,8 @@ describe('terminal route validation', () => {
15
15
  expect(validateStartRequest({ agentId: 'agent-1', workspacePath: '/path' }).valid).toBe(true);
16
16
  });
17
17
  it('uses SDK default command when not provided', () => {
18
- const resolveCommand = (command) => command || 'claude';
19
- expect(resolveCommand(undefined)).toBe('claude');
18
+ const resolveCommand = (command) => command || 'acode';
19
+ expect(resolveCommand(undefined)).toBe('acode');
20
20
  expect(resolveCommand('codex')).toBe('codex');
21
21
  });
22
22
  it('uses SDK launch mode for unsupported mode values', () => {
@@ -130,10 +130,26 @@ describe('persisted SDK session reuse', () => {
130
130
  const decision = evaluatePersistedSessionReuse({ pid: 1234 }, () => false);
131
131
  expect(decision).toEqual({ reusable: false, reason: 'dead-pid' });
132
132
  });
133
- it('reuses a persisted session only when its PID is still running', () => {
134
- const decision = evaluatePersistedSessionReuse({ pid: 1234 }, () => true);
133
+ it('reuses a persisted session only when its PID is still running for externally controllable providers', () => {
134
+ const decision = evaluatePersistedSessionReuse({ pid: 1234, command: 'claude' }, () => true);
135
135
  expect(decision).toEqual({ reusable: true, reason: 'active-pid' });
136
136
  });
137
+ it('does not reuse ACode persisted sessions because their control channel is in-process only', () => {
138
+ const decision = evaluatePersistedSessionReuse({ pid: 1234, command: 'acode' }, () => true);
139
+ expect(decision).toEqual({ reusable: false, reason: 'in-process-provider' });
140
+ });
141
+ });
142
+ describe('SDK session display status', () => {
143
+ it('prefers adapter fine-grained status over coarse running fallback', () => {
144
+ expect(resolveSdkSessionDisplayStatus('waiting_input', 'running')).toBe('waiting_input');
145
+ expect(resolveSdkSessionDisplayStatus('thinking', 'running')).toBe('thinking');
146
+ });
147
+ it('uses in-memory runtime status when adapter status is temporarily unavailable', () => {
148
+ expect(resolveSdkSessionDisplayStatus(undefined, 'tool_using')).toBe('tool_using');
149
+ });
150
+ it('falls back to running only when no fine-grained state exists', () => {
151
+ expect(resolveSdkSessionDisplayStatus(undefined, undefined)).toBe('running');
152
+ });
137
153
  });
138
154
  describe('terminal command helpers', () => {
139
155
  it('expands a whole mini-term clipboard paste file path into SDK input content', async () => {
@@ -188,10 +204,10 @@ describe('resolveSdkProviderId', () => {
188
204
  it('resolves opencode commands to opencode provider', () => {
189
205
  expect(resolveSdkProviderId('opencode')).toBe('opencode');
190
206
  });
191
- it('falls back to claude-code for claude or unknown commands', () => {
207
+ it('falls back to acode for unknown or empty commands while keeping claude mapped to claude-code', () => {
192
208
  expect(resolveSdkProviderId('claude')).toBe('claude-code');
193
- expect(resolveSdkProviderId('custom-binary')).toBe('claude-code');
194
- expect(resolveSdkProviderId(undefined)).toBe('claude-code');
209
+ expect(resolveSdkProviderId('custom-binary')).toBe('acode');
210
+ expect(resolveSdkProviderId(undefined)).toBe('acode');
195
211
  });
196
212
  });
197
213
  describe('SDK status usage forwarding', () => {
@@ -0,0 +1,8 @@
1
+ export declare function getReleasedAcodeEntryPath(): string;
2
+ /**
3
+ * 释放随 bridge 打包的内置 ACode runtime。
4
+ * 主流程:检查 bundled dist -> 复制 dist 与 metadata -> 链接 bridge node_modules,
5
+ * 确保 ACode 无需安装即可被 Adapter 动态加载。
6
+ */
7
+ export declare function ensureAcodePackageReleased(): string | null;
8
+ //# sourceMappingURL=acode-package.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acode-package.d.ts","sourceRoot":"","sources":["../../src/services/acode-package.ts"],"names":[],"mappings":"AAyDA,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,GAAG,IAAI,CAwB1D"}