@serviceme/devtools-core 0.1.5 → 0.1.7
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 +194 -2
- package/dist/index.d.ts +194 -2
- package/dist/index.js +716 -115
- package/dist/index.mjs +708 -115
- package/package.json +3 -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,8 +71,314 @@ __export(index_exports, {
|
|
|
63
71
|
});
|
|
64
72
|
module.exports = __toCommonJS(index_exports);
|
|
65
73
|
|
|
66
|
-
// src/
|
|
74
|
+
// src/agents/AgentCatalogClient.ts
|
|
67
75
|
var import_devtools_protocol = require("@serviceme/devtools-protocol");
|
|
76
|
+
var AgentCatalogClient = class {
|
|
77
|
+
constructor(options = {}) {
|
|
78
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
79
|
+
this.baseUrl = options.baseUrl;
|
|
80
|
+
}
|
|
81
|
+
async getCatalog() {
|
|
82
|
+
if (!this.baseUrl) {
|
|
83
|
+
return {
|
|
84
|
+
agents: [],
|
|
85
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const response = await this.fetchImpl(
|
|
89
|
+
`${this.baseUrl}/api/v1/marketplace/agents`
|
|
90
|
+
);
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
throw new Error(`Failed to fetch agents catalog: ${response.status}`);
|
|
93
|
+
}
|
|
94
|
+
const data = await response.json();
|
|
95
|
+
return {
|
|
96
|
+
agents: data.agents ?? [],
|
|
97
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async downloadAgent(remoteId) {
|
|
101
|
+
if (!this.baseUrl) {
|
|
102
|
+
throw (0, import_devtools_protocol.createServicemeError)(
|
|
103
|
+
"workspace_not_found",
|
|
104
|
+
"Agent catalog baseUrl is not configured."
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const response = await this.fetchImpl(
|
|
108
|
+
`${this.baseUrl}/api/v1/marketplace/agents/download/${remoteId}`
|
|
109
|
+
);
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
if (response.status === 404) {
|
|
112
|
+
throw (0, import_devtools_protocol.createServicemeError)(
|
|
113
|
+
"not_found",
|
|
114
|
+
`Agent '${remoteId}' not found`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Failed to download agent ${remoteId}: ${response.status}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const payload = await response.json();
|
|
122
|
+
return payload.data?.files ?? [];
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/permissions/agent-permissions.ts
|
|
127
|
+
var TOOL_RISK_MAP = {
|
|
128
|
+
shell: "high",
|
|
129
|
+
terminal: "high",
|
|
130
|
+
run_in_terminal: "high",
|
|
131
|
+
execution_subagent: "high",
|
|
132
|
+
filesystem: "medium",
|
|
133
|
+
fetch: "medium",
|
|
134
|
+
fetch_webpage: "medium",
|
|
135
|
+
create_file: "medium",
|
|
136
|
+
replace_string_in_file: "medium",
|
|
137
|
+
multi_replace_string_in_file: "medium",
|
|
138
|
+
read_file: "low",
|
|
139
|
+
search: "low",
|
|
140
|
+
grep_search: "low",
|
|
141
|
+
file_search: "low",
|
|
142
|
+
semantic_search: "low",
|
|
143
|
+
list_dir: "low"
|
|
144
|
+
};
|
|
145
|
+
var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
146
|
+
var TOOLS_LINE_REGEX = /^tools:\s*$/m;
|
|
147
|
+
var TOOLS_INLINE_REGEX = /^tools:\s*\[([^\]]*)\]/m;
|
|
148
|
+
var LIST_ITEM_REGEX = /^\s*-\s+(.+)$/;
|
|
149
|
+
function parseAgentToolPermissions(content) {
|
|
150
|
+
const fmMatch = content.match(FRONTMATTER_REGEX);
|
|
151
|
+
if (!fmMatch?.[1]) return [];
|
|
152
|
+
const frontmatter = fmMatch[1];
|
|
153
|
+
const inlineMatch = frontmatter.match(TOOLS_INLINE_REGEX);
|
|
154
|
+
if (inlineMatch?.[1] != null) {
|
|
155
|
+
const raw = inlineMatch[1];
|
|
156
|
+
return raw.split(",").map((t) => t.trim()).filter(Boolean).map((tool) => ({
|
|
157
|
+
tool,
|
|
158
|
+
riskLevel: TOOL_RISK_MAP[tool] ?? "medium"
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
const blockMatch = frontmatter.match(TOOLS_LINE_REGEX);
|
|
162
|
+
if (!blockMatch?.[0]) return [];
|
|
163
|
+
const toolsStartIndex = frontmatter.indexOf(blockMatch[0]) + blockMatch[0].length;
|
|
164
|
+
const remaining = frontmatter.slice(toolsStartIndex);
|
|
165
|
+
const lines = remaining.split(/\r?\n/);
|
|
166
|
+
const tools = [];
|
|
167
|
+
for (const line of lines) {
|
|
168
|
+
const itemMatch = line.match(LIST_ITEM_REGEX);
|
|
169
|
+
if (itemMatch?.[1]) {
|
|
170
|
+
const tool = itemMatch[1].trim();
|
|
171
|
+
tools.push({ tool, riskLevel: TOOL_RISK_MAP[tool] ?? "medium" });
|
|
172
|
+
} else if (line.trim() !== "" && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return tools;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/agents/AgentReconciler.ts
|
|
180
|
+
var AgentReconciler = class {
|
|
181
|
+
constructor(deps) {
|
|
182
|
+
this.deps = deps;
|
|
183
|
+
}
|
|
184
|
+
async mutate(request) {
|
|
185
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
186
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
187
|
+
}
|
|
188
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
189
|
+
return {
|
|
190
|
+
status: "success",
|
|
191
|
+
changed: true,
|
|
192
|
+
message: `Agent ${request.action} completed.`
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (request.action !== "install") {
|
|
196
|
+
return {
|
|
197
|
+
status: "blocked",
|
|
198
|
+
changed: false,
|
|
199
|
+
message: `Agent action is not supported by bridge reconciler: ${request.action}`
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
203
|
+
const remoteAgent = catalog.agents.find(
|
|
204
|
+
(agent) => this.deps.agentStore.normalizeRemoteAgentId(agent.id) === request.agentId
|
|
205
|
+
);
|
|
206
|
+
if (!remoteAgent) {
|
|
207
|
+
return {
|
|
208
|
+
status: "blocked",
|
|
209
|
+
changed: false,
|
|
210
|
+
message: "Agent not found in catalog."
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (!request.confirmed && this.hasHighRiskTool(remoteAgent.tools)) {
|
|
214
|
+
return {
|
|
215
|
+
status: "requires_confirmation",
|
|
216
|
+
changed: false,
|
|
217
|
+
message: "This agent uses high-risk tools that require confirmation.",
|
|
218
|
+
tools: remoteAgent.tools
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
status: "success",
|
|
223
|
+
changed: true,
|
|
224
|
+
message: "Agent installed."
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
getPermissionSummary(agentId, agentName, content) {
|
|
228
|
+
const tools = parseAgentToolPermissions(content);
|
|
229
|
+
return {
|
|
230
|
+
agentId,
|
|
231
|
+
agentName,
|
|
232
|
+
tools,
|
|
233
|
+
highRiskCount: tools.filter((tool) => tool.riskLevel === "high").length,
|
|
234
|
+
mediumRiskCount: tools.filter((tool) => tool.riskLevel === "medium").length,
|
|
235
|
+
lowRiskCount: tools.filter((tool) => tool.riskLevel === "low").length
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
hasHighRiskTool(tools) {
|
|
239
|
+
return tools.some((tool) => TOOL_RISK_MAP[tool] === "high");
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/agents/AgentStore.ts
|
|
244
|
+
var fs = __toESM(require("fs/promises"));
|
|
245
|
+
var path = __toESM(require("path"));
|
|
246
|
+
var WORKSPACE_AGENTS_ROOT_RELATIVE = ".github/agents";
|
|
247
|
+
var WORKSPACE_AGENTS_STATE_RELATIVE = ".github/.ms-devtools-agents.yml";
|
|
248
|
+
var SAFE_LOCAL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
249
|
+
function assertSafeLocalAgentId(agentId) {
|
|
250
|
+
if (typeof agentId !== "string" || agentId.length === 0 || agentId === "." || agentId === ".." || agentId.includes("/") || agentId.includes("\\") || !SAFE_LOCAL_ID_PATTERN.test(agentId)) {
|
|
251
|
+
throw new Error(`Invalid agent id: ${agentId}`);
|
|
252
|
+
}
|
|
253
|
+
return agentId;
|
|
254
|
+
}
|
|
255
|
+
var AgentStore = class {
|
|
256
|
+
constructor(options) {
|
|
257
|
+
this.workspacePath = options.workspacePath;
|
|
258
|
+
this.userAgentsRoot = options.userAgentsRoot;
|
|
259
|
+
this.fileSystem = options.fileSystem ?? fs;
|
|
260
|
+
this.schemaVersion = options.schemaVersion ?? 1;
|
|
261
|
+
}
|
|
262
|
+
normalizeRemoteAgentId(remoteId) {
|
|
263
|
+
if (remoteId.startsWith("official/")) {
|
|
264
|
+
return assertSafeLocalAgentId(remoteId.slice("official/".length));
|
|
265
|
+
}
|
|
266
|
+
if (remoteId.startsWith("community/")) {
|
|
267
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
268
|
+
return assertSafeLocalAgentId(remoteId.slice(lastSlash + 1));
|
|
269
|
+
}
|
|
270
|
+
return assertSafeLocalAgentId(remoteId);
|
|
271
|
+
}
|
|
272
|
+
getWorkspaceAgentsRootPath() {
|
|
273
|
+
return WORKSPACE_AGENTS_ROOT_RELATIVE;
|
|
274
|
+
}
|
|
275
|
+
getWorkspaceStateFilePath() {
|
|
276
|
+
return WORKSPACE_AGENTS_STATE_RELATIVE;
|
|
277
|
+
}
|
|
278
|
+
getUserAgentsRootPath() {
|
|
279
|
+
return this.userAgentsRoot;
|
|
280
|
+
}
|
|
281
|
+
async listWorkspaceAgentIds() {
|
|
282
|
+
return this.listAgentIds(
|
|
283
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
async listUserAgentIds() {
|
|
287
|
+
return this.listAgentIds(this.userAgentsRoot);
|
|
288
|
+
}
|
|
289
|
+
async readState() {
|
|
290
|
+
try {
|
|
291
|
+
const raw = await this.fileSystem.readFile(
|
|
292
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE),
|
|
293
|
+
"utf-8"
|
|
294
|
+
);
|
|
295
|
+
const parsed = JSON.parse(raw);
|
|
296
|
+
if (typeof parsed.schemaVersion !== "number" || !Array.isArray(parsed.installedAgents)) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
return parsed;
|
|
300
|
+
} catch {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async writeState(state) {
|
|
305
|
+
const statePath = path.join(
|
|
306
|
+
this.workspacePath,
|
|
307
|
+
WORKSPACE_AGENTS_STATE_RELATIVE
|
|
308
|
+
);
|
|
309
|
+
await this.fileSystem.mkdir(path.dirname(statePath), { recursive: true });
|
|
310
|
+
await this.fileSystem.writeFile(
|
|
311
|
+
statePath,
|
|
312
|
+
JSON.stringify(state, null, 2),
|
|
313
|
+
"utf-8"
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
async addInstalledAgent(entry) {
|
|
317
|
+
const state = await this.readState() ?? {
|
|
318
|
+
schemaVersion: this.schemaVersion,
|
|
319
|
+
installedAgents: []
|
|
320
|
+
};
|
|
321
|
+
state.installedAgents = state.installedAgents.filter(
|
|
322
|
+
(agent) => agent.id !== entry.id
|
|
323
|
+
);
|
|
324
|
+
state.installedAgents.push(entry);
|
|
325
|
+
await this.writeState(state);
|
|
326
|
+
}
|
|
327
|
+
async removeInstalledAgent(agentId) {
|
|
328
|
+
const state = await this.readState();
|
|
329
|
+
if (!state) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
state.installedAgents = state.installedAgents.filter(
|
|
333
|
+
(agent) => agent.id !== agentId
|
|
334
|
+
);
|
|
335
|
+
await this.writeState(state);
|
|
336
|
+
}
|
|
337
|
+
async writeAgentFiles(agentId, scope, files) {
|
|
338
|
+
const root = scope === "workspace" ? path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE) : this.userAgentsRoot;
|
|
339
|
+
const firstFile = files[0];
|
|
340
|
+
const isSingleFlatFile = files.length === 1 && firstFile !== void 0 && firstFile.path === `${agentId}.agent.md` && !firstFile.path.includes("/");
|
|
341
|
+
const targetDir = isSingleFlatFile ? root : path.join(root, agentId);
|
|
342
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
343
|
+
for (const file of files) {
|
|
344
|
+
const filePath = path.join(targetDir, file.path);
|
|
345
|
+
await this.fileSystem.mkdir(path.dirname(filePath), { recursive: true });
|
|
346
|
+
await this.fileSystem.writeFile(filePath, file.content, "utf-8");
|
|
347
|
+
if (file.executable) {
|
|
348
|
+
try {
|
|
349
|
+
await this.fileSystem.chmod(filePath, 493);
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async listAgentIds(dir) {
|
|
356
|
+
try {
|
|
357
|
+
const entries = await this.fileSystem.readdir(dir, {
|
|
358
|
+
withFileTypes: true
|
|
359
|
+
});
|
|
360
|
+
const ids = [];
|
|
361
|
+
for (const entry of entries) {
|
|
362
|
+
if (entry.name.startsWith(".")) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (entry.isDirectory()) {
|
|
366
|
+
ids.push(entry.name);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (entry.isFile() && entry.name.endsWith(".agent.md")) {
|
|
370
|
+
ids.push(entry.name.replace(/\.agent\.md$/, ""));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return ids.sort();
|
|
374
|
+
} catch {
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// src/copilot/doctor.ts
|
|
381
|
+
var import_devtools_protocol2 = require("@serviceme/devtools-protocol");
|
|
68
382
|
|
|
69
383
|
// src/process/runCommand.ts
|
|
70
384
|
var import_node_child_process = require("child_process");
|
|
@@ -208,20 +522,20 @@ async function copilotDoctor() {
|
|
|
208
522
|
return { installed: true, version, authenticated };
|
|
209
523
|
}
|
|
210
524
|
function createCopilotNotInstalledError() {
|
|
211
|
-
return (0,
|
|
212
|
-
|
|
525
|
+
return (0, import_devtools_protocol2.createServicemeError)(
|
|
526
|
+
import_devtools_protocol2.COPILOT_ERROR_CODES.NOT_INSTALLED,
|
|
213
527
|
"GitHub Copilot CLI is not installed. Visit https://docs.github.com/en/copilot/using-github-copilot/using-github-copilot-in-the-command-line to install."
|
|
214
528
|
);
|
|
215
529
|
}
|
|
216
530
|
function createCopilotAuthRequiredError() {
|
|
217
|
-
return (0,
|
|
218
|
-
|
|
531
|
+
return (0, import_devtools_protocol2.createServicemeError)(
|
|
532
|
+
import_devtools_protocol2.COPILOT_ERROR_CODES.AUTH_REQUIRED,
|
|
219
533
|
"GitHub Copilot CLI requires authentication. Run `copilot auth login` to sign in."
|
|
220
534
|
);
|
|
221
535
|
}
|
|
222
536
|
|
|
223
537
|
// src/copilot/prompt.ts
|
|
224
|
-
var
|
|
538
|
+
var import_devtools_protocol3 = require("@serviceme/devtools-protocol");
|
|
225
539
|
var COPILOT_COMMAND2 = "copilot";
|
|
226
540
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
227
541
|
function stripAnsi(text) {
|
|
@@ -255,8 +569,8 @@ async function copilotPrompt(options) {
|
|
|
255
569
|
});
|
|
256
570
|
const output = stripAnsi(result.stdout);
|
|
257
571
|
if (output.includes("I'm sorry, but I cannot assist") && result.stderr?.includes("Stream completed without a response")) {
|
|
258
|
-
throw (0,
|
|
259
|
-
|
|
572
|
+
throw (0, import_devtools_protocol3.createServicemeError)(
|
|
573
|
+
import_devtools_protocol3.COPILOT_ERROR_CODES.AUTH_REQUIRED,
|
|
260
574
|
"Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.",
|
|
261
575
|
{ exitCode: 0, output, stderr: result.stderr }
|
|
262
576
|
);
|
|
@@ -265,8 +579,8 @@ async function copilotPrompt(options) {
|
|
|
265
579
|
} catch (error) {
|
|
266
580
|
const err = error;
|
|
267
581
|
if (err.code === "ETIMEDOUT") {
|
|
268
|
-
throw (0,
|
|
269
|
-
|
|
582
|
+
throw (0, import_devtools_protocol3.createServicemeError)(
|
|
583
|
+
import_devtools_protocol3.COPILOT_ERROR_CODES.TIMEOUT,
|
|
270
584
|
`Copilot prompt timed out after ${String(timeout)}ms.`
|
|
271
585
|
);
|
|
272
586
|
}
|
|
@@ -274,8 +588,8 @@ async function copilotPrompt(options) {
|
|
|
274
588
|
const output = stripAnsi(err.stdout ?? "");
|
|
275
589
|
const stderr = err.stderr ?? err.message ?? "";
|
|
276
590
|
if (output.includes("I'm sorry, but I cannot assist") || stderr.includes("Stream completed without a response")) {
|
|
277
|
-
throw (0,
|
|
278
|
-
|
|
591
|
+
throw (0, import_devtools_protocol3.createServicemeError)(
|
|
592
|
+
import_devtools_protocol3.COPILOT_ERROR_CODES.AUTH_REQUIRED,
|
|
279
593
|
"Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.",
|
|
280
594
|
{ exitCode, output, stderr }
|
|
281
595
|
);
|
|
@@ -283,8 +597,8 @@ async function copilotPrompt(options) {
|
|
|
283
597
|
if (exitCode !== 0 && output) {
|
|
284
598
|
return { output, exitCode };
|
|
285
599
|
}
|
|
286
|
-
throw (0,
|
|
287
|
-
|
|
600
|
+
throw (0, import_devtools_protocol3.createServicemeError)(
|
|
601
|
+
import_devtools_protocol3.COPILOT_ERROR_CODES.EXECUTION_FAILED,
|
|
288
602
|
stderr || `Copilot exited with code ${String(exitCode)}.`,
|
|
289
603
|
{ exitCode, stderr }
|
|
290
604
|
);
|
|
@@ -292,19 +606,25 @@ async function copilotPrompt(options) {
|
|
|
292
606
|
}
|
|
293
607
|
|
|
294
608
|
// src/env/environmentInspector.ts
|
|
295
|
-
var
|
|
609
|
+
var import_devtools_protocol4 = require("@serviceme/devtools-protocol");
|
|
296
610
|
var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
|
|
297
611
|
var TOOL_CHECK_TIMEOUT_MS = {
|
|
298
612
|
nuget: 12e3,
|
|
299
613
|
nvm: 8e3,
|
|
300
|
-
dotnet: 8e3
|
|
614
|
+
dotnet: 8e3,
|
|
615
|
+
// nvm4w/npm shims and globally-installed .cmd tools (pnpm, nrm) are slow to
|
|
616
|
+
// resolve on cold PATHs. Give them extra headroom so the version probe
|
|
617
|
+
// doesn't fall through to the generic 5s default.
|
|
618
|
+
npm: 15e3,
|
|
619
|
+
pnpm: 15e3,
|
|
620
|
+
nrm: 15e3
|
|
301
621
|
};
|
|
302
622
|
var ERROR_CODE_NOT_FOUND = 127;
|
|
303
623
|
var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
|
|
304
624
|
var EnvironmentInspector = class {
|
|
305
625
|
async checkEnvironment() {
|
|
306
626
|
const results = await Promise.all(
|
|
307
|
-
|
|
627
|
+
import_devtools_protocol4.KNOWN_ENVIRONMENT_TOOLS.map(
|
|
308
628
|
async (tool) => [tool, await this.checkTool(tool)]
|
|
309
629
|
)
|
|
310
630
|
);
|
|
@@ -312,7 +632,7 @@ var EnvironmentInspector = class {
|
|
|
312
632
|
}
|
|
313
633
|
async checkTool(toolName) {
|
|
314
634
|
if (!/^[a-zA-Z0-9-]+$/.test(toolName)) {
|
|
315
|
-
throw (0,
|
|
635
|
+
throw (0, import_devtools_protocol4.createServicemeError)("invalid_params", "Invalid tool name.");
|
|
316
636
|
}
|
|
317
637
|
try {
|
|
318
638
|
if (toolName === "nvm") {
|
|
@@ -344,8 +664,29 @@ var EnvironmentInspector = class {
|
|
|
344
664
|
return void 0;
|
|
345
665
|
}
|
|
346
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* On Windows, prefer the `.cmd` shim for npm/pnpm/nrm. nvm4w registers
|
|
669
|
+
* BOTH an extensionless entry (pointing to node.exe) and a `<tool>.cmd`
|
|
670
|
+
* wrapper; `where` returns them in that order, and the extensionless one
|
|
671
|
+
* would just print node's own version.
|
|
672
|
+
*/
|
|
673
|
+
async getToolShimPath(toolName) {
|
|
674
|
+
try {
|
|
675
|
+
const result = await runCommand("where", {
|
|
676
|
+
args: [toolName],
|
|
677
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
678
|
+
});
|
|
679
|
+
const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
680
|
+
const cmdShim = candidates.find(
|
|
681
|
+
(line) => line.toLowerCase().endsWith(".cmd")
|
|
682
|
+
);
|
|
683
|
+
return cmdShim ?? candidates[0];
|
|
684
|
+
} catch {
|
|
685
|
+
return void 0;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
347
688
|
async getToolVersion(toolName) {
|
|
348
|
-
const invocation = this.getVersionInvocation(toolName);
|
|
689
|
+
const invocation = await this.getVersionInvocation(toolName);
|
|
349
690
|
const result = await runCommand(invocation.command, {
|
|
350
691
|
args: invocation.args,
|
|
351
692
|
timeoutMs: this.getToolTimeout(toolName)
|
|
@@ -420,12 +761,19 @@ var EnvironmentInspector = class {
|
|
|
420
761
|
return this.handleToolCheckError(error);
|
|
421
762
|
}
|
|
422
763
|
}
|
|
423
|
-
getVersionInvocation(toolName) {
|
|
764
|
+
async getVersionInvocation(toolName) {
|
|
424
765
|
const isWindows = process.platform === "win32";
|
|
425
766
|
if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
|
|
767
|
+
const shim = await this.getToolShimPath(toolName);
|
|
768
|
+
if (shim) {
|
|
769
|
+
return {
|
|
770
|
+
command: "cmd.exe",
|
|
771
|
+
args: ["/c", shim, "--version"]
|
|
772
|
+
};
|
|
773
|
+
}
|
|
426
774
|
return {
|
|
427
775
|
command: "cmd.exe",
|
|
428
|
-
args: ["/
|
|
776
|
+
args: ["/c", toolName, "--version"]
|
|
429
777
|
};
|
|
430
778
|
}
|
|
431
779
|
const commands = {
|
|
@@ -468,7 +816,7 @@ var EnvironmentInspector = class {
|
|
|
468
816
|
const execError = error;
|
|
469
817
|
const message = execError.message || String(error);
|
|
470
818
|
const code = execError.code;
|
|
471
|
-
const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || code === "ENOENT" || code === ERROR_CODE_NOT_FOUND;
|
|
819
|
+
const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || message.includes("EACCES") || code === "ENOENT" || code === "EACCES" || code === ERROR_CODE_NOT_FOUND;
|
|
472
820
|
const isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes("timed out");
|
|
473
821
|
return {
|
|
474
822
|
installed: false,
|
|
@@ -478,9 +826,9 @@ var EnvironmentInspector = class {
|
|
|
478
826
|
};
|
|
479
827
|
|
|
480
828
|
// src/image/imageTools.ts
|
|
481
|
-
var
|
|
482
|
-
var
|
|
483
|
-
var
|
|
829
|
+
var fs2 = __toESM(require("fs/promises"));
|
|
830
|
+
var path2 = __toESM(require("path"));
|
|
831
|
+
var import_devtools_protocol5 = require("@serviceme/devtools-protocol");
|
|
484
832
|
var SUPPORTED_FORMATS = [
|
|
485
833
|
".jpg",
|
|
486
834
|
".jpeg",
|
|
@@ -500,7 +848,7 @@ var ImageTools = class {
|
|
|
500
848
|
if (!await this.pathExists(filePath)) {
|
|
501
849
|
return { valid: false };
|
|
502
850
|
}
|
|
503
|
-
if (!SUPPORTED_FORMATS.includes(
|
|
851
|
+
if (!SUPPORTED_FORMATS.includes(path2.extname(filePath).toLowerCase())) {
|
|
504
852
|
return { valid: false };
|
|
505
853
|
}
|
|
506
854
|
await this.getInfo(filePath, sharpModulePath);
|
|
@@ -512,7 +860,7 @@ var ImageTools = class {
|
|
|
512
860
|
async getInfo(imagePath, sharpModulePath) {
|
|
513
861
|
const sharp = this.loadSharp(sharpModulePath);
|
|
514
862
|
const metadata = await sharp(imagePath).metadata();
|
|
515
|
-
const stats = await
|
|
863
|
+
const stats = await fs2.stat(imagePath);
|
|
516
864
|
return {
|
|
517
865
|
width: metadata.width ?? 0,
|
|
518
866
|
height: metadata.height ?? 0,
|
|
@@ -524,12 +872,12 @@ var ImageTools = class {
|
|
|
524
872
|
async compress(imagePath, options) {
|
|
525
873
|
const validation = await this.validate(imagePath, options.sharpModulePath);
|
|
526
874
|
if (!validation.valid) {
|
|
527
|
-
throw (0,
|
|
875
|
+
throw (0, import_devtools_protocol5.createServicemeError)(
|
|
528
876
|
"invalid_params",
|
|
529
877
|
`Invalid image file: ${imagePath}`
|
|
530
878
|
);
|
|
531
879
|
}
|
|
532
|
-
const originalStats = await
|
|
880
|
+
const originalStats = await fs2.stat(imagePath);
|
|
533
881
|
const originalSize = originalStats.size;
|
|
534
882
|
const outputPath = this.getOutputPath(imagePath, options);
|
|
535
883
|
const compressedBuffer = await this.compressWithSharp(imagePath, options);
|
|
@@ -544,7 +892,7 @@ var ImageTools = class {
|
|
|
544
892
|
outputPath: imagePath
|
|
545
893
|
};
|
|
546
894
|
}
|
|
547
|
-
await
|
|
895
|
+
await fs2.writeFile(outputPath, compressedBuffer);
|
|
548
896
|
return {
|
|
549
897
|
originalSize,
|
|
550
898
|
compressedSize,
|
|
@@ -555,7 +903,7 @@ var ImageTools = class {
|
|
|
555
903
|
async compressWithSharp(imagePath, options) {
|
|
556
904
|
const sharp = this.loadSharp(options.sharpModulePath);
|
|
557
905
|
let pipeline = sharp(imagePath);
|
|
558
|
-
switch (options.format ??
|
|
906
|
+
switch (options.format ?? path2.extname(imagePath).toLowerCase().slice(1)) {
|
|
559
907
|
case "jpg":
|
|
560
908
|
case "jpeg":
|
|
561
909
|
pipeline = pipeline.jpeg({ quality: options.quality });
|
|
@@ -581,14 +929,14 @@ var ImageTools = class {
|
|
|
581
929
|
if (options.replaceOriginImage) {
|
|
582
930
|
return inputPath;
|
|
583
931
|
}
|
|
584
|
-
const dir =
|
|
585
|
-
const ext =
|
|
586
|
-
const name =
|
|
587
|
-
return
|
|
932
|
+
const dir = path2.dirname(inputPath);
|
|
933
|
+
const ext = path2.extname(inputPath);
|
|
934
|
+
const name = path2.basename(inputPath, ext);
|
|
935
|
+
return path2.join(dir, `${name}_compressed${ext}`);
|
|
588
936
|
}
|
|
589
937
|
async pathExists(targetPath) {
|
|
590
938
|
try {
|
|
591
|
-
await
|
|
939
|
+
await fs2.access(targetPath);
|
|
592
940
|
return true;
|
|
593
941
|
} catch {
|
|
594
942
|
return false;
|
|
@@ -598,7 +946,7 @@ var ImageTools = class {
|
|
|
598
946
|
try {
|
|
599
947
|
return sharpModulePath ? require(sharpModulePath) : require("sharp");
|
|
600
948
|
} catch (error) {
|
|
601
|
-
throw (0,
|
|
949
|
+
throw (0, import_devtools_protocol5.createServicemeError)(
|
|
602
950
|
"internal_error",
|
|
603
951
|
`sharp is required for image operations: ${error instanceof Error ? error.message : String(error)}`
|
|
604
952
|
);
|
|
@@ -610,13 +958,13 @@ function createImageTools() {
|
|
|
610
958
|
}
|
|
611
959
|
|
|
612
960
|
// src/json/jsonTools.ts
|
|
613
|
-
var
|
|
961
|
+
var import_devtools_protocol6 = require("@serviceme/devtools-protocol");
|
|
614
962
|
var import_comment_json = require("comment-json");
|
|
615
963
|
var import_json5 = __toESM(require("json5"));
|
|
616
964
|
function createJsonTools() {
|
|
617
965
|
return {
|
|
618
966
|
sort(text, options) {
|
|
619
|
-
const finalOptions = { ...
|
|
967
|
+
const finalOptions = { ...import_devtools_protocol6.DEFAULT_JSON_SORT_OPTIONS, ...options };
|
|
620
968
|
const json = parseJson(text);
|
|
621
969
|
const sorted = sortObject(json, finalOptions);
|
|
622
970
|
return JSON.stringify(sorted, null, detectIndent(text));
|
|
@@ -652,7 +1000,7 @@ function parseJson(text) {
|
|
|
652
1000
|
} catch {
|
|
653
1001
|
}
|
|
654
1002
|
}
|
|
655
|
-
throw (0,
|
|
1003
|
+
throw (0, import_devtools_protocol6.createServicemeError)("json_invalid_input", "Invalid JSON format.");
|
|
656
1004
|
}
|
|
657
1005
|
function sortObject(obj, options) {
|
|
658
1006
|
if (Array.isArray(obj)) {
|
|
@@ -744,9 +1092,9 @@ function createConsoleLogger(prefix = "serviceme") {
|
|
|
744
1092
|
}
|
|
745
1093
|
|
|
746
1094
|
// src/project/projectTools.ts
|
|
747
|
-
var
|
|
748
|
-
var
|
|
749
|
-
var
|
|
1095
|
+
var fs3 = __toESM(require("fs/promises"));
|
|
1096
|
+
var path3 = __toESM(require("path"));
|
|
1097
|
+
var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
|
|
750
1098
|
|
|
751
1099
|
// src/utils/fileUtils.ts
|
|
752
1100
|
var import_node_fs = require("fs");
|
|
@@ -866,10 +1214,10 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
866
1214
|
var ProjectTools = class {
|
|
867
1215
|
async extractTemplate(zipPath, workspacePath, tempExtractDir, input) {
|
|
868
1216
|
await unzipFile(zipPath, tempExtractDir);
|
|
869
|
-
let sourceDir =
|
|
1217
|
+
let sourceDir = path3.join(tempExtractDir, input.extractedDirName);
|
|
870
1218
|
let actualDirName = input.extractedDirName;
|
|
871
1219
|
if (!await this.pathExists(sourceDir)) {
|
|
872
|
-
const entries = await
|
|
1220
|
+
const entries = await fs3.readdir(tempExtractDir, { withFileTypes: true });
|
|
873
1221
|
const directories = entries.filter(
|
|
874
1222
|
(entry) => entry.isDirectory() && !entry.name.startsWith(".")
|
|
875
1223
|
);
|
|
@@ -881,7 +1229,7 @@ var ProjectTools = class {
|
|
|
881
1229
|
);
|
|
882
1230
|
if (selectedDirectory) {
|
|
883
1231
|
actualDirName = selectedDirectory;
|
|
884
|
-
sourceDir =
|
|
1232
|
+
sourceDir = path3.join(tempExtractDir, actualDirName);
|
|
885
1233
|
} else if (directories.length === 0) {
|
|
886
1234
|
throw new Error(
|
|
887
1235
|
`No directory found after extraction. Expected directory: ${input.extractedDirName}`
|
|
@@ -902,7 +1250,7 @@ var ProjectTools = class {
|
|
|
902
1250
|
}
|
|
903
1251
|
async installDependencies(workspacePath, command) {
|
|
904
1252
|
if (!command) {
|
|
905
|
-
throw (0,
|
|
1253
|
+
throw (0, import_devtools_protocol7.createServicemeError)("invalid_params", "Expected install command.");
|
|
906
1254
|
}
|
|
907
1255
|
await runCommand(command, {
|
|
908
1256
|
cwd: workspacePath,
|
|
@@ -939,7 +1287,7 @@ var ProjectTools = class {
|
|
|
939
1287
|
} else {
|
|
940
1288
|
for (const scriptPath of scripts) {
|
|
941
1289
|
try {
|
|
942
|
-
await
|
|
1290
|
+
await fs3.chmod(scriptPath, 493);
|
|
943
1291
|
updatedCount += 1;
|
|
944
1292
|
} catch {
|
|
945
1293
|
}
|
|
@@ -965,13 +1313,16 @@ var ProjectTools = class {
|
|
|
965
1313
|
}
|
|
966
1314
|
async runScaffoldPrune(workspacePath, preset) {
|
|
967
1315
|
if (!preset) {
|
|
968
|
-
throw (0,
|
|
1316
|
+
throw (0, import_devtools_protocol7.createServicemeError)("invalid_params", "Expected scaffold preset.");
|
|
969
1317
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
];
|
|
1318
|
+
await this.ensurePresetManifest(workspacePath, preset);
|
|
1319
|
+
const pruneCommand = `pnpm run scaffold:prune -- --preset ${preset} --project-root .`;
|
|
1320
|
+
const initMetadataCommand = `pnpm run scaffold:init-metadata -- --preset ${preset} --project-root . --force`;
|
|
1321
|
+
const commands = [pruneCommand, initMetadataCommand];
|
|
974
1322
|
for (const command of commands) {
|
|
1323
|
+
if (command === initMetadataCommand) {
|
|
1324
|
+
await this.ensurePresetManifest(workspacePath, preset);
|
|
1325
|
+
}
|
|
975
1326
|
await runCommand(command, {
|
|
976
1327
|
cwd: workspacePath,
|
|
977
1328
|
shell: true
|
|
@@ -983,16 +1334,54 @@ var ProjectTools = class {
|
|
|
983
1334
|
commands
|
|
984
1335
|
};
|
|
985
1336
|
}
|
|
1337
|
+
async ensurePresetManifest(workspacePath, preset) {
|
|
1338
|
+
const presetManifestPath = path3.join(
|
|
1339
|
+
workspacePath,
|
|
1340
|
+
".ms-scaffold",
|
|
1341
|
+
"presets",
|
|
1342
|
+
`${preset}.json`
|
|
1343
|
+
);
|
|
1344
|
+
if (await this.pathExists(presetManifestPath)) {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const projectModePath = path3.join(
|
|
1348
|
+
workspacePath,
|
|
1349
|
+
".ms-scaffold",
|
|
1350
|
+
"project-mode.json"
|
|
1351
|
+
);
|
|
1352
|
+
if (!await this.pathExists(projectModePath)) {
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
const projectModeRaw = await fs3.readFile(projectModePath, "utf8");
|
|
1356
|
+
const projectMode = JSON.parse(projectModeRaw);
|
|
1357
|
+
const synthesizedPreset = {
|
|
1358
|
+
preset,
|
|
1359
|
+
selectedModules: Array.isArray(projectMode.selectedModules) ? projectMode.selectedModules : [],
|
|
1360
|
+
prunedModules: Array.isArray(projectMode.prunedModules) ? projectMode.prunedModules : [],
|
|
1361
|
+
exclude: [],
|
|
1362
|
+
recommendedFollowUps: [],
|
|
1363
|
+
managedFiles: [],
|
|
1364
|
+
mergeManagedFiles: [],
|
|
1365
|
+
userOwnedPaths: []
|
|
1366
|
+
};
|
|
1367
|
+
await fs3.mkdir(path3.dirname(presetManifestPath), { recursive: true });
|
|
1368
|
+
await fs3.writeFile(
|
|
1369
|
+
presetManifestPath,
|
|
1370
|
+
`${JSON.stringify(synthesizedPreset, null, 2)}
|
|
1371
|
+
`,
|
|
1372
|
+
"utf8"
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
986
1375
|
async findScripts(dir, extensions) {
|
|
987
1376
|
const results = [];
|
|
988
1377
|
let entries;
|
|
989
1378
|
try {
|
|
990
|
-
entries = await
|
|
1379
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
991
1380
|
} catch {
|
|
992
1381
|
return results;
|
|
993
1382
|
}
|
|
994
1383
|
for (const entry of entries) {
|
|
995
|
-
const fullPath =
|
|
1384
|
+
const fullPath = path3.join(dir, entry.name);
|
|
996
1385
|
if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
997
1386
|
results.push(...await this.findScripts(fullPath, extensions));
|
|
998
1387
|
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -1003,7 +1392,7 @@ var ProjectTools = class {
|
|
|
1003
1392
|
}
|
|
1004
1393
|
async pathExists(targetPath) {
|
|
1005
1394
|
try {
|
|
1006
|
-
await
|
|
1395
|
+
await fs3.access(targetPath);
|
|
1007
1396
|
return true;
|
|
1008
1397
|
} catch {
|
|
1009
1398
|
return false;
|
|
@@ -1022,7 +1411,7 @@ var ProjectTools = class {
|
|
|
1022
1411
|
const matches = [];
|
|
1023
1412
|
for (const directoryName of directoryNames) {
|
|
1024
1413
|
if (await this.directoryMatchesProjectPattern(
|
|
1025
|
-
|
|
1414
|
+
path3.join(tempExtractDir, directoryName),
|
|
1026
1415
|
projectFilePattern
|
|
1027
1416
|
)) {
|
|
1028
1417
|
matches.push(directoryName);
|
|
@@ -1034,7 +1423,7 @@ var ProjectTools = class {
|
|
|
1034
1423
|
return null;
|
|
1035
1424
|
}
|
|
1036
1425
|
async directoryMatchesProjectPattern(directoryPath, projectFilePattern) {
|
|
1037
|
-
const entries = await
|
|
1426
|
+
const entries = await fs3.readdir(directoryPath);
|
|
1038
1427
|
if (projectFilePattern.includes("*")) {
|
|
1039
1428
|
const regex = new RegExp(`^${projectFilePattern.replace("*", ".*")}$`);
|
|
1040
1429
|
return entries.some((entry) => regex.test(entry));
|
|
@@ -1047,17 +1436,17 @@ function createProjectTools() {
|
|
|
1047
1436
|
}
|
|
1048
1437
|
|
|
1049
1438
|
// src/scheduled-tasks/daemon/DaemonLogger.ts
|
|
1050
|
-
var
|
|
1051
|
-
var
|
|
1439
|
+
var fs4 = __toESM(require("fs"));
|
|
1440
|
+
var path4 = __toESM(require("path"));
|
|
1052
1441
|
var CONFIG_DIR = ".serviceme";
|
|
1053
1442
|
var LOG_FILE = "scheduler.log";
|
|
1054
1443
|
var MAX_LOG_SIZE = 1024 * 1024;
|
|
1055
1444
|
var DaemonLogger = class {
|
|
1056
1445
|
constructor(workspacePath) {
|
|
1057
|
-
this.logPath =
|
|
1058
|
-
const dir =
|
|
1059
|
-
if (!
|
|
1060
|
-
|
|
1446
|
+
this.logPath = path4.join(workspacePath, CONFIG_DIR, LOG_FILE);
|
|
1447
|
+
const dir = path4.dirname(this.logPath);
|
|
1448
|
+
if (!fs4.existsSync(dir)) {
|
|
1449
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1061
1450
|
}
|
|
1062
1451
|
}
|
|
1063
1452
|
getLogPath() {
|
|
@@ -1068,16 +1457,16 @@ var DaemonLogger = class {
|
|
|
1068
1457
|
const line = `[${ts}] [${level.toUpperCase()}] ${message}
|
|
1069
1458
|
`;
|
|
1070
1459
|
this.rotateIfNeeded();
|
|
1071
|
-
|
|
1460
|
+
fs4.appendFileSync(this.logPath, line, "utf-8");
|
|
1072
1461
|
}
|
|
1073
1462
|
rotateIfNeeded() {
|
|
1074
1463
|
try {
|
|
1075
|
-
const stats =
|
|
1464
|
+
const stats = fs4.statSync(this.logPath);
|
|
1076
1465
|
if (stats.size > MAX_LOG_SIZE) {
|
|
1077
|
-
const content =
|
|
1466
|
+
const content = fs4.readFileSync(this.logPath, "utf-8");
|
|
1078
1467
|
const halfIdx = content.indexOf("\n", Math.floor(content.length / 2));
|
|
1079
1468
|
if (halfIdx > 0) {
|
|
1080
|
-
|
|
1469
|
+
fs4.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
|
|
1081
1470
|
}
|
|
1082
1471
|
}
|
|
1083
1472
|
} catch {
|
|
@@ -1086,33 +1475,33 @@ var DaemonLogger = class {
|
|
|
1086
1475
|
};
|
|
1087
1476
|
|
|
1088
1477
|
// src/scheduled-tasks/daemon/PidManager.ts
|
|
1089
|
-
var
|
|
1090
|
-
var
|
|
1478
|
+
var fs5 = __toESM(require("fs"));
|
|
1479
|
+
var path5 = __toESM(require("path"));
|
|
1091
1480
|
var CONFIG_DIR2 = ".serviceme";
|
|
1092
1481
|
var PID_FILE = "scheduler.pid";
|
|
1093
1482
|
var PidManager = class {
|
|
1094
1483
|
constructor(workspacePath) {
|
|
1095
|
-
this.pidPath =
|
|
1484
|
+
this.pidPath = path5.join(workspacePath, CONFIG_DIR2, PID_FILE);
|
|
1096
1485
|
}
|
|
1097
1486
|
getPidPath() {
|
|
1098
1487
|
return this.pidPath;
|
|
1099
1488
|
}
|
|
1100
1489
|
writePid(pid) {
|
|
1101
|
-
const dir =
|
|
1102
|
-
if (!
|
|
1103
|
-
|
|
1490
|
+
const dir = path5.dirname(this.pidPath);
|
|
1491
|
+
if (!fs5.existsSync(dir)) {
|
|
1492
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1104
1493
|
}
|
|
1105
|
-
|
|
1494
|
+
fs5.writeFileSync(this.pidPath, String(pid), "utf-8");
|
|
1106
1495
|
}
|
|
1107
1496
|
readPid() {
|
|
1108
|
-
if (!
|
|
1109
|
-
const raw =
|
|
1497
|
+
if (!fs5.existsSync(this.pidPath)) return null;
|
|
1498
|
+
const raw = fs5.readFileSync(this.pidPath, "utf-8").trim();
|
|
1110
1499
|
const pid = Number.parseInt(raw, 10);
|
|
1111
1500
|
return Number.isNaN(pid) ? null : pid;
|
|
1112
1501
|
}
|
|
1113
1502
|
removePid() {
|
|
1114
|
-
if (
|
|
1115
|
-
|
|
1503
|
+
if (fs5.existsSync(this.pidPath)) {
|
|
1504
|
+
fs5.unlinkSync(this.pidPath);
|
|
1116
1505
|
}
|
|
1117
1506
|
}
|
|
1118
1507
|
isProcessRunning(pid) {
|
|
@@ -1133,12 +1522,12 @@ var PidManager = class {
|
|
|
1133
1522
|
};
|
|
1134
1523
|
|
|
1135
1524
|
// src/scheduled-tasks/daemon/SchedulerDaemon.ts
|
|
1136
|
-
var
|
|
1525
|
+
var fs10 = __toESM(require("fs"));
|
|
1137
1526
|
var os = __toESM(require("os"));
|
|
1138
1527
|
|
|
1139
1528
|
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1140
1529
|
var import_node_child_process2 = require("child_process");
|
|
1141
|
-
var
|
|
1530
|
+
var fs6 = __toESM(require("fs"));
|
|
1142
1531
|
|
|
1143
1532
|
// src/scheduled-tasks/executors/timeout.ts
|
|
1144
1533
|
function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
|
|
@@ -1173,7 +1562,7 @@ function redactArgs(args) {
|
|
|
1173
1562
|
function writeDiagnostic(message) {
|
|
1174
1563
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1175
1564
|
if (logPath) {
|
|
1176
|
-
|
|
1565
|
+
fs6.appendFileSync(logPath, message);
|
|
1177
1566
|
return;
|
|
1178
1567
|
}
|
|
1179
1568
|
process.stderr.write(message);
|
|
@@ -1383,15 +1772,15 @@ ${body}`.trim()
|
|
|
1383
1772
|
|
|
1384
1773
|
// src/scheduled-tasks/executors/ShellExecutor.ts
|
|
1385
1774
|
var import_node_child_process3 = require("child_process");
|
|
1386
|
-
var
|
|
1387
|
-
var
|
|
1775
|
+
var fs7 = __toESM(require("fs"));
|
|
1776
|
+
var path6 = __toESM(require("path"));
|
|
1388
1777
|
var MAX_OUTPUT_BYTES2 = 1024 * 1024;
|
|
1389
1778
|
var DEFAULT_TIMEOUT_MS4 = 6e4;
|
|
1390
1779
|
var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
|
|
1391
1780
|
function resolveShellExecution(script, options = {}) {
|
|
1392
1781
|
const platform = options.platform ?? process.platform;
|
|
1393
1782
|
const env = options.env ?? process.env;
|
|
1394
|
-
const fileExists = options.fileExists ??
|
|
1783
|
+
const fileExists = options.fileExists ?? fs7.existsSync;
|
|
1395
1784
|
if (platform === "win32") {
|
|
1396
1785
|
const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
|
|
1397
1786
|
if (posixShell) {
|
|
@@ -1436,10 +1825,10 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1436
1825
|
if (fileExists(candidate)) return candidate;
|
|
1437
1826
|
}
|
|
1438
1827
|
const pathValue = env.Path ?? env.PATH ?? "";
|
|
1439
|
-
for (const dir of pathValue.split(
|
|
1828
|
+
for (const dir of pathValue.split(path6.win32.delimiter)) {
|
|
1440
1829
|
if (!dir) continue;
|
|
1441
1830
|
for (const executable of POSIX_SHELL_CANDIDATES) {
|
|
1442
|
-
const candidate =
|
|
1831
|
+
const candidate = path6.win32.join(dir, executable);
|
|
1443
1832
|
if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
|
|
1444
1833
|
return candidate;
|
|
1445
1834
|
}
|
|
@@ -1448,13 +1837,13 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1448
1837
|
return null;
|
|
1449
1838
|
}
|
|
1450
1839
|
function isWindowsWslLauncher(candidate) {
|
|
1451
|
-
const normalized =
|
|
1840
|
+
const normalized = path6.win32.normalize(candidate).toLowerCase();
|
|
1452
1841
|
return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
|
|
1453
1842
|
}
|
|
1454
1843
|
function writeDiagnostic2(message) {
|
|
1455
1844
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1456
1845
|
if (logPath) {
|
|
1457
|
-
|
|
1846
|
+
fs7.appendFileSync(logPath, message);
|
|
1458
1847
|
return;
|
|
1459
1848
|
}
|
|
1460
1849
|
process.stderr.write(message);
|
|
@@ -1601,9 +1990,9 @@ function getExecutor(taskType) {
|
|
|
1601
1990
|
|
|
1602
1991
|
// src/scheduled-tasks/TaskConfigManager.ts
|
|
1603
1992
|
var import_node_crypto = require("crypto");
|
|
1604
|
-
var
|
|
1605
|
-
var
|
|
1606
|
-
var
|
|
1993
|
+
var fs8 = __toESM(require("fs"));
|
|
1994
|
+
var path7 = __toESM(require("path"));
|
|
1995
|
+
var import_devtools_protocol8 = require("@serviceme/devtools-protocol");
|
|
1607
1996
|
var CONFIG_DIR3 = ".serviceme";
|
|
1608
1997
|
var CONFIG_FILE = "scheduled-tasks.json";
|
|
1609
1998
|
function emptyConfig() {
|
|
@@ -1614,7 +2003,7 @@ function isRecord(value) {
|
|
|
1614
2003
|
}
|
|
1615
2004
|
function requireNonEmptyString(payload, field, taskType) {
|
|
1616
2005
|
if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
|
|
1617
|
-
throw (0,
|
|
2006
|
+
throw (0, import_devtools_protocol8.createServicemeError)(
|
|
1618
2007
|
"invalid_payload",
|
|
1619
2008
|
`${taskType} payload ${field} is required`
|
|
1620
2009
|
);
|
|
@@ -1639,27 +2028,27 @@ function validateTaskPayload(taskType, payload) {
|
|
|
1639
2028
|
}
|
|
1640
2029
|
var TaskConfigManager = class {
|
|
1641
2030
|
constructor(workspacePath) {
|
|
1642
|
-
this.configPath =
|
|
2031
|
+
this.configPath = path7.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
|
|
1643
2032
|
}
|
|
1644
2033
|
getConfigPath() {
|
|
1645
2034
|
return this.configPath;
|
|
1646
2035
|
}
|
|
1647
2036
|
readConfig() {
|
|
1648
|
-
if (!
|
|
2037
|
+
if (!fs8.existsSync(this.configPath)) {
|
|
1649
2038
|
return emptyConfig();
|
|
1650
2039
|
}
|
|
1651
|
-
const raw =
|
|
2040
|
+
const raw = fs8.readFileSync(this.configPath, "utf-8");
|
|
1652
2041
|
const parsed = JSON.parse(raw);
|
|
1653
2042
|
return parsed;
|
|
1654
2043
|
}
|
|
1655
2044
|
writeConfig(config) {
|
|
1656
|
-
const dir =
|
|
1657
|
-
if (!
|
|
1658
|
-
|
|
2045
|
+
const dir = path7.dirname(this.configPath);
|
|
2046
|
+
if (!fs8.existsSync(dir)) {
|
|
2047
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1659
2048
|
}
|
|
1660
2049
|
const tmp = `${this.configPath}.tmp`;
|
|
1661
|
-
|
|
1662
|
-
|
|
2050
|
+
fs8.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
|
|
2051
|
+
fs8.renameSync(tmp, this.configPath);
|
|
1663
2052
|
}
|
|
1664
2053
|
listTasks() {
|
|
1665
2054
|
return this.readConfig().tasks;
|
|
@@ -1930,8 +2319,8 @@ var TaskExecutionEngine = class {
|
|
|
1930
2319
|
|
|
1931
2320
|
// src/scheduled-tasks/TaskLogManager.ts
|
|
1932
2321
|
var import_node_crypto2 = require("crypto");
|
|
1933
|
-
var
|
|
1934
|
-
var
|
|
2322
|
+
var fs9 = __toESM(require("fs"));
|
|
2323
|
+
var path8 = __toESM(require("path"));
|
|
1935
2324
|
var CONFIG_DIR4 = ".serviceme";
|
|
1936
2325
|
var LOG_FILE2 = "scheduled-tasks-log.json";
|
|
1937
2326
|
var MAX_LOGS = 200;
|
|
@@ -1956,17 +2345,17 @@ function validateAndRepairLogFile(raw) {
|
|
|
1956
2345
|
}
|
|
1957
2346
|
var TaskLogManager = class {
|
|
1958
2347
|
constructor(workspacePath) {
|
|
1959
|
-
this.logPath =
|
|
2348
|
+
this.logPath = path8.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
|
|
1960
2349
|
}
|
|
1961
2350
|
getLogPath() {
|
|
1962
2351
|
return this.logPath;
|
|
1963
2352
|
}
|
|
1964
2353
|
readLogFile() {
|
|
1965
|
-
if (!
|
|
2354
|
+
if (!fs9.existsSync(this.logPath)) {
|
|
1966
2355
|
return emptyLogFile();
|
|
1967
2356
|
}
|
|
1968
2357
|
try {
|
|
1969
|
-
const raw =
|
|
2358
|
+
const raw = fs9.readFileSync(this.logPath, "utf-8");
|
|
1970
2359
|
const parsed = JSON.parse(raw);
|
|
1971
2360
|
const file = validateAndRepairLogFile(parsed);
|
|
1972
2361
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
|
|
@@ -1982,21 +2371,21 @@ var TaskLogManager = class {
|
|
|
1982
2371
|
}
|
|
1983
2372
|
backupCorruptedFile() {
|
|
1984
2373
|
try {
|
|
1985
|
-
if (
|
|
2374
|
+
if (fs9.existsSync(this.logPath)) {
|
|
1986
2375
|
const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
|
|
1987
|
-
|
|
2376
|
+
fs9.copyFileSync(this.logPath, backupPath);
|
|
1988
2377
|
}
|
|
1989
2378
|
} catch {
|
|
1990
2379
|
}
|
|
1991
2380
|
}
|
|
1992
2381
|
writeLogFile(file) {
|
|
1993
|
-
const dir =
|
|
1994
|
-
if (!
|
|
1995
|
-
|
|
2382
|
+
const dir = path8.dirname(this.logPath);
|
|
2383
|
+
if (!fs9.existsSync(dir)) {
|
|
2384
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1996
2385
|
}
|
|
1997
2386
|
const tmp = `${this.logPath}.tmp`;
|
|
1998
|
-
|
|
1999
|
-
|
|
2387
|
+
fs9.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
|
|
2388
|
+
fs9.renameSync(tmp, this.logPath);
|
|
2000
2389
|
}
|
|
2001
2390
|
appendLog(input) {
|
|
2002
2391
|
const file = this.readLogFile();
|
|
@@ -2107,8 +2496,8 @@ var SchedulerDaemon = class {
|
|
|
2107
2496
|
const configPath = this.configManager.getConfigPath();
|
|
2108
2497
|
const dir = configPath.substring(0, configPath.lastIndexOf("/"));
|
|
2109
2498
|
try {
|
|
2110
|
-
if (
|
|
2111
|
-
this.watcher =
|
|
2499
|
+
if (fs10.existsSync(dir)) {
|
|
2500
|
+
this.watcher = fs10.watch(dir, (_eventType, filename) => {
|
|
2112
2501
|
if (filename === "scheduled-tasks.json") {
|
|
2113
2502
|
this.logger.log("info", "Config file changed, reconciling...");
|
|
2114
2503
|
}
|
|
@@ -2260,8 +2649,215 @@ function matchCronField(field, value) {
|
|
|
2260
2649
|
const values = field.split(",");
|
|
2261
2650
|
return values.some((v) => Number.parseInt(v, 10) === value);
|
|
2262
2651
|
}
|
|
2652
|
+
|
|
2653
|
+
// src/skills/SkillCatalogClient.ts
|
|
2654
|
+
var import_devtools_protocol9 = require("@serviceme/devtools-protocol");
|
|
2655
|
+
var SkillCatalogClient = class {
|
|
2656
|
+
constructor(options = {}) {
|
|
2657
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
2658
|
+
this.baseUrl = options.baseUrl;
|
|
2659
|
+
}
|
|
2660
|
+
async getCatalog() {
|
|
2661
|
+
if (!this.baseUrl) {
|
|
2662
|
+
return {
|
|
2663
|
+
skills: [],
|
|
2664
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
const response = await this.fetchImpl(
|
|
2668
|
+
`${this.baseUrl}/api/v1/marketplace/skills`
|
|
2669
|
+
);
|
|
2670
|
+
if (!response.ok) {
|
|
2671
|
+
throw new Error(`Failed to fetch skills catalog: ${response.status}`);
|
|
2672
|
+
}
|
|
2673
|
+
const data = await response.json();
|
|
2674
|
+
return {
|
|
2675
|
+
skills: data.skills ?? [],
|
|
2676
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
async downloadSkill(remoteId) {
|
|
2680
|
+
if (!this.baseUrl) {
|
|
2681
|
+
throw (0, import_devtools_protocol9.createServicemeError)(
|
|
2682
|
+
"workspace_not_found",
|
|
2683
|
+
"Skill catalog baseUrl is not configured."
|
|
2684
|
+
);
|
|
2685
|
+
}
|
|
2686
|
+
const response = await this.fetchImpl(
|
|
2687
|
+
`${this.baseUrl}/api/v1/marketplace/skills/download/${remoteId}`
|
|
2688
|
+
);
|
|
2689
|
+
if (!response.ok) {
|
|
2690
|
+
if (response.status === 404) {
|
|
2691
|
+
throw (0, import_devtools_protocol9.createServicemeError)(
|
|
2692
|
+
"not_found",
|
|
2693
|
+
`Skill '${remoteId}' not found`
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
throw new Error(
|
|
2697
|
+
`Failed to download skill ${remoteId}: ${response.status}`
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
const payload = await response.json();
|
|
2701
|
+
return payload.data?.files ?? [];
|
|
2702
|
+
}
|
|
2703
|
+
};
|
|
2704
|
+
|
|
2705
|
+
// src/skills/SkillReconciler.ts
|
|
2706
|
+
var SkillReconciler = class {
|
|
2707
|
+
constructor(deps) {
|
|
2708
|
+
this.deps = deps;
|
|
2709
|
+
}
|
|
2710
|
+
async mutate(request) {
|
|
2711
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
2712
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
2713
|
+
}
|
|
2714
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
2715
|
+
return {
|
|
2716
|
+
status: "success",
|
|
2717
|
+
changed: true,
|
|
2718
|
+
message: `Skill ${request.action} completed.`
|
|
2719
|
+
};
|
|
2720
|
+
}
|
|
2721
|
+
if (request.action !== "install") {
|
|
2722
|
+
return {
|
|
2723
|
+
status: "blocked",
|
|
2724
|
+
changed: false,
|
|
2725
|
+
message: `Skill action is not supported by bridge reconciler: ${request.action}`
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
2729
|
+
const remoteSkill = catalog.skills.find(
|
|
2730
|
+
(skill) => this.deps.skillStore.normalizeRemoteSkillId(skill.id) === request.skillId
|
|
2731
|
+
);
|
|
2732
|
+
if (!remoteSkill) {
|
|
2733
|
+
return {
|
|
2734
|
+
status: "blocked",
|
|
2735
|
+
changed: false,
|
|
2736
|
+
message: "Skill not found in catalog."
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
if (!request.confirmed && (remoteSkill.hasScripts || remoteSkill.hasHooks)) {
|
|
2740
|
+
return {
|
|
2741
|
+
status: "requires_confirmation",
|
|
2742
|
+
changed: false,
|
|
2743
|
+
hasScripts: Boolean(remoteSkill.hasScripts),
|
|
2744
|
+
hasHooks: Boolean(remoteSkill.hasHooks),
|
|
2745
|
+
message: "This skill contains executable scripts that require confirmation."
|
|
2746
|
+
};
|
|
2747
|
+
}
|
|
2748
|
+
return {
|
|
2749
|
+
status: "success",
|
|
2750
|
+
changed: true,
|
|
2751
|
+
message: "Skill installed."
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
};
|
|
2755
|
+
|
|
2756
|
+
// src/skills/SkillStore.ts
|
|
2757
|
+
var fs11 = __toESM(require("fs/promises"));
|
|
2758
|
+
var path9 = __toESM(require("path"));
|
|
2759
|
+
var USER_SKILL_MARKER_FILE = ".ms-devtools-skill.json";
|
|
2760
|
+
var WORKSPACE_SKILLS_ROOT_RELATIVE = ".github/skills";
|
|
2761
|
+
var WORKSPACE_SKILLS_MARKER_RELATIVE = ".github/.ms-devtools-skills.yml";
|
|
2762
|
+
var SAFE_LOCAL_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
2763
|
+
function assertSafeLocalSkillId(skillId) {
|
|
2764
|
+
if (typeof skillId !== "string" || skillId.length === 0 || skillId === "." || skillId === ".." || skillId.includes("/") || skillId.includes("\\") || !SAFE_LOCAL_ID_PATTERN2.test(skillId)) {
|
|
2765
|
+
throw new Error(`Invalid skill id: ${skillId}`);
|
|
2766
|
+
}
|
|
2767
|
+
return skillId;
|
|
2768
|
+
}
|
|
2769
|
+
var SkillStore = class {
|
|
2770
|
+
constructor(options) {
|
|
2771
|
+
this.workspacePath = options.workspacePath;
|
|
2772
|
+
this.userSkillsRoot = options.userSkillsRoot;
|
|
2773
|
+
this.fileSystem = options.fileSystem ?? fs11;
|
|
2774
|
+
}
|
|
2775
|
+
normalizeRemoteSkillId(remoteId) {
|
|
2776
|
+
if (remoteId.startsWith("official/")) {
|
|
2777
|
+
return assertSafeLocalSkillId(remoteId.slice("official/".length));
|
|
2778
|
+
}
|
|
2779
|
+
if (remoteId.startsWith("community/")) {
|
|
2780
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
2781
|
+
return assertSafeLocalSkillId(remoteId.slice(lastSlash + 1));
|
|
2782
|
+
}
|
|
2783
|
+
return assertSafeLocalSkillId(remoteId);
|
|
2784
|
+
}
|
|
2785
|
+
getWorkspaceSkillPath(skillId) {
|
|
2786
|
+
return `${WORKSPACE_SKILLS_ROOT_RELATIVE}/${skillId}`;
|
|
2787
|
+
}
|
|
2788
|
+
getWorkspaceMarkerPath() {
|
|
2789
|
+
return WORKSPACE_SKILLS_MARKER_RELATIVE;
|
|
2790
|
+
}
|
|
2791
|
+
getUserSkillPath(skillId) {
|
|
2792
|
+
return path9.join(this.userSkillsRoot, skillId);
|
|
2793
|
+
}
|
|
2794
|
+
async listWorkspaceSkillIds() {
|
|
2795
|
+
const skillsRootPath = path9.join(
|
|
2796
|
+
this.workspacePath,
|
|
2797
|
+
WORKSPACE_SKILLS_ROOT_RELATIVE
|
|
2798
|
+
);
|
|
2799
|
+
try {
|
|
2800
|
+
const entries = await this.fileSystem.readdir(skillsRootPath, {
|
|
2801
|
+
withFileTypes: true
|
|
2802
|
+
});
|
|
2803
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2804
|
+
} catch {
|
|
2805
|
+
return [];
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
async listUserSkillIds() {
|
|
2809
|
+
try {
|
|
2810
|
+
const entries = await this.fileSystem.readdir(this.userSkillsRoot, {
|
|
2811
|
+
withFileTypes: true
|
|
2812
|
+
});
|
|
2813
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2814
|
+
} catch {
|
|
2815
|
+
return [];
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
async writeManagedUserSkillMarker(skillId) {
|
|
2819
|
+
const targetDir = this.getUserSkillPath(skillId);
|
|
2820
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
2821
|
+
await this.fileSystem.writeFile(
|
|
2822
|
+
path9.join(targetDir, USER_SKILL_MARKER_FILE),
|
|
2823
|
+
JSON.stringify({ skillId, installedBy: "ms-devtools" }, null, 2),
|
|
2824
|
+
"utf-8"
|
|
2825
|
+
);
|
|
2826
|
+
}
|
|
2827
|
+
async isManagedUserSkill(skillId) {
|
|
2828
|
+
try {
|
|
2829
|
+
const marker = await this.fileSystem.readFile(
|
|
2830
|
+
path9.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
|
|
2831
|
+
"utf-8"
|
|
2832
|
+
);
|
|
2833
|
+
const parsed = JSON.parse(marker);
|
|
2834
|
+
return parsed.skillId === skillId;
|
|
2835
|
+
} catch {
|
|
2836
|
+
return false;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
async writeSkillFiles(skillId, scope, files) {
|
|
2840
|
+
const root = scope === "workspace" ? path9.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE) : this.userSkillsRoot;
|
|
2841
|
+
const targetDir = path9.join(root, skillId);
|
|
2842
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
2843
|
+
for (const file of files) {
|
|
2844
|
+
const filePath = path9.join(targetDir, file.path);
|
|
2845
|
+
await this.fileSystem.mkdir(path9.dirname(filePath), { recursive: true });
|
|
2846
|
+
await this.fileSystem.writeFile(filePath, file.content, "utf-8");
|
|
2847
|
+
if (file.executable) {
|
|
2848
|
+
try {
|
|
2849
|
+
await this.fileSystem.chmod(filePath, 493);
|
|
2850
|
+
} catch {
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2263
2856
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2264
2857
|
0 && (module.exports = {
|
|
2858
|
+
AgentCatalogClient,
|
|
2859
|
+
AgentReconciler,
|
|
2860
|
+
AgentStore,
|
|
2265
2861
|
DaemonLogger,
|
|
2266
2862
|
EnvironmentInspector,
|
|
2267
2863
|
GithubCopilotCliExecutor,
|
|
@@ -2271,6 +2867,10 @@ function matchCronField(field, value) {
|
|
|
2271
2867
|
ProjectTools,
|
|
2272
2868
|
SchedulerDaemon,
|
|
2273
2869
|
ShellExecutor,
|
|
2870
|
+
SkillCatalogClient,
|
|
2871
|
+
SkillReconciler,
|
|
2872
|
+
SkillStore,
|
|
2873
|
+
TOOL_RISK_MAP,
|
|
2274
2874
|
TaskConfigManager,
|
|
2275
2875
|
TaskExecutionEngine,
|
|
2276
2876
|
TaskLogManager,
|
|
@@ -2288,6 +2888,7 @@ function matchCronField(field, value) {
|
|
|
2288
2888
|
matchesCron,
|
|
2289
2889
|
moveFiles,
|
|
2290
2890
|
noopLogger,
|
|
2891
|
+
parseAgentToolPermissions,
|
|
2291
2892
|
parseIntervalMs,
|
|
2292
2893
|
resolveTaskExecutionPayload,
|
|
2293
2894
|
unzipFile,
|