@serviceme/devtools-core 0.1.5 → 0.1.6
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/index.d.mts +184 -2
- package/dist/index.d.ts +184 -2
- package/dist/index.js +576 -82
- package/dist/index.mjs +576 -90
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -5,6 +5,287 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
+
// src/agents/AgentCatalogClient.ts
|
|
9
|
+
var AgentCatalogClient = class {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
12
|
+
this.baseUrl = options.baseUrl;
|
|
13
|
+
}
|
|
14
|
+
async getCatalog() {
|
|
15
|
+
if (!this.baseUrl) {
|
|
16
|
+
return {
|
|
17
|
+
agents: [],
|
|
18
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const response = await this.fetchImpl(
|
|
22
|
+
`${this.baseUrl}/api/v1/marketplace/agents`
|
|
23
|
+
);
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`Failed to fetch agents catalog: ${response.status}`);
|
|
26
|
+
}
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
return {
|
|
29
|
+
agents: data.agents ?? [],
|
|
30
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/permissions/agent-permissions.ts
|
|
36
|
+
var TOOL_RISK_MAP = {
|
|
37
|
+
shell: "high",
|
|
38
|
+
terminal: "high",
|
|
39
|
+
run_in_terminal: "high",
|
|
40
|
+
execution_subagent: "high",
|
|
41
|
+
filesystem: "medium",
|
|
42
|
+
fetch: "medium",
|
|
43
|
+
fetch_webpage: "medium",
|
|
44
|
+
create_file: "medium",
|
|
45
|
+
replace_string_in_file: "medium",
|
|
46
|
+
multi_replace_string_in_file: "medium",
|
|
47
|
+
read_file: "low",
|
|
48
|
+
search: "low",
|
|
49
|
+
grep_search: "low",
|
|
50
|
+
file_search: "low",
|
|
51
|
+
semantic_search: "low",
|
|
52
|
+
list_dir: "low"
|
|
53
|
+
};
|
|
54
|
+
var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
55
|
+
var TOOLS_LINE_REGEX = /^tools:\s*$/m;
|
|
56
|
+
var TOOLS_INLINE_REGEX = /^tools:\s*\[([^\]]*)\]/m;
|
|
57
|
+
var LIST_ITEM_REGEX = /^\s*-\s+(.+)$/;
|
|
58
|
+
function parseAgentToolPermissions(content) {
|
|
59
|
+
const fmMatch = content.match(FRONTMATTER_REGEX);
|
|
60
|
+
if (!fmMatch?.[1]) return [];
|
|
61
|
+
const frontmatter = fmMatch[1];
|
|
62
|
+
const inlineMatch = frontmatter.match(TOOLS_INLINE_REGEX);
|
|
63
|
+
if (inlineMatch?.[1] != null) {
|
|
64
|
+
const raw = inlineMatch[1];
|
|
65
|
+
return raw.split(",").map((t) => t.trim()).filter(Boolean).map((tool) => ({
|
|
66
|
+
tool,
|
|
67
|
+
riskLevel: TOOL_RISK_MAP[tool] ?? "medium"
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
const blockMatch = frontmatter.match(TOOLS_LINE_REGEX);
|
|
71
|
+
if (!blockMatch?.[0]) return [];
|
|
72
|
+
const toolsStartIndex = frontmatter.indexOf(blockMatch[0]) + blockMatch[0].length;
|
|
73
|
+
const remaining = frontmatter.slice(toolsStartIndex);
|
|
74
|
+
const lines = remaining.split(/\r?\n/);
|
|
75
|
+
const tools = [];
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
const itemMatch = line.match(LIST_ITEM_REGEX);
|
|
78
|
+
if (itemMatch?.[1]) {
|
|
79
|
+
const tool = itemMatch[1].trim();
|
|
80
|
+
tools.push({ tool, riskLevel: TOOL_RISK_MAP[tool] ?? "medium" });
|
|
81
|
+
} else if (line.trim() !== "" && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return tools;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/agents/AgentReconciler.ts
|
|
89
|
+
var AgentReconciler = class {
|
|
90
|
+
constructor(deps) {
|
|
91
|
+
this.deps = deps;
|
|
92
|
+
}
|
|
93
|
+
async mutate(request) {
|
|
94
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
95
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
96
|
+
}
|
|
97
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
98
|
+
return {
|
|
99
|
+
status: "success",
|
|
100
|
+
changed: true,
|
|
101
|
+
message: `Agent ${request.action} completed.`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (request.action !== "install") {
|
|
105
|
+
return {
|
|
106
|
+
status: "blocked",
|
|
107
|
+
changed: false,
|
|
108
|
+
message: `Agent action is not supported by bridge reconciler: ${request.action}`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
112
|
+
const remoteAgent = catalog.agents.find(
|
|
113
|
+
(agent) => this.deps.agentStore.normalizeRemoteAgentId(agent.id) === request.agentId
|
|
114
|
+
);
|
|
115
|
+
if (!remoteAgent) {
|
|
116
|
+
return {
|
|
117
|
+
status: "blocked",
|
|
118
|
+
changed: false,
|
|
119
|
+
message: "Agent not found in catalog."
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (!request.confirmed && this.hasHighRiskTool(remoteAgent.tools)) {
|
|
123
|
+
return {
|
|
124
|
+
status: "requires_confirmation",
|
|
125
|
+
changed: false,
|
|
126
|
+
message: "This agent uses high-risk tools that require confirmation.",
|
|
127
|
+
tools: remoteAgent.tools
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
status: "success",
|
|
132
|
+
changed: true,
|
|
133
|
+
message: "Agent installed."
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
getPermissionSummary(agentId, agentName, content) {
|
|
137
|
+
const tools = parseAgentToolPermissions(content);
|
|
138
|
+
return {
|
|
139
|
+
agentId,
|
|
140
|
+
agentName,
|
|
141
|
+
tools,
|
|
142
|
+
highRiskCount: tools.filter((tool) => tool.riskLevel === "high").length,
|
|
143
|
+
mediumRiskCount: tools.filter((tool) => tool.riskLevel === "medium").length,
|
|
144
|
+
lowRiskCount: tools.filter((tool) => tool.riskLevel === "low").length
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
hasHighRiskTool(tools) {
|
|
148
|
+
return tools.some((tool) => TOOL_RISK_MAP[tool] === "high");
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/agents/AgentStore.ts
|
|
153
|
+
import * as fs from "fs/promises";
|
|
154
|
+
import * as path from "path";
|
|
155
|
+
var WORKSPACE_AGENTS_ROOT_RELATIVE = ".github/agents";
|
|
156
|
+
var WORKSPACE_AGENTS_STATE_RELATIVE = ".github/.ms-devtools-agents.yml";
|
|
157
|
+
var SAFE_LOCAL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
158
|
+
function assertSafeLocalAgentId(agentId) {
|
|
159
|
+
if (typeof agentId !== "string" || agentId.length === 0 || agentId === "." || agentId === ".." || agentId.includes("/") || agentId.includes("\\") || !SAFE_LOCAL_ID_PATTERN.test(agentId)) {
|
|
160
|
+
throw new Error(`Invalid agent id: ${agentId}`);
|
|
161
|
+
}
|
|
162
|
+
return agentId;
|
|
163
|
+
}
|
|
164
|
+
var AgentStore = class {
|
|
165
|
+
constructor(options) {
|
|
166
|
+
this.workspacePath = options.workspacePath;
|
|
167
|
+
this.userAgentsRoot = options.userAgentsRoot;
|
|
168
|
+
this.fileSystem = options.fileSystem ?? fs;
|
|
169
|
+
this.schemaVersion = options.schemaVersion ?? 1;
|
|
170
|
+
}
|
|
171
|
+
normalizeRemoteAgentId(remoteId) {
|
|
172
|
+
if (remoteId.startsWith("official/")) {
|
|
173
|
+
return assertSafeLocalAgentId(remoteId.slice("official/".length));
|
|
174
|
+
}
|
|
175
|
+
if (remoteId.startsWith("community/")) {
|
|
176
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
177
|
+
return assertSafeLocalAgentId(remoteId.slice(lastSlash + 1));
|
|
178
|
+
}
|
|
179
|
+
return assertSafeLocalAgentId(remoteId);
|
|
180
|
+
}
|
|
181
|
+
getWorkspaceAgentsRootPath() {
|
|
182
|
+
return WORKSPACE_AGENTS_ROOT_RELATIVE;
|
|
183
|
+
}
|
|
184
|
+
getWorkspaceStateFilePath() {
|
|
185
|
+
return WORKSPACE_AGENTS_STATE_RELATIVE;
|
|
186
|
+
}
|
|
187
|
+
getUserAgentsRootPath() {
|
|
188
|
+
return this.userAgentsRoot;
|
|
189
|
+
}
|
|
190
|
+
async listWorkspaceAgentIds() {
|
|
191
|
+
return this.listAgentIds(
|
|
192
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE)
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
async listUserAgentIds() {
|
|
196
|
+
return this.listAgentIds(this.userAgentsRoot);
|
|
197
|
+
}
|
|
198
|
+
async readState() {
|
|
199
|
+
try {
|
|
200
|
+
const raw = await this.fileSystem.readFile(
|
|
201
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE),
|
|
202
|
+
"utf-8"
|
|
203
|
+
);
|
|
204
|
+
const parsed = JSON.parse(raw);
|
|
205
|
+
if (typeof parsed.schemaVersion !== "number" || !Array.isArray(parsed.installedAgents)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return parsed;
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async writeState(state) {
|
|
214
|
+
const statePath = path.join(
|
|
215
|
+
this.workspacePath,
|
|
216
|
+
WORKSPACE_AGENTS_STATE_RELATIVE
|
|
217
|
+
);
|
|
218
|
+
await this.fileSystem.mkdir(path.dirname(statePath), { recursive: true });
|
|
219
|
+
await this.fileSystem.writeFile(
|
|
220
|
+
statePath,
|
|
221
|
+
JSON.stringify(state, null, 2),
|
|
222
|
+
"utf-8"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
async addInstalledAgent(entry) {
|
|
226
|
+
const state = await this.readState() ?? {
|
|
227
|
+
schemaVersion: this.schemaVersion,
|
|
228
|
+
installedAgents: []
|
|
229
|
+
};
|
|
230
|
+
state.installedAgents = state.installedAgents.filter(
|
|
231
|
+
(agent) => agent.id !== entry.id
|
|
232
|
+
);
|
|
233
|
+
state.installedAgents.push(entry);
|
|
234
|
+
await this.writeState(state);
|
|
235
|
+
}
|
|
236
|
+
async removeInstalledAgent(agentId) {
|
|
237
|
+
const state = await this.readState();
|
|
238
|
+
if (!state) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
state.installedAgents = state.installedAgents.filter(
|
|
242
|
+
(agent) => agent.id !== agentId
|
|
243
|
+
);
|
|
244
|
+
await this.writeState(state);
|
|
245
|
+
}
|
|
246
|
+
async writeAgentFiles(agentId, scope, files) {
|
|
247
|
+
const root = scope === "workspace" ? path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE) : this.userAgentsRoot;
|
|
248
|
+
const firstFile = files[0];
|
|
249
|
+
const isSingleFlatFile = files.length === 1 && firstFile !== void 0 && firstFile.path === `${agentId}.agent.md` && !firstFile.path.includes("/");
|
|
250
|
+
const targetDir = isSingleFlatFile ? root : path.join(root, agentId);
|
|
251
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
252
|
+
for (const file of files) {
|
|
253
|
+
const filePath = path.join(targetDir, file.path);
|
|
254
|
+
await this.fileSystem.mkdir(path.dirname(filePath), { recursive: true });
|
|
255
|
+
await this.fileSystem.writeFile(filePath, file.content, "utf-8");
|
|
256
|
+
if (file.executable) {
|
|
257
|
+
try {
|
|
258
|
+
await this.fileSystem.chmod(filePath, 493);
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async listAgentIds(dir) {
|
|
265
|
+
try {
|
|
266
|
+
const entries = await this.fileSystem.readdir(dir, {
|
|
267
|
+
withFileTypes: true
|
|
268
|
+
});
|
|
269
|
+
const ids = [];
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
if (entry.name.startsWith(".")) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (entry.isDirectory()) {
|
|
275
|
+
ids.push(entry.name);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (entry.isFile() && entry.name.endsWith(".agent.md")) {
|
|
279
|
+
ids.push(entry.name.replace(/\.agent\.md$/, ""));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return ids.sort();
|
|
283
|
+
} catch {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
8
289
|
// src/copilot/doctor.ts
|
|
9
290
|
import {
|
|
10
291
|
COPILOT_ERROR_CODES,
|
|
@@ -248,7 +529,13 @@ var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
|
|
|
248
529
|
var TOOL_CHECK_TIMEOUT_MS = {
|
|
249
530
|
nuget: 12e3,
|
|
250
531
|
nvm: 8e3,
|
|
251
|
-
dotnet: 8e3
|
|
532
|
+
dotnet: 8e3,
|
|
533
|
+
// nvm4w/npm shims and globally-installed .cmd tools (pnpm, nrm) are slow to
|
|
534
|
+
// resolve on cold PATHs. Give them extra headroom so the version probe
|
|
535
|
+
// doesn't fall through to the generic 5s default.
|
|
536
|
+
npm: 15e3,
|
|
537
|
+
pnpm: 15e3,
|
|
538
|
+
nrm: 15e3
|
|
252
539
|
};
|
|
253
540
|
var ERROR_CODE_NOT_FOUND = 127;
|
|
254
541
|
var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
|
|
@@ -295,8 +582,29 @@ var EnvironmentInspector = class {
|
|
|
295
582
|
return void 0;
|
|
296
583
|
}
|
|
297
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* On Windows, prefer the `.cmd` shim for npm/pnpm/nrm. nvm4w registers
|
|
587
|
+
* BOTH an extensionless entry (pointing to node.exe) and a `<tool>.cmd`
|
|
588
|
+
* wrapper; `where` returns them in that order, and the extensionless one
|
|
589
|
+
* would just print node's own version.
|
|
590
|
+
*/
|
|
591
|
+
async getToolShimPath(toolName) {
|
|
592
|
+
try {
|
|
593
|
+
const result = await runCommand("where", {
|
|
594
|
+
args: [toolName],
|
|
595
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
596
|
+
});
|
|
597
|
+
const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
598
|
+
const cmdShim = candidates.find(
|
|
599
|
+
(line) => line.toLowerCase().endsWith(".cmd")
|
|
600
|
+
);
|
|
601
|
+
return cmdShim ?? candidates[0];
|
|
602
|
+
} catch {
|
|
603
|
+
return void 0;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
298
606
|
async getToolVersion(toolName) {
|
|
299
|
-
const invocation = this.getVersionInvocation(toolName);
|
|
607
|
+
const invocation = await this.getVersionInvocation(toolName);
|
|
300
608
|
const result = await runCommand(invocation.command, {
|
|
301
609
|
args: invocation.args,
|
|
302
610
|
timeoutMs: this.getToolTimeout(toolName)
|
|
@@ -371,12 +679,19 @@ var EnvironmentInspector = class {
|
|
|
371
679
|
return this.handleToolCheckError(error);
|
|
372
680
|
}
|
|
373
681
|
}
|
|
374
|
-
getVersionInvocation(toolName) {
|
|
682
|
+
async getVersionInvocation(toolName) {
|
|
375
683
|
const isWindows = process.platform === "win32";
|
|
376
684
|
if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
|
|
685
|
+
const shim = await this.getToolShimPath(toolName);
|
|
686
|
+
if (shim) {
|
|
687
|
+
return {
|
|
688
|
+
command: "cmd.exe",
|
|
689
|
+
args: ["/c", shim, "--version"]
|
|
690
|
+
};
|
|
691
|
+
}
|
|
377
692
|
return {
|
|
378
693
|
command: "cmd.exe",
|
|
379
|
-
args: ["/
|
|
694
|
+
args: ["/c", toolName, "--version"]
|
|
380
695
|
};
|
|
381
696
|
}
|
|
382
697
|
const commands = {
|
|
@@ -429,8 +744,8 @@ var EnvironmentInspector = class {
|
|
|
429
744
|
};
|
|
430
745
|
|
|
431
746
|
// src/image/imageTools.ts
|
|
432
|
-
import * as
|
|
433
|
-
import * as
|
|
747
|
+
import * as fs2 from "fs/promises";
|
|
748
|
+
import * as path2 from "path";
|
|
434
749
|
import {
|
|
435
750
|
createServicemeError as createServicemeError4
|
|
436
751
|
} from "@serviceme/devtools-protocol";
|
|
@@ -453,7 +768,7 @@ var ImageTools = class {
|
|
|
453
768
|
if (!await this.pathExists(filePath)) {
|
|
454
769
|
return { valid: false };
|
|
455
770
|
}
|
|
456
|
-
if (!SUPPORTED_FORMATS.includes(
|
|
771
|
+
if (!SUPPORTED_FORMATS.includes(path2.extname(filePath).toLowerCase())) {
|
|
457
772
|
return { valid: false };
|
|
458
773
|
}
|
|
459
774
|
await this.getInfo(filePath, sharpModulePath);
|
|
@@ -465,7 +780,7 @@ var ImageTools = class {
|
|
|
465
780
|
async getInfo(imagePath, sharpModulePath) {
|
|
466
781
|
const sharp = this.loadSharp(sharpModulePath);
|
|
467
782
|
const metadata = await sharp(imagePath).metadata();
|
|
468
|
-
const stats = await
|
|
783
|
+
const stats = await fs2.stat(imagePath);
|
|
469
784
|
return {
|
|
470
785
|
width: metadata.width ?? 0,
|
|
471
786
|
height: metadata.height ?? 0,
|
|
@@ -482,7 +797,7 @@ var ImageTools = class {
|
|
|
482
797
|
`Invalid image file: ${imagePath}`
|
|
483
798
|
);
|
|
484
799
|
}
|
|
485
|
-
const originalStats = await
|
|
800
|
+
const originalStats = await fs2.stat(imagePath);
|
|
486
801
|
const originalSize = originalStats.size;
|
|
487
802
|
const outputPath = this.getOutputPath(imagePath, options);
|
|
488
803
|
const compressedBuffer = await this.compressWithSharp(imagePath, options);
|
|
@@ -497,7 +812,7 @@ var ImageTools = class {
|
|
|
497
812
|
outputPath: imagePath
|
|
498
813
|
};
|
|
499
814
|
}
|
|
500
|
-
await
|
|
815
|
+
await fs2.writeFile(outputPath, compressedBuffer);
|
|
501
816
|
return {
|
|
502
817
|
originalSize,
|
|
503
818
|
compressedSize,
|
|
@@ -508,7 +823,7 @@ var ImageTools = class {
|
|
|
508
823
|
async compressWithSharp(imagePath, options) {
|
|
509
824
|
const sharp = this.loadSharp(options.sharpModulePath);
|
|
510
825
|
let pipeline = sharp(imagePath);
|
|
511
|
-
switch (options.format ??
|
|
826
|
+
switch (options.format ?? path2.extname(imagePath).toLowerCase().slice(1)) {
|
|
512
827
|
case "jpg":
|
|
513
828
|
case "jpeg":
|
|
514
829
|
pipeline = pipeline.jpeg({ quality: options.quality });
|
|
@@ -534,14 +849,14 @@ var ImageTools = class {
|
|
|
534
849
|
if (options.replaceOriginImage) {
|
|
535
850
|
return inputPath;
|
|
536
851
|
}
|
|
537
|
-
const dir =
|
|
538
|
-
const ext =
|
|
539
|
-
const name =
|
|
540
|
-
return
|
|
852
|
+
const dir = path2.dirname(inputPath);
|
|
853
|
+
const ext = path2.extname(inputPath);
|
|
854
|
+
const name = path2.basename(inputPath, ext);
|
|
855
|
+
return path2.join(dir, `${name}_compressed${ext}`);
|
|
541
856
|
}
|
|
542
857
|
async pathExists(targetPath) {
|
|
543
858
|
try {
|
|
544
|
-
await
|
|
859
|
+
await fs2.access(targetPath);
|
|
545
860
|
return true;
|
|
546
861
|
} catch {
|
|
547
862
|
return false;
|
|
@@ -700,8 +1015,8 @@ function createConsoleLogger(prefix = "serviceme") {
|
|
|
700
1015
|
}
|
|
701
1016
|
|
|
702
1017
|
// src/project/projectTools.ts
|
|
703
|
-
import * as
|
|
704
|
-
import * as
|
|
1018
|
+
import * as fs3 from "fs/promises";
|
|
1019
|
+
import * as path3 from "path";
|
|
705
1020
|
import {
|
|
706
1021
|
createServicemeError as createServicemeError6
|
|
707
1022
|
} from "@serviceme/devtools-protocol";
|
|
@@ -717,7 +1032,7 @@ import {
|
|
|
717
1032
|
rename,
|
|
718
1033
|
rm
|
|
719
1034
|
} from "fs/promises";
|
|
720
|
-
import { dirname as
|
|
1035
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
721
1036
|
import { open } from "yauzl";
|
|
722
1037
|
var unzipFile = (zipPath, dest) => {
|
|
723
1038
|
return new Promise((resolve, reject) => {
|
|
@@ -730,12 +1045,12 @@ var unzipFile = (zipPath, dest) => {
|
|
|
730
1045
|
zipfile.readEntry();
|
|
731
1046
|
zipfile.on("entry", (entry) => {
|
|
732
1047
|
if (/\/$/.test(entry.fileName)) {
|
|
733
|
-
void mkdir(
|
|
1048
|
+
void mkdir(join3(dest, entry.fileName), { recursive: true }).then(() => {
|
|
734
1049
|
zipfile.readEntry();
|
|
735
1050
|
}).catch(reject);
|
|
736
1051
|
} else {
|
|
737
|
-
const outputPath =
|
|
738
|
-
void mkdir(
|
|
1052
|
+
const outputPath = join3(dest, entry.fileName);
|
|
1053
|
+
void mkdir(dirname3(outputPath), { recursive: true }).then(() => {
|
|
739
1054
|
zipfile.openReadStream(
|
|
740
1055
|
entry,
|
|
741
1056
|
(streamError, readStream) => {
|
|
@@ -788,8 +1103,8 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
|
|
|
788
1103
|
const children = await readdir(sourcePath);
|
|
789
1104
|
for (const child of children) {
|
|
790
1105
|
await mergeEntry(
|
|
791
|
-
|
|
792
|
-
|
|
1106
|
+
join3(sourcePath, child),
|
|
1107
|
+
join3(destPath, child),
|
|
793
1108
|
overwrite
|
|
794
1109
|
);
|
|
795
1110
|
}
|
|
@@ -814,8 +1129,8 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
814
1129
|
await mkdir(destDir, { recursive: true });
|
|
815
1130
|
const files = await readdir(sourceDir);
|
|
816
1131
|
for (const file of files) {
|
|
817
|
-
const sourceFile =
|
|
818
|
-
const destFile =
|
|
1132
|
+
const sourceFile = join3(sourceDir, file);
|
|
1133
|
+
const destFile = join3(destDir, file);
|
|
819
1134
|
if (!overwrite) {
|
|
820
1135
|
try {
|
|
821
1136
|
await access2(destFile, constants.F_OK);
|
|
@@ -832,10 +1147,10 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
832
1147
|
var ProjectTools = class {
|
|
833
1148
|
async extractTemplate(zipPath, workspacePath, tempExtractDir, input) {
|
|
834
1149
|
await unzipFile(zipPath, tempExtractDir);
|
|
835
|
-
let sourceDir =
|
|
1150
|
+
let sourceDir = path3.join(tempExtractDir, input.extractedDirName);
|
|
836
1151
|
let actualDirName = input.extractedDirName;
|
|
837
1152
|
if (!await this.pathExists(sourceDir)) {
|
|
838
|
-
const entries = await
|
|
1153
|
+
const entries = await fs3.readdir(tempExtractDir, { withFileTypes: true });
|
|
839
1154
|
const directories = entries.filter(
|
|
840
1155
|
(entry) => entry.isDirectory() && !entry.name.startsWith(".")
|
|
841
1156
|
);
|
|
@@ -847,7 +1162,7 @@ var ProjectTools = class {
|
|
|
847
1162
|
);
|
|
848
1163
|
if (selectedDirectory) {
|
|
849
1164
|
actualDirName = selectedDirectory;
|
|
850
|
-
sourceDir =
|
|
1165
|
+
sourceDir = path3.join(tempExtractDir, actualDirName);
|
|
851
1166
|
} else if (directories.length === 0) {
|
|
852
1167
|
throw new Error(
|
|
853
1168
|
`No directory found after extraction. Expected directory: ${input.extractedDirName}`
|
|
@@ -905,7 +1220,7 @@ var ProjectTools = class {
|
|
|
905
1220
|
} else {
|
|
906
1221
|
for (const scriptPath of scripts) {
|
|
907
1222
|
try {
|
|
908
|
-
await
|
|
1223
|
+
await fs3.chmod(scriptPath, 493);
|
|
909
1224
|
updatedCount += 1;
|
|
910
1225
|
} catch {
|
|
911
1226
|
}
|
|
@@ -953,12 +1268,12 @@ var ProjectTools = class {
|
|
|
953
1268
|
const results = [];
|
|
954
1269
|
let entries;
|
|
955
1270
|
try {
|
|
956
|
-
entries = await
|
|
1271
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
957
1272
|
} catch {
|
|
958
1273
|
return results;
|
|
959
1274
|
}
|
|
960
1275
|
for (const entry of entries) {
|
|
961
|
-
const fullPath =
|
|
1276
|
+
const fullPath = path3.join(dir, entry.name);
|
|
962
1277
|
if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
963
1278
|
results.push(...await this.findScripts(fullPath, extensions));
|
|
964
1279
|
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -969,7 +1284,7 @@ var ProjectTools = class {
|
|
|
969
1284
|
}
|
|
970
1285
|
async pathExists(targetPath) {
|
|
971
1286
|
try {
|
|
972
|
-
await
|
|
1287
|
+
await fs3.access(targetPath);
|
|
973
1288
|
return true;
|
|
974
1289
|
} catch {
|
|
975
1290
|
return false;
|
|
@@ -988,7 +1303,7 @@ var ProjectTools = class {
|
|
|
988
1303
|
const matches = [];
|
|
989
1304
|
for (const directoryName of directoryNames) {
|
|
990
1305
|
if (await this.directoryMatchesProjectPattern(
|
|
991
|
-
|
|
1306
|
+
path3.join(tempExtractDir, directoryName),
|
|
992
1307
|
projectFilePattern
|
|
993
1308
|
)) {
|
|
994
1309
|
matches.push(directoryName);
|
|
@@ -1000,7 +1315,7 @@ var ProjectTools = class {
|
|
|
1000
1315
|
return null;
|
|
1001
1316
|
}
|
|
1002
1317
|
async directoryMatchesProjectPattern(directoryPath, projectFilePattern) {
|
|
1003
|
-
const entries = await
|
|
1318
|
+
const entries = await fs3.readdir(directoryPath);
|
|
1004
1319
|
if (projectFilePattern.includes("*")) {
|
|
1005
1320
|
const regex = new RegExp(`^${projectFilePattern.replace("*", ".*")}$`);
|
|
1006
1321
|
return entries.some((entry) => regex.test(entry));
|
|
@@ -1013,17 +1328,17 @@ function createProjectTools() {
|
|
|
1013
1328
|
}
|
|
1014
1329
|
|
|
1015
1330
|
// src/scheduled-tasks/daemon/DaemonLogger.ts
|
|
1016
|
-
import * as
|
|
1017
|
-
import * as
|
|
1331
|
+
import * as fs4 from "fs";
|
|
1332
|
+
import * as path4 from "path";
|
|
1018
1333
|
var CONFIG_DIR = ".serviceme";
|
|
1019
1334
|
var LOG_FILE = "scheduler.log";
|
|
1020
1335
|
var MAX_LOG_SIZE = 1024 * 1024;
|
|
1021
1336
|
var DaemonLogger = class {
|
|
1022
1337
|
constructor(workspacePath) {
|
|
1023
|
-
this.logPath =
|
|
1024
|
-
const dir =
|
|
1025
|
-
if (!
|
|
1026
|
-
|
|
1338
|
+
this.logPath = path4.join(workspacePath, CONFIG_DIR, LOG_FILE);
|
|
1339
|
+
const dir = path4.dirname(this.logPath);
|
|
1340
|
+
if (!fs4.existsSync(dir)) {
|
|
1341
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1027
1342
|
}
|
|
1028
1343
|
}
|
|
1029
1344
|
getLogPath() {
|
|
@@ -1034,16 +1349,16 @@ var DaemonLogger = class {
|
|
|
1034
1349
|
const line = `[${ts}] [${level.toUpperCase()}] ${message}
|
|
1035
1350
|
`;
|
|
1036
1351
|
this.rotateIfNeeded();
|
|
1037
|
-
|
|
1352
|
+
fs4.appendFileSync(this.logPath, line, "utf-8");
|
|
1038
1353
|
}
|
|
1039
1354
|
rotateIfNeeded() {
|
|
1040
1355
|
try {
|
|
1041
|
-
const stats =
|
|
1356
|
+
const stats = fs4.statSync(this.logPath);
|
|
1042
1357
|
if (stats.size > MAX_LOG_SIZE) {
|
|
1043
|
-
const content =
|
|
1358
|
+
const content = fs4.readFileSync(this.logPath, "utf-8");
|
|
1044
1359
|
const halfIdx = content.indexOf("\n", Math.floor(content.length / 2));
|
|
1045
1360
|
if (halfIdx > 0) {
|
|
1046
|
-
|
|
1361
|
+
fs4.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
|
|
1047
1362
|
}
|
|
1048
1363
|
}
|
|
1049
1364
|
} catch {
|
|
@@ -1052,33 +1367,33 @@ var DaemonLogger = class {
|
|
|
1052
1367
|
};
|
|
1053
1368
|
|
|
1054
1369
|
// src/scheduled-tasks/daemon/PidManager.ts
|
|
1055
|
-
import * as
|
|
1056
|
-
import * as
|
|
1370
|
+
import * as fs5 from "fs";
|
|
1371
|
+
import * as path5 from "path";
|
|
1057
1372
|
var CONFIG_DIR2 = ".serviceme";
|
|
1058
1373
|
var PID_FILE = "scheduler.pid";
|
|
1059
1374
|
var PidManager = class {
|
|
1060
1375
|
constructor(workspacePath) {
|
|
1061
|
-
this.pidPath =
|
|
1376
|
+
this.pidPath = path5.join(workspacePath, CONFIG_DIR2, PID_FILE);
|
|
1062
1377
|
}
|
|
1063
1378
|
getPidPath() {
|
|
1064
1379
|
return this.pidPath;
|
|
1065
1380
|
}
|
|
1066
1381
|
writePid(pid) {
|
|
1067
|
-
const dir =
|
|
1068
|
-
if (!
|
|
1069
|
-
|
|
1382
|
+
const dir = path5.dirname(this.pidPath);
|
|
1383
|
+
if (!fs5.existsSync(dir)) {
|
|
1384
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1070
1385
|
}
|
|
1071
|
-
|
|
1386
|
+
fs5.writeFileSync(this.pidPath, String(pid), "utf-8");
|
|
1072
1387
|
}
|
|
1073
1388
|
readPid() {
|
|
1074
|
-
if (!
|
|
1075
|
-
const raw =
|
|
1389
|
+
if (!fs5.existsSync(this.pidPath)) return null;
|
|
1390
|
+
const raw = fs5.readFileSync(this.pidPath, "utf-8").trim();
|
|
1076
1391
|
const pid = Number.parseInt(raw, 10);
|
|
1077
1392
|
return Number.isNaN(pid) ? null : pid;
|
|
1078
1393
|
}
|
|
1079
1394
|
removePid() {
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
1395
|
+
if (fs5.existsSync(this.pidPath)) {
|
|
1396
|
+
fs5.unlinkSync(this.pidPath);
|
|
1082
1397
|
}
|
|
1083
1398
|
}
|
|
1084
1399
|
isProcessRunning(pid) {
|
|
@@ -1099,12 +1414,12 @@ var PidManager = class {
|
|
|
1099
1414
|
};
|
|
1100
1415
|
|
|
1101
1416
|
// src/scheduled-tasks/daemon/SchedulerDaemon.ts
|
|
1102
|
-
import * as
|
|
1417
|
+
import * as fs10 from "fs";
|
|
1103
1418
|
import * as os from "os";
|
|
1104
1419
|
|
|
1105
1420
|
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1106
1421
|
import { spawn as spawn2 } from "child_process";
|
|
1107
|
-
import * as
|
|
1422
|
+
import * as fs6 from "fs";
|
|
1108
1423
|
|
|
1109
1424
|
// src/scheduled-tasks/executors/timeout.ts
|
|
1110
1425
|
function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
|
|
@@ -1139,7 +1454,7 @@ function redactArgs(args) {
|
|
|
1139
1454
|
function writeDiagnostic(message) {
|
|
1140
1455
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1141
1456
|
if (logPath) {
|
|
1142
|
-
|
|
1457
|
+
fs6.appendFileSync(logPath, message);
|
|
1143
1458
|
return;
|
|
1144
1459
|
}
|
|
1145
1460
|
process.stderr.write(message);
|
|
@@ -1349,15 +1664,15 @@ ${body}`.trim()
|
|
|
1349
1664
|
|
|
1350
1665
|
// src/scheduled-tasks/executors/ShellExecutor.ts
|
|
1351
1666
|
import { spawn as spawn3 } from "child_process";
|
|
1352
|
-
import * as
|
|
1353
|
-
import * as
|
|
1667
|
+
import * as fs7 from "fs";
|
|
1668
|
+
import * as path6 from "path";
|
|
1354
1669
|
var MAX_OUTPUT_BYTES2 = 1024 * 1024;
|
|
1355
1670
|
var DEFAULT_TIMEOUT_MS4 = 6e4;
|
|
1356
1671
|
var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
|
|
1357
1672
|
function resolveShellExecution(script, options = {}) {
|
|
1358
1673
|
const platform = options.platform ?? process.platform;
|
|
1359
1674
|
const env = options.env ?? process.env;
|
|
1360
|
-
const fileExists = options.fileExists ??
|
|
1675
|
+
const fileExists = options.fileExists ?? fs7.existsSync;
|
|
1361
1676
|
if (platform === "win32") {
|
|
1362
1677
|
const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
|
|
1363
1678
|
if (posixShell) {
|
|
@@ -1402,10 +1717,10 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1402
1717
|
if (fileExists(candidate)) return candidate;
|
|
1403
1718
|
}
|
|
1404
1719
|
const pathValue = env.Path ?? env.PATH ?? "";
|
|
1405
|
-
for (const dir of pathValue.split(
|
|
1720
|
+
for (const dir of pathValue.split(path6.win32.delimiter)) {
|
|
1406
1721
|
if (!dir) continue;
|
|
1407
1722
|
for (const executable of POSIX_SHELL_CANDIDATES) {
|
|
1408
|
-
const candidate =
|
|
1723
|
+
const candidate = path6.win32.join(dir, executable);
|
|
1409
1724
|
if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
|
|
1410
1725
|
return candidate;
|
|
1411
1726
|
}
|
|
@@ -1414,13 +1729,13 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1414
1729
|
return null;
|
|
1415
1730
|
}
|
|
1416
1731
|
function isWindowsWslLauncher(candidate) {
|
|
1417
|
-
const normalized =
|
|
1732
|
+
const normalized = path6.win32.normalize(candidate).toLowerCase();
|
|
1418
1733
|
return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
|
|
1419
1734
|
}
|
|
1420
1735
|
function writeDiagnostic2(message) {
|
|
1421
1736
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1422
1737
|
if (logPath) {
|
|
1423
|
-
|
|
1738
|
+
fs7.appendFileSync(logPath, message);
|
|
1424
1739
|
return;
|
|
1425
1740
|
}
|
|
1426
1741
|
process.stderr.write(message);
|
|
@@ -1567,8 +1882,8 @@ function getExecutor(taskType) {
|
|
|
1567
1882
|
|
|
1568
1883
|
// src/scheduled-tasks/TaskConfigManager.ts
|
|
1569
1884
|
import { randomUUID } from "crypto";
|
|
1570
|
-
import * as
|
|
1571
|
-
import * as
|
|
1885
|
+
import * as fs8 from "fs";
|
|
1886
|
+
import * as path7 from "path";
|
|
1572
1887
|
import { createServicemeError as createServicemeError7 } from "@serviceme/devtools-protocol";
|
|
1573
1888
|
var CONFIG_DIR3 = ".serviceme";
|
|
1574
1889
|
var CONFIG_FILE = "scheduled-tasks.json";
|
|
@@ -1605,27 +1920,27 @@ function validateTaskPayload(taskType, payload) {
|
|
|
1605
1920
|
}
|
|
1606
1921
|
var TaskConfigManager = class {
|
|
1607
1922
|
constructor(workspacePath) {
|
|
1608
|
-
this.configPath =
|
|
1923
|
+
this.configPath = path7.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
|
|
1609
1924
|
}
|
|
1610
1925
|
getConfigPath() {
|
|
1611
1926
|
return this.configPath;
|
|
1612
1927
|
}
|
|
1613
1928
|
readConfig() {
|
|
1614
|
-
if (!
|
|
1929
|
+
if (!fs8.existsSync(this.configPath)) {
|
|
1615
1930
|
return emptyConfig();
|
|
1616
1931
|
}
|
|
1617
|
-
const raw =
|
|
1932
|
+
const raw = fs8.readFileSync(this.configPath, "utf-8");
|
|
1618
1933
|
const parsed = JSON.parse(raw);
|
|
1619
1934
|
return parsed;
|
|
1620
1935
|
}
|
|
1621
1936
|
writeConfig(config) {
|
|
1622
|
-
const dir =
|
|
1623
|
-
if (!
|
|
1624
|
-
|
|
1937
|
+
const dir = path7.dirname(this.configPath);
|
|
1938
|
+
if (!fs8.existsSync(dir)) {
|
|
1939
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1625
1940
|
}
|
|
1626
1941
|
const tmp = `${this.configPath}.tmp`;
|
|
1627
|
-
|
|
1628
|
-
|
|
1942
|
+
fs8.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
|
|
1943
|
+
fs8.renameSync(tmp, this.configPath);
|
|
1629
1944
|
}
|
|
1630
1945
|
listTasks() {
|
|
1631
1946
|
return this.readConfig().tasks;
|
|
@@ -1896,8 +2211,8 @@ var TaskExecutionEngine = class {
|
|
|
1896
2211
|
|
|
1897
2212
|
// src/scheduled-tasks/TaskLogManager.ts
|
|
1898
2213
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1899
|
-
import * as
|
|
1900
|
-
import * as
|
|
2214
|
+
import * as fs9 from "fs";
|
|
2215
|
+
import * as path8 from "path";
|
|
1901
2216
|
var CONFIG_DIR4 = ".serviceme";
|
|
1902
2217
|
var LOG_FILE2 = "scheduled-tasks-log.json";
|
|
1903
2218
|
var MAX_LOGS = 200;
|
|
@@ -1922,17 +2237,17 @@ function validateAndRepairLogFile(raw) {
|
|
|
1922
2237
|
}
|
|
1923
2238
|
var TaskLogManager = class {
|
|
1924
2239
|
constructor(workspacePath) {
|
|
1925
|
-
this.logPath =
|
|
2240
|
+
this.logPath = path8.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
|
|
1926
2241
|
}
|
|
1927
2242
|
getLogPath() {
|
|
1928
2243
|
return this.logPath;
|
|
1929
2244
|
}
|
|
1930
2245
|
readLogFile() {
|
|
1931
|
-
if (!
|
|
2246
|
+
if (!fs9.existsSync(this.logPath)) {
|
|
1932
2247
|
return emptyLogFile();
|
|
1933
2248
|
}
|
|
1934
2249
|
try {
|
|
1935
|
-
const raw =
|
|
2250
|
+
const raw = fs9.readFileSync(this.logPath, "utf-8");
|
|
1936
2251
|
const parsed = JSON.parse(raw);
|
|
1937
2252
|
const file = validateAndRepairLogFile(parsed);
|
|
1938
2253
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
|
|
@@ -1948,21 +2263,21 @@ var TaskLogManager = class {
|
|
|
1948
2263
|
}
|
|
1949
2264
|
backupCorruptedFile() {
|
|
1950
2265
|
try {
|
|
1951
|
-
if (
|
|
2266
|
+
if (fs9.existsSync(this.logPath)) {
|
|
1952
2267
|
const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
|
|
1953
|
-
|
|
2268
|
+
fs9.copyFileSync(this.logPath, backupPath);
|
|
1954
2269
|
}
|
|
1955
2270
|
} catch {
|
|
1956
2271
|
}
|
|
1957
2272
|
}
|
|
1958
2273
|
writeLogFile(file) {
|
|
1959
|
-
const dir =
|
|
1960
|
-
if (!
|
|
1961
|
-
|
|
2274
|
+
const dir = path8.dirname(this.logPath);
|
|
2275
|
+
if (!fs9.existsSync(dir)) {
|
|
2276
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1962
2277
|
}
|
|
1963
2278
|
const tmp = `${this.logPath}.tmp`;
|
|
1964
|
-
|
|
1965
|
-
|
|
2279
|
+
fs9.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
|
|
2280
|
+
fs9.renameSync(tmp, this.logPath);
|
|
1966
2281
|
}
|
|
1967
2282
|
appendLog(input) {
|
|
1968
2283
|
const file = this.readLogFile();
|
|
@@ -2073,8 +2388,8 @@ var SchedulerDaemon = class {
|
|
|
2073
2388
|
const configPath = this.configManager.getConfigPath();
|
|
2074
2389
|
const dir = configPath.substring(0, configPath.lastIndexOf("/"));
|
|
2075
2390
|
try {
|
|
2076
|
-
if (
|
|
2077
|
-
this.watcher =
|
|
2391
|
+
if (fs10.existsSync(dir)) {
|
|
2392
|
+
this.watcher = fs10.watch(dir, (_eventType, filename) => {
|
|
2078
2393
|
if (filename === "scheduled-tasks.json") {
|
|
2079
2394
|
this.logger.log("info", "Config file changed, reconciling...");
|
|
2080
2395
|
}
|
|
@@ -2226,7 +2541,173 @@ function matchCronField(field, value) {
|
|
|
2226
2541
|
const values = field.split(",");
|
|
2227
2542
|
return values.some((v) => Number.parseInt(v, 10) === value);
|
|
2228
2543
|
}
|
|
2544
|
+
|
|
2545
|
+
// src/skills/SkillCatalogClient.ts
|
|
2546
|
+
var SkillCatalogClient = class {
|
|
2547
|
+
constructor(options = {}) {
|
|
2548
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
2549
|
+
this.baseUrl = options.baseUrl;
|
|
2550
|
+
}
|
|
2551
|
+
async getCatalog() {
|
|
2552
|
+
if (!this.baseUrl) {
|
|
2553
|
+
return {
|
|
2554
|
+
skills: [],
|
|
2555
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
const response = await this.fetchImpl(
|
|
2559
|
+
`${this.baseUrl}/api/v1/marketplace/skills`
|
|
2560
|
+
);
|
|
2561
|
+
if (!response.ok) {
|
|
2562
|
+
throw new Error(`Failed to fetch skills catalog: ${response.status}`);
|
|
2563
|
+
}
|
|
2564
|
+
const data = await response.json();
|
|
2565
|
+
return {
|
|
2566
|
+
skills: data.skills ?? [],
|
|
2567
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
|
|
2572
|
+
// src/skills/SkillReconciler.ts
|
|
2573
|
+
var SkillReconciler = class {
|
|
2574
|
+
constructor(deps) {
|
|
2575
|
+
this.deps = deps;
|
|
2576
|
+
}
|
|
2577
|
+
async mutate(request) {
|
|
2578
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
2579
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
2580
|
+
}
|
|
2581
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
2582
|
+
return {
|
|
2583
|
+
status: "success",
|
|
2584
|
+
changed: true,
|
|
2585
|
+
message: `Skill ${request.action} completed.`
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
if (request.action !== "install") {
|
|
2589
|
+
return {
|
|
2590
|
+
status: "blocked",
|
|
2591
|
+
changed: false,
|
|
2592
|
+
message: `Skill action is not supported by bridge reconciler: ${request.action}`
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
2596
|
+
const remoteSkill = catalog.skills.find(
|
|
2597
|
+
(skill) => this.deps.skillStore.normalizeRemoteSkillId(skill.id) === request.skillId
|
|
2598
|
+
);
|
|
2599
|
+
if (!remoteSkill) {
|
|
2600
|
+
return {
|
|
2601
|
+
status: "blocked",
|
|
2602
|
+
changed: false,
|
|
2603
|
+
message: "Skill not found in catalog."
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
if (!request.confirmed && (remoteSkill.hasScripts || remoteSkill.hasHooks)) {
|
|
2607
|
+
return {
|
|
2608
|
+
status: "requires_confirmation",
|
|
2609
|
+
changed: false,
|
|
2610
|
+
hasScripts: Boolean(remoteSkill.hasScripts),
|
|
2611
|
+
hasHooks: Boolean(remoteSkill.hasHooks),
|
|
2612
|
+
message: "This skill contains executable scripts that require confirmation."
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
return {
|
|
2616
|
+
status: "success",
|
|
2617
|
+
changed: true,
|
|
2618
|
+
message: "Skill installed."
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2623
|
+
// src/skills/SkillStore.ts
|
|
2624
|
+
import * as fs11 from "fs/promises";
|
|
2625
|
+
import * as path9 from "path";
|
|
2626
|
+
var USER_SKILL_MARKER_FILE = ".ms-devtools-skill.json";
|
|
2627
|
+
var WORKSPACE_SKILLS_ROOT_RELATIVE = ".github/skills";
|
|
2628
|
+
var WORKSPACE_SKILLS_MARKER_RELATIVE = ".github/.ms-devtools-skills.yml";
|
|
2629
|
+
var SAFE_LOCAL_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
2630
|
+
function assertSafeLocalSkillId(skillId) {
|
|
2631
|
+
if (typeof skillId !== "string" || skillId.length === 0 || skillId === "." || skillId === ".." || skillId.includes("/") || skillId.includes("\\") || !SAFE_LOCAL_ID_PATTERN2.test(skillId)) {
|
|
2632
|
+
throw new Error(`Invalid skill id: ${skillId}`);
|
|
2633
|
+
}
|
|
2634
|
+
return skillId;
|
|
2635
|
+
}
|
|
2636
|
+
var SkillStore = class {
|
|
2637
|
+
constructor(options) {
|
|
2638
|
+
this.workspacePath = options.workspacePath;
|
|
2639
|
+
this.userSkillsRoot = options.userSkillsRoot;
|
|
2640
|
+
this.fileSystem = options.fileSystem ?? fs11;
|
|
2641
|
+
}
|
|
2642
|
+
normalizeRemoteSkillId(remoteId) {
|
|
2643
|
+
if (remoteId.startsWith("official/")) {
|
|
2644
|
+
return assertSafeLocalSkillId(remoteId.slice("official/".length));
|
|
2645
|
+
}
|
|
2646
|
+
if (remoteId.startsWith("community/")) {
|
|
2647
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
2648
|
+
return assertSafeLocalSkillId(remoteId.slice(lastSlash + 1));
|
|
2649
|
+
}
|
|
2650
|
+
return assertSafeLocalSkillId(remoteId);
|
|
2651
|
+
}
|
|
2652
|
+
getWorkspaceSkillPath(skillId) {
|
|
2653
|
+
return `${WORKSPACE_SKILLS_ROOT_RELATIVE}/${skillId}`;
|
|
2654
|
+
}
|
|
2655
|
+
getWorkspaceMarkerPath() {
|
|
2656
|
+
return WORKSPACE_SKILLS_MARKER_RELATIVE;
|
|
2657
|
+
}
|
|
2658
|
+
getUserSkillPath(skillId) {
|
|
2659
|
+
return path9.join(this.userSkillsRoot, skillId);
|
|
2660
|
+
}
|
|
2661
|
+
async listWorkspaceSkillIds() {
|
|
2662
|
+
const skillsRootPath = path9.join(
|
|
2663
|
+
this.workspacePath,
|
|
2664
|
+
WORKSPACE_SKILLS_ROOT_RELATIVE
|
|
2665
|
+
);
|
|
2666
|
+
try {
|
|
2667
|
+
const entries = await this.fileSystem.readdir(skillsRootPath, {
|
|
2668
|
+
withFileTypes: true
|
|
2669
|
+
});
|
|
2670
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2671
|
+
} catch {
|
|
2672
|
+
return [];
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
async listUserSkillIds() {
|
|
2676
|
+
try {
|
|
2677
|
+
const entries = await this.fileSystem.readdir(this.userSkillsRoot, {
|
|
2678
|
+
withFileTypes: true
|
|
2679
|
+
});
|
|
2680
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2681
|
+
} catch {
|
|
2682
|
+
return [];
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
async writeManagedUserSkillMarker(skillId) {
|
|
2686
|
+
const targetDir = this.getUserSkillPath(skillId);
|
|
2687
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
2688
|
+
await this.fileSystem.writeFile(
|
|
2689
|
+
path9.join(targetDir, USER_SKILL_MARKER_FILE),
|
|
2690
|
+
JSON.stringify({ skillId, installedBy: "ms-devtools" }, null, 2),
|
|
2691
|
+
"utf-8"
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
async isManagedUserSkill(skillId) {
|
|
2695
|
+
try {
|
|
2696
|
+
const marker = await this.fileSystem.readFile(
|
|
2697
|
+
path9.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
|
|
2698
|
+
"utf-8"
|
|
2699
|
+
);
|
|
2700
|
+
const parsed = JSON.parse(marker);
|
|
2701
|
+
return parsed.skillId === skillId;
|
|
2702
|
+
} catch {
|
|
2703
|
+
return false;
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2229
2707
|
export {
|
|
2708
|
+
AgentCatalogClient,
|
|
2709
|
+
AgentReconciler,
|
|
2710
|
+
AgentStore,
|
|
2230
2711
|
DaemonLogger,
|
|
2231
2712
|
EnvironmentInspector,
|
|
2232
2713
|
GithubCopilotCliExecutor,
|
|
@@ -2236,6 +2717,10 @@ export {
|
|
|
2236
2717
|
ProjectTools,
|
|
2237
2718
|
SchedulerDaemon,
|
|
2238
2719
|
ShellExecutor,
|
|
2720
|
+
SkillCatalogClient,
|
|
2721
|
+
SkillReconciler,
|
|
2722
|
+
SkillStore,
|
|
2723
|
+
TOOL_RISK_MAP,
|
|
2239
2724
|
TaskConfigManager,
|
|
2240
2725
|
TaskExecutionEngine,
|
|
2241
2726
|
TaskLogManager,
|
|
@@ -2253,6 +2738,7 @@ export {
|
|
|
2253
2738
|
matchesCron,
|
|
2254
2739
|
moveFiles,
|
|
2255
2740
|
noopLogger,
|
|
2741
|
+
parseAgentToolPermissions,
|
|
2256
2742
|
parseIntervalMs,
|
|
2257
2743
|
resolveTaskExecutionPayload,
|
|
2258
2744
|
unzipFile,
|