@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.js
CHANGED
|
@@ -30,6 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
AgentCatalogClient: () => AgentCatalogClient,
|
|
34
|
+
AgentReconciler: () => AgentReconciler,
|
|
35
|
+
AgentStore: () => AgentStore,
|
|
33
36
|
DaemonLogger: () => DaemonLogger,
|
|
34
37
|
EnvironmentInspector: () => EnvironmentInspector,
|
|
35
38
|
GithubCopilotCliExecutor: () => GithubCopilotCliExecutor,
|
|
@@ -39,6 +42,10 @@ __export(index_exports, {
|
|
|
39
42
|
ProjectTools: () => ProjectTools,
|
|
40
43
|
SchedulerDaemon: () => SchedulerDaemon,
|
|
41
44
|
ShellExecutor: () => ShellExecutor,
|
|
45
|
+
SkillCatalogClient: () => SkillCatalogClient,
|
|
46
|
+
SkillReconciler: () => SkillReconciler,
|
|
47
|
+
SkillStore: () => SkillStore,
|
|
48
|
+
TOOL_RISK_MAP: () => TOOL_RISK_MAP,
|
|
42
49
|
TaskConfigManager: () => TaskConfigManager,
|
|
43
50
|
TaskExecutionEngine: () => TaskExecutionEngine,
|
|
44
51
|
TaskLogManager: () => TaskLogManager,
|
|
@@ -56,6 +63,7 @@ __export(index_exports, {
|
|
|
56
63
|
matchesCron: () => matchesCron,
|
|
57
64
|
moveFiles: () => moveFiles,
|
|
58
65
|
noopLogger: () => noopLogger,
|
|
66
|
+
parseAgentToolPermissions: () => parseAgentToolPermissions,
|
|
59
67
|
parseIntervalMs: () => parseIntervalMs,
|
|
60
68
|
resolveTaskExecutionPayload: () => resolveTaskExecutionPayload,
|
|
61
69
|
unzipFile: () => unzipFile,
|
|
@@ -63,6 +71,287 @@ __export(index_exports, {
|
|
|
63
71
|
});
|
|
64
72
|
module.exports = __toCommonJS(index_exports);
|
|
65
73
|
|
|
74
|
+
// src/agents/AgentCatalogClient.ts
|
|
75
|
+
var AgentCatalogClient = class {
|
|
76
|
+
constructor(options = {}) {
|
|
77
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
78
|
+
this.baseUrl = options.baseUrl;
|
|
79
|
+
}
|
|
80
|
+
async getCatalog() {
|
|
81
|
+
if (!this.baseUrl) {
|
|
82
|
+
return {
|
|
83
|
+
agents: [],
|
|
84
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const response = await this.fetchImpl(
|
|
88
|
+
`${this.baseUrl}/api/v1/marketplace/agents`
|
|
89
|
+
);
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(`Failed to fetch agents catalog: ${response.status}`);
|
|
92
|
+
}
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
return {
|
|
95
|
+
agents: data.agents ?? [],
|
|
96
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/permissions/agent-permissions.ts
|
|
102
|
+
var TOOL_RISK_MAP = {
|
|
103
|
+
shell: "high",
|
|
104
|
+
terminal: "high",
|
|
105
|
+
run_in_terminal: "high",
|
|
106
|
+
execution_subagent: "high",
|
|
107
|
+
filesystem: "medium",
|
|
108
|
+
fetch: "medium",
|
|
109
|
+
fetch_webpage: "medium",
|
|
110
|
+
create_file: "medium",
|
|
111
|
+
replace_string_in_file: "medium",
|
|
112
|
+
multi_replace_string_in_file: "medium",
|
|
113
|
+
read_file: "low",
|
|
114
|
+
search: "low",
|
|
115
|
+
grep_search: "low",
|
|
116
|
+
file_search: "low",
|
|
117
|
+
semantic_search: "low",
|
|
118
|
+
list_dir: "low"
|
|
119
|
+
};
|
|
120
|
+
var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
121
|
+
var TOOLS_LINE_REGEX = /^tools:\s*$/m;
|
|
122
|
+
var TOOLS_INLINE_REGEX = /^tools:\s*\[([^\]]*)\]/m;
|
|
123
|
+
var LIST_ITEM_REGEX = /^\s*-\s+(.+)$/;
|
|
124
|
+
function parseAgentToolPermissions(content) {
|
|
125
|
+
const fmMatch = content.match(FRONTMATTER_REGEX);
|
|
126
|
+
if (!fmMatch?.[1]) return [];
|
|
127
|
+
const frontmatter = fmMatch[1];
|
|
128
|
+
const inlineMatch = frontmatter.match(TOOLS_INLINE_REGEX);
|
|
129
|
+
if (inlineMatch?.[1] != null) {
|
|
130
|
+
const raw = inlineMatch[1];
|
|
131
|
+
return raw.split(",").map((t) => t.trim()).filter(Boolean).map((tool) => ({
|
|
132
|
+
tool,
|
|
133
|
+
riskLevel: TOOL_RISK_MAP[tool] ?? "medium"
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
const blockMatch = frontmatter.match(TOOLS_LINE_REGEX);
|
|
137
|
+
if (!blockMatch?.[0]) return [];
|
|
138
|
+
const toolsStartIndex = frontmatter.indexOf(blockMatch[0]) + blockMatch[0].length;
|
|
139
|
+
const remaining = frontmatter.slice(toolsStartIndex);
|
|
140
|
+
const lines = remaining.split(/\r?\n/);
|
|
141
|
+
const tools = [];
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
const itemMatch = line.match(LIST_ITEM_REGEX);
|
|
144
|
+
if (itemMatch?.[1]) {
|
|
145
|
+
const tool = itemMatch[1].trim();
|
|
146
|
+
tools.push({ tool, riskLevel: TOOL_RISK_MAP[tool] ?? "medium" });
|
|
147
|
+
} else if (line.trim() !== "" && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return tools;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/agents/AgentReconciler.ts
|
|
155
|
+
var AgentReconciler = class {
|
|
156
|
+
constructor(deps) {
|
|
157
|
+
this.deps = deps;
|
|
158
|
+
}
|
|
159
|
+
async mutate(request) {
|
|
160
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
161
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
162
|
+
}
|
|
163
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
164
|
+
return {
|
|
165
|
+
status: "success",
|
|
166
|
+
changed: true,
|
|
167
|
+
message: `Agent ${request.action} completed.`
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (request.action !== "install") {
|
|
171
|
+
return {
|
|
172
|
+
status: "blocked",
|
|
173
|
+
changed: false,
|
|
174
|
+
message: `Agent action is not supported by bridge reconciler: ${request.action}`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
178
|
+
const remoteAgent = catalog.agents.find(
|
|
179
|
+
(agent) => this.deps.agentStore.normalizeRemoteAgentId(agent.id) === request.agentId
|
|
180
|
+
);
|
|
181
|
+
if (!remoteAgent) {
|
|
182
|
+
return {
|
|
183
|
+
status: "blocked",
|
|
184
|
+
changed: false,
|
|
185
|
+
message: "Agent not found in catalog."
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (!request.confirmed && this.hasHighRiskTool(remoteAgent.tools)) {
|
|
189
|
+
return {
|
|
190
|
+
status: "requires_confirmation",
|
|
191
|
+
changed: false,
|
|
192
|
+
message: "This agent uses high-risk tools that require confirmation.",
|
|
193
|
+
tools: remoteAgent.tools
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
status: "success",
|
|
198
|
+
changed: true,
|
|
199
|
+
message: "Agent installed."
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
getPermissionSummary(agentId, agentName, content) {
|
|
203
|
+
const tools = parseAgentToolPermissions(content);
|
|
204
|
+
return {
|
|
205
|
+
agentId,
|
|
206
|
+
agentName,
|
|
207
|
+
tools,
|
|
208
|
+
highRiskCount: tools.filter((tool) => tool.riskLevel === "high").length,
|
|
209
|
+
mediumRiskCount: tools.filter((tool) => tool.riskLevel === "medium").length,
|
|
210
|
+
lowRiskCount: tools.filter((tool) => tool.riskLevel === "low").length
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
hasHighRiskTool(tools) {
|
|
214
|
+
return tools.some((tool) => TOOL_RISK_MAP[tool] === "high");
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/agents/AgentStore.ts
|
|
219
|
+
var fs = __toESM(require("fs/promises"));
|
|
220
|
+
var path = __toESM(require("path"));
|
|
221
|
+
var WORKSPACE_AGENTS_ROOT_RELATIVE = ".github/agents";
|
|
222
|
+
var WORKSPACE_AGENTS_STATE_RELATIVE = ".github/.ms-devtools-agents.yml";
|
|
223
|
+
var SAFE_LOCAL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
224
|
+
function assertSafeLocalAgentId(agentId) {
|
|
225
|
+
if (typeof agentId !== "string" || agentId.length === 0 || agentId === "." || agentId === ".." || agentId.includes("/") || agentId.includes("\\") || !SAFE_LOCAL_ID_PATTERN.test(agentId)) {
|
|
226
|
+
throw new Error(`Invalid agent id: ${agentId}`);
|
|
227
|
+
}
|
|
228
|
+
return agentId;
|
|
229
|
+
}
|
|
230
|
+
var AgentStore = class {
|
|
231
|
+
constructor(options) {
|
|
232
|
+
this.workspacePath = options.workspacePath;
|
|
233
|
+
this.userAgentsRoot = options.userAgentsRoot;
|
|
234
|
+
this.fileSystem = options.fileSystem ?? fs;
|
|
235
|
+
this.schemaVersion = options.schemaVersion ?? 1;
|
|
236
|
+
}
|
|
237
|
+
normalizeRemoteAgentId(remoteId) {
|
|
238
|
+
if (remoteId.startsWith("official/")) {
|
|
239
|
+
return assertSafeLocalAgentId(remoteId.slice("official/".length));
|
|
240
|
+
}
|
|
241
|
+
if (remoteId.startsWith("community/")) {
|
|
242
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
243
|
+
return assertSafeLocalAgentId(remoteId.slice(lastSlash + 1));
|
|
244
|
+
}
|
|
245
|
+
return assertSafeLocalAgentId(remoteId);
|
|
246
|
+
}
|
|
247
|
+
getWorkspaceAgentsRootPath() {
|
|
248
|
+
return WORKSPACE_AGENTS_ROOT_RELATIVE;
|
|
249
|
+
}
|
|
250
|
+
getWorkspaceStateFilePath() {
|
|
251
|
+
return WORKSPACE_AGENTS_STATE_RELATIVE;
|
|
252
|
+
}
|
|
253
|
+
getUserAgentsRootPath() {
|
|
254
|
+
return this.userAgentsRoot;
|
|
255
|
+
}
|
|
256
|
+
async listWorkspaceAgentIds() {
|
|
257
|
+
return this.listAgentIds(
|
|
258
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
async listUserAgentIds() {
|
|
262
|
+
return this.listAgentIds(this.userAgentsRoot);
|
|
263
|
+
}
|
|
264
|
+
async readState() {
|
|
265
|
+
try {
|
|
266
|
+
const raw = await this.fileSystem.readFile(
|
|
267
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE),
|
|
268
|
+
"utf-8"
|
|
269
|
+
);
|
|
270
|
+
const parsed = JSON.parse(raw);
|
|
271
|
+
if (typeof parsed.schemaVersion !== "number" || !Array.isArray(parsed.installedAgents)) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
return parsed;
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async writeState(state) {
|
|
280
|
+
const statePath = path.join(
|
|
281
|
+
this.workspacePath,
|
|
282
|
+
WORKSPACE_AGENTS_STATE_RELATIVE
|
|
283
|
+
);
|
|
284
|
+
await this.fileSystem.mkdir(path.dirname(statePath), { recursive: true });
|
|
285
|
+
await this.fileSystem.writeFile(
|
|
286
|
+
statePath,
|
|
287
|
+
JSON.stringify(state, null, 2),
|
|
288
|
+
"utf-8"
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
async addInstalledAgent(entry) {
|
|
292
|
+
const state = await this.readState() ?? {
|
|
293
|
+
schemaVersion: this.schemaVersion,
|
|
294
|
+
installedAgents: []
|
|
295
|
+
};
|
|
296
|
+
state.installedAgents = state.installedAgents.filter(
|
|
297
|
+
(agent) => agent.id !== entry.id
|
|
298
|
+
);
|
|
299
|
+
state.installedAgents.push(entry);
|
|
300
|
+
await this.writeState(state);
|
|
301
|
+
}
|
|
302
|
+
async removeInstalledAgent(agentId) {
|
|
303
|
+
const state = await this.readState();
|
|
304
|
+
if (!state) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
state.installedAgents = state.installedAgents.filter(
|
|
308
|
+
(agent) => agent.id !== agentId
|
|
309
|
+
);
|
|
310
|
+
await this.writeState(state);
|
|
311
|
+
}
|
|
312
|
+
async writeAgentFiles(agentId, scope, files) {
|
|
313
|
+
const root = scope === "workspace" ? path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE) : this.userAgentsRoot;
|
|
314
|
+
const firstFile = files[0];
|
|
315
|
+
const isSingleFlatFile = files.length === 1 && firstFile !== void 0 && firstFile.path === `${agentId}.agent.md` && !firstFile.path.includes("/");
|
|
316
|
+
const targetDir = isSingleFlatFile ? root : path.join(root, agentId);
|
|
317
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
318
|
+
for (const file of files) {
|
|
319
|
+
const filePath = path.join(targetDir, file.path);
|
|
320
|
+
await this.fileSystem.mkdir(path.dirname(filePath), { recursive: true });
|
|
321
|
+
await this.fileSystem.writeFile(filePath, file.content, "utf-8");
|
|
322
|
+
if (file.executable) {
|
|
323
|
+
try {
|
|
324
|
+
await this.fileSystem.chmod(filePath, 493);
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async listAgentIds(dir) {
|
|
331
|
+
try {
|
|
332
|
+
const entries = await this.fileSystem.readdir(dir, {
|
|
333
|
+
withFileTypes: true
|
|
334
|
+
});
|
|
335
|
+
const ids = [];
|
|
336
|
+
for (const entry of entries) {
|
|
337
|
+
if (entry.name.startsWith(".")) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (entry.isDirectory()) {
|
|
341
|
+
ids.push(entry.name);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (entry.isFile() && entry.name.endsWith(".agent.md")) {
|
|
345
|
+
ids.push(entry.name.replace(/\.agent\.md$/, ""));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return ids.sort();
|
|
349
|
+
} catch {
|
|
350
|
+
return [];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
66
355
|
// src/copilot/doctor.ts
|
|
67
356
|
var import_devtools_protocol = require("@serviceme/devtools-protocol");
|
|
68
357
|
|
|
@@ -297,7 +586,13 @@ var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
|
|
|
297
586
|
var TOOL_CHECK_TIMEOUT_MS = {
|
|
298
587
|
nuget: 12e3,
|
|
299
588
|
nvm: 8e3,
|
|
300
|
-
dotnet: 8e3
|
|
589
|
+
dotnet: 8e3,
|
|
590
|
+
// nvm4w/npm shims and globally-installed .cmd tools (pnpm, nrm) are slow to
|
|
591
|
+
// resolve on cold PATHs. Give them extra headroom so the version probe
|
|
592
|
+
// doesn't fall through to the generic 5s default.
|
|
593
|
+
npm: 15e3,
|
|
594
|
+
pnpm: 15e3,
|
|
595
|
+
nrm: 15e3
|
|
301
596
|
};
|
|
302
597
|
var ERROR_CODE_NOT_FOUND = 127;
|
|
303
598
|
var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
|
|
@@ -344,8 +639,29 @@ var EnvironmentInspector = class {
|
|
|
344
639
|
return void 0;
|
|
345
640
|
}
|
|
346
641
|
}
|
|
642
|
+
/**
|
|
643
|
+
* On Windows, prefer the `.cmd` shim for npm/pnpm/nrm. nvm4w registers
|
|
644
|
+
* BOTH an extensionless entry (pointing to node.exe) and a `<tool>.cmd`
|
|
645
|
+
* wrapper; `where` returns them in that order, and the extensionless one
|
|
646
|
+
* would just print node's own version.
|
|
647
|
+
*/
|
|
648
|
+
async getToolShimPath(toolName) {
|
|
649
|
+
try {
|
|
650
|
+
const result = await runCommand("where", {
|
|
651
|
+
args: [toolName],
|
|
652
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
653
|
+
});
|
|
654
|
+
const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
655
|
+
const cmdShim = candidates.find(
|
|
656
|
+
(line) => line.toLowerCase().endsWith(".cmd")
|
|
657
|
+
);
|
|
658
|
+
return cmdShim ?? candidates[0];
|
|
659
|
+
} catch {
|
|
660
|
+
return void 0;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
347
663
|
async getToolVersion(toolName) {
|
|
348
|
-
const invocation = this.getVersionInvocation(toolName);
|
|
664
|
+
const invocation = await this.getVersionInvocation(toolName);
|
|
349
665
|
const result = await runCommand(invocation.command, {
|
|
350
666
|
args: invocation.args,
|
|
351
667
|
timeoutMs: this.getToolTimeout(toolName)
|
|
@@ -420,12 +736,19 @@ var EnvironmentInspector = class {
|
|
|
420
736
|
return this.handleToolCheckError(error);
|
|
421
737
|
}
|
|
422
738
|
}
|
|
423
|
-
getVersionInvocation(toolName) {
|
|
739
|
+
async getVersionInvocation(toolName) {
|
|
424
740
|
const isWindows = process.platform === "win32";
|
|
425
741
|
if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
|
|
742
|
+
const shim = await this.getToolShimPath(toolName);
|
|
743
|
+
if (shim) {
|
|
744
|
+
return {
|
|
745
|
+
command: "cmd.exe",
|
|
746
|
+
args: ["/c", shim, "--version"]
|
|
747
|
+
};
|
|
748
|
+
}
|
|
426
749
|
return {
|
|
427
750
|
command: "cmd.exe",
|
|
428
|
-
args: ["/
|
|
751
|
+
args: ["/c", toolName, "--version"]
|
|
429
752
|
};
|
|
430
753
|
}
|
|
431
754
|
const commands = {
|
|
@@ -478,8 +801,8 @@ var EnvironmentInspector = class {
|
|
|
478
801
|
};
|
|
479
802
|
|
|
480
803
|
// src/image/imageTools.ts
|
|
481
|
-
var
|
|
482
|
-
var
|
|
804
|
+
var fs2 = __toESM(require("fs/promises"));
|
|
805
|
+
var path2 = __toESM(require("path"));
|
|
483
806
|
var import_devtools_protocol4 = require("@serviceme/devtools-protocol");
|
|
484
807
|
var SUPPORTED_FORMATS = [
|
|
485
808
|
".jpg",
|
|
@@ -500,7 +823,7 @@ var ImageTools = class {
|
|
|
500
823
|
if (!await this.pathExists(filePath)) {
|
|
501
824
|
return { valid: false };
|
|
502
825
|
}
|
|
503
|
-
if (!SUPPORTED_FORMATS.includes(
|
|
826
|
+
if (!SUPPORTED_FORMATS.includes(path2.extname(filePath).toLowerCase())) {
|
|
504
827
|
return { valid: false };
|
|
505
828
|
}
|
|
506
829
|
await this.getInfo(filePath, sharpModulePath);
|
|
@@ -512,7 +835,7 @@ var ImageTools = class {
|
|
|
512
835
|
async getInfo(imagePath, sharpModulePath) {
|
|
513
836
|
const sharp = this.loadSharp(sharpModulePath);
|
|
514
837
|
const metadata = await sharp(imagePath).metadata();
|
|
515
|
-
const stats = await
|
|
838
|
+
const stats = await fs2.stat(imagePath);
|
|
516
839
|
return {
|
|
517
840
|
width: metadata.width ?? 0,
|
|
518
841
|
height: metadata.height ?? 0,
|
|
@@ -529,7 +852,7 @@ var ImageTools = class {
|
|
|
529
852
|
`Invalid image file: ${imagePath}`
|
|
530
853
|
);
|
|
531
854
|
}
|
|
532
|
-
const originalStats = await
|
|
855
|
+
const originalStats = await fs2.stat(imagePath);
|
|
533
856
|
const originalSize = originalStats.size;
|
|
534
857
|
const outputPath = this.getOutputPath(imagePath, options);
|
|
535
858
|
const compressedBuffer = await this.compressWithSharp(imagePath, options);
|
|
@@ -544,7 +867,7 @@ var ImageTools = class {
|
|
|
544
867
|
outputPath: imagePath
|
|
545
868
|
};
|
|
546
869
|
}
|
|
547
|
-
await
|
|
870
|
+
await fs2.writeFile(outputPath, compressedBuffer);
|
|
548
871
|
return {
|
|
549
872
|
originalSize,
|
|
550
873
|
compressedSize,
|
|
@@ -555,7 +878,7 @@ var ImageTools = class {
|
|
|
555
878
|
async compressWithSharp(imagePath, options) {
|
|
556
879
|
const sharp = this.loadSharp(options.sharpModulePath);
|
|
557
880
|
let pipeline = sharp(imagePath);
|
|
558
|
-
switch (options.format ??
|
|
881
|
+
switch (options.format ?? path2.extname(imagePath).toLowerCase().slice(1)) {
|
|
559
882
|
case "jpg":
|
|
560
883
|
case "jpeg":
|
|
561
884
|
pipeline = pipeline.jpeg({ quality: options.quality });
|
|
@@ -581,14 +904,14 @@ var ImageTools = class {
|
|
|
581
904
|
if (options.replaceOriginImage) {
|
|
582
905
|
return inputPath;
|
|
583
906
|
}
|
|
584
|
-
const dir =
|
|
585
|
-
const ext =
|
|
586
|
-
const name =
|
|
587
|
-
return
|
|
907
|
+
const dir = path2.dirname(inputPath);
|
|
908
|
+
const ext = path2.extname(inputPath);
|
|
909
|
+
const name = path2.basename(inputPath, ext);
|
|
910
|
+
return path2.join(dir, `${name}_compressed${ext}`);
|
|
588
911
|
}
|
|
589
912
|
async pathExists(targetPath) {
|
|
590
913
|
try {
|
|
591
|
-
await
|
|
914
|
+
await fs2.access(targetPath);
|
|
592
915
|
return true;
|
|
593
916
|
} catch {
|
|
594
917
|
return false;
|
|
@@ -744,8 +1067,8 @@ function createConsoleLogger(prefix = "serviceme") {
|
|
|
744
1067
|
}
|
|
745
1068
|
|
|
746
1069
|
// src/project/projectTools.ts
|
|
747
|
-
var
|
|
748
|
-
var
|
|
1070
|
+
var fs3 = __toESM(require("fs/promises"));
|
|
1071
|
+
var path3 = __toESM(require("path"));
|
|
749
1072
|
var import_devtools_protocol6 = require("@serviceme/devtools-protocol");
|
|
750
1073
|
|
|
751
1074
|
// src/utils/fileUtils.ts
|
|
@@ -866,10 +1189,10 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
866
1189
|
var ProjectTools = class {
|
|
867
1190
|
async extractTemplate(zipPath, workspacePath, tempExtractDir, input) {
|
|
868
1191
|
await unzipFile(zipPath, tempExtractDir);
|
|
869
|
-
let sourceDir =
|
|
1192
|
+
let sourceDir = path3.join(tempExtractDir, input.extractedDirName);
|
|
870
1193
|
let actualDirName = input.extractedDirName;
|
|
871
1194
|
if (!await this.pathExists(sourceDir)) {
|
|
872
|
-
const entries = await
|
|
1195
|
+
const entries = await fs3.readdir(tempExtractDir, { withFileTypes: true });
|
|
873
1196
|
const directories = entries.filter(
|
|
874
1197
|
(entry) => entry.isDirectory() && !entry.name.startsWith(".")
|
|
875
1198
|
);
|
|
@@ -881,7 +1204,7 @@ var ProjectTools = class {
|
|
|
881
1204
|
);
|
|
882
1205
|
if (selectedDirectory) {
|
|
883
1206
|
actualDirName = selectedDirectory;
|
|
884
|
-
sourceDir =
|
|
1207
|
+
sourceDir = path3.join(tempExtractDir, actualDirName);
|
|
885
1208
|
} else if (directories.length === 0) {
|
|
886
1209
|
throw new Error(
|
|
887
1210
|
`No directory found after extraction. Expected directory: ${input.extractedDirName}`
|
|
@@ -939,7 +1262,7 @@ var ProjectTools = class {
|
|
|
939
1262
|
} else {
|
|
940
1263
|
for (const scriptPath of scripts) {
|
|
941
1264
|
try {
|
|
942
|
-
await
|
|
1265
|
+
await fs3.chmod(scriptPath, 493);
|
|
943
1266
|
updatedCount += 1;
|
|
944
1267
|
} catch {
|
|
945
1268
|
}
|
|
@@ -987,12 +1310,12 @@ var ProjectTools = class {
|
|
|
987
1310
|
const results = [];
|
|
988
1311
|
let entries;
|
|
989
1312
|
try {
|
|
990
|
-
entries = await
|
|
1313
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
991
1314
|
} catch {
|
|
992
1315
|
return results;
|
|
993
1316
|
}
|
|
994
1317
|
for (const entry of entries) {
|
|
995
|
-
const fullPath =
|
|
1318
|
+
const fullPath = path3.join(dir, entry.name);
|
|
996
1319
|
if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
997
1320
|
results.push(...await this.findScripts(fullPath, extensions));
|
|
998
1321
|
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -1003,7 +1326,7 @@ var ProjectTools = class {
|
|
|
1003
1326
|
}
|
|
1004
1327
|
async pathExists(targetPath) {
|
|
1005
1328
|
try {
|
|
1006
|
-
await
|
|
1329
|
+
await fs3.access(targetPath);
|
|
1007
1330
|
return true;
|
|
1008
1331
|
} catch {
|
|
1009
1332
|
return false;
|
|
@@ -1022,7 +1345,7 @@ var ProjectTools = class {
|
|
|
1022
1345
|
const matches = [];
|
|
1023
1346
|
for (const directoryName of directoryNames) {
|
|
1024
1347
|
if (await this.directoryMatchesProjectPattern(
|
|
1025
|
-
|
|
1348
|
+
path3.join(tempExtractDir, directoryName),
|
|
1026
1349
|
projectFilePattern
|
|
1027
1350
|
)) {
|
|
1028
1351
|
matches.push(directoryName);
|
|
@@ -1034,7 +1357,7 @@ var ProjectTools = class {
|
|
|
1034
1357
|
return null;
|
|
1035
1358
|
}
|
|
1036
1359
|
async directoryMatchesProjectPattern(directoryPath, projectFilePattern) {
|
|
1037
|
-
const entries = await
|
|
1360
|
+
const entries = await fs3.readdir(directoryPath);
|
|
1038
1361
|
if (projectFilePattern.includes("*")) {
|
|
1039
1362
|
const regex = new RegExp(`^${projectFilePattern.replace("*", ".*")}$`);
|
|
1040
1363
|
return entries.some((entry) => regex.test(entry));
|
|
@@ -1047,17 +1370,17 @@ function createProjectTools() {
|
|
|
1047
1370
|
}
|
|
1048
1371
|
|
|
1049
1372
|
// src/scheduled-tasks/daemon/DaemonLogger.ts
|
|
1050
|
-
var
|
|
1051
|
-
var
|
|
1373
|
+
var fs4 = __toESM(require("fs"));
|
|
1374
|
+
var path4 = __toESM(require("path"));
|
|
1052
1375
|
var CONFIG_DIR = ".serviceme";
|
|
1053
1376
|
var LOG_FILE = "scheduler.log";
|
|
1054
1377
|
var MAX_LOG_SIZE = 1024 * 1024;
|
|
1055
1378
|
var DaemonLogger = class {
|
|
1056
1379
|
constructor(workspacePath) {
|
|
1057
|
-
this.logPath =
|
|
1058
|
-
const dir =
|
|
1059
|
-
if (!
|
|
1060
|
-
|
|
1380
|
+
this.logPath = path4.join(workspacePath, CONFIG_DIR, LOG_FILE);
|
|
1381
|
+
const dir = path4.dirname(this.logPath);
|
|
1382
|
+
if (!fs4.existsSync(dir)) {
|
|
1383
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1061
1384
|
}
|
|
1062
1385
|
}
|
|
1063
1386
|
getLogPath() {
|
|
@@ -1068,16 +1391,16 @@ var DaemonLogger = class {
|
|
|
1068
1391
|
const line = `[${ts}] [${level.toUpperCase()}] ${message}
|
|
1069
1392
|
`;
|
|
1070
1393
|
this.rotateIfNeeded();
|
|
1071
|
-
|
|
1394
|
+
fs4.appendFileSync(this.logPath, line, "utf-8");
|
|
1072
1395
|
}
|
|
1073
1396
|
rotateIfNeeded() {
|
|
1074
1397
|
try {
|
|
1075
|
-
const stats =
|
|
1398
|
+
const stats = fs4.statSync(this.logPath);
|
|
1076
1399
|
if (stats.size > MAX_LOG_SIZE) {
|
|
1077
|
-
const content =
|
|
1400
|
+
const content = fs4.readFileSync(this.logPath, "utf-8");
|
|
1078
1401
|
const halfIdx = content.indexOf("\n", Math.floor(content.length / 2));
|
|
1079
1402
|
if (halfIdx > 0) {
|
|
1080
|
-
|
|
1403
|
+
fs4.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
|
|
1081
1404
|
}
|
|
1082
1405
|
}
|
|
1083
1406
|
} catch {
|
|
@@ -1086,33 +1409,33 @@ var DaemonLogger = class {
|
|
|
1086
1409
|
};
|
|
1087
1410
|
|
|
1088
1411
|
// src/scheduled-tasks/daemon/PidManager.ts
|
|
1089
|
-
var
|
|
1090
|
-
var
|
|
1412
|
+
var fs5 = __toESM(require("fs"));
|
|
1413
|
+
var path5 = __toESM(require("path"));
|
|
1091
1414
|
var CONFIG_DIR2 = ".serviceme";
|
|
1092
1415
|
var PID_FILE = "scheduler.pid";
|
|
1093
1416
|
var PidManager = class {
|
|
1094
1417
|
constructor(workspacePath) {
|
|
1095
|
-
this.pidPath =
|
|
1418
|
+
this.pidPath = path5.join(workspacePath, CONFIG_DIR2, PID_FILE);
|
|
1096
1419
|
}
|
|
1097
1420
|
getPidPath() {
|
|
1098
1421
|
return this.pidPath;
|
|
1099
1422
|
}
|
|
1100
1423
|
writePid(pid) {
|
|
1101
|
-
const dir =
|
|
1102
|
-
if (!
|
|
1103
|
-
|
|
1424
|
+
const dir = path5.dirname(this.pidPath);
|
|
1425
|
+
if (!fs5.existsSync(dir)) {
|
|
1426
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1104
1427
|
}
|
|
1105
|
-
|
|
1428
|
+
fs5.writeFileSync(this.pidPath, String(pid), "utf-8");
|
|
1106
1429
|
}
|
|
1107
1430
|
readPid() {
|
|
1108
|
-
if (!
|
|
1109
|
-
const raw =
|
|
1431
|
+
if (!fs5.existsSync(this.pidPath)) return null;
|
|
1432
|
+
const raw = fs5.readFileSync(this.pidPath, "utf-8").trim();
|
|
1110
1433
|
const pid = Number.parseInt(raw, 10);
|
|
1111
1434
|
return Number.isNaN(pid) ? null : pid;
|
|
1112
1435
|
}
|
|
1113
1436
|
removePid() {
|
|
1114
|
-
if (
|
|
1115
|
-
|
|
1437
|
+
if (fs5.existsSync(this.pidPath)) {
|
|
1438
|
+
fs5.unlinkSync(this.pidPath);
|
|
1116
1439
|
}
|
|
1117
1440
|
}
|
|
1118
1441
|
isProcessRunning(pid) {
|
|
@@ -1133,12 +1456,12 @@ var PidManager = class {
|
|
|
1133
1456
|
};
|
|
1134
1457
|
|
|
1135
1458
|
// src/scheduled-tasks/daemon/SchedulerDaemon.ts
|
|
1136
|
-
var
|
|
1459
|
+
var fs10 = __toESM(require("fs"));
|
|
1137
1460
|
var os = __toESM(require("os"));
|
|
1138
1461
|
|
|
1139
1462
|
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1140
1463
|
var import_node_child_process2 = require("child_process");
|
|
1141
|
-
var
|
|
1464
|
+
var fs6 = __toESM(require("fs"));
|
|
1142
1465
|
|
|
1143
1466
|
// src/scheduled-tasks/executors/timeout.ts
|
|
1144
1467
|
function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
|
|
@@ -1173,7 +1496,7 @@ function redactArgs(args) {
|
|
|
1173
1496
|
function writeDiagnostic(message) {
|
|
1174
1497
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1175
1498
|
if (logPath) {
|
|
1176
|
-
|
|
1499
|
+
fs6.appendFileSync(logPath, message);
|
|
1177
1500
|
return;
|
|
1178
1501
|
}
|
|
1179
1502
|
process.stderr.write(message);
|
|
@@ -1383,15 +1706,15 @@ ${body}`.trim()
|
|
|
1383
1706
|
|
|
1384
1707
|
// src/scheduled-tasks/executors/ShellExecutor.ts
|
|
1385
1708
|
var import_node_child_process3 = require("child_process");
|
|
1386
|
-
var
|
|
1387
|
-
var
|
|
1709
|
+
var fs7 = __toESM(require("fs"));
|
|
1710
|
+
var path6 = __toESM(require("path"));
|
|
1388
1711
|
var MAX_OUTPUT_BYTES2 = 1024 * 1024;
|
|
1389
1712
|
var DEFAULT_TIMEOUT_MS4 = 6e4;
|
|
1390
1713
|
var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
|
|
1391
1714
|
function resolveShellExecution(script, options = {}) {
|
|
1392
1715
|
const platform = options.platform ?? process.platform;
|
|
1393
1716
|
const env = options.env ?? process.env;
|
|
1394
|
-
const fileExists = options.fileExists ??
|
|
1717
|
+
const fileExists = options.fileExists ?? fs7.existsSync;
|
|
1395
1718
|
if (platform === "win32") {
|
|
1396
1719
|
const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
|
|
1397
1720
|
if (posixShell) {
|
|
@@ -1436,10 +1759,10 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1436
1759
|
if (fileExists(candidate)) return candidate;
|
|
1437
1760
|
}
|
|
1438
1761
|
const pathValue = env.Path ?? env.PATH ?? "";
|
|
1439
|
-
for (const dir of pathValue.split(
|
|
1762
|
+
for (const dir of pathValue.split(path6.win32.delimiter)) {
|
|
1440
1763
|
if (!dir) continue;
|
|
1441
1764
|
for (const executable of POSIX_SHELL_CANDIDATES) {
|
|
1442
|
-
const candidate =
|
|
1765
|
+
const candidate = path6.win32.join(dir, executable);
|
|
1443
1766
|
if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
|
|
1444
1767
|
return candidate;
|
|
1445
1768
|
}
|
|
@@ -1448,13 +1771,13 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1448
1771
|
return null;
|
|
1449
1772
|
}
|
|
1450
1773
|
function isWindowsWslLauncher(candidate) {
|
|
1451
|
-
const normalized =
|
|
1774
|
+
const normalized = path6.win32.normalize(candidate).toLowerCase();
|
|
1452
1775
|
return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
|
|
1453
1776
|
}
|
|
1454
1777
|
function writeDiagnostic2(message) {
|
|
1455
1778
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1456
1779
|
if (logPath) {
|
|
1457
|
-
|
|
1780
|
+
fs7.appendFileSync(logPath, message);
|
|
1458
1781
|
return;
|
|
1459
1782
|
}
|
|
1460
1783
|
process.stderr.write(message);
|
|
@@ -1601,8 +1924,8 @@ function getExecutor(taskType) {
|
|
|
1601
1924
|
|
|
1602
1925
|
// src/scheduled-tasks/TaskConfigManager.ts
|
|
1603
1926
|
var import_node_crypto = require("crypto");
|
|
1604
|
-
var
|
|
1605
|
-
var
|
|
1927
|
+
var fs8 = __toESM(require("fs"));
|
|
1928
|
+
var path7 = __toESM(require("path"));
|
|
1606
1929
|
var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
|
|
1607
1930
|
var CONFIG_DIR3 = ".serviceme";
|
|
1608
1931
|
var CONFIG_FILE = "scheduled-tasks.json";
|
|
@@ -1639,27 +1962,27 @@ function validateTaskPayload(taskType, payload) {
|
|
|
1639
1962
|
}
|
|
1640
1963
|
var TaskConfigManager = class {
|
|
1641
1964
|
constructor(workspacePath) {
|
|
1642
|
-
this.configPath =
|
|
1965
|
+
this.configPath = path7.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
|
|
1643
1966
|
}
|
|
1644
1967
|
getConfigPath() {
|
|
1645
1968
|
return this.configPath;
|
|
1646
1969
|
}
|
|
1647
1970
|
readConfig() {
|
|
1648
|
-
if (!
|
|
1971
|
+
if (!fs8.existsSync(this.configPath)) {
|
|
1649
1972
|
return emptyConfig();
|
|
1650
1973
|
}
|
|
1651
|
-
const raw =
|
|
1974
|
+
const raw = fs8.readFileSync(this.configPath, "utf-8");
|
|
1652
1975
|
const parsed = JSON.parse(raw);
|
|
1653
1976
|
return parsed;
|
|
1654
1977
|
}
|
|
1655
1978
|
writeConfig(config) {
|
|
1656
|
-
const dir =
|
|
1657
|
-
if (!
|
|
1658
|
-
|
|
1979
|
+
const dir = path7.dirname(this.configPath);
|
|
1980
|
+
if (!fs8.existsSync(dir)) {
|
|
1981
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1659
1982
|
}
|
|
1660
1983
|
const tmp = `${this.configPath}.tmp`;
|
|
1661
|
-
|
|
1662
|
-
|
|
1984
|
+
fs8.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
|
|
1985
|
+
fs8.renameSync(tmp, this.configPath);
|
|
1663
1986
|
}
|
|
1664
1987
|
listTasks() {
|
|
1665
1988
|
return this.readConfig().tasks;
|
|
@@ -1930,8 +2253,8 @@ var TaskExecutionEngine = class {
|
|
|
1930
2253
|
|
|
1931
2254
|
// src/scheduled-tasks/TaskLogManager.ts
|
|
1932
2255
|
var import_node_crypto2 = require("crypto");
|
|
1933
|
-
var
|
|
1934
|
-
var
|
|
2256
|
+
var fs9 = __toESM(require("fs"));
|
|
2257
|
+
var path8 = __toESM(require("path"));
|
|
1935
2258
|
var CONFIG_DIR4 = ".serviceme";
|
|
1936
2259
|
var LOG_FILE2 = "scheduled-tasks-log.json";
|
|
1937
2260
|
var MAX_LOGS = 200;
|
|
@@ -1956,17 +2279,17 @@ function validateAndRepairLogFile(raw) {
|
|
|
1956
2279
|
}
|
|
1957
2280
|
var TaskLogManager = class {
|
|
1958
2281
|
constructor(workspacePath) {
|
|
1959
|
-
this.logPath =
|
|
2282
|
+
this.logPath = path8.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
|
|
1960
2283
|
}
|
|
1961
2284
|
getLogPath() {
|
|
1962
2285
|
return this.logPath;
|
|
1963
2286
|
}
|
|
1964
2287
|
readLogFile() {
|
|
1965
|
-
if (!
|
|
2288
|
+
if (!fs9.existsSync(this.logPath)) {
|
|
1966
2289
|
return emptyLogFile();
|
|
1967
2290
|
}
|
|
1968
2291
|
try {
|
|
1969
|
-
const raw =
|
|
2292
|
+
const raw = fs9.readFileSync(this.logPath, "utf-8");
|
|
1970
2293
|
const parsed = JSON.parse(raw);
|
|
1971
2294
|
const file = validateAndRepairLogFile(parsed);
|
|
1972
2295
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
|
|
@@ -1982,21 +2305,21 @@ var TaskLogManager = class {
|
|
|
1982
2305
|
}
|
|
1983
2306
|
backupCorruptedFile() {
|
|
1984
2307
|
try {
|
|
1985
|
-
if (
|
|
2308
|
+
if (fs9.existsSync(this.logPath)) {
|
|
1986
2309
|
const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
|
|
1987
|
-
|
|
2310
|
+
fs9.copyFileSync(this.logPath, backupPath);
|
|
1988
2311
|
}
|
|
1989
2312
|
} catch {
|
|
1990
2313
|
}
|
|
1991
2314
|
}
|
|
1992
2315
|
writeLogFile(file) {
|
|
1993
|
-
const dir =
|
|
1994
|
-
if (!
|
|
1995
|
-
|
|
2316
|
+
const dir = path8.dirname(this.logPath);
|
|
2317
|
+
if (!fs9.existsSync(dir)) {
|
|
2318
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1996
2319
|
}
|
|
1997
2320
|
const tmp = `${this.logPath}.tmp`;
|
|
1998
|
-
|
|
1999
|
-
|
|
2321
|
+
fs9.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
|
|
2322
|
+
fs9.renameSync(tmp, this.logPath);
|
|
2000
2323
|
}
|
|
2001
2324
|
appendLog(input) {
|
|
2002
2325
|
const file = this.readLogFile();
|
|
@@ -2107,8 +2430,8 @@ var SchedulerDaemon = class {
|
|
|
2107
2430
|
const configPath = this.configManager.getConfigPath();
|
|
2108
2431
|
const dir = configPath.substring(0, configPath.lastIndexOf("/"));
|
|
2109
2432
|
try {
|
|
2110
|
-
if (
|
|
2111
|
-
this.watcher =
|
|
2433
|
+
if (fs10.existsSync(dir)) {
|
|
2434
|
+
this.watcher = fs10.watch(dir, (_eventType, filename) => {
|
|
2112
2435
|
if (filename === "scheduled-tasks.json") {
|
|
2113
2436
|
this.logger.log("info", "Config file changed, reconciling...");
|
|
2114
2437
|
}
|
|
@@ -2260,8 +2583,174 @@ function matchCronField(field, value) {
|
|
|
2260
2583
|
const values = field.split(",");
|
|
2261
2584
|
return values.some((v) => Number.parseInt(v, 10) === value);
|
|
2262
2585
|
}
|
|
2586
|
+
|
|
2587
|
+
// src/skills/SkillCatalogClient.ts
|
|
2588
|
+
var SkillCatalogClient = class {
|
|
2589
|
+
constructor(options = {}) {
|
|
2590
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
2591
|
+
this.baseUrl = options.baseUrl;
|
|
2592
|
+
}
|
|
2593
|
+
async getCatalog() {
|
|
2594
|
+
if (!this.baseUrl) {
|
|
2595
|
+
return {
|
|
2596
|
+
skills: [],
|
|
2597
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
const response = await this.fetchImpl(
|
|
2601
|
+
`${this.baseUrl}/api/v1/marketplace/skills`
|
|
2602
|
+
);
|
|
2603
|
+
if (!response.ok) {
|
|
2604
|
+
throw new Error(`Failed to fetch skills catalog: ${response.status}`);
|
|
2605
|
+
}
|
|
2606
|
+
const data = await response.json();
|
|
2607
|
+
return {
|
|
2608
|
+
skills: data.skills ?? [],
|
|
2609
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
};
|
|
2613
|
+
|
|
2614
|
+
// src/skills/SkillReconciler.ts
|
|
2615
|
+
var SkillReconciler = class {
|
|
2616
|
+
constructor(deps) {
|
|
2617
|
+
this.deps = deps;
|
|
2618
|
+
}
|
|
2619
|
+
async mutate(request) {
|
|
2620
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
2621
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
2622
|
+
}
|
|
2623
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
2624
|
+
return {
|
|
2625
|
+
status: "success",
|
|
2626
|
+
changed: true,
|
|
2627
|
+
message: `Skill ${request.action} completed.`
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
if (request.action !== "install") {
|
|
2631
|
+
return {
|
|
2632
|
+
status: "blocked",
|
|
2633
|
+
changed: false,
|
|
2634
|
+
message: `Skill action is not supported by bridge reconciler: ${request.action}`
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
2638
|
+
const remoteSkill = catalog.skills.find(
|
|
2639
|
+
(skill) => this.deps.skillStore.normalizeRemoteSkillId(skill.id) === request.skillId
|
|
2640
|
+
);
|
|
2641
|
+
if (!remoteSkill) {
|
|
2642
|
+
return {
|
|
2643
|
+
status: "blocked",
|
|
2644
|
+
changed: false,
|
|
2645
|
+
message: "Skill not found in catalog."
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
if (!request.confirmed && (remoteSkill.hasScripts || remoteSkill.hasHooks)) {
|
|
2649
|
+
return {
|
|
2650
|
+
status: "requires_confirmation",
|
|
2651
|
+
changed: false,
|
|
2652
|
+
hasScripts: Boolean(remoteSkill.hasScripts),
|
|
2653
|
+
hasHooks: Boolean(remoteSkill.hasHooks),
|
|
2654
|
+
message: "This skill contains executable scripts that require confirmation."
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
return {
|
|
2658
|
+
status: "success",
|
|
2659
|
+
changed: true,
|
|
2660
|
+
message: "Skill installed."
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
|
|
2665
|
+
// src/skills/SkillStore.ts
|
|
2666
|
+
var fs11 = __toESM(require("fs/promises"));
|
|
2667
|
+
var path9 = __toESM(require("path"));
|
|
2668
|
+
var USER_SKILL_MARKER_FILE = ".ms-devtools-skill.json";
|
|
2669
|
+
var WORKSPACE_SKILLS_ROOT_RELATIVE = ".github/skills";
|
|
2670
|
+
var WORKSPACE_SKILLS_MARKER_RELATIVE = ".github/.ms-devtools-skills.yml";
|
|
2671
|
+
var SAFE_LOCAL_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
2672
|
+
function assertSafeLocalSkillId(skillId) {
|
|
2673
|
+
if (typeof skillId !== "string" || skillId.length === 0 || skillId === "." || skillId === ".." || skillId.includes("/") || skillId.includes("\\") || !SAFE_LOCAL_ID_PATTERN2.test(skillId)) {
|
|
2674
|
+
throw new Error(`Invalid skill id: ${skillId}`);
|
|
2675
|
+
}
|
|
2676
|
+
return skillId;
|
|
2677
|
+
}
|
|
2678
|
+
var SkillStore = class {
|
|
2679
|
+
constructor(options) {
|
|
2680
|
+
this.workspacePath = options.workspacePath;
|
|
2681
|
+
this.userSkillsRoot = options.userSkillsRoot;
|
|
2682
|
+
this.fileSystem = options.fileSystem ?? fs11;
|
|
2683
|
+
}
|
|
2684
|
+
normalizeRemoteSkillId(remoteId) {
|
|
2685
|
+
if (remoteId.startsWith("official/")) {
|
|
2686
|
+
return assertSafeLocalSkillId(remoteId.slice("official/".length));
|
|
2687
|
+
}
|
|
2688
|
+
if (remoteId.startsWith("community/")) {
|
|
2689
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
2690
|
+
return assertSafeLocalSkillId(remoteId.slice(lastSlash + 1));
|
|
2691
|
+
}
|
|
2692
|
+
return assertSafeLocalSkillId(remoteId);
|
|
2693
|
+
}
|
|
2694
|
+
getWorkspaceSkillPath(skillId) {
|
|
2695
|
+
return `${WORKSPACE_SKILLS_ROOT_RELATIVE}/${skillId}`;
|
|
2696
|
+
}
|
|
2697
|
+
getWorkspaceMarkerPath() {
|
|
2698
|
+
return WORKSPACE_SKILLS_MARKER_RELATIVE;
|
|
2699
|
+
}
|
|
2700
|
+
getUserSkillPath(skillId) {
|
|
2701
|
+
return path9.join(this.userSkillsRoot, skillId);
|
|
2702
|
+
}
|
|
2703
|
+
async listWorkspaceSkillIds() {
|
|
2704
|
+
const skillsRootPath = path9.join(
|
|
2705
|
+
this.workspacePath,
|
|
2706
|
+
WORKSPACE_SKILLS_ROOT_RELATIVE
|
|
2707
|
+
);
|
|
2708
|
+
try {
|
|
2709
|
+
const entries = await this.fileSystem.readdir(skillsRootPath, {
|
|
2710
|
+
withFileTypes: true
|
|
2711
|
+
});
|
|
2712
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2713
|
+
} catch {
|
|
2714
|
+
return [];
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
async listUserSkillIds() {
|
|
2718
|
+
try {
|
|
2719
|
+
const entries = await this.fileSystem.readdir(this.userSkillsRoot, {
|
|
2720
|
+
withFileTypes: true
|
|
2721
|
+
});
|
|
2722
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2723
|
+
} catch {
|
|
2724
|
+
return [];
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
async writeManagedUserSkillMarker(skillId) {
|
|
2728
|
+
const targetDir = this.getUserSkillPath(skillId);
|
|
2729
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
2730
|
+
await this.fileSystem.writeFile(
|
|
2731
|
+
path9.join(targetDir, USER_SKILL_MARKER_FILE),
|
|
2732
|
+
JSON.stringify({ skillId, installedBy: "ms-devtools" }, null, 2),
|
|
2733
|
+
"utf-8"
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
async isManagedUserSkill(skillId) {
|
|
2737
|
+
try {
|
|
2738
|
+
const marker = await this.fileSystem.readFile(
|
|
2739
|
+
path9.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
|
|
2740
|
+
"utf-8"
|
|
2741
|
+
);
|
|
2742
|
+
const parsed = JSON.parse(marker);
|
|
2743
|
+
return parsed.skillId === skillId;
|
|
2744
|
+
} catch {
|
|
2745
|
+
return false;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
};
|
|
2263
2749
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2264
2750
|
0 && (module.exports = {
|
|
2751
|
+
AgentCatalogClient,
|
|
2752
|
+
AgentReconciler,
|
|
2753
|
+
AgentStore,
|
|
2265
2754
|
DaemonLogger,
|
|
2266
2755
|
EnvironmentInspector,
|
|
2267
2756
|
GithubCopilotCliExecutor,
|
|
@@ -2271,6 +2760,10 @@ function matchCronField(field, value) {
|
|
|
2271
2760
|
ProjectTools,
|
|
2272
2761
|
SchedulerDaemon,
|
|
2273
2762
|
ShellExecutor,
|
|
2763
|
+
SkillCatalogClient,
|
|
2764
|
+
SkillReconciler,
|
|
2765
|
+
SkillStore,
|
|
2766
|
+
TOOL_RISK_MAP,
|
|
2274
2767
|
TaskConfigManager,
|
|
2275
2768
|
TaskExecutionEngine,
|
|
2276
2769
|
TaskLogManager,
|
|
@@ -2288,6 +2781,7 @@ function matchCronField(field, value) {
|
|
|
2288
2781
|
matchesCron,
|
|
2289
2782
|
moveFiles,
|
|
2290
2783
|
noopLogger,
|
|
2784
|
+
parseAgentToolPermissions,
|
|
2291
2785
|
parseIntervalMs,
|
|
2292
2786
|
resolveTaskExecutionPayload,
|
|
2293
2787
|
unzipFile,
|