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
@@ -6,9 +6,30 @@
6
6
  import path from 'node:path';
7
7
  import os from 'node:os';
8
8
  import { promises as fs } from 'node:fs';
9
- import { ensureDir } from '../utils/file-utils.js';
9
+ import { atomicWriteJsonFile, withFileLock } from '../utils/file-utils.js';
10
10
  import { createLogger } from '../utils/logger.js';
11
11
  const log = createLogger('terminal-persistence');
12
+ async function readAllPersistedSessions(filePath) {
13
+ try {
14
+ const raw = await fs.readFile(filePath, 'utf-8');
15
+ return JSON.parse(raw);
16
+ }
17
+ catch (error) {
18
+ const err = error;
19
+ if (err.code !== 'ENOENT') {
20
+ log.warn(`[readAllPersistedSessions] 读取持久化会话失败: ${filePath}, error=${err.message}`);
21
+ }
22
+ return [];
23
+ }
24
+ }
25
+ async function updatePersistedSessions(updater) {
26
+ const filePath = getPersistedSessionsFile();
27
+ await withFileLock(filePath, async () => {
28
+ const sessions = await readAllPersistedSessions(filePath);
29
+ const nextSessions = await updater(sessions);
30
+ await atomicWriteJsonFile(filePath, nextSessions);
31
+ });
32
+ }
12
33
  /**
13
34
  * 获取持久化会话文件路径
14
35
  *
@@ -24,15 +45,11 @@ export function getPersistedSessionsFile() {
24
45
  */
25
46
  export async function loadPersistedSessions() {
26
47
  const filePath = getPersistedSessionsFile();
27
- try {
28
- const raw = await fs.readFile(filePath, 'utf-8');
29
- const sessions = JSON.parse(raw);
48
+ return withFileLock(filePath, async () => {
49
+ const sessions = await readAllPersistedSessions(filePath);
30
50
  // 只返回状态为 running 的会话
31
51
  return sessions.filter(s => s.status === 'running');
32
- }
33
- catch {
34
- return [];
35
- }
52
+ });
36
53
  }
37
54
  /**
38
55
  * 保存持久化的终端会话列表
@@ -41,8 +58,9 @@ export async function loadPersistedSessions() {
41
58
  */
42
59
  export async function savePersistedSessions(sessions) {
43
60
  const filePath = getPersistedSessionsFile();
44
- await ensureDir(path.dirname(filePath));
45
- await fs.writeFile(filePath, JSON.stringify(sessions, null, 2), 'utf-8');
61
+ await withFileLock(filePath, async () => {
62
+ await atomicWriteJsonFile(filePath, sessions);
63
+ });
46
64
  }
47
65
  /**
48
66
  * 添加或更新持久化会话
@@ -50,15 +68,17 @@ export async function savePersistedSessions(sessions) {
50
68
  * @param session - 会话数据
51
69
  */
52
70
  export async function upsertPersistedSession(session) {
53
- const sessions = await loadPersistedSessions();
54
- const index = sessions.findIndex(s => s.sessionId === session.sessionId);
55
- if (index >= 0) {
56
- sessions[index] = session;
57
- }
58
- else {
59
- sessions.push(session);
60
- }
61
- await savePersistedSessions(sessions);
71
+ await updatePersistedSessions((sessions) => {
72
+ const runningSessions = sessions.filter(s => s.status === 'running');
73
+ const index = runningSessions.findIndex(s => s.sessionId === session.sessionId);
74
+ if (index >= 0) {
75
+ runningSessions[index] = session;
76
+ }
77
+ else {
78
+ runningSessions.push(session);
79
+ }
80
+ return runningSessions;
81
+ });
62
82
  }
63
83
  /**
64
84
  * 移除持久化会话(从完整文件中删除,包括 stopped 状态的)
@@ -67,9 +87,8 @@ export async function upsertPersistedSession(session) {
67
87
  */
68
88
  export async function removePersistedSession(sessionId) {
69
89
  const filePath = getPersistedSessionsFile();
70
- try {
71
- const raw = await fs.readFile(filePath, 'utf-8');
72
- const allSessions = JSON.parse(raw);
90
+ await withFileLock(filePath, async () => {
91
+ const allSessions = await readAllPersistedSessions(filePath);
73
92
  const beforeCount = allSessions.length;
74
93
  const filtered = allSessions.filter(s => s.sessionId !== sessionId);
75
94
  const afterCount = filtered.length;
@@ -79,12 +98,8 @@ export async function removePersistedSession(sessionId) {
79
98
  else {
80
99
  log.warn(`[removePersistedSession] 未找到要删除的会话: sessionId=${sessionId}, 当前会话数=${beforeCount}`);
81
100
  }
82
- await savePersistedSessions(filtered);
83
- }
84
- catch (error) {
85
- // 文件不存在,无需删除
86
- log.debug(`[removePersistedSession] 文件不存在或读取失败: ${filePath}`);
87
- }
101
+ await atomicWriteJsonFile(filePath, filtered);
102
+ });
88
103
  }
89
104
  /**
90
105
  * 根据 agentId 查找持久化会话
@@ -104,9 +119,8 @@ export async function findPersistedSessionByAgentId(agentId) {
104
119
  */
105
120
  export async function removePersistedSessionByAgentId(agentId) {
106
121
  const filePath = getPersistedSessionsFile();
107
- try {
108
- const raw = await fs.readFile(filePath, 'utf-8');
109
- const allSessions = JSON.parse(raw);
122
+ await withFileLock(filePath, async () => {
123
+ const allSessions = await readAllPersistedSessions(filePath);
110
124
  const beforeCount = allSessions.length;
111
125
  const filtered = allSessions.filter(s => s.agentId !== agentId);
112
126
  const afterCount = filtered.length;
@@ -116,10 +130,6 @@ export async function removePersistedSessionByAgentId(agentId) {
116
130
  else {
117
131
  log.debug(`[removePersistedSessionByAgentId] 未找到要删除的会话: agentId=${agentId}`);
118
132
  }
119
- await savePersistedSessions(filtered);
120
- }
121
- catch (error) {
122
- // 文件不存在,无需删除
123
- log.debug(`[removePersistedSessionByAgentId] 文件不存在或读取失败: ${filePath}`);
124
- }
133
+ await atomicWriteJsonFile(filePath, filtered);
134
+ });
125
135
  }
@@ -1,8 +1,33 @@
1
1
  /**
2
2
  * Terminal Persistence 服务单元测试
3
3
  */
4
- import { describe, it, expect } from 'vitest';
4
+ import path from 'node:path';
5
+ import { promises as fs } from 'node:fs';
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
+ const tmpRoot = path.join(process.cwd(), '.tmp-vitest-terminal-persistence');
8
+ vi.mock('node:os', () => ({
9
+ default: {
10
+ tmpdir: () => tmpRoot,
11
+ },
12
+ }));
13
+ const persistence = await import('./terminal-persistence.js');
14
+ function makeSession(sessionId, agentId) {
15
+ return {
16
+ sessionId,
17
+ agentId,
18
+ workspacePath: `/workspace/${agentId}`,
19
+ command: 'claude',
20
+ startedAt: new Date(0).toISOString(),
21
+ status: 'running',
22
+ };
23
+ }
5
24
  describe('terminal-persistence service', () => {
25
+ beforeEach(async () => {
26
+ await fs.rm(tmpRoot, { recursive: true, force: true });
27
+ });
28
+ afterEach(async () => {
29
+ await fs.rm(tmpRoot, { recursive: true, force: true });
30
+ });
6
31
  it('generates correct file path', () => {
7
32
  const getPersistedSessionsFile = (tmpdir) => {
8
33
  return `${tmpdir}/agentswork-runtime-bridge/sessions.json`;
@@ -85,4 +110,25 @@ describe('terminal-persistence service', () => {
85
110
  };
86
111
  expect(parseSessions('invalid json')).toEqual([]);
87
112
  });
113
+ it('serializes concurrent upserts without losing sessions', async () => {
114
+ await Promise.all([
115
+ persistence.upsertPersistedSession(makeSession('session-1', 'agent-1')),
116
+ persistence.upsertPersistedSession(makeSession('session-2', 'agent-2')),
117
+ persistence.upsertPersistedSession(makeSession('session-3', 'agent-3')),
118
+ ]);
119
+ const sessions = await persistence.loadPersistedSessions();
120
+ expect(sessions.map(session => session.sessionId).sort()).toEqual(['session-1', 'session-2', 'session-3']);
121
+ });
122
+ it('does not revive a removed session during concurrent writes', async () => {
123
+ await persistence.savePersistedSessions([
124
+ makeSession('session-1', 'agent-1'),
125
+ makeSession('session-2', 'agent-2'),
126
+ ]);
127
+ await Promise.all([
128
+ persistence.removePersistedSession('session-1'),
129
+ persistence.upsertPersistedSession(makeSession('session-3', 'agent-3')),
130
+ ]);
131
+ const sessions = await persistence.loadPersistedSessions();
132
+ expect(sessions.map(session => session.sessionId).sort()).toEqual(['session-2', 'session-3']);
133
+ });
88
134
  });
@@ -1,6 +1,9 @@
1
1
  import type { SkillPackage, DownloadResult, Skill } from '../types.js';
2
2
  import type { AppFlags, InstalledSkill, CcSwitchSdk } from '@cc-switch/sdk';
3
3
  export declare function ensureDir(targetDir: string): Promise<string>;
4
+ export declare function withFileLock<T>(targetPath: string, operation: () => Promise<T>): Promise<T>;
5
+ export declare function atomicWriteFile(targetPath: string, content: string): Promise<void>;
6
+ export declare function atomicWriteJsonFile(targetPath: string, value: unknown): Promise<void>;
4
7
  export declare function downloadSkillZip(skillPackage: SkillPackage, targetDir: string): Promise<DownloadResult | null>;
5
8
  export declare function runCommand(command: string, args: string[]): Promise<void>;
6
9
  export declare function escapePowerShellLiteral(text: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE5E,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA0ChC;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/E;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhG;AAED,wBAAsB,oCAAoC,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASlG;AAED,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,QAAQ,CAW1E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAM1D;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhF;AAED,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,WAAW,EAChB,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAyBhC"}
1
+ {"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI5E,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED,wBAAsB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAqBjG;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxF;AAED,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAE3F;AAED,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA0ChC;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/E;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhG;AAED,wBAAsB,oCAAoC,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASlG;AAED,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,QAAQ,CAW1E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAM1D;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhF;AAED,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,WAAW,EAChB,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAyBhC"}
@@ -4,10 +4,42 @@ import { promises as fs } from 'node:fs';
4
4
  import { createHash, randomUUID } from 'node:crypto';
5
5
  import axios from 'axios';
6
6
  import { schedulerBaseUrl, runtimeToken } from '../config.js';
7
+ const fileLocks = new Map();
7
8
  export async function ensureDir(targetDir) {
8
9
  await fs.mkdir(targetDir, { recursive: true });
9
10
  return targetDir;
10
11
  }
12
+ export async function withFileLock(targetPath, operation) {
13
+ const lockKey = path.resolve(targetPath);
14
+ const previous = fileLocks.get(lockKey) ?? Promise.resolve();
15
+ let release;
16
+ const current = new Promise((resolve) => {
17
+ release = resolve;
18
+ });
19
+ const chained = previous.then(() => current);
20
+ fileLocks.set(lockKey, chained);
21
+ await previous;
22
+ try {
23
+ return await operation();
24
+ }
25
+ finally {
26
+ release();
27
+ if (fileLocks.get(lockKey) === chained) {
28
+ fileLocks.delete(lockKey);
29
+ }
30
+ }
31
+ }
32
+ export async function atomicWriteFile(targetPath, content) {
33
+ await ensureDir(path.dirname(targetPath));
34
+ const targetDir = path.dirname(targetPath);
35
+ const targetBase = path.basename(targetPath);
36
+ const tmpPath = path.join(targetDir, `.${targetBase}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`);
37
+ await fs.writeFile(tmpPath, content, 'utf-8');
38
+ await fs.rename(tmpPath, targetPath);
39
+ }
40
+ export async function atomicWriteJsonFile(targetPath, value) {
41
+ await atomicWriteFile(targetPath, `${JSON.stringify(value, null, 2)}\n`);
42
+ }
11
43
  export async function downloadSkillZip(skillPackage, targetDir) {
12
44
  if (!skillPackage || !skillPackage.downloadUrl) {
13
45
  return null;