aws-runtime-bridge 1.0.2 → 1.1.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.
Files changed (148) hide show
  1. package/dist/adapter/adapter.test.js +4 -4
  2. package/dist/adapter/types.d.ts.map +1 -1
  3. package/dist/adapter/types.js +0 -7
  4. package/dist/adapter/types.test.js +5 -53
  5. package/dist/config.d.ts +1 -1
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +2 -2
  8. package/dist/middleware/auth.d.ts.map +1 -1
  9. package/dist/middleware/auth.js +4 -0
  10. package/dist/routes/instance.d.ts.map +1 -1
  11. package/dist/routes/instance.js +88 -34
  12. package/dist/routes/runtime-binding.d.ts.map +1 -1
  13. package/dist/routes/runtime-binding.js +59 -2
  14. package/dist/routes/sessions.js +1 -1
  15. package/dist/routes/terminal.d.ts.map +1 -1
  16. package/dist/routes/terminal.js +48 -14
  17. package/dist/routes/terminal.test.js +6 -2
  18. package/dist/services/agent-process-manager.js +4 -4
  19. package/dist/services/auto-register.d.ts +12 -1
  20. package/dist/services/auto-register.d.ts.map +1 -1
  21. package/dist/services/auto-register.js +206 -42
  22. package/dist/services/aws-client-agent-mcp.test.js +3 -0
  23. package/dist/services/mcp-launch-binding-queue.d.ts +36 -0
  24. package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -0
  25. package/dist/services/mcp-launch-binding-queue.js +92 -0
  26. package/dist/services/mcp-launch-binding-queue.test.d.ts +2 -0
  27. package/dist/services/mcp-launch-binding-queue.test.d.ts.map +1 -0
  28. package/dist/services/mcp-launch-binding-queue.test.js +107 -0
  29. package/dist/services/orphan-monitor.js +1 -1
  30. package/dist/services/process-detector.d.ts +1 -1
  31. package/dist/services/process-detector.d.ts.map +1 -1
  32. package/dist/services/process-detector.js +2 -11
  33. package/dist/services/process-registry.d.ts +1 -0
  34. package/dist/services/process-registry.d.ts.map +1 -1
  35. package/dist/services/process-registry.js +129 -108
  36. package/dist/services/runtime-binding.d.ts +11 -1
  37. package/dist/services/runtime-binding.d.ts.map +1 -1
  38. package/dist/services/runtime-binding.js +130 -4
  39. package/dist/services/terminal-persistence.d.ts.map +1 -1
  40. package/dist/services/terminal-persistence.js +47 -37
  41. package/dist/services/terminal-persistence.test.js +47 -1
  42. package/dist/utils/file-utils.d.ts +3 -0
  43. package/dist/utils/file-utils.d.ts.map +1 -1
  44. package/dist/utils/file-utils.js +32 -0
  45. package/package/aws-client-agent-mcp/README.md +288 -288
  46. package/package.json +76 -76
  47. package/dist/routes/aws-mcp.d.ts +0 -10
  48. package/dist/routes/aws-mcp.d.ts.map +0 -1
  49. package/dist/routes/aws-mcp.js +0 -74
  50. package/dist/routes/aws-mcp.test.d.ts +0 -2
  51. package/dist/routes/aws-mcp.test.d.ts.map +0 -1
  52. package/dist/routes/aws-mcp.test.js +0 -42
  53. package/dist/routes/memory.d.ts +0 -13
  54. package/dist/routes/memory.d.ts.map +0 -1
  55. package/dist/routes/memory.js +0 -429
  56. package/dist/services/aws-mcp-http.d.ts +0 -11
  57. package/dist/services/aws-mcp-http.d.ts.map +0 -1
  58. package/dist/services/aws-mcp-http.js +0 -225
  59. package/dist/services/aws-mcp-http.test.d.ts +0 -2
  60. package/dist/services/aws-mcp-http.test.d.ts.map +0 -1
  61. package/dist/services/aws-mcp-http.test.js +0 -27
  62. package/dist/services/easytier-manager.d.ts +0 -106
  63. package/dist/services/easytier-manager.d.ts.map +0 -1
  64. package/dist/services/easytier-manager.js +0 -331
  65. package/dist/services/easytier-manager.test.d.ts +0 -5
  66. package/dist/services/easytier-manager.test.d.ts.map +0 -1
  67. package/dist/services/easytier-manager.test.js +0 -98
  68. package/dist/services/memory-service.d.ts +0 -195
  69. package/dist/services/memory-service.d.ts.map +0 -1
  70. package/dist/services/memory-service.js +0 -650
  71. package/dist/services/session-lookup.d.ts +0 -20
  72. package/dist/services/session-lookup.d.ts.map +0 -1
  73. package/dist/services/session-lookup.js +0 -43
  74. package/dist/services/user-api-key-service.d.ts +0 -28
  75. package/dist/services/user-api-key-service.d.ts.map +0 -1
  76. package/dist/services/user-api-key-service.js +0 -75
  77. package/node_modules/@cc-switch/sdk/dist/adapters/common.d.ts +0 -38
  78. package/node_modules/@cc-switch/sdk/dist/adapters/common.d.ts.map +0 -1
  79. package/node_modules/@cc-switch/sdk/dist/adapters/common.js +0 -47
  80. package/node_modules/@cc-switch/sdk/dist/adapters/index.d.ts +0 -5
  81. package/node_modules/@cc-switch/sdk/dist/adapters/index.d.ts.map +0 -1
  82. package/node_modules/@cc-switch/sdk/dist/adapters/index.js +0 -28
  83. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claude.d.ts +0 -10
  84. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claude.d.ts.map +0 -1
  85. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claude.js +0 -39
  86. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claudecode.d.ts +0 -10
  87. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claudecode.d.ts.map +0 -1
  88. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claudecode.js +0 -40
  89. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.d.ts +0 -18
  90. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.d.ts.map +0 -1
  91. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.js +0 -63
  92. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.test.d.ts +0 -2
  93. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.test.d.ts.map +0 -1
  94. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.test.js +0 -86
  95. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-placeholder.d.ts +0 -9
  96. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-placeholder.d.ts.map +0 -1
  97. package/node_modules/@cc-switch/sdk/dist/adapters/mcp-placeholder.js +0 -14
  98. package/node_modules/@cc-switch/sdk/dist/adapters/skill-claude.d.ts +0 -10
  99. package/node_modules/@cc-switch/sdk/dist/adapters/skill-claude.d.ts.map +0 -1
  100. package/node_modules/@cc-switch/sdk/dist/adapters/skill-claude.js +0 -51
  101. package/node_modules/@cc-switch/sdk/dist/adapters/skill-claudecode.d.ts +0 -10
  102. package/node_modules/@cc-switch/sdk/dist/adapters/skill-claudecode.d.ts.map +0 -1
  103. package/node_modules/@cc-switch/sdk/dist/adapters/skill-claudecode.js +0 -51
  104. package/node_modules/@cc-switch/sdk/dist/adapters/skill-opencode.d.ts +0 -10
  105. package/node_modules/@cc-switch/sdk/dist/adapters/skill-opencode.d.ts.map +0 -1
  106. package/node_modules/@cc-switch/sdk/dist/adapters/skill-opencode.js +0 -51
  107. package/node_modules/@cc-switch/sdk/dist/adapters/skill-placeholder.d.ts +0 -9
  108. package/node_modules/@cc-switch/sdk/dist/adapters/skill-placeholder.d.ts.map +0 -1
  109. package/node_modules/@cc-switch/sdk/dist/adapters/skill-placeholder.js +0 -14
  110. package/node_modules/@cc-switch/sdk/dist/services/instance-service.d.ts +0 -78
  111. package/node_modules/@cc-switch/sdk/dist/services/instance-service.d.ts.map +0 -1
  112. package/node_modules/@cc-switch/sdk/dist/services/instance-service.js +0 -180
  113. package/package/cc-switch-sdk/dist/adapters/common.d.ts +0 -38
  114. package/package/cc-switch-sdk/dist/adapters/common.d.ts.map +0 -1
  115. package/package/cc-switch-sdk/dist/adapters/common.js +0 -47
  116. package/package/cc-switch-sdk/dist/adapters/index.d.ts +0 -5
  117. package/package/cc-switch-sdk/dist/adapters/index.d.ts.map +0 -1
  118. package/package/cc-switch-sdk/dist/adapters/index.js +0 -28
  119. package/package/cc-switch-sdk/dist/adapters/mcp-claude.d.ts +0 -10
  120. package/package/cc-switch-sdk/dist/adapters/mcp-claude.d.ts.map +0 -1
  121. package/package/cc-switch-sdk/dist/adapters/mcp-claude.js +0 -39
  122. package/package/cc-switch-sdk/dist/adapters/mcp-claudecode.d.ts +0 -10
  123. package/package/cc-switch-sdk/dist/adapters/mcp-claudecode.d.ts.map +0 -1
  124. package/package/cc-switch-sdk/dist/adapters/mcp-claudecode.js +0 -40
  125. package/package/cc-switch-sdk/dist/adapters/mcp-opencode.d.ts +0 -18
  126. package/package/cc-switch-sdk/dist/adapters/mcp-opencode.d.ts.map +0 -1
  127. package/package/cc-switch-sdk/dist/adapters/mcp-opencode.js +0 -63
  128. package/package/cc-switch-sdk/dist/adapters/mcp-opencode.test.d.ts +0 -2
  129. package/package/cc-switch-sdk/dist/adapters/mcp-opencode.test.d.ts.map +0 -1
  130. package/package/cc-switch-sdk/dist/adapters/mcp-opencode.test.js +0 -86
  131. package/package/cc-switch-sdk/dist/adapters/mcp-placeholder.d.ts +0 -9
  132. package/package/cc-switch-sdk/dist/adapters/mcp-placeholder.d.ts.map +0 -1
  133. package/package/cc-switch-sdk/dist/adapters/mcp-placeholder.js +0 -14
  134. package/package/cc-switch-sdk/dist/adapters/skill-claude.d.ts +0 -10
  135. package/package/cc-switch-sdk/dist/adapters/skill-claude.d.ts.map +0 -1
  136. package/package/cc-switch-sdk/dist/adapters/skill-claude.js +0 -51
  137. package/package/cc-switch-sdk/dist/adapters/skill-claudecode.d.ts +0 -10
  138. package/package/cc-switch-sdk/dist/adapters/skill-claudecode.d.ts.map +0 -1
  139. package/package/cc-switch-sdk/dist/adapters/skill-claudecode.js +0 -51
  140. package/package/cc-switch-sdk/dist/adapters/skill-opencode.d.ts +0 -10
  141. package/package/cc-switch-sdk/dist/adapters/skill-opencode.d.ts.map +0 -1
  142. package/package/cc-switch-sdk/dist/adapters/skill-opencode.js +0 -51
  143. package/package/cc-switch-sdk/dist/adapters/skill-placeholder.d.ts +0 -9
  144. package/package/cc-switch-sdk/dist/adapters/skill-placeholder.d.ts.map +0 -1
  145. package/package/cc-switch-sdk/dist/adapters/skill-placeholder.js +0 -14
  146. package/package/cc-switch-sdk/dist/services/instance-service.d.ts +0 -78
  147. package/package/cc-switch-sdk/dist/services/instance-service.d.ts.map +0 -1
  148. package/package/cc-switch-sdk/dist/services/instance-service.js +0 -180
@@ -7,7 +7,7 @@ import os from 'node:os';
7
7
  import path from 'node:path';
8
8
  import { promises as fs } from 'node:fs';
9
9
  import { createLogger } from '../utils/logger.js';
10
- import { ensureDir } from '../utils/file-utils.js';
10
+ import { atomicWriteJsonFile, withFileLock } from '../utils/file-utils.js';
11
11
  const log = createLogger('process-registry');
12
12
  /**
13
13
  * 获取持久化注册表文件路径
@@ -47,15 +47,17 @@ export class ProcessRegistry {
47
47
  async persist() {
48
48
  const filePath = getProcessRegistryFile();
49
49
  try {
50
- await ensureDir(path.dirname(filePath));
51
50
  const records = Array.from(this.records.values());
52
- await fs.writeFile(filePath, JSON.stringify(records, null, 2), 'utf-8');
51
+ await atomicWriteJsonFile(filePath, records);
53
52
  log.debug(`[persist] 已持久化 ${records.length} 条进程记录`);
54
53
  }
55
54
  catch (error) {
56
55
  log.error('[persist] 持久化失败:', error);
57
56
  }
58
57
  }
58
+ async mutate(operation) {
59
+ return withFileLock(getProcessRegistryFile(), async () => operation());
60
+ }
59
61
  /**
60
62
  * 注册新进程
61
63
  *
@@ -63,19 +65,21 @@ export class ProcessRegistry {
63
65
  * @returns 是否注册成功
64
66
  */
65
67
  async register(record) {
66
- const fullRecord = {
67
- ...record,
68
- createdAt: new Date().toISOString(),
69
- };
70
- // 如果已存在相同 agentId,先移除旧的映射
71
- const existing = this.records.get(record.agentId);
72
- if (existing) {
73
- this.pidToAgentId.delete(existing.pid);
74
- }
75
- this.records.set(record.agentId, fullRecord);
76
- this.pidToAgentId.set(record.pid, record.agentId);
77
- log.info(`[register] 注册进程: agentId=${record.agentId}, pid=${record.pid}, mode=${record.mode}, state=${record.state}`);
78
- await this.persist();
68
+ await this.mutate(async () => {
69
+ const fullRecord = {
70
+ ...record,
71
+ createdAt: new Date().toISOString(),
72
+ };
73
+ // 如果已存在相同 agentId,先移除旧的映射
74
+ const existing = this.records.get(record.agentId);
75
+ if (existing) {
76
+ this.pidToAgentId.delete(existing.pid);
77
+ }
78
+ this.records.set(record.agentId, fullRecord);
79
+ this.pidToAgentId.set(record.pid, record.agentId);
80
+ log.info(`[register] 注册进程: agentId=${record.agentId}, pid=${record.pid}, mode=${record.mode}, state=${record.state}`);
81
+ await this.persist();
82
+ });
79
83
  }
80
84
  /**
81
85
  * 更新进程记录
@@ -85,20 +89,22 @@ export class ProcessRegistry {
85
89
  * @returns 是否更新成功
86
90
  */
87
91
  async update(agentId, updates) {
88
- const record = this.records.get(agentId);
89
- if (!record) {
90
- log.warn(`[update] 未找到进程: agentId=${agentId}`);
91
- return false;
92
- }
93
- // 如果更新包含 pid,需要更新 pid 映射
94
- if (updates.pid !== undefined && updates.pid !== record.pid) {
95
- this.pidToAgentId.delete(record.pid);
96
- this.pidToAgentId.set(updates.pid, agentId);
97
- }
98
- Object.assign(record, updates);
99
- log.debug(`[update] 更新进程: agentId=${agentId}, updates=${JSON.stringify(updates)}`);
100
- await this.persist();
101
- return true;
92
+ return this.mutate(async () => {
93
+ const record = this.records.get(agentId);
94
+ if (!record) {
95
+ log.warn(`[update] 未找到进程: agentId=${agentId}`);
96
+ return false;
97
+ }
98
+ // 如果更新包含 pid,需要更新 pid 映射
99
+ if (updates.pid !== undefined && updates.pid !== record.pid) {
100
+ this.pidToAgentId.delete(record.pid);
101
+ this.pidToAgentId.set(updates.pid, agentId);
102
+ }
103
+ Object.assign(record, updates);
104
+ log.debug(`[update] 更新进程: agentId=${agentId}, updates=${JSON.stringify(updates)}`);
105
+ await this.persist();
106
+ return true;
107
+ });
102
108
  }
103
109
  /**
104
110
  * 通过 agentId 获取进程记录
@@ -164,16 +170,18 @@ export class ProcessRegistry {
164
170
  * @returns 是否标记成功
165
171
  */
166
172
  async markOrphaned(agentId) {
167
- const record = this.records.get(agentId);
168
- if (!record) {
169
- log.warn(`[markOrphaned] 未找到进程: agentId=${agentId}`);
170
- return false;
171
- }
172
- record.state = 'orphaned';
173
- record.orphanedAt = new Date().toISOString();
174
- log.info(`[markOrphaned] 标记进程为孤儿: agentId=${agentId}, orphanedAt=${record.orphanedAt}`);
175
- await this.persist();
176
- return true;
173
+ return this.mutate(async () => {
174
+ const record = this.records.get(agentId);
175
+ if (!record) {
176
+ log.warn(`[markOrphaned] 未找到进程: agentId=${agentId}`);
177
+ return false;
178
+ }
179
+ record.state = 'orphaned';
180
+ record.orphanedAt = new Date().toISOString();
181
+ log.info(`[markOrphaned] 标记进程为孤儿: agentId=${agentId}, orphanedAt=${record.orphanedAt}`);
182
+ await this.persist();
183
+ return true;
184
+ });
177
185
  }
178
186
  /**
179
187
  * 标记进程为终止状态
@@ -182,15 +190,17 @@ export class ProcessRegistry {
182
190
  * @returns 是否标记成功
183
191
  */
184
192
  async markTerminated(agentId) {
185
- const record = this.records.get(agentId);
186
- if (!record) {
187
- log.warn(`[markTerminated] 未找到进程: agentId=${agentId}`);
188
- return false;
189
- }
190
- record.state = 'terminated';
191
- log.info(`[markTerminated] 标记进程为终止: agentId=${agentId}`);
192
- await this.persist();
193
- return true;
193
+ return this.mutate(async () => {
194
+ const record = this.records.get(agentId);
195
+ if (!record) {
196
+ log.warn(`[markTerminated] 未找到进程: agentId=${agentId}`);
197
+ return false;
198
+ }
199
+ record.state = 'terminated';
200
+ log.info(`[markTerminated] 标记进程为终止: agentId=${agentId}`);
201
+ await this.persist();
202
+ return true;
203
+ });
194
204
  }
195
205
  /**
196
206
  * 移除进程记录
@@ -199,16 +209,18 @@ export class ProcessRegistry {
199
209
  * @returns 是否移除成功
200
210
  */
201
211
  async remove(agentId) {
202
- const record = this.records.get(agentId);
203
- if (!record) {
204
- log.warn(`[remove] 未找到进程: agentId=${agentId}`);
205
- return false;
206
- }
207
- this.records.delete(agentId);
208
- this.pidToAgentId.delete(record.pid);
209
- log.info(`[remove] 移除进程: agentId=${agentId}, pid=${record.pid}`);
210
- await this.persist();
211
- return true;
212
+ return this.mutate(async () => {
213
+ const record = this.records.get(agentId);
214
+ if (!record) {
215
+ log.warn(`[remove] 未找到进程: agentId=${agentId}`);
216
+ return false;
217
+ }
218
+ this.records.delete(agentId);
219
+ this.pidToAgentId.delete(record.pid);
220
+ log.info(`[remove] 移除进程: agentId=${agentId}, pid=${record.pid}`);
221
+ await this.persist();
222
+ return true;
223
+ });
212
224
  }
213
225
  /**
214
226
  * 更新进程心跳时间
@@ -217,15 +229,17 @@ export class ProcessRegistry {
217
229
  * @returns 是否更新成功
218
230
  */
219
231
  async updateHeartbeat(agentId) {
220
- const record = this.records.get(agentId);
221
- if (!record) {
222
- log.warn(`[updateHeartbeat] 未找到进程: agentId=${agentId}`);
223
- return false;
224
- }
225
- record.lastHeartbeat = new Date().toISOString();
226
- log.debug(`[updateHeartbeat] 更新心跳: agentId=${agentId}, lastHeartbeat=${record.lastHeartbeat}`);
227
- await this.persist();
228
- return true;
232
+ return this.mutate(async () => {
233
+ const record = this.records.get(agentId);
234
+ if (!record) {
235
+ log.warn(`[updateHeartbeat] 未找到进程: agentId=${agentId}`);
236
+ return false;
237
+ }
238
+ record.lastHeartbeat = new Date().toISOString();
239
+ log.debug(`[updateHeartbeat] 更新心跳: agentId=${agentId}, lastHeartbeat=${record.lastHeartbeat}`);
240
+ await this.persist();
241
+ return true;
242
+ });
229
243
  }
230
244
  /**
231
245
  * 更新进程健康状态
@@ -235,15 +249,17 @@ export class ProcessRegistry {
235
249
  * @returns 是否更新成功
236
250
  */
237
251
  async updateHealth(agentId, health) {
238
- const record = this.records.get(agentId);
239
- if (!record) {
240
- log.warn(`[updateHealth] 未找到进程: agentId=${agentId}`);
241
- return false;
242
- }
243
- record.healthStatus = health;
244
- log.debug(`[updateHealth] 更新健康状态: agentId=${agentId}, health=${JSON.stringify(health)}`);
245
- await this.persist();
246
- return true;
252
+ return this.mutate(async () => {
253
+ const record = this.records.get(agentId);
254
+ if (!record) {
255
+ log.warn(`[updateHealth] 未找到进程: agentId=${agentId}`);
256
+ return false;
257
+ }
258
+ record.healthStatus = health;
259
+ log.debug(`[updateHealth] 更新健康状态: agentId=${agentId}, health=${JSON.stringify(health)}`);
260
+ await this.persist();
261
+ return true;
262
+ });
247
263
  }
248
264
  /**
249
265
  * 从持久化会话重建注册表
@@ -251,30 +267,32 @@ export class ProcessRegistry {
251
267
  * @param sessions - 持久化会话数组
252
268
  */
253
269
  async rebuildFromPersisted(sessions) {
254
- log.info(`[rebuildFromPersisted] ${sessions.length} 个持久化会话重建注册表`);
255
- this.records.clear();
256
- this.pidToAgentId.clear();
257
- for (const session of sessions) {
258
- if (!session.pid) {
259
- log.warn(`[rebuildFromPersisted] 跳过无 PID 的会话: agentId=${session.agentId}`);
260
- continue;
270
+ await this.mutate(async () => {
271
+ log.info(`[rebuildFromPersisted] 从 ${sessions.length} 个持久化会话重建注册表`);
272
+ this.records.clear();
273
+ this.pidToAgentId.clear();
274
+ for (const session of sessions) {
275
+ if (!session.pid) {
276
+ log.warn(`[rebuildFromPersisted] 跳过无 PID 的会话: agentId=${session.agentId}`);
277
+ continue;
278
+ }
279
+ const record = {
280
+ agentId: session.agentId,
281
+ sessionId: session.sessionId,
282
+ pid: session.pid,
283
+ mode: session.mode || 'sdk',
284
+ state: 'unknown', // 重建时状态未知,需要外部检测
285
+ workspacePath: session.workspacePath || '',
286
+ command: session.command || '',
287
+ createdAt: new Date().toISOString(),
288
+ };
289
+ this.records.set(record.agentId, record);
290
+ this.pidToAgentId.set(record.pid, record.agentId);
291
+ log.debug(`[rebuildFromPersisted] 重建进程记录: agentId=${record.agentId}, pid=${record.pid}`);
261
292
  }
262
- const record = {
263
- agentId: session.agentId,
264
- sessionId: session.sessionId,
265
- pid: session.pid,
266
- mode: session.mode || 'sdk',
267
- state: 'unknown', // 重建时状态未知,需要外部检测
268
- workspacePath: session.workspacePath || '',
269
- command: session.command || '',
270
- createdAt: new Date().toISOString(),
271
- };
272
- this.records.set(record.agentId, record);
273
- this.pidToAgentId.set(record.pid, record.agentId);
274
- log.debug(`[rebuildFromPersisted] 重建进程记录: agentId=${record.agentId}, pid=${record.pid}`);
275
- }
276
- log.info(`[rebuildFromPersisted] 重建完成,共 ${this.records.size} 条记录`);
277
- await this.persist();
293
+ log.info(`[rebuildFromPersisted] 重建完成,共 ${this.records.size} 条记录`);
294
+ await this.persist();
295
+ });
278
296
  }
279
297
  /**
280
298
  * 导出所有进程记录用于持久化
@@ -308,11 +326,13 @@ export class ProcessRegistry {
308
326
  * 清空所有进程记录
309
327
  */
310
328
  async clear() {
311
- const count = this.records.size;
312
- this.records.clear();
313
- this.pidToAgentId.clear();
314
- log.info(`[clear] 清空所有进程记录: ${count} 条`);
315
- await this.persist();
329
+ await this.mutate(async () => {
330
+ const count = this.records.size;
331
+ this.records.clear();
332
+ this.pidToAgentId.clear();
333
+ log.info(`[clear] 清空所有进程记录: ${count} 条`);
334
+ await this.persist();
335
+ });
316
336
  }
317
337
  }
318
338
  ProcessRegistry.instance = null;
@@ -348,7 +368,8 @@ export async function loadPersistedProcessRegistry() {
348
368
  */
349
369
  export async function savePersistedProcessRegistry(records) {
350
370
  const filePath = getProcessRegistryFile();
351
- await ensureDir(path.dirname(filePath));
352
- await fs.writeFile(filePath, JSON.stringify(records, null, 2), 'utf-8');
353
- log.debug(`[savePersistedProcessRegistry] 保存了 ${records.length} 条进程记录`);
371
+ await withFileLock(filePath, async () => {
372
+ await atomicWriteJsonFile(filePath, records);
373
+ log.debug(`[savePersistedProcessRegistry] 保存了 ${records.length} 条进程记录`);
374
+ });
354
375
  }
@@ -1,6 +1,7 @@
1
1
  export type RuntimeBindingState = {
2
2
  status: "unpaired" | "paired";
3
3
  instanceId?: string;
4
+ userId?: string;
4
5
  schedulerBaseUrl?: string;
5
6
  /**
6
7
  * Plain runtime access token issued by the scheduler.
@@ -15,17 +16,26 @@ export type RuntimeBindingState = {
15
16
  };
16
17
  export declare function getRuntimePairingCode(): string;
17
18
  export declare function getRuntimeBindingFilePath(): string;
19
+ export declare function getRuntimeTokenStoreFilePath(): string;
20
+ export declare function buildRuntimeTokenKey(userId: unknown, serverBaseUrl: unknown): string;
21
+ export declare function saveScopedRuntimeAccessToken(input: {
22
+ userId: string;
23
+ serverBaseUrl: string;
24
+ accessToken: string;
25
+ }): string;
26
+ export declare function getScopedRuntimeAccessToken(userId: unknown, serverBaseUrl: unknown): string | undefined;
18
27
  export declare function loadRuntimeBinding(): RuntimeBindingState;
19
28
  export declare function getRuntimeBindingPublicState(): Omit<RuntimeBindingState, "tokenHash" | "accessToken"> & {
20
29
  paired: boolean;
21
30
  };
22
31
  export declare function hasRuntimeBinding(): boolean;
23
32
  export declare function validateRuntimeBindingToken(token: unknown): boolean;
24
- export declare function getRuntimeAccessToken(): string | undefined;
33
+ export declare function getRuntimeAccessToken(userId?: unknown, serverBaseUrl?: unknown): string | undefined;
25
34
  export declare function validateRuntimePairingCode(code: unknown): boolean;
26
35
  export declare function saveRuntimeBinding(input: {
27
36
  accessToken: string;
28
37
  instanceId?: string;
38
+ userId?: string;
29
39
  schedulerBaseUrl?: string;
30
40
  }): RuntimeBindingState;
31
41
  export declare function clearRuntimeBinding(): void;
@@ -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,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;AAuCF,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,kBAAkB,IAAI,mBAAmB,CAWxD;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,CAYnE;AAED,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,SAAS,CAM1D;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,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,mBAAmB,CA6BtB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAkB1C"}
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;AAoDF,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,CAWxD;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,CAWpB;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,CAuCtB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAkB1C"}
@@ -18,6 +18,9 @@ function bindingDir() {
18
18
  function bindingFile() {
19
19
  return path.join(bindingDir(), "binding.json");
20
20
  }
21
+ function tokenStoreFile() {
22
+ return path.join(bindingDir(), "runtime-tokens.properties");
23
+ }
21
24
  function hashToken(token) {
22
25
  return crypto
23
26
  .createHash(TOKEN_HASH_ALGORITHM)
@@ -32,6 +35,14 @@ function safeEqual(a, b) {
32
35
  }
33
36
  return crypto.timingSafeEqual(left, right);
34
37
  }
38
+ function safeTextEqual(a, b) {
39
+ const left = Buffer.from(a, "utf8");
40
+ const right = Buffer.from(b, "utf8");
41
+ if (left.length !== right.length) {
42
+ return false;
43
+ }
44
+ return crypto.timingSafeEqual(left, right);
45
+ }
35
46
  function normalizeToken(token) {
36
47
  return String(token || "").trim();
37
48
  }
@@ -41,6 +52,106 @@ export function getRuntimePairingCode() {
41
52
  export function getRuntimeBindingFilePath() {
42
53
  return bindingFile();
43
54
  }
55
+ export function getRuntimeTokenStoreFilePath() {
56
+ return tokenStoreFile();
57
+ }
58
+ function normalizeServerIp(serverBaseUrl) {
59
+ const raw = String(serverBaseUrl || "").trim();
60
+ if (!raw) {
61
+ return "";
62
+ }
63
+ try {
64
+ return new URL(raw).hostname.toLowerCase();
65
+ }
66
+ catch {
67
+ return raw
68
+ .replace(/^wss?:\/\//i, "")
69
+ .replace(/^https?:\/\//i, "")
70
+ .split("/")[0]
71
+ .split(":")[0]
72
+ .toLowerCase();
73
+ }
74
+ }
75
+ export function buildRuntimeTokenKey(userId, serverBaseUrl) {
76
+ const normalizedUserId = String(userId || "").trim();
77
+ const serverIp = normalizeServerIp(serverBaseUrl);
78
+ if (!normalizedUserId || !serverIp) {
79
+ throw new Error("userId and serverBaseUrl are required to build runtime token key");
80
+ }
81
+ return crypto
82
+ .createHash("md5")
83
+ .update(`${normalizedUserId}:${serverIp}`, "utf8")
84
+ .digest("hex");
85
+ }
86
+ function loadRuntimeTokenStore() {
87
+ const result = new Map();
88
+ try {
89
+ const content = fs.readFileSync(tokenStoreFile(), "utf8");
90
+ for (const line of content.split(/\r?\n/)) {
91
+ const trimmed = line.trim();
92
+ if (!trimmed || trimmed.startsWith("#")) {
93
+ continue;
94
+ }
95
+ const separatorIndex = trimmed.indexOf("=");
96
+ if (separatorIndex <= 0) {
97
+ continue;
98
+ }
99
+ const key = trimmed.slice(0, separatorIndex).trim();
100
+ const token = trimmed.slice(separatorIndex + 1).trim();
101
+ if (key && token) {
102
+ result.set(key, token);
103
+ }
104
+ }
105
+ }
106
+ catch {
107
+ // Missing token store is valid for an unpaired bridge.
108
+ }
109
+ return result;
110
+ }
111
+ function saveRuntimeTokenStore(tokens) {
112
+ fs.mkdirSync(bindingDir(), { recursive: true });
113
+ const lines = [...tokens.entries()]
114
+ .sort(([left], [right]) => left.localeCompare(right))
115
+ .map(([key, token]) => `${key}=${token}`);
116
+ fs.writeFileSync(tokenStoreFile(), `${lines.join("\n")}\n`, {
117
+ encoding: "utf8",
118
+ mode: 0o600,
119
+ });
120
+ }
121
+ export function saveScopedRuntimeAccessToken(input) {
122
+ const accessToken = normalizeToken(input.accessToken);
123
+ if (accessToken.length < 16) {
124
+ throw new Error("accessToken must be at least 16 characters");
125
+ }
126
+ const key = buildRuntimeTokenKey(input.userId, input.serverBaseUrl);
127
+ const tokens = loadRuntimeTokenStore();
128
+ tokens.set(key, accessToken);
129
+ saveRuntimeTokenStore(tokens);
130
+ return key;
131
+ }
132
+ export function getScopedRuntimeAccessToken(userId, serverBaseUrl) {
133
+ if (!userId || !serverBaseUrl) {
134
+ return undefined;
135
+ }
136
+ try {
137
+ const key = buildRuntimeTokenKey(userId, serverBaseUrl);
138
+ return loadRuntimeTokenStore().get(key);
139
+ }
140
+ catch {
141
+ return undefined;
142
+ }
143
+ }
144
+ function validateRuntimeTokenStore(token) {
145
+ if (!token) {
146
+ return false;
147
+ }
148
+ for (const storedToken of loadRuntimeTokenStore().values()) {
149
+ if (safeTextEqual(token, storedToken)) {
150
+ return true;
151
+ }
152
+ }
153
+ return false;
154
+ }
44
155
  export function loadRuntimeBinding() {
45
156
  try {
46
157
  const raw = fs.readFileSync(bindingFile(), "utf8");
@@ -72,12 +183,18 @@ export function validateRuntimeBindingToken(token) {
72
183
  return false;
73
184
  }
74
185
  const state = loadRuntimeBinding();
75
- if (state.status !== "paired" || !state.tokenHash) {
76
- return false;
186
+ if (state.status === "paired" && state.tokenHash) {
187
+ if (safeEqual(hashToken(candidate), state.tokenHash)) {
188
+ return true;
189
+ }
77
190
  }
78
- return safeEqual(hashToken(candidate), state.tokenHash);
191
+ return validateRuntimeTokenStore(candidate);
79
192
  }
80
- export function getRuntimeAccessToken() {
193
+ export function getRuntimeAccessToken(userId, serverBaseUrl) {
194
+ const scopedToken = getScopedRuntimeAccessToken(userId, serverBaseUrl);
195
+ if (scopedToken) {
196
+ return scopedToken;
197
+ }
81
198
  const state = loadRuntimeBinding();
82
199
  if (state.status !== "paired") {
83
200
  return undefined;
@@ -99,6 +216,7 @@ export function saveRuntimeBinding(input) {
99
216
  instanceId: input.instanceId
100
217
  ? String(input.instanceId)
101
218
  : previous.instanceId,
219
+ userId: input.userId ? String(input.userId) : previous.userId,
102
220
  schedulerBaseUrl: input.schedulerBaseUrl
103
221
  ? String(input.schedulerBaseUrl)
104
222
  : previous.schedulerBaseUrl,
@@ -112,6 +230,14 @@ export function saveRuntimeBinding(input) {
112
230
  encoding: "utf8",
113
231
  mode: 0o600,
114
232
  });
233
+ if (next.userId && next.schedulerBaseUrl) {
234
+ const key = saveScopedRuntimeAccessToken({
235
+ userId: next.userId,
236
+ serverBaseUrl: next.schedulerBaseUrl,
237
+ accessToken,
238
+ });
239
+ log.info(`[runtime-bridge] scoped runtime token saved: key=${key}`);
240
+ }
115
241
  log.info(`[runtime-bridge] runtime binding saved: ${bindingFile()}`);
116
242
  return next;
117
243
  }
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-persistence.d.ts","sourceRoot":"","sources":["../../src/services/terminal-persistence.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAKpD;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAUzE;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvF;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CASrF;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB7E;AAED;;;;;GAKG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAGvC;AAED;;;;;GAKG;AACH,wBAAsB,+BAA+B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBpF"}
1
+ {"version":3,"file":"terminal-persistence.d.ts","sourceRoot":"","sources":["../../src/services/terminal-persistence.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA6BpD;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAOzE;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvF;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrF;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB7E;AAED;;;;;GAKG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAGvC;AAED;;;;;GAKG;AACH,wBAAsB,+BAA+B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBpF"}