@lemoncat7/dsh-partner 1.4.2 → 1.5.1
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 +38 -0
- package/docs/architecture.md +99 -0
- package/lib/agent-runtime.d.ts +31 -8
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +230 -48
- package/lib/agent-runtime.js.map +1 -1
- package/lib/api/features/workspace-api.d.ts +18 -0
- package/lib/api/features/workspace-api.d.ts.map +1 -0
- package/lib/api/features/workspace-api.js +188 -0
- package/lib/api/features/workspace-api.js.map +1 -0
- package/lib/api/http.d.ts +12 -0
- package/lib/api/http.d.ts.map +1 -0
- package/lib/api/http.js +53 -0
- package/lib/api/http.js.map +1 -0
- package/lib/api.d.ts +10 -0
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +48 -63
- package/lib/api.js.map +1 -1
- package/lib/channels/manager.d.ts +4 -1
- package/lib/channels/manager.d.ts.map +1 -1
- package/lib/channels/manager.js +20 -3
- package/lib/channels/manager.js.map +1 -1
- package/lib/client-api.d.ts +157 -1
- package/lib/client-api.d.ts.map +1 -1
- package/lib/client-api.js.map +1 -1
- package/lib/client-controller.d.ts +15 -0
- package/lib/client-controller.d.ts.map +1 -0
- package/lib/client-controller.js +69 -0
- package/lib/client-controller.js.map +1 -0
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +2827 -885
- package/lib/client.js.map +4 -4
- package/lib/collaboration/composition.d.ts +27 -0
- package/lib/collaboration/composition.d.ts.map +1 -0
- package/lib/collaboration/composition.js +345 -0
- package/lib/collaboration/composition.js.map +1 -0
- package/lib/collaboration/domain.d.ts +38 -0
- package/lib/collaboration/domain.d.ts.map +1 -0
- package/lib/collaboration/domain.js +3 -0
- package/lib/collaboration/domain.js.map +1 -0
- package/lib/collaboration/retry-policy.d.ts +4 -0
- package/lib/collaboration/retry-policy.d.ts.map +1 -0
- package/lib/collaboration/retry-policy.js +18 -0
- package/lib/collaboration/retry-policy.js.map +1 -0
- package/lib/collaboration/service.d.ts +76 -0
- package/lib/collaboration/service.d.ts.map +1 -0
- package/lib/collaboration/service.js +370 -0
- package/lib/collaboration/service.js.map +1 -0
- package/lib/companions/service.d.ts +13 -0
- package/lib/companions/service.d.ts.map +1 -0
- package/lib/companions/service.js +51 -0
- package/lib/companions/service.js.map +1 -0
- package/lib/concern-domain.d.ts +8 -1
- package/lib/concern-domain.d.ts.map +1 -1
- package/lib/concern-domain.js +25 -1
- package/lib/concern-domain.js.map +1 -1
- package/lib/concern-store.d.ts.map +1 -1
- package/lib/concern-store.js +2 -1
- package/lib/concern-store.js.map +1 -1
- package/lib/concern-tool.d.ts +6 -0
- package/lib/concern-tool.d.ts.map +1 -1
- package/lib/concern-tool.js +35 -9
- package/lib/concern-tool.js.map +1 -1
- package/lib/core/collections.d.ts +2 -0
- package/lib/core/collections.d.ts.map +1 -0
- package/lib/core/collections.js +6 -0
- package/lib/core/collections.js.map +1 -0
- package/lib/core/semaphore.d.ts +10 -0
- package/lib/core/semaphore.d.ts.map +1 -0
- package/lib/core/semaphore.js +32 -0
- package/lib/core/semaphore.js.map +1 -0
- package/lib/core/validation.d.ts +7 -0
- package/lib/core/validation.d.ts.map +1 -0
- package/lib/core/validation.js +40 -0
- package/lib/core/validation.js.map +1 -0
- package/lib/domain.d.ts +28 -2
- package/lib/domain.d.ts.map +1 -1
- package/lib/domain.js +3 -3
- package/lib/domain.js.map +1 -1
- package/lib/execution/agent-support.d.ts +12 -0
- package/lib/execution/agent-support.d.ts.map +1 -0
- package/lib/execution/agent-support.js +48 -0
- package/lib/execution/agent-support.js.map +1 -0
- package/lib/execution/domain.d.ts +17 -0
- package/lib/execution/domain.d.ts.map +1 -0
- package/lib/execution/domain.js +2 -0
- package/lib/execution/domain.js.map +1 -0
- package/lib/execution/service.d.ts +52 -0
- package/lib/execution/service.d.ts.map +1 -0
- package/lib/execution/service.js +175 -0
- package/lib/execution/service.js.map +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +42 -3
- package/lib/index.js.map +1 -1
- package/lib/memory-reflection.js +2 -2
- package/lib/memory-reflection.js.map +1 -1
- package/lib/scheduler/domain.d.ts +25 -0
- package/lib/scheduler/domain.d.ts.map +1 -0
- package/lib/scheduler/domain.js +2 -0
- package/lib/scheduler/domain.js.map +1 -0
- package/lib/scheduler/service.d.ts +26 -0
- package/lib/scheduler/service.d.ts.map +1 -0
- package/lib/scheduler/service.js +177 -0
- package/lib/scheduler/service.js.map +1 -0
- package/lib/skills/builtin.d.ts +7 -0
- package/lib/skills/builtin.d.ts.map +1 -0
- package/lib/skills/builtin.js +16 -0
- package/lib/skills/builtin.js.map +1 -0
- package/lib/skills/domain.d.ts +52 -0
- package/lib/skills/domain.d.ts.map +1 -0
- package/lib/skills/domain.js +2 -0
- package/lib/skills/domain.js.map +1 -0
- package/lib/skills/loader.d.ts +18 -0
- package/lib/skills/loader.d.ts.map +1 -0
- package/lib/skills/loader.js +134 -0
- package/lib/skills/loader.js.map +1 -0
- package/lib/skills/markets/adapters.d.ts +8 -0
- package/lib/skills/markets/adapters.d.ts.map +1 -0
- package/lib/skills/markets/adapters.js +72 -0
- package/lib/skills/markets/adapters.js.map +1 -0
- package/lib/skills/markets/builtin.d.ts +10 -0
- package/lib/skills/markets/builtin.d.ts.map +1 -0
- package/lib/skills/markets/builtin.js +19 -0
- package/lib/skills/markets/builtin.js.map +1 -0
- package/lib/skills/network.d.ts +14 -0
- package/lib/skills/network.d.ts.map +1 -0
- package/lib/skills/network.js +174 -0
- package/lib/skills/network.js.map +1 -0
- package/lib/skills/repository.d.ts +17 -0
- package/lib/skills/repository.d.ts.map +1 -0
- package/lib/skills/repository.js +76 -0
- package/lib/skills/repository.js.map +1 -0
- package/lib/skills/service.d.ts +48 -0
- package/lib/skills/service.d.ts.map +1 -0
- package/lib/skills/service.js +248 -0
- package/lib/skills/service.js.map +1 -0
- package/lib/skills/zip.d.ts +3 -0
- package/lib/skills/zip.d.ts.map +1 -0
- package/lib/skills/zip.js +59 -0
- package/lib/skills/zip.js.map +1 -0
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +69 -3
- package/lib/store.js.map +1 -1
- package/lib/tasks/domain.d.ts +39 -0
- package/lib/tasks/domain.d.ts.map +1 -0
- package/lib/tasks/domain.js +3 -0
- package/lib/tasks/domain.js.map +1 -0
- package/lib/tasks/result.d.ts +14 -0
- package/lib/tasks/result.d.ts.map +1 -0
- package/lib/tasks/result.js +109 -0
- package/lib/tasks/result.js.map +1 -0
- package/lib/tasks/service.d.ts +39 -0
- package/lib/tasks/service.d.ts.map +1 -0
- package/lib/tasks/service.js +334 -0
- package/lib/tasks/service.js.map +1 -0
- package/lib/ui/companion-access-panel.d.ts +4 -0
- package/lib/ui/companion-access-panel.d.ts.map +1 -0
- package/lib/ui/companion-access-panel.js +46 -0
- package/lib/ui/companion-access-panel.js.map +1 -0
- package/lib/ui/companion-create.d.ts +11 -0
- package/lib/ui/companion-create.d.ts.map +1 -0
- package/lib/ui/companion-create.js +30 -0
- package/lib/ui/companion-create.js.map +1 -0
- package/lib/ui/partner-components.d.ts +36 -0
- package/lib/ui/partner-components.d.ts.map +1 -0
- package/lib/ui/partner-components.js +32 -0
- package/lib/ui/partner-components.js.map +1 -0
- package/lib/ui/schedule-panel.d.ts +5 -0
- package/lib/ui/schedule-panel.d.ts.map +1 -0
- package/lib/ui/schedule-panel.js +86 -0
- package/lib/ui/schedule-panel.js.map +1 -0
- package/lib/ui/skills-panel.d.ts +5 -0
- package/lib/ui/skills-panel.d.ts.map +1 -0
- package/lib/ui/skills-panel.js +236 -0
- package/lib/ui/skills-panel.js.map +1 -0
- package/lib/ui/task-board-panel.d.ts +2 -0
- package/lib/ui/task-board-panel.d.ts.map +1 -0
- package/lib/ui/task-board-panel.js +282 -0
- package/lib/ui/task-board-panel.js.map +1 -0
- package/lib/ui/workspace-components.d.ts +40 -0
- package/lib/ui/workspace-components.d.ts.map +1 -0
- package/lib/ui/workspace-components.js +61 -0
- package/lib/ui/workspace-components.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface RemoteTextRequest {
|
|
2
|
+
url: string;
|
|
3
|
+
maxBytes: number;
|
|
4
|
+
timeoutMs: number;
|
|
5
|
+
method?: string;
|
|
6
|
+
headers?: HeadersInit;
|
|
7
|
+
body?: string;
|
|
8
|
+
proxyUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
/** A bounded HTTP client used by both market discovery and package installs. */
|
|
11
|
+
export declare function requestRemoteText(input: RemoteTextRequest): Promise<string>;
|
|
12
|
+
export declare function requestRemoteBytes(input: RemoteTextRequest): Promise<Uint8Array>;
|
|
13
|
+
export declare function normalizeProxyUrl(value: unknown): string | undefined;
|
|
14
|
+
//# sourceMappingURL=network.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/skills/network.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,gFAAgF;AAChF,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAIhF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAQpE"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { request as httpRequest } from 'node:http';
|
|
2
|
+
import { request as httpsRequest } from 'node:https';
|
|
3
|
+
import { connect as tlsConnect } from 'node:tls';
|
|
4
|
+
const MAX_REDIRECTS = 5;
|
|
5
|
+
/** A bounded HTTP client used by both market discovery and package installs. */
|
|
6
|
+
export async function requestRemoteText(input) {
|
|
7
|
+
return new TextDecoder().decode(await requestRemoteBytes(input));
|
|
8
|
+
}
|
|
9
|
+
export function requestRemoteBytes(input) {
|
|
10
|
+
return input.proxyUrl
|
|
11
|
+
? requestThroughHttpProxy(input, 0)
|
|
12
|
+
: requestDirect(input);
|
|
13
|
+
}
|
|
14
|
+
export function normalizeProxyUrl(value) {
|
|
15
|
+
if (value === undefined || value === null || value === '')
|
|
16
|
+
return undefined;
|
|
17
|
+
if (typeof value !== 'string')
|
|
18
|
+
throw new Error('proxyUrl must be a string');
|
|
19
|
+
const proxy = new URL(value.trim());
|
|
20
|
+
if (proxy.protocol !== 'http:')
|
|
21
|
+
throw new Error('Skill 市场代理目前仅支持 http:// 地址');
|
|
22
|
+
if (proxy.username || proxy.password)
|
|
23
|
+
throw new Error('Skill 市场代理地址不能包含账号或密码');
|
|
24
|
+
if (proxy.pathname !== '/' || proxy.search || proxy.hash)
|
|
25
|
+
throw new Error('Skill 市场代理地址不能包含路径、查询参数或片段');
|
|
26
|
+
return proxy.toString();
|
|
27
|
+
}
|
|
28
|
+
async function requestDirect(input) {
|
|
29
|
+
const response = await fetch(input.url, {
|
|
30
|
+
...(input.method ? { method: input.method } : {}),
|
|
31
|
+
...(input.headers ? { headers: input.headers } : {}),
|
|
32
|
+
...(input.body !== undefined ? { body: input.body } : {}),
|
|
33
|
+
signal: AbortSignal.timeout(input.timeoutMs),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok)
|
|
36
|
+
throw new Error(`HTTP ${response.status} while fetching ${new URL(input.url).host}`);
|
|
37
|
+
const advertised = Number(response.headers.get('content-length') ?? 0);
|
|
38
|
+
if (advertised > input.maxBytes)
|
|
39
|
+
throw new Error('Remote content exceeds the size limit');
|
|
40
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
41
|
+
if (bytes.byteLength > input.maxBytes)
|
|
42
|
+
throw new Error('Remote content exceeds the size limit');
|
|
43
|
+
return bytes;
|
|
44
|
+
}
|
|
45
|
+
async function requestThroughHttpProxy(input, redirects) {
|
|
46
|
+
if (redirects > MAX_REDIRECTS)
|
|
47
|
+
throw new Error('Skill market request exceeded the redirect limit');
|
|
48
|
+
const target = new URL(input.url);
|
|
49
|
+
const proxy = new URL(normalizeProxyUrl(input.proxyUrl));
|
|
50
|
+
const response = target.protocol === 'https:'
|
|
51
|
+
? await tunnelHttps(target, proxy, input)
|
|
52
|
+
: await proxyHttp(target, proxy, input);
|
|
53
|
+
const location = response.headers.location;
|
|
54
|
+
if (location && [301, 302, 303, 307, 308].includes(response.statusCode ?? 0)) {
|
|
55
|
+
response.resume();
|
|
56
|
+
const switchToGet = response.statusCode === 303 || ((response.statusCode === 301 || response.statusCode === 302) && input.method === 'POST');
|
|
57
|
+
if (switchToGet) {
|
|
58
|
+
const { body: _body, method: _method, ...rest } = input;
|
|
59
|
+
return requestThroughHttpProxy({ ...rest, method: 'GET', url: new URL(location, target).toString() }, redirects + 1);
|
|
60
|
+
}
|
|
61
|
+
return requestThroughHttpProxy({ ...input, url: new URL(location, target).toString() }, redirects + 1);
|
|
62
|
+
}
|
|
63
|
+
return readBounded(response, input.maxBytes, target.host);
|
|
64
|
+
}
|
|
65
|
+
function proxyHttp(target, proxy, input) {
|
|
66
|
+
const headers = requestHeaders(input.headers, target.host, input.body);
|
|
67
|
+
return issue(httpRequest, {
|
|
68
|
+
hostname: proxy.hostname,
|
|
69
|
+
port: proxy.port || 80,
|
|
70
|
+
method: input.method ?? 'GET',
|
|
71
|
+
path: target.toString(),
|
|
72
|
+
headers,
|
|
73
|
+
}, input.body, input.timeoutMs);
|
|
74
|
+
}
|
|
75
|
+
function tunnelHttps(target, proxy, input) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const targetPort = Number(target.port || 443);
|
|
78
|
+
const connectRequest = httpRequest({
|
|
79
|
+
hostname: proxy.hostname,
|
|
80
|
+
port: proxy.port || 80,
|
|
81
|
+
method: 'CONNECT',
|
|
82
|
+
path: `${target.hostname}:${targetPort}`,
|
|
83
|
+
headers: { host: `${target.hostname}:${targetPort}` },
|
|
84
|
+
});
|
|
85
|
+
const fail = (error) => reject(networkError(error));
|
|
86
|
+
connectRequest.setTimeout(input.timeoutMs, () => connectRequest.destroy(new Error('Skill market proxy connection timed out')));
|
|
87
|
+
connectRequest.on('error', fail);
|
|
88
|
+
connectRequest.once('connect', (response, socket, head) => {
|
|
89
|
+
if (response.statusCode !== 200) {
|
|
90
|
+
socket.destroy();
|
|
91
|
+
reject(new Error(`Skill market proxy CONNECT returned HTTP ${response.statusCode ?? 0}`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// CONNECT exposes the underlying socket separately from the TLS wrapper.
|
|
95
|
+
// Some proxies reset that raw socket during package downloads; keep an
|
|
96
|
+
// error consumer attached for the socket's full lifetime so a failed
|
|
97
|
+
// install cannot become an uncaught process-level error.
|
|
98
|
+
socket.on('error', fail);
|
|
99
|
+
if (head.byteLength > 0)
|
|
100
|
+
socket.unshift(head);
|
|
101
|
+
const secureSocket = tlsConnect({ socket, servername: target.hostname });
|
|
102
|
+
secureSocket.setTimeout(input.timeoutMs, () => secureSocket.destroy(new Error('Skill market TLS connection timed out')));
|
|
103
|
+
secureSocket.on('error', fail);
|
|
104
|
+
secureSocket.once('secureConnect', () => {
|
|
105
|
+
const headers = requestHeaders(input.headers, target.host, input.body);
|
|
106
|
+
void issue(httpsRequest, {
|
|
107
|
+
hostname: target.hostname,
|
|
108
|
+
port: targetPort,
|
|
109
|
+
method: input.method ?? 'GET',
|
|
110
|
+
path: `${target.pathname}${target.search}`,
|
|
111
|
+
headers,
|
|
112
|
+
agent: false,
|
|
113
|
+
createConnection: () => secureSocket,
|
|
114
|
+
}, input.body, input.timeoutMs).then(resolve, reject);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
connectRequest.end();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function issue(request, options, body, timeoutMs) {
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
const outgoing = request(options, resolve);
|
|
123
|
+
outgoing.setTimeout(timeoutMs, () => outgoing.destroy(new Error('Skill market request timed out')));
|
|
124
|
+
outgoing.on('error', error => reject(networkError(error)));
|
|
125
|
+
if (body)
|
|
126
|
+
outgoing.write(body);
|
|
127
|
+
outgoing.end();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function requestHeaders(value, host, body) {
|
|
131
|
+
const headers = Object.fromEntries(new Headers(value).entries());
|
|
132
|
+
headers.host = host;
|
|
133
|
+
if (body !== undefined)
|
|
134
|
+
headers['content-length'] = String(Buffer.byteLength(body));
|
|
135
|
+
return headers;
|
|
136
|
+
}
|
|
137
|
+
function readBounded(response, limit, host) {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
if ((response.statusCode ?? 0) < 200 || (response.statusCode ?? 0) >= 300) {
|
|
140
|
+
response.resume();
|
|
141
|
+
reject(new Error(`HTTP ${response.statusCode ?? 0} while fetching ${host}`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const advertised = Number(response.headers['content-length'] ?? 0);
|
|
145
|
+
if (advertised > limit) {
|
|
146
|
+
response.destroy();
|
|
147
|
+
reject(new Error('Remote content exceeds the size limit'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const chunks = [];
|
|
151
|
+
let total = 0;
|
|
152
|
+
response.on('data', (chunk) => {
|
|
153
|
+
total += chunk.byteLength;
|
|
154
|
+
if (total > limit) {
|
|
155
|
+
response.destroy(new Error('Remote content exceeds the size limit'));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
chunks.push(chunk);
|
|
159
|
+
});
|
|
160
|
+
response.once('end', () => resolve(Buffer.concat(chunks)));
|
|
161
|
+
response.on('error', error => reject(networkError(error)));
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function networkError(error) {
|
|
165
|
+
const code = error?.code;
|
|
166
|
+
if (code === 'ECONNRESET')
|
|
167
|
+
return new Error('Skill 市场代理连接被中断,请检查代理后重试');
|
|
168
|
+
if (code === 'ECONNREFUSED')
|
|
169
|
+
return new Error('无法连接 Skill 市场代理,请确认地址和端口可用');
|
|
170
|
+
if (code === 'ENOTFOUND')
|
|
171
|
+
return new Error('无法解析 Skill 市场或代理地址,请检查网络配置');
|
|
172
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/skills/network.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,WAAW,EAA6C,MAAM,WAAW,CAAA;AAC7F,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,UAAU,CAAA;AAEhD,MAAM,aAAa,GAAG,CAAC,CAAA;AAYvB,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAwB;IAC9D,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAwB;IACzD,OAAO,KAAK,CAAC,QAAQ;QACnB,CAAC,CAAC,uBAAuB,CAAC,KAAK,EAAE,CAAC,CAAC;QACnC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,SAAS,CAAA;IAC3E,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;IAC3E,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;IACnC,IAAI,KAAK,CAAC,QAAQ,KAAK,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAA;IAC7E,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAA;IAC9E,IAAI,KAAK,CAAC,QAAQ,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAA;IACvG,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAA;AACzB,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,KAAwB;IACnD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE;QACtC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;KAC7C,CAAC,CAAA;IACF,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,mBAAmB,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IACtG,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAA;IACtE,IAAI,UAAU,GAAG,KAAK,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;IACzF,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;IAC1D,IAAI,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;IAC/F,OAAO,KAAK,CAAA;AACd,CAAC;AAED,KAAK,UAAU,uBAAuB,CAAC,KAAwB,EAAE,SAAiB;IAChF,IAAI,SAAS,GAAG,aAAa;QAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;IAClG,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACjC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,QAAQ,CAAE,CAAC,CAAA;IACzD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,KAAK,QAAQ;QAC3C,CAAC,CAAC,MAAM,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC;QACzC,CAAC,CAAC,MAAM,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;IACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAA;IAC1C,IAAI,QAAQ,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QAC7E,QAAQ,CAAC,MAAM,EAAE,CAAA;QACjB,MAAM,WAAW,GAAG,QAAQ,CAAC,UAAU,KAAK,GAAG,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,KAAK,GAAG,IAAI,QAAQ,CAAC,UAAU,KAAK,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAA;QAC5I,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAA;YACvD,OAAO,uBAAuB,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,GAAG,CAAC,CAAC,CAAA;QACtH,CAAC;QACD,OAAO,uBAAuB,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,IAAI,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,GAAG,CAAC,CAAC,CAAA;IACxG,CAAC;IACD,OAAO,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;AAC3D,CAAC;AAED,SAAS,SAAS,CAAC,MAAW,EAAE,KAAU,EAAE,KAAwB;IAClE,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;IACtE,OAAO,KAAK,CAAC,WAAW,EAAE;QACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;QACtB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK;QAC7B,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE;QACvB,OAAO;KACR,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,SAAS,WAAW,CAAC,MAAW,EAAE,KAAU,EAAE,KAAwB;IACpE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,CAAC,CAAA;QAC7C,MAAM,cAAc,GAAG,WAAW,CAAC;YACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;YACtB,MAAM,EAAE,SAAS;YACjB,IAAI,EAAE,GAAG,MAAM,CAAC,QAAQ,IAAI,UAAU,EAAE;YACxC,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,QAAQ,IAAI,UAAU,EAAE,EAAE;SACtD,CAAC,CAAA;QACF,MAAM,IAAI,GAAG,CAAC,KAAc,EAAQ,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;QAClE,cAAc,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC,CAAC,CAAA;QAC9H,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QAChC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;YACxD,IAAI,QAAQ,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;gBAChC,MAAM,CAAC,OAAO,EAAE,CAAA;gBAChB,MAAM,CAAC,IAAI,KAAK,CAAC,4CAA4C,QAAQ,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;gBACzF,OAAM;YACR,CAAC;YACD,yEAAyE;YACzE,uEAAuE;YACvE,qEAAqE;YACrE,yDAAyD;YACzD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YACxB,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC;gBAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;YAC7C,MAAM,YAAY,GAAG,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;YACxE,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC,CAAC,CAAA;YACxH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YAC9B,YAAY,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE;gBACtC,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;gBACtE,KAAK,KAAK,CAAC,YAAY,EAAE;oBACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,IAAI,EAAE,UAAU;oBAChB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK;oBAC7B,IAAI,EAAE,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE;oBAC1C,OAAO;oBACP,KAAK,EAAE,KAAK;oBACZ,gBAAgB,EAAE,GAAG,EAAE,CAAC,YAAY;iBACrC,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YACvD,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QACF,cAAc,CAAC,GAAG,EAAE,CAAA;IACtB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,KAAK,CACZ,OAA2B,EAC3B,OAAuB,EACvB,IAAwB,EACxB,SAAiB;IAEjB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC1C,QAAQ,CAAC,UAAU,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC,CAAA;QACnG,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC1D,IAAI,IAAI;YAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC9B,QAAQ,CAAC,GAAG,EAAE,CAAA;IAChB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAA8B,EAAE,IAAY,EAAE,IAAwB;IAC5F,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAChE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;IACnB,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;IACnF,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,QAAyB,EAAE,KAAa,EAAE,IAAY;IACzE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,CAAC,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;YAC1E,QAAQ,CAAC,MAAM,EAAE,CAAA;YACjB,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,UAAU,IAAI,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,CAAA;YAC5E,OAAM;QACR,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAA;QAClE,IAAI,UAAU,GAAG,KAAK,EAAE,CAAC;YACvB,QAAQ,CAAC,OAAO,EAAE,CAAA;YAClB,MAAM,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC,CAAA;YAC1D,OAAM;QACR,CAAC;QACD,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,IAAI,KAAK,GAAG,CAAC,CAAA;QACb,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACpC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAA;YACzB,IAAI,KAAK,GAAG,KAAK,EAAE,CAAC;gBAClB,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC,CAAA;gBACpE,OAAM;YACR,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACpB,CAAC,CAAC,CAAA;QACF,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QAC1D,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,MAAM,IAAI,GAAI,KAA2C,EAAE,IAAI,CAAA;IAC/D,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;IACvE,IAAI,IAAI,KAAK,cAAc;QAAE,OAAO,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAA;IAC3E,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAA;IACxE,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;AAClE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LoadedSkill, PartnerSkill, SkillSourceKind } from './domain.js';
|
|
2
|
+
export declare class SkillRepository {
|
|
3
|
+
readonly root: string;
|
|
4
|
+
constructor(root: string);
|
|
5
|
+
initialize(): Promise<void>;
|
|
6
|
+
read(skill: PartnerSkill): Promise<LoadedSkill>;
|
|
7
|
+
install(input: {
|
|
8
|
+
id: string;
|
|
9
|
+
document: string;
|
|
10
|
+
source: SkillSourceKind;
|
|
11
|
+
sourceId?: string;
|
|
12
|
+
trusted: boolean;
|
|
13
|
+
now?: number;
|
|
14
|
+
}): Promise<PartnerSkill>;
|
|
15
|
+
remove(skill: PartnerSkill): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/skills/repository.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAI7E,qBAAa,eAAe;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;IAE3B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ/C,OAAO,CAAC,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,eAAe,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IA+BnJ,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAKjD"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { loadSkill, skillMetadata } from './loader.js';
|
|
5
|
+
const SAFE_ID = /^[a-z0-9][a-z0-9._-]{0,119}$/i;
|
|
6
|
+
export class SkillRepository {
|
|
7
|
+
root;
|
|
8
|
+
constructor(root) {
|
|
9
|
+
this.root = root;
|
|
10
|
+
}
|
|
11
|
+
async initialize() {
|
|
12
|
+
await mkdir(this.root, { recursive: true, mode: 0o700 });
|
|
13
|
+
}
|
|
14
|
+
async read(skill) {
|
|
15
|
+
assertInside(this.root, skill.rootPath);
|
|
16
|
+
return loadSkill({
|
|
17
|
+
id: skill.id, rootPath: skill.rootPath, source: skill.source, ...(skill.sourceId ? { sourceId: skill.sourceId } : {}),
|
|
18
|
+
trusted: skill.trusted, installedAt: skill.installedAt, updatedAt: skill.updatedAt,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async install(input) {
|
|
22
|
+
if (!SAFE_ID.test(input.id))
|
|
23
|
+
throw new Error('Skill id is invalid');
|
|
24
|
+
if (Buffer.byteLength(input.document) > 512 * 1024)
|
|
25
|
+
throw new Error('SKILL.md exceeds the 512 KiB limit');
|
|
26
|
+
await this.initialize();
|
|
27
|
+
const now = input.now ?? Date.now();
|
|
28
|
+
const target = join(this.root, input.id);
|
|
29
|
+
const temporary = join(this.root, `.${input.id}.${randomUUID()}.tmp`);
|
|
30
|
+
const backup = join(this.root, `.${input.id}.${randomUUID()}.backup`);
|
|
31
|
+
await mkdir(temporary, { recursive: false, mode: 0o700 });
|
|
32
|
+
try {
|
|
33
|
+
await writeFile(join(temporary, 'SKILL.md'), input.document, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
34
|
+
const loaded = await loadSkill({
|
|
35
|
+
id: input.id, rootPath: temporary, source: input.source, ...(input.sourceId ? { sourceId: input.sourceId } : {}),
|
|
36
|
+
trusted: input.trusted, installedAt: now, updatedAt: now,
|
|
37
|
+
});
|
|
38
|
+
let replaced = false;
|
|
39
|
+
try {
|
|
40
|
+
await rename(target, backup);
|
|
41
|
+
replaced = true;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error.code !== 'ENOENT')
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
await rename(temporary, target);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (replaced)
|
|
52
|
+
await rename(backup, target).catch(() => { });
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
await rm(backup, { recursive: true, force: true });
|
|
56
|
+
return { ...skillMetadata(loaded), rootPath: target };
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await rm(temporary, { recursive: true, force: true }).catch(() => { });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async remove(skill) {
|
|
63
|
+
assertInside(this.root, skill.rootPath);
|
|
64
|
+
await rm(skill.rootPath, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function assertInside(root, path) {
|
|
68
|
+
const base = resolve(root);
|
|
69
|
+
const candidate = resolve(path);
|
|
70
|
+
const rel = relative(base, candidate);
|
|
71
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
|
|
72
|
+
throw new Error('Skill path escapes the repository');
|
|
73
|
+
if (dirname(candidate) !== base)
|
|
74
|
+
throw new Error('Skill path must be a direct repository child');
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/skills/repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Companion, PartnerState } from '../domain.js';
|
|
2
|
+
import type { PartnerStore } from '../store.js';
|
|
3
|
+
import type { LoadedSkill, MarketSkillEntry, PartnerSkill, SkillMarketSource } from './domain.js';
|
|
4
|
+
import { SkillRepository } from './repository.js';
|
|
5
|
+
export declare class SkillService {
|
|
6
|
+
private readonly store;
|
|
7
|
+
readonly repository: SkillRepository;
|
|
8
|
+
private readonly cache;
|
|
9
|
+
private readonly mutations;
|
|
10
|
+
constructor(store: PartnerStore, repository: SkillRepository);
|
|
11
|
+
initialize(): Promise<void>;
|
|
12
|
+
bindings(companionId: string, state?: Pick<PartnerState, 'skills' | 'skillBindings'>): PartnerSkill[];
|
|
13
|
+
load(skillId: string): Promise<LoadedSkill>;
|
|
14
|
+
installLocal(document: string, id?: string): Promise<PartnerSkill>;
|
|
15
|
+
private installLocalLocked;
|
|
16
|
+
uninstall(skillId: string): Promise<void>;
|
|
17
|
+
private uninstallLocked;
|
|
18
|
+
setBinding(companionId: string, skillId: string, enabled: boolean): Promise<void>;
|
|
19
|
+
addMarketSource(input: unknown): Promise<SkillMarketSource>;
|
|
20
|
+
removeMarketSource(sourceId: string): Promise<void>;
|
|
21
|
+
networkSettings(): {
|
|
22
|
+
proxyUrl?: string;
|
|
23
|
+
};
|
|
24
|
+
setNetworkSettings(input: unknown): Promise<{
|
|
25
|
+
proxyUrl?: string;
|
|
26
|
+
}>;
|
|
27
|
+
testNetwork(input: unknown): Promise<{
|
|
28
|
+
ok: true;
|
|
29
|
+
latencyMs: number;
|
|
30
|
+
sourceCount: number;
|
|
31
|
+
entryCount: number;
|
|
32
|
+
}>;
|
|
33
|
+
market(force?: boolean): Promise<{
|
|
34
|
+
sources: SkillMarketSource[];
|
|
35
|
+
entries: MarketSkillEntry[];
|
|
36
|
+
errors: Array<{
|
|
37
|
+
sourceId: string;
|
|
38
|
+
error: string;
|
|
39
|
+
}>;
|
|
40
|
+
}>;
|
|
41
|
+
installMarket(sourceId: string, entryId: string): Promise<PartnerSkill>;
|
|
42
|
+
private installMarketLocked;
|
|
43
|
+
private loadMarket;
|
|
44
|
+
private fetchMarket;
|
|
45
|
+
private assertInstallCapacity;
|
|
46
|
+
}
|
|
47
|
+
export declare function renderEnabledSkills(companion: Companion, skills: PartnerSkill[], injectedIds?: ReadonlySet<string>): string;
|
|
48
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/skills/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG/C,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEjG,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAYjD,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,KAAK;IAAgB,QAAQ,CAAC,UAAU,EAAE,eAAe;IAHtF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;gBAErB,KAAK,EAAE,YAAY,EAAW,UAAU,EAAE,eAAe;IAEhF,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAejC,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,GAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,eAAe,CAAyB,GAAG,YAAY,EAAE;IAKtH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ3C,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YAI1D,kBAAkB;IAU1B,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAIjC,eAAe;IAUvB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjF,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAe3D,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASzD,eAAe,IAAI;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;IAIlC,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAQlE,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAc9G,MAAM,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC;QAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAWjJ,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YAI/D,mBAAmB;YA6BnB,UAAU;YAQV,WAAW;IAMzB,OAAO,CAAC,qBAAqB;CAI9B;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,WAAW,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,MAAM,CAMtI"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { optionalBoolean, requiredText } from '../core/validation.js';
|
|
3
|
+
import { AsyncSemaphore } from '../core/semaphore.js';
|
|
4
|
+
import { sha256 } from './loader.js';
|
|
5
|
+
import { BUILTIN_SKILLS, BUILTIN_SKILL_SOURCE } from './builtin.js';
|
|
6
|
+
import { marketRequest, parseMarketResponse } from './markets/adapters.js';
|
|
7
|
+
import { extractSkillMarkdown } from './zip.js';
|
|
8
|
+
import { normalizeProxyUrl, requestRemoteBytes, requestRemoteText } from './network.js';
|
|
9
|
+
const MARKET_CACHE_MS = 5 * 60_000;
|
|
10
|
+
const MAX_MARKET_BYTES = 1024 * 1024;
|
|
11
|
+
const MAX_SKILL_BYTES = 512 * 1024;
|
|
12
|
+
export class SkillService {
|
|
13
|
+
store;
|
|
14
|
+
repository;
|
|
15
|
+
cache = new Map();
|
|
16
|
+
mutations = new AsyncSemaphore(1);
|
|
17
|
+
constructor(store, repository) {
|
|
18
|
+
this.store = store;
|
|
19
|
+
this.repository = repository;
|
|
20
|
+
}
|
|
21
|
+
async initialize() {
|
|
22
|
+
await this.repository.initialize();
|
|
23
|
+
const replacements = [];
|
|
24
|
+
for (const skill of this.store.snapshot().skills) {
|
|
25
|
+
if (skill.source !== 'builtin')
|
|
26
|
+
continue;
|
|
27
|
+
const builtin = BUILTIN_SKILLS.get(skill.id);
|
|
28
|
+
if (!builtin || skill.checksum === sha256(builtin.document))
|
|
29
|
+
continue;
|
|
30
|
+
replacements.push(await this.repository.install({ id: skill.id, document: builtin.document, source: 'builtin', sourceId: BUILTIN_SKILL_SOURCE, trusted: true }));
|
|
31
|
+
}
|
|
32
|
+
if (replacements.length > 0)
|
|
33
|
+
await this.store.update(state => {
|
|
34
|
+
const byId = new Map(replacements.map(skill => [skill.id, skill]));
|
|
35
|
+
state.skills = state.skills.map(skill => byId.get(skill.id) ?? skill);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
bindings(companionId, state = this.store.snapshot()) {
|
|
39
|
+
const enabled = new Set(state.skillBindings.filter(item => item.companionId === companionId && item.enabled).map(item => item.skillId));
|
|
40
|
+
return state.skills.filter(item => enabled.has(item.id));
|
|
41
|
+
}
|
|
42
|
+
async load(skillId) {
|
|
43
|
+
const skill = this.store.snapshot().skills.find(item => item.id === skillId);
|
|
44
|
+
if (!skill)
|
|
45
|
+
throw new Error('Skill is not installed');
|
|
46
|
+
const loaded = await this.repository.read(skill);
|
|
47
|
+
if (loaded.checksum !== skill.checksum)
|
|
48
|
+
throw new Error('Skill content changed outside the installer; reinstall it before use');
|
|
49
|
+
return loaded;
|
|
50
|
+
}
|
|
51
|
+
async installLocal(document, id) {
|
|
52
|
+
return this.mutations.use(() => this.installLocalLocked(document, id));
|
|
53
|
+
}
|
|
54
|
+
async installLocalLocked(document, id) {
|
|
55
|
+
const skillId = id ? requiredText(id, 'skill id', 120) : `skill-${randomUUID()}`;
|
|
56
|
+
this.assertInstallCapacity(skillId);
|
|
57
|
+
const installed = await this.repository.install({ id: skillId, document, source: 'local', trusted: true });
|
|
58
|
+
await this.store.update(state => {
|
|
59
|
+
state.skills = [...state.skills.filter(item => item.id !== installed.id), installed];
|
|
60
|
+
});
|
|
61
|
+
return installed;
|
|
62
|
+
}
|
|
63
|
+
async uninstall(skillId) {
|
|
64
|
+
return this.mutations.use(() => this.uninstallLocked(skillId));
|
|
65
|
+
}
|
|
66
|
+
async uninstallLocked(skillId) {
|
|
67
|
+
const skill = this.store.snapshot().skills.find(item => item.id === skillId);
|
|
68
|
+
if (!skill)
|
|
69
|
+
throw new Error('Skill is not installed');
|
|
70
|
+
await this.store.update(state => {
|
|
71
|
+
state.skills = state.skills.filter(item => item.id !== skillId);
|
|
72
|
+
state.skillBindings = state.skillBindings.filter(item => item.skillId !== skillId);
|
|
73
|
+
});
|
|
74
|
+
await this.repository.remove(skill);
|
|
75
|
+
}
|
|
76
|
+
async setBinding(companionId, skillId, enabled) {
|
|
77
|
+
const state = this.store.snapshot();
|
|
78
|
+
if (!state.companions.some(item => item.id === companionId))
|
|
79
|
+
throw new Error('Companion does not exist');
|
|
80
|
+
if (!state.skills.some(item => item.id === skillId))
|
|
81
|
+
throw new Error('Skill is not installed');
|
|
82
|
+
await this.store.update(draft => {
|
|
83
|
+
draft.skillBindings = draft.skillBindings.filter(item => !(item.companionId === companionId && item.skillId === skillId));
|
|
84
|
+
draft.skillBindings.push({ companionId, skillId, enabled });
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async addMarketSource(input) {
|
|
88
|
+
const value = input;
|
|
89
|
+
const indexUrl = validHttpUrl(requiredText(value.indexUrl, 'indexUrl', 2000));
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
const source = {
|
|
92
|
+
id: `market-${randomUUID()}`, name: requiredText(value.name, 'name', 100), kind: 'dsh-index', indexUrl,
|
|
93
|
+
enabled: optionalBoolean(value.enabled, true), trusted: optionalBoolean(value.trusted, false), createdAt: now, updatedAt: now,
|
|
94
|
+
};
|
|
95
|
+
await this.store.update(state => {
|
|
96
|
+
if (state.skillMarketSources.length >= 20)
|
|
97
|
+
throw new Error('Skill market source limit reached');
|
|
98
|
+
state.skillMarketSources.push(source);
|
|
99
|
+
});
|
|
100
|
+
return source;
|
|
101
|
+
}
|
|
102
|
+
async removeMarketSource(sourceId) {
|
|
103
|
+
await this.store.update(state => {
|
|
104
|
+
const source = state.skillMarketSources.find(item => item.id === sourceId);
|
|
105
|
+
if (source?.builtin)
|
|
106
|
+
throw new Error('Built-in Skill market source cannot be removed');
|
|
107
|
+
state.skillMarketSources = state.skillMarketSources.filter(item => item.id !== sourceId);
|
|
108
|
+
});
|
|
109
|
+
this.cache.delete(sourceId);
|
|
110
|
+
}
|
|
111
|
+
networkSettings() {
|
|
112
|
+
return this.store.snapshot().skillMarketNetwork;
|
|
113
|
+
}
|
|
114
|
+
async setNetworkSettings(input) {
|
|
115
|
+
const value = input;
|
|
116
|
+
const proxyUrl = normalizeProxyUrl(value.proxyUrl);
|
|
117
|
+
await this.store.update(state => { state.skillMarketNetwork = proxyUrl ? { proxyUrl } : {}; });
|
|
118
|
+
this.cache.clear();
|
|
119
|
+
return this.networkSettings();
|
|
120
|
+
}
|
|
121
|
+
async testNetwork(input) {
|
|
122
|
+
const value = input;
|
|
123
|
+
const proxyUrl = normalizeProxyUrl(value.proxyUrl);
|
|
124
|
+
const sources = this.store.snapshot().skillMarketSources.filter(item => item.enabled);
|
|
125
|
+
const startedAt = Date.now();
|
|
126
|
+
const results = await Promise.allSettled(sources.map(source => this.fetchMarket(source, proxyUrl)));
|
|
127
|
+
const succeeded = results.filter((result) => result.status === 'fulfilled');
|
|
128
|
+
if (succeeded.length === 0) {
|
|
129
|
+
const reasons = results.flatMap(result => result.status === 'rejected' ? [result.reason instanceof Error ? result.reason.message : String(result.reason)] : []);
|
|
130
|
+
throw new Error(`Skill 市场网络测试失败:${reasons.slice(0, 2).join(';') || '没有可用市场'}`);
|
|
131
|
+
}
|
|
132
|
+
return { ok: true, latencyMs: Date.now() - startedAt, sourceCount: succeeded.length, entryCount: succeeded.reduce((sum, result) => sum + result.value.length, 0) };
|
|
133
|
+
}
|
|
134
|
+
async market(force = false) {
|
|
135
|
+
const sources = this.store.snapshot().skillMarketSources.filter(item => item.enabled);
|
|
136
|
+
const errors = [];
|
|
137
|
+
const groups = await Promise.all(sources.map(async (source) => {
|
|
138
|
+
try {
|
|
139
|
+
return await this.loadMarket(source, force);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
errors.push({ sourceId: source.id, error: error instanceof Error ? error.message : String(error) });
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}));
|
|
146
|
+
const entries = deduplicateMarket([...BUILTIN_SKILLS.values()].map(item => item.entry).concat(groups.flat()));
|
|
147
|
+
return { sources, entries, errors };
|
|
148
|
+
}
|
|
149
|
+
async installMarket(sourceId, entryId) {
|
|
150
|
+
return this.mutations.use(() => this.installMarketLocked(sourceId, entryId));
|
|
151
|
+
}
|
|
152
|
+
async installMarketLocked(sourceId, entryId) {
|
|
153
|
+
this.assertInstallCapacity(entryId);
|
|
154
|
+
if (sourceId === BUILTIN_SKILL_SOURCE) {
|
|
155
|
+
const builtin = BUILTIN_SKILLS.get(entryId);
|
|
156
|
+
if (!builtin)
|
|
157
|
+
throw new Error('Built-in Skill does not exist');
|
|
158
|
+
const installed = await this.repository.install({ id: entryId, document: builtin.document, source: 'builtin', sourceId: BUILTIN_SKILL_SOURCE, trusted: true });
|
|
159
|
+
await this.store.update(state => {
|
|
160
|
+
state.skills = [...state.skills.filter(item => item.id !== installed.id), installed];
|
|
161
|
+
});
|
|
162
|
+
return installed;
|
|
163
|
+
}
|
|
164
|
+
const source = this.store.snapshot().skillMarketSources.find(item => item.id === sourceId && item.enabled);
|
|
165
|
+
if (!source)
|
|
166
|
+
throw new Error('Skill market source is unavailable');
|
|
167
|
+
const entry = (await this.loadMarket(source, false)).find(item => item.id === entryId);
|
|
168
|
+
if (!entry)
|
|
169
|
+
throw new Error('Skill market entry does not exist');
|
|
170
|
+
const proxyUrl = this.networkSettings().proxyUrl;
|
|
171
|
+
const document = entry.installKind === 'zip'
|
|
172
|
+
? await fetchSkillFromZip(entry.skillUrl, MAX_SKILL_BYTES, proxyUrl)
|
|
173
|
+
: await fetchText(entry.skillUrl, MAX_SKILL_BYTES, undefined, proxyUrl);
|
|
174
|
+
if (entry.checksum && sha256(document) !== normalizeChecksum(entry.checksum))
|
|
175
|
+
throw new Error('Skill checksum verification failed');
|
|
176
|
+
const installed = await this.repository.install({
|
|
177
|
+
id: entry.id, document, source: 'market', sourceId: source.id, trusted: source.trusted,
|
|
178
|
+
});
|
|
179
|
+
await this.store.update(state => {
|
|
180
|
+
state.skills = [...state.skills.filter(item => item.id !== installed.id), installed];
|
|
181
|
+
});
|
|
182
|
+
return installed;
|
|
183
|
+
}
|
|
184
|
+
async loadMarket(source, force) {
|
|
185
|
+
const cached = this.cache.get(source.id);
|
|
186
|
+
if (!force && cached && cached.expiresAt > Date.now())
|
|
187
|
+
return cached.entries;
|
|
188
|
+
const entries = await this.fetchMarket(source, this.networkSettings().proxyUrl);
|
|
189
|
+
this.cache.set(source.id, { entries, expiresAt: Date.now() + MARKET_CACHE_MS });
|
|
190
|
+
return entries;
|
|
191
|
+
}
|
|
192
|
+
async fetchMarket(source, proxyUrl) {
|
|
193
|
+
const request = marketRequest(source);
|
|
194
|
+
const raw = await fetchText(request.url, MAX_MARKET_BYTES, request.init, proxyUrl);
|
|
195
|
+
return parseMarketResponse(JSON.parse(raw), source);
|
|
196
|
+
}
|
|
197
|
+
assertInstallCapacity(skillId) {
|
|
198
|
+
const skills = this.store.snapshot().skills;
|
|
199
|
+
if (!skills.some(item => item.id === skillId) && skills.length >= 500)
|
|
200
|
+
throw new Error('Installed Skill limit reached');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export function renderEnabledSkills(companion, skills, injectedIds = new Set()) {
|
|
204
|
+
if (!companion.capabilities.includes('skills') || skills.length === 0)
|
|
205
|
+
return '';
|
|
206
|
+
return [
|
|
207
|
+
'以下 Skill 已绑定到当前伙伴。已注入的可信 inline Skill 可直接遵循;其他 Skill 必须通过 partner_skill 加载或运行,不要假装已经执行。隔离 Skill 只能缩小既有工具权限。',
|
|
208
|
+
...skills.map(skill => `- ${skill.id}|${skill.displayName}|${skill.description}|${injectedIds.has(skill.id) ? '已注入当前会话' : skill.executionContext === 'fork' ? '按需隔离运行' : '按需加载'}`),
|
|
209
|
+
].join('\n');
|
|
210
|
+
}
|
|
211
|
+
async function fetchText(url, limit, init = {}, proxyUrl) {
|
|
212
|
+
const headers = new Headers(init.headers);
|
|
213
|
+
if (!headers.has('accept'))
|
|
214
|
+
headers.set('accept', 'application/json,text/markdown,text/plain');
|
|
215
|
+
return requestRemoteText({
|
|
216
|
+
url: validHttpUrl(url), maxBytes: limit, timeoutMs: 15_000, headers,
|
|
217
|
+
...(init.method ? { method: init.method } : {}),
|
|
218
|
+
...(typeof init.body === 'string' ? { body: init.body } : {}),
|
|
219
|
+
...(proxyUrl ? { proxyUrl } : {}),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
async function fetchSkillFromZip(url, limit, proxyUrl) {
|
|
223
|
+
const bytes = await requestRemoteBytes({
|
|
224
|
+
url: validHttpUrl(url), maxBytes: 8 * 1024 * 1024, timeoutMs: 20_000,
|
|
225
|
+
headers: { accept: 'application/zip,application/octet-stream' }, ...(proxyUrl ? { proxyUrl } : {}),
|
|
226
|
+
});
|
|
227
|
+
return extractSkillMarkdown(bytes, limit);
|
|
228
|
+
}
|
|
229
|
+
function validHttpUrl(value) {
|
|
230
|
+
const url = new URL(value);
|
|
231
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
232
|
+
throw new Error('Only HTTP(S) market URLs are supported');
|
|
233
|
+
url.username = '';
|
|
234
|
+
url.password = '';
|
|
235
|
+
return url.toString();
|
|
236
|
+
}
|
|
237
|
+
function normalizeChecksum(value) {
|
|
238
|
+
const checksum = value.replace(/^sha256:/i, '').trim().toLocaleLowerCase();
|
|
239
|
+
if (!/^[a-f0-9]{64}$/.test(checksum))
|
|
240
|
+
throw new Error('Skill checksum must be SHA-256');
|
|
241
|
+
return checksum;
|
|
242
|
+
}
|
|
243
|
+
function deduplicateMarket(entries) {
|
|
244
|
+
const seen = new Set();
|
|
245
|
+
return entries.filter(entry => { const key = `${entry.sourceId}:${entry.id}`; if (seen.has(key))
|
|
246
|
+
return false; seen.add(key); return true; });
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=service.js.map
|