aws-runtime-bridge 1.7.48 → 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.
- package/dist/adapter/AcodeSdkAdapter.d.ts +77 -0
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -0
- package/dist/adapter/AcodeSdkAdapter.js +322 -0
- package/dist/adapter/AcodeSdkAdapter.test.d.ts +2 -0
- package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +1 -0
- package/dist/adapter/AcodeSdkAdapter.test.js +273 -0
- package/dist/adapter/AdapterRegistry.d.ts +1 -1
- package/dist/adapter/AdapterRegistry.d.ts.map +1 -1
- package/dist/adapter/AdapterRegistry.js +2 -0
- package/dist/adapter/AdapterRegistry.test.js +10 -1
- package/dist/adapter/SdkProviderSpi.d.ts.map +1 -1
- package/dist/adapter/SdkProviderSpi.js +10 -1
- package/dist/adapter/index.d.ts +1 -0
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +1 -0
- package/dist/adapters/cc-switch/index.d.ts +2 -0
- package/dist/adapters/cc-switch/index.d.ts.map +1 -1
- package/dist/adapters/cc-switch/index.js +6 -0
- package/dist/adapters/cc-switch/mcp-acode.d.ts +17 -0
- package/dist/adapters/cc-switch/mcp-acode.d.ts.map +1 -0
- package/dist/adapters/cc-switch/mcp-acode.js +71 -0
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts +2 -0
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +1 -0
- package/dist/adapters/cc-switch/mcp-acode.test.js +80 -0
- package/dist/adapters/cc-switch/mcp-claude.d.ts.map +1 -1
- package/dist/adapters/cc-switch/mcp-claude.js +1 -0
- package/dist/adapters/cc-switch/mcp-claudecode.d.ts.map +1 -1
- package/dist/adapters/cc-switch/mcp-claudecode.js +1 -0
- package/dist/adapters/cc-switch/mcp-claudecode.test.js +1 -0
- package/dist/adapters/cc-switch/mcp-codex.d.ts.map +1 -1
- package/dist/adapters/cc-switch/mcp-codex.js +1 -0
- package/dist/adapters/cc-switch/mcp-codex.test.js +1 -0
- package/dist/adapters/cc-switch/mcp-opencode.d.ts.map +1 -1
- package/dist/adapters/cc-switch/mcp-opencode.js +1 -0
- package/dist/adapters/cc-switch/mcp-opencode.test.js +1 -0
- package/dist/adapters/cc-switch/skill-acode.d.ts +11 -0
- package/dist/adapters/cc-switch/skill-acode.d.ts.map +1 -0
- package/dist/adapters/cc-switch/skill-acode.js +12 -0
- package/dist/adapters/cc-switch/skill-directory.test.js +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/index.js +2 -0
- package/dist/routes/ai-sources.js +3 -2
- package/dist/routes/instance.js +1 -1
- package/dist/routes/instance.test.js +2 -1
- package/dist/routes/mcp.js +2 -2
- package/dist/routes/mcp.test.js +2 -1
- package/dist/routes/runtime-binding.d.ts +3 -2
- package/dist/routes/runtime-binding.d.ts.map +1 -1
- package/dist/routes/runtime-binding.js +9 -56
- package/dist/routes/runtime-binding.test.js +4 -80
- package/dist/routes/sessions.d.ts +5 -0
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +36 -16
- package/dist/routes/sessions.test.js +13 -0
- package/dist/routes/skills.js +2 -2
- package/dist/routes/skills.test.js +2 -1
- package/dist/routes/terminal.d.ts +24 -4
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +99 -10
- package/dist/routes/terminal.test.js +74 -8
- package/dist/services/acode-package.d.ts +8 -0
- package/dist/services/acode-package.d.ts.map +1 -0
- package/dist/services/acode-package.js +82 -0
- package/dist/services/ai-source-apply.d.ts +4 -0
- package/dist/services/ai-source-apply.d.ts.map +1 -1
- package/dist/services/ai-source-apply.js +38 -9
- package/dist/services/ai-source-apply.test.js +58 -1
- package/dist/services/cc-switch-sdk.d.ts.map +1 -1
- package/dist/services/cc-switch-sdk.js +9 -1
- package/dist/services/instance-init-service.d.ts.map +1 -1
- package/dist/services/instance-init-service.js +3 -2
- package/dist/services/instance-init-service.test.js +6 -5
- package/dist/services/instance-service.d.ts.map +1 -1
- package/dist/services/instance-service.js +1 -0
- package/dist/services/instance-state.js +1 -1
- package/dist/services/instance-state.test.js +4 -4
- package/dist/services/tool-installer.d.ts.map +1 -1
- package/dist/services/tool-installer.js +10 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +1 -0
- package/dist/utils/mcp-utils.d.ts.map +1 -1
- package/dist/utils/mcp-utils.js +4 -1
- package/dist/utils/validation.js +1 -1
- package/node_modules/@cc-switch/sdk/dist/constants.d.ts.map +1 -1
- package/node_modules/@cc-switch/sdk/dist/constants.js +7 -0
- package/node_modules/@cc-switch/sdk/dist/schemas.d.ts.map +1 -1
- package/node_modules/@cc-switch/sdk/dist/schemas.js +1 -0
- package/node_modules/@cc-switch/sdk/dist/sdk.d.ts.map +1 -1
- package/node_modules/@cc-switch/sdk/dist/sdk.js +7 -3
- package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts +7 -0
- package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts.map +1 -1
- package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.js +29 -2
- package/node_modules/@cc-switch/sdk/dist/types.d.ts +4 -1
- package/node_modules/@cc-switch/sdk/dist/types.d.ts.map +1 -1
- package/package/acode/README.md +88 -0
- package/package/acode/dist/built-in-file-tools.d.ts +16 -0
- package/package/acode/dist/built-in-file-tools.d.ts.map +1 -0
- package/package/acode/dist/built-in-file-tools.js +231 -0
- package/package/acode/dist/cli.d.ts +3 -0
- package/package/acode/dist/cli.d.ts.map +1 -0
- package/package/acode/dist/cli.js +37 -0
- package/package/acode/dist/commands/commandLoader.d.ts +7 -0
- package/package/acode/dist/commands/commandLoader.d.ts.map +1 -0
- package/package/acode/dist/commands/commandLoader.js +33 -0
- package/package/acode/dist/commands/commandParser.d.ts +7 -0
- package/package/acode/dist/commands/commandParser.d.ts.map +1 -0
- package/package/acode/dist/commands/commandParser.js +77 -0
- package/package/acode/dist/commands/commandRegistry.d.ts +13 -0
- package/package/acode/dist/commands/commandRegistry.d.ts.map +1 -0
- package/package/acode/dist/commands/commandRegistry.js +30 -0
- package/package/acode/dist/config.d.ts +4 -0
- package/package/acode/dist/config.d.ts.map +1 -0
- package/package/acode/dist/config.js +80 -0
- package/package/acode/dist/hooks/hookBus.d.ts +19 -0
- package/package/acode/dist/hooks/hookBus.d.ts.map +1 -0
- package/package/acode/dist/hooks/hookBus.js +40 -0
- package/package/acode/dist/index.d.ts +8 -0
- package/package/acode/dist/index.d.ts.map +1 -0
- package/package/acode/dist/index.js +7 -0
- package/package/acode/dist/runtime.d.ts +34 -0
- package/package/acode/dist/runtime.d.ts.map +1 -0
- package/package/acode/dist/runtime.js +571 -0
- package/package/acode/dist/types.d.ts +99 -0
- package/package/acode/dist/types.d.ts.map +1 -0
- package/package/acode/dist/types.js +1 -0
- package/package/acode/package.json +24 -0
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +4 -4
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.js +9 -6
- package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.test.js +21 -0
- package/package/aws-client-agent-mcp/dist/agent-client.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts +1 -17
- package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.js +7 -113
- package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.test.js +22 -2
- package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +0 -5
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +1 -23
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +89 -6
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +3 -0
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +0 -13
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +3 -100
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
- package/package/cc-switch-sdk/dist/constants.d.ts.map +1 -1
- package/package/cc-switch-sdk/dist/constants.js +7 -0
- package/package/cc-switch-sdk/dist/schemas.d.ts.map +1 -1
- package/package/cc-switch-sdk/dist/schemas.js +1 -0
- package/package/cc-switch-sdk/dist/sdk.d.ts.map +1 -1
- package/package/cc-switch-sdk/dist/sdk.js +7 -3
- package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts +7 -0
- package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts.map +1 -1
- package/package/cc-switch-sdk/dist/services/ai-config-service.js +29 -2
- package/package/cc-switch-sdk/dist/types.d.ts +4 -1
- package/package/cc-switch-sdk/dist/types.d.ts.map +1 -1
- package/package.json +8 -4
package/dist/routes/mcp.test.js
CHANGED
|
@@ -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
|
-
*
|
|
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):
|
|
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":"
|
|
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
|
-
*
|
|
9
|
+
* 主流程:MCP Node token 必须通过 /runtime/binding/claim-agent 从启动绑定领取。
|
|
10
|
+
* 不再返回 bridge/user/server scoped runtime token,避免 MCP Node 降级使用旧 bridge 级凭据。
|
|
35
11
|
*/
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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 =
|
|
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("
|
|
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 =
|
|
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:
|
|
60
|
-
|
|
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"}
|
package/dist/routes/sessions.js
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
76
|
-
runtimeStatus:
|
|
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 || '
|
|
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
|
|
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) => {
|
package/dist/routes/skills.js
CHANGED
|
@@ -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,20 +46,40 @@ 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
|
-
*
|
|
55
|
-
*
|
|
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'
|
|
62
|
+
export declare function evaluatePersistedSessionReuse(session: Pick<PersistedSession, 'pid'> & Partial<Pick<PersistedSession, 'command'>>, isPidRunning?: (pid: number) => boolean): PersistedSessionReuseDecision;
|
|
58
63
|
/**
|
|
59
64
|
* 生成显示在终端输出流中的工作目录提示符。
|
|
60
65
|
*/
|
|
61
66
|
export declare function formatTerminalPrompt(currentDirectory: string): string;
|
|
62
67
|
export declare function buildRuntimeEnv(agentId: string, workspacePath: string | undefined, baseEnv: NodeJS.ProcessEnv, envOverrides?: Record<string, unknown>): Record<string, string>;
|
|
68
|
+
/**
|
|
69
|
+
* 主流程:SDK 模式输入若只是 mini-term 剪贴板临时文件路径,则展开为文件内容再发给模型。
|
|
70
|
+
* 具体实现:只处理整串匹配的绝对临时 paste 文件路径,避免影响显式 `type "path"` 等正常命令。
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveSdkInputMessage(input: unknown): Promise<string>;
|
|
73
|
+
export interface ClaudeCodeLaunchConfig {
|
|
74
|
+
envOverrides: Record<string, string>;
|
|
75
|
+
model?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 主流程:将已持久化的 ClaudeCode AI 配置合并到启动配置,再让请求级覆盖保持最高优先级。
|
|
79
|
+
*/
|
|
80
|
+
export declare function buildClaudeCodeLaunchConfig(savedClaudeConfig: {
|
|
81
|
+
env?: Record<string, unknown>;
|
|
82
|
+
} | null | undefined, requestEnvOverrides?: Record<string, unknown>): ClaudeCodeLaunchConfig;
|
|
63
83
|
/**
|
|
64
84
|
* 将 SDK 工具结束事件转换为时间线返回数据状态。
|
|
65
85
|
* 主流程:格式化工具结果 -> 保留 MCP 专用展示 -> 其它工具按动作类型追加到对应时间线事件。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/routes/terminal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAKxC,OAAO,EAOL,KAAK,oBAAoB,EAEzB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAE7B,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/routes/terminal.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/terminal.js
CHANGED
|
@@ -14,6 +14,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
14
14
|
import { adapterRegistry, getToolActionInfo, isTransientClipboardPasteFilePath, redactTransientClipboardPastePaths, registerSdkProviders, resolveSdkProviderIdByCommand, } from '../adapter/index.js';
|
|
15
15
|
import { validateToken } from '../middleware/auth.js';
|
|
16
16
|
import { getAgentProcessManager } from '../services/agent-process-manager.js';
|
|
17
|
+
import { getAiConfigService } from '../services/cc-switch-sdk.js';
|
|
17
18
|
import { enqueueMcpLaunchBinding } from '../services/mcp-launch-binding-queue.js';
|
|
18
19
|
import { findClaudeCodeProcesses, isProcessRunning } from '../services/process-detector.js';
|
|
19
20
|
import { sendOutput, sendQuestionRequest, sendStatus } from '../services/session-output.js';
|
|
@@ -117,10 +118,11 @@ function findActiveSdkSessionByAgentId(agentId) {
|
|
|
117
118
|
}
|
|
118
119
|
async function buildExistingSessionResponse(entry) {
|
|
119
120
|
const adapter = adapterRegistry.get(entry.providerId || 'claude-code');
|
|
121
|
+
const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(entry.sessionId), entry.currentRuntimeStatus);
|
|
120
122
|
return {
|
|
121
123
|
sessionId: entry.sessionId,
|
|
122
|
-
status
|
|
123
|
-
runtimeStatus:
|
|
124
|
+
status,
|
|
125
|
+
runtimeStatus: status,
|
|
124
126
|
runtimeActionType: entry.currentRuntimeActionType,
|
|
125
127
|
runtimeActionLabel: entry.currentRuntimeActionLabel,
|
|
126
128
|
runtimeActionDetail: entry.currentRuntimeActionDetail,
|
|
@@ -133,8 +135,20 @@ async function buildExistingSessionResponse(entry) {
|
|
|
133
135
|
};
|
|
134
136
|
}
|
|
135
137
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
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.
|
|
138
152
|
*/
|
|
139
153
|
export function evaluatePersistedSessionReuse(session, isPidRunning = isProcessRunning) {
|
|
140
154
|
if (!session.pid) {
|
|
@@ -143,6 +157,9 @@ export function evaluatePersistedSessionReuse(session, isPidRunning = isProcessR
|
|
|
143
157
|
if (!isPidRunning(session.pid)) {
|
|
144
158
|
return { reusable: false, reason: 'dead-pid' };
|
|
145
159
|
}
|
|
160
|
+
if (resolveSdkProviderId(session.command) === 'acode') {
|
|
161
|
+
return { reusable: false, reason: 'in-process-provider' };
|
|
162
|
+
}
|
|
146
163
|
return { reusable: true, reason: 'active-pid' };
|
|
147
164
|
}
|
|
148
165
|
function stopTerminalCommandProcess(sessionId) {
|
|
@@ -301,6 +318,69 @@ export function buildRuntimeEnv(agentId, workspacePath, baseEnv, envOverrides) {
|
|
|
301
318
|
env.AWS_MCP_CLAIM_LAUNCH_BINDING = 'true';
|
|
302
319
|
return env;
|
|
303
320
|
}
|
|
321
|
+
function stripWrappingQuotes(value) {
|
|
322
|
+
const trimmed = value.trim();
|
|
323
|
+
const first = trimmed[0];
|
|
324
|
+
const last = trimmed[trimmed.length - 1];
|
|
325
|
+
if ((first === '"' || first === "'") && first === last) {
|
|
326
|
+
return trimmed.slice(1, -1);
|
|
327
|
+
}
|
|
328
|
+
return trimmed;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* 主流程:SDK 模式输入若只是 mini-term 剪贴板临时文件路径,则展开为文件内容再发给模型。
|
|
332
|
+
* 具体实现:只处理整串匹配的绝对临时 paste 文件路径,避免影响显式 `type "path"` 等正常命令。
|
|
333
|
+
*/
|
|
334
|
+
export async function resolveSdkInputMessage(input) {
|
|
335
|
+
const message = String(input);
|
|
336
|
+
if (!isTransientClipboardPasteFilePath(message)) {
|
|
337
|
+
return message;
|
|
338
|
+
}
|
|
339
|
+
const filePath = stripWrappingQuotes(message);
|
|
340
|
+
if (!path.isAbsolute(filePath)) {
|
|
341
|
+
return message;
|
|
342
|
+
}
|
|
343
|
+
return fs.readFile(filePath, 'utf-8');
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* 主流程:将已持久化的 ClaudeCode AI 配置合并到启动配置,再让请求级覆盖保持最高优先级。
|
|
347
|
+
*/
|
|
348
|
+
export function buildClaudeCodeLaunchConfig(savedClaudeConfig, requestEnvOverrides) {
|
|
349
|
+
const envOverrides = {};
|
|
350
|
+
for (const source of [savedClaudeConfig?.env, requestEnvOverrides]) {
|
|
351
|
+
if (!source) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
for (const [key, value] of Object.entries(source)) {
|
|
355
|
+
const normalizedKey = key.trim();
|
|
356
|
+
if (!normalizedKey || value === undefined || value === null) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const normalizedValue = String(value).trim();
|
|
360
|
+
if (!normalizedValue) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
envOverrides[normalizedKey] = normalizedValue;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const model = envOverrides.ANTHROPIC_MODEL?.trim()
|
|
367
|
+
|| envOverrides.CODEX_MODEL?.trim()
|
|
368
|
+
|| undefined;
|
|
369
|
+
return { envOverrides, model };
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* 具体逻辑:读取 ClaudeCode live settings;读取失败不阻断启动,保持原 SDK 启动路径可用。
|
|
373
|
+
*/
|
|
374
|
+
async function readSavedClaudeCodeLaunchConfig() {
|
|
375
|
+
try {
|
|
376
|
+
return await getAiConfigService().readClaudeCodeConfig();
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
const err = error;
|
|
380
|
+
console.warn(`[Runtime] Failed to read saved ClaudeCode AI config: ${err.message}`);
|
|
381
|
+
return {};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
304
384
|
function formatToolResultForTimeline(result) {
|
|
305
385
|
if (result === undefined || result === null) {
|
|
306
386
|
return undefined;
|
|
@@ -506,22 +586,29 @@ async function startSdkSession(req, res) {
|
|
|
506
586
|
}
|
|
507
587
|
startingAgents.add(normalizedAgentId);
|
|
508
588
|
// 根据命令选择对应的 adapter
|
|
509
|
-
const normalizedCommand = (command || '
|
|
589
|
+
const normalizedCommand = (command || 'acode').toLowerCase();
|
|
510
590
|
const providerId = resolveSdkProviderId(command);
|
|
511
591
|
// ★ 特征日志:明确输出选择的 provider
|
|
512
592
|
console.log(`[Runtime] ★★★ SDK启动 ★★★ command="${command}", normalizedCommand="${normalizedCommand}", providerId="${providerId}"`);
|
|
513
593
|
const adapter = adapterRegistry.get(providerId);
|
|
514
594
|
sessionId = uuidv4();
|
|
595
|
+
const savedClaudeConfig = providerId === 'claude-code'
|
|
596
|
+
? await readSavedClaudeCodeLaunchConfig()
|
|
597
|
+
: null;
|
|
598
|
+
const claudeLaunchConfig = providerId === 'claude-code'
|
|
599
|
+
? buildClaudeCodeLaunchConfig(savedClaudeConfig, envOverrides)
|
|
600
|
+
: buildClaudeCodeLaunchConfig(null, envOverrides);
|
|
515
601
|
// 构建SDK配置
|
|
516
602
|
const config = {
|
|
517
|
-
command: command || '
|
|
603
|
+
command: command || 'acode',
|
|
518
604
|
workingDirectory: workspacePath,
|
|
519
605
|
autoAccept: Boolean(autoAccept),
|
|
520
606
|
initialPrompt,
|
|
607
|
+
model: claudeLaunchConfig.model,
|
|
521
608
|
// MCP 配置:不再默认注入 aws-mcp;由用户在面板中安装/配置
|
|
522
609
|
mcpConfigPath,
|
|
523
610
|
extraMcpServers: extraMcpServers || {},
|
|
524
|
-
envOverrides: buildRuntimeEnv(String(agentId), String(workspacePath), process.env, envOverrides),
|
|
611
|
+
envOverrides: buildRuntimeEnv(String(agentId), String(workspacePath), process.env, claudeLaunchConfig.envOverrides),
|
|
525
612
|
};
|
|
526
613
|
// 存储会话信息
|
|
527
614
|
sdkSessions.set(sessionId, {
|
|
@@ -559,6 +646,7 @@ async function startSdkSession(req, res) {
|
|
|
559
646
|
console.warn(`[Runtime] Failed to get SDK PID for agentId=${agentId}:`, pidError);
|
|
560
647
|
}
|
|
561
648
|
// ★ 持久化会话信息(包含 PID)
|
|
649
|
+
const startedStatus = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(sessionId), sdkSessions.get(sessionId)?.currentRuntimeStatus);
|
|
562
650
|
await upsertPersistedSession({
|
|
563
651
|
sessionId,
|
|
564
652
|
agentId,
|
|
@@ -566,7 +654,7 @@ async function startSdkSession(req, res) {
|
|
|
566
654
|
command: config.command,
|
|
567
655
|
startedAt: new Date().toISOString(),
|
|
568
656
|
status: 'running',
|
|
569
|
-
runtimeStatus:
|
|
657
|
+
runtimeStatus: startedStatus,
|
|
570
658
|
mode: 'sdk',
|
|
571
659
|
pid: sdkPid,
|
|
572
660
|
providerSessionId: adapter.getProviderSessionId(sessionId),
|
|
@@ -592,7 +680,8 @@ async function startSdkSession(req, res) {
|
|
|
592
680
|
}
|
|
593
681
|
res.json({
|
|
594
682
|
sessionId,
|
|
595
|
-
status:
|
|
683
|
+
status: startedStatus,
|
|
684
|
+
runtimeStatus: startedStatus,
|
|
596
685
|
agentId,
|
|
597
686
|
workspacePath,
|
|
598
687
|
command: config.command,
|
|
@@ -844,7 +933,7 @@ terminalRouter.post('/input', validateToken, async (req, res) => {
|
|
|
844
933
|
res.json({ ok: true, mode: 'shortcut' });
|
|
845
934
|
return;
|
|
846
935
|
}
|
|
847
|
-
await adapter.sendMessage(sessionId,
|
|
936
|
+
await adapter.sendMessage(sessionId, await resolveSdkInputMessage(input));
|
|
848
937
|
res.json({ ok: true, mode: 'sdk' });
|
|
849
938
|
}
|
|
850
939
|
catch (error) {
|