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.
- package/dist/adapter/adapter.test.js +4 -4
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/adapter/types.js +0 -7
- package/dist/adapter/types.test.js +5 -53
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +4 -0
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +88 -34
- package/dist/routes/runtime-binding.d.ts.map +1 -1
- package/dist/routes/runtime-binding.js +59 -2
- package/dist/routes/sessions.js +1 -1
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +48 -14
- package/dist/routes/terminal.test.js +6 -2
- package/dist/services/agent-process-manager.js +4 -4
- package/dist/services/auto-register.d.ts +12 -1
- package/dist/services/auto-register.d.ts.map +1 -1
- package/dist/services/auto-register.js +206 -42
- package/dist/services/aws-client-agent-mcp.test.js +3 -0
- package/dist/services/mcp-launch-binding-queue.d.ts +36 -0
- package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -0
- package/dist/services/mcp-launch-binding-queue.js +92 -0
- package/dist/services/mcp-launch-binding-queue.test.d.ts +2 -0
- package/dist/services/mcp-launch-binding-queue.test.d.ts.map +1 -0
- package/dist/services/mcp-launch-binding-queue.test.js +107 -0
- package/dist/services/orphan-monitor.js +1 -1
- package/dist/services/process-detector.d.ts +1 -1
- package/dist/services/process-detector.d.ts.map +1 -1
- package/dist/services/process-detector.js +2 -11
- package/dist/services/process-registry.d.ts +1 -0
- package/dist/services/process-registry.d.ts.map +1 -1
- package/dist/services/process-registry.js +129 -108
- package/dist/services/runtime-binding.d.ts +11 -1
- package/dist/services/runtime-binding.d.ts.map +1 -1
- package/dist/services/runtime-binding.js +130 -4
- package/dist/services/terminal-persistence.d.ts.map +1 -1
- package/dist/services/terminal-persistence.js +47 -37
- package/dist/services/terminal-persistence.test.js +47 -1
- package/dist/utils/file-utils.d.ts +3 -0
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +32 -0
- package/package/aws-client-agent-mcp/README.md +288 -288
- package/package.json +76 -76
- package/dist/routes/aws-mcp.d.ts +0 -10
- package/dist/routes/aws-mcp.d.ts.map +0 -1
- package/dist/routes/aws-mcp.js +0 -74
- package/dist/routes/aws-mcp.test.d.ts +0 -2
- package/dist/routes/aws-mcp.test.d.ts.map +0 -1
- package/dist/routes/aws-mcp.test.js +0 -42
- package/dist/routes/memory.d.ts +0 -13
- package/dist/routes/memory.d.ts.map +0 -1
- package/dist/routes/memory.js +0 -429
- package/dist/services/aws-mcp-http.d.ts +0 -11
- package/dist/services/aws-mcp-http.d.ts.map +0 -1
- package/dist/services/aws-mcp-http.js +0 -225
- package/dist/services/aws-mcp-http.test.d.ts +0 -2
- package/dist/services/aws-mcp-http.test.d.ts.map +0 -1
- package/dist/services/aws-mcp-http.test.js +0 -27
- package/dist/services/easytier-manager.d.ts +0 -106
- package/dist/services/easytier-manager.d.ts.map +0 -1
- package/dist/services/easytier-manager.js +0 -331
- package/dist/services/easytier-manager.test.d.ts +0 -5
- package/dist/services/easytier-manager.test.d.ts.map +0 -1
- package/dist/services/easytier-manager.test.js +0 -98
- package/dist/services/memory-service.d.ts +0 -195
- package/dist/services/memory-service.d.ts.map +0 -1
- package/dist/services/memory-service.js +0 -650
- package/dist/services/session-lookup.d.ts +0 -20
- package/dist/services/session-lookup.d.ts.map +0 -1
- package/dist/services/session-lookup.js +0 -43
- package/dist/services/user-api-key-service.d.ts +0 -28
- package/dist/services/user-api-key-service.d.ts.map +0 -1
- package/dist/services/user-api-key-service.js +0 -75
- package/node_modules/@cc-switch/sdk/dist/adapters/common.d.ts +0 -38
- package/node_modules/@cc-switch/sdk/dist/adapters/common.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/common.js +0 -47
- package/node_modules/@cc-switch/sdk/dist/adapters/index.d.ts +0 -5
- package/node_modules/@cc-switch/sdk/dist/adapters/index.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/index.js +0 -28
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claude.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claude.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claude.js +0 -39
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claudecode.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claudecode.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claudecode.js +0 -40
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.d.ts +0 -18
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.js +0 -63
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.test.d.ts +0 -2
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.test.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.test.js +0 -86
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-placeholder.d.ts +0 -9
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-placeholder.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-placeholder.js +0 -14
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claude.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claude.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claude.js +0 -51
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claudecode.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claudecode.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claudecode.js +0 -51
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-opencode.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-opencode.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-opencode.js +0 -51
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-placeholder.d.ts +0 -9
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-placeholder.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-placeholder.js +0 -14
- package/node_modules/@cc-switch/sdk/dist/services/instance-service.d.ts +0 -78
- package/node_modules/@cc-switch/sdk/dist/services/instance-service.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/instance-service.js +0 -180
- package/package/cc-switch-sdk/dist/adapters/common.d.ts +0 -38
- package/package/cc-switch-sdk/dist/adapters/common.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/common.js +0 -47
- package/package/cc-switch-sdk/dist/adapters/index.d.ts +0 -5
- package/package/cc-switch-sdk/dist/adapters/index.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/index.js +0 -28
- package/package/cc-switch-sdk/dist/adapters/mcp-claude.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/mcp-claude.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-claude.js +0 -39
- package/package/cc-switch-sdk/dist/adapters/mcp-claudecode.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/mcp-claudecode.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-claudecode.js +0 -40
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.d.ts +0 -18
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.js +0 -63
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.test.d.ts +0 -2
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.test.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.test.js +0 -86
- package/package/cc-switch-sdk/dist/adapters/mcp-placeholder.d.ts +0 -9
- package/package/cc-switch-sdk/dist/adapters/mcp-placeholder.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-placeholder.js +0 -14
- package/package/cc-switch-sdk/dist/adapters/skill-claude.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/skill-claude.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/skill-claude.js +0 -51
- package/package/cc-switch-sdk/dist/adapters/skill-claudecode.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/skill-claudecode.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/skill-claudecode.js +0 -51
- package/package/cc-switch-sdk/dist/adapters/skill-opencode.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/skill-opencode.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/skill-opencode.js +0 -51
- package/package/cc-switch-sdk/dist/adapters/skill-placeholder.d.ts +0 -9
- package/package/cc-switch-sdk/dist/adapters/skill-placeholder.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/skill-placeholder.js +0 -14
- package/package/cc-switch-sdk/dist/services/instance-service.d.ts +0 -78
- package/package/cc-switch-sdk/dist/services/instance-service.d.ts.map +0 -1
- 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 {
|
|
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
|
-
|
|
28
|
-
const
|
|
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
|
|
45
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
71
|
-
const
|
|
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
|
|
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
|
-
|
|
108
|
-
const
|
|
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
|
|
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
|
|
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;
|
|
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"}
|
package/dist/utils/file-utils.js
CHANGED
|
@@ -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;
|