aws-runtime-bridge 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -77
- package/dist/adapter/ClaudeSdkAdapter.d.ts +1 -0
- package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/ClaudeSdkAdapter.js +7 -3
- package/dist/adapter/ClaudeSdkAdapter.test.js +2 -2
- package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/CodexSdkAdapter.js +7 -4
- package/dist/adapter/CodexSdkAdapter.test.js +4 -2
- package/dist/adapter/OpencodeSdkAdapter.d.ts +2 -0
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +15 -1
- package/dist/adapter/OpencodeSdkAdapter.test.js +5 -0
- package/dist/index.js +0 -0
- package/dist/routes/properties.test.js +4 -4
- package/dist/routes/runtime-binding.d.ts.map +1 -1
- package/dist/routes/runtime-binding.js +8 -13
- package/dist/routes/runtime-mcp-proxy.d.ts +3 -0
- package/dist/routes/runtime-mcp-proxy.d.ts.map +1 -0
- package/dist/routes/runtime-mcp-proxy.js +102 -0
- package/dist/routes/runtime-mcp-proxy.test.d.ts +2 -0
- package/dist/routes/runtime-mcp-proxy.test.d.ts.map +1 -0
- package/dist/routes/runtime-mcp-proxy.test.js +111 -0
- package/dist/routes/terminal.js +2 -5
- package/dist/routes/terminal.test.js +3 -4
- package/dist/services/auto-register.d.ts +6 -0
- package/dist/services/auto-register.d.ts.map +1 -1
- package/dist/services/auto-register.js +63 -1
- package/dist/services/aws-client-agent-mcp.d.ts.map +1 -1
- package/dist/services/aws-client-agent-mcp.js +73 -7
- package/dist/services/aws-client-agent-mcp.test.js +83 -2
- package/dist/services/mcp-launch-binding-queue.d.ts +0 -2
- package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
- package/dist/services/mcp-launch-binding-queue.js +44 -16
- package/dist/services/mcp-launch-binding-queue.test.js +42 -37
- package/dist/services/runtime-binding.d.ts +1 -0
- package/dist/services/runtime-binding.d.ts.map +1 -1
- package/dist/services/runtime-binding.js +39 -5
- package/dist/services/runtime-binding.test.d.ts +2 -0
- package/dist/services/runtime-binding.test.d.ts.map +1 -0
- package/dist/services/runtime-binding.test.js +11 -0
- package/dist/utils/yaml-utils.test.js +129 -129
- package/node_modules/@cc-switch/sdk/README.md +540 -540
- package/node_modules/@cc-switch/sdk/dist/sdk-import.test.d.ts +2 -0
- package/node_modules/@cc-switch/sdk/dist/sdk-import.test.d.ts.map +1 -0
- package/node_modules/@cc-switch/sdk/dist/sdk-import.test.js +119 -0
- package/node_modules/@cc-switch/sdk/package.json +31 -31
- package/package/aws-client-agent-mcp/README.md +288 -288
- package/package/aws-client-agent-mcp/dist/config.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/config.js +96 -13
- package/package/aws-client-agent-mcp/dist/config.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/config.test.js +26 -8
- package/package/aws-client-agent-mcp/dist/config.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/constants.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/constants.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/constants.js +0 -1
- package/package/aws-client-agent-mcp/dist/constants.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.js +49 -13
- package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.test.js +40 -13
- package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/index.js +11 -6
- package/package/aws-client-agent-mcp/dist/index.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/logger.d.ts +11 -1
- package/package/aws-client-agent-mcp/dist/logger.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/logger.js +91 -6
- package/package/aws-client-agent-mcp/dist/logger.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/logger.test.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/logger.test.d.ts.map +1 -0
- package/package/aws-client-agent-mcp/dist/logger.test.js +27 -0
- package/package/aws-client-agent-mcp/dist/logger.test.js.map +1 -0
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +18 -14
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +51 -21
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +3 -2
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +1 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +18 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js +53 -2
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
- package/package/aws-client-agent-mcp/package.json +52 -52
- package/package/cc-switch-sdk/README.md +540 -540
- package/package/cc-switch-sdk/dist/sdk-import.test.d.ts +2 -0
- package/package/cc-switch-sdk/dist/sdk-import.test.d.ts.map +1 -0
- package/package/cc-switch-sdk/dist/sdk-import.test.js +119 -0
- package/package/cc-switch-sdk/package.json +31 -31
- package/package.json +78 -78
|
@@ -4,7 +4,7 @@ describe('mcp launch binding queue', () => {
|
|
|
4
4
|
afterEach(() => {
|
|
5
5
|
clearMcpLaunchBindingQueues();
|
|
6
6
|
});
|
|
7
|
-
it('claims same-workspace bindings
|
|
7
|
+
it('claims same-workspace same-server bindings from the front of the queue', () => {
|
|
8
8
|
const firstBinding = enqueueMcpLaunchBinding({
|
|
9
9
|
agentId: 'agent-a',
|
|
10
10
|
workspacePath: 'C:/repo/demo',
|
|
@@ -22,15 +22,11 @@ describe('mcp launch binding queue', () => {
|
|
|
22
22
|
schedulerBaseUrl: 'http://server-a:7380',
|
|
23
23
|
});
|
|
24
24
|
expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(2);
|
|
25
|
-
const
|
|
26
|
-
agentId: 'agent-b',
|
|
27
|
-
bindingId: secondBinding?.id,
|
|
25
|
+
const first = claimMcpLaunchBinding({
|
|
28
26
|
workspacePath: 'C:/repo/demo',
|
|
29
27
|
serverUrl: 'ws://server-a:7380/ws/agent',
|
|
30
28
|
});
|
|
31
|
-
const
|
|
32
|
-
agentId: 'agent-a',
|
|
33
|
-
bindingId: firstBinding?.id,
|
|
29
|
+
const second = claimMcpLaunchBinding({
|
|
34
30
|
workspacePath: 'C:/repo/demo',
|
|
35
31
|
serverUrl: 'ws://server-a:7380/ws/agent',
|
|
36
32
|
});
|
|
@@ -40,68 +36,77 @@ describe('mcp launch binding queue', () => {
|
|
|
40
36
|
expect(second?.runtimeAccessToken).toBe('token-b');
|
|
41
37
|
expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(0);
|
|
42
38
|
});
|
|
43
|
-
it('
|
|
39
|
+
it('claims by workspace path and server URL regardless of agent id', () => {
|
|
44
40
|
const binding = enqueueMcpLaunchBinding({
|
|
45
41
|
agentId: 'agent-a',
|
|
46
42
|
workspacePath: 'C:/repo/demo',
|
|
47
43
|
serverUrl: 'ws://server:7380/ws/agent',
|
|
48
44
|
});
|
|
49
45
|
expect(claimMcpLaunchBinding({
|
|
50
|
-
agentId: 'agent-b',
|
|
51
|
-
bindingId: binding?.id,
|
|
52
|
-
workspacePath: 'C:/repo/demo',
|
|
53
|
-
serverUrl: 'ws://server:7380/ws/agent',
|
|
54
|
-
})).toBeNull();
|
|
55
|
-
expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(1);
|
|
56
|
-
expect(claimMcpLaunchBinding({
|
|
57
|
-
agentId: 'agent-a',
|
|
58
|
-
bindingId: binding?.id,
|
|
59
46
|
workspacePath: 'C:/repo/demo',
|
|
60
47
|
serverUrl: 'ws://server:7380/ws/agent',
|
|
61
48
|
})?.agentId).toBe('agent-a');
|
|
49
|
+
expect(binding?.agentId).toBe('agent-a');
|
|
62
50
|
});
|
|
63
|
-
it('
|
|
51
|
+
it('ignores explicit binding id and claims the first matching queue entry', () => {
|
|
64
52
|
const binding = enqueueMcpLaunchBinding({
|
|
65
53
|
agentId: 'agent-a',
|
|
66
54
|
workspacePath: 'C:/repo/demo',
|
|
67
55
|
serverUrl: 'ws://server:7380/ws/agent',
|
|
68
56
|
});
|
|
69
57
|
expect(claimMcpLaunchBinding({
|
|
70
|
-
agentId: 'agent-a',
|
|
71
58
|
workspacePath: 'C:/repo/demo',
|
|
72
59
|
serverUrl: 'ws://server:7380/ws/agent',
|
|
73
|
-
})).
|
|
60
|
+
})?.agentId).toBe('agent-a');
|
|
61
|
+
expect(binding?.id).toBeTruthy();
|
|
62
|
+
});
|
|
63
|
+
it('matches by server URL as well as workspace path', () => {
|
|
64
|
+
const firstBinding = enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' });
|
|
65
|
+
const secondBinding = enqueueMcpLaunchBinding({ agentId: 'agent-b', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' });
|
|
66
|
+
expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' })?.agentId).toBe('agent-b');
|
|
67
|
+
expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' })?.agentId).toBe('agent-a');
|
|
68
|
+
});
|
|
69
|
+
it('matches http scheduler URLs and websocket agent URLs by scheduler origin', () => {
|
|
70
|
+
enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/demo', schedulerBaseUrl: 'http://server-a:7380' });
|
|
74
71
|
expect(claimMcpLaunchBinding({
|
|
72
|
+
workspacePath: 'C:/repo/demo',
|
|
73
|
+
serverUrl: 'ws://server-a:7380/ws/agent',
|
|
74
|
+
})?.agentId).toBe('agent-a');
|
|
75
|
+
});
|
|
76
|
+
it('repairs duplicated scheduler ports when enqueuing launch bindings', () => {
|
|
77
|
+
const binding = enqueueMcpLaunchBinding({
|
|
75
78
|
agentId: 'agent-a',
|
|
76
|
-
bindingId: 'wrong-binding',
|
|
77
79
|
workspacePath: 'C:/repo/demo',
|
|
78
|
-
|
|
79
|
-
})
|
|
80
|
-
expect(
|
|
80
|
+
schedulerBaseUrl: 'http://127.0.0.1:8080:8080',
|
|
81
|
+
});
|
|
82
|
+
expect(binding?.serverUrl).toBe('http://127.0.0.1:8080');
|
|
83
|
+
expect(binding?.schedulerBaseUrl).toBe('http://127.0.0.1:8080');
|
|
81
84
|
expect(claimMcpLaunchBinding({
|
|
85
|
+
workspacePath: 'C:/repo/demo',
|
|
86
|
+
serverUrl: 'ws://127.0.0.1:8080/ws/agent',
|
|
87
|
+
})?.schedulerBaseUrl).toBe('http://127.0.0.1:8080');
|
|
88
|
+
});
|
|
89
|
+
it('falls back to the only same-workspace binding when scheduler URLs differ', () => {
|
|
90
|
+
enqueueMcpLaunchBinding({
|
|
82
91
|
agentId: 'agent-a',
|
|
83
|
-
bindingId: binding?.id,
|
|
84
92
|
workspacePath: 'C:/repo/demo',
|
|
85
|
-
serverUrl: 'ws://
|
|
93
|
+
serverUrl: 'ws://127.0.0.1:7380/ws/agent',
|
|
94
|
+
});
|
|
95
|
+
expect(claimMcpLaunchBinding({
|
|
96
|
+
workspacePath: 'C:/repo/demo',
|
|
97
|
+
serverUrl: 'ws://101.42.200.84:7380/ws/agent',
|
|
86
98
|
})?.agentId).toBe('agent-a');
|
|
87
99
|
});
|
|
88
|
-
it('matches by server URL as well as workspace path', () => {
|
|
89
|
-
const firstBinding = enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' });
|
|
90
|
-
const secondBinding = enqueueMcpLaunchBinding({ agentId: 'agent-b', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' });
|
|
91
|
-
expect(claimMcpLaunchBinding({ agentId: 'agent-b', bindingId: secondBinding?.id, workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' })?.agentId).toBe('agent-b');
|
|
92
|
-
expect(claimMcpLaunchBinding({ agentId: 'agent-a', bindingId: firstBinding?.id, workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' })?.agentId).toBe('agent-a');
|
|
93
|
-
});
|
|
94
100
|
it('does not mix different workspace paths', () => {
|
|
95
101
|
const firstBinding = enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/a', serverUrl: 'ws://server:7380/ws/agent' });
|
|
96
102
|
const secondBinding = enqueueMcpLaunchBinding({ agentId: 'agent-b', workspacePath: 'C:/repo/b', serverUrl: 'ws://server:7380/ws/agent' });
|
|
97
|
-
expect(claimMcpLaunchBinding({
|
|
98
|
-
expect(claimMcpLaunchBinding({
|
|
103
|
+
expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/b', serverUrl: 'ws://server:7380/ws/agent' })?.agentId).toBe('agent-b');
|
|
104
|
+
expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/a', serverUrl: 'ws://server:7380/ws/agent' })?.agentId).toBe('agent-a');
|
|
99
105
|
});
|
|
100
106
|
it('returns null for missing or invalid workspaces', () => {
|
|
101
107
|
expect(enqueueMcpLaunchBinding({ agentId: '', workspacePath: 'C:/repo/demo' })).toBeNull();
|
|
102
108
|
expect(enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: '' })).toBeNull();
|
|
103
|
-
expect(claimMcpLaunchBinding({
|
|
104
|
-
expect(claimMcpLaunchBinding({
|
|
105
|
-
expect(claimMcpLaunchBinding({ agentId: 'agent-a', bindingId: 'binding-a', workspacePath: 'C:/repo/demo' })).toBeNull();
|
|
109
|
+
expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/missing', serverUrl: 'ws://server:7380/ws/agent' })).toBeNull();
|
|
110
|
+
expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/demo' })).toBeNull();
|
|
106
111
|
});
|
|
107
112
|
});
|
|
@@ -14,6 +14,7 @@ export type RuntimeBindingState = {
|
|
|
14
14
|
updatedAt?: string;
|
|
15
15
|
revokedAt?: string;
|
|
16
16
|
};
|
|
17
|
+
export declare function normalizeSchedulerBaseUrl(value: unknown): string | undefined;
|
|
17
18
|
export declare function getRuntimePairingCode(): string;
|
|
18
19
|
export declare function getRuntimeBindingFilePath(): string;
|
|
19
20
|
export declare function getRuntimeTokenStoreFilePath(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA2DF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAqB5E;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAoBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,CAYR;AAsCD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAWT;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,GAAG,SAAS,CAUpB;AAcD,wBAAgB,kBAAkB,IAAI,mBAAmB,CAgBxD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAClD,mBAAmB,EACnB,WAAW,GAAG,aAAa,CAC5B,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAWtB;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAG3C;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAcnE;AAED,wBAAgB,qBAAqB,CACnC,MAAM,CAAC,EAAE,OAAO,EAChB,aAAa,CAAC,EAAE,OAAO,GACtB,MAAM,GAAG,SAAS,CAepB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAEjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,mBAAmB,CAwCtB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAkB1C"}
|
|
@@ -46,6 +46,33 @@ function safeTextEqual(a, b) {
|
|
|
46
46
|
function normalizeToken(token) {
|
|
47
47
|
return String(token || "").trim();
|
|
48
48
|
}
|
|
49
|
+
function repairDuplicatePort(raw) {
|
|
50
|
+
return raw.replace(/^((?:https?|wss?):\/\/(?:\[[^\]]+\]|[^/:?#]+):\d+):\d+(?=\/|$)/i, "$1");
|
|
51
|
+
}
|
|
52
|
+
export function normalizeSchedulerBaseUrl(value) {
|
|
53
|
+
const raw = normalizeToken(value);
|
|
54
|
+
if (!raw) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const repaired = repairDuplicatePort(raw);
|
|
58
|
+
try {
|
|
59
|
+
const url = new URL(repaired);
|
|
60
|
+
if (url.protocol === "ws:") {
|
|
61
|
+
url.protocol = "http:";
|
|
62
|
+
}
|
|
63
|
+
else if (url.protocol === "wss:") {
|
|
64
|
+
url.protocol = "https:";
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
url.protocol = url.protocol.toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
url.hostname = url.hostname.toLowerCase();
|
|
70
|
+
return url.origin;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return repaired.replace(/\/+$/, "");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
49
76
|
export function getRuntimePairingCode() {
|
|
50
77
|
return pairingCode;
|
|
51
78
|
}
|
|
@@ -56,7 +83,7 @@ export function getRuntimeTokenStoreFilePath() {
|
|
|
56
83
|
return tokenStoreFile();
|
|
57
84
|
}
|
|
58
85
|
function normalizeServerIp(serverBaseUrl) {
|
|
59
|
-
const raw = String(serverBaseUrl || "").trim();
|
|
86
|
+
const raw = normalizeSchedulerBaseUrl(serverBaseUrl) || String(serverBaseUrl || "").trim();
|
|
60
87
|
if (!raw) {
|
|
61
88
|
return "";
|
|
62
89
|
}
|
|
@@ -157,7 +184,11 @@ export function loadRuntimeBinding() {
|
|
|
157
184
|
const raw = fs.readFileSync(bindingFile(), "utf8");
|
|
158
185
|
const parsed = JSON.parse(raw);
|
|
159
186
|
if (parsed?.status === "paired" && parsed.tokenHash) {
|
|
160
|
-
return
|
|
187
|
+
return {
|
|
188
|
+
...parsed,
|
|
189
|
+
schedulerBaseUrl: normalizeSchedulerBaseUrl(parsed.schedulerBaseUrl) ||
|
|
190
|
+
parsed.schedulerBaseUrl,
|
|
191
|
+
};
|
|
161
192
|
}
|
|
162
193
|
}
|
|
163
194
|
catch {
|
|
@@ -195,6 +226,9 @@ export function getRuntimeAccessToken(userId, serverBaseUrl) {
|
|
|
195
226
|
if (scopedToken) {
|
|
196
227
|
return scopedToken;
|
|
197
228
|
}
|
|
229
|
+
if (serverBaseUrl) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
198
232
|
const state = loadRuntimeBinding();
|
|
199
233
|
if (state.status !== "paired") {
|
|
200
234
|
return undefined;
|
|
@@ -210,6 +244,8 @@ export function saveRuntimeBinding(input) {
|
|
|
210
244
|
throw new Error("accessToken must be at least 16 characters");
|
|
211
245
|
}
|
|
212
246
|
const previous = loadRuntimeBinding();
|
|
247
|
+
const schedulerBaseUrl = normalizeSchedulerBaseUrl(input.schedulerBaseUrl) ||
|
|
248
|
+
normalizeSchedulerBaseUrl(previous.schedulerBaseUrl);
|
|
213
249
|
const now = new Date().toISOString();
|
|
214
250
|
const next = {
|
|
215
251
|
status: "paired",
|
|
@@ -217,9 +253,7 @@ export function saveRuntimeBinding(input) {
|
|
|
217
253
|
? String(input.instanceId)
|
|
218
254
|
: previous.instanceId,
|
|
219
255
|
userId: input.userId ? String(input.userId) : previous.userId,
|
|
220
|
-
schedulerBaseUrl
|
|
221
|
-
? String(input.schedulerBaseUrl)
|
|
222
|
-
: previous.schedulerBaseUrl,
|
|
256
|
+
schedulerBaseUrl,
|
|
223
257
|
accessToken,
|
|
224
258
|
tokenHash: hashToken(accessToken),
|
|
225
259
|
createdAt: previous.createdAt || now,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-binding.test.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { normalizeSchedulerBaseUrl } from './runtime-binding.js';
|
|
3
|
+
describe('runtime binding scheduler URL normalization', () => {
|
|
4
|
+
it('repairs duplicated scheduler ports', () => {
|
|
5
|
+
expect(normalizeSchedulerBaseUrl('http://127.0.0.1:8080:8080')).toBe('http://127.0.0.1:8080');
|
|
6
|
+
});
|
|
7
|
+
it('normalizes websocket agent URLs to scheduler HTTP origins', () => {
|
|
8
|
+
expect(normalizeSchedulerBaseUrl('ws://127.0.0.1:8080/ws/agent')).toBe('http://127.0.0.1:8080');
|
|
9
|
+
expect(normalizeSchedulerBaseUrl('wss://example.com/ws/agent')).toBe('https://example.com');
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -12,95 +12,95 @@ describe('yaml-utils', () => {
|
|
|
12
12
|
expect(hasMatchingAgentPath('', 'test')).toBeNull();
|
|
13
13
|
});
|
|
14
14
|
it('should return null when no agent key exists', () => {
|
|
15
|
-
const yaml = `
|
|
16
|
-
other:
|
|
17
|
-
- name: test
|
|
15
|
+
const yaml = `
|
|
16
|
+
other:
|
|
17
|
+
- name: test
|
|
18
18
|
`;
|
|
19
19
|
expect(hasMatchingAgentPath(yaml, 'test')).toBeNull();
|
|
20
20
|
});
|
|
21
21
|
it('should return null when agent is not an array', () => {
|
|
22
|
-
const yaml = `
|
|
23
|
-
agent:
|
|
24
|
-
name: single
|
|
22
|
+
const yaml = `
|
|
23
|
+
agent:
|
|
24
|
+
name: single
|
|
25
25
|
`;
|
|
26
26
|
expect(hasMatchingAgentPath(yaml, 'test')).toBeNull();
|
|
27
27
|
});
|
|
28
28
|
it('should return null when agent has no path', () => {
|
|
29
|
-
const yaml = `
|
|
30
|
-
agent:
|
|
31
|
-
- name: test
|
|
32
|
-
description: no path
|
|
29
|
+
const yaml = `
|
|
30
|
+
agent:
|
|
31
|
+
- name: test
|
|
32
|
+
description: no path
|
|
33
33
|
`;
|
|
34
34
|
expect(hasMatchingAgentPath(yaml, 'test')).toBeNull();
|
|
35
35
|
});
|
|
36
36
|
it('should match exact path', () => {
|
|
37
|
-
const yaml = `
|
|
38
|
-
agent:
|
|
39
|
-
- name: test
|
|
40
|
-
path: src/modules
|
|
37
|
+
const yaml = `
|
|
38
|
+
agent:
|
|
39
|
+
- name: test
|
|
40
|
+
path: src/modules
|
|
41
41
|
`;
|
|
42
42
|
expect(hasMatchingAgentPath(yaml, 'src/modules')).toBe('src/modules');
|
|
43
43
|
});
|
|
44
44
|
it('should match path with different separators (backslash)', () => {
|
|
45
|
-
const yaml = `
|
|
46
|
-
agent:
|
|
47
|
-
- name: test
|
|
48
|
-
path: src/modules
|
|
45
|
+
const yaml = `
|
|
46
|
+
agent:
|
|
47
|
+
- name: test
|
|
48
|
+
path: src/modules
|
|
49
49
|
`;
|
|
50
50
|
expect(hasMatchingAgentPath(yaml, 'src\\modules')).toBe('src/modules');
|
|
51
51
|
});
|
|
52
52
|
it('should match path with forward slash when yaml has backslash', () => {
|
|
53
|
-
const yaml = `
|
|
54
|
-
agent:
|
|
55
|
-
- name: test
|
|
56
|
-
path: src\\modules
|
|
53
|
+
const yaml = `
|
|
54
|
+
agent:
|
|
55
|
+
- name: test
|
|
56
|
+
path: src\\modules
|
|
57
57
|
`;
|
|
58
58
|
expect(hasMatchingAgentPath(yaml, 'src/modules')).toBe('src\\modules');
|
|
59
59
|
});
|
|
60
60
|
it('should match current directory "." path', () => {
|
|
61
|
-
const yaml = `
|
|
62
|
-
agent:
|
|
63
|
-
- name: test
|
|
64
|
-
path: .
|
|
61
|
+
const yaml = `
|
|
62
|
+
agent:
|
|
63
|
+
- name: test
|
|
64
|
+
path: .
|
|
65
65
|
`;
|
|
66
66
|
expect(hasMatchingAgentPath(yaml, '.')).toBe('.');
|
|
67
67
|
});
|
|
68
68
|
it('should match empty path when target is "."', () => {
|
|
69
|
-
const yaml = `
|
|
70
|
-
agent:
|
|
71
|
-
- name: test
|
|
72
|
-
path: ''
|
|
69
|
+
const yaml = `
|
|
70
|
+
agent:
|
|
71
|
+
- name: test
|
|
72
|
+
path: ''
|
|
73
73
|
`;
|
|
74
74
|
expect(hasMatchingAgentPath(yaml, '.')).toBe('');
|
|
75
75
|
});
|
|
76
76
|
it('should return null when no matching path found', () => {
|
|
77
|
-
const yaml = `
|
|
78
|
-
agent:
|
|
79
|
-
- name: test
|
|
80
|
-
path: other/path
|
|
77
|
+
const yaml = `
|
|
78
|
+
agent:
|
|
79
|
+
- name: test
|
|
80
|
+
path: other/path
|
|
81
81
|
`;
|
|
82
82
|
expect(hasMatchingAgentPath(yaml, 'different/path')).toBeNull();
|
|
83
83
|
});
|
|
84
84
|
it('should return first matching path from multiple agents', () => {
|
|
85
|
-
const yaml = `
|
|
86
|
-
agent:
|
|
87
|
-
- name: first
|
|
88
|
-
path: first/path
|
|
89
|
-
- name: second
|
|
90
|
-
path: second/path
|
|
85
|
+
const yaml = `
|
|
86
|
+
agent:
|
|
87
|
+
- name: first
|
|
88
|
+
path: first/path
|
|
89
|
+
- name: second
|
|
90
|
+
path: second/path
|
|
91
91
|
`;
|
|
92
92
|
expect(hasMatchingAgentPath(yaml, 'first/path')).toBe('first/path');
|
|
93
93
|
expect(hasMatchingAgentPath(yaml, 'second/path')).toBe('second/path');
|
|
94
94
|
});
|
|
95
95
|
it('should handle complex YAML with nested objects', () => {
|
|
96
|
-
const yaml = `
|
|
97
|
-
agent:
|
|
98
|
-
- name: complex
|
|
99
|
-
path: complex/path
|
|
100
|
-
project: my-project
|
|
101
|
-
prompt: |
|
|
102
|
-
This is a multi-line
|
|
103
|
-
prompt
|
|
96
|
+
const yaml = `
|
|
97
|
+
agent:
|
|
98
|
+
- name: complex
|
|
99
|
+
path: complex/path
|
|
100
|
+
project: my-project
|
|
101
|
+
prompt: |
|
|
102
|
+
This is a multi-line
|
|
103
|
+
prompt
|
|
104
104
|
`;
|
|
105
105
|
expect(hasMatchingAgentPath(yaml, 'complex/path')).toBe('complex/path');
|
|
106
106
|
});
|
|
@@ -113,12 +113,12 @@ agent:
|
|
|
113
113
|
expect(result.isTemplate).toBe(false);
|
|
114
114
|
});
|
|
115
115
|
it('should return matching agent config', () => {
|
|
116
|
-
const yaml = `
|
|
117
|
-
agent:
|
|
118
|
-
- name: my-agent
|
|
119
|
-
path: src/modules
|
|
120
|
-
project: my-project
|
|
121
|
-
prompt: test prompt
|
|
116
|
+
const yaml = `
|
|
117
|
+
agent:
|
|
118
|
+
- name: my-agent
|
|
119
|
+
path: src/modules
|
|
120
|
+
project: my-project
|
|
121
|
+
prompt: test prompt
|
|
122
122
|
`;
|
|
123
123
|
const result = extractAgentConfigOrTemplate(yaml, 'src/modules');
|
|
124
124
|
expect(result.isTemplate).toBe(false);
|
|
@@ -127,11 +127,11 @@ agent:
|
|
|
127
127
|
expect(result.content).toContain('my-project');
|
|
128
128
|
});
|
|
129
129
|
it('should generate template when no matching agent found', () => {
|
|
130
|
-
const yaml = `
|
|
131
|
-
agent:
|
|
132
|
-
- name: other-agent
|
|
133
|
-
path: other/path
|
|
134
|
-
project: existing-project
|
|
130
|
+
const yaml = `
|
|
131
|
+
agent:
|
|
132
|
+
- name: other-agent
|
|
133
|
+
path: other/path
|
|
134
|
+
project: existing-project
|
|
135
135
|
`;
|
|
136
136
|
const result = extractAgentConfigOrTemplate(yaml, 'new/path');
|
|
137
137
|
expect(result.isTemplate).toBe(true);
|
|
@@ -139,10 +139,10 @@ agent:
|
|
|
139
139
|
expect(result.content).toContain('new/path');
|
|
140
140
|
});
|
|
141
141
|
it('should generate template with empty project when no agents have project', () => {
|
|
142
|
-
const yaml = `
|
|
143
|
-
agent:
|
|
144
|
-
- name: agent1
|
|
145
|
-
path: path1
|
|
142
|
+
const yaml = `
|
|
143
|
+
agent:
|
|
144
|
+
- name: agent1
|
|
145
|
+
path: path1
|
|
146
146
|
`;
|
|
147
147
|
const result = extractAgentConfigOrTemplate(yaml, 'new/path');
|
|
148
148
|
expect(result.isTemplate).toBe(true);
|
|
@@ -150,21 +150,21 @@ agent:
|
|
|
150
150
|
expect(result.content).toContain('project: ""');
|
|
151
151
|
});
|
|
152
152
|
it('should match current directory "." path', () => {
|
|
153
|
-
const yaml = `
|
|
154
|
-
agent:
|
|
155
|
-
- name: root-agent
|
|
156
|
-
path: .
|
|
157
|
-
project: root-project
|
|
153
|
+
const yaml = `
|
|
154
|
+
agent:
|
|
155
|
+
- name: root-agent
|
|
156
|
+
path: .
|
|
157
|
+
project: root-project
|
|
158
158
|
`;
|
|
159
159
|
const result = extractAgentConfigOrTemplate(yaml, '.');
|
|
160
160
|
expect(result.isTemplate).toBe(false);
|
|
161
161
|
expect(result.content).toContain('root-agent');
|
|
162
162
|
});
|
|
163
163
|
it('should normalize path separators in output', () => {
|
|
164
|
-
const yaml = `
|
|
165
|
-
agent:
|
|
166
|
-
- name: test
|
|
167
|
-
path: src\\modules
|
|
164
|
+
const yaml = `
|
|
165
|
+
agent:
|
|
166
|
+
- name: test
|
|
167
|
+
path: src\\modules
|
|
168
168
|
`;
|
|
169
169
|
const result = extractAgentConfigOrTemplate(yaml, 'src/modules');
|
|
170
170
|
expect(result.isTemplate).toBe(false);
|
|
@@ -192,78 +192,78 @@ agent:
|
|
|
192
192
|
expect(autoFillPathInYml(invalidYaml, 'test/path')).toBe(invalidYaml);
|
|
193
193
|
});
|
|
194
194
|
it('should return original content when no agent key', () => {
|
|
195
|
-
const yaml = `
|
|
196
|
-
other:
|
|
197
|
-
- name: test
|
|
195
|
+
const yaml = `
|
|
196
|
+
other:
|
|
197
|
+
- name: test
|
|
198
198
|
`;
|
|
199
199
|
expect(autoFillPathInYml(yaml, 'test/path')).toBe(yaml);
|
|
200
200
|
});
|
|
201
201
|
it('should fill empty path', () => {
|
|
202
|
-
const yaml = `
|
|
203
|
-
agent:
|
|
204
|
-
- name: test
|
|
205
|
-
path: ''
|
|
206
|
-
project: my-project
|
|
202
|
+
const yaml = `
|
|
203
|
+
agent:
|
|
204
|
+
- name: test
|
|
205
|
+
path: ''
|
|
206
|
+
project: my-project
|
|
207
207
|
`;
|
|
208
208
|
const result = autoFillPathInYml(yaml, 'new/path');
|
|
209
209
|
expect(result).toContain('new/path');
|
|
210
210
|
expect(result).not.toContain("path: ''");
|
|
211
211
|
});
|
|
212
212
|
it('should fill undefined path', () => {
|
|
213
|
-
const yaml = `
|
|
214
|
-
agent:
|
|
215
|
-
- name: test
|
|
216
|
-
project: my-project
|
|
213
|
+
const yaml = `
|
|
214
|
+
agent:
|
|
215
|
+
- name: test
|
|
216
|
+
project: my-project
|
|
217
217
|
`;
|
|
218
218
|
const result = autoFillPathInYml(yaml, 'new/path');
|
|
219
219
|
expect(result).toContain('new/path');
|
|
220
220
|
});
|
|
221
221
|
it('should fill null path', () => {
|
|
222
|
-
const yaml = `
|
|
223
|
-
agent:
|
|
224
|
-
- name: test
|
|
225
|
-
path: null
|
|
222
|
+
const yaml = `
|
|
223
|
+
agent:
|
|
224
|
+
- name: test
|
|
225
|
+
path: null
|
|
226
226
|
`;
|
|
227
227
|
const result = autoFillPathInYml(yaml, 'new/path');
|
|
228
228
|
expect(result).toContain('new/path');
|
|
229
229
|
});
|
|
230
230
|
it('should fill whitespace-only path', () => {
|
|
231
|
-
const yaml = `
|
|
232
|
-
agent:
|
|
233
|
-
- name: test
|
|
234
|
-
path: ' '
|
|
231
|
+
const yaml = `
|
|
232
|
+
agent:
|
|
233
|
+
- name: test
|
|
234
|
+
path: ' '
|
|
235
235
|
`;
|
|
236
236
|
const result = autoFillPathInYml(yaml, 'new/path');
|
|
237
237
|
expect(result).toContain('new/path');
|
|
238
238
|
});
|
|
239
239
|
it('should not modify existing non-empty path', () => {
|
|
240
|
-
const yaml = `
|
|
241
|
-
agent:
|
|
242
|
-
- name: test
|
|
243
|
-
path: existing/path
|
|
240
|
+
const yaml = `
|
|
241
|
+
agent:
|
|
242
|
+
- name: test
|
|
243
|
+
path: existing/path
|
|
244
244
|
`;
|
|
245
245
|
const result = autoFillPathInYml(yaml, 'new/path');
|
|
246
246
|
expect(result).toContain('existing/path');
|
|
247
247
|
expect(result).not.toContain('new/path');
|
|
248
248
|
});
|
|
249
249
|
it('should handle multiple agents', () => {
|
|
250
|
-
const yaml = `
|
|
251
|
-
agent:
|
|
252
|
-
- name: agent1
|
|
253
|
-
path: ''
|
|
254
|
-
- name: agent2
|
|
255
|
-
path: existing/path
|
|
256
|
-
- name: agent3
|
|
250
|
+
const yaml = `
|
|
251
|
+
agent:
|
|
252
|
+
- name: agent1
|
|
253
|
+
path: ''
|
|
254
|
+
- name: agent2
|
|
255
|
+
path: existing/path
|
|
256
|
+
- name: agent3
|
|
257
257
|
`;
|
|
258
258
|
const result = autoFillPathInYml(yaml, 'new/path');
|
|
259
259
|
// agent1 should have path filled
|
|
260
260
|
expect(result).toContain('new/path');
|
|
261
261
|
});
|
|
262
262
|
it('should return original content when no modification needed', () => {
|
|
263
|
-
const yaml = `
|
|
264
|
-
agent:
|
|
265
|
-
- name: test
|
|
266
|
-
path: existing/path
|
|
263
|
+
const yaml = `
|
|
264
|
+
agent:
|
|
265
|
+
- name: test
|
|
266
|
+
path: existing/path
|
|
267
267
|
`;
|
|
268
268
|
const result = autoFillPathInYml(yaml, 'new/path');
|
|
269
269
|
expect(result).toBe(yaml);
|
|
@@ -296,11 +296,11 @@ agent:
|
|
|
296
296
|
it('should load config from workspace directory with matching path "."', async () => {
|
|
297
297
|
const configPath = path.join(workspacePath, relativePath);
|
|
298
298
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
299
|
-
await fs.writeFile(configPath, `
|
|
300
|
-
agent:
|
|
301
|
-
- name: local-agent
|
|
302
|
-
path: .
|
|
303
|
-
project: local-project
|
|
299
|
+
await fs.writeFile(configPath, `
|
|
300
|
+
agent:
|
|
301
|
+
- name: local-agent
|
|
302
|
+
path: .
|
|
303
|
+
project: local-project
|
|
304
304
|
`, 'utf-8');
|
|
305
305
|
const result = await loadYmlWithUpwardSearch(workspacePath, relativePath);
|
|
306
306
|
expect(result.content).not.toBeNull();
|
|
@@ -312,11 +312,11 @@ agent:
|
|
|
312
312
|
// 在 project/module 目录创建配置,匹配 src 路径
|
|
313
313
|
const parentConfigPath = path.join(tempRoot, 'project', 'module', relativePath);
|
|
314
314
|
await fs.mkdir(path.dirname(parentConfigPath), { recursive: true });
|
|
315
|
-
await fs.writeFile(parentConfigPath, `
|
|
316
|
-
agent:
|
|
317
|
-
- name: module-agent
|
|
318
|
-
path: src
|
|
319
|
-
project: module-project
|
|
315
|
+
await fs.writeFile(parentConfigPath, `
|
|
316
|
+
agent:
|
|
317
|
+
- name: module-agent
|
|
318
|
+
path: src
|
|
319
|
+
project: module-project
|
|
320
320
|
`, 'utf-8');
|
|
321
321
|
const result = await loadYmlWithUpwardSearch(workspacePath, relativePath);
|
|
322
322
|
expect(result.content).not.toBeNull();
|
|
@@ -328,10 +328,10 @@ agent:
|
|
|
328
328
|
// 在 project/module 目录创建配置,但不匹配 src 路径
|
|
329
329
|
const parentConfigPath = path.join(tempRoot, 'project', 'module', relativePath);
|
|
330
330
|
await fs.mkdir(path.dirname(parentConfigPath), { recursive: true });
|
|
331
|
-
await fs.writeFile(parentConfigPath, `
|
|
332
|
-
agent:
|
|
333
|
-
- name: other-agent
|
|
334
|
-
path: other/path
|
|
331
|
+
await fs.writeFile(parentConfigPath, `
|
|
332
|
+
agent:
|
|
333
|
+
- name: other-agent
|
|
334
|
+
path: other/path
|
|
335
335
|
`, 'utf-8');
|
|
336
336
|
const result = await loadYmlWithUpwardSearch(workspacePath, relativePath);
|
|
337
337
|
expect(result.content).not.toBeNull();
|
|
@@ -342,17 +342,17 @@ agent:
|
|
|
342
342
|
// 在 workspace 目录创建匹配的配置
|
|
343
343
|
const localConfigPath = path.join(workspacePath, relativePath);
|
|
344
344
|
await fs.mkdir(path.dirname(localConfigPath), { recursive: true });
|
|
345
|
-
await fs.writeFile(localConfigPath, `
|
|
346
|
-
agent:
|
|
347
|
-
- name: local-agent
|
|
348
|
-
path: .
|
|
345
|
+
await fs.writeFile(localConfigPath, `
|
|
346
|
+
agent:
|
|
347
|
+
- name: local-agent
|
|
348
|
+
path: .
|
|
349
349
|
`, 'utf-8');
|
|
350
350
|
// 在 parent 目录也创建配置
|
|
351
351
|
const parentConfigPath = path.join(tempRoot, 'project', 'module', relativePath);
|
|
352
|
-
await fs.writeFile(parentConfigPath, `
|
|
353
|
-
agent:
|
|
354
|
-
- name: parent-agent
|
|
355
|
-
path: src
|
|
352
|
+
await fs.writeFile(parentConfigPath, `
|
|
353
|
+
agent:
|
|
354
|
+
- name: parent-agent
|
|
355
|
+
path: src
|
|
356
356
|
`, 'utf-8');
|
|
357
357
|
const result = await loadYmlWithUpwardSearch(workspacePath, relativePath);
|
|
358
358
|
// 应该使用 local 的配置,因为更近且匹配
|